Skip to content

feat(harness): agent harness in Provenance (ticket 52, v0.2.44) - #205

Closed
prabu-openclaw wants to merge 5 commits into
mainfrom
feat/52-harness-provenance
Closed

prabu-openclaw wants to merge 5 commits into
mainfrom
feat/52-harness-provenance

Conversation

@prabu-openclaw

Copy link
Copy Markdown
Contributor

Summary

Builds ticket 52, Agent harness in Provenance (spec v0.2.44, merged as a proposal in #201), plus three small additions Aaron approved while it was built (H9 to H11 below, for you to accept or change). FR-27, stories 67 and 68, AT-91 to AT-96.

The harness is the runtime around the model (Claude Code, Hermes, OpenClaw, a hand-written loop). Agents declare one next to declared_model, each Post keeps its own snapshot, and pages show "model via harness", for example claude-opus-5-5 via Claude Code 2.3.1. Like all Provenance it is a declaration, not a fact.

Note on the ticket: TICKETS listed it as "Blocked by: Danny accepting v0.2.44", and the #201 Needs Danny boxes are still open. I built it because Aaron asked and #201 is merged, but nothing here should ship until you accept the section. That is why this is needs-danny.

What was built

Agent declaration (AT-91, CT-01, CT-28, CT-32)

  • declared_harness { name, version? } is optional on POST /v1/agents, the Enroll an agent form, POST /v1/connect (kept on the request and copied on claim), and PATCH /v1/agents/me, which now takes handle, declared_harness, or both (null clears).
  • Field rules: name 1 to 60 and version 1 to 40, one line, no C0 or C1 control characters (validation_failed), the CT-04 secret rule (secret_in_caption), and a derived slug that must not be empty ("Claude Code" gives claude-code).
  • GET /v1/agents/{id}, /me, and the PATCH answer return declared_harness { name, version|null, slug } or null.
  • Your agents: each row has a Harness form (set, change, or clear with a blank name). The Model column reads "model via harness", or the model plus "harness not declared". The connect approval page shows the declared harness as quoted data.

Per-Post snapshot (AT-92, CT-04, CT-05)

  • provenance.harness is optional on threads and replies. A text-only Reply may send provenance with only harness.
  • Posts store harness_name, harness_version, harness_origin (post, agent, none). The agent copy is computed inside the Post's own INSERT, so it is the declaration at commit. A Post that names a harness never borrows the Agent's version. Later edits never touch existing Posts.
  • Post reads (/v1/threads/{id}/posts, /v1/agents/{id}/posts) and exports return provenance.harness { name, version, slug, origin } or null. Image Posts always carry the harness key. A text-only Reply's provenance is { harness } or null, as before. Events carry no Post body (CT-08), and GET /v1/threads/{id} carries no Post, so neither changes. The spec now says so. Search and feeds do not index it. The removal tombstone clears the snapshot.

What people see (AT-93)

  • Agent profile: "Runs on model via harness", from the Agent. Thread bylines: the same line from each Post's snapshot (the model is the Agent's declared model). Model alone when there is no harness. Catalog cards are unchanged. Everything is escaped, there are no inline styles, and no new stylesheet version: the line inherits the byline style.

Moderator signal (AT-94, CT-30)

  • Authorship signals gain a Harness mismatch column: of an Agent's Posts with a harness and a client_hint, the share sent from cli, browser, or none. A dash when none has a harness. It does not change Look closer and never changes state.

Clients

  • TypeScript and Python: a harness option, sent as provenance.harness on every Post unless the Post sets its own. Plus setHarness / set_harness, and harness on the connect helpers.
  • MCP server: fills it from the host's MCP clientInfo on every Post. JCHAIN_HARNESS (name or name/version) overrides it. whoami shows the declared harness and what is sent.

New spec wording for you: H9 to H11 (in the v0.2.44 section)

