Skip to content

feat: bots ask structured questions that people answer from a card - #258

Open
sercada wants to merge 1 commit into
openclaw:mainfrom
sercada:feat/agent-questions
Open

sercada wants to merge 1 commit into
openclaw:mainfrom
sercada:feat/agent-questions

Conversation

@sercada

@sercada sercada commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor
Maintainer edits

MUST: Keep Allow edits from maintainers enabled for this PR so maintainers
can help update the branch when needed.

What Problem This Solves

Agents connected to ClickClack can only ask for input as plain text, so people
reply in free-form messages that the agent has to interpret, with no deadline,
no way to say who should answer, and no record of what was decided.

User Impact

User impact: a bot can attach a question to a channel message, direct message,
or thread reply. People answer from a card in the conversation: one tap for a
single choice, or a short form with up to five questions, multi-select,
"Other..." answers, free text, number-key shortcuts, and Skip. The bot is told
through a question.submitted event, reads the answer, and records the outcome
(answered, skipped, cancelled, expired, not delivered, or reopened with a note).
The card then stays in history as a compact receipt. Clients that do not render
questions keep showing the message body. The change adds one table in both
stores (new migration, no backfill).

Why This Change Was Made

  • The question is a facet of an ordinary bot message, so history, search,
    notifications, threads, pins, and older clients keep working with the body as
    fallback.
  • The server validates every answer against the question and the first valid
    answer wins through a version-guarded update. Events carry identifiers only;
    bots read the answers through normal message access.
  • The asking bot owns the outcome, including reopening a rejected answer with a
    note, and GET /api/bots/self/questions lets a runtime settle questions after
    a restart. X-ClickClack-Questions: supported on create responses tells
    clients the server stored the question.
  • Questions keep the existing token boundaries. The reconciliation list needs
    messages:read and includes direct-message questions only when the token
    also has dms:read, the same rule as reading those messages. Pages go up to
    200 with a working cursor.
  • Answers carry expected_version, the version the person saw. After a lost
    response and a reopen, a retry or an old tab gets 409 and the card reloads
    instead of answering the reopened question blind. The web app always sends
    it; the SDK accepts it as an option.
  • A create retry with the same nonce returns the original question, even when
    its deadline is now inside the minimum lifetime. The deadline and responder
    rules apply only when a question is first created.
  • Questions attach only to ordinary messages. Agent activity rows fold into
    progress blocks and never render a card, so kind: agent_* with a question
    returns 400.
  • Two small web fixes came out of the end-to-end test: number keys on a focused
    answer choice were redirected to the composer by type-to-focus after the first
    press, and a card that grows at the bottom of the channel (a reopened
    question) pushed its controls out of view. Controls can now declare the keys
    they handle with data-shortcut-keys, and the message list re-pins to the
    bottom when a question changes version.

The contract is documented in docs/features/questions.md. The OpenClaw
clickclack channel plugin can use it for the agent ask_user tool; that
change is openclaw/openclaw#147027.

The new migrations (sqlite/0043, postgres/0036) share numbers with the
channel deletion migrations in #257. Migrations apply by file name, so both work
together; whichever PR lands second can renumber to keep the sequence tidy.

Evidence

Screenshots from a local build (dark theme):

Form with an "Other..." answer, receipt above Waiting for the bot; a question only Dana can answer
form waiting
Reopened with the bot's note; answer the bot could not use Thread question at its deadline
reopened expired

Real server, token boundary. A branch build on fresh SQLite, driven over HTTP.
Asker is a bot with three tokens; Riley is a member. IDs and tokens are
replaced by names.

Transcript: DM reconciliation per token, revoked token, stale version, replay near the deadline, activity rows
# Branch build, fresh SQLite data. Asker is a bot; Riley is a workspace member; <owner> is the dev session.
# Asker holds three tokens: bot:write (includes dms:read), messages:read only, and bot:write + agent_activity:write.

$ Asker asks in #orders
  POST /api/channels/<orders>/messages
  -> 201 {"message": {"id": "<q-ship>", "question": {"status": "open", "version": 1}}}
$ Asker asks Riley in their DM
  POST /api/dms/<dm asker+riley>/messages
  -> 201 {"message": {"id": "<q-dm>", "question": {"status": "open", "version": 1}}}

## Reconciliation list keeps DM questions behind dms:read
$ bot:write token lists its open questions
  GET /api/bots/self/questions
  -> 200 {"questions": [{"message_id": "<q-ship>", "channel_id": "<orders>", "status": "open", "version": 1}, {"message_id": "<q-dm>", "direct_conversation_id": "<dm asker+riley>", "status": "open", "version": 1}], "next_cursor": null}
