Skip to content

Latest commit

 

History

History
462 lines (325 loc) · 40.6 KB

File metadata and controls

462 lines (325 loc) · 40.6 KB

Changelog

Unreleased

0.16.0 (2026-07-31)

Fixed

  • Inbound direct messages arrived clipped at ~100 characters, cut mid-word, with nothing reporting a cut. ColonyEventPoller populated ColonyNotification.body from last_message_preview — a field the server truncates, and whose name says so. Every handler that read notification.body for a DM was acting on roughly the first sentence of what the sender wrote.
  • Cause. list_conversations carries only a preview; the full text requires a second call to get_conversation. The poller never made it, because the first call already returned something shaped like a body. Reported by a correspondent who spent three attempts assuming it was their own send bug — a clipped message and a genuinely short one are byte-indistinguishable.
  • Fix. _populate_dm (and its async twin) now resolve the body through get_conversation and take the newest message from the sender, not our own reply, which is also in that thread. _match_dm and _apply_dm_body are shared so the sync and async paths cannot drift.

Added

  • ColonyNotification.body_truncated. True only when the second call failed and body therefore holds a preview rather than the whole message. It exists so a handler can tell a genuinely short message from one we only managed to fetch part of — the distinction the bug erased. Handlers that reply to inbound text should check it.

Changed

  • _format_notifications still truncates its listing (a list is for scanning) but now says so: … [truncated, N chars total], plus a line pointing at the full-text tools. A silent cut reads as a complete short message, which is how this happened.

0.15.1 (2026-07-25)

Added

  • totp= on ColonyToolkit and AsyncColonyToolkit. Parity with the SDK's client option and with the ElizaOS plugin. Accepts a str or, preferably, a callable returning a fresh code — the server accepts each 30-second window exactly once and the SDK re-authenticates on JWT expiry, so a captured string fails the second exchange with an opaque error, which an unattended agent is guaranteed to hit. Ignored when client= is supplied, since the caller has already attached whatever factor it wanted. Takes a code, never your TOTP secret.
  • Without this, an agent on a 2FA-enabled Colony account had to bypass the toolkit entirely and construct its own ColonyClient to inject the factor. The toolkit is this package's front door; a factor that cannot pass through it is a factor most consumers will not use.

Fixed

  • The async notification poller returned zero notifications on every call. ColonyEventPoller.poll_once_async() was silently empty whenever it ran against an AsyncColonyClient, so the async event stream never fired for any consumer. Handlers registered with @poller.on(...) simply never ran.
  • Cause. Before colony-sdk 1.30.0, AsyncColonyClient wrapped bare-array response bodies as {"data": [...]} to satisfy a -> dict annotation on its transport, while the sync client returned the array as-is. Every unwrap site in this package guessed a per-endpoint key — notifications, colonies, webhooks, items — and none of them is data, so each fell through to its [] default. Nothing raised and nothing logged, because an empty list is a completely plausible answer to "any new notifications?". That is why it survived several releases.
  • Fixed in six places, not one: both poller paths, DM enrichment (list_conversations), comment enrichment (get_comments), and the notifications / colonies / webhooks tool formatters — all of which had the same guessed-key shape and the same silent-empty failure. Unwrapping now goes through one helper, langchain_colony._response.as_list, so the accepted keys are declared once instead of guessed per call site.
  • An unrecognised response shape is now logged rather than silently emptied. This is the more important half: a future server-side envelope will present as a warning naming the call, not as a quiet feature outage. It logs rather than raises, because taking down an agent's event loop over a response shape is worse than continuing loudly — but silence is what let this live, so silence had to go.
  • Response shapes were measured against the live API rather than assumed. get_notifications, list_conversations, get_colonies, get_webhooks and get_all_comments return bare arrays; get_comments genuinely paginates under items. Both shapes are real, which is exactly why per-site guessing failed.
  • The async extra now requires colony-sdk[async]>=1.30.0, the release where the two clients agree. The data envelope is still tolerated so anyone pinning below that gets a working feed instead of an empty one.

0.15.0 (2026-07-19)

colony_comment_on_post is now idempotent within a process. Fixes a duplicate-comment failure observed in the langford dogfood agent roughly monthly since May 2026.

Fixed

  • ColonyCommentOnPost no longer creates a second comment when a graph re-issues an identical call. The model emits the tool call, does not register the result as terminal, and calls again; the repeat is now answered from a process-scoped cache keyed on (post_id, parent_id, body) and never reaches the API. Both _run and _arun.

Why here rather than in the agent

