Skip to content

Operating agent sessions

In short

  • Each agent's command line is verified against one CLI build: Codex 0.162.1, Gemini CLI 0.62.0, Claude Code 2.1.270. Cursor runs through its SDK and is not pinned.
  • Preflight checks every agent a profile uses before each session starts. A different build is a warning, not a refusal.
  • After upgrading a CLI, run the recovery drills below with a real login.
  • On a shared server, mind what each agent can read: see Security notes.

The CLIs and their pinned versions

Each adapter's command line is verified against one CLI build:

CLI Pinned Pin variable Login
Claude Code 2.1.270 PARLEML_AGENTS_CLAUDE_PINNED_VERSION claude, then /login (subscription), or ANTHROPIC_API_KEY
Codex 0.162.1 PARLEML_AGENTS_CODEX_PINNED_VERSION codex login (ChatGPT plan), or OPENAI_API_KEY
Gemini CLI 0.62.0 PARLEML_AGENTS_GEMINI_PINNED_VERSION gemini → Sign in with Google, or GEMINI_API_KEY
Cursor SDK (not pinned) — CURSOR_API_KEY

See Credentials & logins for every variable each CLI accepts.

Preflight (before every session start; Settings → Profiles → a profile's Run preflight) checks each CLI the profile uses: installed, signed in, and the pinned build. A different build is reported as a warning, not refused: the flags may have moved. Settings → Agents shows installed against pinned for each CLI. On a server, uv run python scripts/check_agent_clis.py prints the same and exits 1 on a mismatch (--json for scripts; a missing CLI is reported, not failed).

Upgrading a CLI. Install the new build beside the old one, point the PARLEML_AGENTS_<CLI>_BINARY variable at it on a staging server, run preflight and the recovery drills below with a real login, then set the …_PINNED_VERSION variable (or raise the default in parleml_agents/config.py). When the vendor changes the wording of a limit or login message, record it for the failure classifier (parleml_agents/parleml_agents/failures.py, fixtures in parleml_agents/tests/fixtures/hosts/<cli>/).

The credentialed checks in parleml_agents/tests/test_live_hosts.py run a real CLI: pytest -m live_claude (or live_codex, live_gemini) from parleml_agents/.

Recovery drills (after upgrading a CLI)

  1. Stop and resume. Start ml-investigate, stop it mid-turn, resume: the conversation continues and nothing is repeated.
  2. Kill the process. Kill the CLI process: the session shows a lost process; Resume continues it.
  3. Restart the backend while a decision is open: the question is still there after the restart, and answering then resuming continues the agent.
  4. Provider limit. Replay the recorded limit message (or wait for a real one): the banner says "Paused · provider limit" with the reset time; turn on Resume automatically and check it resumes after the reset.
  5. Login expired. Log the CLI out: the banner names the login command.
  6. Queue. Queue a message during a job wait: the agent reads it with the wait's result, before the job finishes.

Running without a CLI

Details: scripted sessions for tests

PARLEML_AGENTS_HOST_ADAPTER=fake with PARLEML_AGENTS_FAKE_SCENARIO=decide (or slow) runs scripted sessions that stream, ask a decision, start a step, write a report and finish, through the real session tools. scripts/agents_e2e_backend.sh starts a backend this way on a throwaway data root for the browser tests in apps/web/e2e-agents.

When the runtime is not running

Details: a server without the runtime

The runtime starts inside the backend (app.main.create_app mounts it). A server built without it answers 503 "The agent runtime is not running on this server." on the ParleML routes that need it (/api/workspaces/{id}/agent-profile, …/documents/publish, /api/provenance/{actor}) rather than failing. A second backend process on the same data root fails to start because the first holds runs/.runtime.lock.

Security notes

  • People drive sessions; tokens do not. The session routes accept a signed-in person only; API tokens are refused. Settings (all but Access) is for administrators (PARLEML_AGENTS_ADMIN_ENABLED=false closes the agent runtime's admin routes).
  • Agent administration runs programs on the server. A stdio tool server is a command the server spawns, so an agent administrator has shell-level reach: give the ParleML administrator role accordingly. With sign-in off (PARLEML_AUTH_REQUIRED=false), the anonymous caller is an agent administrator only for a request from the server's own machine (loopback address, Host and Origin); a request from another host, a container's published port or another site's page gets 403.
  • Per-attempt tokens. Each step attempt gets its own ParleML token for the person who started the session, scoped by the skill's parleml-scope (analyst, compute, or builder bound to the workspace), expiring with the attempt's wall clock and revoked when it ends. An agent can never do more than that person. What a token creates names its session and step (provenance), even after the token is revoked.
  • Tokens go only to ParleML. A ParleML token is minted only for a connection whose URL is this backend's own /mcp; a connection pointed elsewhere gets none and fails preflight.
  • Confinement. Each session has its own work directory and only the granted MCP servers. What its file tools can reach depends on the CLI:

    CLI Reads Writes
    Claude Code Its work directory, the operator uploads, the profile's reference paths and the skill packs — each granted as a path-scoped rule (Read(//<dir>/**)); every other path, another session's work directory and the run records included, is refused. Its work directory only (Edit(//<work dir>/**)); every read-only directory also carries a scoped edit deny.
    Gemini CLI Its work directory and --include-directories: the reference paths and the skill's folder (a per-attempt copy when it may write). Wherever it reads, so it refuses reference paths when it may write.
    Codex Anything the server's account can read: its sandbox (-s workspace-write) narrows writes, not reads, and it reads through its shell. Its work directory.
    Cursor Not narrowed by the runtime (SDK local agent, sandbox off). Not narrowed.

    Claude Code and Gemini CLI get file tools but no shell or web tools (PARLEML_AGENTS_BUILTIN_TOOLS), and the briefing tells them so; Codex keeps its shell with web search and native subagents off. Where sessions of different people share one server, run them on Claude Code or Gemini CLI, or give each person's sessions their own server account: Codex and Cursor sessions can read every session's files the account can. An installation that grants Claude Code a shell (Bash) grants it the same reach. The Files tab, the reader and Publish refuse paths outside the work directory, links, host settings folders (.claude, .cursor, .git) and environment files (.env, .envrc, .env.*). - No secrets in packs or runs. Connection secrets are vault:// references resolved at use; plugin manifests read the MCP URL and token from the environment; imported packs are stored inert until adopted, after a risk preview. Pack hooks run with the backend user's rights (Host-native rules & hooks). - Messages are data. A queued message is delivered as the person's words, attributed to them, never as a system instruction.

Next