claude-code: launched over stdio (ADR 0193), the console's five tools in its instructions (ADR 0195)

Every bundle is now a child speaking MCP over stdio, so stdout is the channel: the module logs on
stderr. The managed CLAUDE.md teaches mesh_search, mesh_describe, mesh_call, mesh_overview and
mesh_machine with addresses (<seat>.<verb>, <node>/<module>.<tool>) instead of flat tool names.
A hand-over is applied whatever the trailing render says; a failed render is reported beside it.
Proven over stdio as the runtime drives it: five tools listed, a key made on first use, a sealed
switch writing an access-token-only 0600 credentials file that keeps unknown keys.
This commit is contained in:
jochen
2026-10-03 23:41:01 +02:00
parent f42b58f789
commit eab335b755
3 changed files with 27 additions and 14 deletions
+14 -7
View File
@@ -1,6 +1,7 @@
// claude-code's tools (novox/hq design 36, ADR 0183). Served by the node's tool runtime, which runs as
// the operator account; this bundle is given its state directory and two files the mesh renders into it
// (ADR 0192), and the runtime's own words — the operator's account and home among them.
// claude-code's tools (novox/hq design 36, ADR 0183). A bundle the node's runtime launches and speaks MCP
// to over stdio (ADR 0193), as the operator account; it is given its state directory and two files the
// mesh renders into it (ADR 0192), and the runtime's own words — the operator's account and home among
// them. **stdout is the MCP channel**: everything this module says, it says on stderr.
//
// Every time the runtime collects these tools, the managed directory is rendered: written only when its
// content changed, through the account's escalation, because /etc is root's. The credentials file under
@@ -122,8 +123,14 @@ function apply(p: Paths, args: Record<string, unknown>): Record<string, unknown>
writeCredentials(credentialsPath(p), withGrant(local, grant));
}
writeFileSync(bindingPath(p), JSON.stringify({ licence: handed.licence, kind: handed.kind }) + "\n", { mode: 0o600 });
// An API-key binding adds the key-helper to the managed settings; a subscription takes it away.
const rendered = renderNow(p);
// An API-key binding adds the key-helper to the managed settings; a subscription takes it away. The
// licence is applied whatever the render says; a render that fails is reported beside it, not instead.
let rendered: string[] | { failed: string };
try {
rendered = renderNow(p);
} catch (err) {
rendered = { failed: err instanceof Error ? err.message : String(err) };
}
return { applied: true, licence: handed.licence, kind: handed.kind, source, rendered };
}
@@ -209,10 +216,10 @@ registerModuleTools("claude-code", (env) => {
if (!p) return [];
try {
keypair(p);
for (const line of renderNow(p)) if (!line.endsWith("unchanged")) console.log(`[claude-code] ${line}`);
for (const line of renderNow(p)) if (!line.endsWith("unchanged")) console.error(`[claude-code] ${line}`);
} catch (err) {
// Said, and the tools still served: claude_code_status and claude_code_render say what is wrong.
console.log(`[claude-code] ${err instanceof Error ? err.message : String(err)}`);
console.error(`[claude-code] ${err instanceof Error ? err.message : String(err)}`);
}
return getClaudeCodeTools(p);
});