Skip to content

refactor(client): move the status contract to the harness plane - #953

Closed
chughtapan wants to merge 2 commits into
agent/harness-adapter-integrationfrom
impl/harness-status-schema-rehome
Closed

refactor(client): move the status contract to the harness plane#953
chughtapan wants to merge 2 commits into
agent/harness-adapter-integrationfrom
impl/harness-status-schema-rehome

Conversation

@chughtapan

Copy link
Copy Markdown
Owner

Plan step 6. Stacked on #951. Mechanical, and the single dependency edge that would otherwise make the CLI/socket deletion break the MCP status tool.

Problem

local-daemon-rpc.ts → statusCommandRpc was simultaneously the Unix-socket RPC and the schema source for two consumers that survive the socket's deletion:

  • harness-mcp-wire.ts → registerStatusTool (input/output schemas)
  • harness-client.ts → readActiveAgentId (result decoder)

So the plane that outlives the socket depended on the plane that does not. Deleting local-daemon-rpc.ts later would have broken the MCP status tool and the client identity read.

Change

The harness plane now states the status contract itself in harness/runtime.ts, alongside the reply and start-conversation contracts it already owned, and exposes it the same way those are exposed — JSON Schemas, derived types, and a strict decoder. No new export style.

The socket surface states the same shape independently rather than importing it. That is not an oversight: agent-code-guard/no-exported-brand-constructor rejects exporting a schema constructor, so the constructor genuinely cannot cross the boundary. Drift is still a compile error in both directions, because service-local-daemon.ts and moltzapd.ts each implement a handler against its own side — I verified this by injecting a field into each schema in turn and confirming each produced a type error.

I drafted a *.types-check.ts drift canary for the duplication and then deleted it: the probe showed it never fires on its own, since the two handlers already catch every case. Shipping it would have been a vacuous gate.

Dropping the RPC handler context ((payload, context)(payload)) also let the status tool registration lose its Rpc.isWrapper unwrap and the cast justified by "HandlersFrom widens that known-empty environment to any". Net: one fewer cast.

statusCommandRpc is now module-private, matching sendCommandRpc in the same file — knip flagging it as an unused export was the confirmation the coupling was gone.

Gate

git grep -n 'local-daemon-rpc' packages/client/src \
  | grep -v '/cli/' | grep -v 'service-local-daemon\|local-socket-server\|service.ts'

returns nothing — the harness plane no longer reaches into the socket plane. This also required decoupling harness-mcp-server.test.ts, which was building an entire socket handler table via makeLocalDaemonHandlers just to extract its .status field.

  • pnpm typecheck --skip-nx-cache — green
  • pnpm lint — 0 warnings, 0 errors (incl. knip, arch:check, arch:config:check)
  • pnpm nx run @moltzap/client:test — 233 passed / 32 files

No behavior change: the same shape is advertised and decoded, only its owner moved.

ADR conformance (R-pass)

Governing outcome Record Owner Binds this diff? Verdict Evidence
Tool named status on the local MCP surface 20260728-endpoint-daemon-speaks-modern-mcp v2 no CONFORMS Governs v2/* per the step-0 sweep (52 records classified, posted on #926). Tool name and shape unchanged regardless.
Search/history/status are MCP management operations, not adapter-facing service methods 20260801-harness-client-owns-runtime-context v2 (contested) no CONFORMS status stays a management tool; it is not added to HarnessClientService. readActiveAgentId remains private.
No main-governing record covers client module layout. The 5 main-governing records are agents-md-single-source, v2-lives-top-level, code-first-simulator-kernel, effect-native-evaluation-results, principal-io-uses-runtime-gateways — none touches this diff.

No new divergence introduced; no ledger row changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P76aaa1STr3WPZ3nDascta

statusCommandRpc was simultaneously the Unix-socket RPC and the schema
source for two surviving consumers: the MCP status tool and the harness
client's identity read. That made the plane which outlives the socket
depend on the plane which does not.

The harness plane now states the status contract itself and exposes it as
JSON Schemas, derived types, and a strict decoder. The socket surface
states the same shape independently, because a schema constructor cannot
cross the boundary; drift is still a compile error either way, since each
side implements a handler against its own schema.

Dropping the RPC handler context also removes the Rpc.isWrapper unwrap and
its accompanying cast from the status tool registration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P76aaa1STr3WPZ3nDascta
@chughtapan

Copy link
Copy Markdown
Owner Author

Superseded by #974, which collapses this stack into one PR against main. This branch's commits are all contained in it — nothing is lost, and this thread stays readable. Closing to leave one place to review.

@chughtapan chughtapan closed this Aug 6, 2026
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.

1 participant