Architecture
At runtime, Loopy is one engine process plus a fresh sandbox for each step's agent. This page walks through both, and the outside services they depend on.
Loopy engine
The running system has two halves. The engine (loopy run) is one ordinary
process: it receives webhooks, turns them into events, and decides which steps run next.
The agents do the actual work, each in its own sandbox, away from the engine. When
something happens outside (a Sentry alert, a push to GitHub) it arrives as a webhook and a
sensor turns the payload into an event. A cron trigger skips that path: on schedule, the
engine builds the tick event itself. Either way, the engine finds the steps that listen
for the event, and each of those steps gets an agent in a fresh sandbox.
External sources your signals
POST /hooks/…
The engine
@sensor code: a raw payload becomes a registered event.on: subscriber. The one piece you can swap out.after: order, fills in their templates, records history.
On one machine the bus lives inside the process; point it at redis to
receive webhooks on one machine and run steps on another. Run history is written to
SQLite. For each step, the AgentHarness fills in the step's
instructions, then starts the agent in the sandbox below.
AgentHarness launches the agent
Sandbox
The agent CLI runs here, isolated from the engine. The agent's model calls and
tool side effects (git push, open a PR) all originate here. Egress is open unless
the sandbox sets a network: allowlist.
How it runs today: one machine, plus sandboxes
Today Loopy is built to run on a single machine. Everything in the diagram except the
sandboxes lives inside the one loopy run process: the SensorRunner,
EventReceiver, EventBus, Scheduler, Runtime, and the per-step AgentHarness are all part of
it, and the StateStore (run history) is a SQLite file next to it. There is one process to
start, one to watch, and one file of history.
The agents are the only part that runs somewhere else. Each step gets its own fresh sandbox
(Daytona by default), so the model calls and the git work happen away from the
engine, with egress open unless the sandbox sets a network: allowlist. Secrets are
injected into the sandbox, never into the engine or the compiled manifest. The engine
decides what runs; the sandboxes do the running. Two services sit outside both and are
reached from inside the sandbox: the model API (Anthropic or OpenAI) and GitHub.
Growing past one machine is a swap, not a rewrite. Point the EventBus at Redis, and
webhooks can land on one machine while workers on other machines pick up steps from the
stream. The sandboxes were already elsewhere, so nothing else moves, and no workflow
.md changes.
The services Loopy relies on.
Loopy itself is the engine that compiles your project and drives runs. It leans on a few external services for the parts it doesn't run.
The default sandbox: a container cloud where each step's agent runs, isolated and built from the image: spec in registry.yml. You reach it with DAYTONA_API_KEY in loopy.env (DAYTONA_API_URL is optional and defaults to prod). To run agents locally instead, switch a sandbox's provider: to docker (hermetic, same image spec) or local (a bare subprocess).
The model behind the default claude-code harness. Set ANTHROPIC_API_KEY in the sandbox's env_file (secrets/base.env); it is injected into the sandbox at runtime, not held by the engine. The scaffold's default model for the claude-code harness is claude-opus-4-8, set per agent in registry.yml (every agent names its model and harness explicitly) (for example on an agent named Claude).
The model behind the codex harness, an alternative to claude-code. A codex agent (say Codex) sets a gpt-* model such as gpt-5.5; put OPENAI_API_KEY in the same sandbox env_file. An agent names exactly one harness, so a project can mix both by referencing a claude-code agent (Claude) on some steps and a codex agent (Codex) on others.
Where agents clone, push, and open PRs. The recommended path is a GitHub App you create with the manifest flow: the engine mints a short-lived, repo-scoped token per step and injects only that token into the sandbox. A plain GITHUB_TOKEN (a PAT) in the sandbox env_file also works if you skip the App.
Where each credential lives is the whole point of the split:
loopy.env
holds the engine's infrastructure creds (Daytona, the GitHub App key), and the sandbox
env_file
holds the agents' creds (the model key). See Secrets.