mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-28 08:21:53 +00:00
Inject TORCH tools into runtime launches
This commit is contained in:
1 parent
3efdbd258f
commit
bcbe2fc319
8 files changed
+91
-24
No files matched your search
@@ -40,6 +40,8 @@ and the first Claude reference-fleet path:
|
||||
- an explicit-capability Claude adapter with durable-message fallback;
|
||||
- an explicit-capability Codex adapter with resumable JSONL thread capture;
|
||||
- per-domain mixed Claude/Codex runtime planning and identity-bound MCP registration plans;
|
||||
- invocation-scoped MCP injection on every Claude/Codex create and resume
|
||||
launch, without changing the user's global runtime configuration;
|
||||
- durable-first Codex steering with native `queue` notification when available;
|
||||
- worker-first/Session-Manager-last fresh start and resume planning;
|
||||
- safe wind-down with active-work refusal and local resume snapshots.
|
||||
|
||||
@@ -1482,9 +1482,13 @@ Exit gate: Claude and Codex occupy persistent domains in one Fleet and
|
||||
coordinate through TORCH rather than native vendor messaging.
|
||||
|
||||
Implementation checkpoint (2026-09-27): the Codex adapter, mixed-runtime
|
||||
planning, identity-bound MCP configuration plans, JSONL runtime-ID capture,
|
||||
durable-first steering, and explicit degraded capabilities are implemented and
|
||||
scenario-tested with virtualized runners. The exit gate remains open until an
|
||||
planning, invocation-scoped identity-bound MCP configuration for every create
|
||||
and resume launch, JSONL runtime-ID capture, durable-first steering, and
|
||||
explicit degraded capabilities are implemented and scenario-tested with
|
||||
virtualized runners plus a real local stdio MCP handshake. Claude receives an
|
||||
ephemeral `--mcp-config`; Codex receives invocation-local `mcp_servers`
|
||||
overrides, so TORCH does not rewrite either runtime's global configuration.
|
||||
The exit gate remains open until an
|
||||
owner-authorized live Claude/Codex acceptance run demonstrates coordination;
|
||||
the automated suite intentionally incurs no provider usage.
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ test_integrity_note:
|
||||
- id: SCN-mixed-runtime
|
||||
given: one approved Codex worker domain and one Claude Session Manager with stable Fleet identities
|
||||
when: the mixed fleet starts and winds down through virtualized runtime boundaries
|
||||
then: provider-specific commands and runtime IDs remain separate, the manager starts last, Codex completes idle, and only resident processes are stopped
|
||||
then: provider-specific commands and runtime IDs remain separate, every create and resume launch receives identity-bound TORCH MCP access, the manager starts last, Codex completes idle, and only resident processes are stopped
|
||||
- id: SCN-backlog-lifecycle
|
||||
given: tracked tasks in the Session Manager worktree with domain owners, dependencies, and an integration lookup
|
||||
when: tasks move through assignment, work, blocking, verification, readiness, landing, and completion
|
||||
@@ -296,6 +296,7 @@ test_integrity_note:
|
||||
- MCP and CLI drifting onto separate business logic
|
||||
- launch plans that execute provider commands during preview
|
||||
- a mixed-runtime claim implemented as renamed Claude-only commands
|
||||
- runtime sessions launching without their identity-bound TORCH tool surface
|
||||
- lost Codex thread IDs or native steering that bypasses durable messaging
|
||||
- manager-first startup or manager-first shutdown
|
||||
- passing receipts detached from the commit actually measured
|
||||
|
||||
+14
-3
@@ -28,6 +28,16 @@ function commandRecord(command, args, cwd) {
|
||||
return { command, args, cwd, mutatesRuntime: true };
|
||||
}
|
||||
|
||||
function mcpArgs(mcp) {
|
||||
if (!mcp) return [];
|
||||
if (typeof mcp.name !== 'string' || typeof mcp.command !== 'string' || !Array.isArray(mcp.args)) {
|
||||
throw new TorchError('Claude MCP launch configuration is invalid', { code: 'INVALID_RUNTIME_INPUT' });
|
||||
}
|
||||
return ['--mcp-config', JSON.stringify({
|
||||
mcpServers: { [mcp.name]: { command: mcp.command, args: mcp.args } },
|
||||
})];
|
||||
}
|
||||
|
||||
function mcpName(areaId) {
|
||||
return `torch-${areaId.toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-|-$/g, '')}`;
|
||||
}
|
||||
@@ -85,7 +95,7 @@ export class ClaudeRuntimeAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
createSession({ areaId, title, worktree, promptFile, firstMessage, model = 'opus', background = true } = {}) {
|
||||
createSession({ areaId, title, worktree, promptFile, firstMessage, model = 'opus', background = true, mcp } = {}) {
|
||||
const runtimeSessionId = this.idFactory();
|
||||
const args = [];
|
||||
if (background) args.push('--bg');
|
||||
@@ -94,6 +104,7 @@ export class ClaudeRuntimeAdapter {
|
||||
'--session-id', runtimeSessionId,
|
||||
'-n', `TORCH · ${text(title ?? areaId, 'title')}`,
|
||||
'--append-system-prompt-file', text(promptFile, 'promptFile'),
|
||||
...mcpArgs(mcp),
|
||||
);
|
||||
if (firstMessage) args.push(text(firstMessage, 'firstMessage'));
|
||||
return {
|
||||
@@ -102,10 +113,10 @@ export class ClaudeRuntimeAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
resumeSession({ areaId, runtimeSessionId, worktree, model = 'opus', message, background = true } = {}) {
|
||||
resumeSession({ areaId, runtimeSessionId, worktree, model = 'opus', message, background = true, mcp } = {}) {
|
||||
const args = [];
|
||||
if (background) args.push('--bg');
|
||||
args.push('--model', text(model, 'model'), '--resume', text(runtimeSessionId, 'runtimeSessionId'));
|
||||
args.push('--model', text(model, 'model'), ...mcpArgs(mcp), '--resume', text(runtimeSessionId, 'runtimeSessionId'));
|
||||
if (message) args.push(text(message, 'message'));
|
||||
return {
|
||||
adapter: this.name, areaId: text(areaId, 'areaId'), runtimeSessionId,
|
||||
|
||||
+19
-5
@@ -31,6 +31,18 @@ function commandRecord(command, args, cwd) {
|
||||
return { command, args, cwd, mutatesRuntime: true };
|
||||
}
|
||||
|
||||
function mcpArgs(mcp) {
|
||||
if (!mcp) return [];
|
||||
if (typeof mcp.name !== 'string' || !/^[a-z0-9_-]+$/.test(mcp.name)
|
||||
|| typeof mcp.command !== 'string' || !Array.isArray(mcp.args)) {
|
||||
throw new TorchError('Codex MCP launch configuration is invalid', { code: 'INVALID_RUNTIME_INPUT' });
|
||||
}
|
||||
return [
|
||||
'-c', `mcp_servers.${mcp.name}.command=${JSON.stringify(mcp.command)}`,
|
||||
'-c', `mcp_servers.${mcp.name}.args=${JSON.stringify(mcp.args)}`,
|
||||
];
|
||||
}
|
||||
|
||||
function mcpName(areaId) {
|
||||
return `torch-${areaId.toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-|-$/g, '')}`;
|
||||
}
|
||||
@@ -89,19 +101,20 @@ export class CodexRuntimeAdapter {
|
||||
mutationPerformed: false,
|
||||
mcp: {
|
||||
name: mcpName(id),
|
||||
command: this.executable,
|
||||
args: ['mcp', 'add', mcpName(id), '--', this.nodeExecutable, entry, '--root', root, '--area', id],
|
||||
command: this.nodeExecutable,
|
||||
args: [entry, '--root', root, '--area', id],
|
||||
cwd: root,
|
||||
},
|
||||
note: 'Apply this identity-bound MCP registration only through an owner-approved Codex configuration change.',
|
||||
note: 'Pass this identity-bound MCP server as an invocation-scoped Codex configuration.',
|
||||
};
|
||||
}
|
||||
|
||||
createSession({ areaId, worktree, promptFile, firstMessage, model } = {}) {
|
||||
createSession({ areaId, worktree, promptFile, firstMessage, model, mcp } = {}) {
|
||||
const id = text(areaId, 'areaId');
|
||||
const cwd = text(worktree, 'worktree');
|
||||
const args = [
|
||||
'exec', '--json', '--cd', cwd, '--sandbox', 'workspace-write', '--approve-for-me',
|
||||
...mcpArgs(mcp),
|
||||
...optionalModelArgs(model),
|
||||
`${text(firstMessage, 'firstMessage')}\n\nRead and follow the TORCH domain prompt at ${text(promptFile, 'promptFile')}.`,
|
||||
];
|
||||
@@ -115,12 +128,13 @@ export class CodexRuntimeAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
resumeSession({ areaId, runtimeSessionId, worktree, message, model } = {}) {
|
||||
resumeSession({ areaId, runtimeSessionId, worktree, message, model, mcp } = {}) {
|
||||
const id = text(areaId, 'areaId');
|
||||
const sessionId = text(runtimeSessionId, 'runtimeSessionId');
|
||||
const cwd = text(worktree, 'worktree');
|
||||
const args = [
|
||||
'exec', '--json', '--cd', cwd, '--sandbox', 'workspace-write', '--approve-for-me',
|
||||
...mcpArgs(mcp),
|
||||
...optionalModelArgs(model), 'resume', sessionId, text(message, 'message'),
|
||||
];
|
||||
return {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { TorchError } from '../kernel/errors.mjs';
|
||||
import { readInstallManifest } from '../kernel/install.mjs';
|
||||
import { loadFleetDefinition } from '../kernel/worktrees.mjs';
|
||||
import { validateRuntimeAdapter } from '../adapters/runtime.mjs';
|
||||
|
||||
const MCP_ENTRY = fileURLToPath(new URL('../mcp/server.mjs', import.meta.url));
|
||||
|
||||
function localStateRoot(manifest) {
|
||||
const record = (manifest.external ?? []).find((entry) => entry.type === 'local-state');
|
||||
if (!record?.path) throw new TorchError('Installation manifest has no local state root', { code: 'LOCAL_STATE_MISSING' });
|
||||
@@ -140,6 +143,15 @@ export function planFleetUp({ repositoryRoot, controlPlane, adapter, adapters, f
|
||||
}
|
||||
validateRuntimeAdapter(runtimeAdapter);
|
||||
const identity = controlPlane.identity(area.id);
|
||||
const runtimeIntegration = runtimeAdapter.configure({
|
||||
repositoryRoot, areaId: area.id, mcpEntry: MCP_ENTRY,
|
||||
});
|
||||
if (runtimeIntegration?.mutationPerformed !== false || !runtimeIntegration?.mcp) {
|
||||
blockers.push({
|
||||
areaId: area.id, code: 'RUNTIME_CONFIGURATION_INVALID', runtime,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const prompts = promptPaths(stateRoot, worktree, area.id);
|
||||
const runtimeConfig = config.runtimes?.[runtime] ?? {};
|
||||
const model = area.model ?? runtimeConfig.model;
|
||||
@@ -148,11 +160,11 @@ export function planFleetUp({ repositoryRoot, controlPlane, adapter, adapters, f
|
||||
const runtimePlan = shouldResume
|
||||
? runtimeAdapter.resumeSession({
|
||||
areaId: area.id, runtimeSessionId: identity.runtimeSessionId, worktree: worktree.path,
|
||||
model, background, message: startupMessage(area.id, false),
|
||||
model, background, message: startupMessage(area.id, false), mcp: runtimeIntegration.mcp,
|
||||
})
|
||||
: runtimeAdapter.createSession({
|
||||
areaId: area.id, title: area.title, worktree: worktree.path, promptFile: prompts.combined,
|
||||
firstMessage: startupMessage(area.id, true), model, background,
|
||||
firstMessage: startupMessage(area.id, true), model, background, mcp: runtimeIntegration.mcp,
|
||||
});
|
||||
actions.push({
|
||||
areaId: area.id, title: area.title, mode: shouldResume ? 'resume' : 'create',
|
||||
@@ -160,7 +172,8 @@ export function planFleetUp({ repositoryRoot, controlPlane, adapter, adapters, f
|
||||
requiresRuntimeIdCapture: runtimePlan.requiresRuntimeIdCapture ?? false,
|
||||
completionState: runtimePlan.completionState ?? 'starting',
|
||||
worktree: worktree.path, branch: worktree.branch, promptFile: prompts.combined,
|
||||
promptSources: [prompts.common, prompts.area], launch: runtimePlan.launch,
|
||||
promptSources: [prompts.common, prompts.area], mcp: runtimeIntegration.mcp,
|
||||
launch: runtimePlan.launch,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,6 +213,16 @@ export function planAreaUp({ repositoryRoot, controlPlane, areaId, adapter, adap
|
||||
}
|
||||
validateRuntimeAdapter(runtimeAdapter);
|
||||
const identity = controlPlane.identity(area.id);
|
||||
const runtimeIntegration = runtimeAdapter.configure({
|
||||
repositoryRoot, areaId: area.id, mcpEntry: MCP_ENTRY,
|
||||
});
|
||||
if (runtimeIntegration?.mutationPerformed !== false || !runtimeIntegration?.mcp) {
|
||||
return {
|
||||
action: 'area-up', projectId: manifest.projectId, areaId, fresh,
|
||||
actions: [], blockers: [{ areaId, code: 'RUNTIME_CONFIGURATION_INVALID', runtime }],
|
||||
canProceed: false, mutationPerformed: false,
|
||||
};
|
||||
}
|
||||
const prompts = promptPaths(localStateRoot(manifest), worktree, area.id);
|
||||
const runtimeConfig = config.runtimes?.[runtime] ?? {};
|
||||
const model = area.model ?? runtimeConfig.model;
|
||||
@@ -208,11 +231,11 @@ export function planAreaUp({ repositoryRoot, controlPlane, areaId, adapter, adap
|
||||
const runtimePlan = shouldResume
|
||||
? runtimeAdapter.resumeSession({
|
||||
areaId: area.id, runtimeSessionId: identity.runtimeSessionId, worktree: worktree.path,
|
||||
model, background, message: startupMessage(area.id, false),
|
||||
model, background, message: startupMessage(area.id, false), mcp: runtimeIntegration.mcp,
|
||||
})
|
||||
: runtimeAdapter.createSession({
|
||||
areaId: area.id, title: area.title, worktree: worktree.path, promptFile: prompts.combined,
|
||||
firstMessage: startupMessage(area.id, true), model, background,
|
||||
firstMessage: startupMessage(area.id, true), model, background, mcp: runtimeIntegration.mcp,
|
||||
});
|
||||
const action = {
|
||||
areaId: area.id, title: area.title, mode: shouldResume ? 'resume' : 'create',
|
||||
@@ -220,7 +243,8 @@ export function planAreaUp({ repositoryRoot, controlPlane, areaId, adapter, adap
|
||||
requiresRuntimeIdCapture: runtimePlan.requiresRuntimeIdCapture ?? false,
|
||||
completionState: runtimePlan.completionState ?? 'starting',
|
||||
worktree: worktree.path, branch: worktree.branch, promptFile: prompts.combined,
|
||||
promptSources: [prompts.common, prompts.area], launch: runtimePlan.launch,
|
||||
promptSources: [prompts.common, prompts.area], mcp: runtimeIntegration.mcp,
|
||||
launch: runtimePlan.launch,
|
||||
};
|
||||
return {
|
||||
action: 'area-up', projectId: manifest.projectId, areaId, fresh,
|
||||
|
||||
@@ -48,6 +48,8 @@ test('SCN-fleet-fresh-resume: workers start before manager and stable identities
|
||||
const fresh = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapter, 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);
|
||||
assert.equal(fresh.actions.every((action) => action.launch.args.includes('--mcp-config')), true);
|
||||
assert.equal(fresh.mutationPerformed, false);
|
||||
|
||||
const launches = [];
|
||||
@@ -133,7 +135,10 @@ test('SCN-mixed-runtime: Codex and Claude share stable identities while planning
|
||||
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.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');
|
||||
|
||||
const started = startFleet({
|
||||
|
||||
@@ -83,12 +83,16 @@ test('SCN-claude-adapter: planning is deterministic, capabilities are honest, an
|
||||
assert.equal(configuration.mcp.args.at(-1), context.worker);
|
||||
const launch = adapter.createSession({
|
||||
areaId: context.worker, title: 'Core', worktree: '/tmp/core', promptFile: '/tmp/core.md',
|
||||
firstMessage: 'Resume TASK-1.', model: 'opus',
|
||||
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',
|
||||
]);
|
||||
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.getStatus({ runtimeSessionId: 'runtime-123' }).status, 'idle');
|
||||
assert.equal(calls.length, 1);
|
||||
@@ -110,14 +114,13 @@ test('SCN-codex-adapter: turns are resumable, identity-bound, durable, and capab
|
||||
repositoryRoot: context.root, areaId: context.worker, mcpEntry: MCP_SERVER,
|
||||
});
|
||||
assert.equal(configuration.mutationPerformed, false);
|
||||
assert.deepEqual(configuration.mcp.args.slice(0, 4), [
|
||||
'mcp', 'add', `torch-${context.worker}`, '--',
|
||||
]);
|
||||
assert.equal(configuration.mcp.command, process.execPath);
|
||||
assert.equal(configuration.mcp.args[0], MCP_SERVER);
|
||||
assert.equal(configuration.mcp.args.includes(context.worker), true);
|
||||
|
||||
const launch = adapter.createSession({
|
||||
areaId: context.worker, worktree: '/tmp/codex-core', promptFile: '/tmp/codex-core.md',
|
||||
firstMessage: 'Resume TASK-2.', model: 'gpt-test',
|
||||
firstMessage: 'Resume TASK-2.', model: 'gpt-test', mcp: configuration.mcp,
|
||||
});
|
||||
assert.equal(launch.runtimeSessionId, null);
|
||||
assert.equal(launch.requiresRuntimeIdCapture, true);
|
||||
@@ -125,6 +128,8 @@ test('SCN-codex-adapter: turns are resumable, identity-bound, durable, and capab
|
||||
assert.deepEqual(launch.launch.args.slice(0, 7), [
|
||||
'exec', '--json', '--cd', '/tmp/codex-core', '--sandbox', 'workspace-write', '--approve-for-me',
|
||||
]);
|
||||
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);
|
||||
assert.equal(calls.length, 0, 'creating a launch plan must not execute Codex');
|
||||
assert.equal(adapter.captureRuntimeId({
|
||||
areaId: context.worker,
|
||||
@@ -133,10 +138,11 @@ test('SCN-codex-adapter: turns are resumable, identity-bound, durable, and capab
|
||||
|
||||
const resumed = adapter.resumeSession({
|
||||
areaId: context.worker, runtimeSessionId: 'codex-thread-123', worktree: '/tmp/codex-core',
|
||||
message: 'Continue.',
|
||||
message: 'Continue.', mcp: configuration.mcp,
|
||||
});
|
||||
assert.equal(resumed.launch.args.includes('resume'), true);
|
||||
assert.equal(resumed.launch.args.includes('codex-thread-123'), true);
|
||||
assert.equal(resumed.launch.args.some((arg) => arg.includes(MCP_SERVER)), true);
|
||||
|
||||
const delivery = adapter.sendOrSteer({
|
||||
controlPlane: control, recipient: context.worker, body: 'Check your durable inbox.',
|
||||
|
||||
Reference in new issue
Block a user