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:
# 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
TypeErrorinside 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_errorresponse with your traceback in the execution logs; a run past its time budget producesfunction_timeout. async defentrypoints 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.
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
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:
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
| limit | value | enforced by |
|---|---|---|
timeout | 1s – 300s (default 30s) | SIGALRM in-runner + host-side container SIGKILL |
memory | 64 – 1024 MB (default 256 MB) | cgroup memory cap (+ equal swap cap) + rlimits |
processes | 128 pids | cgroup pids controller |
/tmp | 64 MB tmpfs (noexec, nosuid) | container mount — the only writable path |
response size | 1024 KB | host |
logs | 500 lines × 2000 chars | host |
open files | 128 (rlimit), 256 (container) | rlimit + ulimit |
file writes | 8 MB per file (RLIMIT_FSIZE) | rlimit |
Determinism & environment
- Python 3.12 (
python:3.12-slim), run with-I(isolated) andPYTHONHASHSEED=0. - Read-only filesystem; your code is bind-mounted read-only at
/app/code. Only/tmpis writable, and it is wiped after every execution — useanimica.statefor persistence. - No network interface exists inside the sandbox. Outbound HTTP is exclusively
animica.http.fetch;pip installat 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.