$ messages:read-only token lists its open questions
  GET /api/bots/self/questions
  -> 200 {"questions": [{"message_id": "<q-ship>", "channel_id": "<orders>", "status": "open", "version": 1}], "next_cursor": null}
$ a page above 200 is rejected
  GET /api/bots/self/questions?limit=201
  -> 400 {"error": "limit must be between 1 and 200"}
$ owner revokes the messages:read token
  POST /api/bot-tokens/<read-only token id>/revoke
  -> 200 {"bot_token": {"name": "read only", "scopes": ["messages:read"], "revoked_at": "set"}}
$ the revoked token lists again
  GET /api/bots/self/questions
  -> 401 {"error": "sql: no rows in result set"}

## expected_version stops a stale tab from answering a reopened question
$ Riley answers version 1
  POST /api/messages/<q-ship>/question/answers
  -> 200 {"message": {"id": "<q-ship>", "question": {"status": "submitted", "version": 2}}}
$ Asker reopens it (the answer did not fit)
  POST /api/messages/<q-ship>/question/resolution
  -> 200 {"message": {"id": "<q-ship>", "question": {"status": "open", "version": 3}}}
$ Riley's stale tab submits again with version 2
  POST /api/messages/<q-ship>/question/answers
  -> 409 {"error": "question changed; reload it and try again"}
$ Riley answers the reopened version 3
  POST /api/messages/<q-ship>/question/answers
  -> 200 {"message": {"id": "<q-ship>", "question": {"status": "submitted", "version": 4}}}

## A nonce replay returns the original question even inside the minimum lifetime
$ Asker asks with 14 s left and nonce ask-pickup
  POST /api/channels/<orders>/messages
  -> 201 {"message": {"id": "<q-pickup>", "question": {"status": "open", "version": 1}}}
# 6 s later only 8 s remain, under the 10 s minimum for a new question
$ Asker's retry replays the same body and nonce
  POST /api/channels/<orders>/messages
  -> 200 {"message": {"id": "<q-pickup>", "question": {"status": "open", "version": 1}}}
$ a new question with 8 s left is rejected
  POST /api/channels/<orders>/messages
  -> 400 {"error": "invalid question: expires_at must be between 10s and 168h0m0s from now"}

## Questions attach only to ordinary messages
$ activity row with a question
  POST /api/channels/<orders>/messages
  -> 400 {"error": "questions attach only to ordinary messages"}

The same script against the previous head of this PR (e070bee4) shows the
review findings as they were: the messages:read-only token also listed
<q-dm>; Riley's stale tab answered the reopened question (200, version 4)
and the current tab then got 409 question is no longer open; the retry with
8 s left got 400 expires_at must be between 10s and 168h0m0s; and the activity
row with a question was stored (201). The fifth finding, limit=200 returning
100 rows and no cursor, is covered by
TestBotQuestionListingKeepsDirectScopesAndPages below.

Populated upgrade, SQLite and Postgres. main (eeefa041) creates a channel,
a thread, a DM, a bot and its token, and receives a question it does not know
(stored as a plain message). The branch build then serves the same database:
the migration applies, both histories hash the same before and after, the old
bot token keeps working, main's CLI (an older client) still reads the channel
and thread including the new question messages by their body, and questions in
the existing channel, thread and DM are answered, skipped and resolved.

Transcript: SQLite upgrade
# sqlite: populate with main (eeefa041), then serve the same database with the branch build

## main build
$ owner posts in #orders
  POST /api/channels/<orders>/messages
  -> 201 {"message": {"id": "<root>", "body": "Order 118 is ready for packing", "question": null}}
$ Riley replies in the thread
  POST /api/messages/<root>/thread/replies
  -> 201 {"message": {"id": "<old reply>", "body": "Packing starts at 9", "question": null}}
$ Asker posts in #orders
  POST /api/channels/<orders>/messages
  -> 201 {"message": {"id": "<old bot message>", "body": "Tracking will follow", "question": null}}
$ Asker writes to Riley
  POST /api/dms/<dm asker+riley>/messages
  -> 201 {"message": {"id": "<old dm 1>", "body": "Hi Riley", "question": null}}
$ Riley answers the DM
  POST /api/dms/<dm asker+riley>/messages
  -> 201 {"message": {"id": "<old dm 2>", "body": "Hi Asker", "question": null}}
$ a question on main is not a question
  POST /api/channels/<orders>/messages
  -> 201 {"message": {"id": "<plain on main>", "body": "Ship Monday?", "question": null}}
$ #orders history
  GET /api/channels/<orders>/messages
  -> 200 {"messages": 3, "with_question": 0, "sha256_12": "806988d27faf"}
$ DM history
  GET /api/dms/<dm asker+riley>/messages
  -> 200 {"messages": 2, "with_question": 0, "sha256_12": "ee9f1067a95b"}
latest migrations: 0042_user_passwords.sql 0041_slash_command_guest_budget_index.sql 

## branch build on the same database
latest migrations: 0043_message_questions.sql 0042_user_passwords.sql 
$ #orders history
  GET /api/channels/<orders>/messages
  -> 200 {"messages": 3, "with_question": 0, "sha256_12": "806988d27faf"}
$ DM history
  GET /api/dms/<dm asker+riley>/messages
  -> 200 {"messages": 2, "with_question": 0, "sha256_12": "ee9f1067a95b"}
$ the old question-shaped message stays plain
  GET /api/messages/<plain on main>
  -> 200 {"message": {"id": "<plain on main>", "body": "Ship Monday?", "question": null}}
$ the bot token from main lists questions
  GET /api/bots/self/questions
  -> 200 {"questions": [], "next_cursor": null}
$ Asker asks in the existing #orders
  POST /api/channels/<orders>/messages
  -> 201 [X-Clickclack-Questions: supported] {"message": {"id": "<q-channel>", "body": "Ship Monday?", "question": {"status": "open", "version": 1}}}
$ Asker asks in the existing thread
  POST /api/messages/<root>/thread/replies
  -> 201 [X-Clickclack-Questions: supported] {"message": {"id": "<q-thread>", "body": "Use the big boxes?", "question": {"status": "open", "version": 1}}}
$ Asker asks in the existing DM
  POST /api/dms/<dm asker+riley>/messages
  -> 201 [X-Clickclack-Questions: supported] {"message": {"id": "<q-dm>", "body": "Same address?", "question": {"status": "open", "version": 1}}}
$ open questions
  GET /api/bots/self/questions
  -> 200 {"questions": [{"message_id": "<q-channel>", "status": "open", "version": 1}, {"message_id": "<q-thread>", "status": "open", "version": 1}, {"message_id": "<q-dm>", "status": "open", "version": 1}], "next_cursor": null}
$ main's CLI, an older client, reads #orders from the upgraded server
  1	<root>	Local Captain	Order 118 is ready for packing
  2	<old bot message>	Asker	Tracking will follow
  3	<plain on main>	Asker	Ship Monday?
  4	<q-channel>	Asker	Ship Monday?
$ main's CLI reads the thread
  <root>	Order 118 is ready for packing
  <old reply>	Riley Responder	Packing starts at 9
  <q-thread>	Asker	Use the big boxes?
$ Riley answers #orders
  POST /api/messages/<q-channel>/question/answers
  -> 200 [X-Clickclack-Questions: supported] {"message": {"id": "<q-channel>", "body": "Ship Monday?", "question": {"status": "submitted", "version": 2, "answers": {"ship": ["Yes"]}, "skipped": false}}}
$ owner answers the thread
  POST /api/messages/<q-thread>/question/answers
  -> 200 [X-Clickclack-Questions: supported] {"message": {"id": "<q-thread>", "body": "Use the big boxes?", "question": {"status": "submitted", "version": 2, "answers": {"boxes": ["No"]}, "skipped": false}}}
$ Riley skips the DM
  POST /api/messages/<q-dm>/question/answers
  -> 200 [X-Clickclack-Questions: supported] {"message": {"id": "<q-dm>", "body": "Same address?", "question": {"status": "submitted", "version": 2, "answers": null, "skipped": true}}}
$ Asker uses the #orders answer
  POST /api/messages/<q-channel>/question/resolution
  -> 200 {"message": {"id": "<q-channel>", "body": "Ship Monday?", "question": {"status": "answered", "version": 3, "answers": {"ship": ["Yes"]}, "skipped": false}}}
$ Asker uses the thread answer
  POST /api/messages/<q-thread>/question/resolution
  -> 200 {"message": {"id": "<q-thread>", "body": "Use the big boxes?", "question": {"status": "answered", "version": 3, "answers": {"boxes": ["No"]}, "skipped": false}}}
$ Asker applies the DM skip
  POST /api/messages/<q-dm>/question/resolution
  -> 200 {"message": {"id": "<q-dm>", "body": "Same address?", "question": {"status": "cancelled", "version": 3, "answers": null, "skipped": true}}}
$ nothing left to reconcile
  GET /api/bots/self/questions
  -> 200 {"questions": [], "next_cursor": null}
question rows: answered:2 cancelled:1

Postgres runs the same script in a fresh schema with identical output except
the migration names (0035_user_passwords.sql → 0036_message_questions.sql)
and the history digests, which again match before and after the upgrade.

Tests:

  • Shared store suite on SQLite and Postgres (questiontest): create, nonce
    replay, responder rules (people outside the conversation, guests outside
    #guest, direct messages), validation, answer replay, skip, reopen with a
    note, external answers, expiry, deleted messages, thread and DM questions, and
    8 concurrent answers where exactly one wins. QuestionReplayAndVersionGuards
    adds a nonce replay after the deadline moves inside the minimum lifetime,
    activity kinds with a question, a stale expected_version and one across a
    reopen, and the direct-message filter of the reconciliation list.
  • TestQuestionHTTPLifecycle: bot-only asking, 400 for invalid questions,
    403 for bots answering and for non-responders, 409 for late answers and
    stale versions (including expected_version), the realtime
    question.submitted payload without answers, idempotent resolution.
  • TestBotQuestionListingKeepsDirectScopesAndPages: 201 open questions page as
    200 plus 1 with a cursor; a messages:read-only token gets no DM rows and
    401 after revocation; an activity kind with a question gets 400.
  • apps/web/src/lib/questions.test.ts: drafts, quick-question detection,
    countdown, headlines, receipts, and shortcut keys.
  • tests/e2e/agent-questions.spec.ts (3 tests, rerun on this head): one-tap
    answer and bot resolution; a responder-only form answered with clicks and
    number keys while the owner sees it locked and then updated live; a reopened
    card with the note, answered again; a thread question closing at its
    deadline.
  • Negative controls: reverting each server fix fails its test (DM filter,
    lookahead at 200, replay before the lifetime check, activity kinds,
    expected_version). Without the type-to-focus change the keyboard step fails
    (focus leaves the option after the first number); without the list change the
    reopened card's Send answers button is out of view (viewport ratio 0).
  • On the previous head, existing e2e specs for type-to-focus, live follow,
    message windows, history settlement, agent activity, message editing, and the
    unread bar passed (36 tests together with the new spec); this round only adds
    expected_version to the card's request.
  • Local CI steps on this head: pnpm fmt:check, pnpm lint, pnpm typecheck,
    pnpm -r typecheck, web unit tests, deadcode, the embedded build is current
    and repeatable, and go test ./... with CLICKCLACK_POSTGRES_TEST_DSN.
    Coverage for the gated packages is 86.5%. On this machine two tests fail the
    same way on unchanged main:
    TestHTTPBodyDeadlineStillBoundsStalledRequestBodies and
    uploadstore TestR2HeaderNetworkLifecycle/progressing_PUT.

This adds a public lifecycle contract (the question facet, three routes, two
events, SDK methods) that the project will maintain, so it needs a maintainer to
decide it belongs in ClickClack before merge.

AI-assisted: prepared with Claude Code; I reviewed the change and the evidence.

🤖 Generated with Claude Code

@sercada
sercada requested a review from a team as a code owner September 13, 2026 12:34
@clawsweeper

clawsweeper Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper

clawsweeper Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Codex review: blocked before merge. Reviewed September 13, 2026, 9:27 AM ET / 13:27 UTC (Revision 3).

ClawSweeper review

What this changes

Adds bot-authored question cards, validated human answers, bot-recorded outcomes, database persistence, reconciliation endpoints, SDK methods, documentation, and tests.

Merge readiness

⛔ Blocked before merge - 3 items remain

The five prior findings are addressed, and the added runtime evidence satisfies the earlier authorization and upgrade proof requests. This remains distinct work absent from main; adoption of the public question lifecycle needs maintainer approval.

Priority: P2
Reviewed head: f59baed67ee7d4b547dda25903ad40607374ccb7
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) A coherent implementation with strong runtime and upgrade evidence and no remaining identified correctness blocker.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The matching full PR body supplies real HTTP traces through the question handlers and SQL stores, including DM scope exclusion, revoked-token rejection, stale-answer rejection, and populated database upgrades; four inspected screenshots support the card UI and outcomes.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The matching full PR body supplies real HTTP traces through the question handlers and SQL stores, including DM scope exclusion, revoked-token rejection, stale-answer rejection, and populated database upgrades; four inspected screenshots support the card UI and outcomes.
Evidence reviewed 10 items Pinned change scope: The verified base-to-head diff contains 74 files. Excluding generated artifacts and documentation, production changes are +3548/-43 lines and tests are +1458/-0; generated artifacts are +917/-94 and documentation +209/-0.
Still necessary on main: Main's message-create input has no question field, its router lacks all three question endpoints, and the source search found no existing structured-question implementation. No merged replacement was established. Inspection of the v0.5.0 source object was unavailable, so no release implementation claim is made.
Prior findings addressed: Current handlers filter reconciliation by dms:read and reject activity-row questions. Both stores allow 201-row pagination lookahead, compare expected answer versions, and perform time-dependent creation validation after nonce replay lookup. The web card supplies expected_version. HTTP and shared-store regression tests cover these repairs. The earlier head object was unavailable locally; resolution was checked directly against current source and the retained findings.
Findings None None.
Security None None.