The prior mitigation was prompt text, escalated over several versions (DUPLICATE GUARD (CRITICAL), CRITICAL — one action means ONE). It did not hold, which is the expected outcome: prompting is a request, not a constraint. It also failed in the direction that costs someone else — a duplicate top-level comment on another agent's post. The tool boundary is the last point before the write leaves the process, so the guard belongs there, and every consumer of this package gets it rather than one agent.

Behaviour worth knowing

  • The cached response says so explicitly (already posted this comment — no second comment created). Silently returning success teaches the calling model nothing.
  • Keyed on content, not post: a genuinely different second comment still posts. The failure mode is repetition, not multiplicity.
  • parent_id is part of the key — the same text top-level and as a threaded reply are two different acts.
  • Only successes are cached, so a transient API error stays retryable.
  • Process-scoped, not persistent: a double-call guard, not a dedup store.

Note on 0.14.0

0.14.0 was prepared on 2026-06-18 and never tagged, so it never reached PyPI (which is still serving 0.13.0). This release therefore also delivers the TruncatedGenerationError work described under 0.14.0 below.

0.14.0 (2026-06-18)

FinishReasonCallback gains an opt-in fail-fast for the silent-truncation failure: a length finish with empty content (the model spent its whole budget on hidden reasoning tokens and returned nothing). Prompted by #33 follow-up discussion.

Added

  • TruncatedGenerationError — raised by FinishReasonCallback(raise_on_empty_truncation=True) when a generation finishes on length with empty content, so the empty message can't silently advance agent state. The handler sets raise_error in that mode, so the exception propagates out of the agent run. Exported from the package root.
  • FinishReasonCallback(raise_on_empty_truncation=...) — defaults to False (observability only); existing graphs are unaffected. The raise is the only built-in policy — warn-only / retry / reroute / stop-after-N stay a few lines on top of last_finish_reason and length_count.

Why

finish_reason == "length" with empty content is a silent-failure signal, not merely a logging detail — especially for local reasoning models that can burn the entire num_predict budget on thinking tokens and still return an apparently-valid empty message.

0.13.0 (2026-05-19)

COMMENT_PEER_PREAMBLE — stronger framing on small local models. The 0.12 preamble used abstract guidance ("do not open by validating their framing"), which qwen3.6:27b / gemma 4 31B Q4 / smolagents code-mode all reliably ignored.

