Runtime & ABI

The runtime contract is deliberately tiny: one Python module, one entrypoint, JSON in, JSON out, and a capability-mediated animica host API for everything beyond pure computation.

The entrypoint

Your source is deployed as a single module (handler.py) with a module-level entrypoint — main by default, configurable to any function name. It takes the request, and optionally a context object:

the whole ABI
# Either shape works — the runner inspects the signature:

def main(request):
    ...

def main(request, ctx):
    ...
  • request — the parsed JSON body of a POST (≤ 512 KB), or the query-string parameters of a GET, as a dict.
  • The return value must be JSON-serializable; it becomes the HTTP response body. A non-serializable return raises a clear TypeError inside your function.
  • print() works normally and is captured into the execution's logs (it can never corrupt the runtime protocol — stdout is redirected before your code is imported).
  • Uncaught exceptions produce a function_error response with your traceback in the execution logs; a run past its time budget produces function_timeout.
  • async def entrypoints are not supported by the current ABI (the validator rejects them at deploy time).

The host API

import animica inside the sandbox gives you the host API. Every call below is a mediated RPC to the host broker, which checks the function's declared capabilities, the caller's grants and the remaining budgets server-side before performing the operation — the sandbox holds no credentials, keys or network access of its own.

the animica module
import animica

# AI (AI_INFERENCE) — metered per token
text = animica.ai.infer("prompt", max_tokens=200)
text = animica.ai.infer(messages=[{"role": "user", "content": "hi"}],
                        model=None, max_tokens=None, temperature=None)
text = animica.ai.chat(messages)              # alias of infer(messages=...)

# Chain reads (READ_CHAIN)
head = animica.chain.head()                   # {"height": int, "hash": "0x…"}
nanm = animica.chain.balance("anim1…")        # int nANM

# Spending (SPEND_ANM — requires the CALLER's explicit grant)
animica.wallet.pay("anim1…", amount_nanm, memo="")
nanm = animica.wallet.balance()               # the caller's platform balance

# Per-function persistent state (PERSIST_STATE) — encrypted at rest
animica.state.set("key", {"any": "json"})     # <= 16 KB per value, <= 200 keys
value = animica.state.get("key", default=None)
animica.state.delete("key")

# Mediated outbound HTTP (HTTP_FETCH) — the sandbox itself has NO network
res = animica.http.fetch("https://…", method="GET", headers={}, body=None, timeout=10)
# -> {"status": int, "headers": {…}, "body": str, "truncated": bool}

# Call another deployed function (CALL_FUNCTION / CALL_APP)
res = animica.call("owner/slug", {"payload": 1})
# -> {"status": "succeeded"|…, "result": …, "request_id": "rq_…", "cost_nanm": "…"}

# Structured logging (always available)
animica.log("message", level="info")          # debug | info | warn | error

# Secrets injected into THIS execution (configured via the secrets API)
token = animica.secret("MY_API_TOKEN", default=None)

The context object

ctx
def main(request, ctx):
    ctx.request_id   # "rq_…" — matches the receipt and execution history
    ctx.function     # the function slug
    ctx.version      # deployed version number
    ctx.owner        # developer's anim1… address
    ctx.caller       # "account" or "anonymous"
    ctx.deadline_ms  # the configured timeout
    # plus the same helpers as the module: ctx.ai, ctx.chain, ctx.wallet,
    # ctx.state, ctx.http, ctx.call(), ctx.log(), ctx.secret()

Errors

Host-API failures are typed so your function can react precisely:

error taxonomy
import animica

try:
    animica.wallet.pay(to, amount)
except animica.CapabilityDenied:   # not declared / not granted by the caller
    ...
except animica.BudgetExceeded:     # spend/AI/call budget or quota exhausted
    ...
except animica.AnimicaError:       # any other host-side failure (e.g. AI unavailable)
    ...

Execution envelope

limitvalueenforced by
timeout1s – 300s (default 30s)SIGALRM in-runner + host-side container SIGKILL
memory64 – 1024 MB (default 256 MB)cgroup memory cap (+ equal swap cap) + rlimits
processes128 pidscgroup pids controller
/tmp64 MB tmpfs (noexec, nosuid)container mount — the only writable path
response size1024 KBhost
logs500 lines × 2000 charshost
open files128 (rlimit), 256 (container)rlimit + ulimit
file writes8 MB per file (RLIMIT_FSIZE)rlimit
Billing note: the billed CPU time is the host-measured container wall time — the resource the platform actually reserves for you. Guest-reported CPU numbers are recorded for observability but never drive billing (hostile code could understate them).

Determinism & environment

  • Python 3.12 (python:3.12-slim), run with -I (isolated) and PYTHONHASHSEED=0.
  • Read-only filesystem; your code is bind-mounted read-only at /app/code. Only /tmp is writable, and it is wiped after every execution — use animica.state for persistence.
  • No network interface exists inside the sandbox. Outbound HTTP is exclusively animica.http.fetch; pip install at runtime is impossible by construction — see supported packages.
  • Each execution is a fresh container: no state leaks between runs, and module-level globals reset every time.