Skip to content

fix: v0.2.1 — sandbox spawn reliability and auto-topup#246

Merged
unifiedh merged 10 commits intomainfrom
unifiedh_fixes_v0.2.1
Feb 27, 2026
Merged

fix: v0.2.1 — sandbox spawn reliability and auto-topup#246
unifiedh merged 10 commits intomainfrom
unifiedh_fixes_v0.2.1

Conversation

@unifiedh
Copy link
Collaborator

@unifiedh unifiedh commented Feb 27, 2026

Summary

  • Auto-topup on 402: When sandbox creation fails with INSUFFICIENT_CREDITS, automatically top up credits via x402 and retry once (both orchestrator spawnAgent and spawn_child tool paths)
  • Fix sandbox pricing tiers: Align createSandbox specs with valid Conway tiers (was sending 1 vCPU / 1024 MB / 5 GB which doesn't match any tier; now uses a lookup table)
  • Remove sandbox deletion: Conway API no longer supports DELETE /v1/sandboxes — make deleteSandbox a no-op, clean up all callers
  • Self-mod & replication fixes: Git-version source edits in repo root, self-replication tools target child sandbox not parent
  • Version bump: 0.2.0 → 0.2.1

Test plan

  • pnpm exec tsc --noEmit — zero type errors
  • pnpm vitest run — 141 tests passing across replication, lifecycle, and tools-security suites
  • Tests updated to reflect sandbox deletion removal

🤖 Generated with Claude Code


Open with Devin

devin-ai-integration[bot]

This comment was marked as resolved.

unifiedh and others added 7 commits February 28, 2026 02:05
…rent

All replication tools (start_child, check_child_status, verify_child_constitution)
were running commands on the parent's sandbox instead of the child's. Fixed by
using createScopedClient(child.sandboxId) to route exec/writeFile to the correct
sandbox. Also fixed spawn to clone from GitHub (matching README) instead of
installing a non-existent npm package, and added proper error handling to
start_child with process verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… tools

editFile() was committing snapshots to ~/.automaton/ (state dir) instead of the
repo root where source code lives. Now uses gitCommit() targeting process.cwd().
Also triggers `npm run build` after editing .ts/.js/.tsx/.jsx files so changes
take effect at runtime.

Adds revert_last_edit (git revert HEAD) and reset_to_upstream (git reset --hard
origin/main) tools so the agent can recover from bad self-modifications.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rker

When sandbox creation fails with 402 INSUFFICIENT_CREDITS, attempt to
top up credits via x402 and retry once before falling back to a local
worker. Uses a 60s cooldown to prevent hammering the topup endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The hardcoded diskGb=5 with 1024MB memory doesn't match any pricing
tier (Medium requires 10GB disk). Add a SANDBOX_TIERS lookup so the
disk and vCPU are always consistent with the requested memory size.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The spawn_child tool calls spawnChild() directly without the
topup-and-retry logic that the orchestrator's spawnAgent has.
Add the same pattern: catch 402, topup credits, retry once.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Conway API no longer supports DELETE /v1/sandboxes — sandboxes are
prepaid and non-refundable. Make deleteSandbox a no-op, remove cleanup
calls in spawn error paths, update the delete_sandbox tool to inform
the agent, and let SandboxCleanup transition to cleaned_up directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@unifiedh unifiedh force-pushed the unifiedh_fixes_v0.2.1 branch from c8a7049 to 21ab862 Compare February 27, 2026 17:05
unifiedh and others added 2 commits February 28, 2026 02:06
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@unifiedh unifiedh merged commit 6fd836a into main Feb 27, 2026
6 checks passed
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 14 additional findings in Devin Review.

Open in Devin Review

Comment on lines +1594 to +1613
const topup = await topupForSandbox({
apiUrl: ctx.config.conwayApiUrl,
account: ctx.identity.account,
error: err,
});
if (topup?.success) {
const retryLifecycle = new ChildLifecycle(ctx.db.raw);
const retryGenesis = generateGenesisConfig(ctx.identity, ctx.config, {
name: args.name as string,
specialization: args.specialization as string | undefined,
message: args.message as string | undefined,
});
child = await spawnChild(
ctx.conway,
ctx.identity,
ctx.db,
retryGenesis,
retryLifecycle,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Unhandled exception in spawn_child retry path causes original error to be lost

In the spawn_child tool's auto-topup retry logic, the retry spawnChild() call on line 1606 is not wrapped in a try-catch. If the retry throws (e.g., max children reached because the first attempt already created a child record in failed state, or any other spawn error), the exception escapes the outer catch block entirely, and the if (!child) throw err on line 1616 is never reached.

Detailed comparison with orchestrator's spawnAgent

The orchestrator's spawnAgent in src/agent/loop.ts:287-309 correctly wraps the retry spawn in its own try-catch:

try {
  const child = await retrySpawn(...);
  return { ... };
} catch (retryError) {
  logger.warn("Spawn retry after topup failed", { ... });
}

But the spawn_child tool in src/agent/tools.ts:1594-1613 does NOT wrap either topupForSandbox() or the retry spawnChild() in a try-catch:

const topup = await topupForSandbox({...});  // could throw
if (topup?.success) {
  child = await spawnChild(...);  // CAN THROW, escapes catch block
}

If the retry spawnChild throws, the new error propagates out of the catch block, the original err is lost, and the if (!child) throw err on line 1616 is never reached. This can result in confusing error messages and the failed first-attempt child record being left in an inconsistent state.

Impact: When a 402 topup succeeds but the retry spawn fails, the user gets an unhandled error from the retry instead of graceful fallback behavior. The original error context is lost.

Suggested change
const topup = await topupForSandbox({
apiUrl: ctx.config.conwayApiUrl,
account: ctx.identity.account,
error: err,
});
if (topup?.success) {
const retryLifecycle = new ChildLifecycle(ctx.db.raw);
const retryGenesis = generateGenesisConfig(ctx.identity, ctx.config, {
name: args.name as string,
specialization: args.specialization as string | undefined,
message: args.message as string | undefined,
});
child = await spawnChild(
ctx.conway,
ctx.identity,
ctx.db,
retryGenesis,
retryLifecycle,
);
}
const topup = await topupForSandbox({
apiUrl: ctx.config.conwayApiUrl,
account: ctx.identity.account,
error: err,
});
if (topup?.success) {
try {
const retryLifecycle = new ChildLifecycle(ctx.db.raw);
const retryGenesis = generateGenesisConfig(ctx.identity, ctx.config, {
name: args.name as string,
specialization: args.specialization as string | undefined,
message: args.message as string | undefined,
});
child = await spawnChild(
ctx.conway,
ctx.identity,
ctx.db,
retryGenesis,
retryLifecycle,
);
} catch { /* retry failed, will throw original err below */ }
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@unifiedh unifiedh deleted the unifiedh_fixes_v0.2.1 branch February 27, 2026 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant