Source: https://docs.any.org/programs/effects.html
# Effects
An effect is the only way guest code touches the world. The catalog below is the entire host surface — small and stable by design. Everything else a program uses (the `any` client, LLM adapters, memory, the conversation loop) is itself guest Python loaded with `use()`. Provider keys ride effects too, as [credentials](https://docs.any.org/programs/credentials.md.txt).
## The catalog
| Syscall | Class | What it does |
| --- | --- | --- |
| `http.get/head/post/put/patch/delete(url, params?, headers?, json?, body?, timeout?, credential?, redirects?, response?)` | route-derived | the one outbound door → `Response` with `.status`, `.headers`, `.url`, `.text` / `.json()` for a text body, `.blob` for bytes |
| `config.get(key)` | read | agent config; secret namespaces are refused |
| `config.set(key, value)` | mutate | writes one agent config row |
| `runtime.get(key)` | read | runtime wiring — `any.base_url`, `overlays.aliases`, `bao.space`, `shell` |
| `mailbox.drain()` | read | loop control (injected messages, breaks) as a recorded effect |
| `time.now` / `uuid4` / `sleep` / `env.get` | read | determinism pins — `time.now` returns `{epoch, offset_s, tz}`; `sleep` caps at 300 s |
| `fuel.state` | read | `{remaining, budget}` — a checkpoint signal for long loops |
| `module.resolve(spec, frm?)` | read | what `use()` calls; output carries the source |
| `batch(name, payloads)` | per item | fan-out; records land in input order |
| `blob.read(hash, offset, length)` / `blob.put(data, mime)` | read | bytes behind a `Blob` handle, held by the host |
| `oauth.connect(provider, scopes?, timeout?)` | mutate | consent via loopback + PKCE; returns `{ok, provider, grantedScopes, account}`, never tokens |
| `oauth.status(provider)` | read | `{connected, pending, scopes, account, expiresAt}` — no network |
| `oauth.disconnect(provider)` | mutate | provider-side revoke (best effort) + stored grant delete |
| `oauth.refresh(provider)` | mutate | host-emitted only; a direct guest call fails typed `host_only` |
| `trace.effects_of(cell?, span?, run?)` / `trace.effect_get(seq, run?)` | read | a run's trace as a view — this run, or a past one by run id |
| `trace.runs(program?, filter?, sort?, limit?)` / `trace.stats(run)` / `trace.query(pipeline, coll?)` | read | the run finder, one run's cost summary, read-only aggregation over the trace store |
| `bao.status(line)` | mutate | the agent's live status line |
| `sh.run/spawn/poll/kill`, `fs.read/list/write/edit` | mutate (`fs.read`/`fs.list` read) | processes and files on the device — only in a runtime built with the `shell` feature |
| `span.begin/end` | — | guest-declared grouping (broker machinery, not registry effects) |
`http.*` derives its capability from the route (below); every other syscall's capability is its own name.
Ergonomic shims sit on top: `now()`, `rand()`, `env(...)`, `uuid4()` as globals, and proxied `datetime` / `time` / `os.environ` modules whose "what time is it" calls route through the same effects. The present is always a record — `datetime.now()` and `time.time()` are real `time.now` records. Randomness is not one record per draw: `random`, `secrets` and `uuid` run on a stream seeded from the trace header's `seed`, so a replay re-derives every value.
## Pythonic surface
```python
r = http.get("https://api.github.com/repos/anyproto/any",
headers={"Accept": "application/vnd.github+json"}, timeout=20)
if r.status == 200:
print(r.json()["stargazers_count"]) # print() is the output channel
```
`print()` is the model-facing output channel: each call is captured as a structured value in the cell's result and is the primary input to its digest. `http.*` mirrors the requests idiom (`params=`, `headers=`, `json=`); a call returns the response as data, including 3xx when redirects are manual. The host decides text versus bytes from the response itself: a binary body comes back as a `Blob` handle on `.blob` (`.text` raises), and a `Blob` passed as a request `body` or inside `json` is expanded by the host at send time while the trace keeps only the reference.
## Read or mutate is a boundary fact
Every record carries `meta.class` of `read` or `mutate`. For HTTP the class and the required capability derive from `(method, url)` against a route table scoped to the any server's base URL:
| Route | Class | Capability |
| --- | --- | --- |
| `GET` / `HEAD` | read | `data.read` on the any server, `net.http` elsewhere |
| any server `POST …/query`, `…/objects/query`, `…/search`, `…/aggregate` | read | `data.read` |
| every other any server call | mutate | `data.write` |
| LLM completion endpoints (`/v1/messages`, `/chat/completions`, `/v1/complete`) | read | `llm.chat` |
| anything else | mutate | `net.http` |
Guest code cannot declare its own class. A `@span(kind="getter")` facade whose span contains a mutate is a legible inconsistency a viewer can flag; the log never lets the label override the boundary fact. Reads are safe to re-execute; mutates are not, and replay tooling relies on that distinction.
## The pipeline
Every effect call, no exceptions, flows through one path:
```
normalize → key → capability check → replay/mock consult → execute → record → return
```
- **Normalize** produces the canonical input — the payload as JSON with sorted keys; sha256 over the effect name and that JSON is the record's `key`.
- **Capability check** consults the active grant set before anything runs. The runtime ships a permissive default; a denial is recorded as `error.type: "capability_denied"`.
- **Record always** — success, error, denial, mock. A failure is data in the record *and* a typed `EffectError` (`": "`) raised into the guest, so a cell may catch it and nothing is silently swallowed.
> **Why it matters.** The guest-side facade (`http.get`, `print()`) holds zero authority — bypassing it gains nothing. Classification, secrets and recording all happen on the host side of one serialized crossing. The cage and its syscalls never change; everything the agent *is* lives above the boundary as deployed code.
## Fan-out
Cells are single-threaded. `http.get_many(items)` carries a list of requests across the boundary once as a `batch` effect; the host runs each item as its own effect call and appends one record per item **in input order**, so strict replay stays deterministic. A per-item failure is an `EffectError` value in that slot, never a whole-batch exception. Looping the same effect sequentially is correct-but-slow, never wrong.
## Introspecting your own run
`effects.of(cell)` returns a scope's immediate children — bare effect records plus child span-end rows — and `effects.get(seq)` resolves one full record; both take `run=` to read a past run. `effects.runs(...)` finds runs by program, status or cost, and `effects.query(pipeline)` aggregates over every recorded run. This is how the agent answers "why did you do that?" from inside a conversation; the human-side equivalent is `anyrt trace show` on [Traces and replay](https://docs.any.org/programs/traces-and-replay.md.txt).