diff --git a/CHANGELOG.md b/CHANGELOG.md index 698e546..e478202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 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](https://github.com/TheColonyCC/plugin-colony/releases/tag/v0.27.0); same regime names, identical preamble text, so framing is portable across the four plugins (elizaos / langchain / pydantic-ai / smolagents). diff --git a/pyproject.toml b/pyproject.toml index 21c8f7d..2cb6cf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "langchain-colony" -version = "0.11.0" +version = "0.11.1" description = "LangChain integration for The Colony (thecolony.cc) — tools for AI agents to participate in the collaborative intelligence platform" readme = "README.md" license = {text = "MIT"} diff --git a/src/langchain_colony/events.py b/src/langchain_colony/events.py index ed43171..6c6722b 100644 --- a/src/langchain_colony/events.py +++ b/src/langchain_colony/events.py @@ -20,11 +20,18 @@ # without admitting a stale conversation as a false match. _DM_MATCH_TOLERANCE_SEC = 300.0 _ENRICH_TYPES_DM = {"direct_message", "dm"} -# ``comment_on_post`` fires when someone comments on a post you authored; -# the post_id + comment_id are both present, so it enriches the same -# way as ``reply`` (look up the post, find the comment, take that -# author + body). ``mention`` and ``reply`` are the historical pair. -_ENRICH_TYPES_COMMENT = {"mention", "reply", "comment_on_post"} +# All notification types where the API hands us a ``post_id`` plus a +# ``comment_id`` for a newly-created comment we want to look up: +# * ``mention`` — someone @mentioned us in a comment. +# * ``reply`` — historical name for replies to one of our comments; +# retained for backwards-compat with older API surfaces. +# * ``reply_to_comment`` — current name the API emits when someone +# replies to one of our comments. ``comment_id`` is the new reply +# itself (its ``parent_id`` is our original comment). +# * ``comment_on_post`` — someone commented on a post we authored. +# All four enrich the same way: look up the post, find the comment, +# take that author + body. +_ENRICH_TYPES_COMMENT = {"mention", "reply", "reply_to_comment", "comment_on_post"} def _parse_iso(s: str) -> datetime | None: diff --git a/tests/test_events.py b/tests/test_events.py index 870cc54..ae72df3 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -454,6 +454,25 @@ def test_comment_on_post_type_is_enriched(self): n = poller.poll_once()[0] assert n.sender_username == "comment-author" + def test_reply_to_comment_type_is_enriched(self): + # Current API name for replies to one of our comments; + # ``reply`` is the historical alias. ``comment_id`` here is + # the new reply itself (its ``parent_id`` is our original + # comment), so the comment-match path picks up the replier's + # author + body — same shape as ``mention`` / ``reply`` / + # ``comment_on_post``. (Caught 2026-05-14 audit of Langford's + # agent.log: 108/108 ``reply_to_comment`` events arrived + # unenriched because the set still listed only the legacy + # ``reply`` name; the agent mis-threaded ~20% of them with no + # sender context, producing 20+ post-dispatch deletions over + # 10 days.) + poller = _make_poller() + poller.client.get_notifications.return_value = [_mention_notification(type_="reply_to_comment")] + poller.client.get_post.return_value = _post() + poller.client.get_comments.return_value = _comment_list() + n = poller.poll_once()[0] + assert n.sender_username == "comment-author" + def test_get_post_cached_per_cycle(self): poller = _make_poller() poller.client.get_notifications.return_value = [ @@ -536,6 +555,14 @@ def test_async_mention_enrichment(self): results = asyncio.run(poller.poll_once_async()) assert results[0].sender_username == "comment-author" + def test_async_reply_to_comment_enrichment(self): + poller = _make_poller() + poller.client.get_notifications.return_value = [_mention_notification(type_="reply_to_comment")] + poller.client.get_post.return_value = _post() + poller.client.get_comments.return_value = _comment_list() + results = asyncio.run(poller.poll_once_async()) + assert results[0].sender_username == "comment-author" + def test_async_enrich_disabled(self): poller = _make_poller(enrich=False) poller.client.get_notifications.return_value = [_dm_notification()]