feat(profile): materializer + host wiring + ACP/socket backends (full profile provisioning)#57
Merged
Merged
Conversation
…zer (all dimensions)
Generalizes the MCP-only materialisers in profile-support.ts to EVERY AgentProfile
dimension — context, skills, mcp, hooks, subagents, commands — routed per the verified
capability matrix (workflow harness-capability-matrix). materializeProfile(profile,
harness) → WorkspacePlan {files, env, flags, unsupported}: the exact native files to
write into the run cwd, plus env/flags for harnesses with NO cwd auto-discovery.
Verified per-harness routing (the thing a wrong path silently breaks):
- context file is per-harness: claude/nanoclaw→CLAUDE.md, gemini→GEMINI.md (NOT AGENTS.md;
current not legacy), hermes→HERMES.md, rest→AGENTS.md.
- skills: .{claude,codex,opencode,kimi,pi,gemini}/skills/<n>/SKILL.md; openclaw→skills/;
hermes has NO cwd skill dir (fail-closed).
- mcp: claude .mcp.json+settings enable, codex [mcp_servers] (trust), opencode opencode.json,
gemini settings.json mcpServers, kimi --mcp-config-file FLAG, pi/hermes/openclaw fail-closed.
- hooks/subagents/commands: native files where supported; per-harness FORMAT (claude/gemini
settings.json, codex toml/json, opencode JS plugin) — emitted correctly, not one shape.
FAIL-CLOSED, never silent-drop: a dimension with no cwd path on the chosen harness is
recorded in plan.unsupported with the reason (the "looks provisioned, loads nothing" trap).
JSON multi-writer files (claude/gemini settings.json from mcp+hooks) are merged, not clobbered.
Pure, no IO — golden-file tested across all 9 harnesses (11/11), typechecks clean. This is
the ONE materializer the cli-bridge host spawn, the agent-dev-container box, and VB will all
consume. Next: wire into the backend spawn + register openclaw/hermes/nanoclaw backends +
canary each harness×capability (secret-word skill / MCP tool-call n>0 / hook sentinel).
…e IO consumer) The IO sibling of the pure materializeProfile: writes every plan file (creating parent dirs) into the workspace and returns the env + flags the spawn must apply. This is what the cli-bridge host spawn, the agent-dev-container box, and VB all call. PROVEN LIVE end-to-end: a skill materialized by materializeProfile + applied here is actually loaded by real harnesses through cli-bridge — claude-code, kimi-code, pi each returned a secret that exists ONLY inside the materialized SKILL.md file (the manipulation check; opencode invokes the skill tool but ends its turn on the call). 12/12 tests.
… Protocol)
cli-bridge had no ACP client; the existing backends spawn a CLI and parse stdout, but
hermes and openclaw expose `<bin> acp` — a JSON-RPC 2.0 ndjson stdio server with a
stateful session. AcpBackend drives any such agent:
spawn `<bin> acp` (cwd=req.cwd so it discovers workspace skills/context)
→ initialize → session/new {cwd,mcpServers} → session/prompt {prompt:[text]}
← stream session/update {content:{text}} → ChatDelta.content (message chunks only;
thought/plan/tool chunks skipped so reasoning never pollutes the OpenAI stream)
← session/request_permission → auto-allow (first option; trusted scope)
← result {stopReason} → finish_reason
Registered as hermes + openclaw backends (config bins HERMES_BIN/OPENCLAW_BIN, opt-in
via BRIDGE_BACKENDS; health reports `unavailable` if the binary is absent).
VERIFIED: live end-to-end against `hermes acp` — health ready (v0.10.0), the handshake +
session/prompt streamed real deltas with a clean stop. Wire protocol confirmed by a raw
ndjson probe first. 4/4 unit tests against a mock ACP agent (handshake, message-vs-thought
filtering, permission auto-allow, spawn-error), typechecks clean.
nanoclaw is NOT included here: it is a multi-channel messaging DAEMON (boots a DB +
channels, no `acp`/one-shot stdio mode), so it does not fit the stdio-backend model —
it needs a different integration (its own HTTP/queue interface), tracked separately.
…daemon
NanoClaw isn't a one-shot CLI: it's a long-lived multi-channel agent daemon (Claude Code
under the @onecli-sh Chat SDK) that exposes a CLI channel over a Unix socket
(<nanoclaw>/data/cli.sock). So the proper integration is a socket CLIENT, mirroring
NanoClaw's own scripts/chat.ts:
connect(socket) → write {"text": prompt}\n
← stream {"text": reply}\n lines → ChatDelta.content
completion is SILENCE-based (NanoClaw sends no done event) — ends after silenceMs of
quiet following the first reply, or socket close, or the hard cap.
Registered as the `nanoclaw` backend (NANOCLAW_SOCKET → the daemon's data/cli.sock;
opt-in via BRIDGE_BACKENDS; health=unavailable when the daemon isn't running).
LIMITATION (documented): the daemon owns its own workspace, so NanoClaw does NOT honor a
per-request cwd — workspace skill/profile materialization doesn't reach it (configure
skills on the NanoClaw side). This is inherent to its daemon model, not a shortcut.
Verified: 5/5 tests against a REAL Unix-socket server speaking NanoClaw's exact protocol
(stream-then-silence, socket-close finish, health ready/unavailable). Typechecks clean.
…e 2)
Each spawn backend (claude, codex, opencode, kimi, gemini, pi) now provisions a
profile's CWD-NATIVE dimensions — skills, context, hooks, subagents, commands — into
the run workspace before the harness spawns, via the shared provisionProfileWorkspace
helper. So a profile's skills/hooks/etc. are MOUNTED the way each harness natively
loads them, not prompt-injected.
SAFE + additive:
- MCP is SKIPPED here (materializeProfile {skip:['mcp']}) — cli-bridge's existing
per-harness MCP path (config-dir + env) stays the source of truth, so MCP can't regress.
- The new dimensions are not provisioned today, so writing them into cwd is purely
additive (can't change existing behavior).
- provisionProfileWorkspace is FAIL-SAFE (try/catch): a materialization error returns an
un-provisioned run, never crashes a live request.
PROVEN end-to-end through the REAL backend code: a profile skill provisioned by the
wired KimiBackend was loaded by kimi from cwd (returned the secret that exists only in
the materialized SKILL.md). Typechecks clean; 21/21 existing tests green.
Builds on the materializer (#55) + the new ACP/nanoclaw backends (#56) — this branch
includes both.
# Conflicts: # src/backends/materialize-profile.ts
…s-ref shape
Per review: agent-dev-container already materializes profiles in the cursor provider
(materializeCursorProfileResources) via resources.skills/commands/agents as resource
REFS — not a top-level name→content map. This materializer was diverging on input shape
(a fork waiting to drift). Align to the box's canonical shape so BOTH layers read the
SAME profile and this can be lifted to one shared package (extend, don't fork):
- skills/commands/agents move under `resources` as AgentProfileResourceRef[] (inline
{kind,name,content} | github), matching cursor + the published AgentProfileResources.
- resolveInlineRef resolves inline refs; github refs need async fetch (cursor does this)
so the sync materializer reports them unsupported for the caller to pre-resolve.
- hooks stays top-level (the dimension being promoted); subagents stays canonical
top-level (structured AgentSubagentProfile).
Re-proven end-to-end: a profile with resources.skills (inline ref) provisioned by the
wired KimiBackend was loaded by kimi from cwd. 12/12 materializer tests, full suite green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The complete profile-provisioning system for cli-bridge. Builds on (and includes) #55 (materializer) and #56 (ACP/nanoclaw backends), plus the Phase-2 host wiring.
What's here
materializeProfile(profile, harness)— one materializer, all 6 dimensions (context/skills/mcp/hooks/subagents/commands) × 9 harnesses, verified routing, fail-closed.applyWorkspacePlanwrites it into a cwd. (feat(profile): materializeProfile — one per-harness profile materializer (all dimensions) #55)provisionProfileWorkspacebefore spawn, mounting a profile's cwd-native dimensions (skills, context, hooks, subagents, commands) into the run workspace the way each harness natively loads them.Safe + additive
{skip:['mcp']}) — cli-bridge's existing per-harness MCP path stays the source of truth, so MCP can't regress.provisionProfileWorkspaceis fail-safe (try/catch): a materialization error yields an un-provisioned run, never a crashed request.Proven, not claimed
materializeProfile→ a skill loads in claude/kimi/pi through the bridge (secret-in-file canary). (feat(profile): materializeProfile — one per-harness profile materializer (all dimensions) #55)Remaining (separate, canary-gated)
mcpServers:[]drop).composeWorkerSkills.🤖 Generated with Claude Code