inis.run

Best practices

Patterns that get good default behavior out of sessions — persistence, starting fast, scoped credentials, and locked-down egress.

Short answers to "what's the right way to do this" for the choices that come up on nearly every integration. Each one links to the fuller reference for the mechanism behind it.

Keep a session, don't recreate one

A session holds its files, installed packages, and running processes across calls — destroying it and creating a new one throws all of that away and pays a full template start again. Pause it instead; any operation wakes it automatically, and idle sessions pause themselves.

session = client.sessions.create(template="python", idle_timeout_ms=60_000)
session.exec(["pip", "install", "httpx", "-q"])
# ... later, same session, no manual pause/resume needed ...
session.exec(["python3", "agent_step.py"])  # wakes automatically if paused

Set idle_timeout_ms so a session between tool calls pauses on its own instead of you tracking idle time yourself — see Session lifecycle for the rest of the timer knobs and Production best practices for why a client-side warm pool isn't needed here.

Prefer resume and fork over cold starts

Resuming a paused session restores its own retained state — tens of milliseconds when it's hot, a second or two when it's warm — rather than booting a template from scratch. Forking clones a live session into N independent children in one call, useful for parallel eval or rollouts that all need the same starting state:

curl -X POST https://api.inis.run/v1/sessions/$SID/fork \
  -H "Authorization: Bearer $INIS_API_KEY" \
  -d '{"count": 4}'

Reach for a fresh template only when you actually want a clean baseline. To roll a session back to a known-good state without losing the session identity, checkpoint and restore instead — see Concepts.

Start from a template instead of installing on every run

A template is a saved starting environment — official (python, node, data-science, …) or one you published from your own session. Starting from one that already has your dependencies beats running the same install step at the top of every session.

# One-time: configure a session, then save it as a template
inis templates save "$SID" --name my-agent-env

# Every later session starts with deps already installed
inis sessions create --template my-agent-env

Pin an exact version (my-agent-env@2026.08.01) when a run needs a byte-identical baseline; use the bare name when you want the latest patched build.

Scope third-party credentials with Connections

Don't bake a provider API key into a template, an env var, or code the session can read directly. A Connection binds a session to one external origin with a credential scoped to specific HTTP methods and paths — the platform injects it on the way out, so your code never holds the real value:

curl -X POST https://api.inis.run/v1/sessions \
  -H "Authorization: Bearer $INIS_API_KEY" \
  -d '{
    "connections": [{
      "name": "stripe",
      "origin": "https://api.stripe.com",
      "authentication": { "type": "bearer", "secret": "sk_live_..." },
      "allow": { "methods": ["GET", "POST"], "paths": ["/v1/*"] }
    }]
  }'
import stripe
stripe.api_key = "placeholder"  # overwritten by the platform, never sent as-is
customers = stripe.Customer.list()

Know the limit before you rely on it: if the destination reflects request headers back (a debug endpoint, an error body that echoes what it received), code in the session can read the credential it was just handed — response bodies aren't scrubbed, and scrubbing wouldn't hold up as a real boundary anyway. Use a scoped, short-lived upstream credential (a Stripe restricted key, a GitHub fine-grained token) so a leaked value can't do more than allow.methods/allow.paths already permitted, and keep allow.paths away from endpoints you know reflect requests. Full detail in Connections — The limit.

Lock down egress for untrusted code

Sessions get full public egress by default. For code you don't fully trust — model-generated, third-party, anything you'd rather fail closed — set a deny-by-default allowlist instead of leaving egress open:

session = client.sessions.create(
    egress_default="deny",
    egress_allow=["api.openai.com", "*.githubusercontent.com"],
)

Enforcement matches on the TLS SNI, not just DNS, so hardcoding an IP to dodge the resolver doesn't get around it. Every allowed and denied attempt is recorded — see Networking & egress and Execution log. Set an org or project default so callers don't have to opt in per session.

Pass exec commands as an argv array, not a shell string

command on exec is an array — each element is one argument, not a shell line to be parsed:

// Wrong — tries to exec a binary literally named "pip install httpx -q"
{ "command": ["pip install httpx -q"] }

// Right
{ "command": ["pip", "install", "httpx", "-q"] }

If you need shell features — pipes, &&, globbing — wrap explicitly instead of hand-splitting them yourself:

session.exec(["bash", "-c", "pip install httpx -q && python3 -c 'import httpx'"])

Use a volume for data that must outlive the session

A session's own disk goes away with the session. For files that need to survive across sessions — a dataset, a build cache, accumulated output — create a volume once and attach it by id:

curl -X POST https://api.inis.run/v1/volumes -H "Authorization: Bearer $INIS_API_KEY" \
  -d '{"size_gb": 5}'
# {"id": "vol_7f3ac21b9e4d", ...}

curl -X POST https://api.inis.run/v1/sessions -H "Authorization: Bearer $INIS_API_KEY" \
  -d '{"volume_id": "vol_7f3ac21b9e4d", "template": "python"}'

A volume's contents are durable as of the last time its session was destroyed, not continuously — writes during a live session exist only on the machine running it until then. Treat it as a working disk, not a live backup; see Volumes — Durability for exactly what's guaranteed and when.

Where to go next

On this page