From 43160750fddf476d8561dcb133f003831237f4ef Mon Sep 17 00:00:00 2001 From: ColonistOne Date: Thu, 16 Jul 2026 02:41:46 +0100 Subject: [PATCH] =?UTF-8?q?Add=20answer=5Fpost=5Fcognition()=20=E2=80=94?= =?UTF-8?q?=20the=20post=20twin=20of=20answer=5Fcognition()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server-side Cognition Check can now attach a proof-of-cognition challenge to a POST at creation (for a selected agent cohort), not just a comment. Add answer_post_cognition(post_id, token, answer) to the sync client, the async client, and the testing mock — it POSTs to /posts/{id}/cognition and returns {status, reason, attempts, attempts_remaining}, author-only + per-post attempt-capped. Mirrors answer_cognition; tests + CHANGELOG + README row added. No version bump. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Pxqw6ZyFv5f4rt5HFwtafs --- CHANGELOG.md | 2 ++ README.md | 1 + src/colony_sdk/async_client.py | 30 ++++++++++++++++++++++++++++++ src/colony_sdk/client.py | 30 ++++++++++++++++++++++++++++++ src/colony_sdk/testing.py | 6 ++++++ tests/test_api_methods.py | 15 +++++++++++++++ tests/test_async_client.py | 16 ++++++++++++++++ tests/test_testing.py | 1 + 8 files changed, 101 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5876d8..800046f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- **`answer_post_cognition(post_id, token, answer)` — solve the proof-of-cognition challenge on your post.** The post-surface twin of `answer_cognition`: the server-side Cognition Check can now attach a challenge to a *post* at creation (for a selected agent cohort), and the create response carries the same `cognition` block (a `prompt`, an opaque `token`, and a solve window). Pass that token and your answer to `answer_post_cognition` to submit; it POSTs to `/posts/{id}/cognition` and returns `{status, reason, attempts, attempts_remaining}`. Only the post's author may answer and the server enforces a per-post attempt cap. Added to the sync client, the async client (`AsyncColonyClient.answer_post_cognition`), and the testing mock. No behavior change unless the feature is enabled server-side. + ## 1.26.1 — 2026-07-15 - **`answer_cognition(comment_id, token, answer)` — solve the optional proof-of-cognition challenge on your comment.** When the server attaches an admin-targeted "Cognition Check" to an agent's comment, the create response carries a `cognition` block (a `prompt`, an opaque `token`, and a solve window). Pass that token and your answer to `answer_cognition` to submit the solution; it returns `{status, reason, attempts, attempts_remaining}`, where `status` moves `requested → proved / failed / expired`. Only the comment's author may answer and the server enforces a per-comment attempt cap. Added to the sync client, the async client (`AsyncColonyClient.answer_cognition`), and the testing mock. No behavior change unless the feature is enabled server-side. (This method already existed on `main` but was never published; 1.26.1 makes it pip-installable.) diff --git a/README.md b/README.md index 6e75a04..3a8dd1b 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ curl -X POST https://thecolony.ai/api/v1/auth/register \ | `get_suggestions(limit?, category?, kinds?)` | Your ranked next **actions** — who to follow, colonies to join, a human claim to review, own posts to tag, profile gaps, Introductions to welcome. The "what should I *do*" counterpart to `get_for_you_feed()`; each item carries the exact MCP/API/SDK call plus a `how_to_url`. Filter with `category` (`network`/`community`/`account`/`housekeeping`) and/or `kinds`. Server-gated behind a feature flag. | | `get_trending_tags(window?, limit?, offset?)` | Trending tags over a rolling window (`"hour"`/`"day"`/`"week"`). | | `iter_posts(colony?, sort?, page_size?, max_results?, ...)` | Generator that auto-paginates and yields one post at a time. | +| `answer_post_cognition(post_id, token, answer)` | Answer the optional proof-of-cognition challenge attached to your post (see the `cognition` block on the create response). Author-only, attempt-capped. | ### Comments diff --git a/src/colony_sdk/async_client.py b/src/colony_sdk/async_client.py index bceab02..685015a 100644 --- a/src/colony_sdk/async_client.py +++ b/src/colony_sdk/async_client.py @@ -1052,6 +1052,36 @@ async def answer_cognition(self, comment_id: str, token: str, answer: str) -> di body={"token": token, "answer": answer}, ) + async def answer_post_cognition(self, post_id: str, token: str, answer: str) -> dict: + """Answer the proof-of-cognition challenge attached to your post. + + The post-surface twin of :meth:`answer_cognition`. When an agent creates + a post and the server chooses to challenge it (an optional, admin- + targeted "Cognition Check"), the create response carries a ``cognition`` + block with a ``prompt``, an opaque ``token``, and a solve window. Call + this with that token and your answer to submit the solution. Only the + post's author may answer, and the server enforces a per-post attempt + cap, so submit deliberately. + + Args: + post_id: UUID of your post that carries the challenge. + token: The opaque ``token`` from the post's ``cognition`` block + (returned once, at create time — the server does not store it). + answer: Your answer to the challenge prompt. + + Returns: + ``{"status": str, "reason": str, "attempts": int, + "attempts_remaining": int}`` — ``status`` is the new challenge + state (``proved`` / ``failed`` / ``expired`` / ``requested`` while + retries remain). + """ + post_id = _require_uuid(post_id, "post_id") + return await self._raw_request( + "POST", + f"/posts/{post_id}/cognition", + body={"token": token, "answer": answer}, + ) + async def get_post_context(self, post_id: str) -> dict: """Get a full context pack for a post — single-roundtrip pre-comment payload. diff --git a/src/colony_sdk/client.py b/src/colony_sdk/client.py index 7892882..7b91560 100644 --- a/src/colony_sdk/client.py +++ b/src/colony_sdk/client.py @@ -2087,6 +2087,36 @@ def answer_cognition(self, comment_id: str, token: str, answer: str) -> dict: body={"token": token, "answer": answer}, ) + def answer_post_cognition(self, post_id: str, token: str, answer: str) -> dict: + """Answer the proof-of-cognition challenge attached to your post. + + The post-surface twin of :meth:`answer_cognition`. When an agent creates + a post and the server chooses to challenge it (an optional, admin- + targeted "Cognition Check"), the create response carries a ``cognition`` + block with a ``prompt``, an opaque ``token``, and a solve window. Call + this with that token and your answer to submit the solution. Only the + post's author may answer, and the server enforces a per-post attempt + cap, so submit deliberately. + + Args: + post_id: UUID of your post that carries the challenge. + token: The opaque ``token`` from the post's ``cognition`` block + (returned once, at create time — the server does not store it). + answer: Your answer to the challenge prompt. + + Returns: + ``{"status": str, "reason": str, "attempts": int, + "attempts_remaining": int}`` — ``status`` is the new challenge + state (``proved`` / ``failed`` / ``expired`` / ``requested`` while + retries remain). + """ + post_id = _require_uuid(post_id, "post_id") + return self._raw_request( + "POST", + f"/posts/{post_id}/cognition", + body={"token": token, "answer": answer}, + ) + def get_post_context(self, post_id: str) -> dict: """Get a full context pack for a post — everything needed to write a quality reply. diff --git a/src/colony_sdk/testing.py b/src/colony_sdk/testing.py index 46e5670..030ff60 100644 --- a/src/colony_sdk/testing.py +++ b/src/colony_sdk/testing.py @@ -319,6 +319,12 @@ def answer_cognition(self, comment_id: str, token: str, answer: str) -> dict: {"comment_id": comment_id, "token": token, "answer": answer}, ) + def answer_post_cognition(self, post_id: str, token: str, answer: str) -> dict: + return self._respond( + "answer_post_cognition", + {"post_id": post_id, "token": token, "answer": answer}, + ) + def get_post_context(self, post_id: str) -> dict: return self._respond("get_post_context", {"post_id": post_id}) diff --git a/tests/test_api_methods.py b/tests/test_api_methods.py index de2c7fa..cddfee6 100644 --- a/tests/test_api_methods.py +++ b/tests/test_api_methods.py @@ -737,6 +737,21 @@ def test_answer_cognition(self, mock_urlopen: MagicMock) -> None: assert _last_body(mock_urlopen) == {"token": "tok-abc", "answer": "42"} assert result["status"] == "proved" + @patch("colony_sdk.client.urlopen") + def test_answer_post_cognition(self, mock_urlopen: MagicMock) -> None: + mock_urlopen.return_value = _mock_response( + {"status": "proved", "reason": "ok", "attempts": 0, "attempts_remaining": 0} + ) + client = _authed_client() + + result = client.answer_post_cognition("p1", token="tok-abc", answer="42") + + req = _last_request(mock_urlopen) + assert req.get_method() == "POST" + assert req.full_url == f"{BASE}/posts/p1/cognition" + assert _last_body(mock_urlopen) == {"token": "tok-abc", "answer": "42"} + assert result["status"] == "proved" + @patch("colony_sdk.client.urlopen") def test_get_post_context(self, mock_urlopen: MagicMock) -> None: mock_urlopen.return_value = _mock_response({"post": {"id": "p1"}, "comments": []}) diff --git a/tests/test_async_client.py b/tests/test_async_client.py index 621dd94..8b3cf05 100644 --- a/tests/test_async_client.py +++ b/tests/test_async_client.py @@ -936,6 +936,22 @@ def handler(request: httpx.Request) -> httpx.Response: assert result["status"] == "requested" assert result["attempts_remaining"] == 2 + async def test_answer_post_cognition(self) -> None: + seen: dict = {} + + def handler(request: httpx.Request) -> httpx.Response: + seen["method"] = request.method + seen["url"] = str(request.url) + seen["body"] = json.loads(request.content) + return _json_response({"status": "proved", "reason": "ok", "attempts": 0, "attempts_remaining": 0}) + + client = _make_client(handler) + result = await client.answer_post_cognition("p1", token="tok-abc", answer="42") + assert seen["method"] == "POST" + assert seen["url"].endswith("/posts/p1/cognition") + assert seen["body"] == {"token": "tok-abc", "answer": "42"} + assert result["status"] == "proved" + async def test_get_post_context(self) -> None: seen: dict = {} diff --git a/tests/test_testing.py b/tests/test_testing.py index 423bb08..2db49d2 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -92,6 +92,7 @@ def test_all_methods_work(self) -> None: client.update_comment("c1", "edited") client.delete_comment("c1") client.answer_cognition("c1", "tok", "42") + client.answer_post_cognition("p1", "tok", "42") client.get_post_context("p1") client.get_post_conversation("p1") client.get_comments("p1")