Skip to content

feat: Phase 47 β€” Tool Execution With Sandboxing (v4.0.0-alpha.7) - #54

Merged
aarambh-darshan merged 1 commit into
mainfrom
feat/phase-47-sandboxed-tool-execution
Aug 17, 2026
Merged

feat: Phase 47 β€” Tool Execution With Sandboxing (v4.0.0-alpha.7)#54
aarambh-darshan merged 1 commit into
mainfrom
feat/phase-47-sandboxed-tool-execution

Conversation

@aarambh-darshan

Copy link
Copy Markdown
Member

Summary

Closes the boundary v2 Β§30 opened (tool calls are emitted, never executed) and
v3 Β§46 extended (multi-step chains, still emit-only): the model's tool calls
can now be actually executed by aarambh-studio itself β€” but only inside a
strict, closed-world sandbox. This is the highest-risk phase before Phase 51
and is scoped conservatively on purpose: there is no generic "run a shell
command" or "eval this code" executor anywhere in the crate, by design.

Targets milestone v4.0.0-alpha.7 per ROADMAP_V4.md Phase 47.

What changed

crates/aarambh-studio-agent (extended β€” no new crate, no new dependency)

  • src/sandbox.rs β€” the closed-world execution sandbox:
    • ToolExecutor trait: one specific, named capability per implementor
      (name() -> &'static str + execute(&ValidatedArgs, &ExecContext) -> Result<ToolResultContent, ExecError>).
    • ToolSandbox::execute() enforces the full ARCHITECTURE_V4.md Β§61
      pipeline, in order:
      1. Closed-world allowlist β€” name must match a registered executor β†’ else ExecError::UnknownTool (hard refusal, no attempt).
      2. Operator authorization β€” name must be in AuthorizationScope β†’ else ExecError::Unauthorized (distinct from UnknownTool).
      3. Argument-size ceiling (max_args_bytes, default 8 KiB).
      4. Schema re-validation against the declared JSON Schema (defense-in-depth on top of the grammar-constrained decoder) β†’ malformed calls are never executed.
      5. Bounded envelope β€” worker thread + recv_timeout wall-clock ceiling (timeout_ms, default 5 s) with cooperative cancellation via an AtomicBool flag; detached-on-timeout since safe Rust cannot force-kill a thread.
      6. Output-size ceiling (max_output_bytes, default 64 KiB).
    • Every failure yields a fail-closed ToolResult{status:Error, error:...} β€” the chain records the refusal and continues.
    • SandboxedToolProvider implements ToolResultProvider, so execution plugs into the existing ToolChain with zero chain changes β€” results re-enter via the unchanged result_ingestion path. Execution is purely additive to v3 Β§46.
    • Reference executors: ReadFileInWorkdir (the milestone read-only, workdir-confined file lookup β€” refuses absolute paths/.. traversal, caps bytes, no network/write access) and StaticLookup (in-memory keyβ†’text for deterministic tests).
  • src/authorization.rs β€” AuthorizationScope: the operator's closed set of
    enabled tool names (operator decision, not model decision). intersect()
    supports Phase 48 sub-agent scope narrowing (a sub-agent's scope can only be
    a subset of its orchestrator's).
  • src/lib.rs β€” exports the new modules/types.

aarambh-studio (CLI)

  • src/cmd/agent.rs β€” new flags: --execute-tools, --allow-tool <NAME>
    (repeatable), --exec-timeout-ms, --exec-max-output-bytes,
    --exec-workdir <DIR>. New CliResultProvider::Sandbox variant.
    validate_sandbox_config() runs before model load so operator config
    errors surface immediately; build_sandbox_provider() constructs the sandbox
    after the tool definitions are loaded.

Version + docs

  • Cargo.toml / Cargo.lock: 4.0.0-alpha.6 β†’ 4.0.0-alpha.7.
  • ROADMAP_V4.md: Phase 47 tasks marked [x], milestone tag updated, status note added.
  • CHANGELOG.md: new [4.0.0-alpha.7] section.
  • README.md: v4 feature paragraph, "Current Boundaries" tool-chain bullet, docs link, citation version.
  • docs/phase47_sandbox.md: new runbook. docs/README.md: phase 47 entry.
  • ARCHITECTURE_V4.md Β§61 + SELF_LEARNING_V4.md Β§47: implementation notes.

Tests + smoke

  • 6 roadmap-named acceptance tests in sandbox.rs (real bodies) + supporting tests in authorization.rs:
    • unlisted_tool_name_is_hard_refused_never_attempted
    • unauthorized_but_declared_tool_is_refused_at_execution_not_declaration
    • execution_timeout_kills_a_hanging_tool_call
    • execution_respects_configured_memory_and_cpu_ceiling
    • malformed_tool_call_json_is_never_executed
    • execution_result_re_ingests_correctly_into_the_next_chain_step (drives the real ToolChain + FakeDecoder + SandboxedToolProvider + StaticLookup)
  • scripts/phase47_smoke.sh: agent-crate sandbox/authorization unit tests + CLI flag checks + two operator-error-path checks + scorecard at artifacts/phase47_sandbox_smoke.json.
  • data/tools_sandbox_smoke.json: the read_file_in_workdir tool definition.

Design decisions

  • No new crate, no new dependency β€” keeps the release audit's 20-package
    invariant and the rustsec audit footprint unchanged. Only std::thread/
    std::sync (mpsc, atomic, Arc) + existing serde/thiserror are used.
  • Additive composability β€” SandboxedToolProvider: ToolResultProvider means
    the existing ToolChain is untouched; execution is purely additive to v3 Β§46's
    result_ingestion path.
  • Operator, not model, decides authorization β€” the model can declare and
    request any tool, but only operator-enabled names ever execute.
  • Closed-world, no shell/eval β€” every executor is one named capability;
    an unrecognised name is a hard refusal, never a best-effort fallback.

Honesty boundary

Phase 47's sandbox is pure-Rust and CPU-only: wall-clock timeout (cooperative
cancellation + thread-detachment on timeout, since safe Rust cannot force-kill a
thread), output/argument-size ceilings, closed-world allowlist, operator
authorization, and schema re-validation. OS-level isolation (seccomp/cgroups/
namespaces) is out of scope for the source release, consistent with the project's
CPU-first posture. The safety-relevant property β€” a runaway or hung call never
blocks the chain and always produces a fail-closed result
β€” holds under every
tested failure condition. A general-purpose code-execution sandbox remains
explicitly out of scope: Phase 47 is strictly closed-world, named-capability tool
execution, never arbitrary code or shell execution.

CI gates verified locally

  • cargo fmt --all --check
  • cargo check --workspace --all-targets --locked
  • cargo clippy --workspace --all-targets --locked -- -D warnings -D clippy::undocumented_unsafe_blocks
  • cargo +1.89.0 check --workspace --all-targets --locked (MSRV)
  • RUSTDOCFLAGS="-D warnings -D missing_docs" cargo doc --workspace --no-deps --locked
  • scripts/phase28_release_audit.sh (passes for 4.0.0-alpha.7: 20 packages, one version, no TODO/FIXME markers, no model artifacts, no cargo publish in workflows)
  • bash -n on all scripts
  • cargo build --release --locked -p aarambh-studio (26 MB binary)
  • Full CLI --help smoke (incl. agent --help surfacing the 5 new flags)
  • scripts/phase47_smoke.sh (9 sandbox + 6 authorization unit tests; flag checks; operator error-path checks; scorecard)
  • cargo test -p aarambh-studio-agent --lib (22/22)
  • cargo test -p aarambh-studio --lib (6/6)

Note: the full cargo test --workspace (incl. integration tests) could not
complete in the local sandbox (~10 GB disk) because each test binary links the
full candle-core dependency tree (~300 MB each Γ— ~15 crates), exhausting disk
during linking. This is an environmental disk constraint, not a code defect β€”
every crate compiles cleanly (check --all-targets + clippy --all-targets
pass), the affected crates are unchanged from the green alpha.6 baseline, and
this PR only touches aarambh-studio-agent (tested) and the CLI cmd/agent.rs
(tested). CI on GitHub's larger runners will run the full suite.

@aarambh-darshan
aarambh-darshan merged commit e485d97 into main Aug 17, 2026
3 checks passed
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