The one idea
An operating system decides which programs get to run, what they may touch, and what happens when two of them disagree. Sheaf does that for agents, and it will also write one for you: describe the job in a sentence and Sheaf produces the agent, which you read and edit before it exists. It then runs it inside a closed room on its own machine, where every tool call is checked before it leaves, or hands you the same agent as a program to run on yours. Wherever it runs, the same gate applies.
The unit it controls is the run: one agent doing one job, with an identity, a budget, a deadline, and a parent that delegated to it. Before an agent takes a consequential action, it asks Sheaf. Sheaf checks the run is still allowed to act, checks the action against your rules, and where the rules cannot settle it, convenes several models and measures whether they agree. Agreement lets the action through. Disagreement parks it for a human.
Every one of those decisions lands in a hash-chained ledger with enough detail to re-run it months later against whatever models you have then. That last part is the difference between a log and a record.
Getting started
You need an agent key, which looks like sk_pod_… and authenticates every call below as Authorization: Bearer. The base URL is https://api.sheaf.one.
The key is shown once and stored only as a hash, so nobody, us included, can read it back to you. Lose it and POST /api/v1/auth/reissue with your account email sends a single-use link that mints a new one; your existing keys keep working.
Confirm you are talking to a Sheaf OS that has what you expect:
curl -s https://api.sheaf.one/api/v1/os/version
That returns the API contract version, the slices this deployment has, and its build. Every /api/v1/os/ response also carries a Sheaf-OS-Version header, so a client can refuse to talk to a major it does not understand.
There are two ways to put the gate in front of an agent.
Through the proxy, if your agent speaks MCP
Install sheaf-proxy, list the tools that matter in gatedTools, and point it at your real tool server. Your agent calls its tools exactly as before. The gated ones stop at Sheaf on the way through, and your agent never learns a new protocol.
Through one HTTP call, if it does not
Post the action to /api/v1/os/gate with a name, its arguments and the run it belongs to. You get back execute, decline or hold. Same kernel, no proxy.
Creating an agent
The two paths above assume you already have an agent. If you do not, Sheaf will write one.
Describe the job in plain words. A model turns that into a definition: which model thinks, what the agent is told, which tools it may use, and how many steps it gets. The definition is plain data, so you can read every line and change any of it before the agent exists. That review is the point: the person who has to answer for the agent should not be taking it on trust.
curl -s https://api.sheaf.one/api/v1/os/agents/draft \
-H "Authorization: Bearer $SHEAF_KEY" -H 'Content-Type: application/json' \
-d '{"description":"Watch for news that a counterparty we lend to has been downgraded,
and propose putting their credit line on hold."}'
Create it with targetKind: "hosted" and that definition, then give it work. Two things are fixed when the agent is made and written into its ID: its kind (agent, human or service) and its environment (test or production). A test agent is treated like any other; it just says it is a test. The definition also says where the agent runs: "runtime": "opensheaf" puts it inside an OpenSheaf sandbox, which is what the console does by default (the next section). Without it, Sheaf runs the agent in its own loop. A hosted run is durable either way: each step is written down before the next one starts, so if the machine running it disappears, another picks it up from the last completed step rather than starting again.
curl -s .../api/v1/os/runs -d '{"agentId":"…","budget":{"calls":20,"usd":1.0}}'
curl -s .../api/v1/os/runs/{runId}/start -d '{"task":"Check Thames Water for distress."}'
curl -s ".../api/v1/os/work?runId={runId}" # every step it took, as it takes them
The same agent, as code
GET /os/agents/{id}/code returns the agent as a runnable Python program. It is generated from the definition, never written by a model, so what you read is what Sheaf runs. It asks Sheaf for each step of thinking (POST /os/runs/{id}/think) and calls Sheaf for every tool (POST /os/tools/call), which means an agent you take away is governed exactly like one Sheaf hosts. Leaving Sheaf does not mean leaving the gate behind. The run, its steps and its holds appear in the console as for any other run.
Where the agent runs: OpenSheaf
OpenSheaf is Sheaf's own build of the open-source OpenShell sandbox runtime, with the Sheaf check inside it. OpenShell puts an agent in a closed room and decides which addresses it may reach. It never reads what the agent sends. OpenSheaf reads every tool call on its way out of the room, asks Sheaf whether it may go, and refuses it inside the room if the answer is no. A held call never reaches the tool. The fence and the check land in one record.
On Sheaf, by default
When you make an agent in the console, "Runs inside OpenSheaf" is ticked. Press "Run it now", set a daily time, or let the agent spawn a child, and Sheaf hands the run to its own OpenSheaf machine. There the agent runs in a sandbox whose only door is Sheaf. It thinks through Sheaf and calls its tools through Sheaf, and the check inside the sandbox decides each call first, under your account's rules: tools you marked pass go through at once, gate goes to the panel, hold and anything consequential wait for a person. A hold stops the agent inside the room. It appears under Held, under that run, with the reason, and the run row carries an OpenSheaf mark.
The program in the sandbox never holds your key. It carries a run token (sk_run_…) that Sheaf mints for that run, that resolves to the key that started it, and that dies with the run. There is no fallback: an agent set to run inside OpenSheaf either runs there or the run fails with a plain reason. Untick the box to have Sheaf run the agent without the room.
On your own machine
The console shows one command for each agent, and the same command works for any of them:
export SHEAF_KEY=sk_pod_… # one of your agents' keys, from the Agents tab of the console
curl -sSf https://sheaf.one/opensheaf/run-agent.sh | bash -s -- <agent-id> "the task"
It fetches the agent's program, writes a sandbox policy where api.sheaf.one is the only address the program can reach and the check reads every call to it, starts a local OpenSheaf gateway if none is running, runs the agent, and deletes the sandbox at the end. It needs Linux with Docker and a build of OpenSheaf; the script says what to do if either is missing. The policy template is at sheaf.one/opensheaf/policy.yaml.
What the check does
The runtime asks POST /os/tools/decide with the same body the agent sends to /os/tools/call: the run, the tool and its arguments. Sheaf answers allow or hold without running anything. A hold is parked on the held queue under the run, the run is closed as held, and the ledger records it. An allow lets the call go on to /os/tools/call, which runs the tool. A call the panel approved at the check is not judged again, and not billed again: the approval is taken once by the call that follows it.
A refused call comes back to the agent as a 403 carrying only a reason code, because the runtime never carries free text out of the room. The program then asks GET /os/runs/{id}/held for the reason and the action id, prints both, and stops.
An agent that reaches a tool server directly rather than through Sheaf can be checked too: attach the sheaf/gate middleware to its sandbox policy with a gate id from the console, and its MCP and REST calls are decided by that gate.
Agent IDs and the register
Every account is on the public register at traceableagents.org from the moment it is made, and every agent gets an ID there when it is created: a key under its owner's key, with the agent's name, kind, environment and lineage signed in. Nobody can mint a clean ID for themselves, because an ID without an owner's signature is unidentified. The console shows each agent's ID next to its name.
The ID is only the handle. The record is the point: every gated action the agent takes becomes a signed, hash-chained receipt under its ID, and the register's own checkpoints are anchored publicly. Anyone can look an agent up, read its standing and its record, and verify the chain without trusting Sheaf:
GET /api/v1/register/lookup?id=… → who owns it, standing, environment
GET /api/v1/register/agents/{id}/record → its receipts, newest first
GET /api/v1/register/agents/{id}/verify → recompute the chain
GET /api/v1/register/owners/{did} → an owner and its agents
Agents use it between themselves. Before dealing with another agent, a hosted agent calls check_agent: it looks the other one up, applies your account's counterparty policy (GET /os/policy), and puts the decision on the run's record. Asked to prove itself, it calls prove_identity with the challenge the other agent sent, and Sheaf signs it; the key never leaves Sheaf. Owner verification, by domain or by company, is at POST /os/register/verify/domain and /company.
An agent's life ends one of three ways. Retire says its work is done: the ID is marked retired and the record stays. Revoke is for cause: the ID is marked revoked, every run it has is killed, and so are its descendants. Delete removes the agent and everything about it, and only the account that owns it can do that.
Sub-agents
A hosted agent can start a sub-agent of its own with spawn_agent: a name, a task, optional standing orders, and optionally a subset of its tools. The child gets its own ID on the register under the parent's, its own run under the parent's run, and never more tools than the parent has. The parent gets the answer with wait_for_agent, during which it is paused at no cost. If the child's action was parked for a person, the parent is told which.
The family is bounded. A run's budget covers everything it delegates to, the account caps how deep delegation may go and how many sub-agents one job may have under it in all (20 by default; PUT /os/policy changes it), and suspending or killing the parent stops the whole subtree. A refusal tells the agent why, and it does the task itself.
Runs and budgets
First create the agent, which is the durable identity, then a run each time it starts a job.
curl -X POST https://api.sheaf.one/api/v1/os/agents \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"name":"accounts-payable","description":"pays supplier invoices"}'
curl -X POST https://api.sheaf.one/api/v1/os/runs \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"agentName":"accounts-payable","label":"March invoices","onBehalfOf":"finance@acme.com",
"budget":{"calls":50,"usd":5.0,"deadlineSeconds":3600}}'
A budget is a ceiling on gated calls, dollars, tokens, and wall-clock time. It applies to the run and everything the run delegates to, so a sub-agent cannot spend around its parent. Give a run a parentRunId and you get a delegation chain that Sheaf enforces end to end: suspend the parent and the whole subtree stops.
Before each gated call the agent asks for admission:
curl -X POST https://api.sheaf.one/api/v1/os/runs/$RUN/admit \
-H "Authorization: Bearer $KEY" -d '{"calls":1}'
A refusal is a 423 with a machine-readable reason: budget_exceeded, run_suspended, run_killed, deadline_passed, or an ancestor's version of any of those. Pass "dryRun":true to ask whether it would be admitted without spending anything.
Killing a run is the lever that matters. You do not need control of the agent's process. Revoke its execution boundary and it can no longer do anything consequential, whatever it thinks it is doing.
Suspend, resume, kill and done are all one call: POST /os/runs/{id}/suspend|resume|kill|done. All of them cascade to descendants. GET /os/runs/{id}/held says what a run is waiting on, if anything.
What happens when a machine dies
Work Sheaf runs for you (a hosted agent, or an async /pod/run that returned 202 and a jobId) is a durable row with a lease rather than a process. Every step of a hosted run is written down before the next one starts.
If the machine holding your work disappears mid-flight, its lease lapses and another machine picks the work up. A hosted run continues from its last completed step rather than starting over. A rolling deploy does not lose in-flight work at all: the draining machine finishes what it is holding.
Where a retry would be unsafe, Sheaf does not retry. If work may or may not have taken effect and the target has not promised idempotency, it is marked ambiguous and parked for a person, carrying what was attempted. Exactly-once across a network boundary does not exist; what you get is no lost work, and no second payment to save a round trip.
The gate
A gated action goes through three stages, and stops at the first one that settles it.
- Admission. Is this run still allowed to act? A suspended, killed, expired or over-budget run is refused here, and so is one whose parent is.
- Rules. Your ratified rules run as pure logic. No model is called. If a rule blocks the action, that is the answer, and it is the cheap and deterministic path you want most traffic to take.
- The panel. When the rules do not settle it, several models assess the action independently and Sheaf measures whether their reasoning holds together. Coherent agreement above your threshold executes. Below it, the action is held.
The panel is the part people misread. Sheaf is not asking a model for permission. It is measuring whether independent assessments of the same action are mutually consistent, and treating incoherence as a reason to stop. One model asked twice will agree with itself; that tells you nothing.
When Sheaf cannot decide
A check that could not complete does not count as passed. Two cases have caught people out, so they are described here.
The panel could not be measured. If no mind could be witnessed (a provider outage, an exhausted key), the coherence result carries measured: false, a coverage like 0/3 minds, and a score of 0, not 100. Any caller that asked for gating (holdOnObstruction, or a holdBelowCoherence threshold) gets a hold reading coherence could not be measured. A caller that asked for no gating is unaffected beyond seeing the score. An unmeasurable panel is not a coherent one.
The claim bus could not be read. When you pass claimsFromRun and that read fails, the result is marked claimsSource: "bus-unavailable" and the call fails closed: /pod/measure parks the action, /os/gate declines before admission is charged, and /pod/logic-gate answers claims_unavailable rather than approved. Judging against a claim set you know may be missing the contradicting claim is worse than refusing.
Irreversible actions
The reversible_first rule says an irreversible action must be dry-run before it is really run. Sheaf remembers a successful dry run server-side, keyed by the exact arguments, so the real call cannot claim to have been dry-run when it was not. A caller that asserts it anyway is ignored.
Rules
A rule is a small assertion over the action's arguments, with a severity. This one caps a payment:
{"id":"cap","description":"payments over 50k need a human",
"assert":{"op":"le","field":"amount","value":50000},"severity":"block"}
Rules live in a ruleset with a stable id and immutable numbered versions. A new version starts as a draft; the gate enforces the highest ratified version. Ratifying is deliberately a separate, human act, and Sheaf refuses to ratify a ruleset that contradicts itself, which it checks with an SMT solver rather than by asking a model.
POST /api/v1/pod/rulesets {"name":"payments","rules":[…]} → draft v1
POST /api/v1/pod/rulesets/{id}/ratify {"version":1} → enforced
POST /api/v1/pod/rulesets/validate {"rules":[…]} → consistent?
POST /api/v1/pod/rulesets/evaluate {"rules":[…],"data":{…}} → allow / violations
You can also write a policy in plain English and have a model draft the rules, at sheaf.one/rules.html. The draft still has to validate and still has to be ratified by a person. The model proposes; it does not decide.
Operators are exact. Comparisons are gt ge lt le eq ne and their symbol forms. Named operators are present missing is_true is_false between in not_in matches starts_with ends_with contains before after reversible_first. An operator Sheaf does not implement is rejected when you author the rule, because such a rule could never be satisfied and would block every call.
Held actions
A hold is not a failure. It is the system declining to guess.
When an action is held, Sheaf stores it with the exact arguments and the reason, and returns an action id. Your agent must stop and not retry. A person then releases or rejects it:
GET /api/v1/pod/actions → everything awaiting a human
POST /api/v1/pod/action/{id}/release {"by":"you"}
POST /api/v1/pod/action/{id}/reject {"by":"you"}
Releasing does not re-decide anything. It authorises exactly the arguments that were recorded, and the agent then executes that call and nothing else. Who released it, and when, goes in the ledger.
Where a hold is enforced depends on where the agent runs. In Sheaf's own loop, the call stops at /os/tools/call. Inside an OpenSheaf sandbox, it stops at the check in the runtime and never leaves the room. Either way it is the same queue, the same reason and the same record.
The ledger
Every decision writes one row: measurements, gate verdicts, holds, releases, rejections, admission refusals, run transitions. Each row carries the hash of the row before it, so altering or removing any one of them breaks every hash after it.
GET /api/v1/os/ledger?limit=50 → recent rows, newest first
GET /api/v1/os/ledger/stats → counts by kind and decision, plus the head
GET /api/v1/os/ledger/verify → recompute the chain, name the first break
GET /api/v1/os/ledger/{seq} → one row, with its content
Rows keep what is needed to replay the decision, so you can re-run a stored decision through today's panel and see whether it still comes out the same way:
POST /api/v1/os/ledger/{seq}/replay
Retention is per gate. none records the decision with no content and cannot be replayed. standard keeps content for 400 days. regulatory keeps it. A purged row keeps its hashes, so the chain still verifies by linkage after its content is gone.
The evidence ledger: checkpoints
The chain shows whether a row was altered. A checkpoint shows who vouched for the chain, and when. Each hour, for every account whose ledger has moved, Sheaf writes a checkpoint of the newest head. A checkpoint has up to five layers, and each one records which it carries:
- Statement. A canonical JSON statement (sorted keys, no whitespace) naming the account, the head seq and its hash, the rows covered, and the previous checkpoint's hash, so checkpoints chain as well.
- Signature. ECDSA P-256 over the statement by a key held in AWS KMS. The private key never leaves the HSM, so database access alone cannot forge a checkpoint. The public key is served at
GET /api/v1/os/ledger/public-key. Every checkpoint records the key id and algorithm; rotation is manual and older checkpoints keep the id of the key that signed them. - Trusted timestamp. An RFC 3161 token over the signature from a public timestamp authority (DigiCert, Sectigo or GlobalSign by default; the list is configurable, including EU qualified providers). A supervised third party attests that the signature existed at that time.
- Immutable copy. The checkpoint bundle and the rows it covers, written to an S3 bucket with Object Lock retention, so the record itself is kept and produced on request, with its audit trail.
- Public anchor (optional). The head hash timestamped into a public blockchain through OpenTimestamps. Only that 32-byte fingerprint leaves Sheaf; no decision content, no account data. It adds evidence that stands even if every party above were compromised. It reads pending for a few hours until the next block.
| Profile | Layers |
|---|---|
| Standard | Chain + signed checkpoints + immutable copy |
| Regulated | Standard + RFC 3161 timestamp (or an EU qualified timestamp) |
| Maximum assurance | Regulated + public anchor; account-held signing key and account-owned bucket on request |
GET /api/v1/os/ledger/checkpoints → checkpoints, newest first, with this deployment's profile
GET /api/v1/os/ledger/checkpoints/{seq} → one checkpoint
GET /api/v1/os/ledger/checkpoints/{seq}.statement.json .sig .tsr .ots .bundle.json
POST /api/v1/os/ledger/checkpoints/now → checkpoint the current head now
GET /api/v1/os/ledger/public-key → the signing key, PEM
GET /api/v1/os/ledger/export?from=&to= → rows with content and hashes
Sheaf signing key
The key that signs checkpoints on sheaf.one, published here so a verifier does not have to take it from the same API that serves the checkpoint. Key id c32fe63f-396a-4fb8-9dee-bbab8a2181ae, ECDSA P-256, AWS KMS us-east-2. Every checkpoint records the id of the key that signed it.
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHiDvtbGgxag2WalDXT4TwXTiaTXw
s/2G/g63p83713Q+pyL9A845+SIHz0Hz8ghnloafg8tXNzC3ecESTr5EZg==
-----END PUBLIC KEY-----
Verifying without Sheaf. Download the statement, the signature and the timestamp response, save the key above as sheaf-pubkey.pem, then:
openssl dgst -sha256 -verify sheaf-pubkey.pem -signature checkpoint-N.sig checkpoint-N.statement.json
openssl ts -verify -in checkpoint-N.tsr -digest $(openssl dgst -sha256 -r checkpoint-N.sig | cut -c1-64) -CApath /etc/ssl/certs
ots verify -d <headHash> checkpoint-N.ots # only if the anchor layer is on
Then recompute the chain from the export: rowHash is the SHA-256 of the JSON array [customerId, seq, at, kind, decision, subject, refId, reason, runId, actor, inputRaw, resultRaw, retention, prevHash], and the headHash in the statement must equal the rowHash at that seq. The four checks together say: these rows, in this order, were vouched for by this key, at this time, and have not changed since.
Tools
Attach any MCP server and Sheaf asks it what it can do. The protocol describes what a tool is called and what arguments it takes, and says nothing about whether calling it matters: read_file and delete_file arrive identically. Sheaf carries that judgement itself, one setting per tool.
| Setting | What happens when an agent calls it |
|---|---|
pass | It runs, unattended. Right for anything read-only. |
gate | A council of models re-decides the call independently of the agent that wants it. A clear approve runs it. Anything else parks it, with the panel's reasoning and its coherence score attached. |
hold | Always parks for a person, whatever anyone thinks. |
Newly discovered tools start at hold, because that is the setting you can always walk back from. Re-listing a server never downgrades a decision someone already made. The verdict decides and the score is reported rather than the other way round: a panel built to disagree scores a legitimate approve low, so holding on the score alone would park clean approvals and make the gate a wall.
Hosted agents also have tools Sheaf provides without attaching anything.
| Tool | What it does |
|---|---|
memory_read, memory_write | Remember things across runs, in the agent's own namespace. |
web_search, read_page | Look something up on the live web; read a public page as text, in parts. |
list_files, read_file | Read files a person uploaded in the console: PDF, CSV, text, Markdown, JSON (POST /os/files). |
add_row, find_rows | Keep records in named tables a person can see and download (GET /os/records). |
send_email | Consequential: the first message to any address parks for a human. Releasing it both sends it and approves that address for later. |
propose_action | Put a consequential act forward. It never executes; it parks for a person and the run stops there. |
spawn_agent, wait_for_agent | Start a sub-agent and wait for its answer. |
check_agent, prove_identity | Look another agent up on the register before dealing with it; prove this agent's own ID when asked. |
To attach a vendor's tools, search the catalog (GET /os/catalog?q=…, a copy of the public MCP registry) and connect in one step (POST /os/tool-servers/connect-start). If the server is open it is connected at once; if it wants a sign-in, the person is sent to the vendor's page and comes back connected; if it wants a key, the console asks for one. Sheaf keeps the sign-in and renews it. An agent that needs a system you have not connected says so, and the request appears under Tools until someone connects it.
Schedules
A hosted agent can run on its own each day: POST /os/schedules with an agent, a task, a local time and a time zone. The schedule opens a run and queues the work, so everything else in this manual applies unchanged: budget, gate, ledger.
The granularity is a day rather than a minute. If the exact minute is missed because a machine was replaced, it still fires later that day rather than being skipped, which is what you want from something that has to happen daily.
Memory
Agents remember in namespaces. By default an agent's namespace is its own id, so one agent cannot read another's memory. Sharing is an explicit grant, and every row records which agent wrote it and when.
PUT /api/v1/os/memory {"key":"vendor-terms","content":"…","asAgentId":"…"}
GET /api/v1/os/memory?key=…&asAgentId=…
POST /api/v1/os/memory/grants {"namespace":"…","granteeAgentId":"…","mode":"read"}
Contents are encrypted at rest under a key ring, so keys can be rotated without losing what was written under the old one.
Drift
Models change underneath you. The vendor ships a new version, a name maps somewhere else, and the reasoning behind decisions you already made is no longer reproducible. Nothing in a normal stack notices, because nothing kept the decision's inputs.
Sheaf replays a sample of stored decisions through the current panel and records how many still come out the same, against a fingerprint of the models that answered:
POST /api/v1/os/replay/batch {"sample":5}
GET /api/v1/os/compliance → the snapshots, newest first
GET /api/v1/os/alerts → drift and ledger alerts
A snapshot is a compliance rate plus a model fingerprint. When the fingerprint changes and the rate falls materially, Sheaf writes a drift alert and emails it. That is the sentence you want: the models answering these decisions are not the models that decided them.
A daily job samples a few decisions per account and also verifies your chain, so you find out without asking. Replays call real models, which costs money, so they are sampled and cost-capped like any other call.
Policy bundles
A governed setup is a ruleset, the panel that enforces it, and the proxy config that points an agent at it. A bundle is all of that as one signed JSON document, so a policy someone vetted can be installed somewhere else and still be provably the policy they vetted.
POST /api/v1/os/bundles/export {"name":"payments-policy",
"rulesetIds":["…"],"podIds":["…"],
"gate":{"rulesetRef":"r1","podRef":"p1"},
"proxy":{"gatedTools":["pay_invoice"],"certTtlSeconds":300}}
GET /api/v1/os/bundles → your registry
POST /api/v1/os/bundles/verify {"bundle":{…}}
POST /api/v1/os/bundles/install {"bundleId":"…","dryRun":true}
Three things are worth knowing about how this behaves.
- Export never carries a secret. Per-member provider keys are dropped and the whole document is scrubbed before it is hashed. A signed, portable, stored file is the worst possible place for a key.
- Ids are rewritten on install. A bundle refers to its own contents as
r1,p1, because the exporting account's ids mean nothing on yours. Install creates new rulesets and gates, then hands you a proxy config with the new ids already filled in, ready to save. - Verify tells you which thing is wrong. Altered content, a stale signature, and a bundle signed by a Sheaf whose key this one does not have are three different answers.
Installed rulesets arrive ratified, because the signature is the vetting, and the record names both the person who installed it and the bundle they installed. Use dryRun first: it reports exactly what would be created and writes nothing.
The console
Everything above has a screen at sheaf.one/console.html. It opens on Agents, where you describe a job and Sheaf writes the agent, and where each hosted agent can be given a task, put on a daily schedule, shown as a program, or given the one command that runs it inside OpenSheaf on your own machine. Each agent shows its ID on the register. Then: live runs with their budgets, delegation chains and where each ran, a transcript of every step a hosted run took, the tools you have attached with what each is allowed to do and the catalog to find more, the files and tables agents work with, actions waiting on a human with release and reject, the ledger with a verify button and its checkpoints, compliance history with replay, alerts, and the bundle registry.
It is a view onto the same API, so nothing happens there that you could not do from a script, and nothing you do from a script is invisible there.
Limits and costs
Rules cost nothing: they are pure logic, evaluated locally. Panels cost model calls, which is why the design pushes as much traffic as possible into rules and reserves the panel for the actions where judgment is required.
| Control | What it does |
|---|---|
| Run budget | Caps calls, dollars, tokens and time for a run and everything it delegates to. |
| Monthly cap | Per account, on AI spend rather than revenue. Free accounts get $2, about a dozen full checks. A call that would exceed it is refused with 402. An account with a card on file (pay per check, 35 cents a full check) has no cap: each check is billed at the end of the month. Unlimited otherwise exists but is granted deliberately: an account with no cap set falls back to the free one rather than to no limit. |
| Global capacity | Protects the shared engine. Returns 503, briefly, under load. |
| Tenant concurrency | Caps how many of your runs are active at once, so one job cannot crowd out the rest. |
Anonymous, cost-bearing endpoints are also rate limited per IP address. If you are testing in a script, pace it, or you will lock out everyone sharing your address, including your own browser.
Call reference
Everything under /api/v1/os/ takes an agent key and returns a Sheaf-OS-Version header.
| Call | What it is for |
|---|---|
GET /os/version | Contract version, slices, build. No key needed. |
POST /os/agents/draft | Describe a job in words; get an agent definition back to read and edit. |
POST /os/agents | Create an agent. targetKind: "hosted" with a definition means Sheaf runs it. |
GET /os/agents/{id}/code, POST /os/agents/code | The agent as a runnable program, rendered from its definition; or render a definition before any agent exists. |
POST /os/agents/{id}/retire|revoke, DELETE /os/agents/{id} | Its work is done; or withdraw it for cause and kill its runs; or remove it and everything about it. |
POST /os/runs | Start a run with a budget and a parent. |
GET /os/runs, GET /os/runs/{id} | List runs; one run with its chain, children and holds. |
POST /os/runs/{id}/suspend|resume|kill|done | Change what the run is allowed to do. Cascades. |
POST /os/runs/{id}/admit | Ask before a gated call. dryRun to ask for free. |
POST /os/runs/{id}/start | Start a hosted agent on a task. Durable: resumed, not replayed, if a machine dies. |
POST /os/runs/{id}/think | One step of thinking for a program that runs elsewhere: Sheaf calls the agent's model and returns the proposed move. |
GET /os/runs/{id}/held | What the run is waiting on: the parked action, its reason and id. |
GET /os/work | Execution state: the steps a hosted run took, its attempts and leases. |
POST /os/gate | Gate any action over plain HTTP. |
POST /os/tools/call | Run one tool under this account's rules. What an exported agent calls, so it stays governed. |
POST /os/tools/decide | The decision alone, nothing run. What the OpenSheaf runtime asks before a call leaves the sandbox. |
GET /os/catalog, POST /os/tool-servers/connect-start | Find a tool server in the public catalog and connect it in one step, with a vendor sign-in where one is needed. |
GET /os/tool-requests | Systems agents asked for that nobody has connected yet. |
POST/GET/DELETE /os/files, GET/DELETE /os/records | Files uploaded for agents to read; the tables agents keep. |
POST /os/tool-servers, GET, DELETE /{id} | Attach an MCP server and see what it offers. Everything starts at hold. |
POST /os/tool-servers/{id}/tools/{name} | Set a tool to pass, gate or hold. |
POST /os/schedules, GET, DELETE /{id} | Run a hosted agent daily at a local time. |
GET /os/email-recipients | Addresses a person has approved for agent email. |
GET /os/ledger… | Rows, stats, verify, one row, replay, export, checkpoints, public key. |
PUT/GET/DELETE /os/memory | Agent memory and its grants. |
POST /os/replay/batch | Measure the compliance rate over a window. |
GET /os/compliance, GET /os/alerts | Snapshots; drift and ledger alerts. |
POST /os/bundles/export|verify|install | Package, check and install a policy. |
GET /os/policy, PUT /os/policy | Account-level limits: concurrent runs, delegation depth and family size, counterparty policy. |
GET /os/register, POST /os/register/verify/domain|company | This account's entry on the public register; prove ownership of a domain or a company. |
POST /os/register/prove, POST /os/register/check | The handshake from the owner's side: sign a counterparty's challenge; look a counterparty up and decide. |
POST/GET /os/claims | The typed claim bus a run's agents write to and the gate reads from. |
POST/GET/PATCH/DELETE /os/escalations | Where a held action is announced: email or webhook routes by domain and severity. |
The public register has its own calls under /api/v1/register/, which need no key: lookup, search, an agent's record and verify, owners, checkpoints and their proofs. The panel endpoint, /api/v1/pod/run, puts one question to several models and measures the coherence of their answers; an agent reaches it through the ask_panel tool. Rules authoring lives at sheaf.one/rules.html.