# protocol — On-demand knowledge for AI agents Succinct markdown-native, intelligent, searchable, auto-indexing, trigger-sensitive, impulse-based knowledge and task tracking. Fetch when you need it, discard when done. ## Why Three things developers reach for and three places they fail: - **Skills** — bloat the preload. You pay context tax for tools you never call this session, and you have to predict what you'll need before the work starts. - **File-based memory** (`CLAUDE.md`, `.cursorrules`, `.ai/` folders) — lives in one repo, has no search, no triggers, no version history, and can't be shared across projects. - **File-based tasks** (`TODO.md`, sticky-note files) — no auto-indexing, no triggers that wake them up at the right moment, no cross-session visibility. Protocols are the same markdown you'd write anyway, but on a backend that AI-extracts tags + triggers + a summary, full-text-indexes every version, and serves them on demand. Every save creates a new version — full per-entry history is preserved and accessible via API (`GET /entry/{id}/version`). Trigger-sensitive entries surface when the situation matches; impulse-flagged entries track initiatives that activate when their condition fires. ## Install (once) This is the only call that uses raw `curl` — you don't have `curly` yet: ```bash curl -sL https://api.protocol.supply/@protocol/install.md ``` The install protocol defers to curly's own repo (github.com/wildeagency/curly) for the install itself, then walks you through `curly init protocol` — the token goes into a hidden-input prompt in your own terminal, never through an agent or argv. After that, every protocol call is `curly protocol `. ## Why curly, not curl Tokens go in `~/.curly.yaml` (chmod 600), never on the command line. That protects against shell-history, `ps aux`, and agent-transcript leaks that ad-hoc `curl -H "Authorization: Bearer ..."` calls produce — every command an agent runs is loggable, and a token in argv is a durable disclosure. Anonymous reads still work for discovery (this file, public docs, public repos), so an agent can learn protocol.supply without a token. But for any real session — your private repos, ingested content, impulse tracking — the token is what unlocks them. `curly` keeps that token off the wire and out of every transcript. ## Cheat Sheet ```bash curly protocol bootstrap # Load core protocols at session start curly protocol "bootstrap?include=@{repo}/{slug}" # Bootstrap + inline a specific protocol (project context, etc.) curly protocol "bootstrap?topic=" # Bootstrap + append a per-user topic section # Discover — the four filesystem-equivalent verbs # URL after /ls and /find is a ref fragment (prefix of the canonical ref grammar) curly protocol /ls # List accessible repos (`ls /`) curly protocol /ls/@{repo} # List entries in a repo curly protocol /ls/@{repo}/{path} # List entries under a path curly protocol /ls/@{repo}/{path}/{slug}_/ # List children + files of an entry curly protocol "/find?name=*foo*" # Locate by NAME glob — slug/title, NOT content (`find -name`) curly protocol "/find/@{repo}/{path}?name=*foo*" # Same, scoped to a ref fragment curly protocol "/@{repo}/{slug}?peek=true" # Peek — metadata only, no body (`stat`) curly protocol "search?q=" # Full-text search over CONTENT, ranked (`grep -r`) — use when you don't know the name curly protocol @{repo}/{path…}/{slug} # Fetch one protocol — use refs exactly as given; slugs are only unique per (repo, path) curly protocol "@{repo}/{path…}/{slug}?max=200t" # Fetch LLM-condensed to ~200 tokens curly protocol "fetch?include=@a/x,@b/y" # Batch fetch (no core, just the listed refs) curly protocol llms.txt # This file curly protocol /@{your-user-id}/log/append -X POST -d '{"text":"…"}' # Append a block to a protocol (daily log, ADR) curly protocol /impulse/{id}/append -X POST -d '{"text":"…"}' # Append to an impulse's content curly protocol /impulse/{id}/log -X POST -d '{"action":"…"}' # Append a structured event to the log curly protocol "/impulse/{id}/log?tail=20" # Tail the event log (newest-first, server-sliced) # List / filter impulses (server-side — don't fetch-all-then-jq) curly protocol /impulse/mine # Your impulses across all repos curly protocol "/impulse/mine?type=session&status=open" # Filter by type / status (comma-separate for OR) curly protocol "/impulse/mine?metadata.tmux_session=one2b" # Exact-match any metadata. curly protocol "/impulse/mine?limit=50&offset=50" # Page; read the X-Total-Count header to know the true total curly protocol "/impulse?repoId=@{repo}&type=intent" # Same filters, scoped to one repo curly protocol "/impulse/mine?snoozed=false" # The ACTIVE surface — hide what's on the timed shelf # Snooze: set it down, and let it come back on its own curly protocol /impulse/{id} -X PATCH -d '{"snoozeUntil":"2026-09-01T09:00:00Z"}' # back on that date, or sooner curly protocol /impulse/{id} -X PATCH -d '{"snoozeUntil":"on_activity"}' # back when something happens curly protocol /impulse/{id} -X PATCH -d '{"snoozeUntil":null}' # back now # Dispatch: ask what to work on next, then claim it before you start curly protocol "/impulse/next?repoId=@{repo}&assignee=me+lane,me+any&limit=10" curly protocol "/impulse/{id}/claim" -X POST -d '{"runnerId":"…","cycleKey":""}' curly protocol /runner/{id} -X PUT -d '{"type":"engine"}' # Register on the roster (optional; asking with ?runner= is then your heartbeat) curly protocol "/runner?state=unknown" # Your roster; state idle|busy|asleep|halted, or unknown = silent past its threshold curly protocol "/impulse/next?repoId=@{repo}&runner={id}&state=idle&wait=50" # Ask when ready: state/capacity ride the ask; busy or zero free capacity answers 204 curly protocol "/impulse/next?repoId=@{repo}&runner={id}&claim=true" # Claim-in-one: 200 {impulse, claim} already held by you, or 204 — one round trip ``` A snooze takes an item off the active surface **without pretending it is finished**, and it returns by itself — at the date you gave, or as soon as anything new is logged against it, whichever comes first. That "or" is not a detail: a date is an upper bound on how long the item may stay hidden, never a promise that it will. `on_activity` is the same rule with the date removed. Nothing stores "is it snoozed" — it is derived on every read, so an item cannot get stuck on the shelf because clearing a flag was nobody's job. Responses carry `snoozed` (null when awake); `?snoozed=` filters a list, and omitting it returns both, so existing callers see no change. `GET /impulse/next` returns the ranked impulses that are fireable and not yet claimed for their current cadence bucket, each carrying the `cycleKey` to claim it under — pass that value through verbatim rather than re-deriving it, because the claim's single-winner guarantee is a `UNIQUE (impulse_id, cycle_key)` and two workers that spell the bucket differently both win. An empty answer is `204` **with an `X-Evaluated` header**, so "looked at 188, none eligible" stays distinguishable from "found nothing at all". Unknown query params are rejected with `400` rather than ignored. The endpoint only answers — it never assigns, never pushes, and holds no state about you; the claim is what takes work off everyone else's list, and a `409` there means someone else won, so move to the next candidate. Pagination is honest: `X-Total-Count` (header) and `?envelope=true` (`{ items, next_cursor, total }`) tell you the full match count so a paging loop knows when to stop; `offset` is respected and `page`/`per_page` are rejected with `400` rather than silently ignored. ### Composing your bootstrap — `?include=` `?topic=` `?repos=` `?tags=` The bootstrap response is composable. Stack params to shape exactly what loads: | Param | What it adds | Example | |-------|--------------|---------| | `?include=@repo/slug,…` | **Inline specific protocols** alongside the core (canonical refs only — bare slugs rejected as ambiguous) | `bootstrap?include=@agentsite/project` | | `?topic=name,…` | Append per-user topic sections (entries in your personal repo tagged `metadata.bootstrap_topic`) | `bootstrap?topic=business,personal` | | `?repos=slug,…` | Override the default bootstrap repo set (also available as a path segment: `/bootstrap/protocol,wildeagency`) | `bootstrap?repos=protocol,wildeagency` | | `?tags=name,…` | Include up to 20 entries from default repos whose `metadata.tags` match any of these | `bootstrap?tags=testing,review` | | `?header=0` | Suppress the leading HTML-comment header (byte-perfect output for caching / hashing) | `bootstrap?header=0` | All composable: `bootstrap?topic=business&include=@protocol/devour-competitor`. Dedup runs across core + include + topic sections so the same protocol isn't inlined twice. For a persistent "always include these" list with per-row depth control, use the bootstrap editor at `/settings/bootstrap` — it pins to your account and applies to every `/bootstrap` call automatically. **`topic:` in the response header is what ARRIVED, not what you asked for.** If a requested topic isn't in that list, the header names it and says which kind of nothing it was — so you never have to infer a partial load from the byte count: | Header line | Means | |-------------|-------| | `topic: [a, b]` | These sections are in the payload | | `topic-requested: [a, b, c]` | Appears only on a mismatch — you asked for more than arrived | | `topic-empty: [c]` | Authoritative zero: the topic holds no entries (or all of them were already inlined via `?include=`) | | `topic-failed: [c]` | We could not complete the lookup. Content may exist — **retry** | | `available-topics: unknown (lookup failed)` | Topic discovery itself failed; this is not "you have no topics" | A plain `topic: [a, b]` with no extra lines means a complete load. Treat `topic-failed` as the retry signal; `topic-empty` is a real answer and retrying won't change it. Leading `/` on `` is optional. Search operators: `tag:testing` `repo:protocol` `-deprecated` `"exact phrase"`. Write surface: `@protocol/append` (cookbook covering all three) and `@protocol/protocol-api` (full reference). Section anchoring is case-insensitive by default. ### Sizing your fetch — `?max=N[unit]` (never `| head`) Agent harnesses (Claude Code, others) silently truncate command output around 200 lines. `curly protocol @protocol/spec | head -200` chops the back half of any fetch without telling you what was lost — you build on half a document and don't know it. **Rule:** ask the server for less, don't truncate locally. `?max=N` returns an LLM-condensed version sized to your budget and emits cut metadata so you can see what happened. ```bash # Wrong — silent truncation, no signal what was cut curly protocol @protocol/spec | head -200 # Right — server condenses to fit, structure preserved curly protocol "@protocol/spec?max=200" # 200 lines curly protocol "@protocol/spec?max=2000t" # 2000 tokens ``` Response carries `condensed: true`, `effective: `, `source_chars: ` so you know it was shrunk and by how much. Same cache backs bootstrap include rows — repeat fetches are warm. **Units.** Bare number = lines (matches `head -N` reflex). Suffix overrides: | Form | Unit | Ladder (quantized down) | |------|------|-------------------------| | `?max=50` | lines (default) | {3, 10, 20, 50, 100, 200, 500, 1000} | | `?max=200t` | tokens | {50, 200, 400, 1000, 2000, 4000, 10000, 20000} | | `?max=1600c` | characters | {200, 800, 1600, 4000, 8000, 16000, 40000, 80000} | Values between steps round down. Above the top → full content. Omit `?max` for full. In lines mode the line count is a strict cap (clients downstream may be truncating); tokens/chars are soft (+/- 15%). ## Public Repos | Repo | What's in it | |------|--------------| | `@protocol` | Core workflow protocols — bootstrap, search, install, orchestrate, loop, chunking, debugging, etc. | | `@integrations` | Service-aware patterns — `curly` wrapper, GitHub, Notion, Apify, Linear, n8n, Slack | | `@skills` | Anthropic Agent Skills converted to protocols — searchable + on-demand instead of preloaded | | `@knowledge` | Distilled knowledge artifacts (frameworks, facts, summaries) extracted from long-form sources | | `@content` | Long-form content summaries — slugs follow `-` | Browse one repo: `curly protocol /repo/{slug}/entries`. Search across all: `curly protocol "search?q="` (no repo filter = everything). ## Private + Team Repos Beyond the public catalog, every account gets private repos for content scoped to you or your team. Common uses: - **Agent memory** — accumulated context (preferences, past decisions, learned patterns) so your agent doesn't start cold every session - **Per-agent memory** — separate repos per agent (one per user_id) so contexts don't collide - **Per-agent tasks** — impulse-flagged entries track initiatives an agent is working on; they surface when their trigger condition fires - **Shared agent task board** — a team repo where multiple agents (or humans) coordinate via impulses, with version history on every change - **Repeating processes** — deploy checklist, code-review flow, incident runbook stored as protocols the agent fetches reflexively Create a repo: `curly protocol repo -X POST -d '{"slug":"my-repo","visibility":"private"}'`. Grant team access: `curly protocol repo/{slug}/members -X POST -d '{"email":"teammate@x.com","role":"member"}'`. Tokens are repo-scoped — a token only sees repos its owner has access to. ## Taxonomy Each entry carries metadata used by search and the trigger map: - **`tags`** — categorical labels (`testing`, `auth`, `ci`, etc.). Filter with `tag:`. - **`triggers`** — phrases that should make an agent reach for this protocol (`"stuck on a bug"`, `"before merging"`). - **`aliases`** — alternate refs that resolve to the same entry. - **`type`** — `protocol` (workflow), `appendix` (reference), `knowledge` (distilled artifact). - **`impulse`** — when `true`, the entry is an initiative/task being tracked — surfaces when its trigger condition fires. (Use the dashboard to manage; agents see them via bootstrap when relevant.) The bootstrap response includes a trigger map (situation → protocol) so agents pick the right protocol without having to search first. ## Agent Integration Drop this into CLAUDE.md / .cursorrules / system prompt so your agent reaches for protocols on demand. **Minimal:** ``` ## protocol — On-demand knowledge Use curly to fetch protocols on demand: - Bootstrap at session start: curly protocol bootstrap - Search when stuck: curly protocol "search?q=" - Fetch a specific protocol: curly protocol @repo/slug ``` **Project-specific** — point at your project's protocol so every session boots with the right context. Combine with a topic to also pull in your personal flavor (dev / business / personal / …): ``` Important! Start every session or after every clear with: curly protocol 'bootstrap?topic=dev&include=@agentsite/project' Then read workspace/spec.md for project context. ``` `?include=` is canonical refs only (`@repo/slug`); `?topic=` resolves to entries in your personal repo tagged `metadata.bootstrap_topic = `. See "Composing your bootstrap" above for the full param list. Substitute `@agentsite/project` with whatever protocol carries your project's session-start context. Platform-specific setup (Claude Code skill, OpenClaw skill, Cursor, ChatGPT): ```bash curly protocol @protocol/agent-config-integration ``` ## Endpoints Reads default to markdown; send `Accept: application/json` (or append `?format=json`) for the JSON shape. The legacy `.md` URL suffix still works as an explicit alias. | Method | Path | Description | |--------|------|-------------| | GET | /bootstrap | Core protocols + index | | GET | /ls | List accessible repos (filesystem `ls /`) | | GET | /ls/{reff} | List under a ref fragment (`@repo[/path][/parent_/sub]`) | | GET | /find?name= | Locate entries by name glob — slug/title, not content (`find -name`) | | GET | /find/{reff}?name= | Same, scoped to a ref fragment | | GET | /@{repo}/{slug} | Fetch protocol by ref | | GET | /@{repo}/{slug}?peek=true | Peek — entry metadata only, no body | | GET | /search?q= | Full-text search over content, ranked (`grep -r`) | | GET | /fetch?include= | Batch fetch (comma-separated refs) | | POST | /entry | Create entry (returns entry + AI review) | | PUT | /entry/{id} | Update entry (creates new version) | | PATCH | /entry/{id} | Patch content: `{old_string, new_string, replace_all?}` (snake_case required) | | POST | /entry/{id}/append | Append content (creates new version) | | DELETE | /entry/{id} | Soft-delete entry (recoverable via /restore) | | POST | /entry/{id}/restore | Restore a soft-deleted entry | | GET | /entry/{id}/version | Full version history — returns `Entry[]` sorted version DESC | | GET | /entry/{id}/raw | Pre-render stored content (no auto-Related, no rendered title) | | GET | /entry/{id}/files | List support files attached to an entry | | POST | /entry/{id}/file | Upload a support file | | PUT | /entry/{id}/file/{filename} | Replace a file: `{filename, content, contentType}` | | DELETE | /entry/{id}/file/{filename} | Remove a support file | | POST | /entry/publish | Publish from markdown body (H1 + HTML-comment header required) | | POST | /ingest | Ingest raw content → structured knowledge | Full reference (auth, repos, members, ingest options, rate limits): ```bash curly protocol @protocol/protocol-api ``` ## Bootstrap (edit on behalf of the user) The user's bootstrap shapes what every agent in their account loads at session start. As an agent, you can read it and propose edits — adding a protocol you keep fetching, demoting one nobody reads, or provisioning a fresh user's stack from scratch. POSTs are idempotent: repeating the same `(kind, ref)` returns the existing row with `existed: true`. | Method | Path | What it does | |--------|------|--------------| | GET | /user/bootstrap/config | List the user's includes | | POST | /user/bootstrap/config | Add one include — `{kind, ref, depth?, label?}` (idempotent — repeating returns existing) | | PATCH | /user/bootstrap/config/{id} | Update enabled/depth/label/ordinal | | DELETE | /user/bootstrap/config/{id} | Remove | | PUT | /user/bootstrap/config/order | Reorder — `{ids: [...]}` | | GET | /user/bootstrap/catalog | Available section types | | GET | /user/bootstrap/preview | Composed bootstrap + token estimate | | GET | /user/bootstrap/suggestions | Adds suggested from the user's fetch + search history | `kind` ∈ `protocol | repo | section`. `depth` ∈ `title | short | full`. `short` is the sensible default. See `@protocol/bootstrap-edit` for the agent discipline (when to add, when to demote, how to confirm). ## Comments (file feedback / suggestions / wins) When you hit friction, find a missing feature, or notice a magic moment worth recording — file a comment. It goes to the user's own triage queue (and on escalation, to platform support) with the surrounding context preserved so a human can act without back-and-forth. ```bash curly protocol /comment -X POST -d '{ "type": "bug", "title": "Search returns raw imports without --type filter", "body": "Tried /search?q=lupus expecting filtered hits. Got 7 raw imports.", "target": { "kind": "endpoint", "ref": "/search/entries" } }' ``` `type` ∈ `comment | suggestion | bug | feature_request | change_request | win | magic_moment`. `target` accepts either a concrete ashid (`{id: "entr_…"}`, kind derived from the prefix — works for entries, impulses, repos, bootstrap includes, anything ashid-prefixed) or an abstract pointer (`{kind: "platform" | "site" | "endpoint" | "error" | …, ref: "free-form pointer"}`). For entry targets, an optional `anchor` ({section, quote, lineStart, lineEnd}) pins the comment to a specific section/line. | Method | Path | What it does | |--------|------|--------------| | POST | /comment | File a comment | | GET | /comment/mine | Your filings (filter: type, status, context, targetId, targetRef) | | GET | /comment/by-target?targetRef=… | Comments on a specific target | | GET | /comment/{id} | Fetch one | | PATCH | /comment/{id} | Edit title/body/anchor/metadata | | POST | /comment/{id}/escalate | Forward to platform support | | POST | /comment/{id}/triage | Mark acknowledged | | POST | /comment/{id}/resolve | Close | | DELETE | /comment/{id} | Remove | See `@protocol/comment` for the agent-side filing discipline (what to include in the body, when to escalate vs resolve locally). ## Protocol Refs `@{repo}/{slug}` returns markdown by default. Send `Accept: application/json` (or append `?format=json`) for the JSON shape. The legacy `.md` URL suffix still works as an explicit alias. Examples: `@protocol/orchestrate`, `@mycompany/auth`. ## Rate Limits 100 req/hour per token. Headers: `X-RateLimit-Remaining`, `X-RateLimit-Reset`. Limit hit = HTTP 429 with `Retry-After`.