gooselake is an air traffic control tower for machine agents, not a chat widget with ambition problems.
That line is a little rude, but it is also the cleanest way to explain the repo.
Most agent products start with the wrong center of gravity: the UI.
You wire a provider into a frontend, stream a few tokens, add a tool or two, and it feels like progress, at least for a while.
Most agent products start the same way:
- wire a model provider into a frontend
- stream some tokens
- add a few tools
- bolt on persistence later
That works right up until you want the agent to stop being a demo and start being a system.
The moment an agent has to do real work on a real machine, the problem changes completely.
Now you are not building "chat."
You are building a runtime.
You do not just need "chat with tools." You need:
- durable sessions
- reconnectable event streams
- machine-local auth
- processes that outlive a browser tab
- worktrees and filesystem actions
- team communication
- provider-specific quirks hidden behind one contract
Most agent stacks start as frontend demos and accidentally become distributed systems.
gooselake skips the accident.
That is the moment this project becomes relevant.
It is a standalone runtime you can deploy on a laptop, a VPS, or a dedicated machine, then drive from any frontend over HTTP and SSE.
Instead of burying agent behavior inside your UI, you move the hard part into a machine-side runtime that can persist, recover, stream, and keep working even when the client disappears.
The browser tab is now a remote control, not the engine block.
The project exists because frontend-first agent architectures usually collapse under their own success.
They do not fail because they were foolish. They fail because they actually started to matter.
At first, everything feels simple:
flowchart LR
UI[Frontend App] --> Provider[Provider SDK / CLI]
UI --> LocalState[In-memory state]
Then you need more:
- a second provider
- then a third
- resumable sessions
- background execution
- real filesystem work
- team messaging
- process management
- durable history
- a second client
Now the app looks more like this:
flowchart TD
UI[Frontend] --> ProviderA[Codex integration]
UI --> ProviderB[Claude integration]
UI --> ProviderC[ACP integration]
UI --> Sessions[Session state]
UI --> Events[Streaming logic]
UI --> Auth[Auth handling]
UI --> Tools[Tool routing]
UI --> Teams[Team state]
UI --> Worktrees[Worktree logic]
UI --> Proc[Process management]
That architecture is fragile. Every new product surface ends up rebuilding the same runtime concerns.
It is the software equivalent of taping a second steering wheel onto the passenger seat and calling it multi-driver support.
This repo takes the opposite approach:
flowchart LR
UI1[Desktop UI]
UI2[Web App]
UI3[CLI]
UI4[Ops Console]
UI1 --> Runtime[gooselake]
UI2 --> Runtime
UI3 --> Runtime
UI4 --> Runtime
Runtime --> Codex[Codex]
Runtime --> Claude[Claude]
Runtime --> ACP[ACP]
Runtime --> SQLite[(SQLite)]
Runtime --> MCP[MCP Sidecar]
Runtime --> Machine[Processes / Worktrees / Filesystem]
The runtime becomes the durable backend product. The UI becomes a client.
That is the whole bet.
And it is a strong bet, because the runtime is where the complexity was going to end up anyway.
The cool part is not just that it talks to Codex, Claude, and ACP.
The cool part is that it treats agent execution as infrastructure:
- providers are adapters, not app architecture
- events are durable and replayable
- long-running work belongs to the runtime, not the browser tab
- machine operations are first-class
- frontend apps can stay thin
In practice, that means you can build a serious agent system once at the runtime layer, then expose it through multiple products without rewriting the hard parts.
That is the difference between:
- "our app has an agent feature"
- and "we have an agent platform we can keep building on"
Provider APIs change. Runtime contracts should not.
gg-runtime-server is the control plane for agent work on a machine.
It owns:
- provider-backed sessions
- turn execution
- persistent event history
- SSE fanout
- auth staging and provider readiness
- process execution
- team messaging and deliveries
- worktree allocation and lifecycle
- MCP tool plumbing
The UI does not need to know how Codex auth is staged, how Claude is bridged, how ACP agents are launched over stdio, how provider quirks are normalized, or how a worktree is claimed. It just speaks HTTP and SSE.
That boundary is what makes the architecture reusable.
If your agents can mutate repos and spawn processes, the machine is the product boundary. This repo just stops pretending otherwise.
Think of the system like this:
flowchart TD
Client[Frontend / CLI / API Consumer]
HTTP[HTTP API]
SSE[SSE Streams]
Core[Runtime Core]
Store[(SQLite Store)]
CodexP[Codex Provider]
ClaudeP[Claude Provider]
AcpP[ACP Provider]
MCP[GG MCP Server]
Bridge[Claude Bridge]
Host[Host Machine]
Client --> HTTP
Client --> SSE
HTTP --> Core
SSE --> Core
Core <--> Store
Core --> CodexP
Core --> ClaudeP
Core --> AcpP
Core --> MCP
ClaudeP --> Bridge
CodexP --> Host
ClaudeP --> Host
AcpP --> Host
MCP --> Host
The important boundary is between the client and the runtime, not between the client and the model provider.
That sounds subtle, but it changes everything. Once that boundary is real, your frontend stops owning orchestration logic it was never meant to carry.
Different engine rooms, same cockpit instruments.
Today the runtime supports:
- Codex
- Claude
- ACP
Those providers are normalized into one shared model for:
- sessions
- turns
- streamed events
- approvals
- terminal assistant output
- recovery and replay
That matters because frontends should not have to learn different lifecycle semantics per provider.
The provider layer should be replaceable. The runtime contract should be stable.
Codex, Claude, and ACP are different dialects. The runtime is the interpreter with a durable transcript.
Events are stored in SQLite and streamed over SSE.
That gives you:
- reconnectable UIs
- replayable session history
- better debugging
- less frontend guesswork
- a backend that can keep running while clients disconnect and reconnect
This runtime is built for agents that actually operate on a machine, including:
- filesystem work
- provider session execution
- MCP tool calls
- process management
- worktree operations
- team-style coordination flows
It is much closer to a control plane than a chatbot server.
That is the right mental category for this repo.
Or, if you prefer the less polite version: it is Kubernetes for agent turns, but with receipts.
The receipts matter. SQLite stores the event log, scoped sequencing, delivery states, and recovery metadata, which means the runtime can explain what happened instead of shrugging in JSON.
The browser reconnects. The runtime remembers.
This is one of the most interesting parts of the repo.
The runtime does not just let one user talk to one model. It has a native agent layer where agents can:
- message other agents
- supervise other agents
- interrupt or defer messages based on policy
- spawn teammates
- claim worktrees
- coordinate live work across one shared runtime
And it does that without caring whether a given agent is running on Codex, Claude, or ACP.
That is a bigger deal than it sounds.
Most systems treat agent collaboration like theater. A bunch of personas, some prompts, and good luck.
This repo treats it like transport infrastructure.
Agent-to-agent messaging is modeled like delivery infrastructure:
- policy
- queueing
- retries
- cancellation
- replay
- audit events
So the system is not just "agents talking." It is agents talking with timing rules, interruption rules, delivery states, deferred injection, and receipts.
That is how you turn multi-agent behavior from roleplay into operations.
The runtime ships as one bundle:
bin/gg-runtime-serversidecars/claude-bridge/claude-bridgesidecars/gg-mcp-server/gg-mcp-server
That bundle is the backend product. Your UI is not where the business logic has to live anymore.
If you are building multiple agent applications, or even one serious one, that separation pays for itself fast.
Your frontend is Slack. This runtime is the message bus plus scheduler plus incident log.
Repository shape:
crates/runtime-server: HTTP/SSE server and bootstrap layercrates/runtime-core: runtime state machine, orchestration, events, teamscrates/runtime-store-sqlite: durable state and event storagecrates/runtime-provider-codex: Codex adaptercrates/runtime-provider-claude: Claude adaptercrates/runtime-provider-acp: ACP stdio adaptercrates/runtime-tools: shared runtime tooling/process supportsidecars/claude-bridge: Claude bridge processsidecars/gg-mcp-server: MCP sidecar process
Why sidecars exist:
flowchart LR
Runtime[Runtime Server]
Bridge[Claude Bridge]
MCP[GG MCP Server]
SDK[Claude SDK behavior]
Tools[Tool execution boundary]
Runtime --> Bridge
Runtime --> MCP
Bridge --> SDK
MCP --> Tools
They keep unstable or provider-specific behavior isolated behind stable process boundaries.
That is boring in exactly the right way.
You want the provider weirdness and MCP weirdness in boxes with labels, not leaking into the whole runtime like a kitchen sink backing up into the living room.
Most operational commands in this repo are exposed through make:
make helpmake installOr directly from GitHub:
curl -fsSL https://raw.githubusercontent.com/amxv/gg-agent-runtime/main/scripts/install-runtime.sh | \
bash -s -- latestOnly set GG_RUNTIME_REPO if you intentionally want a fork:
GG_RUNTIME_REPO=owner/repo make installcodex login
claude loginACP is different in the first release:
- configure an ACP-compatible agent command in
runtime-server.toml - ACP transport is stdio only in v1
- ACP auth is agent-managed in v1, so there is no runtime ACP login/import flow
export PATH="$HOME/.local/bin:$PATH"
cp "$HOME/.local/runtime-server.toml.example" ./runtime-server.toml
gg-runtime-server --check-config --config ./runtime-server.toml
gg-runtime-server --config ./runtime-server.tomlThat is the default path. Change config only when you need different bind addresses, auth settings, or data locations.
To enable ACP in the same config:
[providers.acp]
enabled = true
command = "/absolute/path/to/your-acp-agent"
args = ["serve", "--stdio"]
transport = "stdio"Optional environment variables for the ACP agent can be set under [providers.acp.env].
One command:
make vps-deployWith post-start API verification:
make vps-deploy BASE_URL="http://127.0.0.1:8080" TOKEN="$GG_RUNTIME_TOKEN"Manual runbook and troubleshooting are documented in docs/DEPLOYMENT.md.
The runtime exposes:
- JSON HTTP endpoints for lifecycle actions
- SSE streams for live event delivery
- OpenAPI for the route surface
Important routes:
GET /healthGET /openapi.yamlGET /v1/openapi.yaml/v1/providers/*/v1/sessions/*/v1/events/stream/v1/teams/*/v1/processes/*/v1/worktrees/*/v1/mcp/*
Simple shape:
sequenceDiagram
participant UI as Frontend
participant RT as gooselake
participant P as Provider
participant DB as SQLite
UI->>RT: POST /v1/sessions
RT->>DB: persist session
RT-->>UI: session created
UI->>RT: POST /v1/sessions/:id/turns
RT->>P: execute turn
P-->>RT: events / final output
RT->>DB: persist events
RT-->>UI: SSE stream + final response
This is the design goal in one sentence: your application should talk to a runtime service, not directly to provider CLIs.
That keeps your frontend simpler, your backend more honest, and your system much easier to evolve.
Team orchestration is already a first-class runtime API here. The MCP-facing team/tool surface is still evolving toward full parity, and it is worth being honest about that. The right story is not "everything is magically unified already." The right story is "the runtime contract is real, and the remaining surface area is being pulled into it on purpose."
- Codex: machine login via
codex login, with staged runtime auth support from~/.gg/codex/auth.json - Claude default:
host_machine, which uses machine login material - Claude optional:
runtime_managed, which lets the runtime own imported auth/config files - ACP: configured agent command over stdio, with auth handled by the agent itself in v1
ACP first-release limits:
- no streamable HTTP ACP transport;
transport = "stdio"is the only supported mode - no runtime ACP logout/API-key/import routes
- no runtime-managed ACP auth mutations
- ACP permission requests are unsupported in v1 and fail the active turn clearly if the agent sends
session/request_permission
- public route:
GET /openapi.yaml - auth route:
GET /v1/openapi.yaml - repo artifact:
openapi/runtime-server-openapi.yaml
Regenerate it with:
make api-docs-refreshReview API/doc sync status:
make api-docs-status
make api-docs-checkThe OpenAPI file is generated from maintained source parsing in
crates/runtime-server/src/openapi.rs,
not from runtime route introspection. That is a deliberate tradeoff and is documented honestly.
If you want to build from source instead of downloading a release bundle:
make install-sourceThe repo includes a GitHub Actions release workflow:
It builds release bundles for:
- Linux x86_64
- macOS arm64
- macOS x86_64
and publishes gg-runtime-<platform>-<arch>.tar.gz assets on v* tags.
- Install: docs/INSTALL.md
- Deployment: docs/DEPLOYMENT.md
- API: docs/API.md
- API Doc Sync Workflow: docs/API_DOC_SYNC.md
- Architecture: docs/ARCHITECTURE.md
- Config template: examples/runtime-server.toml
This repo is already beyond the "prototype with ambition" stage.
It includes:
- provider-backed sessions
- durable event replay
- team/comms flows
- worktree lifecycle support
- MCP sidecar support
- real Codex, Claude, and ACP validation
- release packaging
- deploy docs
- generated OpenAPI
The obvious next step is not "make another backend." The obvious next step is to build better clients on top of this one.
If this project works, it should make future agent applications feel lighter, because the heavy machinery already lives here.
That is the real appeal of the project.
Not "look, it streams tokens."
More like: "look, the architecture finally stopped lying."