Runenv revised#424
Open
jhlee525 wants to merge 5 commits into
Open
Conversation
Replace the RunEnv/RunEnvHandle indirection with a Machine handle that agents share directly as `SharedMachine = Arc<Mutex<dyn MachineDyn>>`. This removes the implicit start-on-first-get / stop-on-last-drop lifecycle in favor of explicit `start`/`stop`, and lets pure tools skip booting the machine entirely. - Machine trait: add `is_running`, `start` now returns `&Console` (borrow tied to the machine), `stop` returns Result and defaults to no-op. - Replace `SandboxConfig` struct with a chainable `SandboxBuilder`; per-process /tmp volume sweeping and ailoy-managed labels are gone, microsandbox::SandboxConfig is reused directly. - `ToolFunc` splits into Pure / WithConsole variants and exposes `needs_console()`; builtins now take `&dyn Console` instead of `Arc<RunEnvHandle>`. - `AgentBuilder`/`Agent` constructors renamed `.runenv(..)` → `.machine(..)`/`.shared_machine(..)` and `try_with_runenv` → `try_with_machine`. Subagents inherit the parent machine via `Arc::clone`. - Bump microsandbox 0.5.2 → 0.5.4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the SharedMachine handle out of `agent::rt` into the `runenv` module, where the underlying `MachineDyn` trait already lives, and replace the `Arc<Mutex<dyn MachineDyn>>` type alias + free `shared_machine()` helper with a `#[derive(Clone)] struct SharedMachine` exposing `new`, `get`, and a `From<M: Machine>` impl. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ec field Move sub-agent ToolFunc construction from tool/impl/subagent.rs into a new agent/subagent.rs so it can share resolution logic with rt.rs. Pre- resolve everything that depends on AgentProvider once at outer-call time into a new AgentParts struct (model factory + options, tool descs/funcs, flattened files, skills, card name, system message); the sub-agent closure captures only these owned values, so it no longer clones an AgentProvider per invocation. LangModelProvider::provide now returns a cheap LangModelFactory that can be cloned and lazily turned into a LangModel. Agent no longer keeps a copy of AgentSpec: files are pre-flattened across the sub-spec subtree, and skills/card_name are lifted onto the runtime struct directly. Adds Agent::files() accessor. AgentState moves to its own module. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
khj809
reviewed
Jun 11, 2026
Comment on lines
-26
to
-59
| /// A volume mount attached to a sandbox at creation time. | ||
| #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] | ||
| #[serde(tag = "type", rename_all = "snake_case")] | ||
| pub enum VolumeMount { | ||
| /// Bind-mount a host directory into the guest. | ||
| Bind { | ||
| /// Absolute or relative host path. | ||
| host: PathBuf, | ||
| /// Absolute guest path (e.g. `/data`). | ||
| guest: String, | ||
| /// When `true`, the guest cannot write to this mount. | ||
| #[serde(default)] | ||
| readonly: bool, | ||
| }, | ||
| /// Mount a microsandbox named volume, stored under the resolved | ||
| /// microsandbox home directory (e.g. `~/.microsandbox/volumes/<name>/` | ||
| /// or `$MSB_HOME/volumes/<name>/`). | ||
| /// The volume persists across sandbox restarts and can be shared between sandboxes. | ||
| Named { | ||
| /// Name of the pre-existing microsandbox volume. | ||
| name: String, | ||
| /// Absolute guest path. | ||
| guest: String, | ||
| /// When `true`, the guest cannot write to this mount. | ||
| #[serde(default)] | ||
| readonly: bool, | ||
| }, | ||
| /// Memory-backed temporary filesystem. Disappears when the sandbox stops. | ||
| Tmpfs { | ||
| /// Absolute guest path. | ||
| guest: String, | ||
| /// Size limit in MiB. `None` means no limit. | ||
| size_mib: Option<u32>, | ||
| }, |
Contributor
There was a problem hiding this comment.
agent-k already depends on this volume mount for mounting project/session files. Why did you decide to remove this, and how you're planning to mount the files without this?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
RunEnv/RunEnvHandleindirection — anArc-counted wrapper that wrapped aBox<dyn MachineDyn>and managed start-on-first-get/ stop-on-last-drop ref-counting — is gone. Agents now share aMachinedirectly via aSharedMachine = Arc<Mutex<dyn MachineDyn>>handle, with explicitstart/stopand no implicit lifecycle.This removes a layer of state that always existed and was always a half-step out of sync with the underlying machine. The previous design hid two correctness pitfalls behind the ref-count:
get, even when nothing would touch the console.RunEnvHandle::Drop's background stop keptArcsemantics surprising: dropping the last handle was the only way to actually stop the VM, but callers couldn't tell whichArc::clonewas the last one without inspecting refcounts.After this PR, the machine's running state is exactly what
Machine::is_runningreports, andToolFunccarries enough type-level information for the runtime to skip starting the machine for tools that don't need it. The companion microsandbox bump to v0.5.4 lets us delete the ailoy-side/tmpvolume management that #416 had to put in place to compensate for missing upstream behaviour.Changes
1.
refactor(runenv): drop RunEnv wrapper, expose Machine via SharedMachine(85e0e81)Machinetrait reshape. Addis_running.startnow returns&Consolewhose lifetime is tied to&mut self, so a console borrow holds the machine for the duration of a tool stream.stopreturnsResultand defaults to no-op.SandboxConfig→SandboxBuilder. The struct + builder split is collapsed into a single chainableSandboxBuilder. Per-process/tmpvolume sweeping and theailoy.managedlabel scheme introduced in fix(runenv): unblock Drop, persist /tmp, rename Machine verbs, bump msb 0.5.0 #416 are gone —microsandbox::SandboxConfigis reused directly (see the dep bump below).ToolFuncsplits.ToolFunc::PureandToolFunc::WithConsole, with a publicneeds_console(). The runtime skips locking and starting the machine for pure tools inexecute_tool_calls. Builtins that need shell access —shell,read,write,edit,apply_patch,glob,grep,python_repl— take&dyn Consoleinstead ofArc<RunEnvHandle>.AgentBuilder/AgentAPI renames..runenv(..)→.machine(..)(owned) and.shared_machine(..)(pre-shared handle);Agent::try_with_runenv→Agent::try_with_machine; subagents inherit the parent machine viaArc::clone.2.
chore(deps): bump microsandbox 0.5.2 → 0.5.4(rolled into 85e0e81)The runenv refactor depends on this bump for two reasons:
/tmpvolume management. fix(runenv): unblock Drop, persist /tmp, rename Machine verbs, bump msb 0.5.0 #416 added per-sandbox named volumes (ailoy-tmp-{name}) with anailoy.managedlabel and a startup sweep, working around microsandbox not having a first-class auto-volume API. The 0.5.4 release ships that auto-volume work upstream, so the ~200 lines of label-tracking, sweep, and rollback logic inrunenv/sandbox.rscollapse to a directmicrosandbox::SandboxConfigreuse.SandboxBuilderparity. The chainable builder this PR introduces mirrors the upstream builder API surface that landed in 0.5.3/0.5.4.Cargo.toml:microsandbox = "0.5.2"→"0.5.4".cargo updateregenerates lock-file entries formicrosandbox-*crates and their transitive deps.3.
refactor(runenv): promote SharedMachine to a struct(891563c)The handle moves out of
agent::rtintorunenv, alongside theMachinetrait it wraps. TheArc<Mutex<dyn MachineDyn>>type alias and freeshared_machine()helper are replaced with#[derive(Clone)] struct SharedMachineexposingnew,get, and aFrom<M: Machine>impl. Agents passSharedMachinearound as an opaque handle; callers don't need to know about theArc<Mutex<>>shape.4.
refactor(agent): split subagent module, introduce AgentParts, drop spec field(107b422)Riding on the new
MachineAPI, the agent-side wiring gets a parallel cleanup:ToolFuncconstruction moves fromtool/impl/subagent.rsintoagent/subagent.rsso it can shareAgent-internal resolution helpers withagent/rt.rs.AgentParts: everything that depends onAgentProvideris resolved once at outer-call time (model factory + options, tool descs and funcs, the flattened file list, the system message). The sub-agent closure captures only this owned,Clone-able struct — no per-invocationAgentProviderclone.LangModelFactory:LangModelProvider::providenow returns a cheap factory that can be cloned freely and turned into aLangModellazily via.make(). This is what letsAgentPartsdefer the actualLangModelconstruction to the moment a sub-agent is actually invoked.Agentno longer retainsAgentSpec. Files are pre-flattened across the sub-spec subtree at construction time;skillsandcard_nameare lifted onto the runtime struct directly. AddsAgent::files()accessor.Agent::spec()is removed.AgentStatemoves to its own module (agent/state.rs).5.
chore(agent): remove dead subagent file(012a854)src/tool/impl/subagent.rswas already dead (itsmoddeclaration was commented out in step 4). Deleting the file and dropping the leftover commented-out lines insrc/tool/impl/mod.rs.