Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Trajectory: Finalize and publish E2B home contract

> **Status:** ✅ Completed
> **Task:** sandbox#44
> **Confidence:** 97%
> **Started:** September 1, 2026 at 11:16 AM
> **Completed:** September 1, 2026 at 11:18 AM

---

## Summary

Reconciled sandbox 0.1.12 lock metadata, strengthened E2B preflight validation coverage, and passed 817 tests, typecheck, package smoke, and npm ci.

**Approach:** Standard approach

---

## Key Decisions

### Keep package and lockfile release versions identical
- **Chose:** Keep package and lockfile release versions identical
- **Reasoning:** The manual publish workflow consumes the checked-out lockfile and package metadata, so the 0.1.12 release candidate must be internally consistent.

---

## Chapters

### 1. Work
*Agent: default*

- Keep package and lockfile release versions identical: Keep package and lockfile release versions identical
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"id": "traj_h8qiliylqnav",
"version": 1,
"task": {
"title": "Finalize and publish E2B home contract",
"source": {
"system": "plain",
"id": "sandbox#44"
}
},
"status": "completed",
"startedAt": "2026-09-01T09:16:35.911Z",
"completedAt": "2026-09-01T09:18:23.343Z",
"agents": [
{
"name": "default",
"role": "lead",
"joinedAt": "2026-09-01T09:18:22.563Z"
}
],
"chapters": [
{
"id": "chap_2r53v77klt5l",
"title": "Work",
"agentName": "default",
"startedAt": "2026-09-01T09:18:22.563Z",
"endedAt": "2026-09-01T09:18:23.343Z",
"events": [
{
"ts": 1788254302564,
"type": "decision",
"content": "Keep package and lockfile release versions identical: Keep package and lockfile release versions identical",
"raw": {
"question": "Keep package and lockfile release versions identical",
"chosen": "Keep package and lockfile release versions identical",
"alternatives": [],
"reasoning": "The manual publish workflow consumes the checked-out lockfile and package metadata, so the 0.1.12 release candidate must be internally consistent."
},
"significance": "high"
}
]
}
],
"retrospective": {
"summary": "Reconciled sandbox 0.1.12 lock metadata, strengthened E2B preflight validation coverage, and passed 817 tests, typecheck, package smoke, and npm ci.",
"approach": "Standard approach",
"confidence": 0.97
},
"commits": [],
"filesChanged": [],
"projectId": "AgentWorkforce/sandbox",
"tags": [],
"_trace": {
"startRef": "1455ce7494fbb762bde4595026735e8b366dd0a5",
"endRef": "1455ce7494fbb762bde4595026735e8b366dd0a5"
}
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agent-relay/sandbox",
"version": "0.1.11",
"version": "0.1.12",
Comment thread
khaliqgant marked this conversation as resolved.
"description": "Provider-agnostic sandbox runtimes and orchestration for agent workloads.",
"license": "Apache-2.0",
"type": "module",
Expand Down
43 changes: 43 additions & 0 deletions src/e2b/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,51 @@ describe("E2BSandboxRuntime public contract", () => {
);
assert.equal(calls.create.length, 0);
});

it("must-not-fire: rejects an explicitly empty default home directory", () => {
const { statics, calls } = fakeStatics();

assert.throws(
() => new E2BSandboxRuntime({
apiKey: API_KEY,
template: TEMPLATE,
defaultHomeDir: " ",
sandbox: statics,
}),
/default home directory must not be empty/u,
);
assert.equal(calls.create.length, 0);
});
});

