Wingthing is a local-first, agent-first manager for durable agent runs and terminals. Start by giving Codex, Claude, or another parent agent typed MCP control of agents on the same machine, using the code and provider login already there. Add remote machines or a browser only when the work requires them.
For the complete terminal workflow, see the daily-driver guide:
cold wt claude, live saved machines in bare wt, detach, attention, and recent-run recovery.
The agents run where the code and hardware already live. Wingthing keeps their terminals alive, records semantic runs as durable tasks, applies sandbox policy, and gives each caller an owner, actor, grant set, bound, and audit trail.
hero.mp4
Choose the smallest route that fits:
- An agent manages local agents through stdio MCP.
- A person starts a local sandboxed agent terminal.
- A person or agent reaches one known machine through ordinary SSH.
- An agent uses direct remote MCP when it needs one qualified multi-wing inventory.
- A person who needs a browser runs a self-hosted roost.
- An entitled account may optionally use the hosted
wingthing.aibrowser relay.
Install Wingthing, start a personal wing, then register its local MCP client with the agent that will coordinate the work:
curl -fsSL https://wingthing.ai/install.sh | sh
wt roost start # starts a local roost with its wing
# Codex
codex mcp add wingthing -- wt mcp stdio --client codex
# Claude Code
claude mcp add --scope user wingthing -- wt mcp stdio --client claudeThe installer verifies the release checksum and confirms that the downloaded binary implements the command surface shown by the website before replacing an existing installation.
Restart the client after registration. Ask it to call
wingthing_capabilities before it starts work. The model can then:
- discover installed agent CLIs and their runtime requirements;
- start a persistent agent terminal that a person can reattach to;
- submit a headless
agent_runwith a provider and model, then wait for its semantic result without parsing terminal output; - inspect durable conversation trees and checkpoint progress; and
- inspect the effective sandbox before launching anything.
Clients using MCP 2025-11-25 can submit agent_run as a native task and retrieve
its result after disconnecting. See MCP Tasks and client verification
for protocol examples and the Claude Code compatibility check.
agent_run has no deadline by default. Omit timeout_seconds or set it to 0
to run until the agent finishes, you call agent_stop, or the provider or egg
host exits. A positive timeout must be at least 10 seconds and has no upper cap;
86400 sets a one-day timeout. agent_status reports deadline: "no deadline"
for unbounded runs. Wait-tool timeouts only end that observation, and client
disconnection or a wing restart does not stop an admitted egg. Persistent
agent_start and terminal_start sessions also have no deadline.
The child agents use existing project directories and provider credentials on
this computer. Wingthing does not clone the code or copy a provider login. The
wing executes every tool and resolves --client against its clients.yaml.
Sessions belong to the wing's owner user. The unnamed/default client shares that
user's web and remote MCP sessions; named clients have separate logical principals
unless clients.yaml maps them to a common owner. Grants and spawn bounds remain
wing policy, within the current OS user's authority.
wt mcp stdio requires a wing already running for the same WINGTHING_DIR.
Start one with wt roost start or wt wing; stdio never starts a daemon. The
personal wing's owner-only control.sock checks the connecting UID and negotiates
the control protocol and wing ID. Local control keeps working while the relay is
offline. Shared and organization wings do not expose this socket.
On a Mac, start Claude itself as a wing-owned parent with its scoped MCP already injected:
wt wing start --local-only --paths "$HOME/repos"
cd "$HOME/repos/my-project"
wt claude --name research -- --model sonnet
# Ctrl+B Q detaches; wt attach research returns to the same session.Its local and remote workers run as eggs on their owning wings. See Claude across a Mac and an SSH wing for setup, durable receipts, disconnect recovery, and read-only observation.
Use the same runtime directly when a person wants the terminal:
cd /path/to/existing/project
wt egg claude --name research
# Ctrl+B Q detaches without stopping the process
wt attach researchThe provider CLI must already be installed and authenticated for the current OS
user. The project must already exist. No Wingthing account is required. The
initial wt egg command can run independently; wt attach requires a running
wing and checks that client's grants and session ownership.
The CLI also exposes persistent shells, arbitrary commands, and raw terminal operations:
wt terminal --name work # persistent sandboxed shell
wt terminal --name api -- npm run dev # persistent arbitrary command
# Ctrl+B Q detaches without stopping the process
wt attach # list live sessions
wt attach work # reattach by name or ID
wt session ps --json
wt session read api --json
wt session send api r --enter --json
wt session wait api --contains ready --json
wt session rename api frontend --json
wt session fork api --name experiment --json # Claude only; keeps the source intact
wt session kill frontend --jsonTerminal snapshots are ANSI state. Wingthing does not infer that an agent is
done because a string appeared on screen. Use agent_run, agent_wait, and
agent_result when the caller needs semantic task state.
Use the same commands on an SSH host without installing local credentials or opening a local Wingthing store:
# Reattach, choose, or start a persistent shell on the remote machine.
wt --remote work1
wt --remote work1 --remote-cwd /home/me/work/project
# Launch an agent in a remote checkout and return structured session data.
wt --remote work1 egg codex --cwd /home/me/work/project --name review --json -- -m gpt-5.6-sol
# Inspect and control remote sessions without a TTY.
wt --remote work1 --json
wt --remote work1 session ps --json
wt --remote work1 session read review --json
wt --remote work1 session send review --stdin < input.txt
wt --remote work1 session stop review --json
wt --remote work1 attach reviewwork1 is a normal OpenSSH destination or alias. The remote login user owns the
workspace, WINGTHING_DIR, egg policy, provider installation, and provider
credentials. Wingthing does not copy any of them from the client. The same
remote egg.yaml discovery and sandbox enforcement used by a local invocation
still apply. A compatible wt must already exist on the remote host;
--remote-binary selects a nonstandard or staged path. The client does not
install or replace that binary.
For agent launches, put the provider immediately after egg; arguments after
-- reach that provider verbatim.
Bare interactive entry attaches the only matching live session or offers a
picker for several. --remote-cwd filters that choice to one existing remote
directory and is required to start a persistent shell when no matching session
exists. Without it, an empty inventory returns an error. With noninteractive
input, bare entry prints the matching sessions as JSON and never creates one.
--json requests that same read-only inventory explicitly from any terminal.
An explicit attachment detaches when its input closes; it leaves the remote
process running. Use session read, send, and wait for scripted control.
This route needs a POSIX remote command shell because it uses POSIX quoting.
Configure ports, bastions, keys, and host-key policy in OpenSSH. Wingthing does
not disable host-key verification or accept SSH options through --remote.
session kill/stop and egg stop forward the existing local stop operation.
A successful result means the remote egg confirmed that its session process
exited; remote command failures retain their nonzero exit status. Doctor, MCP,
daemon, and browser commands are outside this route.
An unexpected stream closure is reported as an unconfirmed session exit. SSH
status 255 retains the SSH diagnostic and tells you to reconnect and list the
inventory before relaunching. Wingthing never retries a launch automatically.
For a complete launch, detach, reconnect, and inspect journey, see SSH remote sessions.
To give the parent agent one qualified inventory across remote wings, first log in and start Wingthing on every execution machine. Install and authenticate each provider CLI there as the OS user who will own its runs:
wt login
wt startThen log in on the parent-agent machine and add the direct connector (if it is also an execution wing, the commands above already satisfy its wing setup):
wt login
# Codex
codex mcp add wingthing -- wt mcp connect --client codex
# Claude Code
claude mcp add --scope user wingthing -- wt mcp connect --client claudeThe agent calls wing_list, then supplies wing_id to every wing-owned tool.
wingthing.ai authenticates the peers, returns the access-filtered directory,
and carries the WebRTC offer/answer. MCP payloads travel directly to the selected
wing. The first native release expects a shared LAN or tailnet unless ICE servers
are configured. It never silently falls back to the hosted relay.
Direct control has explicit wing-side grants and per-principal spawn/session bounds.
The compatible defaults require no config; operators can narrow grants, change bounds,
or disable native control under direct_mcp in wing.yaml. Organization members
remain owner- and path-scoped, while owners/admins retain all configured paths. See
the security model for the policy shape.
The direct connector is the route to choose when execution moves to another
machine. It does not provide a browser display. Use agent_run for a semantic
result, or agent_start when a person will later attach through the execution
machine's CLI or SSH.
The product is an agent control plane over runtimes:
parent agent: MCP --> agent manager --> selected wing --> sessions + runs
^
person: CLI or browser ----------------/ (inspect or take over)
| Term | Meaning |
|---|---|
| Portal | The unified inventory and controls exposed to an agent through MCP or to a person through the browser. |
| Wing | One machine running Wingthing. It owns the authoritative egg, terminal, and task state on that machine. |
| Session | A persistent interactive PTY for an agent, shell, or command. A person or model can detach and reattach. |
| Run | A supervised headless agent task with semantic status, events, output, and error data. It is separate from a PTY session. |
| Egg | The per-session execution boundary: process, PTY, sandbox policy, and local control socket. |
| Roost | The self-hosted bundle started by wt roost start: a portal/gateway and an embedded wing in one process. Other wings may register with its gateway. |
| Question | Wingthing's contract |
|---|---|
| Execution | Select the wing that will run the process. Direct remote calls require its wing_id; Wingthing never substitutes another wing. |
| Workspace | Pass a cwd that already exists on that wing. Wingthing does not clone, create, or synchronize repositories. |
| Display | Use agent_run for semantic status and a final result without a live browser view. Use agent_start when the task needs a PTY that a person can attach to from the CLI, SSH, or an entitled/self-hosted browser. |
| Credentials | The provider CLI reads credentials from the execution owner's agent home on that wing. Shared hosts separate those homes by owner. Do not put provider tokens or SSH keys in prompts or MCP arguments. |
| Durable memory | Wingthing stores task, result, message, and thread records in ~/.wingthing/wt.db, session state under ~/.wingthing/eggs, and optional prompt memory under ~/.wingthing/memory. Provider-native history stays in the provider home. These are wing-local unless the operator arranges replication. |
WINGTHING_DIR changes the ~/.wingthing state root. A terminal survives client
detachment, but not an unplanned host restart. A run's record and result persist;
an active headless run still depends on the supervising Wingthing process.
wingthing.ai can supply identity, an access-filtered directory, key exchange,
and connection coordination for direct remote MCP. It does not run the agents or
carry direct MCP payloads. Local MCP, local terminals, SSH, and self-hosted roosts
do not require it.
Local stdio, authenticated HTTP, and direct remote MCP derive their tool schemas
from one registry. The remote surface has no mutable current wing: every
wing-owned call requires wing_id, while wing_list is coordinator-owned. A
terminal created with MCP is the same durable egg a browser or CLI can inspect.
Headless runs do not yet have a browser view, locked wings still need a native passkey ceremony, and independent self-hosted roosts do not yet federate their directories. See the direct agent manager design for the rollout and security boundary.
Each egg resolves an egg.yaml policy. The defaults make the working directory
writable, mount home read-only, deny common credential directories, strip the
environment, and allow only the network domains required by the selected agent.
- macOS uses Seatbelt.
- Linux uses user and PID namespaces, mount isolation, seccomp, and cgroups when available.
- A local CONNECT proxy applies domain rules. Linux enforcement limits are documented in the security model.
On Linux, even network: "*" uses the route-less namespace and permits any TCP
target presented through HTTP CONNECT; it is not a general routed interface for
ordinary raw-socket clients, UDP, or programs that ignore proxy configuration.
CONNECT policies filter hosts rather than ports. See sandbox limitations.
Project policy is additive:
# egg.yaml
fs:
- "ro:~/.ssh" # override the default deny for ~/.ssh
network:
- "github.com" # add a domain to the agent profile
env:
- SSH_AUTH_SOCK # pass the SSH agent socketUse wt egg explain <agent> --json or the MCP sandbox_explain tool to inspect
the resolved boundary before launch. Put a default policy at
~/.wingthing/egg.yaml, or use base: none for a blank slate.
If the machine is already an access-segregated VM, --unsandboxed declares the
outer VM as the security boundary. Wingthing still provides persistence and the
control plane, reports outer-boundary to MCP clients, and records that mode in
the audit log. For local MCP, the personal wing must explicitly opt in through
its wing.yaml:
allow_unsandboxed: trueThen wt mcp stdio --unsandboxed --client claude requests that profile. Paths,
grants, ownership, and spawn bounds still apply; the flag cannot override them.
For a work VM with a wing already running for that state directory:
ssh HOST "WINGTHING_DIR=/srv/wingthing-state wt mcp stdio --unsandboxed --client claude"When a person needs a browser, run the browser portal locally first:
wt roost start --https
open https://localhost:8443This self-hosted route needs no Wingthing account or hosted-relay entitlement. For a remote execution machine, keep the portal on localhost and carry its wing connection over SSH; follow the self-hosted remote-browser recipe. For several people, configure OAuth, HTTPS, and an exact enrollment list as described below.
A portal may have several wings. The native CLI can query the same authorized roster and probe each wing through the encrypted tunnel:
wt wings
wt wings --json
# Keep a separate state and login profile for another portal.
WINGTHING_DIR=~/.wingthing-lab wt login --roost https://lab.example.com
WINGTHING_DIR=~/.wingthing-lab wt wings --roost https://lab.example.com --jsonThe browser and native client select a wing by its stable wing_id. A
self-hosted gateway can list its embedded wing and other wings registered with
that same gateway. Independent roosts do not federate their directories.
Run a self-hosted portal, gateway, and embedded wing in one process:
wt roost start --https
open https://localhost:8443--https is an explicit, one-time local trust ceremony. WT creates a
localhost-only CA and server certificate on demand under
~/.wingthing/local-tls, keeps both private keys mode 0600 on this machine,
and installs only the public CA certificate in the current user's trust store.
The CA is name-constrained to localhost and loopback IPs. Inspect or undo the
trust change with wt local-cert status and wt local-cert remove.
macOS shows its native Certificate Trust Settings authorization dialog. Linux
uses the current user's Chromium NSS database and requires certutil from
libnss3-tools or nss-tools.
WT verifies the trust-store result before reporting the CA as installed; on
macOS it evaluates the generated leaf under the localhost SSL policy. A
failed or incomplete trust change is not cached as success.
See the local HTTPS design for the listener topology,
certificate lifecycle, and compatibility matrix.
Port 8443 is the browser listener. WT also keeps a loopback-only HTTP listener
on port 8080 for local wings and wings arriving through an SSH reverse tunnel;
it is not exposed to the LAN. Omit --https to keep an HTTP browser origin, but
single-user/no-login mode still rewrites the implicit listener to
127.0.0.1:8080 and refuses an explicitly non-loopback address. Authenticated
organization and hosted listeners keep their configured bind behavior.
Because local mode deliberately has no human login, WT also rejects non-loopback Host headers, cross-origin browser mutations, and cross-origin browser WebSocket handshakes. Native wings and CLI clients without browser Origin headers remain compatible. These checks are defense in depth around the loopback-only bind, not permission to publish a local-mode listener.
With an OAuth provider and public HTTPS URL, the same deployment becomes a
multi-user shared host. Each terminal and run has an owner, each OAuth client
has a separate actor ID, workspaces are bounded by wing.yaml, and provider
credentials live in the owner's agent home. Public/shared deployments continue
to terminate their externally provisioned HTTPS at Caddy, nginx, a VPN proxy,
Fly, or another ingress; they do not use WT's device-local CA.
OAuth proves identity; it does not decide who belongs on a private roost. Set an exact email enrollment list on every shared roost:
export WT_ROOST_ALLOWED_EMAILS=alice@example.com,bob@example.comOnly those accounts may finish login, use tokens, see wings, or authorize MCP. If the list is empty, any account accepted by the configured OAuth provider can enroll. Set the list on every internet-reachable private roost unless the provider or ingress already enforces the same membership boundary.
An LLM connects to its HTTP MCP endpoint:
codex mcp add lab --url https://lab.example.com/mcp
codex mcp login labThis command shape follows the current
Codex MCP documentation. Claude Code
uses claude mcp add --scope user --transport http lab https://lab.example.com/mcp.
Use the hosted browser only when an account already has hosted-relay access and
the selected wing's effective hosted_relay policy is allow:
wt login
wt start
open https://app.wingthing.aiThe wing connects outbound, so it needs no public inbound port. The hosted browser terminal is not part of the free direct-MCP path. In hosted-browser mode, the service relays application-encrypted terminal and control payloads, sees account and routing metadata, and supplies the browser JavaScript. Read security.md for the exact boundary.
Account entitlement and wing policy are separate. A wing can refuse hosted payload relay:
wt wing config set hosted_relay=deny
wt stop && wt startDirect discovery and signaling still work. The effective setting appears in wing capability metadata, and denials are audited without commands, paths, or payload content.
wt doctor reports what is installed. Interactive sessions support:
| Agent | CLI |
|---|---|
| Claude Code | claude |
| Codex | codex |
| Cursor Agent | agent |
| Gemini CLI | gemini |
| Hermes Agent | hermes |
| Ollama | ollama (qwen3:4b default) |
| OpenCode | opencode |
Support has several evidence levels: catalog, exact headless invocation, synthetic PTY lifecycle, real sandbox startup, live model completion, and MCP orchestration. See supported agent evidence for the latest checked-in verification snapshot and testing for the promotion policy.
curl -fsSL https://wingthing.ai/install.sh | shOr build from source with Go 1.26.9+ and Node.js:
git clone https://github.com/ehrlich-b/wingthing.git
cd wingthing
make checkUpdate an installed binary with wt update.
The contributor reading set is this README, development guidance, current gaps, and the four canonical references below:
- Architecture and agent-manager contracts
- Security and sandbox policy
- Test strategy, gates and provider acceptance
- Fly and self-hosted operations
Focused guides: preview channel,
provider onboarding,
SSH remote sessions, and the
phone coordinator setup.
The agent-facing skill, usage patterns
and web documentation remain user entry points.
Historical designs and dated evidence are preserved in docs/archive/.
MIT