Sessions

Sessions

acpx sessions are how multi-turn agent conversations survive between invocations. A session is a JSON record on disk plus, when active, a queue owner process that holds the live ACP connection. The session record tracks the logical conversation; the queue owner lease is the source of truth for whether acpx currently expects a helper process to be alive.

#Scope key

Every session is keyed by a tuple:

(agentCommand, absoluteCwd, optional name)

That is what makes acpx codex in ~/repos/api and acpx codex in ~/repos/web resume different conversations, and why -s backend and -s docs can run side by side in the same repo.

agentCommand comes from either the built-in registry, an unknown positional name (treated as a raw command), or --agent <command>. Two sessions with different commands are different sessions even if everything else matches.

#Lifecycle commands

acpx codex sessions                  # list (alias for `sessions list`)
acpx codex sessions list             # list agent sessions via ACP when supported
acpx codex sessions list --filter-cwd . --cursor <cursor>
acpx codex sessions list --local     # list saved acpx records
acpx codex sessions new              # create a fresh cwd-scoped default session
acpx codex sessions new --name api   # create a fresh named session
acpx codex sessions ensure           # idempotent: existing or create
acpx codex sessions ensure --name api
acpx codex sessions show             # metadata for the cwd-scoped default
acpx codex sessions show api         # metadata for the named session
acpx codex sessions history          # last 20 turn previews
acpx codex sessions history --limit 50
acpx codex sessions export api --output api-session.json
acpx codex sessions import api-session.json --name api-restored
acpx codex sessions close            # soft-close cwd default
acpx codex sessions close api        # soft-close named session
acpx codex sessions prune --dry-run
acpx codex sessions prune --older-than 30
acpx codex sessions prune --before 2026-01-01 --include-history

Top-level acpx sessions … defaults to codex.

sessions list prefers the agent-side ACP session/list method when the selected agent advertises sessionCapabilities.list. JSON output includes the agent's SessionInfo fields, any _meta metadata, and nextCursor for manual pagination. Use --filter-cwd <dir> to send the ACP cwd filter; relative paths resolve against global --cwd. Use --local when you specifically want the saved ~/.acpx/sessions records.

#Auto-resume by directory walk

Prompt commands (acpx codex 'fix tests', acpx codex prompt …) resume an existing session rather than create one. Lookup is a directory walk:

  1. Detect the nearest git root by walking up from the absolute cwd.
  2. If a git root exists, walk from cwd up to that root inclusive, checking each directory.
  3. If no git root is found, only check cwd exactly — no parent walk.
  4. At each directory, find the first active (non-closed) session matching (agentCommand, dir, optionalName).
  5. If a match is found, use it. Otherwise exit with code 4 and tell you to run sessions new.

This means most workflows feel like "I was talking to codex in this repo", regardless of whether you happen to be in src/ or docs/ when the next prompt fires.

cd ~/repos/api/src/auth
acpx codex 'remind me what we changed'   # resumes the session created at ~/repos/api

#Named sessions

-s, --session <name> adds the name into the scope key:

acpx codex sessions new --name backend
acpx codex sessions new --name docs
acpx codex -s backend 'fix the API pagination bug'
acpx codex -s docs    'rewrite the changelog'

Named sessions are independent. They do not share state, queue owners, or history.

#Sessions vs. ensure vs. new

CommandIf a matching session existsIf not
sessions newSoft-close it, create a freshCreate a fresh one
sessions ensureReturn itCreate a fresh one
(prompt commands)Resume itExit 4 with guidance to run sessions new

new is the explicit "I want to start over" verb. ensure is the idempotent "give me a session" verb for scripts. Bare prompt is conservative: it never auto-creates so you do not accidentally fork a session by running from the wrong directory.

#Soft-close

sessions close does not delete anything. It marks the record closed: true with closedAt, asks any active queue owner to send ACP session/close, and tears down adapter processes.

  • Closed sessions stay on disk with their full record and history.
  • Auto-resume by scope skips closed sessions.
  • Closed sessions can still be loaded explicitly through embedding APIs.
  • sessions prune is the explicit way to delete closed records.

#Export / import

acpx persists sessions per cwd in ~/.acpx/sessions/. To move a session between machines or share one with a teammate:

# On the source machine:
acpx codex sessions export my-debug-session --output debug.json

# On the destination machine:
acpx codex sessions import debug.json --name debug-on-laptop

Export refuses to run if the session is locked by a live queue owner. Run acpx codex sessions close my-debug-session first.

The archive is plain JSON. Paths are stored relative to home, so an imported session lands at ~/<original-cwd-relative> on the destination machine without embedding the source machine's absolute cwd. Override with --cwd.

Imports keep the archive's provider session id, reopen the copied session as an idle local record, and clear source-machine process metadata. Imported sessions must resume that provider session; if the destination agent cannot load it, prompts fail clearly instead of starting an empty conversation. If the destination already has an active session for the same (agent, cwd, name) scope, import fails; pass --name or --cwd to choose a different scope. If a local record already uses the same provider session id, prune or remove that record before importing.

#Prune

sessions prune removes closed records once you actually want them gone:

# Preview what would be deleted
acpx codex sessions prune --dry-run

# Delete closed sessions older than 30 days (by closeAt, falling back to lastUsedAt)
acpx codex sessions prune --older-than 30

# Delete closed sessions whose close time is before a date
acpx codex sessions prune --before 2026-01-01

# Also remove the per-session event-stream files
acpx codex sessions prune --include-history

Output:

  • text — summary plus the pruned ids and close/last-used time
  • json{ action, dryRun, count, bytesFreed, pruned }
  • quiet — one pruned session id per line

#Queue ownership

When a prompt is in flight, acpx becomes the queue owner for that session. Subsequent acpx codex … invocations submit through local IPC instead of starting a second adapter:

acpx codex 'run full test suite and triage failures'
# (still running)
acpx codex --no-wait 'after the suite, summarize root cause in 3 bullets'
acpx codex --no-wait 'and propose 1 follow-up fix'

Queue mechanics:

  • Owner generates a Unix socket at ~/.acpx/queues/<hash>.sock (named pipe on Windows) and a <hash>.lock ownership file.
  • Sockets and lock files are owner-only.
  • After the queue drains, the owner stays alive for an idle TTL (default 300s) so quick follow-ups do not pay the spawn cost.
  • Override TTL with --ttl <seconds>. --ttl 0 keeps it alive indefinitely (until idle shutdown is otherwise triggered).
  • Owner generation IDs are cryptographically random so rapid restarts cannot reuse a stale generation token.

#--no-wait

By default the submitter blocks until the queued prompt completes, streaming events back. --no-wait returns as soon as the running queue owner acknowledges the submission. Useful for scripted "queue up follow-ups" patterns.

acpx codex --no-wait 'after the current turn ends, write the release notes'

#Cancelling

Ctrl+C during an active turn sends ACP session/cancel first, waits briefly for stopReason=cancelled, and only force-kills if cancellation does not finish in time.

The cancel subcommand sends the same cooperative cancel without a terminal signal:

acpx codex cancel
acpx codex cancel -s backend

If nothing is running, cancel exits success with nothing to cancel.

See Session control for set-mode, set <key> <value>, and set model.

#Crash recovery

Saved sessions may include a cached adapter PID from the last connected helper process. That PID is a runtime hint, not proof that the logical session is closed or broken. If a cached PID is gone on the next prompt:

  1. acpx respawns the agent.
  2. Attempts ACP session/resume with the saved provider session id when the agent advertises it, otherwise ACP session/load.
  3. Falls back to session/new if reconnecting fails, transparently updating the saved record.

This makes long-running scripted sessions resilient to crashes, OS restarts, and adapter upgrades.

#Status

acpx codex status reports local process state:

StateMeaning
runningQueue owner alive and processing a prompt
idleSaved session resumable, no queue owner running
deadQueue owner was expected but is unavailable, or the last agent exit was abnormal
no-sessionNo saved record matches this scope

Status checks are local (kill(pid, 0) semantics) — they do not touch the agent. closed describes the logical session lifecycle. A helper process can exit while the session remains open and resumable. Status reports a PID only when a live queue-owner lease ties that process to the session; queue owner liveness comes from ~/.acpx/queues/*.lock plus its heartbeat and process probe.

#CWD scoping

--cwd <dir> sets both:

  • the starting point for the directory-walk lookup
  • the exact cwd for new sessions created with sessions new
acpx --cwd ~/repos/shop codex sessions new --name pr-842
acpx --cwd ~/repos/shop codex -s pr-842 'review PR #842'

CWD is stored as an absolute path in the scope key.

#Session metadata fields

sessions show and the JSON form of sessions new/sessions ensure and status include identity fields:

FieldMeaning
acpxRecordIdLocal record id printed in text and quiet output
acpxSessionIdacpx-side session id (always present)
agentSessionIdProvider-native session id, only when the adapter exposes one

Do not pass an acpx session id to a native provider CLI unless agentSessionId is also present.

#See also