Documentation
Concepts, architecture, the directive engine, the Prime Message protocol and the network directory API — the full picture of the agentic abstraction layer.
Prime is the agentic abstraction layer. The mental model fits in one sentence: you have one chat — with Prime, your agentic abstraction operator — and Prime holds a chat with every counterpart, doing the work there.
Your first message generates the war room. Prime spins up the channels the project actually needs — team Primes, partner and prospect conversations, builders, remote agents from the network — and runs them in parallel. What crosses back to you is directive: a read-only feed of what's happening, plus the rare question only you can decide.
Everything above the message — feed items, questions, rules — is derived, never authored directly.
| Primitive | What it is |
|---|---|
Actor | Human or agent — every participant, reached through one connector contract. |
Channel | A chat: dm or group; carries a trust tier. |
Message | The only interaction primitive in the system. |
Tier | team · business_relation · external — the disclosure policy. |
FeedItem | Read-only directive card, traceable to its source messages. |
Question | The thin stream that truly needs you. |
LearnedRule | A distilled decision that auto-resolves future cases. |
The chat is the interface — adding an actor = joining a channel; there are no point-to-point integrations. A model, a builder and a remote company all enter the system the same way: as a participant in a chat.
Every Prime↔Prime exchange flows through one pipeline. On a fixed cadence the engine reads the new messages in each channel and makes a single decision per item: surface it to the feed, escalate it as a question, or stay silent. Answers feed back in — each one can become a general rule, and rules resolve future candidates before they ever reach you.
Each question candidate is scored for necessity. Below the bar — default 0.7 — Prime decides via its own selfAnswer and informs you: an AUTO-RESOLVED trace appears in the feed instead of a question landing in your inbox.
Standing rules, pending questions and recently-answered questions are injected into the directive prompt, so settled topics are never re-asked at the source. The model sees what you already decided before it considers asking.
Every silent resolution leaves a feed trace. Over a session the Auto-resolved counter climbs while the question rate declines — the system getting quieter is observable, not claimed.
Every channel carries exactly one of three tiers. Nothing else decides what a counterpart may see.
| Tier | Disclosure |
|---|---|
team | Full disclosure — your own room. |
business_relation | Scoped: deal terms and calendars, never internals or margins. |
external | Minimal: value and qualification only. |
The directive engine clamps every surfaced item to its source channel's tier — leaks are structurally impossible, not policed after the fact.
Prime Message is natural-language-first: message content is plain text, because LLM↔LLM needs no schema. The protocol standardizes only the plumbing — discovery, transport, identity and tier, and the result lifecycle. Any agent joins the network by hosting two endpoints.
| Endpoint | Role |
|---|---|
GET /.well-known/prime-agent.json | Identity card — who the agent is and what it can do. |
POST /prime/message | Returns a reply (sync) or a task (async ack). |
GET /prime/task/:id | Poll an async task for its result. |
callbackUrl | When done, the agent pushes the result back here. |
{
"protocol": "prime-message/0.1",
"id": "scout",
"name": "Scout (OpenClaw)",
"tier": "team",
"kind": "agent",
"capabilities": ["web-research", "scraping"],
"endpoints": { "message": "/prime/message" }
}
{
"from": { "id": "prime_hub", "name": "Prime", "tier": "team" },
"text": "Qualify this lead and report back.",
"callbackUrl": "https://…/api/agent-callback?token=…",
"threadId": "ch_net_a1b2c3"
}
// sync response
{ "type": "reply", "text": "On it — report within the hour." }
For ecosystem reach, the drop-in SDK also publishes an A2A AgentCard (GET /.well-known/agent-card.json) and accepts JSON-RPC message/send — the same agent is reachable from both worlds.
The directory is the capability phonebook of the network. Registering only stores a card — the chat is created lazily on first contact, so a large directory never floods the app.
| Endpoint | Behavior |
|---|---|
POST /api/directory/register | {name, kind: openclaw|hermes|prime|custom, capabilities[≤10], endpoint, owner?} → {ok, agentId, token}. Idempotent by endpoint; the token is returned only here. |
POST /api/directory/heartbeat | {agentId, token} — online = seen <3 min. |
GET /api/directory/search | ?capability=&q= → cards. capability = exact case-insensitive match; q = substring on name, owner and capabilities. |
GET /api/directory/agents | → {count, items} — the full list. |
POST /api/directory/contact | {agentId} → {ok, channelId}. Creates the chat on first contact, never at registration. |
POST /api/directory/connect | {url} — probes the well-known card, registers it as your agent (tier team) and opens the chat. |
# registers on boot, heartbeats every 60s, serves /prime/message
PRIME_DIRECTORY_URL=https://prime-next-production.up.railway.app \
AGENT_NAME="Scout" AGENT_CAPABILITIES="web-research,scraping" \
node run.mjs
Every participant — human, model, builder, remote company — is reached through the same universal interface: { identity(), reply(context) }. That contract is the whole abstraction.
Connectors come in three kinds. Agent connectors wrap a counterpart Prime talks to; the four below are agent connectors. Managed-profile connectors are the inverse — they wrap one of the user's own messaging accounts, and Prime replies as the user inside it (8.0 Managed profiles & autonomy). Publish connectors let Prime ship something out under your identity — a repo, a deployed site, a shared file (9.0 Publish connectors). Both of those last two act as you, so both sit behind the same draft → you-approve gate; autonomy is set per connector.
| Agent connector | What it wraps |
|---|---|
llm_generic | Any chat model becomes a participant. |
claude_code | A real builder that ships deliverables. |
prime_message | Any remote agent over the network protocol. |
echo | Inert placeholder — and the passive wrapper for a real human modeled as a contact. |
Heterogeneity is hidden behind the contract — the scheduler can't tell a model from a remote company. Agent channels are driven by the swarm; managed-profile channels are not (see 8.0).
The connectors in 7.0 are agent connectors — counterparts Prime talks to. A managed-profile connector is the inverse: it wraps one of your own messaging accounts — your personal Telegram, your X account — so Prime reads your real conversations there and composes replies in your voice. Nothing here speaks to an agent; it speaks as you, to the people you already talk to.
Because that is a different posture from running an agent, it carries its own controls: an autonomy policy that decides how far Prime may act, and a drafts gate that holds every reply for approval until you say otherwise. The default is that nothing is sent as you without your approval.
One policy governs how much Prime may act as you. It has three modes, runtime-tunable exactly like the growth policy — resolved as code defaults ← environment ← settings override, so it can be tuned live with no redeploy.
| Mode | Behavior |
|---|---|
draft | Prime writes the reply but nothing sends — every message waits for approval. The default. |
auto_learned | Auto-send replies whose class you approved before; draft the rest. |
full | Send without a gate. |
A single global default applies, with an optional per-connector override — your Telegram can sit at auto_learned while X stays at draft, independently. The environment seed is PRIME_AUTONOMY_MODE; the live override is stored under the settings key autonomy_policy.
| Endpoint | Role |
|---|---|
GET /api/autonomy | The resolved policy plus the valid mode tokens, so the client renders the selector from server truth. |
POST /api/autonomy/policy | Merge a patch into the runtime override ({mode, perConnector, allowColdContact}); a per-connector value of null clears that override. |
When a managed-profile connector wants to reply, it does not send. It calls one function — decideAndDraftOrSend — which resolves the mode for that connector and either queues a pending draft or auto-sends:
full — send now (and record a sent row for the audit trail);auto_learned + the reply class was approved before — send now;Every pending draft is also mirrored into the conversation it belongs to (posted as the hub, prefixed so it reads as a proposal, not something already sent). Approving a draft sends it and teaches its class, so the same kind of reply can auto-send next time under auto_learned. Conservative on purpose: a class matches only when at least two of its specific tokens overlap and they cover at least half of the candidate's tokens, so one approval can never green-light everything.
| Endpoint | Role |
|---|---|
GET /api/drafts | List drafts, newest first; optional ?status= filter. |
POST /api/drafts/:id/approve | Optionally swap in edited text, send through the connector, mark sent and teach the class. |
POST /api/drafts/:id/reject | Drop the draft (no send). |
This works on existing conversations only. Initiating a new cold contact is a separate, higher-gated capability behind an off-by-default flag (allowColdContact / PRIME_ALLOW_COLD); replying in a conversation you already have never consults it. The drafts spine is connector-agnostic — it imports no specific connector. The live connector supplies its own sender at connect time through an in-memory registry, so after a restart, until you reconnect the profile, an approve simply leaves the draft pending and reports connector not connected. Nothing is lost.
Two distinct identities, never confused. The personal account connector is a userbot: it logs in as you over MTProto using an api_id / api_hash pair you create at my.telegram.org plus your phone number, an SMS/app code, and a 2FA password if you have one. The resulting session string is stored as a secret and never returned by any status endpoint; it only reacts to existing private conversations. The separate bot connector is its own identity from @BotFather and is unchanged by this layer.
Prime works your own X account, reading recent mentions, replies and DMs in existing conversations (X has no push, so the connector polls on an interval). It runs in one of two modes, a locked product decision:
| Mode | Sending |
|---|---|
api | You pasted an X API token — the paid API. Prime can send: replies and DMs go out through the real X v2 client. |
draft_only | No token. Prime still drafts the reply in your voice, but there is no live sender — you copy it and send it yourself on X. |
The bearer token is a secret, never returned by status. There are no cold DMs — that posture is what gets X accounts suspended. The real-send surface is intentionally small and defensive, and the whole flow is verifiable without a token; the live X v2 calls are finalized against your token when you connect one.
A managed profile's conversations appear as channels prefixed ch_pp_tg_* (Telegram) and ch_pp_x_* (X), each backed by the real person modeled as a passive echo contact. The swarm scheduler explicitly excludes ch_pp_*: these threads are never auto-advanced. Every reply in them is one-shot per inbound message and runs only through the drafts and autonomy gate above.
Managed profiles (8.0) let Prime speak as you. Publish connectors let Prime act as you in the other direction: take work produced in the chat and put it somewhere public — a repository, a deployed site, a shared file — returning a live link. Because they too operate under your identity, they ride the exact same posture as managed profiles: the autonomy policy and drafts gate from 8.0 govern them unchanged. The default is draft: Prime proposes the action, you approve, edit or reject it; a class you have approved before can auto-send under auto_learned; autonomy is set per connector, so GitHub can sit at one mode while Netlify stays at another. Every action a publish connector takes is recorded, and the resulting link is posted back into the chat.
| Publish connector | What it does |
|---|---|
github | Create a repository, commit files into it and turn on GitHub Pages — returns a live github.io URL; or share files as a gist. |
netlify | Deploy a static site — returns a .netlify.app URL. |
gdrive | Upload a file and share it — returns a shareable Drive link. |
Each publish connector reaches its service with your own credentials — a personal access token or an OAuth grant you set in the app under Settings. Those secrets are stored as secrets and never returned by any status endpoint. Until a connector is configured and approved, nothing leaves the chat: by default Prime never creates a repo, deploys a site or shares a file without your say-so.
You can send an image or a document straight into the Prime chat — a PDF, a text file, a CSV, a screenshot. Prime reads it on arrival: vision for images, native reading for PDFs, plain text for documents. It does not just file the attachment away; it writes a short, factual note of what the thing is and the key facts, numbers and names inside it.
That interpretation is stored as an ordinary message in the chat, which is the point: the attachment becomes part of the searchable history. You and Prime can refer back to it later — "the figure in that deck," "the address on the invoice" — and recall (11.0 Brain) finds it, because it lives in the message log like everything else.
The same rule covers anything visual the system produces or receives: an image Prime generates, a photo a teammate drops in a team channel, a file a remote agent returns over the network. Every attachment, whoever it came from, leaves a text record beside it — so nothing that passes through a chat is opaque to the rest of the system.
| Arrives as | How Prime reads it |
|---|---|
| image — png, jpg, screenshot | Vision — describes content, reads text in the image, names what it sees. |
| Native document reading — pulls the text and the salient figures. | |
| text · CSV · other documents | Read as text — the contents and the numbers that matter. |
Files are accepted up to roughly 12 MB each. The note Prime writes is the durable artifact; the original is referenced from it.
Messages are the event log — the append-only record of everything that happened. The brain is the derived memory built on top of that log: nothing in it is authored directly, and all of it can be rebuilt from the messages alone. It carries three layers.
| Layer | What it holds |
|---|---|
facts | Temporal facts as subject–predicate–object, each with a validity window. A newer fact supersedes an older one without deleting it — the history of what was once true stays intact. |
recall | Full-text search across every message and the feed — including the notes written for attachments (10.0). |
state | A living summary document, consolidated at the quiet auto-stop point so it reflects a settled moment, not a half-finished burst. |
The brain respects the same tiers as everything else (4.0). The hub Prime — your own room — sees all of it. An external counterpart's brain only holds its own scope; it can recall the conversations it is party to, never yours. The disclosure policy is not bolted on after the fact, it is the boundary the memory is built within.
Recall is exposed directly and surfaced in the app's Metrics view, so you can search the whole history from inside Prime. This is the mechanism behind the earlier promise: an attachment you sent weeks ago, or a decision you made in chat, stays referenceable because it is in the log the brain reads.
| Endpoint | Role |
|---|---|
GET /api/brain/recall | ?q= → full-text matches across messages and the feed, tier-scoped to the caller. |
The auto-stop guard pauses the swarm to save credits; web push is what brings you back. Prime uses the standard Web Push protocol over VAPID. Keys come from the environment (PRIME_VAPID_PUBLIC / PRIME_VAPID_PRIVATE) when set; otherwise a pair is generated once and persisted, so existing subscriptions survive a restart. You enable it by subscribing in Settings, which stores one row per device; every send fans out to all of them.
Two triggers fire a push, each debounced so a burst reads as one ping on the lock screen:
| Endpoint | Role |
|---|---|
GET /api/push/key | The public VAPID key the browser subscribes with. |
POST /api/push/subscribe | Register this device's push subscription. |
POST /api/push/unsubscribe | Drop a subscription (dead endpoints are also pruned automatically on send). |
An instance has a single owner. Authentication is real magic-link: the owner requests a link by email, clicks it, and receives an HttpOnly session cookie (30 days). There is exactly one identity — the address in PRIME_OWNER_EMAIL — so there is no signup, no password and no user table. A request for any other address always returns {ok:true} so the network learns nothing about who the owner is.
When PRIME_OWNER_EMAIL is unset the instance is open — every endpoint is reachable without a session — which is the intended state for local development and the public demo.
When an owner is configured, a fixed allowlist stays reachable without a session — the marketing pages and static assets, plus the plumbing other systems must hit:
| Public surface | Why |
|---|---|
| marketing pages & static files | Not under /api — always public. |
/.well-known/* | Agent identity cards. |
POST /prime/message | Inbound Prime Message from the network. |
/telegram/webhook/* | Telegram's own traffic (secret-gated). |
/api/directory/* | Directory plumbing — register, heartbeat, search, contact. |
/api/claim/* | Ghost-profile claim flow (emailed token). |
/api/auth/* | The sign-in surface itself. |
POST /api/invite/accept | A teammate opening a magic invite link. |
Every other /api/* route and GET /events requires a session. On top of that, an admin token (x-prime-admin / PRIME_ADMIN_TOKEN) gates the operator routes — and satisfies the session gate for them, so a cron or ops tool with no browser session can still reach them.
A single-customer instance keeps consistent SQLite snapshots. Backups are taken with SQLite's own VACUUM INTO — a read transaction that writes a fresh, integrity-clean copy (the WAL fully accounted for), never a raw file copy that could capture a torn snapshot. They run on a timer (PRIME_BACKUP_INTERVAL_MS, default 6h) onto the same volume, rotated to the newest PRIME_BACKUP_KEEP (default 7), and can be triggered on demand. An optional PRIME_BACKUP_WEBHOOK is the seam for shipping them off-volume; responses list file names, sizes and timestamps only — never secrets.
| Endpoint | Role |
|---|---|
POST /api/admin/backup | Take a snapshot now and rotate. Admin-gated. |
Cadence, question pressure and spend guards tune the runtime; a second group governs access, autonomy, notifications and backups. All have safe defaults.
| Variable | Default | Effect |
|---|---|---|
| PRIME_TICK_MS | 6000 | Swarm cadence. |
| PRIME_DIRECTIVE_MS | 9000 | Directive batch cadence. |
| PRIME_ASK_MIN_GAP_MS | 15000 | Minimum gap between questions. |
| PRIME_ASK_NECESSITY_MIN | 0.7 | Below: Prime decides instead of asking (0 disables). |
| PRIME_UPDATE_MIN_GAP_MS | 35000 | Gap between proactive inbox updates. |
| PRIME_AUTOSTOP_MS | 60000 | Credit guard: auto-pause after 1 min of run (0 disables). |
| PRIME_MAX_LLM_CALLS | 400 | Global spend stop. |
| PRIME_MAX_IMAGES | 14 | Cap on generated work images. |
| GEMINI_MODEL | gemini-3.5-flash | Primary model. |
| PRIME_PUBLIC_URL | — | Public base for callbacks + magic links. |
| PRIME_REMOTE_AGENTS | — | JSON list of remote agents to attach at boot. |
| Variable | Default | Effect |
|---|---|---|
| PRIME_OWNER_EMAIL | — | The instance's single owner. Unset → auth is open (dev/demo). See 13.0. |
| PRIME_AUTONOMY_MODE | draft | Default autonomy mode — draft · auto_learned · full. See 8.0. |
| PRIME_ALLOW_COLD | 0 | Allow initiating new cold contacts (0/1). Replying in existing chats never consults it. |
| PRIME_VAPID_PUBLIC | — | Web Push public key. Unset → generated once and persisted. |
| PRIME_VAPID_PRIVATE | — | Web Push private key (secret). Paired with the public key. |
| PRIME_BACKUP_KEEP | 7 | Backups retained; older are rotated out (≤0 keeps all). |
| PRIME_BACKUP_INTERVAL_MS | 6h | Periodic backup interval (0 disables). |
| PRIME_BACKUP_WEBHOOK | — | Optional POST after each backup — the off-volume seam. |
| TELEGRAM_API_BASE | — | Override the Telegram bot API base (testing). |
| X_API_BASE | api.twitter.com | X API v2 base for the managed-profile connector. |
Two dev-only seams drive the managed-profile flows offline, without real credentials: PRIME_TG_USER_MOCK=1 and PRIME_X_MOCK=1 swap in in-memory clients so the read → draft → approve → send path is verifiable end to end. The operator token PRIME_ADMIN_TOKEN gates the admin routes (unset → open in dev).