Conversation
added 5 commits
July 27, 2026 11:11
A team launched with relay-team-autonomy runs on the real machine, with the user's credentials, with permission prompts bypassed. The sandbox puts the project and its whole roster inside one container instead: one filesystem, one toolchain, one blast radius. Self-contained by design — docker or podman is the only dependency. No editor, no devcontainer CLI, and no image to pull from a registry we would have to publish and keep current. A project's devcontainer.json and a container an editor already started are both used when present, and nothing degrades when they are not. Three decisions carry the rest: - One container per project, not per agent. Agents on a team need to see each other's edits and share a build cache; isolation between them stays in git worktrees inside the shared mount. Panes are refcounted, so the container outlives any one of them. - The project is identity-mounted. Git records absolute paths in a worktree, so under any other mapping a worktree made on one side is broken on the other. Equal paths keep the worktree verbs working from both sides and remove the need for a path translation layer anywhere. - Relay stays on the host. build::worker resolves the role, renders the harness, and writes the MCP config exactly as it does for a host launch; --sandbox changes only the final exec, rewriting the bus URL to the engine's gateway host and handing the agent its config at the path the container mounts it. container grows Sandbox, Recipe, Mount, adopt (label discovery plus ownership, so a container Sinclair did not create is entered and never removed), and a devcontainer.json reader on config's JSONC parser — all still pure argv construction. app/sandbox resolves settings into a description and brings it up off the render path; root/sandbox counts panes and retires the container. Project resolution walks up to the repository root so every pane in a checkout shares one container, and refuses $HOME, / and depth-1 paths: the sandbox mounts what it is given, and mounting a home directory would hand an unattended agent the whole machine. UI: a Sandbox submenu under File and AI with a live status line, a Project sandbox section in the Containers panel, fourteen sandbox-* settings, and six bindable actions.
Three surfaces were missing, all of them the ones people actually reach for. The command palette listed OS Tabs and Attach to Container but nothing for the sandbox, so the feature was menu-only. Progress and failures lived in a menu the user had to know to open. Bringing a sandbox up the first time builds an image — minutes — and clicking the item looked identical to a hang. Resolving now reveals the Containers drawer, where the live status line and the resolve advisories already were. A failure was worse than invisible: sandbox_fail wrote to stderr and a status line nobody was looking at, so opening a shell from a pane with no repository above it did nothing at all. It now surfaces the reason on screen. show_sandbox_panel reveals rather than toggles — it is only ever called to show something, and collapsing an open drawer would be the opposite of the point.
Issue #15 asks for tab renaming. It was already there — `change_tab_title`, in the View menu, in the command palette, documented — but two things stood between the user and it. The rename dialog never painted. Its host element had no size, so the modal's absolutely-positioned backdrop, painted in the deferred pass, had nothing to resolve against: the dialog was created and re-rendered every frame and was never on screen. That is the whole of the reported problem — renaming looked missing because it was invisible. Filling the window fixes it. `tab-title-show-host` was dead. It parsed, it wrote to settings.json, it had a toggle in Settings and a line in the help text promising "tabs show just the path" — and nothing read it. The tab label now honours it, with a strip that only fires on something genuinely shaped like `user@host:`, so `https://example.com:8080` and `nvim: main.rs` are left alone. A title the user typed is never rewritten. The flag rides in a cell the reload path updates, so toggling it does not need a restart. Right-click on a tab now opens a menu — rename, split, close, close others — because the View menu is not where anyone looks with their hand already on a tab. Handlers defer their action: running straight into the same window re-enters an update in progress, which is refused, and the menu closes having done nothing. Window dragging comes from the guise side: every top-row tab bar drags now, not just the corner one.
`mounts` and `remoteUser` were read out of a project's devcontainer.json and then dropped on the floor. Both are now applied: mounts through a parser for the engine's `type=bind,source=…,target=…` form, which is what the spec uses and not the `-v` shorthand the settings key takes, and `remoteUser` as the container's user unless `sandbox-user` overrides it. A project that names a user has decided who its tooling runs as. New tutorial, docs/devcontainers.html: opening a shell inside the environment a repo already defines, exactly which fields are read and which are ignored, sharing the container an editor started (and why Sinclair never stops one it did not create), the two lines that make a devcontainer suit agents as well as people, and what happens when there is no devcontainer.json at all. Linked from the tutorials index, the docs overview, the nav manifest, and both directions between it and the sandbox page. The tab work is documented too: right-clicking a tab, dragging the window from any top-row tab bar, and what `tab-title-show-host` actually does now that something reads it — its reference entry promised the opposite of the old behaviour.
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.
Shared project sandbox
A project's panes and its whole agent team can run inside one container
instead of on the host, turning
relay-team-autonomy's permission bypass from apromise into a boundary. Self-contained:
dockerorpodmanis the onlydependency — no VS Code, no
devcontainerCLI, no image to pull.Three decisions carry the rest:
each other's edits and share a build cache; isolation between them stays in
git worktrees inside the shared mount. Panes are refcounted, so the container
outlives any one of them.
so under any other mapping a worktree made on one side is broken on the other.
Equal paths keep the worktree verbs working from both sides and remove the
need for a path translation layer anywhere.
build::workerresolves the role, renders theharness, and writes the MCP config exactly as for a host launch;
--sandboxchanges only the final exec, rewriting the bus URL to the engine's gateway
host and handing the agent its config at the path the container mounts it.
Project resolution walks up to the repository root so every pane in a checkout
shares one container, and refuses
$HOME,/, and depth-1 paths: the sandboxmounts what it is given, and mounting a home directory would hand an unattended
agent the whole machine.
Dev containers
A project's
devcontainer.jsonis read when it has one and ignored when it doesnot. Its
imagebecomes the base Sinclair layers the agent CLI onto;mounts,remoteUser,containerEnv/remoteEnvare applied;shutdownActionraises anadvisory, because closing an editor should not stop a container with a team
working in it. A container an editor already started is entered rather than
duplicated, and never stopped or removed — the user's editor is very likely
attached to it.
Tab renaming (#15)
Renaming was already an action, a View-menu item, a palette entry, and a
documented keybinding — and the dialog never painted: its host element had
no size, so the modal's absolutely-positioned backdrop, drawn in the deferred
pass, had nothing to resolve against. The feature looked missing because it was
invisible.
tab-title-show-hostwas dead the same way: parsed, written tosettings.json,a toggle in Settings and a promise in the help text, and no consumer. Tab labels
now honour it.
Right-clicking a tab opens a menu (rename, split, close, close others), and
every tab bar along the layout's top edge drags the window rather than only the
top-right one.
Verification
cargo clippy --all-targetsclean, release build clean.mount, agent CLI,
$HOMEvolume, and gateway host all confirmed inside thecontainer.
the container and read on the host, and the reverse.
dragging, and the rename dialog were all driven in the running app.
Note for review
vendor/guisemoves to a commit on thesinclair-panegroupbranch ofwess/guise, not
main— guise'smainhas since retargeted to crates.io gpui0.2.2, which this workspace does not use.