================================================================================ ATP -- llms.txt (llms.txt) ================================================================================ # ATP (Agent Transcript Protocol) > Lets AI agents run tasks as terse, provenance-carrying shorthand instead > of prose, and hand off the facts they establish to other agents so > nothing gets re-fetched or re-derived. Reachable over MCP, REST, CLI, > A2A, or a JS/TS client -- pick whichever fits the agent calling in. If you are an LLM or agent reading this to decide how to integrate: the fastest path is MCP if your host supports it (zero HTTP code needed), otherwise REST (plain JSON over HTTP, any language). Both are described below with a working example. No task requires understanding the ATL grammar to get a first result -- `kind: "constant"` needs no external data. ATP is a hosted service (like an LLM provider's API), not something you clone and run yourself -- the implementation is proprietary; only the wire protocol (spec/, CC BY 4.0) is open and freely implementable. Two ways in, no local setup either way: an issued Bearer key (subscription billed -- see spec/protocol.md), or **x402: pay per call with no signup at all** (see below) -- prefer x402 if you have a wallet and no existing relationship with the operator. **Note if you're reading this from the source repository rather than a live deployment**: there is no public hosted endpoint yet. The examples below assume one exists; `README.md`'s "Operating your own instance" section covers running the code locally to develop against instead. ## Quickstart (against a live deployment) Get a key from the operator (out-of-band, e.g. a signup flow -- there is no self-serve key-issuance API), then: ``` curl -X POST https:///v1/tasks/run \ -H "Authorization: Bearer atp_..." -H "Content-Type: application/json" \ -d '{"agent":"scout","task_id":"t1","fn_name":"value_it","arg_names":["company"],"args":["Acme Corp"],"kind":"valuation"}' ``` Or skip the key entirely via x402 (see that section below). ## MCP (preferred if your host supports it) - [mcp_server.py](atp/mcp_server.py): 10 tools -- atp_run_task, atp_export_transcript, atp_import_transcript, atp_claim_task, atp_complete_task, atp_merge_transcript, atp_pool_transcript, atp_savings, atp_verify_transcript, atp_usage. Start with `atp serve-mcp` (stdio) and add to your client's MCP server config; every tool takes `api_key`. ## REST API - [server.py](atp/server.py): `atp serve-api --port 8000`, OpenAPI docs at `/docs`. `POST /v1/tasks/run` with `Authorization: Bearer ` is the core operation -- see [protocol.md](spec/protocol.md) for the full operation table. - Example: `curl -X POST http://127.0.0.1:8000/v1/tasks/run -H "Authorization: Bearer atp_..." -H "Content-Type: application/json" -d '{"agent":"scout","task_id":"t1","fn_name":"answer_it","kind":"constant"}'` ## A2A (Agent2Agent) - [a2a_compat.py](atp/a2a_compat.py): Agent Card at `/.well-known/agent-card.json`, JSON-RPC `message/send` at `/a2a`. Synchronous only, no streaming yet. ## x402 (no API key, no signup, ever) - [x402_compat.py](atp/x402_compat.py): `POST /x402/tasks/run` -- if this route returns something other than 404, this deployment accepts payment instead of an API key. Call it with no credential; you'll get `402 Payment Required` with a price and payment instructions. Pay (a signed USDC transfer, verified by a facilitator -- you don't need a blockchain node), retry the same request with proof, get served. Same request body shape as `POST /v1/tasks/run` above, minus the `Authorization` header. This is the [x402 protocol](https://github.com/x402-foundation/x402) -- if you're an agent with a wallet and no ATP account, this is your path in. ## JS/TS - [sdk/js](sdk/js/README.md): `AtpClient`, zero runtime dependencies, same operations as REST. ## Website - [site/index.html](site/index.html): a single standalone page explaining ATP in plain language -- open it directly in a browser or host it anywhere static. No build step, no server required. Everything that used to be separate connect/playground pages now lives on this one page: a "Run It" section (`POST /v1/tasks/run` against the live API, one real call, nothing pre-recorded) and a compact three-tab connect widget (x402 / MCP / REST, one copy-paste command each, hardcoded to the live deployment). If you're an agent that can render/click a page, this is the fastest way to get an exact, working command. ## Reference - [spec/protocol.md](spec/protocol.md): every operation's exact contract, auth model, execution limits, versioning policy. - [spec/atl-grammar.md](spec/atl-grammar.md): the shorthand language agents write when a task has no pre-existing function -- read this only if you're authoring novel task logic yourself rather than calling `atp_run_task` with a `kind` hint. - [spec/transcript.schema.json](spec/transcript.schema.json): the exact JSON shape of a Transcript (what gets handed between agents). - [README.md](README.md): full architecture, the "why shorthand" and "why agents don't repeat work" rationale, multi-tenant safety limits, pricing model reasoning. ## Full text - [llms-full.txt](llms-full.txt): every file above, concatenated, for a single-fetch context load. ================================================================================ ATP -- README (README.md) ================================================================================ # ATP -- Agent Transcript Protocol A hosted service where agents talk to each other the way a court stenographer writes: terse, line-numbered shorthand (ATL -- Agent Transcript Language) instead of prose. Positioned like an LLM provider, not like an open framework you clone and run yourself: the language and wire protocol (`spec/`) are open and freely implementable by anyone (CC BY 4.0), but the implementation that actually runs it -- the interpreter, the coordination engine, the pricing/billing logic -- is proprietary and reached only through the hosted API. See `spec/protocol.md` for the API contract and `LICENSE` for what's open vs. not. **A live deployment exists** at `https://atp-api-mor3.onrender.com` (confirm with `curl https://atp-api-mor3.onrender.com/health`) -- this section describes the actual external adoption path against that instance. It's on Render's paid Starter compute tier (no free-tier sleep-on-inactivity) backed by a real, paid-tier managed Postgres database (survives redeploys) -- not yet behind a custom domain itself (still the `onrender.com` subdomain). The frontend, separately, is live at its own custom domain: `https://useatp.com/` (also reachable at `https://atp-agent-communication.netlify.app/`, its underlying Netlify URL). You can still stand up your own instance instead (see "Operating your own instance" below), the same way you might run a local copy of a hosted service's code to develop against it. If you're an LLM/agent reading this to decide how to integrate, read [`llms.txt`](llms.txt) instead -- it's written for you specifically. ## Using ATP (external adopters) Once a hosted deployment exists, this is the actual adoption path -- get an API key (Bearer, subscription-billed) or pay per call with no signup at all (x402, USDC) and call the API directly. You never install or run the interpreter/engine yourself: - **JS/TS**: `npm install @atp/client` -- see [sdk/js/README.md](sdk/js/README.md). - **Any other language**: plain REST -- `POST /v1/tasks/run` with a Bearer key, or `POST /x402/tasks/run` with no key at all. See `spec/protocol.md` for the full contract. - **MCP-capable agent/IDE**: point your client at the hosted MCP endpoint (once published) the same way you'd add any other MCP server -- no separate integration code. ## Operating your own instance (this repo) This is the code that runs the hosted service -- proprietary, not the intended external distribution mechanism (see above), but this is how you develop, test, or self-operate it. No LLM API key needed to try it locally -- the offline `MockBackend` handles it. ```bash pip install -e .[all] atp keygen --owner "your-name" # prints a key, e.g. atp_abc123... atp run --key atp_abc123... --agent scout --fn answer_it --kind constant ``` That's a full round trip: an "LLM" (the offline mock, swap in `--backend anthropic` or `--backend openai` with a real key any time) writes a few lines of task logic, a sandboxed interpreter runs it, and you get a result back. From here: - **Standing up the hosted API/MCP server?** → `atp serve-api` / `atp serve-mcp` -- see "Connecting from any agent" below. - **Just exploring the mechanism?** → `python examples/run_demo.py` runs a full multi-agent scenario end to end, no setup at all. ## Why shorthand, specifically A stenographer doesn't write "the witness stated that they observed the defendant" -- they write a few strokes that expand back to that meaning unambiguously. ATL statements do the same job for agent reasoning: ``` X financials = load(company) // exhibit: a fetched external fact S discount_rate = 0.08 // stipulation: agreed, computed once, reused O:bad_data financials.conf == 0 // objection: flags a problem R financials = refetch(company) // ruling: resolves the objection D financials.revenue / (1 + discount_rate) // done: the result ``` That's five lines instead of a paragraph, and unlike prose it's actually executed -- every fetch, computation, and control-flow decision runs through a sandboxed interpreter (AST-whitelisted, no eval/exec surface), not trusted blindly as model output. Two things fall out of that for free: - **Cheaper & faster**: fewer tokens per exchange, and the actual arithmetic/control-flow runs deterministically in the interpreter, not token-by-token in the model. The model is only called to *author new logic* or *resolve a disagreement* -- never to re-derive a fact. - **Verifiable**: every value carries `conf` (confidence) and `source` (provenance), so "trust me" isn't the mechanism -- traceability is. ## Why agents don't repeat each other's work Every interpreter run produces a `Transcript`: everything it actually fetched or computed (not cache hits). Export it, hand it to another agent's interpreter, and that agent's registries come pre-populated -- it never re-fetches or re-verifies anything already established. This is the protocol's "cite, don't resend" principle, applied at the execution layer. A file-backed `CoordinationLog` extends the same idea across N agents (processes, potentially machines): claiming a task_id is atomic, so overlapping work assignments never cause duplicate fetches. `examples/run_demo.py` proves it -- 3 agents are each handed the same 5 tasks, only 5 of the 15 attempts actually run, and a downstream "writer" agent builds the final report from the merged transcript with **zero fetches of its own**. ## Layout ``` atp/ interpreter.py ATL parser + sandboxed interpreter + Transcript/Value (the reference pipeline.atl toolkit, ported). Enforces a wall-clock execution budget AND an iteration cap on every loop, shared across nested/recursive calls. coordination.py CoordinationLog + merge_transcript_file (local disk), plus the pluggable CoordinationBackend abstraction: FileCoordinationBackend (default) and RedisCoordinationBackend (pools shared across machines) identity.py Ed25519 signing/verification for Transcript facts -- attribution + tamper-evidence, not a trust system llm_backend.py LLMBackend interface: MockBackend (offline, deterministic), AnthropicBackend and OpenAIBackend (real calls, exact token usage, retry + circuit breaker via resilience.py) resilience.py Retry-with-backoff + circuit breaker for real LLM calls agent.py Agent: runs pre-authored ATL for free, or asks its backend to author+repair novel ATL, bounded retries runner.py Pipeline: N-worker task pool with dedup + a downstream stage that inherits the merged transcript metrics.py ATL-wire-size vs. prose-equivalent token comparison db.py Shared SQLAlchemy engine factory -- sqlite by default, Postgres in production, same code either way auth.py API key issuance/verification, hashed at rest, roles + an append-only audit log (Postgres-capable via db.py) metering.py Per-key usage ledger -- billing's source of truth, does not move money itself (Postgres-capable via db.py) ratelimit.py Per-key token-bucket rate limiting observability.py Prometheus metrics, instrumented once in Engine so every transport contributes, not just REST engine.py The one core every transport wraps -- scopes agents per API key, hosts coordination pools, enforces a per-request timeout + rate limit server.py REST API (FastAPI) -- language-agnostic, any HTTP client. Also serves /metrics, /ready, /llms.txt mcp_server.py MCP server -- any MCP-compatible agent connects directly a2a_compat.py A2A (Agent2Agent) compatibility -- Agent Card + JSON-RPC message/send, so A2A-aware agents can call ATP too cli.py `atp` command -- keygen, keys, revoke, audit, run, usage, serve-api, serve-mcp spec/ atl-grammar.md language-agnostic ATL grammar reference transcript.schema.json JSON Schema for the Transcript wire format protocol.md operation contracts, auth, versioning, limits sdk/js/ TypeScript/JavaScript client (own README, own tests) integrations/ openclaw/ SKILL.md for OpenClaw (openclaw.ai) -- lets an always-on personal agent author repeated task logic through ATP once and reuse it, instead of re-reasoning the same shape every run. Verified against OpenClaw's own skill-format docs, not guessed -- see that directory's own README. site/index.html single standalone page -- open it directly or host it anywhere static (GitHub Pages, Netlify, S3, ...). Live at https://useatp.com/ (https://atp-agent-communication.netlify.app/ underneath). The hero's typing transcript is a scripted client-side animation of illustrative content -- it does NOT call a live server (no fetch()/XHR anywhere on the page as of this writing). Includes a real, working x402 connect widget (one copy-paste curl command, hardcoded to the live deployment) -- that command genuinely hits POST /x402/tasks/run. docs/legal/ DRAFT Terms of Service / Privacy Policy / Acceptable Use Policy -- not legal advice, read that directory's own README first (see "Legal, entities, and liability" below) examples/ pipeline.atl reference program: research_company / build_report run_demo.py end-to-end demo, runs fully offline tests/ unittest suite (stdlib for the core; transport/ identity/coordination tests need extras -- see requirements.txt) Dockerfile, docker-compose.yml containerized deployment (Postgres + Redis + API) -- see "Running at more than one node" render.yaml Render Blueprint -- auto-configures a one-click deploy of the API on Render; see docs/deploy/render.md docs/deploy/render.md beginner-paced, step-by-step Render deployment walkthrough -- start here if you've never deployed a backend before (only ever hosted static sites) .github/workflows/ci.yml test suite on every push/PR once pushed to GitHub (sqlite path, real-Postgres path, JS SDK) ``` ## Running it ``` python examples/run_demo.py # library-level demo, no API key needed python -m unittest discover -s tests -t . ``` Set `ANTHROPIC_API_KEY` (and `pip install anthropic`) and Part 2 of the demo automatically switches from the offline `MockBackend` to real Claude calls with exact token accounting -- nothing else changes. ## Connecting from any agent Four transports, all wrapping the exact same `atp/engine.py` -- pick whichever fits the caller. Every one gets the same auth, rate limiting, per-request timeout, and usage metering for free, because they're thin wrappers, not separate implementations. **Install + get a key** (admin-issued for now, not public self-serve -- see Pricing below for why): ``` pip install -e .[all] atp keygen --owner "your-name" # prints atp_... -- store it ``` **CLI** -- for scripting or an agent that can shell out: ``` atp run --key atp_... --agent scout --fn value_it \ --arg-names company --args "Acme Corp" --kind valuation ``` **REST API** -- any language, any HTTP client: ``` atp serve-api --port 8000 # docs at /docs (OpenAPI, auto-generated) curl -X POST http://127.0.0.1:8000/v1/tasks/run \ -H "Authorization: Bearer atp_..." -H "Content-Type: application/json" \ -d '{"agent":"scout","task_id":"t1","fn_name":"value_it", "arg_names":["company"],"args":["Acme Corp"],"kind":"valuation"}' ``` **MCP** -- Claude Desktop, Claude Code, or any MCP client, as native tools: ``` atp serve-mcp # stdio, for a local MCP client config atp serve-mcp --transport streamable-http --port 8765 # remote ``` Add to an MCP client's config (stdio form): ```json { "mcpServers": { "atp": { "command": "atp", "args": ["serve-mcp"] } } } ``` Ten tools are exposed: `atp_run_task`, `atp_export_transcript`, `atp_import_transcript`, `atp_claim_task`, `atp_complete_task`, `atp_merge_transcript`, `atp_pool_transcript`, `atp_savings`, `atp_verify_transcript`, `atp_usage`. Every call takes `api_key` and is metered into the same usage ledger the REST API and CLI use -- a caller's spend is identical no matter which transport they used to get here. **A2A (Agent2Agent)** -- any A2A-aware agent, without knowing ATP exists: ``` curl http://127.0.0.1:8000/.well-known/agent-card.json # discovery curl -X POST http://127.0.0.1:8000/a2a \ -H "Authorization: Bearer atp_..." -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"1","method":"message/send","params":{"message":{"parts":[ {"kind":"data","data":{"agent":"scout","task_id":"t1","fn_name":"value_it", "arg_names":["company"],"args":["Acme Corp"],"kind":"valuation"}} ]}}}' ``` This targets the widely-implemented A2A JSON-RPC wire format directly rather than binding to the (still-evolving, protobuf-first) `a2a-sdk` Python package's internal API -- see `atp/a2a_compat.py`'s module docstring for the reasoning. Scope: synchronous `message/send` only, no streaming/push notifications yet. **JS/TS SDK** -- for Node/browser/edge callers who'd rather not hand-roll HTTP: see `sdk/js/README.md`. Zero runtime dependencies (native `fetch`), tested against a real `atp serve-api` process, not a mock. ## Pay-per-call access -- zero auth, via x402 Every surface above needs an API key. `/x402/tasks/run` doesn't need anything: hit it with no credential, get back HTTP 402 with a price, pay (a signed USDC transfer, verified by a facilitator -- you never touch private keys or a blockchain node yourself), retry with proof, get served. The payment itself is the credential. This is the ["x402" protocol](https://github.com/x402-foundation/x402), and it's what actually gets you paid by an agent that has never heard of ATP before this request and will never sign up for anything. ```bash pip install -e ".[x402]" # NOT plain `pip install x402` -- see below atp serve-api --x402-pay-to 0xYourWalletAddress # off (404) until set ``` **Install `x402[evm]`, not bare `x402`.** The bare package is missing a piece the default EVM/Base network actually needs -- `install()` will raise a clear `SchemeRegistrationError` telling you this if you get it wrong, rather than a confusing 500 (a real bug hit and fixed while setting this up: the fix is in `atp/x402_compat.py`). The `[x402]` and `[all]` extras in `pyproject.toml` already pull in `[evm]` for you. If `import eth_account` itself fails on your machine (seen on Windows, where a system Application Control / WDAC policy can block a native DLL deep inside it -- unrelated to anything x402 or ATP actually needs, see `atp/_ckzg_shim.py`), `install()` handles that automatically: it transparently falls back to a documented placeholder for the 4 functions that trip the blocked import, none of which ATP's payment path ever calls. You shouldn't need to do anything for this case -- it's covered. ```bash curl -X POST http://127.0.0.1:8000/x402/tasks/run \ -H "Content-Type: application/json" \ -d '{"fn_name":"value_it","arg_names":["company"],"args":["Acme Corp"],"kind":"valuation"}' # -> 402 Payment Required, with a price and payment instructions. # Pay, retry with the proof, get served. No signup, ever, at any point. ``` Defaults to **Base Sepolia (testnet) + a public hosted facilitator** -- deliberately: this moves real money the moment you point it at mainnet, so the safe default is a network where nothing you can lose is real, until you've watched the whole flow work. Override with `--x402-network`, `--x402-price` (default `$0.01`/call -- see "Pricing" below for where that number comes from and how to compute your own), and `--x402-facilitator-url` when you're ready for mainnet. The paying wallet's address becomes the usage/rate-limit scoping identity -- same mechanism as an API key, just keyed by address instead of a hashed token, so `atp_usage`-style accounting still works per payer with zero signup required from them. **Scope of this pass**: only `/x402/tasks/run` is payment-gated -- that's the one operation that actually costs compute/LLM spend. Read- mostly operations stay on the free, open `/v1/*` surface (still Bearer- keyed for now). Extending the same pattern to another operation is adding one entry to `atp/x402_compat.py`'s route config. **What's real here and what isn't, stated plainly**: the integration is built and wired against the actual x402 SDK (v2.20.0) by reading its source, not guessed, and the Engine-integration logic (`run_task_for_payer`) is unit-tested directly. Two real bugs were found and fixed live while setting this up -- `install()` originally only configured the facilitator, not the required LOCAL scheme registration (`register_exact_evm_server`), so the first real request 500'd with a buried `RouteConfigurationError`; and on the Windows machine this was developed on, `import eth_account` itself failed under a system Application Control policy blocking an unrelated native DLL, fixed via the documented, scoped fallback in `atp/_ckzg_shim.py` (not a policy bypass -- see that module's docstring). Both are fixed, and **confirmed working end-to-end**: `atp serve-api --x402-pay-to
` starts cleanly and `POST /x402/tasks/run` with no payment returns a genuine HTTP 402 whose decoded header carries the correct network, the correct USDC contract address, and the configured `payTo` -- verified against the real public facilitator, not mocked, and locked in by `tests/test_x402.py::test_unpaid_request_gets_a_real_402_with_correct_price_and_payto`. What's still **not** verified: an actual payment SETTLING -- that needs a payer with a funded Base Sepolia wallet to sign and submit a real transaction, which is a different machine/actor than the one running the server. Get testnet USDC from any Base Sepolia faucet, act as a payer against your own running server, and that completed payment is this feature's last untested step. **Auth model, made explicit**: this is additive, not a replacement. `/v1/*` (Bearer-keyed) stays exactly as it was -- admin operations (`atp revoke`, `atp audit`, per-key usage lookups) are not exposed payment-gated-and-open on purpose. Paying to run a task shouldn't also mean paying to revoke someone else's key. ## Trust: verifying what you inherit By default every agent an `Engine` creates (so: every CLI/REST/MCP/A2A call) has a signing identity, and `atp_export_transcript` / `GET /v1/agents/{agent}/transcript` return a signed Transcript. Before trusting an inherited fact from an agent you don't operate yourself, verify it: ``` atp_verify_transcript(api_key, transcript) # -> {"Acme Corp": {"status": "ok", "reason": "ok", "signer_pubkey": "..."}} ``` `"ok"` means the fact is attributable to that exact key and unmodified since signing -- it does **not** mean the signer told the truth. Deciding which `signer_pubkey`s to trust (a specific key, "any key I've seen before," N-of-M independent signers) is a policy call left to you; see `spec/protocol.md#4-provenance-verification`. ## Running at more than one node - **Auth + usage** (`atp/db.py`): sqlite by default -- the single most important thing to know if you're scaling this out is that a **local sqlite file is per-process**. Run three `serve-api` replicas with the default settings and each one has its own separate key store; a key issued on replica 1 is invisible to replica 2. Set `--database-url postgresql+psycopg2://...` (or `ATP_DATABASE_URL`) and every replica shares one real database instead -- same SQLAlchemy Core code either way, `tests/test_postgres_smoke.py` (CI-only, needs a real Postgres) exercises the Postgres dialect specifically. - **Coordination pools** (claims + merged transcripts): pass `--coordination redis --redis-url redis://...` (needs `pip install redis` and a real Redis), and any number of Engine processes/machines pointed at the same Redis see the same pools. `RedisCoordinationBackend` and `FileCoordinationBackend` satisfy the exact same `CoordinationBackend` interface and pass the same conformance suite (`tests/test_coordination_backends.py`) -- swapping one for the other changes nothing above `atp/engine.py`. - **Per-agent live state** (an Agent's Interpreter, mid-run) does **not** yet replicate across nodes -- each server process is authoritative for the agents it created. A caller that needs the same named agent reachable from any node would need a shared agent-state store; not built speculatively ahead of the load that would require it. - **Docker**: `docker compose up` brings up Postgres + Redis + the API wired together (`docker compose up --scale api=3` to prove the horizontal-scaling story). *Not build-tested in the environment this was written in -- no Docker daemon was available there.* Written carefully against standard patterns; treat the first real `docker build`/`docker compose up` as this file's actual first test. - **CI**: `.github/workflows/ci.yml` runs the full suite against sqlite, the same suite's Postgres-specific smoke test against a real Postgres service container, and the JS SDK integration test -- on every push/PR once this is pushed to GitHub. ## Enterprise hardening - **API keys are hashed at rest** (SHA-256) in `atp/auth.py` -- a leaked or dumped database exposes nothing usable; a key must be stolen, not read out of storage. `atp keygen --role admin|agent|auditor` tags a role, enforced (not just stored) on the `/v1/admin/*` surface -- see below; `atp keys` shows only prefixes, never the full key, since it's never stored anywhere to show. - **RBAC is enforced on the admin surface**: `/v1/admin/keys` (issue, list, revoke) and `/v1/admin/audit*` require `role == "admin"` (`auditor` gets the read-only slice: list keys, audit trail, audit chain verification, but not issuance/revocation) -- see `Engine.require_admin` / `require_admin_read` in `atp/engine.py` and `_auth_admin` / `_auth_admin_read` in `atp/server.py`. Everything outside that surface (task-running, transcripts, usage) stays gated only on holding *a* valid key, by design -- there's no role-gated concept of a "task-running permission" to enforce there. - **SSO/OIDC is wired for the admin surface**: an org's own IdP-issued JWT (Okta, Azure AD, Google Workspace, Auth0, ...) can authenticate admin/auditor calls instead of a separate ATP-issued key -- see `atp/oidc.py` (`ATP_OIDC_ISSUER` / `--oidc-issuer` to enable). Deliberately scoped to `/v1/admin/*` only, not task-running -- see that module's docstring for why a rotating JWT is a poor fit for the per-call usage/rate-limit bookkeeping the task-running path needs. - **Audit log**: every key issuance/revocation is recorded (`atp audit`), with the actor, action, and detail, append-only. - **Retries + circuit breaker** (`atp/resilience.py`): real LLM backend calls (Anthropic, OpenAI) retry transient failures (rate limits, connection errors, provider 5xxs) with exponential backoff, and trip a circuit breaker after repeated failures so a struggling provider fails fast instead of piling up slow, doomed retries under load. - **Observability** (`atp/observability.py`): `GET /metrics` (Prometheus format -- task counts by backend/outcome, latency histograms, LLM call counts, rate-limit rejections, instrumented once in `Engine.run_task` so every transport contributes, not just REST) and `GET /ready` (checks the auth database and coordination backend specifically, so an orchestrator can tell "still starting" and "dependency down" apart from "healthy" -- `GET /health` stays a cheap, dependency-free liveness check on purpose). - **Not yet built**: RBAC/SSO scoped *beyond* the admin surface (an "agent"-role key can still call every task-running endpoint -- there's no tiered agent permission model, by design, since there's nothing to differentiate yet), and anything compliance-certification shaped (SOC2, etc. are audit/process work, not code). Said plainly rather than implied by omission. ## Pricing -- x402 pay-per-call, wired to real code **The honest baseline**: there's no existing paid "agent-to-agent chat" market to undercut. MCP, Google's A2A, LangGraph/CrewAI/AutoGen are all free/open-source orchestration -- the only real cost agents pay today is the underlying model's per-token price. So "cheaper than what exists" can't mean a lower service fee than a free protocol; it has to mean lower **total spend**: (their reduced token cost) + (our fee) < (their token cost today). That's a real, defensible model as long as the token reduction is real -- which `atp_savings` / `python -m atp.metrics` make concretely measurable per conversation, not a claimed number. **Path A -- x402, zero-signup pay-per-call** (`atp/x402_compat.py`, already live -- see "Pay-per-call access" above). Charges a flat price per `/x402/tasks/run` call, USDC, no account needed. Because x402 routes price flat per route (not per-request) in the current SDK, the price has to safely cover the WORST case -- a novel task shape needing all `MAX_REPAIR_ATTEMPTS=3` authoring rounds -- not the common case (an already-known fn, zero LLM calls, near-zero marginal cost, pure margin at whatever price is set). `atp/pricing.py` computes this from real numbers instead of a guess: ```bash atp pricing-recommend-x402 --input-price 2.0 --output-price 10.0 # claude-sonnet-5's real $/M rate # -> worst_case_cost_usd, recommended_price_usd (worst case x a safety margin) atp pricing-report --key atp_... --input-price 2.0 --output-price 10.0 # -> real observed cache-hit rate and cost-per-call from actual traffic -- use this # periodically to sanity-check a live price against what calls are ACTUALLY costing ``` Ships with a starting default of `--x402-price $0.01`, computed this way from this project's own observed token counts against `claude-sonnet-5`'s real per-token rate as of this writing -- re-run the command above whenever your configured model or its published pricing changes. ### Does this actually protect cost and turn a profit? Real numbers, not a guess `atp/pricing.py`'s `analyze_x402_margin` computes net margin from real inputs (your provider's rates, real or assumed cache-hit rate) instead of asserting a percentage. Run with `claude-sonnet-5`'s actual current rate ($2/$10 per M input/output tokens -- `atp/llm_backend.py`'s `AnthropicBackend` default model) and this project's own observed 470in/17out tokens per authoring call: | cache-hit rate | x402 blended margin (at $0.01/call) | x402 worst-case margin | |---|---|---| | 0% (nothing reused) | 89% | 67% | | 50% | 95% | 67% | | 80% | 98% | 67% | | 95% | 100% | 67% | Three things this table actually says: 1. **The worst-case floor is real and positive (67%)**, by construction -- `recommend_x402_flat_price`'s 3x safety margin means even a call that needs all 3 authoring/repair attempts, with zero reuse, is still profitable, not just break-even. 2. **Margin scales UP with reuse, and reuse is the architecture's whole point.** A cache hit (an already-known task function -- the common case once a customer's agents settle into reusing established logic) costs essentially nothing to serve. This is a genuinely aligned incentive: the more a customer actually uses ATP the way it's designed to be used, the cheaper it gets for them per call AND the more profitable per call for us -- not a tradeoff. 3. **One real, currently-unpriced cost**: the x402 facilitator may charge a settlement fee at real (non-community, mainnet) scale -- no published fee schedule was found for the public community facilitator used by default, but a commercial or self-hosted alternative likely isn't free forever. Modeled at a hypothetical $0.003/settlement, margin at 80% cache-hit drops from 98% to 68% -- still strongly profitable, but this needs a real number from whichever facilitator you actually settle through before trusting the table above at scale. Run `python -c "from atp.pricing import analyze_x402_margin as f; print(f(0.01, 0.8, 470, 17, 2.0, 10.0))"` with your own rates and expected reuse rate to get your own numbers -- these reflect `claude-sonnet-5`'s real pricing as of this writing, not a permanent promise (provider rates change). ### Is this scalable, and does it beat the alternative? There's no paid competitor to systematically undercut on list price (MCP, A2A, LangGraph/CrewAI/AutoGen are free/open orchestration -- see "the honest baseline" above) -- so "beat other options" has to mean beating the realistic alternative: **a team building equivalent coordination themselves and paying full, uncompressed LLM cost for every agent interaction.** Two savings are actually measured, not asserted: - **Token compression**: this project's own demo measures ATL's wire form at ~27-30% fewer tokens than the equivalent facts written as prose (`atp/metrics.py`, reproducible via `python examples/run_demo.py` or `atp_savings`/`POST /v1/savings` on your own real transcripts). This varies by workload -- it's real for THIS project's demo content, not a universal constant; measure it against your own traffic before quoting it to a customer. - **Deduplication**: the demo's 3-agents-vs-5-tasks scenario shows 5 real fetches instead of 15 (agents racing for overlapping work only pay for it once) -- a 66% reduction in *that specific scenario*. The real number scales with how much redundant work a customer's own agent fleet would otherwise duplicate; a fleet with little overlap sees less benefit from this axis specifically (though still benefits from token compression and from not having to build the coordination layer at all). What this can't honestly claim: a dollar figure for engineering time a customer avoids by not building sandboxed execution, multi-agent coordination, provenance signing, rate limiting, and observability themselves -- that's real value, but it depends on their team's own cost structure, not something computable from this codebase. And whether companies actually *want* this at any given price is a demand question that real usage data answers, not a margin calculation -- the numbers above show the unit economics hold up **if** the workload benefits from compression/dedup the way the demo's does; they don't prove customers will pay $0.01/call regardless of that fit. **What's built vs. what's still yours to do:** the billing MECHANISM (usage recording, x402 payment gating, pricing math) is real code, tested, and confirmed working against a live server and the real public facilitator (see "Pay-per-call access" above). What remains genuinely outside what code can do for you: a domain and public deployment (this runs on localhost by default) and the legal/entity groundwork in the next section -- those need you, not more code. *A second, Bearer-key metered-billing path via Stripe (for signed-up users who want persistent identity / higher limits / non-crypto payment) was scoped and built at one point, then deliberately removed -- x402 is the one monetization path this project actually ships and maintains. If that changes later, `atp/metering.py`'s usage ledger already records everything a metered-billing integration would need to read from.* **Known scaling limitation**: coordination pools now scale across nodes (see "Running at more than one node" above); per-agent live state still doesn't -- see the same section for what that means in practice. ## Legal, entities, and liability -- what's real and what isn't You asked for "full legal 0 liability." Stated plainly: **that's not a real achievable state, from any provider, ever** -- no contract, disclaimer, or piece of code can reduce legal exposure to exactly zero. Certain liabilities (gross negligence, willful misconduct, fraud, and various consumer-protection statutes depending on jurisdiction) legally cannot be signed away no matter how the paperwork is worded. What's real, standard, and actually achievable is **substantially reducing and capping** exposure -- and that takes four things, only one of which is a document: 1. **A real limited-liability entity (LLC or corporation), formed BEFORE taking payments.** This is what actually separates your personal assets from the business's liabilities -- a Terms of Service with no entity behind it protects nobody; the individual is personally liable regardless of what the document says. Forming one is a real-world filing (state/country-specific, needs a registered agent, has real fees) -- not something achievable from this repo. Do this first, before the rest of this section means anything in practice. 2. **A well-drafted Terms of Service, Privacy Policy, and Acceptable Use Policy** -- draft versions are in `docs/legal/` (see that directory's own README for what they cover, what they deliberately don't claim, and the checklist of what's left to do). They're a genuine starting point built to reflect what this codebase actually does and actually collects -- not filled with generic filler -- but they are **not legal advice** and **must be reviewed by a licensed attorney in your jurisdiction** before you rely on them with real revenue. 3. **Insurance** (Tech E&O / cyber liability) as the backstop for whatever the contract and the entity structure don't cover. Also a real-world purchase, not something built here. 4. **A genuinely favorable architectural fact, stated because it's true, not to oversell it**: x402 payments never touch a wallet or account you control -- funds move directly from the payer's wallet to your `payTo` address via the facilitator (see `atp/x402_compat.py`). ATP's operator never custodies user funds at any point in that flow. That measurably reduces (does not eliminate) exposure to money-transmitter-style regulation compared to a system that holds customer balances -- but crypto payment regulation varies by jurisdiction and is still evolving, so confirm this with your own attorney rather than take it as settled. The honest punch list, in order: form the entity -> get the ToS/Privacy/AUP reviewed by a real lawyer -> get insurance -> then take real payments at scale. Each step is genuinely yours; I can't file paperwork, hire a lawyer, or buy insurance on your behalf, and I won't claim a document alone achieves what only those steps together actually achieve. ## Multi-tenant safety Running ATL authored by callers you don't control (any public deployment) needs bounds beyond "the sandbox blocks dangerous syntax." Three layers, all on by default: 1. **Iteration cap**: every `while`/`for` loop is capped (`MAX_LOOP_ITERATIONS`, default 100,000) -- both kinds, since `for` was previously uncapped (fixed in this pass). 2. **Wall-clock budget**: `Interpreter(max_seconds=...)` (default 10s) bounds real time independent of iteration count, since one expensive iteration can blow a time budget while staying under the count cap. The budget is set once per top-level call and threaded through every nested/recursive call -- a deep call chain shares one budget, it doesn't get a fresh one per stack frame. 3. **Request timeout + rate limit**: `Engine.run_task` bounds everything the interpreter's loop-based checks can't see (a hung `fetch_fn`, a stuck LLM call) with a wall-clock request timeout, and applies a per-key token-bucket rate limit -- both centralized in the engine so every transport gets them for free. Documented caveat: Python can't forcibly kill a thread, so a timed-out task keeps running in the background until it naturally finishes or its own interpreter-level budget trips it; true hard cancellation needs process-level isolation, a real next step once this matters under load. 4. **Daily USD spend cap** (`atp/spend_guard.py`): the three layers above bound *time* and *request rate*, not *dollars* -- a rate-limited but unpaid Bearer key can still call `backend='anthropic'`/`'openai'` up to the rate limit indefinitely and run up a real bill with zero revenue to offset it (x402 calls can't do this: payment is collected before the task runs, priced from the worst case -- see "Pricing" below). `ATP_MAX_DAILY_SPEND_USD_PER_KEY` hard-stops real-backend calls for one key once its own estimated spend today reaches the cap; `ATP_MAX_DAILY_SPEND_USD` is the same thing deployment-wide, sized as a last-resort circuit breaker (it also gates already-paid x402 traffic, so set it well above expected legitimate volume, not as a tight budget). Off (unlimited) unless set, like every other optional safety knob here. Checks the durable usage ledger, not an in-process counter, so it survives a restart and stays correct across replicas sharing one Postgres database -- see `atp/spend_guard.py`'s module docstring for the full reasoning, including why this is a soft check (based on already-recorded spend) rather than a hard pre-allocated reservation. ## Spec & conformance `spec/` is the language-agnostic contract (grammar, wire schema, protocol semantics) -- written so another implementation could be built from it without reading `atp/`. `tests/test_spec_conformance.py` and `tests/test_coordination_backends.py` check the Python implementation hasn't silently drifted from what the docs promise; run them after any change to `atp/interpreter.py`'s Transcript shape or `atp/coordination.py`. ## Extending it - New task shapes: add a `kind` to `MockBackend._render` (offline) -- `AnthropicBackend` needs no changes, it just prompts the real model with the same `ATL_SPEC` grammar in `llm_backend.py`. - New coordination topologies: `Pipeline.run_worker_pool` / `run_downstream` are the two primitives; compose them for deeper dependency graphs (a worker's output feeding another worker's task list, not just one final aggregator). - Real backends beyond Claude: implement `LLMBackend.complete()`. - Real external data: wire a domain fetch_fn into `Engine.get_or_create_agent` server-side (an HTTP fetcher, a DB lookup) -- remote callers only ever pass string URIs into `load()`, never executable code. - Another coordination substrate: implement `CoordinationBackend`'s five methods (see `atp/coordination.py`) and pass it to `Engine(coordination_backend=...)`. ================================================================================ ATP Protocol Spec (spec/protocol.md) ================================================================================ # ATP Protocol -- v0.1.0 This document defines the operation contracts every ATP transport (REST, MCP, CLI, A2A) implements identically, plus the auth, versioning, and execution-limit rules that apply regardless of transport. If a transport's behavior and this document disagree, the transport has a bug -- file it against `tests/test_transports.py` or the equivalent conformance test. ## 1. Core operations All four transports (`atp/server.py`, `atp/mcp_server.py`, `atp/cli.py`, `atp/a2a_compat.py`) are thin wrappers over `atp/engine.py::Engine`. The canonical operation set: | Operation | REST | MCP tool | CLI | Semantics | |---|---|---|---|---| | Run a task | `POST /v1/tasks/run` | `atp_run_task` | `atp run` | Execute `fn_name` on `agent`. Zero LLM calls if the agent already knows `fn_name`; otherwise the backend authors it (see spec/atl-grammar.md), bounded retries on failure. | | Export a transcript | `GET /v1/agents/{agent}/transcript` | `atp_export_transcript` | -- | Everything `agent` has fetched/computed, as a Transcript (spec/transcript.schema.json). | | Import a transcript | `POST /v1/agents/inherit` | `atp_import_transcript` | -- | Pre-populate `agent`'s registries from a Transcript; local facts already present are not overwritten. | | Claim a task | `POST /v1/tasks/claim` | `atp_claim_task` | -- | Atomic claim of `task_id` within `pool_id`. Exactly one caller across any number of concurrent callers gets `won: true`. | | Complete a task | `POST /v1/tasks/complete` | `atp_complete_task` | -- | Marks `task_id` done in `pool_id`'s claim log. | | Merge into a pool | `POST /v1/transcripts/merge` | `atp_merge_transcript` | -- | Contributes a Transcript into `pool_id`'s shared, merged transcript. First writer wins per fact. | | Read a pool's transcript | `GET /v1/transcripts/{pool_id}` | `atp_pool_transcript` | -- | The full merged Transcript for `pool_id`. | | Estimate savings | `POST /v1/savings` | `atp_savings` | -- | ATL-wire-size vs. prose-equivalent token comparison for a given Transcript. | | Read usage | `GET /v1/usage` | `atp_usage` | `atp usage` | Cumulative LLM calls/tokens/estimated-savings for the caller's API key. | | Send an A2A message | `POST /a2a` (`message/send`) | -- | -- | A2A-wire-format equivalent of "run a task" -- see `atp/a2a_compat.py`'s module docstring for scope. | | Run a task, no API key | `POST /x402/tasks/run` | -- | -- | Same semantics as "Run a task," gated by an x402 payment instead of a Bearer key. Opt-in (404 unless configured) -- see `atp/x402_compat.py` and §2. | ## 2. Auth model Every operation except health/discovery endpoints (`GET /health`, `GET /.well-known/agent-card.json`) requires an API key, presented as: - REST / A2A: `Authorization: Bearer ` HTTP header. - MCP: an `api_key` argument on every tool call. - CLI: `--key `. Keys are opaque bearer tokens (`atp_` + 32 bytes of `secrets.token_urlsafe` randomness), issued out-of-band by whoever operates the deployment (`atp keygen --owner "..."`) -- there is no public self-serve key-issuance endpoint in this version. Every agent a caller creates is scoped to `(api_key, agent_name)`; callers under different keys never see each other's agents, transcripts, or usage. Coordination pools (`pool_id`) are NOT key-scoped -- any caller who knows a `pool_id` can claim/merge/read into it, by design (that's how unrelated agents collaborate in the same pool). Don't put secrets in a pool_id or its contents. **Exception: `POST /x402/tasks/run`** requires no API key at all -- see `atp/x402_compat.py`. It's gated by an [x402](https://github.com/x402-foundation/x402) payment instead of a Bearer token: a caller with no prior signup gets a `402 Payment Required` response, pays, and retries with proof. The scoping identity for that path is the paying wallet address in place of `api_key` -- same `(identity, agent_name)` scoping, same rate limiter, same usage ledger, just keyed differently. This route is opt-in (off, 404, unless the operator sets a receiving wallet address) and additive: every route above still requires a Bearer key exactly as described, this does not weaken auth on any of them. ## 3. Execution limits A conforming interpreter MUST bound every loop (`while` and `for` alike) to a fixed maximum iteration count and raise a runtime error rather than run unbounded (reference default: 100,000 iterations). This bounds *iteration count*, not wall-clock time -- an engine accepting tasks from untrusted callers (i.e., any public deployment) MUST additionally enforce a wall-clock deadline per `run_task` call, independent of the iteration cap, since a single expensive iteration (e.g. a large comprehension) can blow a time budget while staying under the count cap. See `atp/engine.py`'s `run_task` for the reference deadline enforcement and its documented caveat about thread- vs. process-level isolation. ## 4. Provenance verification A Transcript's `exhibits`/`stipulations` are the facts a receiving agent is being asked to trust without re-deriving them. As of 0.1.0, a producer MAY sign each fact with an Ed25519 keypair (`atp/identity.py`); a signed Transcript carries a `signatures` map (schema in `transcript.schema.json`). Verification (`Transcript.verify()`) confirms: 1. the signature is valid for the claimed fact bytes and public key (tamper-evidence), and 2. which public key produced it (attribution). It does **not** by itself establish that the public key belongs to a trustworthy agent -- that's a separate trust-directory/reputation question this version does not solve. Treat "signed" as "attributable and unmodified since signing," not as "verified true." A receiving agent's own policy (trust this key outright, require N independent signers, reject unsigned facts entirely, etc.) is out of scope for the protocol and left to the caller. ## 5. Versioning `atp.spec.PROTOCOL_VERSION` is a semver string. A Transcript carries the producer's version in `atp_version` (absent = pre-0.1.0, treat as `0.0.0`). - **Patch** (`0.1.0` -> `0.1.1`): documentation/test changes only, no wire-format or behavior change. - **Minor** (`0.1.0` -> `0.2.0`): backward-compatible additions -- a new optional Transcript field, a new task `kind`, a new operation. Old consumers ignore fields they don't recognize and keep working. - **Major** (`0.1.0` -> `1.0.0`): a breaking change to the grammar, the Transcript schema's required fields, or an operation's contract. A receiving implementation SHOULD refuse to process a Transcript whose major version it doesn't understand rather than guess. ================================================================================ ATL Grammar Spec (spec/atl-grammar.md) ================================================================================ # ATL Grammar -- v0.1.0 ATL (Agent Transcript Language) is the statement language agents write instead of prose. This document is the language-agnostic reference: an implementation in any language should be able to parse and execute ATL correctly from this document alone, without reading `atp/interpreter.py`. The Python interpreter is one conforming implementation of this spec, not the definition of it -- `tests/test_grammar_conformance.py` checks the two haven't drifted apart. ## 1. Lexical structure Source is UTF-8 text, one statement per physical line. - `// ...` to end of line is a comment, stripped before parsing. - Blank lines (after comment stripping) are ignored. - **Indentation** is significant: leading spaces count as the line's indent level. A block's statements must sit at exactly `parent_indent + 2`. Tabs are not indentation -- implementations should reject a leading tab rather than guess a width. - **Line numbers**: a line may open with `#N` (N = a decimal integer) followed by whitespace; N becomes that statement's reported line number for error messages and trace output. A line without a `#N` prefix is numbered by its physical position in the source instead. Line numbers are for human/error-trace readability only -- they carry no execution semantics (not, e.g., goto targets) and need not be contiguous or ordered. ## 2. Program structure ``` program ::= { function } function ::= "fn" IDENT "(" [ arglist ] ")" ":" NEWLINE INDENT block DEDENT arglist ::= arg { "," arg } arg ::= IDENT [ "=" expr ] block ::= { statement } ``` A program is a flat sequence of top-level function definitions. There is no module-level code outside a `fn` body. ## 3. Statements Every statement below is one line (its body, for block statements, is an indented sub-block). `expr` is a restricted-Python expression -- see section 4. | Form | Meaning | |---|---| | `X name = expr` | **Exhibit.** Records an externally-fetched fact. In the reference runtime, `expr` typically calls `load(uri)` / `load_all(uris)`, which fetch-or-cache and tag the result with `source` and the fetched `conf`. | | `S name = expr` | **Stipulation.** An agreed/derived value. Computed once per interpreter instance (per `name`); every subsequent `S name = ...` for the same name reuses the first result without re-evaluating `expr`. | | `A[conf] name = expr` or `A[conf] name += expr` | **Assertion.** A claim, optionally confidence-tagged: `A` (default conf), `A5`, `A8`, ... (integer conf), `Ax` (exact/verbatim). `+=` adds to the existing value instead of replacing it. | | `O:reason cond` | **Objection.** Evaluates `cond`; pushes `(reason, triggered, line)` onto the current block's objection stack. `reason` is a bare identifier (no spaces). | | `R expr` | **Ruling.** Pops the most recently pushed, still-open objection in the current block. If it was triggered, executes `expr` as a single assignment (`name = expr` -- the *statement* text after `R` must itself parse as `name = expr`); if not triggered, this is a no-op. Raises if no objection is open. | | `R D expr` | **Ruling + done.** As `R`, but if the objection was triggered, evaluates `expr` (a plain expression, not an assignment) and immediately ends the function with that as the return value -- short-circuiting the rest of the block. | | `D expr` | **Done.** Ends the function, returning `expr`'s value. Raises if any objection in the current block is still unresolved (every `O` needs a matching `R` or `R D` before the block can finish, whether via `D` or by falling off the end). | | `Q name = expr` | **Query.** A scratch computation bound to `name`. Semantically identical to a plain local assignment -- unlike `X`/`S`/`A`, it is not recorded as a fact and does not appear in the exported Transcript's exhibits/stipulations. | | `if cond:` / `elif cond:` / `else:` | Standard conditional, Python semantics. | | `while cond:` | Loop. Implementations MUST enforce a maximum iteration count (the reference default is 100,000) and raise rather than run unbounded -- see spec/protocol.md#execution-limits. | | `for var in iterable:` | Loop over `iterable`. Implementations MUST enforce the same iteration cap as `while`. | | `break` / `continue` | Standard loop control, scoped to the nearest enclosing `while`/`for`. | | `try:` / `except [as name]:` | Catches a *runtime* ATL error (not a syntax/security error) raised anywhere in the `try` block; binds the error's message to `name` if given. | ### 3.1 Objection/ruling nesting Objections nest as a stack **per block** (not globally): opening a second `O` before the first is resolved is legal and simply pushes a second frame; the next `R`/`R D` resolves the most recently opened one (LIFO). A block cannot end -- via `D`, `R D`, or simply running out of statements -- while it still has an unresolved objection; implementations MUST raise a syntax-class error in that case rather than silently drop it. ## 4. Expressions `expr` is a single Python-syntax expression, evaluated under a **capability whitelist**, not a blocklist: - Allowed: boolean/binary/unary/comparison operators, function calls, name and attribute access (non-dunder), subscripting/slicing, list/tuple/ dict/set literals and comprehensions, conditional expressions (`a if c else b`), lambdas. - **Forbidden** at the AST level (parse-time rejection, not a runtime check): any node type outside the above (no `import`, no `exec`/`eval` calls as literal names, no statement forms inside an expression); any attribute access starting with `__`; any bare name starting with `__` or in the blocked-builtins set (`eval`, `exec`, `open`, `compile`, `globals`, `locals`, `vars`, `getattr`, `setattr`, `delattr`, `__import__`, `__builtins__`). - A small fixed stdlib is available as bare names: `len abs min max round str int float sum sorted any all bool list dict set tuple range enumerate zip reversed map filter upper lower strip split join replace startswith endswith contains sqrt floor ceil pow`, plus the runtime- provided `load`, `load_all`, `refetch`, and every other `fn` defined in the same program (mutual recursion is allowed). - Every runtime value is provenance-tagged (`data`, `conf`, `source`, `kind` in the reference implementation's `Value` type). Arithmetic and comparisons between two tagged values propagate the *lower* confidence of the two operands to the result; comparing/combining with a bare literal keeps the tagged operand's confidence. Implementations in other languages are not required to reproduce Python's exact expression grammar byte-for-byte, but MUST reproduce this capability model: no code execution/reflection escape hatches reachable from an expression, ever, regardless of how the expression is spelled. ## 5. Non-goals of this document This is the *statement/expression* grammar only. Wire formats (how a Transcript crosses a process boundary) are in `spec/transcript.schema.json`; operation contracts (what `run_task`, `claim`, etc. mean and how auth/versioning work) are in `spec/protocol.md`. ================================================================================ Transcript JSON Schema (spec/transcript.schema.json) ================================================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://atp.dev/spec/transcript.schema.json", "title": "ATP Transcript", "description": "The wire format for handing off everything one agent fetched or computed to another agent. Produced by Transcript.to_dict() / consumed by Transcript.from_dict() in atp/interpreter.py; this document is the language-agnostic contract other implementations conform to -- the Python code is one implementation of it, not the definition of it.", "type": "object", "required": ["exhibits", "stipulations", "lines"], "properties": { "atp_version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", "description": "Semver of the ATP protocol this transcript was produced under. A receiving implementation with a lower MAJOR should treat the transcript as potentially unparseable rather than guess. Absent on transcripts from pre-0.1.0 producers -- treat as '0.0.0'." }, "exhibits": { "type": "object", "description": "uri -> the raw fetched value for that uri, exactly as returned by the fetch function that produced it (commonly, but not required to be, an object with a 'conf' field).", "additionalProperties": true }, "stipulations": { "type": "object", "description": "name -> an agreed/derived value, computed once and reused.", "additionalProperties": { "type": "object", "required": ["data", "conf"], "properties": { "data": {}, "conf": { "$ref": "#/$defs/confidence" } } } }, "lines": { "type": "array", "description": "Human-readable ATP-style trace lines, in execution order. Not required for a receiving implementation to parse structurally -- exhibits/stipulations are the machine-consumed facts; lines are provenance/audit trail.", "items": { "type": "string" } }, "signatures": { "type": "object", "description": "Optional (added in 0.1.0): uri/name -> a detached signature over that fact, proving which signer's key recorded it and that it hasn't been altered since. See spec/protocol.md#provenance-verification and atp/identity.py. Absent means the fact is unsigned -- a receiving agent decides its own policy for trusting unsigned facts.", "additionalProperties": { "$ref": "#/$defs/signature" } } }, "$defs": { "confidence": { "description": "'x' means exact/verbatim (the ATL source uses Ax for this). Any other value is an integer confidence level; lower is less certain. There is no fixed upper bound in the reference implementation, but 0-10 is the conventional range used in examples.", "oneOf": [ { "const": "x" }, { "type": "integer" } ] }, "signature": { "type": "object", "required": ["alg", "signer_pubkey", "signature", "signed_at"], "properties": { "alg": { "const": "ed25519" }, "signer_pubkey": { "type": "string", "description": "Base64-encoded raw Ed25519 public key of the signer." }, "signature": { "type": "string", "description": "Base64-encoded Ed25519 signature over the canonical fact bytes -- see atp/identity.py:canonical_fact_bytes for the exact byte layout signed." }, "signed_at": { "type": "number", "description": "Unix timestamp (seconds) the signature was produced, included in the signed bytes to prevent replay-splicing an old signature onto a new fact of the same name." } } } } } ================================================================================ JS/TS SDK README (sdk/js/README.md) ================================================================================ # @atp/client A minimal TypeScript/JavaScript client for [ATP](../../README.md) (Agent Transcript Protocol) -- run tasks, exchange Transcripts, and coordinate task pools over the REST API from Node, a browser, or an edge runtime. Zero runtime dependencies (uses the platform's native `fetch`). This exists because the reference implementation is Python-only and most agent tooling in the wild is Node/TypeScript-based. An agent written in any language can already drive ATP over plain HTTP or MCP without this package -- this is the convenience wrapper for TypeScript/JavaScript specifically, with types matching `spec/transcript.schema.json`. ## Install ```bash npm install npm run build ``` ## Usage ```ts import { AtpClient } from "@atp/client"; const client = new AtpClient({ baseUrl: "http://127.0.0.1:8000", apiKey: "atp_...", // issued via `python -m atp.cli keygen --owner "..."` }); const result = await client.runTask({ agent: "scout", task_id: "t1", fn_name: "value_it", arg_names: ["company"], args: ["Acme Corp"], kind: "valuation", }); console.log(result.value, result.llm_calls); // Hand facts to another agent for free -- it won't re-fetch them. const transcript = await client.exportTranscript("scout"); await client.importTranscript("writer", transcript); // Prove the transcript wasn't tampered with in transit. const verification = await client.verifyTranscript(transcript); ``` See `src/types.ts` for the full request/response shapes and `src/client.ts` for every method (`claimTask`/`completeTask`/ `mergeTranscript`/`poolTranscript` for multi-agent coordination pools, `savings`/`usage` for cost accounting). ## Testing `test/client.test.js` is a real integration test, not a mock: it spawns the actual Python `atp serve-api` process and drives it with this compiled client over live HTTP, including a signed-transcript verification round trip and a concurrent claim-dedup race. Requires Python + the `atp` package (`pip install -e ../../[api]` from the repo root) to be runnable from `python -m atp.cli`. ```bash npm test ```