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
peterzakin/loopy
workflows/triage/investigate.md
---
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/
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:

workflows/resolve/
---
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.

arbitrate.md
---
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.

on:

The trigger for the workflow's first step — a registered event, or a schedule like cron("0 9 * * *"). Exactly one step has it.

after:

Which step this one runs after. The after: links are what build the order. Every step except the first has one.

agent:

Which agent runs the step. Agents are defined once in registry.yml and referenced here by name.

output:

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.

registry.yml
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.

1
Define the workflows

A folder per workflow, an .md file per step, and a registry.yml for the shared agents, sandboxes, and events.

2
Compile

loopy compile reads the files without running them and checks the graph: every reference resolves, every event is registered, no step is orphaned.

3
Run the server

loopy run starts the server. It listens for events and webhooks, fires schedules, and runs each step on its agent.

~/ai-sre
$ 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.

in your repo

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.

your code, your models

Open where it counts

Workflows are code you own and version, running your choice of models — not config trapped in a dashboard.

composable

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:

Sentry cron 3am Incident WorkItem GoalShipped triage 1 step resolve 4 steps upkeep 1 step
workflow event on the bus sensor / schedule