Saxeo Agents
Saxeo Agents is an agent host where the agent cannot lie about what it did.
You deploy a piece of code once; Saxeo runs it on a schedule or when you
trigger it. Every run executes under the agent's own bounded API key, is
metered, receipted, action-attested, and chained, and the receipt's content
fingerprint equals the deploy-time code_fp, proving the deployed code is
exactly what ran.
The agent gets SAXEO_API_KEY (its own bounded key) and SAXEO_API_BASE
injected into its environment, so it can call Saxeo inference and
sandboxes as itself, within its budget, policy, and circuit
breaker. Its spend, its receipts, and its run chain are all attributable to
that one key.
Deploy an agent
POST /v1/agents (session-authed) stores the code and schedules it.
curl https://www.saxeonetwork.tech/__api/v1/agents \
-H "Authorization: Bearer $SAXEO_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "price-watcher",
"language": "python",
"code": "import os, httpx\nprint(os.environ[\"SAXEO_API_BASE\"])",
"interval_secs": 3600,
"spend_limit_usd": 5,
"spend_window": "day",
"circuit_breaker_usd": 2
}'{
"id": "agt_9b41…",
"name": "price-watcher",
"language": "python",
"interval_secs": 3600,
"enabled": true,
"code_fp": "f2a91c04…",
"api_key_id": "key_5d…",
"run_chain": "agent:agt_9b41…",
"note": "Code and env are stored sealed and destroyed on deletion. Scheduled-run output is discarded; receipts are the durable record."
}
language:python(default),node, orbash.interval_secs: at least 60; at most 30 days. Omit it for a trigger-only agent that runs only when you ask.timeout_secs: per-run ceiling, up to 14400 (4 hours).env: an object of environment variables, sealed at rest alongside the code.network: off by default; the run cannot reach the internet without it.spend_limit_usd+spend_window,policy_id,circuit_breaker_usd: the bounds on the agent's key (see bounds below).vcpu,mem_mb,image: the sandbox resources the run gets, priced like any sandbox run.
code_fp is the fingerprint that ties everything together: every run receipt
carries the content fingerprint of the code that ran, and for a hosted agent
that fingerprint equals code_fp. Anyone holding a receipt can check that the
deployed code, not something swapped in later, produced it.
Trigger a run
POST /v1/agents/{id}/trigger runs the agent now and returns the full sandbox
response, including output:
curl -X POST https://www.saxeonetwork.tech/__api/v1/agents/$AGENT_ID/trigger \
-H "Authorization: Bearer $SAXEO_SESSION_TOKEN"{
"status": "succeeded",
"exit_code": 0,
"stdout": "https://www.saxeonetwork.tech/__api/v1\n",
"stderr": "",
"cost_micro_usd": 231,
"receipt": {
"receipt": "eyJ2Ijoz…",
"signature": "0x4f8c…",
"signer": "0xA1b2…9F"
}
}
The output comes back exactly once and is never stored. Scheduled runs discard their output entirely (see the §3 amendment below): if you need to see what an agent prints, trigger it manually; if you need durable evidence of what it did, that is what the receipts are for.
Triggers
Besides a schedule and the manual trigger, an agent can be started by an event. Both event triggers queue a run rather than running inline, so the caller is never left holding a connection open for a four-hour job.
Inbound webhook
Deploy with "webhook_trigger": true (or mint one later with
POST /v1/agents/{id}/hooks/rotate) and you get a trigger URL:
https://www.saxeonetwork.tech/__api/v1/agents/agt_9f3c…/hooks/8Kd2…
The URL is shown exactly once. Only a sha256 of the token is stored, so it cannot be shown again — rotate to mint a new one, which invalidates the previous URL immediately. Treat it as a credential.
A POST to it queues one run and returns 202 straight away:
curl -X POST "$HOOK_URL" \
-H 'content-type: application/json' \
-H 'Idempotency-Key: order-4471' \
-d '{"order_id": 4471, "total_usd": 89.00}'
{
"run_id": "arun_2c81…",
"agent_id": "agt_9f3c…",
"status": "queued",
"deduplicated": false
}
The raw request body reaches the run as the environment variable
SAXEO_INPUT, and SAXEO_TRIGGER is set to webhook:
import json, os
payload = json.loads(os.environ.get("SAXEO_INPUT", "{}"))
print(payload["order_id"])
- Body limit 64 KiB, and it must be UTF-8 text.
- Idempotency. An
Idempotency-Keyheader dedupes for 24 hours: the same key twice queues one run, and the second response names the first run with"deduplicated": true. Only a sha256 of the key is stored. - Queue depth. Each agent has a bounded queue (100 by default). A POST past
it gets
429. - Unknown agent or wrong token both return
404, deliberately identically, so probing tells you nothing.
Signing inbound requests
Set a secret (at deploy with hook_secret, or later via
POST /v1/agents/{id}/hooks/secret) and every inbound POST must carry an
HMAC-SHA256 of the raw body in X-Saxeo-Signature, or it is rejected 401:
X-Saxeo-Signature: sha256=<hex hmac of the raw body>
import hashlib, hmac
sig = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
headers = {"X-Saxeo-Signature": f"sha256={sig}"}
The comparison is constant-time and the secret is sealed at rest and never returned. With no secret configured, unsigned POSTs are accepted — which is fine when the token is the only credential you are relying on, and not fine when the sender is a third party. Set one.
Mailbox messages
Set trigger_on_message: true and a message landing in the agent's
mailbox queues a run with SAXEO_TRIGGER=message. The
message body is not passed as input: the agent reads its own mailbox with
its injected key, which keeps one code path for reading messages whether the
run was triggered by a message or by anything else.
At most one queued message-run exists per agent at a time. A burst of ten messages produces one run that drains the mailbox, not ten runs racing over the same inbox.
The hourly breaker
Every agent has max_runs_per_hour (default 60, max 3600), which bounds
event-triggered runs — the schedule bounds itself, and a manual trigger is
a human. Past it, the trigger gets 429 and no run is queued. It is the
runaway fuse for a webhook that starts firing in a loop, complementing the
budget and circuit breaker on the agent's key.
Run history
Every run — scheduled, manual, webhook, or mailbox — is a row you can read:
curl https://www.saxeonetwork.tech/__api/v1/agents/agt_9f3c…/runs \
-H "authorization: Bearer $SAXEO_SESSION"
{
"agent_id": "agt_9f3c…",
"runs": [
{
"id": "arun_2c81…",
"trigger": "webhook",
"status": "succeeded",
"input_bytes": 44,
"input_fp": "9c1f3a4b7d2e5061",
"input_content_type": "application/json",
"request_id": "sbx_71a…",
"cost_micro_usd": 231,
"created_at": "2026-09-04T09:12:03Z",
"finished_at": "2026-09-04T09:12:09Z",
"receipt": { "receipt": "eyJ2Ijoz…", "signature": "0x4f8c…", "signer": "0xA1b2…9F" }
}
]
}
Statuses are queued → running → one of succeeded / failed / timeout, plus
skipped for a run whose agent was disabled or deleted before it was claimed.
The record is metadata only: input_bytes and input_fp describe the trigger
payload without containing it, and output was never stored at all.
Endpoints
All session-authed (Authorization: Bearer sess_…) except the trigger URL,
which authenticates on its own token.
| Method | Path | What it does |
|---|---|---|
| POST | Path/v1/agents | What it doesDeploy an agent: seal the code, mint its bounded key, schedule it. |
| GET | Path/v1/agents | What it doesList your agents with schedule, last run, and run count. |
| POST | Path/v1/agents/{id}/trigger | What it doesRun now; returns the full sandbox response once. |
| POST | Path/v1/agents/{id}/gate | What it doesEnable or disable. Body {"enabled": bool}. |
| POST | Path/v1/agents/{id}/triggers | What it doesSet trigger_on_message / max_runs_per_hour. |
| POST | Path/v1/agents/{id}/hooks/rotate | What it doesMint or replace the inbound trigger URL (shown once). |
| POST | Path/v1/agents/{id}/hooks/disable | What it doesDrop the inbound trigger URL. |
| POST | Path/v1/agents/{id}/hooks/secret | What it doesSet or clear the HMAC signing secret. |
| GET | Path/v1/agents/{id}/runs | What it doesThe last 100 runs: metadata plus each signed receipt. |
| GET | Path/v1/agents/{id}/runs/{run_id} | What it doesOne run. |
| DELETE | Path/v1/agents/{id} | What it doesRevoke the agent's key and destroy its sealed code. |
| POST | Path/v1/agents/{id}/hooks/{token} | What it doesThe trigger URL. Token-authed; queues a run, returns 202. |
The bounds
A hosted agent never spends on your root key. Deploying it mints a dedicated key with exactly the bounds you set, enforced on every call the agent makes:
- Budget:
spend_limit_usdover aspend_window(day/week/month/total), the same refreshing budget as any scoped key. A daily budget renews; atotalbudget is a lifetime fuse. - Policy: an optional
policy_idbinds the key to an existing policy. - Circuit breaker:
circuit_breaker_usdfreezes the key when rolling spend velocity exceeds the cap, the same runaway-agent kill switch available on any key.
Disable an agent (gate) and its schedule stops; delete it and its key is
revoked and its sealed code destroyed.
The proof story
Three artifacts make an agent's history checkable by someone who does not trust you, or Saxeo:
- Receipts. Every run mints a signed, metadata-only
receipt, verifiable through the public
POST /v1/receipts/verify. - The code fingerprint. Each run receipt's content fingerprint equals the
deploy-time
code_fp, so a receipt proves which code produced it. - The run chain. Every run chains into the agent's own
run chain at
agent:{id}: a per-agent hash chain you can list atGET /v1/runs/agent:{id}, mint a signed run proof from, anchor, and export as a compliance audit pack.
Together: this code, under this key, ran these times, cost this much, and nothing was inserted or removed from the record.
The §3 amendment
Saxeo's privacy contract says submitted code is never persisted. Hosted agents are the one deliberate, disclosed exception: a scheduler cannot re-run what it does not hold. Plainly stated:
- Hosted-agent code and env are stored, AES-GCM sealed (ciphertext at rest), opened only at the moment of execution, never logged, and destroyed when the agent is deleted.
- Scheduled-run output is discarded. stdout and stderr from a scheduled run are returned to no one and stored nowhere. A manual trigger returns the output exactly once. The signed receipts are the durable record.
- A webhook trigger payload persists only while its run is queued. It is
sealed the moment it arrives, opened in-frame to become
SAXEO_INPUT, and set to null at the instant the run reaches a terminal state — or swept if the run never executes. What survives is its byte length and a sha256 prefix. Mailbox triggers pass no payload at all.
Nothing else about §3 changes: prompts, completions, and one-off sandbox code remain never-persisted, and no receipt or usage row ever contains content.
Scope and limits
Honest scope: Saxeo Agents runs agents on a schedule, on a manual
trigger, on an inbound webhook, and on a mailbox message. It is
still not an always-on host. An agent is not a resident process: every trigger
starts a bounded run that executes and exits, and max_runs_per_hour is a
budget on how often that can happen — not a concurrency dial for a daemon. If
you need a long-lived listener, run it yourself and call Saxeo from it.
- Up to 20 agents per account.
- Trigger payload: at most 64 KiB, UTF-8 text, delivered as
SAXEO_INPUT. - Event-triggered runs:
max_runs_per_hour(default 60) and a bounded per-agent queue (100 by default). - Schedule interval: at least 60 seconds, at most 30 days. Omit for trigger-only.
- Per-run timeout: up to 4 hours (
timeout_secs≤ 14400). - Runs are priced like sandbox runs: vCPU-seconds plus GB-seconds of memory, plus whatever the agent itself spends calling Saxeo under its own key.