How sop, sopops and what comes next fit together.
The concepts behind managing instructions for many task-driven agents:
what a block is, how an agent is built from blocks, and how a change gets from an edit to a live call.
Glossary · The problem · Blocks · Agents · Compiling · Changing things safely · Serving · The loop
| Term | Meaning |
|---|---|
| Block | A reusable piece of instruction: a shared instruction or a procedure. |
| Shared instruction | Prompt text that isn't a procedure: identity, tone, policy, facts. |
| Procedure (SOP) | Goal, when it applies, steps (with tools), never-do's, warning signs. |
| Group | A named, ordered list of blocks that agents list as one. |
| Agent | Platform id, context, an ordered list of blocks, variables. |
| Context | Text only one agent gets; first in its prompt. |
| Variable | A {{placeholder}} in a block, filled per agent. |
| Compile | Agent + blocks + variables → one prompt. |
| Lock file | The prompt's hash and the exact blocks in it. |
| Plan | Which agents a change touches, with prompt diffs. |
| Affected | Just the list of changed agents, for CI. |
| Lint | Duplicates, drift and contradictions across an agent's blocks. |
| Behavior gate | Evals on the affected agents; block the change on regressions. |
| Verify | Compare live platform prompts with the compiled ones. |
| Draft / Release | Saved-but-unseen edits / the snapshot agents are served. |
A team running one agent per customer ends up with dozens of long prompts, mostly copies of each other. A policy change means editing every one by hand. Copies drift: one agent upsells "once per call", another "twice", and nobody remembers which was intended. When a call goes wrong, nobody can say exactly what that agent was told.
The fix is to write each piece of instruction once, build every agent from those pieces, and always know what changed, where, and what was live.
| Tool | What it is | Status | |
|---|---|---|---|
| 📋🧩 | sop | The SOP compiler. Blocks and agents as files in git; a CLI compiles prompts and checks changes. | Released |
| 📋🗄️ | sopops | The same compiler behind a UI and a database: forms, drafts, releases, and an API agents call at start. Coming: QA on real calls against the SOPs that were live, patterns across calls, and suggested block edits as GitHub PRs. | v0 (QA and GitHub planned) |
Same concepts, same compiler. sop is for teams who keep instructions in git; sopops is for teams who keep config in a database and edit it in a UI.
A block is a piece of instruction written once and reused. It holds only its own content: it doesn't say which agents use it. There are two kinds.
Shared instruction. Plain prompt text that isn't a procedure: identity, tone, policies, product facts.
<!-- instructions/brand-voice.md -->
Speak warmly and briefly. Ask one question at a time. Never upsell more than once per call.Procedure (SOP). A standard operating procedure: what to do in a given situation, step by step. It has structure, because QA and tools need to know what "following it" means.
<!-- procedures/allergen-check.md -->
# Allergen check
**Goal:** Customer leaves knowing whether their order is safe for their allergy.
**When:** Any order where the customer mentions a food allergy.
## Steps
1. Ask if anyone in the order has a food allergy
2. Check each item against {{menu_allergen_link}} `tool: lookup_allergens` `required`
## Never
- Never say an item is "allergen-free" or "safe"
## Warning signs
- Customer mentions anaphylaxis; transfer to {{staff_transfer}} `tool: transfer_to_staff`| Part | Meaning |
|---|---|
| Goal | The outcome that means it worked. QA judges calls against it. |
| When | The situation that triggers it. |
| Steps | Ordered actions. A step can name a tool, and mark the call required. |
| Never | Things the agent must not do while following it. |
| Warning signs | Signals to escalate, usually to a person. |
| Delivery | Where it lives: in the prompt (default), or fetched with a get_sop tool when needed (keeps long prompts short on voice). |
Group. A named, ordered list of blocks that many agents share, like delivery-orders = allergen-check, delivery-handling. An agent lists a group as if it were one block.
flowchart LR
subgraph Blocks["Blocks: written once"]
I1[restaurant-host]:::ins
I2[brand-voice]:::ins
I3[closing]:::ins
P1[allergen-check]:::sop
P2[delivery-handling]:::sop
G1{{delivery-orders}}:::grp
end
G1 --- P1
G1 --- P2
classDef ins fill:#eaf1ff,stroke:#2f6feb,color:#0b3d91
classDef sop fill:#fff4e0,stroke:#bf6a02,color:#7a4300
classDef grp fill:#f3edff,stroke:#8250df,color:#4c2a9e
An agent is the one place blocks come together. It has:
| Field | Meaning |
|---|---|
| Platform id | Exactly one of LiveKit, Vapi, ElevenLabs or Retell, and the agent's id there. |
| Context | Facts only this agent needs (location, hours, what it doesn't offer). Goes first in the prompt. |
| Blocks | The instructions, procedures and groups it uses, in prompt order. |
| Variables | Values for {{placeholders}} in its blocks, like restaurant_name. Defaults live in settings. |
# agents/tonys-pizza.yaml
livekit: tonys-pizza
context: |
Tony's is a wood-fired pizza shop in Brooklyn. Pickup only after 10pm.
blocks:
- restaurant-host
- pizza-context
- brand-voice
- delivery-orders
- closing
variables:
restaurant_name: Tony's PizzaReading one agent tells you everything its prompt is made of. Seen across all agents, that's a grid:
In sopops, the agent page is a builder: its context, then its blocks as a stack of cards you add, reorder and open, with the compiled prompt beside it.
Compiling turns an agent into one full prompt: its context, then each block in order (groups expanded in place), with variables filled in. Procedures get a single "Procedures" heading before the first one.
flowchart LR
A[agent: tonys-pizza] --> C((compile))
B[(blocks)] --> C
V[variables] --> C
C --> P["tonys-pizza.prompt.md"]
C --> L["lock.json<br/>hash + exact blocks used"]
Every compiled prompt comes with a lock: the prompt's hash and the exact list of blocks (and their hashes) that went into it. That's how a call can be traced back to precisely what the agent was told.
The output is just text, one prompt per agent, so it works with any platform: the agent loads it from a file (sop) or fetches it at call start (sopops).
Because agents share blocks, one edit can change many prompts. Every change goes through the same checks before it reaches a call.
flowchart LR
E[Edit a block] --> PL[plan<br/>which agents change,<br/>exact prompt diff]
PL --> LI[lint<br/>duplicates, drift,<br/>contradictions]
LI --> AF[affected<br/>only the changed agents]
AF --> EV[evals on those agents<br/>old prompt vs new]
EV -->|regressed| X[blocked]
EV -->|passed| M[merge / publish]
Impact. Editing a block changes every agent that lists it, directly or through a group. sop plan shows which agents change and the exact prompt diff for each:
flowchart LR
BV[brand-voice edited]:::ins --> T[tonys-pizza]
BV --> L[luigis-trattoria]
BV --> S[sakura-sushi]
BV -.not listed.-> C[la-casita]
classDef ins fill:#eaf1ff,stroke:#2f6feb,color:#0b3d91
Checks.
| Check | Catches | Command |
|---|---|---|
| Validate | Broken files: unknown blocks, missing steps, unset variables | sop validate |
| Lint | The same sentence twice, a near-copy that drifted, "once" vs "twice", "always X" vs "never X" | sop lint (--strict fails CI) |
| Behavior gate | An agent that behaves worse after the change | sop affected + your evals |
Lint compares wording, not meaning: "upsell up to 2 times" vs "never more than once" isn't caught yet.
Behavior gate. A diff shows what text changed, not whether agents still behave. The gate runs evals only for the agents a change touches, old prompt vs new, and blocks the change on a regression, with the failing call attached:
Drift. Someone hotfixes a prompt in the platform's dashboard at 11pm, and the next deploy silently reverts it. sop verify reads each agent's live prompt from ElevenLabs, Vapi or Retell and diffs it against what's compiled. Run it nightly.
flowchart LR
G[compiled from blocks] --> D{same?}
P[live on the platform] --> D
D -->|yes| OK[in sync]
D -->|no| DR[drifted: show the diff, fail the job]
With sopops, edits are drafts until someone publishes a release: an immutable snapshot of every block and every compiled prompt. Agents fetch their prompt when a call starts, so a change goes live without redeploying the agent.
sequenceDiagram
participant Ed as Editor (UI)
participant S as sopops
participant A as Agent (call start)
participant R as Call record (e.g. LiveKit room metadata)
Ed->>S: save edits (drafts)
Ed->>S: review diff, publish release 7
A->>S: GET my prompt (optionally ?release=6 for an A/B split)
S-->>A: prompt + release + block versions
A->>R: record release 7 and block versions
Note over S: roll back = make release 6 live again
| Concept | Meaning |
|---|---|
| Draft | A saved edit that no agent sees yet. |
| Release | Numbered snapshot agents are served. One is live at a time. |
| Pin | A call can ask for a specific release, for A/B tests. |
| Rollback | Make an older release live again. |
| Fetch log | Which agent got which release, and when. |
Git works the same way without a server: a commit is the release, build/ holds the prompts, and a revert is the rollback.
Where this is going. Because every call records exactly which blocks it ran, QA can judge each call against the SOPs that were actually live, and the findings can turn into edits to those same blocks.
flowchart LR
B[Blocks] --> C[Compile]
C --> G[Gate: plan, lint, evals]
G --> R[Release]
R --> CALL[Calls]
CALL --> QA[QA against the live SOPs<br/>clusters of violations]
QA --> SUG[Suggested block edits]
SUG -->|reviewed like any change| B
- QA (in sopops, planned). Check calls against each SOP's goal, steps and never-do's; group violations into clusters a small team can review.
- Suggestions. Turn clusters and feedback into proposed edits to specific blocks, opened as GitHub PRs, reviewed as a normal change and gated by the same evals.
- Locks (returning with suggestions). Mark blocks that automated suggestions must never edit.
- Flows. Multi-node agents (Retell conversation flows, ElevenLabs workflows, LiveKit handoffs), where each node is its own list of blocks. Waiting on real demand.
Apache-2.0




