Skip to content

refactor(openclaw): drive the adapter only through HarnessClient - #959

Closed
chughtapan wants to merge 2 commits into
impl/harness-client-acquisitionfrom
impl/openclaw-harness-cutover
Closed

refactor(openclaw): drive the adapter only through HarnessClient#959
chughtapan wants to merge 2 commits into
impl/harness-client-acquisitionfrom
impl/openclaw-harness-cutover

Conversation

@chughtapan

Copy link
Copy Markdown
Owner

Plan step 10. Stacked on #955. −4297 / +991 across 26 files. This is the cutover the whole stack was built toward.

The dual path is gone

startGatewayAccountEffect chose between a HarnessClient and a directly constructed MoltZapService by testing whether a dep was injected. Only the second arm was ever reachable in production, because nothing could compute a daemon endpoint. #954 gave the slot its port and #955 gave it a client, so the account id alone now resolves one:

const harnessClient = injected ?? (yield* harnessClientForProfile(profileName));

The legacy arm goes with everything that existed to serve it — the gateway service and core constructors, the inbound bridge that synthesized a turn from a raw core message, connection-status reporting, dispatchOutbound, the activeClients map, OpenClawClientService, and the legacy half of openclaw-gateway-lifecycle.ts (which drops to 48 lines). The JSDoc mermaid flow is rewritten for the single path, per the repo rule that a flow change updates its diagram in the same PR.

Capability drops — taken deliberately, not discovered

The directory section. Serving listPeers/listGroups through the client would publish agent and conversation search on the adapter-facing surface, which 20260801-harness-client-owns-runtime-context keeps as "MCP management operations, not adapter-facing service methods." A harness-backed account already returned [] for both, so this makes an existing silent gap explicit rather than introducing one.

Proactive conv:<id> outbound and MoltZapService.sendToAgent. Repeated proactive DMs to one agent no longer share a conversation — tracked in #957 with the fix recipe, not left as a bullet here. TARGET_PREFIX_CONVERSATION survives: originatingTarget labels every inbound turn's reply-to address with it, so target parsing outlives proactive sending.

MoltZapService.send also survives — channel-core.ts → sendReply delegates to it and moltzapd.ts wires reply: core.sendReply.bind(core). Generic send dies on the adapter surface, not inside the daemon.

The docker suites had never run

Deleted rather than carried, along with their whole harness. moltzap-eval-agent:local has no Dockerfile in this repo and ci.yml contains zero docker references, so isImageAvailable() always failed, containerAId was "", and both suites describe.skipIf-ed unconditionally. They produced green checkmarks for nothing.

With them gone no integration suite remained in the package, so vitest.integration.config.mjs, its globalSetup, test-helpers.ts, and the test:integration script went too — a setup that provisions nothing is worse than no setup.

Gates

git grep 'MoltZapService|MoltZapChannelCore|createService|createCore' -- 'packages/openclaw-channel/**'

returns nothing — including generated MODULE.md and the package's own AGENTS.md, which still described wrapping MoltZapChannelCore and binding a Unix socket. docs:generate was cached and had to be forced with --skip-nx-cache to catch the stale diagram.

  • pnpm nx run workspace:precommitexit 0 (the gate that includes lint:sloppy-code-guard)
  • typecheck and typecheck:tests — 0 errors
  • @moltzap/openclaw-channel:test71 passed / 8 files

Deviation from the plan, for your call

The plan says delete deps.harnessClientForAccount. It also says rewrite the tests onto createHarnessFixture, which requires injection. Those contradict.

I kept it as the single DI seam. With legacy gone it no longer selects a backing — there is exactly one production path, and this is dependency injection rather than runtime generation selection. Deleting it would have forced every gateway test to spawn a real daemon, trading ~1100 lines of fast unit coverage for integration-only proof.

Worth an explicit ruling rather than my judgment.

ADR conformance (R-pass)

Governing outcome Record Owner Binds this diff? Verdict Evidence
Search/history are MCP management operations, not adapter-facing service methods 20260801-harness-client-owns-runtime-context v2 (contested) no — followed CONFORMS The directory section is dropped rather than ported, which is what the record requires.
"There is no generic send" 20260728-model-surface-is-start-reply-listen v2 no — followed CONFORMS Outbound is startConversation only; conv:<id> sending and sendToAgent deleted. MoltZapService.send survives strictly inside the daemon reply path.
No runtime generation selection 20260801-harness-is-one-profile-slot-daemon v2 no — followed CONFORMS The runtime fork is deleted; one production path remains.
main No main-governing record covers adapter composition. Confirmed against the 5 main-governing records in the #926 baseline.

Ledger row 6 (ConversationWithParticipants documented as daemon-local yet publicly exported) is not resolved here — it lives in packages/client and is settled by #958's wire narrowing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P76aaa1STr3WPZ3nDascta

The plugin chose between a HarnessClient and a directly constructed
MoltZapService at runtime, and only the second arm was reachable in
production, because nothing could compute a daemon endpoint. The slot now
carries its port, so the account id alone resolves a client.

startAccount acquires that client unconditionally and drains it. The legacy
arm goes with everything that existed to serve it: the gateway service and
core constructors, the inbound bridge that synthesized a turn from a raw
core message, connection-status reporting, the outbound dispatcher, the
active-service map, and the legacy half of the gateway lifecycle.

The directory section goes with it. Serving listPeers and listGroups
through the client would publish agent and conversation search on the
adapter-facing surface, which the accepted HarnessClient record keeps as
MCP management operations. A harness-backed account already returned empty
lists, so this makes an existing silent gap explicit.

Proactive sends into an existing conversation and MoltZapService.sendToAgent
go too. Repeated proactive DMs to one agent no longer share a conversation;
that regression is tracked separately.

The docker suites and their container harness are deleted rather than
carried: they were gated behind an image that has no Dockerfile here and no
CI step builds one, so they had never executed. No integration suite
remained in the package afterward, so its setup and config go as well.

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