Align runtime adapters with installed CLIs

This commit is contained in:
thePR0M3TH3AN committed 2026-09-27 20:09:24 -04:00
1 parent 2e0f9dc9ff
commit d8ab841e5b
6 files changed
+73 -33

No files matched your search

@@ -102,11 +102,11 @@ test_integrity_note:
- id: SCN-claude-adapter
given: a Claude runtime adapter with a virtualized command runner
when: sessions are planned and runtime state is queried
then: command construction is deterministic, capabilities are explicit, planning launches no process, and stop requires matching live session and worktree ownership evidence
then: background creation omits the ignored synthetic session flag, captures the sole safe manager-assigned ID, launches no process during planning, and stop requires matching live session and worktree ownership evidence
- id: SCN-codex-adapter
given: a Codex runtime adapter with a virtualized command runner and an identity-bound TORCH control plane
when: create, resume, MCP configuration, runtime-ID capture, and steering are planned or attempted
then: no provider call occurs, thread IDs come from JSONL, every message is durable, and unsupported native controls fail explicitly
then: no provider call occurs, mutually exclusive safety flags are absent, global options precede exec and resume options follow resume, thread IDs come from JSONL, every message is durable, and unsupported native controls fail explicitly
- id: SCN-fleet-fresh-resume
given: approved identities and managed worktrees with a virtualized Claude executor
when: the fleet starts fresh, winds down safely, and plans its next start
@@ -267,6 +267,7 @@ test_integrity_note:
- SQLite state after process restart
- CLI and MCP output over real process boundaries
- runtime command plans and execution order
- installed-CLI-compatible option sets, subcommand ordering, and manager-assigned Claude background IDs
- runtime stop session identity, reported working directory, and fail-closed ownership mismatch
- local resume snapshot, tracked resume brief, final worker message, preserved task identity, and presence transitions
- immutable check receipt commit and result
@@ -312,6 +313,8 @@ test_integrity_note:
- repository content redirecting machine-local database writes
- MCP and CLI drifting onto separate business logic
- launch plans that execute provider commands during preview
- virtualized tests blessing commands that the installed provider CLI rejects during parsing
- a synthetic Claude UUID being persisted when background mode ignored it
- stopping a runtime by PID, parentage, or session label without matching managed-worktree evidence
- a mixed-runtime claim implemented as renamed Claude-only commands
- runtime sessions launching without their identity-bound TORCH tool surface
+16 -4
View File
@@ -42,6 +42,16 @@ function mcpName(areaId) {
return `torch-${areaId.toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-|-$/g, '')}`;
}
function backgroundSessionId(output) {
const lines = String(output ?? '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
if (lines.length !== 1 || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(lines[0])) {
throw new TorchError('Claude background launch did not return exactly one safe session ID', {
code: 'RUNTIME_ID_NOT_CAPTURED', details: { adapter: 'claude' },
});
}
return lines[0];
}
export class ClaudeRuntimeAdapter {
constructor({
env = process.env,
@@ -96,12 +106,12 @@ export class ClaudeRuntimeAdapter {
}
createSession({ areaId, title, worktree, promptFile, firstMessage, model = 'opus', background = true, mcp } = {}) {
const runtimeSessionId = this.idFactory();
const runtimeSessionId = background ? null : this.idFactory();
const args = [];
if (background) args.push('--bg');
args.push(
'--model', text(model, 'model'),
'--session-id', runtimeSessionId,
...(!background ? ['--session-id', runtimeSessionId] : []),
'-n', `TORCH · ${text(title ?? areaId, 'title')}`,
'--append-system-prompt-file', text(promptFile, 'promptFile'),
...mcpArgs(mcp),
@@ -109,6 +119,7 @@ export class ClaudeRuntimeAdapter {
if (firstMessage) args.push(text(firstMessage, 'firstMessage'));
return {
adapter: this.name, areaId: text(areaId, 'areaId'), runtimeSessionId,
requiresRuntimeIdCapture: background,
launch: commandRecord(this.executable, args, text(worktree, 'worktree')),
};
}
@@ -188,8 +199,9 @@ export class ClaudeRuntimeAdapter {
};
}
captureRuntimeId({ areaId, runtimeSessionId } = {}) {
return { areaId: text(areaId, 'areaId'), runtimeSessionId: text(runtimeSessionId, 'runtimeSessionId') };
captureRuntimeId({ areaId, runtimeSessionId, stdout, output } = {}) {
const captured = runtimeSessionId ?? backgroundSessionId(stdout ?? output);
return { areaId: text(areaId, 'areaId'), runtimeSessionId: text(captured, 'runtimeSessionId') };
}
installHooks() {
+5 -3
View File
@@ -113,8 +113,9 @@ export class CodexRuntimeAdapter {
const id = text(areaId, 'areaId');
const cwd = text(worktree, 'worktree');
const args = [
'exec', '--json', '--cd', cwd, '--sandbox', 'workspace-write', '--approve-for-me',
'--cd', cwd, '--approve-for-me',
...mcpArgs(mcp),
'exec', '--json',
...optionalModelArgs(model),
`${text(firstMessage, 'firstMessage')}\n\nRead and follow the TORCH domain prompt at ${text(promptFile, 'promptFile')}.`,
];
@@ -133,9 +134,10 @@ export class CodexRuntimeAdapter {
const sessionId = text(runtimeSessionId, 'runtimeSessionId');
const cwd = text(worktree, 'worktree');
const args = [
'exec', '--json', '--cd', cwd, '--sandbox', 'workspace-write', '--approve-for-me',
'--cd', cwd, '--approve-for-me',
...mcpArgs(mcp),
...optionalModelArgs(model), 'resume', sessionId, text(message, 'message'),
'exec', 'resume', '--json',
...optionalModelArgs(model), sessionId, text(message, 'message'),
];
return {
adapter: this.name,
+8 -4
View File
@@ -76,12 +76,16 @@ test('SCN-bootstrap-acceptance: one installed fixture proves the complete organi
assert.equal(existsSync(join(worktree.path, '.torch', 'prompts', `${agent.areaId}.md`)), true);
}
let runtime = 0;
const adapter = createClaudeAdapter({ idFactory: () => `bootstrap-runtime-${++runtime}` });
const up = planFleetUp({ repositoryRoot: context.root, controlPlane: control, adapter, fresh: true });
const adapter = createClaudeAdapter();
const adapters = new Map([['claude', adapter]]);
const up = planFleetUp({ repositoryRoot: context.root, controlPlane: control, adapters, fresh: true });
assert.equal(up.canProceed, true, JSON.stringify(up.blockers));
assert.equal(up.actions.at(-1).areaId, 'session-manager');
const started = startFleet({ plan: up, controlPlane: control, executor: () => ({ status: 0 }) });
let runtime = 0;
const started = startFleet({
plan: up, controlPlane: control, adapters,
executor: () => ({ status: 0, stdout: `bootstrap-runtime-${++runtime}\n` }),
});
assert.equal(new Set(started.started.map((entry) => entry.runtimeSessionId)).size, roster.agents.length);
for (const action of up.actions) {
assert.match(readFileSync(action.promptFile, 'utf8'), new RegExp(`Area ID: ${action.areaId}|TORCH Session Manager`));
+17 -14
View File
@@ -46,9 +46,9 @@ function fleetFixture({ workerRuntime = 'claude' } = {}) {
test('SCN-fleet-fresh-resume: workers start before manager and stable identities resume captured runtime sessions', () => {
const fixture = fleetFixture();
const control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
let session = 0;
const adapter = createClaudeAdapter({ executable: 'claude', idFactory: () => `runtime-${++session}` });
const fresh = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapter, fresh: true });
const adapter = createClaudeAdapter({ executable: 'claude' });
const adapters = new Map([['claude', adapter]]);
const fresh = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapters, fresh: true });
assert.equal(fresh.actions.at(-1).areaId, 'session-manager');
assert.equal(fresh.actions.every((action) => action.mode === 'create'), true);
assert.equal(fresh.actions.every((action) => action.mcp.args.includes(action.areaId)), true);
@@ -58,8 +58,11 @@ test('SCN-fleet-fresh-resume: workers start before manager and stable identities
const launches = [];
const started = startFleet({
plan: fresh, controlPlane: control,
executor: (launch) => { launches.push(launch); return { status: 0 }; },
plan: fresh, controlPlane: control, adapters,
executor: (launch) => {
launches.push(launch);
return { status: 0, stdout: `runtime-${launches.length}\n` };
},
});
assert.equal(started.started.at(-1).areaId, 'session-manager');
assert.equal(launches.length, fresh.actions.length);
@@ -97,13 +100,13 @@ test('SCN-fleet-fresh-resume: workers start before manager and stable identities
}).stateRoot, 'project.json');
assert.equal(JSON.parse(readFileSync(metadataPath, 'utf8')).detachedAt, detached.detachedAt);
const resumed = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapter, fresh: false });
const resumed = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapters, fresh: false });
assert.equal(resumed.actions.every((action) => action.mode === 'resume'), true);
assert.deepEqual(
resumed.actions.map((action) => action.runtimeSessionId),
fresh.actions.map((action) => action.runtimeSessionId),
started.started.map((action) => action.runtimeSessionId),
);
startFleet({ plan: resumed, controlPlane: control, executor: () => ({ status: 0 }) });
startFleet({ plan: resumed, controlPlane: control, adapters, executor: () => ({ status: 0 }) });
assert.equal(JSON.parse(readFileSync(metadataPath, 'utf8')).detachedAt, null);
control.close();
});
@@ -111,8 +114,7 @@ test('SCN-fleet-fresh-resume: workers start before manager and stable identities
test('SCN-fleet-wind-down-safety: active work blocks runtime shutdown and failed startup prevents manager launch', () => {
const fixture = fleetFixture();
const control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
let session = 0;
const adapter = createClaudeAdapter({ idFactory: () => `failure-${++session}` });
const adapter = createClaudeAdapter();
const plan = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapter, fresh: true });
assert.throws(
() => startFleet({
@@ -148,7 +150,7 @@ test('SCN-mixed-runtime: Codex and Claude share stable identities while planning
const fixture = fleetFixture({ workerRuntime: 'codex' });
const control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
const adapters = new Map([
['claude', createClaudeAdapter({ idFactory: () => 'claude-manager-1' })],
['claude', createClaudeAdapter()],
['codex', createCodexAdapter({ executable: 'codex' })],
]);
const plan = planFleetUp({
@@ -157,18 +159,19 @@ test('SCN-mixed-runtime: Codex and Claude share stable identities while planning
assert.equal(plan.canProceed, true);
assert.equal(plan.actions[0].areaId, fixture.worker);
assert.equal(plan.actions[0].runtime, 'codex');
assert.equal(plan.actions[0].launch.args[0], 'exec');
assert.equal(plan.actions[0].launch.args.includes('exec'), true);
assert.equal(plan.actions[0].launch.args.some((arg) => arg.includes('mcp_servers.')), true);
assert.equal(plan.actions[0].launch.args.some((arg) => arg.includes(fixture.worker)), true);
assert.equal(plan.actions.at(-1).runtime, 'claude');
assert.equal(plan.actions.at(-1).launch.args.includes('--mcp-config'), true);
assert.equal(plan.actions.at(-1).runtimeSessionId, 'claude-manager-1');
assert.equal(plan.actions.at(-1).runtimeSessionId, null);
assert.equal(plan.actions.at(-1).requiresRuntimeIdCapture, true);
const started = startFleet({
plan, controlPlane: control, adapters,
executor: (launch) => launch.areaId === fixture.worker
? { status: 0, stdout: '{"type":"thread.started","thread_id":"codex-worker-1"}\n' }
: { status: 0 },
: { status: 0, stdout: 'claude-manager-1\n' },
});
assert.deepEqual(started.started.map((item) => item.runtimeSessionId), [
'codex-worker-1', 'claude-manager-1',
+22 -6
View File
@@ -85,15 +85,24 @@ test('SCN-claude-adapter: planning is deterministic, capabilities are honest, an
areaId: context.worker, title: 'Core', worktree: '/tmp/core', promptFile: '/tmp/core.md',
firstMessage: 'Resume TASK-1.', model: 'opus', mcp: configuration.mcp,
});
assert.equal(launch.runtimeSessionId, 'runtime-123');
assert.deepEqual(launch.launch.args.slice(0, 7), [
'--bg', '--model', 'opus', '--session-id', 'runtime-123', '-n', 'TORCH · Core',
assert.equal(launch.runtimeSessionId, null);
assert.equal(launch.requiresRuntimeIdCapture, true);
assert.deepEqual(launch.launch.args.slice(0, 5), [
'--bg', '--model', 'opus', '-n', 'TORCH · Core',
]);
assert.equal(launch.launch.args.includes('--session-id'), false);
const mcpIndex = launch.launch.args.indexOf('--mcp-config');
const launchMcp = JSON.parse(launch.launch.args[mcpIndex + 1]).mcpServers[`torch-${context.worker}`];
assert.equal(launchMcp.command, process.execPath);
assert.equal(launchMcp.args.at(-1), context.worker);
assert.equal(calls.length, 0, 'creating a launch plan must not execute Claude');
assert.equal(adapter.captureRuntimeId({
areaId: context.worker, stdout: 'runtime-123\n',
}).runtimeSessionId, 'runtime-123');
assert.throws(
() => adapter.captureRuntimeId({ areaId: context.worker, stdout: 'starting\nruntime-123\n' }),
(error) => error.code === 'RUNTIME_ID_NOT_CAPTURED',
);
assert.equal(adapter.getStatus({ runtimeSessionId: 'runtime-123' }).status, 'idle');
assert.equal(calls.length, 1);
assert.throws(() => adapter.installHooks(), (error) => error.code === 'RUNTIME_CAPABILITY_UNSUPPORTED');
@@ -153,8 +162,11 @@ test('SCN-codex-adapter: turns are resumable, identity-bound, durable, and capab
assert.equal(launch.runtimeSessionId, null);
assert.equal(launch.requiresRuntimeIdCapture, true);
assert.equal(launch.completionState, 'idle');
assert.deepEqual(launch.launch.args.slice(0, 7), [
'exec', '--json', '--cd', '/tmp/codex-core', '--sandbox', 'workspace-write', '--approve-for-me',
assert.deepEqual(launch.launch.args.slice(0, 3), ['--cd', '/tmp/codex-core', '--approve-for-me']);
assert.equal(launch.launch.args.includes('--sandbox'), false);
assert.equal(launch.launch.args.indexOf('--approve-for-me') < launch.launch.args.indexOf('exec'), true);
assert.deepEqual(launch.launch.args.slice(launch.launch.args.indexOf('exec'), launch.launch.args.indexOf('exec') + 2), [
'exec', '--json',
]);
assert.equal(launch.launch.args.includes(`mcp_servers.torch-${context.worker}.command=${JSON.stringify(process.execPath)}`), true);
assert.equal(launch.launch.args.some((arg) => arg.includes(MCP_SERVER)), true);
@@ -168,7 +180,11 @@ test('SCN-codex-adapter: turns are resumable, identity-bound, durable, and capab
areaId: context.worker, runtimeSessionId: 'codex-thread-123', worktree: '/tmp/codex-core',
message: 'Continue.', mcp: configuration.mcp,
});
assert.equal(resumed.launch.args.includes('resume'), true);
assert.equal(resumed.launch.args.includes('--sandbox'), false);
assert.equal(resumed.launch.args.indexOf('--cd') < resumed.launch.args.indexOf('exec'), true);
assert.equal(resumed.launch.args.indexOf('--approve-for-me') < resumed.launch.args.indexOf('exec'), true);
assert.equal(resumed.launch.args.indexOf('exec') < resumed.launch.args.indexOf('resume'), true);
assert.equal(resumed.launch.args.indexOf('resume') < resumed.launch.args.indexOf('--json'), true);
assert.equal(resumed.launch.args.includes('codex-thread-123'), true);
assert.equal(resumed.launch.args.some((arg) => arg.includes(MCP_SERVER)), true);