wingthing

module
v0.145.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: MIT

README

wingthing

ci

Wingthing is an agent manager for agents. Give Codex, Claude, or another parent agent one typed control plane for starting and supervising durable agents across all your machines. A person can inspect or take over the same sessions from a terminal or browser.

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.

https://github.com/user-attachments/assets/f1f04caf-4b07-4298-ba76-db5b226c38f2

Give an agent access to your agents

Install Wingthing, then register its local MCP server with the agent that will coordinate the work:

curl -fsSL https://wingthing.ai/install.sh | sh

# Codex
codex mcp add wingthing -- wt mcp stdio --client codex

# Claude Code
claude mcp add --scope user wingthing -- wt mcp stdio --client claude

The 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_run with a provider and model, then wait for its semantic result without parsing terminal output;
  • coordinate bounded prompt loops and dependency graphs;
  • exchange owner-scoped messages with another authenticated agent client; and
  • inspect the effective sandbox before launching anything.

The local server uses the current OS user's authority. --client supplies ownership and audit attribution inside Wingthing, not a new operating-system security boundary. Optional grants and spawn bounds live in ~/.wingthing/clients.yaml.

To give the parent agent one qualified inventory across remote wings, log in on the client machine and use the direct connector instead:

wt login

# Codex
codex mcp add wingthing -- wt mcp connect --client codex

# Claude Code
claude mcp add --scope user wingthing -- wt mcp connect --client claude

The 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.

Hosted terminal relay is a separate transport decision. Existing configs remain compatible, while a wing can refuse relayed payloads regardless of account entitlement:

wt wing config set hosted_relay=deny
wt stop && wt start

Direct discovery/signaling still works; terminal and general control payloads must go directly to the wing. The effective setting appears in wing capability metadata and denials are audited without commands, paths, or payload content.

Use the same runtime yourself

wt terminal --name work               # persistent sandboxed shell
wt terminal --name api -- npm run dev  # persistent arbitrary command
wt egg claude --name research         # persistent sandboxed agent

# Ctrl+B Q detaches without stopping the process
wt attach                              # list live sessions
wt attach research                     # reattach by name or ID
wt attach research --remote box        # reattach over ordinary SSH

The CLI exposes the raw terminal layer for scripts:

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 kill frontend --json

Terminal snapshots are ANSI state. Wingthing doesn't 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.

The runtime model

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.

wingthing.ai is the hosted identity, directory, key-exchange, and connection coordination service—roughly the control plane in a tailnet. It does not run the agents. New free accounts use direct remote MCP; Pro adds the encrypted hosted terminal and control relay. Local MCP, SSH, and self-hosted roosts do not require it.

Shared control contract

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.

Sandbox

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 socket

Use 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.

Browser and hosted service

Browser access is optional:

wt login
wt start
open https://app.wingthing.ai

The wing connects outbound, so it needs no public inbound port. Free accounts use the hosted site to register the wing and set up direct remote MCP; the hosted browser terminal is not part of that free path. Accounts with hosted relay access may use the application-encrypted browser terminal and control relay. The coordinator sees account, routing, and connection metadata, and the hosted browser still trusts JavaScript served by the service. Read security.md before making a stronger claim.

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 --json

The browser and native client select a wing by its stable wing_id. The hosted directory aggregates every wing registered to the account or its organizations. Peer-roost federation remains follow-up work.

Shared roost

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.com

Only 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 lab

This command shape follows the current Codex MCP documentation. Claude Code uses claude mcp add --scope user --transport http lab https://lab.example.com/mcp.

Supported agents

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.

Install and build

curl -fsSL https://wingthing.ai/install.sh | sh

Or build from source with Go 1.26.6+ and Node.js:

git clone https://github.com/ehrlich-b/wingthing.git
cd wingthing
make check

Update an installed binary with wt update.

Documentation

License

MIT

Directories

Path Synopsis
cmd
wt command
internal
control
Package control defines the transport-independent contract for Wingthing runtime operations.
Package control defines the transport-independent contract for Wingthing runtime operations.
egg
fsutil
Package fsutil contains the small filesystem primitives shared by security-sensitive persistence code.
Package fsutil contains the small filesystem primitives shared by security-sensitive persistence code.
localtls
Package localtls manages the deliberately small certificate authority used by Wingthing's local HTTPS listener.
Package localtls manages the deliberately small certificate authority used by Wingthing's local HTTPS listener.
mcp
Package mcp exposes a roost's privileged tools as a role-scoped MCP server.
Package mcp exposes a roost's privileged tools as a role-scoped MCP server.
ws
test
compat command
Command compatcheck supplies black-box helpers for the release compatibility gate.
Command compatcheck supplies black-box helpers for the release compatibility gate.
mock-agent command
web/canary-agent command
canary-agent is the browser E2E suite's stand-in for an agent CLI.
canary-agent is the browser E2E suite's stand-in for an agent CLI.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL