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 pausedSet 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-envPin 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
- Session lifecycle — full timer table, resume tiers, pause vs checkpoint vs template
- Production best practices — idempotent creates, bulk file transfer, pagination, reconnecting after resume
- Connections — scoped credential brokering, full lifecycle and limits
- Networking & egress — allowlist mechanics, org/project defaults
- Volumes — sizing, resizing, durability guarantees