Skip to content

Run a process in the live view - #446

Merged
SaladDay merged 5 commits into
feature/agent-outside-sandboxfrom
aos/live-view-process
Oct 6, 2026
Merged

SaladDay merged 5 commits into
feature/agent-outside-sandboxfrom
aos/live-view-process

Conversation

@SaladDay

@SaladDay SaladDay commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Runtime–Harness protocol change: ViewSession.Spawn runs a LocalExec binary as another process in a Session's live view while its Harness runs. The first caller is MiniMax Code's Subagent history reader, which must run with the Harness's isolation: never on the agent host outside the view, and never in a second view. That caller is a separate PR, stacked on this one.

Protocol

  • Code: apps/daemon/internal/agent/harness.go adds ViewSession.Spawn and two typed outcomes, ErrNotLocalExec and ErrNoLiveView.
  • Docs: contracts/agents-api/harness-onboarding.md § Spawn, and its zh translation with a current source_hash.

Mechanism (sessionview)

  • The launcher starts the process. The view's launcher, its PID 1, starts it over the existing control socket. It starts it from the thread that carries the restrictions, so the process runs:
    • as the Harness's user;
    • in the view's namespaces and cgroup;
    • with no capabilities, no_new_privs and the same seccomp filter;
    • in a session of its own.
      The daemon never calls setns. The command travels on a pipe and the stdio descriptors travel with SCM_RIGHTS.
  • One spawn at a time. A view runs one spawn at a time. The slot is taken before any descriptor is made and is held until the request settles. The caller's context bounds both the wait and the start. A spawn whose context ends is cleaned up when the process starts late.
  • A slow directory blocks one thread only. The forking thread enters the command's directory itself, with the process's file-system identity and with signals blocked. A directory that waits on the world therefore blocks only that thread, in an ordinary syscall, and reaping, signals, the drain and teardown carry on.
  • Pids stay with their spawn. One SIGCHLD reaper reaps registered children at any time. While a fork is in progress it leaves orphans alone, so a child that exits before its registration keeps its pid until the registration ties it to its spawn.
  • Group signals. A spawn has an ID, and its group is signalled only while its leader is unreaped.
  • Bounded replies. Launcher replies never echo request data. A failed control write ends the channel, as a failed read does.

Testing

Privileged sessionview and agenthost suites, with the 7 spawn tests ×50 on all CPUs and with --cpuset-cpus=0.

The MiniMax Code qualification row 9 (Subagents in a view) passes with the stacked caller PR.

Blind review took three rounds. Round 3's two findings are fixed in 6af05a3: the bounded reply, and a context cancelled before Spawn.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

ViewSession.Spawn runs a LocalExec binary as another process in the Session's live view while the Harness runs. The view's launcher starts it from its restricted thread on a control-socket request, with stdio passed by SCM_RIGHTS, and reports its exit over the same socket. Launch and Spawn return ErrNotLocalExec; Spawn also returns ErrNoLiveView and ErrViewEnded.
The restricted thread now only forks. Reaping, the drain and the exit run on
another goroutine, and signals and spawns no longer share a lock with a fork.
Requests carry IDs, so a pending spawn never blocks a signal and Spawn honours
its context. Spawned processes are known by ID until reaped, the group kill on
exit is gone, the command travels over a pipe, and ErrViewEnded merges into
ErrNoLiveView.
A view admits one spawn at a time, and a spawn waiting for its turn holds no
descriptors; Spawn makes the stdio pipes. The restricted thread only forks.
While it forks, the SIGCHLD-driven reaper reaps registered children only, so
a child that exits before its registration keeps its pid. The launcher holds
off garbage collection during a fork, which a world stall would otherwise
turn into a frozen launcher. Control sends honour their context and a socket
shutdown, replies and exits go through one writer, and only an ended view
maps to ErrNoLiveView.
The launcher's forking thread enters the command's directory itself, as the process's user and with signals blocked, before it forks, so the child inherits it and does nothing before its exec that waits on the world. A slow directory then blocks that thread in an ordinary syscall that holds no P, and garbage collection no longer needs to be paused around the fork.
…xt win a spawn

A failed chdir or exec no longer carries the caller's directory or path back over the control socket, so no reply grows with what a caller passed; the daemon fills the path in from its own command. A control write that fails ends the launcher, as a failed read does, so no request waits for a reply that will not come. Spawn checks its context once the reply is in and sends a spawn whose context has ended through the late-process cleanup.
@SaladDay
SaladDay merged commit dc42417 into feature/agent-outside-sandbox Oct 6, 2026
21 checks passed
@SaladDay
SaladDay deleted the aos/live-view-process branch October 6, 2026 21:52
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