Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
489066e
Add D1 invocation contract and independent task clones
mchwang Sep 24, 2026
d110d81
Harden clone containment traversal and deadline validation
mchwang Sep 24, 2026
17d71d0
Add D2 pinned restricted agent containers
mchwang Sep 24, 2026
124800b
Harden D2 container validation and Linux setup
mchwang Sep 24, 2026
e02995f
Close D2 profile and validation trust gaps
mchwang Sep 24, 2026
4b964a9
Require exact D2 capability and mount profiles
mchwang Sep 24, 2026
0845b6d
Trust D2 helper images and settle cleanup
mchwang Sep 24, 2026
5ccd2aa
Bind D2 resources to their owners
mchwang Sep 24, 2026
b975c7f
Close D2 namespace and cleanup gaps
mchwang Sep 24, 2026
aa1bdbc
Allow clone ownership regression to settle
mchwang Sep 24, 2026
c438462
Pin the D2 Codex state path
mchwang Sep 24, 2026
a4bd301
Seal D2 inputs and complete resource checks
mchwang Sep 24, 2026
353efb0
Add vendor-only egress and phase policy
mchwang Sep 24, 2026
1579033
Enforce invocation-scoped agent policy
mchwang Sep 24, 2026
945e796
Bind adapters and proxy checks to invocation
mchwang Sep 24, 2026
5494bb2
Block agent DNS and harden proxy validation
mchwang Sep 24, 2026
7acc50d
Close remaining network lifecycle gaps
mchwang Sep 24, 2026
981e831
Stabilize live Claude marker probe
mchwang Sep 24, 2026
682e6ae
Add bounded production agent adapters
mchwang Sep 24, 2026
5dd3a85
Harden adapter capture settlement
mchwang Sep 24, 2026
1ab4dab
Pin adapter output to dedicated tmpfs
mchwang Sep 25, 2026
4c68faf
Close remaining adapter lifecycle races
mchwang Sep 25, 2026
3e57ff7
Validate pinned output identities
mchwang Sep 25, 2026
080499c
Make cleanup and decode settlement retryable
mchwang Sep 25, 2026
58a0d42
Abort and await bounded adapter capture
mchwang Sep 25, 2026
38b020d
Retain adapter cleanup ownership
mchwang Sep 25, 2026
9a8ff6a
Allow loaded Docker cleanup observation
mchwang Sep 25, 2026
7c4e098
Secure deferred output acknowledgement
mchwang Sep 25, 2026
abffe7b
Retain colliding cleanup recovery
mchwang Sep 25, 2026
c68aad2
Preserve adapter setup ownership
mchwang Sep 25, 2026
796f3ca
Serialize agent isolation CI tests
mchwang Sep 25, 2026
7b25c0e
Bound cancellation with monotonic deadlines
mchwang Sep 25, 2026
066e92e
Carry invocation ownership through settlement
mchwang Sep 25, 2026
5b4de85
Bound failed network setup cleanup
mchwang Sep 25, 2026
267018d
Guard active profile and close deadline
mchwang Sep 25, 2026
22f362e
Apply adapter timeout across setup
mchwang Sep 25, 2026
0c91f87
Bound adapter cleanup and final stderr
mchwang Sep 25, 2026
da1976e
Keep decoder settlement timers alive
mchwang Sep 25, 2026
ace826c
Retain recovery profile ownership
mchwang Sep 25, 2026
0a0ce54
Revalidate container at launch boundary
mchwang Sep 25, 2026
02ce3e1
Validate profile capability before cleanup
mchwang Sep 25, 2026
158e07d
Preserve cleanup cancellation reasons
mchwang Sep 25, 2026
5506d65
Update cleanup cancellation regression
mchwang Sep 25, 2026
a371bc7
Authenticate profiles at disposal boundary
mchwang Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/agent-isolation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Agent isolation
on:
push:
branches: ['codex/agent-isolation-d2']
pull_request:
paths:
- 'agents/**'
- 'git/clone.ts'
- 'test/agent-*.test.ts'
- '.github/workflows/agent-isolation.yml'
permissions:
contents: read
jobs:
real-docker:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '26.7.0'
cache: npm
- run: npm ci --ignore-scripts
- run: npm run typecheck
- run: npx vitest run --no-file-parallelism test/agent-contract.test.ts test/agent-clone.test.ts test/agent-container.test.ts test/agent-network.test.ts test/agent-policy.test.ts test/agent-adapter.test.ts test/agent-supervisor.test.ts
54 changes: 54 additions & 0 deletions agents/adapters/claude.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { InvocationHandle } from '../contract.ts';
import { createContainerProfile, ProfileCreationCleanupError } from '../container/profile.ts';
import { createVendorNetwork, removeVendorNetwork, VendorNetworkCreationCleanupError,
type VendorNetwork } from '../network/network.ts';
import { createClaudeCommand, createPhasePolicy } from '../policy.ts';
import { retainNetworkCleanup, retainSetupCleanup, startProfileInvocation } from './supervisor.ts';
import { createAdapterInvocationBudget, type AgentAdapterOptions, type AgentAdapterRequest } from './types.ts';

export function parseClaudeOutput(raw: Buffer): { text: string; providerFailed: boolean } {
const envelope = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(raw)) as
{ result?: unknown; is_error?: unknown };
if (typeof envelope.result !== 'string' || typeof envelope.is_error !== 'boolean')
throw new Error('Claude returned a malformed output envelope.');
return Object.freeze({ text: envelope.result, providerFailed: envelope.is_error });
}

