Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down
17 changes: 12 additions & 5 deletions src/langchain_colony/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
27 changes: 27 additions & 0 deletions tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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()]
Expand Down