Framework Session

The machine-wide record of every running and frozen code agent that makes freeze and restore possible.

What it is

A framework session is the framework’s own record of a code agent: which project and tmux window it belongs to, which task it is working, which agent and model it is, the CLI session id it could be resumed from, and where it sits in the freeze/restore lifecycle. One record per agent, for every agent on the machine, across every project.

The records live in a single file, ~/.config/aitasks/agent_sessions.json, with mode 0600. aitask_agent_sessions.sh is the only writer: it holds a lock around each read-modify-write and replaces the file atomically. Readers — the viewer, ait monitor, ait minimonitor — take no lock at all, because every write lands whole, so a reader always sees one complete generation.

A frozen agent’s captured output does not live in that file. It goes to ~/.config/aitasks/frozen/<id>/ (mode 0700) as capture.ansi, the raw terminal capture with its colour intact, and capture.txt, the same output stripped to plain text.

Identity versus location

A record is identified by project root, window name and slot — the slot distinguishing a second agent that was split into the same window. That triple is what survives the things that happen to a running agent.

Its pane id and process id are location, not identity. A tmux server restart, a reattach or a respawn replaces them on the same record, which is why a restarted tmux does not produce a second record for an agent it already knows.

The join between a pane and its record is the tmux pane option @aitask_record, stamped on the pane after the record is written. Pane options die with the pane, so a recycled pane id can never carry a stale one. Two more options mark a frozen pane and a stand-in viewer that has finished booting; together they are what lets the framework tell “the agent is still here” from “the viewer has taken over” without trusting a process name.

States

A record moves through five states. Every transition is a single guarded operation — an illegal one is refused and writes nothing:

                 freeze-begin              freeze-commit
      live ──────────────────▶ freezing ──────────────────▶ frozen
        ▲                          │                          │
        │      freeze-abort        │                          │ restore-begin
        └──────────────────────────┘                          ▼
        ▲                                                  restoring
        │  hook acknowledgement, or liveness confirmation      │
        └──────────────────────────────────────────────────────┤
                                                               │ restore-abort
                            stand-in back                      ▼
                 frozen ◀───────────────────────────────── aborting

      drop, from any state: the record and its capture files are removed

freezing, restoring and aborting are transitional: an operation is in flight. aborting in particular is owned by the restore attempt that failed, which stops a second restore from starting in the gap before the stand-in viewer is back.

Operation leases

Freezing and restoring are multi-step transactions run by a coordinator that must outlive the pane it is respawning. Each takes a lease on the record — a one-time nonce plus the coordinator’s own process id — and every verb that changes a leased record must present the matching nonce. A coordinator that lost the race writes nothing rather than acting twice.

The lease is what makes repair safe. reconcile settles transitional records from what tmux can be seen to hold, but it leaves a record alone while the lease’s owner is alive or the lease is younger than 60 seconds. Only a lease whose owner is gone can be taken over.

The session hook

ait setup installs a SessionStart hook that runs when a code agent starts. It records the agent’s session id into the record, which is what makes a later restore possible at all. It writes nothing into the agent’s session and always exits successfully.

The hook records only when it runs inside the agent’s own pane: the pane’s process must be one of its ancestors, and no Codex app-server daemon may sit between them. This matters for a Codex agent started by hand, without the framework’s launch overrides. Codex can run such an agent through its shared app-server daemon, and the daemon then runs the hook on that agent’s behalf, reporting the pane that started the daemon, which is not necessarily this agent’s pane. Those reports are ignored, so a hand-started Codex agent like this is restored by re-picking its task.

The hook also records which CLI owns the session. An agent launched by the framework names itself. For an agent you start by hand, the hook reads it from the shape of the transcript path the agent reports: a Claude Code transcript lives under projects/, a Codex one is a rollout-… file. It counts only when that path names the very session being reported. A restore resumes the session only with that same CLI. For an agent started by hand, the restore uses the agent and model the record names when freezing could recover them (the agent was started with a model the framework knows). Without them it would launch the project’s default agent for raw sessions, and resumes only when that default is the same CLI. When nothing could tell which CLI it was, or the restore would launch a different one, the restore is refused and the agent stays frozen; you can still re-pick its task. An agent started inside another agent inherits that agent’s identity. When the inherited identity and the transcript disagree, the hook records nothing.

The hook is also the acknowledgement channel for a restore. The replacement agent is launched with the record id and the attempt’s nonce in its environment; the hook hands them back, and the store checks that the session that came up is the session that was asked for. That is the verified outcome, and only a verified restore deletes the capture files.

A resumed Codex agent is verified differently. Its TUI fires the hook only at its first turn (that is the measured behaviour of Codex 0.163.0; 0.153.4 fired it only under codex exec). So the coordinator does not wait for a hook. It reads which session’s transcript the resumed process holds open (from /proc on Linux, from lsof on macOS), and the store compares that session with the one it asked for. A match is verified and deletes the capture. A different session aborts the restore.

When nothing verifies the restore within frozen.restore_ack_grace, it is confirmed on the evidence the coordinator does have: the process it launched is running in the pane. That is the unverified outcome, and it deliberately keeps the capture. It is the outcome for any agent whose CLI reports no session within the grace, and for a Codex resume where the session cannot be read from the process (for example a TUI routed through the shared Codex app-server daemon, which holds the transcript instead).

A hook that reports in after the restore has settled is never refused. Codex does this at its first turn. If the restore was confirmed on liveness and the hook comes from the launched process with the session that was asked for, the restore is upgraded to verified and the capture is deleted. Any other late report simply updates the record, so the pane keeps following the agent’s current session.

Cleanup rules

Records are retired only on positive evidence, and never on a partial view: an observation that could not enumerate everything suppresses every removal, and transitional records are exempt entirely — they belong to reconcile.

ReasonWhat it means
dead_windowa live agent’s window is gone from a project that was fully enumerated
dead_paneits pane is gone or dead and its process is provably gone
capture_missinga frozen record’s capture file no longer exists, so there is nothing left to read or restore from

Two things run this cleanup, so it does not depend on a TUI being open: the maintenance tick of ait monitor and ait minimonitor, and the freeze/restore coordinator, which reconciles after every operation.

Overrides

Two environment variables relocate the state, which is what the test suite uses and what an unusual home directory layout needs:

VariableReplaces
AITASKS_AGENT_SESSIONS_FILE~/.config/aitasks/agent_sessions.json
AITASKS_FROZEN_DIR~/.config/aitasks/frozen

Why it exists

An agent’s pane is not a durable thing. It dies with a tmux restart, it is recycled, it is renamed, it is split. Tying “which agent is this” to a pane therefore loses agents exactly when it matters — after the crash or the reboot you most want to recover from.

The store answers that question from outside tmux, machine-wide rather than per-project, which is what makes it possible to end an agent’s process and still have something that knows how to bring it back.

How to use

You rarely address the store directly. It is what the freeze and restore workflow is built on, what ait frozenagent lists and acts on, and what the frozen rows in ait monitor and ait minimonitor are reading.

See also


Next: Agent memory