describe("E2BSandboxRuntime launch and lookup", () => {
it("must-fire: carries the template home directory onto launch and lookup handles", async () => {
const { statics } = fakeStatics({
pages: [[sandboxInfo("sbx-existing", "running")]],
});
const runtime = createRuntime(statics, { defaultHomeDir: "/home/user" });

const launched = await runtime.launch();
const [lookedUp] = await runtime.findAllByLabels({ purpose: "worker" });

assert.equal(launched.homeDir, "/home/user");
assert.equal(lookedUp?.homeDir, "/home/user");
});

it("must-not-fire: blank attach and lookup home overrides cannot erase the default home", async () => {
const connected = fakeSandbox({ id: "sbx-attached" });
const { statics } = fakeStatics({
connected: new Map([["sbx-lookup", connected]]),
});
const runtime = createRuntime(statics, { defaultHomeDir: "/home/user" });

const lookedUp = await runtime.getById("sbx-lookup", { homeDir: " " });
const attached = runtime.attachSandbox(connected, { homeDir: " " });

assert.equal(lookedUp?.homeDir, "/home/user");
assert.equal(attached.homeDir, "/home/user");
});

it("review guard: applies an explicit sandbox lifetime to create, reattach, and sync execution", async () => {
const created = fakeSandbox({ id: "sbx-lifetime-created" });
const reattached = fakeSandbox({ id: "sbx-lifetime-reattached" });
Expand Down Expand Up @@ -1267,6 +1309,7 @@ function createRuntime(
syncRunBudgetMs?: number;
createTimeoutMs?: number;
sandboxLifetimeMs?: number;
defaultHomeDir?: string;
} = {},
) {
return new E2BSandboxRuntime({
Expand Down
25 changes: 22 additions & 3 deletions src/e2b/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export interface E2BSandboxStatics {
}

export interface E2BAttachedSandboxOptions {
/** Optional handle home; blank values fall back to the runtime default. */
homeDir?: string;
workdir?: string;
owned?: boolean;
Expand Down Expand Up @@ -167,6 +168,8 @@ export type E2BSandboxRuntimeOptions = {
apiKey: string;
/** Account-specific template or snapshot identifier used for every launch. */
template: string;
/** Stable home directory supplied by the selected E2B template. */
defaultHomeDir?: string;
/** Maximum lifetime granted to an asynchronous command and its sandbox. */
runBudgetMs?: number;
/**
Expand Down Expand Up @@ -217,6 +220,7 @@ export class E2BSandboxRuntime implements SandboxRuntime, WorkflowRuntime {

private readonly apiKey: string;
private readonly template: string;
private readonly defaultHomeDir?: string;
private readonly runBudgetMs: number;
private readonly syncRunBudgetMs: number;
private readonly sandboxLifetimeMs: number;
Expand All @@ -235,8 +239,13 @@ export class E2BSandboxRuntime implements SandboxRuntime, WorkflowRuntime {
if (!template) {
throw new Error("E2B sandbox template is required");
}
const defaultHomeDir = options.defaultHomeDir?.trim();
if (options.defaultHomeDir !== undefined && !defaultHomeDir) {
throw new Error("E2B default home directory must not be empty");
}
this.apiKey = apiKey;
this.template = template;
this.defaultHomeDir = defaultHomeDir;
this.runBudgetMs = positiveDuration(options.runBudgetMs, DEFAULT_RUN_BUDGET_MS);
this.syncRunBudgetMs = positiveDuration(options.syncRunBudgetMs, this.runBudgetMs);
this.sandboxLifetimeMs = positiveDuration(
Expand Down Expand Up @@ -362,6 +371,7 @@ export class E2BSandboxRuntime implements SandboxRuntime, WorkflowRuntime {
});
return this.registerSandbox(sandbox, {
owned: true,
homeDir: this.defaultHomeDir,
workdir: options.workdir,
});
}
Expand Down Expand Up @@ -393,7 +403,7 @@ export class E2BSandboxRuntime implements SandboxRuntime, WorkflowRuntime {
): RuntimeHandle {
return this.registerSandbox(sandbox, {
owned: options.owned ?? false,
homeDir: options.homeDir,
homeDir: this.resolveHomeDir(options.homeDir),
workdir: options.workdir,
});
}
Expand Down Expand Up @@ -779,7 +789,9 @@ export class E2BSandboxRuntime implements SandboxRuntime, WorkflowRuntime {
});
return {
...handleFromE2BInfo(info),
...(options.homeDir ? { homeDir: options.homeDir } : {}),
...(this.resolveHomeDir(options.homeDir)
? { homeDir: this.resolveHomeDir(options.homeDir) }
: {}),
...(options.workdir ? { workdir: options.workdir } : {}),
};
}
Expand All @@ -796,11 +808,18 @@ export class E2BSandboxRuntime implements SandboxRuntime, WorkflowRuntime {
return {
id: sandbox.sandboxId,
state: "STARTED",
...(options.homeDir ? { homeDir: options.homeDir } : {}),
...(this.resolveHomeDir(options.homeDir)
? { homeDir: this.resolveHomeDir(options.homeDir) }
: {}),
...(options.workdir ? { workdir: options.workdir } : {}),
};
}

private resolveHomeDir(homeDir?: string): string | undefined {
const normalized = homeDir?.trim();
return normalized || this.defaultHomeDir;
}

private async requireSandbox(handle: RuntimeHandle): Promise<E2BSandbox> {
const registered = this.registrations.get(handle.id);
if (registered?.state === "paused") {
Expand Down
Loading