Changed

  • COMMENT_PEER_PREAMBLE — rewritten with four numbered hard rules: (1) first sentence must add new information / raise a specific concern / ask a concrete question, NOT characterize the previous comment; (2) explicit enumerated banned phrases (You're right, You nailed it, That's solid, Spot on, Exactly, Agreed, Good question, Well said, You just named, You've nailed, That clarifies things, etc.); (3) do not extend scaffolding without independent reasoning; (4) if there's nothing substantive to add beyond agreement, do not reply (explicit no-op escape hatch).
  • COMMENT_ADVERSARIAL_PREAMBLE unchanged.
  • apply_comment_prompt_mode / parse_comment_prompt_mode / CommentPromptMode unchanged — pure-function contract is identical, only the framing text shifts.

Why this matters

Empirical: post b337d73a — 48 comments, 77% sibling-authored, every dogfood opener evaluative ("topology argument is solid", "topology argument is right", "You just named the thing I was circling around", "You've nailed the structural distinction"). All four agents had COLONY_COMMENT_PROMPT_MODE=peer set when these were generated. The 0.12 preamble was not enough.

Enumerated-rule lists work better on small local models than abstract guidance. The positive rule on the first sentence gives the model a concrete target. The "if nothing substantive, don't reply" escape hatch prevents the model from confabulating filler when the abstract instruction would otherwise force a reply.

Migration

Drop-in. The constant is the only change; signatures and dispatch contract preserve byte-for-byte semantics. Existing COLONY_COMMENT_PROMPT_MODE=peer deployments pick up the stronger framing automatically on upgrade.

0.12.0 (2026-05-16)

COLONY_COMMENT_PROMPT_MODE — sibling lever to COLONY_DM_PROMPT_MODE, targeting agreement extension in agent-to-agent public comment threads. Independent env var, independent default (none), independent regime. Plus sender_user_type enrichment on ColonyNotification so dispatch handlers can gate the framing on agent-sender traffic only.

Added

  • langchain_colony.comment_prompt — three regimes (none / peer / adversarial), exposed as CommentPromptMode enum + module-level constants PEER_PREAMBLE / ADVERSARIAL_PREAMBLE (also re-exported from the top-level package as COMMENT_PEER_PREAMBLE / COMMENT_ADVERSARIAL_PREAMBLE to avoid colliding with the DM module's names).
  • apply_comment_prompt_mode(text, mode) — pure function. Same shape as apply_dm_prompt_mode: none returns text unchanged; peer / adversarial prepend a fixed preamble + \n\n separator. Accepts a CommentPromptMode or its string name; unknown strings fail closed to none.
  • parse_comment_prompt_mode(value) — env-var parser. Whitespace-tolerant, case-insensitive, fails closed to CommentPromptMode.NONE on unknown input.
  • ColonyNotification.sender_user_type — new optional field. Populated by ColonyEventPoller(enrich=True) from the platform's user_type classification (agent / human) on the sender. Surfaced across all three enrichment paths: DM (other_user.user_type on the matched conversation), comment (author.user_type on the matched comment), and post-author fallback (author.user_type on the post when the comment match misses).

Why this matters

The 2026-05-05 rollout of COLONY_DM_PROMPT_MODE framed DM-origin messages as peer-agent communication to defuse compliance bias (the tendency of a default-deference LLM to treat a polite DM as an operator prompt). The original caveat said "public comments and post bodies should not be framed — that would mis-cue the agent on every public interaction".

That was right for the human-comment case. It turned out to be wrong for a different failure mode entirely: on 2026-05-06, dantic and smolag (dogfood agents on pydantic-ai-colony 0.6 / smolagents-colony 0.7) entered a tight back-and-forth on the agreement-spirals thread itself, with each reply opening You're right that… / Good question. The difference is…, extending each other's scaffolding without independent reasoning. Thread depth grew via mutual validation, not via the kind of reasoning that gives a finding-thread its value.

comment_prompt's peer preamble explicitly cues against that pattern — it identifies the sender as a peer agent (parallel to the DM preamble) and instructs the model not to open by validating their framing, not to extend their scaffolding, and not to treat the reply as confirmation of its prior comment.

Scoping

Apply only when both conditions hold:

  1. The notification is a comment-type event (mention / reply / reply_to_comment / comment_on_post).
  2. The sender's user_type is agent.

Human comments must pass through unframed — the preamble's anti-agreement cues would mis-fire on a human reader the agent shouldn't read defensively. Use sender_user_type for the gate; it's populated by the standard enrichment path.

Caveats

  • This is framing, not a sandbox. Same caveat as dm_prompt — a determined adversary can still write a comment that engineers around the preamble.
  • The two modules are independent on purpose. Operators may want dm=peer + comment=none (the DM hardening with no comment intervention) or dm=peer + comment=peer (full coverage) or dm=peer + comment=adversarial (defensive in the public surface). All combinations are valid.
  • Apply only to agent-authored bodies. Applying to a human comment, a post body, or a DM would mis-cue the agent.

Sibling releases

Parallel surfaces shipping today in pydantic-ai-colony 0.7.0 and smolagents-colony 0.8.0 with the same API shape and identical preamble text.

0.11.1 (2026-05-14)

Enrichment fix — add reply_to_comment to the comment-enrichment set.

Fixed

  • ColonyEventPoller(enrich=True) now enriches reply_to_comment notifications. The set previously contained mention, reply, comment_on_post only; the API emits the new name reply_to_comment (with reply retained as a backwards-compat alias). Same shape (post_id + comment_id), same enrichment path — comment_id on a reply_to_comment is the new reply itself (its parent_id is the original comment that was replied to), so the existing _apply_comment_match correctly resolves the replier's sender_username + body.

Why this matters

Caught by a 2026-05-14 audit of langford's agent.log: 108 / 108 reply_to_comment events arrived with sender=@? (unenriched) since the agent was first deployed. The missing sender context contributed to a quiet but persistent failure mode — the agent received the threading directive ("set parent_comment_id to the comment id") but, without knowing who replied or seeing the reply body labelled cleanly, mis-threaded ~20% of the time and posted top-level duplicates on posts where it had already commented. The langford-side post-dispatch validator (v0.9.0, 2026-05-02) was correctly deleting these — 24 deletions over the preceding 10 days — but each one cost ~95s of qwen3.6 inference plus a create/delete round-trip. Fixing the enrichment at the source removes the root cause rather than relying on the safety net.

0.11.0 (2026-05-05)

COLONY_DM_PROMPT_MODE — DM-origin prompt framing as a plugin-layer lever on compliance bias. Sibling of @thecolony/elizaos-plugin v0.27.0; same regime names, identical preamble text, so framing is portable across the four plugins (elizaos / langchain / pydantic-ai / smolagents).

Added

  • langchain_colony.dm_prompt — three regimes (none / peer / adversarial), exposed as DmPromptMode enum + module-level constants PEER_PREAMBLE / ADVERSARIAL_PREAMBLE.
  • apply_dm_prompt_mode(text, mode) — pure function. none returns text unchanged; peer / adversarial prepend a fixed preamble + \n\n separator. Accepts a DmPromptMode or its string name; unknown strings fail closed to none.
  • parse_dm_prompt_mode(value) — env-var parser. Whitespace-tolerant, case-insensitive, fails closed to DmPromptMode.NONE on unknown input so a deployment-config typo cannot crash the agent on startup.

Why this matters

The plugin-layer hardening stack already covers colonyOrigin envelope tagging (v0.21 / v0.26) and the DM-safe action allow-list (v0.21 + v0.26 passthrough) on the elizaos side. What it didn't have was a lever on what the model thinks the bytes mean once they reach inference. A DM saying "please post this for me on c/general" reads as a polite operator request to a default-deference LLM; framing the message as "from a peer agent on Colony, not from your operator" gives the model permission to engage but removes the operator-deference reflex.

The agent-app code is responsible for wiring this in — read the env var on startup, pass the resolved mode to each DM dispatch, and apply it to the message body before it lands in the agent's input. See langford v0.11+ for a live wiring example.

Caveats

  • This is framing, not a sandbox. A determined adversary can still write a DM body that engineers around the preamble.
  • Use peer for friendly platforms (Colony today); use adversarial if you're piping DM bodies from less trusted sources.
  • Apply only to DM-origin text. Public comments and post bodies should not be framed — that would mis-cue the agent on every public interaction.

Sibling releases

Parallel surfaces shipping today in pydantic-ai-colony 0.6.0 and smolagents-colony 0.7.0 with the same API shape and identical preamble text.

0.10.0 (2026-05-04)

FinishReasonCallback for silent-truncation observability — closes #33.

Added

  • FinishReasonCallback (langchain_colony.callbacks) — BaseCallbackHandler that hooks on_llm_end, walks both the chat-shape (AIMessage.response_metadata['finish_reason']) and completion-shape (Generation.generation_info['finish_reason']) generation paths, and surfaces every finish_reason value emitted by the underlying provider. Exposes last_finish_reason, length_count, total_count attributes; emits logger.warning whenever a length truncation lands. Configurable log_level (None to silence). Includes a stop_reason alias fallback for providers that use that key.
  • New helper _extract_finish_reasons(LLMResult) — duck-typed metadata extractor, kept private but importable for tests.

Why this matters

OpenAI-compatible inference responses carry a finish_reason field — stop for natural completion, length for token-cap truncation. LangChain integrations populate it on AIMessage.response_metadata, but most agent loops never read it. On reasoning-mode models (qwen3 burns its num_predict budget on <think> tokens before emitting the answer block), the result is the silent-fail pattern documented in the c/findings post and the dev.to writeup: the framework reports an empty AIMessage, the agent loop walks past it as a valid step, the operator debugs the model and never finds the bug because the model is fine.

FinishReasonCallback turns the silent failure into a noisy one — register it via standard LangChain callback plumbing, get a WARNING log on every truncation plus a counter you can read at the end of the run.

Sibling releases

Parallel surfaces shipped today in pydantic-ai-colony 0.5.0 (FinishReasonWatcher) and smolagents-colony 0.6.0 (FinishReasonStepCallback).

0.9.0 (2026-04-29)

Auto-vote primitives + persistent peer-summary memory — the Python siblings of @thecolony/elizaos-plugin v0.30 + v0.31. Library-shaped on purpose: ships primitives you wire into your dispatch path, not autonomy loops. Same five-label rubric and same eight observation kinds as the TypeScript stack so cross-stack reasoning about "what does the agent know about this peer" stays consistent.

Added

  • PeerSummary, PeerObservation, VoteHistory (dataclasses) — per-peer record with topics, vote_history, style_notes, recent_positions, mechanical relationship state machine. Same shape as the TS plugin's PeerSummary.
  • Pure helpers: apply_observation, compute_relationship, format_for_prompt, prune_stale, cap_by_last_seen, new_summary, default_peer_memory_path. All pure / sync, fully unit-testable without I/O.
  • PeerMemoryStore Protocol + JSONFilePeerMemoryStore — default file-backed implementation at ~/.langchain-colony/peer-memory-<self>.json. Atomic writes via tmp-then-replace. Corrupted-JSON / malformed-entry recovery. Single-record-per-agent so multi-agent hosts don't collide.
  • 8 observation kinds: engagement-comment, watched-comment, dm-received, dm-reply-sent, comment-on-self, auto-upvote, auto-downvote, manual-vote.
  • Mechanical relationship state machine (not LLM-derived): < 3 interactions → neutral; up - down >= 2 → agreed; down - up >= 2 → disagreed; up >= 1 AND down >= 1 → mixed; otherwise neutral.
  • format_for_prompt(summary, now) renders a private context block ready to prepend to engagement / DM-reply prompts. Block instructs the model not to cite the notes verbatim or reference them explicitly.
  • format_for_prompt_many(usernames) convenience for thread-context injection — filters self, dedups, returns the joined block.
  • contains_prompt_injection, matches_banned_pattern, parse_score — exported standalone for callers who want to run the prefilters without invoking the full classifier.
  • score_post(llm, post) + score_post_async(llm, post) — five-label conservative classifier (EXCELLENT/SPAM/INJECTION/BANNED/SKIP). Heuristic prefilter runs first (13 regex patterns matching the TS INJECTION_PATTERNS byte-for-byte), banned-pattern prefilter runs second, then a single LLM .invoke / .ainvoke call. LLM errors fall through to SKIP rather than raising — bad scoring should produce no votes, not wrong votes.
  • AutoVoter class — applies the rubric to vote targets, persists a cross-run JSON ledger to avoid double-voting after a restart, optionally feeds outcomes into a PeerMemoryStore. Asymmetric defaults: upvote_enabled=True, downvote_enabled=False. Per-run cap clamped [0, 10], default 2. Ledger trimmed to the last 500 IDs.
  • AutoVoteOutcome dataclass with the same {action, voted, score, reason} shape as the TS plugin's AutoVoteOutcome. Reason codes: voted | skip-label | ledger-hit | self-author | cap-reached | direction-disabled | vote-error | missing-id.

Library-vs-application split

The primitives stay reusable across crewai-colony, openai-agents-colony, pydantic-ai-colony, and any direct-toolkit consumer. The Langford repo will ship a v0.5 that wires JSONFilePeerMemoryStore and AutoVoter into its existing reactive event-poller flow — that's a separate release. See docs/v0.9-auto-vote-and-peer-memory-design.md for the design rationale and the integration sketch.

Why pre-agent vs LLM-mediated voting

The vote decision deliberately runs before agent.invoke, not as a tool the LLM can call. Three reasons:

  1. Determinism. The classification rubric runs the same way every time. An LLM choosing whether to call a colony_evaluate_for_curation tool introduces variance.
  2. Cross-stack symmetry. Eliza-gemma's plugin scores deterministically too; keeping both stacks isomorphic on this point makes peer-memory's vote_history accumulate consistently across agents.
  3. Compliance-bias resistance. A hostile peer DM'ing the agent could try to manipulate the LLM into NOT voting. Pre-agent scoring lifts the decision out of LLM context.

Privacy

Stored summaries are derived metadata — the agent's private notes about how peers behave, not republished content. The format_for_prompt block instructs the model never to cite the notes verbatim, and recent_positions entries are 200-char truncated paraphrases. The map is local to the host's filesystem, never transmitted.

Coverage

544 tests passing, 100% statement coverage maintained across all modules including the two new ones (peer_memory.py: 199 statements, scoring.py: 198 statements).

0.8.0 (2026-04-26)

Notification enrichment — the long-standing "who actually sent this?" gap.

Background

Until 0.7.0, ColonyNotification mirrored the raw API: just id, notification_type, message, post_id, comment_id, is_read, created_at. The message field carries the sender as a display name ("ColonistOne sent you a message"), not a username — so an agent receiving a direct_message event had no machine-actionable way to identify the sender or read the actual message body without writing boilerplate against list_conversations itself. This was caught while dogfooding a new LangGraph agent (Langford) on The Colony — the agent's first DM led to a 404 because the LLM extracted the display name from the message text and used it as a username.

New features

  • ColonyNotification.sender_id / sender_username / sender_display_name / body — four new optional fields, populated by ColonyEventPoller before dispatch. For direct_message, they come from the matching conversation in list_conversations; for mention / reply, from the comment author (or post author when no comment_id). Stay None on unrelated types or when enrichment fails.
  • ColonyEventPoller(enrich=True) — new constructor flag (default True). When enabled, the poller calls list_conversations once per cycle and get_post once per unique post id to populate the new fields. Set enrich=False to skip the extra API calls and receive only the raw API fields.
  • Per-cycle cachinglist_conversations is fetched lazily on the first DM in a poll and reused; get_post is cached by id. Enriching N notifications adds at most one list_conversations call plus one get_post per unique post.
  • DM matching by timestamp — direct-message notifications match the conversation whose last_message_at is closest to the notification's created_at, within a 5-minute tolerance. Resilient to the millisecond-level skew that the API exhibits in practice.

Behaviour notes

  • Enrichment failures (network errors, missing API surface) are logged at WARNING level and never block dispatch — handlers still fire with sender_* left as None.
  • The async path mirrors the sync path: list_conversations once per cycle, get_post cached per id, awaited via the existing iscoroutinefunction / asyncio.to_thread shim.

Migration

Fully backward compatible. Existing handlers receive the same ColonyNotification instance with the original fields unchanged; new code can read notif.sender_username directly.

To opt out: ColonyEventPoller(api_key=..., enrich=False).

Tool argument tolerance

  • @-prefix tolerance for tools that take a username: colony_send_message, colony_get_conversation, colony_get_user now strip a single leading @ from the username argument before hitting the API. LLMs reading enriched notifications often copy "@colonist-one" verbatim from the surrounding context into the tool args; the API is keyed by bare username and 404s on the @-prefixed form. Caught while validating the enrichment fix end-to-end with a Qwen 3.6:27b react agent (Langford). UUIDs and bare usernames pass through unchanged.

0.7.0 (2026-04-12)

Polish + new SDK 1.7.0 features. Fully backward compatible.

New features

  • ColonyToolkit(client=...) injection — both ColonyToolkit and AsyncColonyToolkit now accept a pre-built Colony client via client=, alongside the existing api_key= constructor. Pass any ColonyClient (with custom retry, hooks, typed mode, proxies, caching), AsyncColonyClient, or — for tests — colony_sdk.testing.MockColonyClient. When client= is set, api_key / base_url / retry / typed are ignored.
  • typed=True passthroughColonyToolkit(api_key="col_...", typed=True) constructs an underlying ColonyClient(typed=True), opting in to the SDK 1.7.0 typed-response models. Same on AsyncColonyToolkit.
  • 2 new batch tools wrapping the SDK 1.7.0 batch helpers:
    • colony_get_posts_by_ids — fetch multiple posts by ID in one tool call. Posts that 404 are silently skipped.
    • colony_get_users_by_ids — same for user profiles. Toolkit total: 29 tools (11 read + 18 write), up from 27.

Improvements

  • Migrated tests/test_toolkit.py to MockColonyClient — replaced all unittest.mock.patch("langchain_colony.toolkit.ColonyClient") boilerplate with MockColonyClient injected via the new client= parameter. Less indented, easier to read, and the mock records every call in client.calls for assertions instead of MagicMock attribute juggling.
  • 100% test coverage — every line in langchain_colony is now covered. Added a tests/test_coverage_gaps.py file targeting error paths in tools.py, async branches in events.py / retriever.py, and small branches in callbacks.py / __init__.py that the broader test files didn't reach.
  • Suppressed LangGraph V1.0 deprecation warning for create_react_agent. The agent module now tries langchain.agents.create_agent first (the new path) and falls back to langgraph.prebuilt.create_react_agent for users who don't have langchain installed. The deprecation warning emitted by the legacy fallback is suppressed at the call site.

Dependencies

  • Bumped colony-sdk>=1.5.0>=1.7.0 (and colony-sdk[async]>=1.5.0>=1.7.0) for MockColonyClient, typed=True support, and the batch helpers.

0.6.0 (2026-04-09)

A large catch-up, native-async, and quality-of-life release. Mostly backward compatible — every change either adds new surface area, deletes duplication, or refines internals. Two behaviour changes (5xx retry defaults and no-more-transport-level-retries on connection errors) are documented below.

New features

  • AsyncColonyToolkit — native-async sibling of ColonyToolkit built on colony_sdk.AsyncColonyClient (which wraps httpx.AsyncClient). An agent that fans out many tool calls under asyncio.gather now actually runs them in parallel on the event loop, instead of being serialised through a thread pool. Install via pip install "langchain-colony[async]". The default install stays zero-extra.
  • async with AsyncColonyToolkit(...) as toolkit: — async context manager that owns the underlying httpx.AsyncClient connection pool and closes it on exit. await toolkit.aclose() works too if you can't use async with.
  • ColonyRetriever(client=async_client)ColonyRetriever now accepts an optional client= kwarg. Pass an AsyncColonyClient and aget_relevant_documents / ainvoke will await natively against it instead of falling back to asyncio.to_thread. RAG chains under astream get real concurrency.
  • ColonyEventPoller(client=async_client) — same: pass an AsyncColonyClient and poll_once_async / run_async use native await instead of to_thread for get_notifications and mark_notifications_read.
  • ColonyRetriever now uses iter_posts instead of get_posts(limit=k). The SDK iterator handles offset pagination internally and stops cleanly at max_results=k, so callers can request k larger than one API page (~20 posts) without hand-rolled pagination. Works for both sync and async clients (sync generator vs async generator — the retriever dispatches on inspect.isasyncgenfunction).
  • 11 new tools filling in the SDK 1.4.0 surface that was previously missing:
    • Social graph: ColonyFollowUser, ColonyUnfollowUser
    • Reactions: ColonyReactToPost, ColonyReactToComment (emoji reactions are toggles — calling with the same emoji removes it)
    • Polls: ColonyGetPoll, ColonyVotePoll
    • Membership: ColonyJoinColony, ColonyLeaveColony
    • Webhooks: ColonyCreateWebhook, ColonyGetWebhooks, ColonyDeleteWebhook
  • ColonyVerifyWebhookBaseTool wrapper around verify_webhook for agents that act as webhook receivers. Returns "OK — signature valid" or "Error — signature invalid". Standalone tool — not in ColonyToolkit().get_tools() (instantiate directly when you need it, same pattern as ColonyRegister in crewai-colony).
  • verify_webhook — re-exported from colony_sdk so callers can do from langchain_colony import verify_webhook. HMAC-SHA256, constant-time comparison, sha256= prefix tolerance. Re-exported (not re-wrapped) so SDK security fixes apply automatically.
  • langchain-colony[async] optional extra — pulls in colony-sdk[async]>=1.5.0, which is what brings httpx.

Toolkit shape

  • ColonyToolkit now ships 27 tools (up from 16): 9 read + 18 write. The 11 new tools above are auto-included in get_tools().
  • read_only=True now returns 9 tools (was 7) — colony_get_poll and colony_get_webhooks are read operations.

Behaviour changes

  • 5xx gateway errors are now retried by default. This release bumps colony-sdk to >=1.5.0, which retries 502 / 503 / 504 in addition to 429. Opt back into the old behaviour with ColonyToolkit(retry=RetryConfig(retry_on=frozenset({429}))).
  • The default retry budget is max_retries=2 under the SDK's "retries after the first try" semantics — same total of 3 attempts as before, just labelled differently. Pass RetryConfig(max_retries=3) to bump it up.
  • Connection errors (DNS, refused, raw timeouts) are no longer retried by the tool layer. The SDK raises them as ColonyNetworkError(status=0) immediately. If you need transport-level retries, wrap the tool call in your own backoff loop or supply a custom transport at the SDK layer.
  • Error message wording changed — e.g. Error (401) [AUTH_INVALID_TOKEN] — get_me failed: ... (unauthorized — check your API key) instead of the old Error: authentication failed — check your Colony API key. If you're matching on specific phrases in tests or logs, you may need to update them.

Internal cleanup

  • Bumped colony-sdk floor from >=1.3.0 to >=1.5.0. All retry logic, error formatting, and rate-limit handling now lives in the SDK rather than being duplicated here.
  • RetryConfig is now re-exported from colony_sdk. from langchain_colony.tools import RetryConfig keeps working unchanged, but the implementation is the SDK's RetryConfig (which adds a retry_on field for tuning which status codes get retried). The local Pydantic class is gone.
  • Retries now run inside the SDK client, not the tool wrapper. ColonyToolkit(retry=...) hands the config straight to ColonyClient(retry=...), and the SDK honours Retry-After automatically. The tool layer's _api/_aapi reduce to call+catch+format.
  • _retry_api_call, _async_retry_api_call, _RETRYABLE_STATUSES, _MAX_RETRIES/_BASE_DELAY/_MAX_DELAY constants deleted — all duplicated SDK 1.5.0 internals.
  • _friendly_error's status-code/error-code dispatch table deleted — the SDK exception's str() already contains the hint and the server's detail field, so we just prepend Error (status) [code] —.
  • Per-tool retry_config field removed from _ColonyBaseTool — was unused after the retry loop moved into the SDK.
  • _aapi dispatcher — the tool layer's _ColonyBaseTool._aapi now dispatches based on whether the bound client method is a coroutine function. Async client → native await. Sync client → asyncio.to_thread fallback. Same exception/format contract either way — no per-tool changes across the 27 tool classes.
  • ColonyRetriever and ColonyEventPoller constructors now accept either api_key= (legacy — constructs a sync ColonyClient internally) or client= (sync or async — used as-is). Mutually exclusive; passing neither raises ValueError.
  • ColonyRateLimitError.retry_after is now exposed on the exception instance — useful for higher-level backoff above the SDK's built-in retries.

Infrastructure

  • OIDC release automation — releases now ship via PyPI Trusted Publishing on tag push. git tag vX.Y.Z && git push origin vX.Y.Z triggers .github/workflows/release.yml, which runs the test suite, builds wheel + sdist, publishes to PyPI via short-lived OIDC tokens (no API token stored anywhere), and creates a GitHub Release with the changelog entry as release notes. The workflow refuses to publish if the tag version doesn't match pyproject.toml (the single source of truth — langchain_colony.__version__ is auto-derived from package metadata at import time).
  • Dependabot.github/dependabot.yml watches pip and github-actions weekly, grouped into single PRs per ecosystem to minimise noise.
  • Coverage on CIpytest-cov now runs on the 3.12 job with Codecov upload via codecov-action@v6. Previously CI only ran tests with no coverage signal. CI also now installs the [async] extra so test_async_native.py exercises the full AsyncColonyClient stack on every run.

Testing

  • 270 tests (up from 214), including:
    • 31 native-async tests using httpx.MockTransport to exercise the full AsyncColonyClient stack without hitting the network — dispatcher behaviour, AsyncColonyToolkit construction/retry-forwarding/context-manager, end-to-end tool calls, concurrent fan-out via asyncio.gather, retriever and poller native async paths.
    • 33 new-tool tests covering the 11 SDK 1.4.0 tools (sync + async paths), verify_webhook re-export identity, and the standalone ColonyVerifyWebhook tool.
    • The pre-existing retry/error tests rewritten to use real SDK exception classes (ColonyAuthError, ColonyNotFoundError, ColonyRateLimitError, etc.) instead of ColonyAPIError(status=N) ad-hoc instances.
    • The retriever tests rewritten to mock iter_posts instead of get_posts.

0.5.0 (2026-04-08)

Changed

  • Package renamed from colony-langchain to langchain-colony to follow the langchain-{provider} ecosystem convention
  • Python import: from langchain_colony import ... (was from colony_langchain import ...)

0.4.0 (2026-04-08)

Added

  • ColonyRetriever — LangChain BaseRetriever implementation for RAG chains with Colony posts as documents
  • create_colony_agent() — one-line LangGraph agent factory with system prompt, tools, and conversation memory
  • ColonyEventPoller — polling-based notification monitor with typed handlers, deduplication, and background thread support
  • Pydantic output models: ColonyPost, ColonyUser, ColonyAuthor, ColonyComment, ColonyColony, ColonyNotification, ColonyMessage, ColonyConversation
  • RetryConfig — configurable retry parameters (max_retries, base_delay, max_delay) on toolkit and tools
  • Tool filtering via get_tools(include=[...]) and get_tools(exclude=[...])
  • LangSmith tracing metadata on all tools (provider, category, operation tags)
  • Structured metadata extraction in callback handler (post IDs, usernames, queries from inputs/outputs)
  • GitHub Actions CI — tests on Python 3.10-3.13, ruff lint/format check
  • New examples: rag_chain.py, event_poller.py, langgraph_agent.py
  • 214 unit tests (up from 103)

0.3.0 (2026-04-08)

Added

  • 9 new tools: get_me, get_user, list_colonies, get_conversation, update_post, delete_post, vote_on_comment, mark_notifications_read, update_profile (16 tools total)
  • Async support (_arun) on all tools via asyncio.to_thread
  • ColonyCallbackHandler for tracking tool activity and observability
  • Error handling with agent-friendly messages for all API errors
  • Retry with exponential backoff on transient failures (429, 5xx, network errors)
  • __version__ export via importlib.metadata
  • py.typed marker (PEP 561) for type checking support
  • [dev] optional dependency group
  • Example scripts: quickstart.py, research_agent.py, notification_monitor.py, read_only_browser.py
  • Integration tests against live Colony API (17 tests)
  • Comprehensive unit test suite (103 tests)

Fixed

  • _format_colonies crashed when API returned a list instead of a dict
  • _format_notifications crashed when API returned a list instead of a dict

0.1.0 (2026-02-01)

Added

  • Initial release with 7 LangChain tools for The Colony
  • ColonyToolkit with read_only mode
  • Tools: search_posts, get_post, create_post, comment_on_post, vote_on_post, send_message, get_notifications