export function startClaudeInvocation(request: AgentAdapterRequest,
oauthToken: string, options: AgentAdapterOptions = {}): InvocationHandle {
if (!oauthToken || oauthToken.includes('\0')) throw new Error('Claude OAuth token is malformed.');
const policy = createPhasePolicy(request.invocation);
const remaining = createAdapterInvocationBudget(request.invocation, options.timeoutMs);
let network: VendorNetwork;
try { network = createVendorNetwork(request.invocation, request.imageId, Math.min(60_000, remaining())); }
catch (error) {
if (error instanceof VendorNetworkCreationCleanupError)
return retainSetupCleanup(request.invocation, error.retryCleanup, error.startupError, error,
'network creation cleanup');
throw error;
}
try {
const profile = createContainerProfile({ ...request, policy, network,
command: createClaudeCommand(policy, request.prompt), claudeToken: oauthToken,
timeoutMs: Math.min(60_000, remaining()) });
return startProfileInvocation(profile, { ...options, secrets: { CLAUDE_CODE_OAUTH_TOKEN: oauthToken },
invocationBudget: remaining,
decode: (_profile, raw) => parseClaudeOutput(raw) });
} catch (error) {
if (error instanceof ProfileCreationCleanupError) {
const retryCleanup = (networkTimeoutMs = 30_000) => {
const failures: unknown[] = [];
try { error.retryCleanup(); } catch (cleanupError) { failures.push(cleanupError); }
try { removeVendorNetwork(network, networkTimeoutMs); } catch (cleanupError) { failures.push(cleanupError); }
if (failures.length) throw new AggregateError(failures, 'Adapter setup cleanup did not settle.');
};
try { retryCleanup(Math.min(30_000, remaining())); }
catch (cleanupError) { return retainSetupCleanup(request.invocation, () => retryCleanup(),
error.startupError, cleanupError, 'profile and network cleanup'); }
throw error.startupError;
}
try { removeVendorNetwork(network, Math.min(30_000, remaining())); }
catch (cleanupError) { return retainNetworkCleanup(request.invocation, network, error, cleanupError); }
throw error;
Comment thread
mchwang marked this conversation as resolved.
}
}
57 changes: 57 additions & 0 deletions agents/adapters/codex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { InvocationHandle } from '../contract.ts';
import { createContainerProfile, ProfileCreationCleanupError } from '../container/profile.ts';
import { createVendorNetwork, removeVendorNetwork, VendorNetworkCreationCleanupError,
type VendorNetwork } from '../network/network.ts';
import { createCodexCommand, createPhasePolicy } from '../policy.ts';
import { readBoundedContainerFile, retainNetworkCleanup, retainSetupCleanup,
startProfileInvocation } from './supervisor.ts';
import { createAdapterInvocationBudget, type AgentAdapterOptions, type AgentAdapterRequest } from './types.ts';

export const CODEX_OUTPUT_FILE = '/run/codeboost-output/final.txt';

export async function readCodexOutput(container: string, maximumBytes: number, timeoutMs = 30_000,
signal?: AbortSignal) {
const output = await readBoundedContainerFile(container, CODEX_OUTPUT_FILE, maximumBytes, timeoutMs, signal);
const text = new TextDecoder('utf-8', { fatal: true }).decode(output);
return Object.freeze({ text, additionalBytes: output.length });
}

export function startCodexInvocation(request: AgentAdapterRequest,
authFile: string, options: AgentAdapterOptions = {}): InvocationHandle {
if (!authFile || authFile.includes('\0')) throw new Error('Codex auth path is malformed.');
const policy = createPhasePolicy(request.invocation);
const remaining = createAdapterInvocationBudget(request.invocation, options.timeoutMs);
let network: VendorNetwork;
try { network = createVendorNetwork(request.invocation, request.imageId, Math.min(60_000, remaining())); }
catch (error) {
if (error instanceof VendorNetworkCreationCleanupError)
return retainSetupCleanup(request.invocation, error.retryCleanup, error.startupError, error,
'network creation cleanup');
throw error;
}
try {
const profile = createContainerProfile({ ...request, policy, network,
command: createCodexCommand(policy, request.prompt), codexAuthFile: authFile, deferredOutput: true,
timeoutMs: Math.min(60_000, remaining()) });
return startProfileInvocation(profile, { ...options,
invocationBudget: remaining,
decode: (current, _raw, maximum, timeoutMs, signal) =>
readCodexOutput(current.name, maximum, timeoutMs, signal) });
} catch (error) {
if (error instanceof ProfileCreationCleanupError) {
const retryCleanup = (networkTimeoutMs = 30_000) => {
const failures: unknown[] = [];
try { error.retryCleanup(); } catch (cleanupError) { failures.push(cleanupError); }
try { removeVendorNetwork(network, networkTimeoutMs); } catch (cleanupError) { failures.push(cleanupError); }
if (failures.length) throw new AggregateError(failures, 'Adapter setup cleanup did not settle.');
};
try { retryCleanup(Math.min(30_000, remaining())); }
catch (cleanupError) { return retainSetupCleanup(request.invocation, () => retryCleanup(),
error.startupError, cleanupError, 'profile and network cleanup'); }
throw error.startupError;
}
try { removeVendorNetwork(network, Math.min(30_000, remaining())); }
catch (cleanupError) { return retainNetworkCleanup(request.invocation, network, error, cleanupError); }
throw error;
Comment thread
mchwang marked this conversation as resolved.
}
}
Loading
Loading