Loopy is a declarative
framework for agent workflows.
A workflow is a directory of Markdown files — one file per step. You write what the agent should do in prose; a short config header says how the step is wired. Loopy reads the files, builds the graph, and runs it.
$pip install loopy-core
---
on: Incident # the one entry step — a registered Event
agent: Investigator # defined once in registry.yml
output: { root_cause: str, repro: str } # structured, typed outputs
emits: WorkItem # onto the bus, for other workflows
budget: { wall_clock: 20, spend: { usd: 4 } }
---
Investigate {{ event.title }} from {{ event.link }}. Find the
root cause, write a minimal repro, and hand off a WorkItem
for the resolve workflow to pick up.
workflows
A workflow is a directory of steps.
A Loopy project is a handful of directories — workflows, sensors,
skills — plus one registry.yml. Each step is a single .md file.
ai-sre/
├─ registry.yml
├─ workflows/
│ ├─ triage/ investigate.md
│ ├─ resolve/ arbitrate.md fix.md review.md ship.md
│ ├─ upkeep/ scan-deps.md
│ └─ confirm/ check.md
├─ sensors/ sensors.py
└─ skills/ code-review/ testing/
Here's the resolve workflow, file by file — each step runs after the one before it:
---
on: WorkItem
agent: Investigator
output:
goal: str
---
Decide the goal for "{{ event.proposed_goal }}" given
the work item at {{ event.link }}.
---
after: arbitrate
agent: Fixer
output:
pr_url: url
summary: str
---
Implement the goal: {{ arbitrate.goal }}. Open a PR
and summarize the change.
---
after: fix
agent: Reviewer
output:
verdict: enum[pass, fail]
---
Review {{ fix.pr_url }}: {{ fix.summary }}. Return a
pass/fail verdict.
---
after: review
agent: Releaser
emits: GoalShipped
---
If {{ review.verdict }} is pass, merge the PR and ship
the change.
a step file
A config header, then prose.
Every step file has two parts. A short header between the --- lines wires
the step into the graph. The prose below is the agent's objective.
---
on: WorkItem # what triggers the step
agent: Investigator # who runs it
output: { goal: str } # typed result
---
Decide the goal given the work item at
{{ event.link }}.
Reading data
The body pulls values in by name. {{ event.field }} reads a field off the event that triggered the step. {{ fix.field }} reads a typed output from an upstream step — you name the step (fix) and the field, and it's available because you declared after: fix.
Typed throughout
Outputs declare their shape, so a handoff to the next step is checked when you compile — not at runtime.
The trigger for the workflow's first step — a registered event, or a schedule like cron("0 9 * * *"). Exactly one step has it.
Which step this one runs after. The after: links are what build the order. Every step except the first has one.
Which agent runs the step. Agents are defined once in registry.yml and referenced here by name.
The typed result the step returns. Later steps read these fields by name to pass data down the chain.
Optional: emits:
puts an event on the bus for other workflows, and
budget:
caps time and spend.
registry.yml
Key abstractions live in YAML.
One file declares the reusable pieces your steps refer to by name: the agents that run steps, the sandboxes their code runs in, and the events that move between workflows.
defaults:
agent:
sandbox: default
harness: { runtime: claude-code, model: claude-sonnet-4-6 }
agents:
Investigator: { skills: [code-review] }
Fixer: { harness: { model: claude-opus-4-8 }, tools: [open_pr], skills: [testing] }
Reviewer: { tools: [run_evals], skills: [code-review] }
Releaser: { tools: [merge_pr, set_flags] }
sandboxes: # where an agent's code runs — image + egress allowlist
default:
provider: daytona
image: { debian_slim: "3.12", apt: [git] }
network: [github.com]
events: # typed messages on the bus; a step may only trigger `on:` one registered here
Incident: { fields: { source: enum[sentry, linear, datadog], issue_id: id, title: str, link: url } }
WorkItem: { fields: { link: url, root_cause: str, proposed_goal: str } }
how you build
From files to running, in two commands.
The whole project is files on disk. You write them, compile to check the graph, then run the server.
A folder per workflow, an .md file per step, and a registry.yml for the shared agents, sandboxes, and events.
loopy compile reads the files without running them and checks the graph: every reference resolves, every event is registered, no step is orphaned.
loopy run starts the server. It listens for events and webhooks, fires schedules, and runs each step on its agent.
$ loopy compile
✓ 4 workflows · 7 steps · 4 events — graph ok
$ loopy run
hosting sensor webhooks · 1 schedule armed · waiting for events
why code-first
Automations that live in your repo.
Tools like Cursor and Codex let you set up agent automations in a dashboard. Loopy does the same job — agents that run on a trigger — but as code you own, not config behind a login.
Reviewed like code
Workflows are Markdown and YAML in version control. They diff, they go through pull requests, they roll back. No clicking through a console to see what changed.
Open where it counts
Workflows are code you own and version, running your choice of models — not config trapped in a dashboard.
A system, not one bot
Many workflows wired together by a typed event bus, with a different agent for each step. Dashboard automations top out at one agent and one trigger.
The four workflows in the AI SRE example aren't isolated. A step's emits: puts an event on the bus; another workflow's on: picks it up. Here both triage and upkeep feed the same WorkItem into resolve:
cookbook
The AI SRE example, workflow by workflow.
Four workflows in one project, wired together by events. Click into any one.
Triage
An incident arrives from a sensor. An agent finds the root cause and turns it into a WorkItem.
open example →Resolve
Takes a WorkItem and drives it to done: decide the goal, implement it, review the PR, then ship.
open example →Upkeep
Every night, scan dependencies that changed since the last run and open WorkItems for risks.
open example →Confirm
When a metric crosses its threshold, confirm the shipped goal actually moved it — or reopen it.
open example →