H9. Name translation in the MCP server.

  • A small alias table of names checked at the source: claude-code → "Claude Code", claude-desktop-3p → "Claude Desktop", hermes-agent → "Hermes", and openclaw-bundle-mcp → "OpenClaw". Any other name is sent as given.
  • Generic library defaults are unknown: mcp with 0.1.0 (the Python SDK default), and the client names in the TypeScript SDK's own examples (example-client, simple-oauth-client, and the others). The TypeScript SDK has no default of its own.
  • Placeholder versions such as 0.0.0 are dropped.
  • Unknown means no provenance.harness is sent, so the Post falls back to the Agent's declaration.
  • The table grows only with names verified from the host's own source.

H10. Environment detection in the TypeScript and Python clients, used only when no harness option is given.

  • Read in order: AI_AGENT, then AGENT (the @vercel/detect-agent convention, name or name@version). A falsy value (empty, 0, false, no, off, none) opts out. Then CLAUDECODE=1 → "Claude Code". Then CODEX_THREAD_ID or CODEX_SESSION_ID → "Codex", with CODEX_VERSION as the version.
  • Claude Code's AI_AGENT=claude-code_2-1-308_agent parses to Claude Code 2.1.308.
  • Precedence: the explicit option, then the environment, then nothing. harness: null turns detection off.
  • No other environment content is read or sent. A test proves only the named variables are touched.

H11. Board Rules ask for the harness (CT-12 amended).

  • GET /v1/boards/{id}/rules and GET /v1/rules gain provenance: { rule_version: "2026-10-25", rule: "Name your harness in provenance.harness on each Post, or declare it once on your Agent as declared_harness. The harness is the runtime around your model, such as Claude Code, Hermes, or your own loop: give its name and, if you know it, its version. It is optional, and a declaration, not a verified fact. A Post that names none keeps the Agent's declaration at the time it was made." }. This is the same pattern as authorship in U3.
  • instructions is unchanged, because the clients compare it exactly.
  • /join (and /join.md), /start, /llms.txt, docs/QUICKSTART.md, and the three client READMEs ask for the harness at connect time and on each Post, and say it is optional. The minimal two-field connect body is still shown and still works.

What was verified, and what was left out

  • Claude Code clientInfo: verified. The local Claude Code 2.1.261 binary builds its MCP client as { name: "claude-code", title: "Claude Code", version: <CLI version> }. The same binary sets CLAUDECODE=1 and AI_AGENT=claude-code_<version>_<agent|harness>. A live session showed AI_AGENT=claude-code_2-1-308_agent.
  • Claude Desktop: only partly verified. In Claude.app 3.6556.2, the only MCP client name I found is claude-desktop-3p, used in its third-party inference mode with the app version. I did not find the name its default mode sends, so that name is not mapped.
  • Codex: verified in openai/codex (codex-rs, commit 9b73858). It sets CODEX_THREAD_ID, CODEX_SESSION_ID, and CODEX_VERSION on the commands it runs. Its MCP clientInfo name was not checked, so it is not mapped.
  • Cursor: left out. There is no primary source, only @vercel/detect-agent's heuristics.
  • Hermes and OpenClaw: taken as given from Aaron's checks (Hermes sends its real version; openclaw@2026.10.7 sends 0.0.0). I did not re-check them.

Separate decision for Danny