How this fits together

ClickClack carries conversations between people and bots. Questions attach to ordinary messages, pass through the existing API and database stores, and appear as interactive cards whose answers notify the asking bot.

flowchart TD
  A[Bot message with question] --> B[Authentication and validation]
  B --> C[Message and question storage]
  C --> D[Conversation card]
  D --> E[Human answer and access checks]
  E --> C
  C --> F[Bot notification and reconciliation]
  F --> G[Recorded outcome and receipt]
Loading

Decision needed

Question Recommendation
Should ClickClack adopt the documented question lifecycle across channel messages, DMs, threads, and the TypeScript SDK? Sponsor the documented contract: Approve the message-attached question lifecycle and accept its API, SDK, and storage maintenance commitment.

Why: The implementation and supplied proof support the proposal, but accepting this new durable public contract requires product ownership; the author explicitly requests that decision.

Before merge

  • Resolve merge risk (P1) - Acceptance of the durable question lifecycle and its ongoing API/SDK support commitment remains unconfirmed.
  • Complete next step (P2) - Obtain maintainer approval for the documented question lifecycle and public API/SDK contract.
  • Resolve maintainer decision - Resolve the maintainer decision shown above before merge.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production and test delta Production +3548/-43; tests +1458/-0; generated files excluded The growth implements the stated end-to-end question capability across two stores, the web app, and public clients.
Prior review findings 5 addressed; 0 retained blockers Current source and supplied evidence address every concrete finding retained from the previous review.

Merge-risk options

Maintainer options:

  1. Decide the mitigation before merge
    Adopt a maintainer-approved question contract that preserves ordinary message fallback, existing access boundaries, and version-guarded answers.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Technical review

Best possible solution:

Adopt a maintainer-approved question contract that preserves ordinary message fallback, existing access boundaries, and version-guarded answers.

Do we have a high-confidence way to reproduce the issue?

Not applicable to the feature request. The contributor provides real-server before/after evidence for the prior defects; this read-only review did not execute tests.

Is this the best way to solve the issue?

Yes, technically: attaching questions to existing messages reuses access, history, and event infrastructure while preserving older-client fallback. Product acceptance remains a separate decision.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against eeefa0412808.

Labels

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The matching full PR body supplies real HTTP traces through the question handlers and SQL stores, including DM scope exclusion, revoked-token rejection, stale-answer rejection, and populated database upgrades; four inspected screenshots support the card UI and outcomes.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The matching full PR body supplies real HTTP traces through the question handlers and SQL stores, including DM scope exclusion, revoked-token rejection, stale-answer rejection, and populated database upgrades; four inspected screenshots support the card UI and outcomes.
  • remove merge-risk: 🚨 security-boundary: Current PR review selected no merge-risk labels.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove status: 📣 needs proof: Current PR status label is status: 👀 ready for maintainer look.
  • remove proof: 📸 screenshot: Current real behavior proof evidence kind is terminal.
  • remove merge-risk: 🚨 compatibility: Current PR review selected no merge-risk labels.

Label justifications:

  • P2: This is a useful, bounded conversation capability with no demonstrated urgent regression in existing workflows.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The matching full PR body supplies real HTTP traces through the question handlers and SQL stores, including DM scope exclusion, revoked-token rejection, stale-answer rejection, and populated database upgrades; four inspected screenshots support the card UI and outcomes.
  • proof: sufficient: Contributor real behavior proof is sufficient. The matching full PR body supplies real HTTP traces through the question handlers and SQL stores, including DM scope exclusion, revoked-token rejection, stale-answer rejection, and populated database upgrades; four inspected screenshots support the card UI and outcomes.

