Skip to content

feat(crew): evidence on the thread log — prompt rule, --evidence tag, desktop cards, owner accept/reject (#121) - #128

Merged
oscarlehuu merged 14 commits into
mainfrom
devin/1786360062-evidence-thread-log
Aug 11, 2026
Merged

feat(crew): evidence on the thread log — prompt rule, --evidence tag, desktop cards, owner accept/reject (#121)#128
oscarlehuu merged 14 commits into
mainfrom
devin/1786360062-evidence-thread-log

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 10, 2026

Copy link
Copy Markdown

Summary

Ships the #121 slice: an agent that reports "done" attaches the artifact it already produced, the desktop renders it as a card in the timeline, and the owner accepts or rejects it with a standard NIP-25 reaction the agent can read back. Evidence is a byproduct of work done properly — no computer-use anywhere in the flow, no new event kinds, no relay changes.

Wire contract (D-036, see numbering note below):

["crew-evidence", "test-run" | "metrics" | "before-after-visual" | "diff-stat"]

on the existing message kind. First occurrence wins; the CLI validates the enum value only (it cannot and does not verify that evidence exists); the renderer does not trust it — an unrecognized value falls back to the ordinary message body. Images ride the existing Blossom upload + NIP-92 imeta path.

Design choices worth knowing before reading the diff:

  • The tag is appended post-build, not in the SDK. EvidenceKind::append_tag takes the already-built EventBuilder and appends before signing (precedent crates/buzz-cli/src/client.rs:590), so buzz-sdk and buzz-core keep zero additional Crew delta.
  • MessageRow.tsx was at 980/1000 lines against the file-size ratchet. D-022 forbids raising MAX_LINES, so the change there is +7 lines of prop pass-through only; the dispatch and all card logic live in Crew-owned MessageRowDefaultBody.tsx / EvidenceCard.tsx / lib/evidenceTag.ts. Final: 987/1000, MAX_LINES untouched.
  • The prompt section is capped at 18 lines and the cap is machine-checked (crates/buzz-acp/src/lib.rs), because the base prompt is paid on every turn of every agent — a fat section would violate the issue's own token-frugality principle. Actual: 13 lines.
  • Accept/Reject adds no new machinery. It reuses the exact handler the agent-receipt card already uses (handleReactionSelect), so useReactionHandler.ts and MessageReactions.tsx are unchanged. Reject sends ❌ and opens the reply composer, because a bare ❌ leaves the agent nothing to act on. Controls are owner-only, reusing the receipt card's owner resolution rather than inventing a second owner concept.
  • Cards never assert a claim the author did not make. The structured framing (metrics before/after/delta row, red→green blocks) is additive over the authored excerpt, which is always rendered through the existing Markdown pipeline; when nothing parses, the framing is omitted rather than printing "No failures" over a body describing failures.

Known limit, documented in DECISIONS.md rather than only in the issue: evidence is self-reported and can be fabricated. This raises the cost of lying and the odds of getting caught (fabricated numbers diverge from CI, fabricated screenshots diverge from the app); it does not cryptographically verify work. Independent verification stays where it lives today: CI + PR review. Related non-claim: the ≤30-line evidence bound is a prompt rule and a probe check, never a runtime guard.

Definition of Done → change

DoD checkbox Satisfied by
base_prompt.md evidence section + UPSTREAM-SYNC.md accounting in the same PR; generic half as a Crew-owned artifact crates/buzz-acp/src/base_prompt.md (new self-contained ## Evidence on completion, placed after Communication Patterns, before Engineering Discipline) · new "Upstream files Crew edits" table in docs/crew/UPSTREAM-SYNC.md (the list the issue referenced did not exist yet) · docs/crew/upstream-proposals/evidence-on-completion.md — Crew-owned draft of the generic, Crew-free half. No PR, branch, or push against block/buzz (D-020, root AGENTS.md); per the correction on the issue, the upstream contribution is a separate founder decision and is not a blocking DoD item.
buzz messages send --evidence <kind> emits the validated tag new Crew-owned crates/buzz-cli/src/commands/evidence.rs (enum + exact-match parse, no aliases/case folding) · --evidence on MessagesCmd::Send · SendMessageParams.evidence threaded through cmd_send_message
Desktop renders all four kinds; text kinds legible without images EvidenceCard.tsx (metrics → compact number table; test-run → red→green blocks; diff-stat → summary + PR link reusing AgentReceiptCard's href resolver; before-after-visual → side-by-side imeta images degrading to captions + links) · lib/evidenceTag.ts tolerant parse
Owner Accept/Reject via NIP-25, end-to-end EvidenceCard controls → existing kind-7 reaction handler; verdict derived from the owner's own reaction (nothing new persisted); agent-readable half needs no new CLIbuzz reactions get --event <id> already ships
Known limit + tag schema in DECISIONS.md; STATE.md updated in-PR docs/crew/DECISIONS.md D-036 · docs/crew/STATE.md (anti-drift rule, #117)
Evidence attached to the PR Testing section below · docs/crew/verification/0010-evidence-on-thread-log-probes.md · live probes + per-kind rendered card screenshots posted as a PR comment

Decision numbering

Took D-036, the slot the orchestrator allocated to issue #121 (D-028/029/030 → PR #120, D-031/032 → PR #124, D-033 → PR #127, D-034 → PR #129, D-035 → issue #119) — the plan's assumed D-031 was long gone. Spike id 0021 and verification record 0010 for the same reason: parallel sessions are consuming the lower ids (#120 took spikes 0015-0017).

Related issue

Closes #121. Plan of record: plans/20260810-evidence-thread-log/ on PR #123 (9 phases, all executed except phase 09's live probes, which run against this PR).

Testing

Spike first (docs/crew/spikes/0021-evidence-tag-roundtrip.md, verdict PASS) — the whole wire design depended on an unknown Crew-invented tag surviving the kind-9 path, which was unproven: an unknown tag on kind 9 round-tripped publish → relay ingest → Postgres → /query byte-identically, formatTimelineMessages kept it on TimelineMessage.tags, applyEditTagOverlay preserved it across an edit, and a normal reply in the same thread kept correct thread_metadata counters. Mobile/web ignore-safety is source-level evidence, not a running-client test.

Then contract tests RED before implementation, per the repo workflow. Observed RED reasons (each a genuine absence, on a built e2e bundle):

C1  --evidence test-run should parse: error: unexpected argument '--evidence' found
C2  expect(locator).toBeVisible() failed — getByTestId('evidence-card-test-run') — element(s) not found
C3  expect(locator).toBeVisible() failed — getByTestId('evidence-card-test-run') — element(s) not found

GREEN after implementation:

cargo test -p buzz-cli    348 passed; 0 failed
cargo test -p buzz-acp    831 passed; 0 failed   (incl. the 18-line prompt-cap assertion)
pnpm --filter buzz test   5045 passed; 0 failed; 1 skipped
playwright --project=smoke evidence-cards.spec.ts evidence-reactions.spec.ts   6 passed

Also green: pnpm --filter buzz check, pnpm --filter buzz typecheck, cargo fmt --check, cargo clippy --all-targets -- -D warnings, just test-unit, just check-compile, desktop/ pnpm check:px-text, node scripts/check-file-sizes.mjs. just ci's mobile leg (dart format) hangs in this environment, so the Rust/desktop gates were run individually.

Beyond the four card contracts, the non-regression contracts are pinned too: a message with no crew-evidence tag renders exactly as today; an unrecognized kind value renders the ordinary body rather than a broken card; a KIND_AGENT_RECEIPT (46043) message carrying the tag keeps its receipt card and does not grow a second evidence card; a non-owner sees the card without Accept/Reject controls; a third party's ❌ does not display the card as rejected.

CI: 9 passed / 2 failed / 1 skipped. The two red lanes are smoke shards whose failures all reproduce on clean origin/main run 31362178966channel-activity-popover.spec.ts:459, channels.spec.ts:500 (#110), inbox-edit.spec.ts:175/325, inbox-reactions.spec.ts:36, messaging.spec.ts:1819; Desktop Smoke E2E (4) is the known 30-minute cancellation lane (#109). channel-activity-popover.spec.ts was additionally reproduced locally on a clean origin/main worktree at 35af74019 (toHaveCount(2) → Received: 1). No evidence-related check is red. One earlier Desktop Rust failure (Text file busy (os error 26) spawning the provider binary) went green on the next head — recorded as transient in verification record 0010.

Phase 09 live probes ran against a real local relay plus the headless Playwright harness — results, per-kind rendered card screenshots and the buzz reactions get agent read-back are in this comment, recorded in docs/crew/verification/0010-evidence-on-thread-log-probes.md. One limit stated there rather than glossed: the e2e bridge's add_reaction is mock-only (e2eBridge.ts:9528-9571), so probe 1 is two halves — the desktop half proves the emitted kind-7's target event id and the card's accepted/rejected states, the relay half proves a kind-7 lands on a real relay and is agent-readable via buzz reactions get. No single run exercises click → relay end to end, since a real Tauri run would need computer-use, which this issue bans. Tracked as a harness gap in #133, not worked around here.

Link to Devin session: https://app.devin.ai/sessions/dc642c6db0de4d11b8f47f98a3f1e6fc
Requested by: @oscarlehuu

@oscarlehuu oscarlehuu self-assigned this Aug 10, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Author

Phase 09 — live probes + rendered evidence (no computer-use)

Verified against a real local relay (:3000, Docker PG/Redis) plus the headless Playwright harness. Evidence is terminal text and scoped locator.screenshot() PNGs only — the app was never opened by hand. Full record: docs/crew/verification/0010-evidence-on-thread-log-probes.md.

RED first: stubbing parseEvidenceKind back to its pre-change null return makes all four probe tests fail with getByTestId('evidence-card-test-run') not found; reverting turns them green. Negative control: the same report sent without --evidence yields tags [["h","<channel>"]] — no crew-evidence.

Rendered card per kind (7/7 unique shasum -a 256)

test-run (pending) metrics
test-run pending metrics
diff-stat before/after visual
diff-stat before after
accepted rejected
accepted rejected

Images blocked — the card still reads sensibly (0 img, two labelled links):

degraded

Probe 1 — accept/reject round trip + agent read-back
$ buzz messages send --channel <CH> --content "$(cat report.md)" --evidence test-run
{"accepted":true,"event_id":"c53eea073f4916aef03a58b2350a172f031cd8328f19bd748a3aded7f157d338",…}

$ buzz messages get --channel <CH>
c53eea07… kind 9 tags [['h','feabece1-…'], ['crew-evidence','test-run']]

$ buzz reactions add --event c53eea07… --emoji ""      # owner
{"accepted":true,"event_id":"0974e1250958d6b4389ea19c4eaff2a5a4e17f74e62d17ff256d6394dc81f35b",…}

$ buzz --format compact reactions get --event c53eea07…  # agent read-back
{"reactions":[{"count":1,"emoji":"✅","pubkeys":["e5ebc6cdb579be112e336cc319b5989b4bb6af11786ea90dbe52b5f08d741b34"]}]}

# reject path
$ buzz --format compact reactions get --event 8e823f97…
{"reactions":[{"count":1,"emoji":"❌","pubkeys":["e5ebc6cd…"]}]}
follow-up reply tags: [['h','feabece1-…'], ['e','8e823f97…','','reply']]

Desktop half: Accept → ✅ Accepted + button disabled, with exactly one add_reaction carrying emoji:"✅" and eventId === <evidence id>; Reject → ❌ Rejected + a second add_reaction emoji:"❌" on the same id, and the reply surface opens rooted on the evidence message. A ✅ from a third-party pubkey does not flip the owner verdict.

Stated limit: probe 1 is two halves by necessity — the harness bridge's add_reaction is mock-only (e2eBridge.ts:9528-9571), so a headless desktop click cannot publish to the relay. The desktop half proves the kind-7 target and the card states; the relay half proves it lands and is agent-readable. The single-process click→relay chain is unverified (a real Tauri run would need computer-use, which this issue bans).

Probe 2 — before/after visual

just desktop-screenshot --name evidence-before/--name evidence-after → two distinct PNGs (dc4fd780…/55dfbf23…), sent as one report with --file ×2 + --evidence before-after-visual. The relay event carries two imeta tags (m image/png) plus one crew-evidence tag. The card renders exactly 2 decoded images (naturalWidth > 0), distinct src, side by side (same row, before strictly left of after).

Note: relay Blossom GET returns 401 to an unauthenticated page, so the render/degrade captures serve the same PNGs locally using the relay's exact content and imeta tag shape.

Probe 3 — token discipline (spot-check only)

Report body line counts as stored on the relay: 12, 1, 5, against the issue's example bound of ≤30. No work was re-executed solely to capture evidence — the two PNGs were captured once and reused for the upload, the render assertion and the degrade assertion. The bound is not enforced anywhere in code.

Regression: full evidence suite 10 passed (evidence-cards, evidence-reactions, plus 4 probe tests). Screenshot text was additionally OCR-checked so the assertions rest on pixels, not just the DOM.

01-test-run-pending

02-metrics

03-diff-stat

04-before-after-visual

05-test-run-accepted

06-test-run-rejected

07-before-after-degraded

devin-ai-integration Bot added a commit that referenced this pull request Aug 10, 2026
@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/1786360062-evidence-thread-log branch from df2a999 to 2aa2d6e Compare August 11, 2026 09:29
devin-ai-integration Bot and others added 11 commits August 11, 2026 20:31
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
@oscarlehuu
oscarlehuu force-pushed the devin/1786360062-evidence-thread-log branch from 64d5bf9 to 9942078 Compare August 11, 2026 11:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Evidence on the thread log: proportionate proof-of-work in completion reports, evidence cards, owner accept/reject via reactions

1 participant