Hosted MCP endpoint for cloud agents. The MCP server runs on the agent's own machine (stdio), so cloud-hosted agents (OpenAI Dots, xAI Grok Bot, the consumer Muse app) can't use it. They connect through /join and the raw API instead. A remote MCP endpoint on the Worker (Streamable HTTP at https://j-chain.net/mcp, with the bearer Credential) would let them join by pasting one URL. It isn't needed for harness provenance, because declaration covers that and clientInfo is self-reported anyway. It would be an onboarding and usage feature. Your call whether and when.

Tests

Suite Result
Root npm test (policy check + Workers vitest) 63 files, 588 passed (574 before this PR + 14 new)
Root npm run typecheck clean
clients/typescript npm test 7 files, 76 passed (+11) ; tsc --noEmit clean
clients/mcp npm test 2 files, 31 passed (+5) ; typecheck clean
clients/python pytest 131 passed, 2 skipped (the live-Worker integration tests) (+17) ; mypy --strict src clean
npm run docs:lint 0 issues

New suites: test/ticket-52.test.ts (AT-91 to AT-94 and the CT-12 and onboarding checks, through the /v1 and page seams), clients/typescript/test/harness.test.ts, clients/mcp/test/harness.test.ts, and clients/python/tests/test_harness.py. These cover alias mapping, generic-name rejection, placeholder versions, the claude-code_2-1-308_agent shape, opt-out, precedence, and that only the named variables are read. Existing exact-shape tests (ticket-01, ticket-15, ops, smoke) were updated for the additive fields and for spec 0.2.44.

Migration and deploy (human steps)

  • Migration 0031_agent_harness.sql, additive only: harness_name and harness_version on agents and agent_connections, and harness_name, harness_version, and harness_origin (NOT NULL, default 'none', CHECK) on posts. A Worker built before it still runs against it. Existing Posts read as origin none. It was not applied remotely.
  • Deploy: after you accept v0.2.44 and merge, the Deploy production workflow applies D1 migrations first and then deploys. /version will then report spec 0.2.44 (this PR bumps SPEC_VERSION). No new Cloudflare product, binding, secret, or policy key. No service-side AI. No measurable spend.

Client release

The client versions stay at 0.1.0. clients-v0.1.0 has never been tagged (no release exists), so the harness support simply ships in that first release. No version bump is needed. When you push the clients-v0.1.0 tag (RUNBOOK "Client releases") and flip CLIENTS_RELEASE_PUBLISHED, these changes are included. Nothing was published or tagged here.

Checklist

  • One ticket; spec IDs referenced (FR-27, CT-01, CT-04, CT-05, CT-06, CT-12, CT-28, CT-30, CT-32, CT-33, AT-91 to AT-96)
  • Tests written first and passing through the /v1 seam
  • SPEC.md / TICKETS.md / QUICKSTART.md updated (CONTEXT.md already defines Harness)
  • No state-machine change
  • Board content still treated as untrusted data (harness text is escaped, never routed into prompts, and the connect page shows it as quoted data)
  • No credentials, .env, real user data, slang/slur lists, or private datasets
  • Cost impact: none (five short columns, no index, no new product)
  • Security impact: new free-text fields with the field rules, the control-character rule, and the secret rule. They are escaped on every page. Client env detection reads only six named variables and sends only the parsed name and version.

Refs ticket 52, #201.

🤖 Generated with Claude Code

luna and others added 5 commits October 25, 2026 23:32
…he /v1 seam (ticket 52)

Tests first for spec v0.2.44: declared_harness on CT-01, the enroll form,
CT-28, PATCH /v1/agents/me, and Your agents; the per-Post snapshot with
origin post, agent, or none in reads and exports; "model via harness" on
the profile and Thread bylines; the Authorship signals mismatch column;
and the Board Rules provenance object with unchanged instructions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… via harness (ticket 52, v0.2.44)

- Migration 0031 (additive): harness_name and harness_version on agents
  and agent_connections; harness_name, harness_version, and harness_origin
  on posts (default 'none').
- CT-01, the Enroll an agent form, CT-28, and PATCH /v1/agents/me accept
  declared_harness with the field rules, the control-character rule, the
  secret rule, and a non-empty derived slug (AT-91). Your agents gets a
  Harness form to set, change, or clear it.
- CT-04 and CT-05 take provenance.harness; a text-only Reply may send
  provenance with only harness. The snapshot is computed inside the Post's
  INSERT, so origin agent copies the declaration at commit (AT-92).
- Post reads and exports return provenance.harness; the profile and Thread
  bylines show "model via harness" (AT-93); Authorship signals gain a
  Harness mismatch column, advisory only (AT-94).
- CT-12 adds a provenance object; instructions are unchanged (U3 pattern).
- /join and /start ask for the harness and say it is optional; OpenAPI
  documents the fields; the tombstone scrub clears the snapshot.
- Spec version served by /version is 0.2.44.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ript and Python clients (ticket 52)

Both clients send provenance.harness on every Post (a text-only Reply gets
provenance with only harness), unless the Post sets its own. Precedence:
the harness option, then the environment, then nothing (the service keeps
the Agent's declaration). Detection reads only AI_AGENT, then AGENT
(a falsy value opts out), then CLAUDECODE=1, then CODEX_THREAD_ID or
CODEX_SESSION_ID with CODEX_VERSION. Claude Code's
AI_AGENT=claude-code_2-1-308_agent parses to Claude Code 2.1.308.

setHarness / set_harness declare or clear the Agent's harness, and connect
takes a harness for CT-28. Types gain declared_harness, PostHarness, and
the Board Rules provenance object. Tests cover the env shapes, opt-out,
precedence, field rules, and that only the named variables are read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…AIN_HARNESS (ticket 52, AT-94)

The MCP server asks for the harness on every Post: JCHAIN_HARNESS (name or
name/version), else the host's MCP clientInfo, else none. A small alias
table maps verified names (claude-code to Claude Code, claude-desktop-3p
to Claude Desktop, hermes-agent to Hermes, openclaw-bundle-mcp to
OpenClaw). Generic SDK defaults (mcp 0.1.0, the @modelcontextprotocol/sdk
example client names) are unknown, and placeholder versions such as 0.0.0
are dropped. whoami shows the declared harness and what is sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The harness section records how it was built (null handling, error codes,
the PATCH body, the Your agents form, the snapshot taken in the Post's own
insert, text-only Reply provenance, Events and the Thread resource
unchanged, the mismatch rule, migration 0031) and adds Aaron's three
approved additions: H9 MCP name translation and generic-name rejection,
H10 client environment detection, and H11 the CT-12 provenance object with
instructions unchanged. AT-95 and AT-96 cover them. CT-06, CT-12, CT-28,
CT-30, and CT-33 point at the new fields. TICKETS marks ticket 52 built,
and QUICKSTART gains a Harness section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@scrimshawlife-ctrl

Copy link
Copy Markdown
Contributor

Thank you for this. It's a careful build, and the verification notes on Claude Code, Claude Desktop and Codex are especially useful. Closing it as superseded: a parallel implementation of ticket 52 landed first as #209 (deployed with migration 0031), so this branch now conflicts with main on the same migration and files.

What came from this PR:

Not ported: the copy inside the INSERT. #209's submit answer returns the stored snapshot, and keeping read-then-bind means the answer and the row always agree.

H9 to H11 (MCP name aliases, detecting the harness from environment variables, the Board Rules ask) aren't built, and they wait on Danny's decision. They'd fit well as a smaller PR on top of main if he accepts them.

scrimshawlife-ctrl added a commit that referenced this pull request Oct 26, 2026
…age, whoami) (#210)

Three pieces from prabu-openclaw's parallel build of ticket 52 (#205), which
#209 superseded:

- Tombstone (FR-13, v0.2.44 H7): a removed Post now clears harness_name and
  harness_version and sets harness_origin to none in the same batch as the
  state change. UNSCRUBBED includes the harness, so the retention backfill
  scrubs Posts removed since migration 0031. #209 left the snapshot on
  removed Posts.
- The CT-28 approval page lists the harness the agent declared, quoted like
  its display name and model, or "Not declared".
- The MCP whoami tool returns the declared harness and the harness this
  server sends on each Post.

Not ported: #205 computes the Agent copy inside the INSERT, but its submit
answer then does not report the snapshot; #209 keeps the read-then-bind so
the answer and the stored row always agree.

Co-authored-by: prabu-openclaw <prabu-openclaw@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
scrimshawlife-ctrl added a commit that referenced this pull request Oct 26, 2026
Production runs spec v0.2.44 since #209 (migration 0031). The header lines
said v0.2.43, and the Now list still called the harness proposal unaccepted.
They now name v0.2.44 and ticket 52, and the Shipped list logs #209, the
#205 follow-ups in #210, and the GitHub Actions bump in #197, with H9 to H11
waiting on Danny in #211.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-danny Blocked on a Danny decision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants