Skip to content

feat(client): compose the production HarnessClient from a profile name - #955

Closed
chughtapan wants to merge 2 commits into
impl/profile-slot-and-mcp-portfrom
impl/harness-client-acquisition
Closed

feat(client): compose the production HarnessClient from a profile name#955
chughtapan wants to merge 2 commits into
impl/profile-slot-and-mcp-portfrom
impl/harness-client-acquisition

Conversation

@chughtapan

Copy link
Copy Markdown
Owner

Plan step 9. Stacked on #954. This is the step that makes a production HarnessClient constructible at all.

What was missing

The only code that started a daemon and connected a client to it lived in test-utils, demanded a caller-supplied port, and needed an injected checkpoint store. Combined with the slot change in #954, nothing was left blocking — but nothing was wired either.

The composition

harnessClientForProfile(profileName)   // that is the entire input

It starts the slot's own daemon child, derives the loopback endpoint from the same persisted port the daemon binds, and provides a file-backed checkpoint store. No URL, no port, no store from the caller — which is exactly what the adapters need in steps 10 and 11.

The checkpoint directory is keyed by profile name, not AgentId. That is forced, not stylistic: acquireHarnessClient reads agentId from the status tool inside its own body, so the identity does not exist at the moment the store must be provided. One slot is exactly one AgentId, so the profile name is a valid agent scope.

The daemon supervisor moves from test-utils/process/packaged-moltzapd.ts to src/moltzapd-child.ts via git mv, so the proven readiness-polling and shutdown behavior is preserved rather than rewritten. Only the port reservation a test performs before writing a slot stays behind, in test-utils/process/reserve-port.ts.

The round-trip test now proves production

harness-adapters.integration.test.ts previously called the test-only acquirer and supplied KeyValueStore.layerMemory. It now calls harnessClientForProfile — so the one ungated end-to-end proof for both adapters exercises the real composition and a real file-backed store. acquireHarnessClient and KeyValueStore became unused imports there, which is the clean signal that the parallel test path is gone.

Architecture findings this surfaced, and how they were resolved

Three real ones, none suppressed blindly:

  1. require-boundary-owned-typesharness-client.ts may not mention node directly. Resolved by moving the node-touching composition into the node-boundary module rather than waiving the rule.
  2. Folder dependency cycle — my first fix put the module under harness/, creating .harness. Reverted; the module sits at the source root where its imports point one way.
  3. no-large-folder / file-implicit-boundary-module — the package genuinely gained a module, and harness-client.ts genuinely is a boundary (it is the ./harness-client subpath export). Both recorded in scripts/gen-architecture-configs.mjs, since the config is generated and a hand edit would be overwritten by arch:config:check.

connectMcpOnce was also rewritten Effect-natively instead of carrying an async suppression into production source — sloppy-code-guard and the eslint rule were both asking for the same thing, and the rewrite drops a try/catch too.

Gates

  • pnpm nx run workspace:precommitexit 0 (this runs lint:sloppy-code-guard, which pnpm lint alone does not)
  • pnpm typecheck and pnpm nx run-many -t typecheck:tests — 0 errors
  • pnpm lint — 0 warnings, 0 errors; arch:check 0 findings on every package
  • pnpm nx run-many -t test — all 9 projects green
  • git grep layerMemory packages/ returns only pure-unit tests

ADR conformance (R-pass)

Governing outcome Record Owner Binds this diff? Verdict Evidence
Daemon and adapter construct http://127.0.0.1:<mcpPort>/mcp; port-zero and bind fallback rejected 20260728-endpoint-daemon-speaks-modern-mcp v2 no — followed anyway CONFORMS The URL is derived from the slot; nothing allocates, discovers, or falls back. A port already in use surfaces the daemon's existing bind failure.
Checkpoints are client-owned presentation state 20260801-harness-client-owns-runtime-context v2 (contested) no CONFORMS The store is provided at the composition site; the client keeps ownership. No fsync, sharding, cache-layout, or corruption policy added — layerFileSystem stock behavior is accepted as-is.
main No main-governing record covers daemon supervision or checkpoint storage. This composition implements the shape the maintainer selected; the main-owned ADR bundle (step 5) is still outstanding.

No new divergence introduced; no #926 ledger row changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P76aaa1STr3WPZ3nDascta

Nothing in production could build a HarnessClient: the only code that
started a daemon and connected to it lived in test-utils, and it demanded
a caller-supplied port and an injected checkpoint store.

harnessClientForProfile takes a profile name and nothing else. It starts
the slot's own daemon child, derives the loopback endpoint from the same
persisted port the daemon binds, and provides a file-backed checkpoint
store. The checkpoint directory is keyed by profile name because the store
must be provided before the client reads its identity from the daemon's
status tool, and one slot is exactly one AgentId.

The daemon supervisor moves out of test-utils to the source root; only the
port reservation a test performs before writing a slot stays behind. The
packaged round-trip test now drives the production composition instead of
a parallel test-only path with an in-memory store.

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