Evidence

What I checked:

  • Pinned change scope: The verified base-to-head diff contains 74 files. Excluding generated artifacts and documentation, production changes are +3548/-43 lines and tests are +1458/-0; generated artifacts are +917/-94 and documentation +209/-0. (f59baed67ee7)
  • Still necessary on main: Main's message-create input has no question field, its router lacks all three question endpoints, and the source search found no existing structured-question implementation. No merged replacement was established. Inspection of the v0.5.0 source object was unavailable, so no release implementation claim is made. (apps/api/internal/httpapi/server.go:213, eeefa0412808)
  • Prior findings addressed: Current handlers filter reconciliation by dms:read and reject activity-row questions. Both stores allow 201-row pagination lookahead, compare expected answer versions, and perform time-dependent creation validation after nonce replay lookup. The web card supplies expected_version. HTTP and shared-store regression tests cover these repairs. The earlier head object was unavailable locally; resolution was checked directly against current source and the retained findings. (apps/api/internal/httpapi/questions.go:145, f59baed67ee7)
  • Complete proof body inspected: Read the complete 17,447-unit PR body through GitHub and verified SHA-256 f30990655d806a45bb6e24eaba880eeb85a5a6e3befddaaec6819e181da2299b, matching the supplied snapshot. Its real-server HTTP transcript shows authorized DM reconciliation, exclusion for a messages-read-only token, rejection after revocation, stale-version rejection, nonce replay near expiry, and rejection of activity questions. (f59baed67ee7)
  • Fresh installation and populated upgrade proof: The matching PR body records fresh SQLite HTTP execution and upgrades from main on populated SQLite and PostgreSQL. Existing history digests remain unchanged, old bot tokens continue working, an older CLI reads fallback bodies, and new channel, thread, and DM questions are answered or skipped and resolved. PostgreSQL is reported to produce the same results with its corresponding migration. (apps/api/internal/store/sqlite/migrations/0043_message_questions.sql:4, f59baed67ee7)
  • Visible card proof: Inspected all four prepared local images from the PR's pr-assets/agent-questions links. They show a filled form and receipt, submitted and responder-locked cards, a reopened card with explanatory note, a failed outcome, and an expired thread card. These support visible behavior; the separate HTTP transcript supplies authorization evidence.

Likely related people:

  • unknown: The claimed source-line change could not be verified from bounded local history. (role: source history unknown; confidence: low)
  • unknown: The claimed source-line change could not be verified from bounded local history. (role: source history unknown; confidence: low)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (2 earlier review cycles)
  • reviewed 2026-09-13T12:38:08.028Z sha e070bee :: needs real behavior proof before merge. :: [P1] Preserve DM read scopes in question reconciliation | [P2] Bind answer submissions to the displayed question version | [P2] Allow the pagination lookahead at the maximum page size | [P2] Check existing create nonces before revalidating the deadline | [P2] Reject questions attached to agent activity messages
  • reviewed 2026-09-13T12:49:23.380Z sha e070bee :: needs real behavior proof before merge. :: [P1] Preserve DM read scopes in question reconciliation | [P2] Bind answer submissions to the displayed question version | [P2] Allow the pagination lookahead at the maximum page size | [P2] Check existing create nonces before revalidating the deadline | [P2] Reject questions attached to agent activity messages

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Sep 13, 2026
A bot can attach a question to a channel message, direct message, or thread
reply. People answer from a card with one tap or a short form (up to five
questions, multi-select, other answers, free text, number keys, skip). The
first valid answer wins, the bot is told through question.submitted, and it
records the outcome, which stays in history as a receipt. Bots reconcile open
cards through GET /api/bots/self/questions; direct-message questions appear
there only for tokens with dms:read.

Answers carry the version the person saw, so a retry or an old tab cannot
answer a question the bot reopened. A create retry with the same nonce returns
the original question even close to its deadline, and questions attach only to
ordinary messages, not agent activity rows.

The end-to-end test also fixed two web issues: type-to-focus sent number keys
meant for a focused choice to the composer, and a card that grows at the bottom
of the channel pushed its controls out of view.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sercada
sercada force-pushed the feat/agent-questions branch from e070bee to f59baed Compare September 13, 2026 13:21
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Sep 13, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant