Skip to content

Serve the Session world to views over the File protocol - #349

Merged
SaladDay merged 5 commits into
feature/agent-outside-sandboxfrom
aos/world-frontend
Oct 1, 2026
Merged

SaladDay merged 5 commits into
feature/agent-outside-sandboxfrom
aos/world-frontend

Conversation

@SaladDay

@SaladDay SaladDay commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Lane L1b of the agent-outside-sandbox workstream. A Session view sees the sandbox's world export as its filesystem. worldfs serves that filesystem with FUSE, and each kernel request becomes at most one File protocol request.

Changes

  • apps/daemon/internal/worldfs: a go-fuse v2 raw-API frontend (Linux; other platforms get a stub that returns ErrUnsupported).
    • Mapping and caching: doc.go holds the mapping table. The profile is uncached: zero TTLs and FOPEN_DIRECT_IO, with only the declared capabilities negotiated.
    • Errors and identity: one errno table, where stale nodes and handles give ESTALE. The service uid/gid is shown as the view uid/gid and mapped back on SetAttr.
    • Presentation:
      • Mountpoints and their missing ancestors are synthetic and immutable.
      • Entries on the way to a mountpoint are pinned with stable node IDs.
      • Sandbox symlinks are followed with Walk/Readlink, at most 40 hops.
      • A changed pinned entry reports ErrTopologyChanged.
    • Link loss: it redials and continues only when ServerInstanceID is unchanged. A request is re-sent only if it was never sent; a new incarnation or an ended attachment marks the world lost.
    • Cleanup: Release, ReleaseDir and Forget that were never sent, or were refused with ResourceExhausted/EffectNone, are queued. They retry after the next redial or after a bounded backoff (20 ms to 2 s), and a request that may have reached the service is never re-sent.
    • Locks: the lock requests of one owner on one handle take turns, so a failed non-blocking conversion's recovery unlock cannot remove a lock that someone else acquired.
      • A SETLKW gives up its turn while it waits.
      • Waiting for a turn or for the connection can be interrupted, which returns EINTR with nothing sent.
      • Unsafe or failed recovery returns EIO.
    • Uncertain Attach: if Attach may have taken effect without an answer, or the stream was redialled during presentation, or Start's context ends, or Detach fails, then Serve fails with ErrAttachmentDirty. The owner of the Link attachment (the agent host, L5a) then ends that attachment.
    • Stop: waits for serving to end, drains pending Forgets, then Detaches. A world whose Serve failed or never ran stops at once.
  • sessionview:
    • World takes Start's context and returns (WorldServer, Presentation, error), and WorldMount carries the view UID/GID. A Start cancelled during construction keeps the World's error (for example ErrAttachmentDirty).
    • The privileged gateway view test sets the StagingParent that Declare agent-host Session views in the Runtime–Harness protocol #350 made required.
    • The launcher mounts at the resolved targets, still with RESOLVE_NO_SYMLINKS.
    • View.Presentation() is added.
  • apps/sandboxio/fileservicetest: a fixture that serves the file service over a temp dir through net.Pipe, with Break(), Restart() and an Intercept hook for deterministic tests.

Declared gaps:

  • POSIX fcntl locks return ENOLCK; the service declares only flock.
  • Link and Symlink return EPERM when undeclared.
  • Mknod returns EOPNOTSUPP.
  • Fallocate is out of phase 1.

Two defects need the File protocol change in L1c, which also moves this frontend onto it:

  • fcntl(F_SETFL) toggling O_APPEND on an open file is ignored, because append is fixed at Open;
  • a handle whose Open reply is lost stays until Detach.

L1c gives per-write append, client-chosen handle IDs and a fence on stream succession. M1 qualification waits for it.

Checks

  • Gofmt and vet; unit tests on worldfs, sessionview and the sandboxio packages.
  • Darwin and windows builds.
  • Privileged container (OAC_TEST_WORLDFS=1): PASS, five runs.
    • It covers the go-fuse posixtest suite (skipping Fallocate and fcntl locks) and owner mapping.
    • It covers presentation (/bin -> usr/bin, the shim at /usr/bin/sh), symlink loops, topology changes, instance changes, and a real launcher view editing the workspace.
  • A race build: three runs.
  • Review rounds: two blind-review rounds and a focused review of the fixes. Each fix has a regression test that fails without it:
    • TestLockRecoveryKeepsLaterLock;
    • TestRefusedReleaseIsRetried;
    • TestReleaseQueuedAfterRedial;
    • TestServeEndsWithItsContext;
    • TestLockInterruptedWhileRedialing;
    • TestStartEndsDuringAttach;
    • TestUnsentLockInterruptedBehindTurn;
    • TestRefusalAfterOwnRedialWaits;
    • TestUnsafeLockRecoveryFails.
  • After the final rebase: in the privileged container, worldfs (19), sessionview (6) and gateway (11) pass.

@SaladDay
SaladDay force-pushed the aos/world-frontend branch 3 times, most recently from 46f234e to cc2aebd Compare October 1, 2026 03:03
@SaladDay
SaladDay merged commit 49b3b80 into feature/agent-outside-sandbox Oct 1, 2026
13 checks passed
Add apps/daemon/internal/worldfs, a go-fuse raw-API frontend that maps each kernel request to at most one File request with the uncached profile, one errno table, view identity mapping, synthetic presentation of mountpoints and link-loss handling.

Extend the sessionview World contract to return a Presentation: the launcher mounts private directories, overlays and shims at the symlink-free targets the world resolved.

Add apps/sandboxio/fileservicetest to serve the Linux file service over in-memory streams for tests outside apps/sandboxio.
Stop drains and detaches under one deadline and returns within 15 seconds.
An interrupted SETLK waits for the request's own outcome; an acquisition that
stays uncertain is unlocked, or the handle fails. Releases a failed stream never
sent are queued and drained after redial. An Attach with an unknown outcome is
detached on abort. Non-retryable Link failures end the world. Presented
directories keep every offset until ReleaseDir, list each sandbox entry at its
own position, send at most one ReadDir per READDIR and report a pinned entry a
complete enumeration does not find. The mmap note now matches the kernel.
Lock requests of one owner on one handle take turns, so a recovery unlock never removes a lock another request reported. A Release or Forget the service refused with ResourceExhausted and EffectNone is retried with backoff, and queuing wakes the drainer in the same step. sessionview.World takes Start's context, which bounds dialing, attaching and presenting, and an interrupt reaches a request still waiting for the stream or a redial. A Serve that cannot show its attachment is clean fails with ErrAttachmentDirty for the Link attachment's owner to end.
… without the turn

Start joins its cancellation with the World's error, so ErrAttachmentDirty reaches the attachment's owner. A waiting lock request records its answer without retaking its owner's turn, which a non-blocking request may hold while it waits for the stream, and a request that needs recovery checks which acquisitions registered after it or were waiting when it registered. A request whose recovery is unsafe or fails returns EIO. The drainer records the generation a failed cleanup ran on, so its own redial does not cut the backoff short.
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