Skip to content

Sessions

The protocol is stateless: context travels with each call. That is right for most tools — crypto, encoding, time have nothing to remember between calls, and a stateless component scales horizontally without any coordination.

Some tools genuinely cannot work that way. A database connection, a 5 MB parsed OpenAPI spec, an upstream MCP handshake, a browser instance, an authenticated API client — re-establishing those on every call is either wasteful or impossible. Those components opt into act:sessions/session-provider, and the host manages the lifetime explicitly rather than pretending it does not exist.

Three functions, and a component that exports them is asking for a lifecycle:

get-open-session-args-schema: async func(metadata) -> result<string, error>;
open-session: async func(args, metadata) -> result<session, error>;
close-session: func(session-id);

open-session returns a session whose id is opaque to the caller. Every later call that should land in that session carries the id as std:session-id metadata. close-session is deliberately synchronous and swallows errors — tearing down should not be able to fail in a way that blocks shutdown.

A component with no external state should not export session-provider. Exporting it commits the host to a lifecycle for no benefit.

The component publishes the shape of its own open-session args, so you never have to guess:

Terminal window
act session open-args-schema actpkg.dev/library/sqlite
{
"title": "OpenArgs",
"description": "open-session args: which database file this session connects to.",
"type": "object",
"properties": {
"database_path": { "type": "string", "description": "Path to the SQLite database file." }
},
"required": ["database_path"]
}
Terminal window
act call actpkg.dev/library/sqlite query \
--args '{"sql": "SELECT sqlite_version()"}' \
--session-args '{"database_path": "/tmp/actdemo/app.sqlite"}' \
--grant '{"wasi:filesystem":{"mode":"allowlist","allow":[
{"path":"/tmp/actdemo/**","mode":"rw"},{"path":"/dev/urandom","mode":"ro"}]}}'

The host opens a session, injects its id for the call, and closes it before exiting. Good for scripts and for trying a component out.

  • The host closes every session it opened before tearing the instance down, so a component can put cleanup in close-session and rely on it.
  • A component that never returns from open-session no longer hangs the host: the wait is bounded and reports what happened, rather than leaving a process that never binds its listener.
  • Calling a tool with an unknown or missing session id fails with std:session-not-found before the component is reached:
Terminal window
Error: std:session-not-found: Missing std:session-id metadata

That error is the usual first surprise with a stateful component — it means the tool needs a session and none was supplied, not that anything is broken.

Credentials arrive in open-session args, not in per-call metadata. The component validates them once, keeps them associated with the session, and the agent only ever handles the opaque id afterwards. Session args are also the one thing the audit trail never records.

See Credentials for how the values get there without being typed into a config file.