Implement ADR 0020 by isolating generated-code execution to a per-attempt sandbox receiver so generated method definitions cannot leak into Agent method space.
Target outcomes:
- Generated
def ...remains idiomatic and local to one attempt. - Dynamic dispatch lifecycle integrity is preserved (
method_missinglanes, delegation traces, contract boundaries). - Existing runtime tenets remain intact: Agent-first, tolerant interfaces, ergonomic runtime surfaces.
- Agent-first mental model:
- Tool Builder/Tool behavior must flow through runtime lanes, not accidental host mutation.
- Tolerant interfaces by default:
- keep ergonomic Ruby generation (
def, helper methods) while constraining side effects to attempt scope.
- keep ergonomic Ruby generation (
- Runtime ergonomics and clarity before premature constraints:
- fix execution surface, not prompt around the symptom.
- Ubiquitous language:
- Tool Builders forge, Tools execute, Workers run code; runtime keeps those boundaries observable.
In scope:
- Per-attempt sandbox receiver for local
evalexecution path. - Explicit forwarded API surface (
tool,delegate,remember,memory,Agent::Outcomeaccess via constant scope). - Lifecycle/observability updates to expose execution receiver.
- Regression coverage for cross-call method leakage.
- Trace validation for delegation fidelity on repeated assistant scenarios.
Out of scope:
- Prompt strategy redesign.
- Recursion primitives (
ContextView/recurse). - Artifact selection policy redesign.
- Domain-specific tool robustness changes (news parsing, etc.).
Current behavior:
_execute_coderuns generated code on Agent binding.- Generated
defcan persist beyond the call. - Persisted artifacts with method definitions can silently alter later lookup behavior.
Observed impact:
- Missing delegated depth-1 log entries for calls that appear delegated.
- Apparent
Outcome.okwhile lifecycle invariants are violated. - Hard-to-debug volatility because execution path diverges from intended runtime lane.
- Keep
defallowed in generated code. - No silent broad
selfexposure to full Agent internals. - Maintain compatibility with ADR 0016 retry/rollback lifecycle.
- Maintain
context[:conversation_history]behavior from ADR 0019. - Preserve worker execution path for dependency-backed programs.
- Introduce
ExecutionSandboxobject instantiated per attempt. - Sandbox holds attempt-local runtime state:
contextargskwargsresult
- Generated code executes in sandbox method binding.
- Sandbox is discarded after call completion.
Sandbox forwards to parent Agent:
tool(...)delegate(...)remember(...)memory
Generated code continues to reference Agent::Outcome directly via constant.
- Method definitions created by generated code are sandbox-local only.
- Agent method lookup remains unchanged across attempts.
- Contract validation, guardrail policy, and outcome repair lanes remain runtime-owned.
Goals:
- Capture pre-change behavior for comparison.
- Add tests that fail on current leakage behavior.
Tasks:
- Capture log traces for Google/Yahoo/NYT sequence with current receiver behavior.
- Add regression spec:
- execute generated code containing
def leaked_helper; assert helper is not available on Agent after call (expected to fail pre-fix).
- execute generated code containing
- Add trace assertion fixture strategy for delegated call visibility.
Exit criteria:
- Baseline traces archived.
- Leakage regression test exists and fails pre-fix.
Goals:
- Move local eval path from Agent binding to sandbox binding.
Tasks:
- Add
runtimes/ruby/lib/recurgent/execution_sandbox.rb. - Refactor
_execute_codeto construct sandbox and execute wrapped code there. - Keep
resultcontract unchanged (raw domain value returned to caller pipeline). - Keep pre/post tool-registry integrity checks around sandbox execution.
Primary files:
Exit criteria:
- Existing dynamic-call tests pass with sandbox receiver.
- No new failures in worker-backed path tests.
Goals:
- Ensure sandbox exposes only intended runtime API.
Tasks:
- Implement explicit forwarding methods only (
tool,delegate,remember,memory). - Ensure
context/args/kwargs/resultlocals are available to generated code as before. - Add behavior tests for each forwarded method.
- Verify generated code that references
selfcannot access unintended Agent internals.
Primary files:
Exit criteria:
- Forwarded API tests pass.
- No accidental host mutation through sandbox path.
Goals:
- Make execution receiver explicit in logs.
- Enable trace-level before/after calibration.
Tasks:
- Add
execution_receiverfield in logs (legacy|sandboxduring rollout;sandboxafter migration). - Thread receiver field through call state/log entry builders.
- Update observability documentation for the new field.
Primary files:
runtimes/ruby/lib/recurgent/call_state.rbruntimes/ruby/lib/recurgent/observability.rbruntimes/ruby/lib/recurgent/observability_attempt_fields.rbdocs/observability.md
Exit criteria:
- New logs include
execution_receiver. - Delegated trace analysis can filter by receiver reliably.
Goals:
- Prove fix closes the leakage class without regressing lifecycle behaviors.
Tasks:
- Add regression test: generated
def fetch_headlinesin one call does not alter next call dispatch behavior. - Add test: repeated assistant-style calls preserve delegated depth-1 traces when tools are reused.
- Add test: ADR 0016 retry lanes still function under sandbox (
guardrail,execution,outcomerepair). - Add test: conversation history append still occurs once per logical call (no duplication under retries).
Primary files:
Exit criteria:
- Leakage regression passes.
- Retry/rollback and history invariants remain green.
Goals:
- Validate real behavior on favorite scenarios.
- Complete migration to sandbox-only receiver.
Tasks:
- Run Google/Yahoo/NYT scenario and inspect logs:
- delegated calls remain visible at depth 1 when tools are used,
- no missing traces due to method leakage,
- receiver field confirms sandbox path.
- Run movie scenario where tool creation previously triggered guardrail issues; verify no new receiver regressions.
- Remove legacy receiver fallback path (if temporarily present).
Exit criteria:
- Sandbox is the only execution receiver.
- Real traces confirm delegation fidelity improvement.
- Sandbox returns
resultcorrectly with assignment and withreturn. - Sandbox forwards
tool,delegate,remember,memory. - Generated method definition in sandbox does not appear on Agent instance/class after call.
- Dynamic call flow still produces typed
Outcome. - Guardrail violation and execution retry lanes still retry under sandbox.
- Outcome repair lane still retries retriable errors and logs attempt counters.
- Google/Yahoo/NYT favorite sequence:
- verify output quality does not regress materially,
- verify delegated depth-1 traces where tool calls are made.
- Movie follow-up scenario:
- verify no execution-surface regressions,
- verify user-correction signals still emit.
Add field:
execution_receiver:legacy(only during transitional rollout, if used),sandbox(target steady state).
Recommended analysis checks:
- Compare delegated depth-1 trace presence before/after sandbox adoption.
- Confirm reduction in “apparent delegation without delegated trace” anomalies.
- Risk: generated code depended on broad Agent
selfsurface.- Mitigation: strict forwarding + runtime repair feedback; add missing forwarders only with evidence.
- Risk: refactor accidentally changes
resultsemantics.- Mitigation: direct unit tests for result channel and control-flow semantics.
- Risk: lifecycle drift in retries/history append.
-
ExecutionSandboximplemented and wired into local eval path. - Forwarded API surface implemented and test-covered.
-
execution_receiverlog field emitted and documented. - Leakage regression suite green.
- Favorite scenario traces captured and analyzed post-migration.
- Legacy receiver path removed (sandbox-only steady state).
- Docs index and retrieval index updated.