Product guides and code maps.View source

41 · Agent sessions

A session is one saved conversation with the person's own agent (Claude Code, Codex, Cursor's agent, GitHub Copilot, Grok Build, OpenCode, Gemini CLI, Antigravity, Mistral Vibe, Qwen Code or Kimi Code) on their computer. Ensemble starts sessions for task runs and terminal chats (30), for assistant chats, asks and routines on an AI plan (30 §8), and for Ensemble Node (35 §8). This page covers how sessions are continued, chosen, grouped by project, shown in the agents' own apps, deleted and cleaned up.

Paths below are under apps/hub-api/src/ unless qualified.

At a glance

Session One per Continued Deleted from the agent
Coding session (task runs, terminal chats) Chain of runs that continue each other Continue an earlier task, the assistant's session, or a fitting session chosen on the computer (§1, §4) When the person or a node deletes it (§6), or once every task in it is retired (§6, clean-up)
A node's own coding session Run a node starts for itself By the node None of its runs started or finished in 7 days (§6)
Assistant chat on an AI plan Chat Every message, while the agent and the chat's position match (§5) With the chat, when replaced, or unused for 30 days (§5, §6)
Routine on an AI plan Routine Every run With the routine, or unused for twice the gap to its next run (30 to 90 days) (§6)
Ask Ensemble on an AI plan Person and page, or surface Every ask there, one at a time in order (§5) 30 minutes after the last answer (§5)
A node's conversation on an AI plan Node conversation (DM, room, plan step) Every message Unused for 7 days (§5)
One-off completion (triage, a summary, a title) None Never When the turn ends (§5)

Settings → Agents → Agent sessions has the three switches, all on by default:

Switch Setting What it does
One folder per project orchestration.projectFolders A project's tasks share <workspace>/projects/<name> (§2)
Continue a fitting agent session orchestration.linkedSessions A new run may continue a session that fits it (§4); Assign → More options → Always start a new agent session skips it for one run
Clean up agent sessions orchestration.cleanSessions Deletes the sessions of retired work from the agents (§6)

Sessions are listed by ensemble sessions (26), Assign's Continue an earlier task, Ensemble Node's Sessions page (35 §8.9) and GET /api/agent/sessions. The Hub has no session list.

1. Coding sessions

A coding session (workspace/sessions.ts) is one conversation with a coding agent (Claude Code, Codex, Cursor's agent, GitHub Copilot, Grok Build, OpenCode, Gemini CLI, Antigravity, Mistral Vibe, Qwen Code, Kimi Code), carried across the runs that continue each other (continueFromJobId) with the same agent. It is derived from the runs, not stored: its id is its first run's id, its agent session is the newest engine event in it, and the chain's resource key keeps it to one turn at a time.

  • Continuing a task in a session (Assign's "Continue an earlier task", the assistant's session, Assign.session) resumes the agent's own conversation (earlierSession walks back to the nearest recorded session) and sends the new task with a line saying it continues there (sessionTaskPrompt); the agent keeps what it learned and its prompt cache. A session that cannot be reopened (SessionGoneError) runs as a new one with the full prompt.
  • A session that no longer exists. The person may delete a session in the agent's own app, or the agent may expire it (Claude Code after 30 days). Every agent says so when asked to reopen it (Claude Code: "No conversation found", exit 1; Codex: "no rollout found for thread id"; Copilot, Cursor and the ACP agents: session/load refused), and the run goes on in a new session in the same folder with the full brief, which has the earlier task's title, outcome and summary ("This continues an earlier task"). Every agent keeps a session's id when it resumes it, so the web tells a resumed run from a restarted one by the ids computers already send (sessionRestartOf in workspace/session-continuity.ts): the task page says the earlier session no longer existed, the run's SessionChoice.outcome.resumed is false, and a node is told in its run note that the agent did not remember the earlier conversation.
  • A session deleted in Ensemble (by the person, a node, or the clean-up) is not reopened at all (sessionToResume): a run continuing one of its runs ("Continue an earlier task" lists them as "its agent session was deleted: a new one starts there") works in that folder in a session of its own (continuesSessionOf in workspace/sessions.ts), which is listed, chosen and cleaned up like any other; the deleted session keeps only its own runs.
  • A run that continues a run in a folder the person granted (a shared folder) works in that folder however long the chain, since only the first run carries the grant (grantedFolderOf in workspace/agent.ts).
  • Assign.session (any run id in it) runs on the session's computer, under its current pairing, in its folder, with its agent, model, thinking level, context window and fast mode unless others are named (a run profile's do not apply); another agent or computer is refused. Each setting is kept, reset to the agent's own default ("default", or fast: false), or set as named, separately (sessionChoices in workspace/assign.ts): naming a model keeps the thinking level and context window. The task's model tier adds nothing to a run in a session.
  • GET /api/agent/sessions?taskId=&deviceId=&q=&limit= lists the person's sessions, newest first, each with agent, model, computer, folder, first and latest task, a one-line summary of the latest report, status (working, waiting, queued, idle) and whether it can be resumed; GET /api/agent/sessions/:id reads one, and a job's detail carries its session. The web shows no session list.
  • Ensemble Node's nodes start and drive sessions, each node only its own (35 §8). A steer is a run that continues the one it interrupts, with context.steer {update} (the claim spec's steer on a paired computer); resuming the session, the agent gets only the change (steerPrompt), and with the session gone it gets the whole brief with the update in its instructions.

2. One folder per project

The person's own agents keep their sessions by working folder (Claude Code's projects, Codex's and Cursor's workspaces, Copilot's sessions by folder), so an Ensemble project gets one folder for its tasks (workspace/agent-projects.ts): <workspace>/projects/<name>. A folder alone is not a project in every agent's app, so Ensemble also files its sessions there itself (§3). A task in a project that the person's own agent does, and that would otherwise get a new folder (no earlier task continued, no folder, repository, chat or Ensemble Node plan), works there, and the agent's app lists it with the project's other work. The folder is a git repository like a run's own; runs in it take turns (the dir: resource key, and the queue's same-project rule). Session lists (the Sessions page, Continue an earlier task, ensemble sessions) show such a session's folder as projects/<name>, from the folder its computer reported for the run (the prepared event); a run in a folder of its own shows "its own checkout".

  • The project's brief (name, summary, notes, project page, repositories, people and their roles, upcoming deliverables) is Markdown with a hash (projectBrief). The web server builds it when a computer claims the run (the claim's project, 25); a local-only computer builds it from its own database. Older computers ignore it and work in a new folder.
  • Before the agent starts, the brief goes into the folder's AGENTS.md between Ensemble's markers (the rest of the file is the folder's own), with CLAUDE.md and GEMINI.md importing it (@AGENTS.md), only when the hash differs from the one last written there. Those three files are kept out of the repository (.git/info/exclude). Runs in the folder can change them, so Ensemble never reads or writes them through a link: it reads a plain file only (O_NOFOLLOW) and writes a new file renamed over the old one. A continued run in the folder gets the same check (bringProject in workspace/agent.ts).
  • The run's rules say it is the project's folder, that AGENTS.md has the brief (and, when the project changed since the folder's last run, to read it again), and to keep files only this task needs in a folder named for it. Codex, Copilot, Cursor and OpenCode read AGENTS.md themselves; headless Claude Code reads only the person's own settings in an Ensemble folder (a run could otherwise plant hooks for the next one), so its brief comes through the prompt's context and the rule, and CLAUDE.md is for the person opening the folder in the Claude app.
  • Which folder is which project's, and the hash of the brief each last got, is kept in projects/.ensemble-projects.json, beside the folders and outside any of them, so nothing a run writes in a project folder can point Ensemble at another. A renamed project keeps its folder (and its agents' sessions); one removed is made again under its name; another project with the same name gets its short id after the name. Opening a project's task in the agent's own app (30 §4) opens the project's folder too.
  • Off with Settings → Agents → Agent sessions → One folder per project (orchestration.projectFolders). Apps' own "Projects" (claude.ai, ChatGPT) have no API an agent run can use; the working folder is what the coding agents group by.

3. In the agents' own apps

An agent names a session after its first message, which for Ensemble is its instructions, and its desktop app lists only what it knows. So Ensemble does the following, best effort and never failing a turn (workspace/engines/app-sessions.ts):

App Titles Ensemble projects What stays out of reach
ChatGPT app (Codex) thread/name/set: a chat's title, an ask's page (Ask Ensemble: <page>), a task's title A Codex app-server project (project/create), found again by metadata.ensembleProjectId or by its folder and renamed with the Ensemble project; a task's new thread starts in it (thread/start projectId), as do an Ensemble Node's chats in its folder. The app's sidebar shows only the projects in its own file, .codex-global-state.json in Codex's home (CODEX_HOME, else ~/.codex), which it reads when it starts and writes back whole while it runs. So the project goes in that file under its app-server id (local-projects, last in project-order), with the thread and the project's earlier threads (thread-project-assignments), only while the app is closed: no live single-instance lock in the app's data folder (SingletonLock on macOS and Linux, lockfile on Windows) and, on macOS and Windows, no app process (codexAppOpen, fileCodexAppProject). Until then it is owed in <state>/codex-app-projects.json, tried again every 2 minutes and when the runtime starts, and shows when the app is next opened. A thread already in a project or set apart as projectless stays where it is; the person's own project for the folder keeps its entry and gets the threads; a project renamed in the app keeps the person's name. A project removed in the app (waiting to be deleted, or gone after Ensemble filed it) is left removed: the thread starts outside it. Deleting the Ensemble project removes its Codex project once the project has been in Trash 10 minutes, so Undo finds it as it was and a project back by then keeps it (workspace/project-delete.ts, frame project.delete: 25 §4.9). The computer finds the Codex projects carrying the project's id, never the person's own for the folder, and takes each out of the sidebar file the way the app does when the person removes one (local-projects, project-order, pinned-project-ids; its threads move from thread-project-assignments to projectless-thread-ids), only while the app is closed and owed until then like a filing. Codex deletes it only once the file no longer shows it (project/delete, which leaves its threads in no project), since the app imports again, when it starts, an entry whose project Codex no longer has. It is never filed again (removeCodexAppProjects, remote/project-delete.ts), and the project's folder and the agents' sessions stay. Codex keeps an idempotency key with the project it deleted. A project brought back from Trash whose Codex project Ensemble deleted (deletedFor in <state>/codex-app-projects.json) gets a new one under the next key (ensemble-project:<id>:2, …) with its next task; one the person removed in the app is not made again. Chats and asks stay outside any project Codex before its experimental project API: threads in no project. The sidebar file is the app's own, not an interface it offers: an app that keeps its projects elsewhere does not show what Ensemble wrote there
Claude app (Code tab) Claude Code's --name on a new session (from a Claude Code whose help lists it) The Code tab lists only sessions it has a record for, in the profile the app runs on: Claude (signed in with a Claude account) or Claude-3p (on a gateway or a cloud provider), in ~/Library/Application Support (macOS), %APPDATA%, %LOCALAPPDATA% for Claude-3p, and a Microsoft Store build's own copies (Windows), ~/.config (Linux), each with claude-code-sessions/<account>/<org>/. A new session of Ensemble's, a chat's or a task's (in its project folder), gets the record the app keeps for a Claude Code session it imported (local_<session>.json with the transcript's id, folder and title, adoptedFromOtherSurface and fromCliTranscript; claudeCodeTabRecord), in the profile the app ran on last and the list of the account and organization it wrote to last (claudeCodeTabFolder); a Claude-3p profile whose Code tab was never opened gets it under the app's install id (ant-did) and its local organization. Ensemble deleting the session removes its records from every profile. Nothing is added for a Claude account whose Code tab was never used. The app reads its list when it starts, so a running app shows a new record once reopened The chat side's Projects are claude.ai's, which no program on the computer can make
GitHub Copilot (CLI, VS Code's sessions) --name on a new chat session (user_named) Sessions by folder A task run's session (ACP) is named by its prompt, which starts with the task's title
Cursor ACP sessions (~/.cursor/acp-sessions) are in neither the Cursor app's history nor agent --resume: Cursor keeps the three apart
Antigravity Ensemble runs Google's own agent server with its own home and sign-in, not the Antigravity app, which keeps its own history

4. Choosing a session

A new run by the person's own agent that would start a new session in a new folder (no session, continueFromJobId, folder, repository, pull request or chat; not an Ensemble Node plan or a retry) may continue one of the agent's sessions that fits the task (workspace/session-choice/). No model takes part: the same records always give the same decision, in milliseconds.

  • Where. On the computer that runs the agent, when it takes the job (createJob in its runtime, mayChoose): its sessions and the text of its earlier runs are there. Ensemble on the web sends with each claim what the computer cannot know (related: the task's project, deliverable, meeting, parent page, labels and the tasks it names, factsOf; sessionChoice: the config, with the person's setting in enabled; newSession). A local-only computer has the records itself. The computer keeps each claimed task's facts on its run (context.taskFacts), so later runs are matched against it. A run claimed by an older runtime has none and is never a candidate.
  • Candidates. This computer's sessions of the same agent that are idle, resumable, not chats, not being deleted, active in the last limits.maxAgeDays (30, as agents keep them), at most limits.candidates (20), newest first, and whose every task is of the same space and account as the new task (the web space a claim came from and whom it runs for there, related.space and related.runner; this computer's own tasks are a space of their own) and in its project, or all in none: sessions never cross spaces or projects.
  • Signals (Signals in score.ts), for each candidate against the new task:
    • link: the task names a task of the session: an @mention on its page, a link in its text or the run's instructions (ensemble://task/<id>, /tasks/<id>), or a dependency;
    • deliverable, meeting, parent page: the same one as a task of the session;
    • anchors: exact things both name (anchorsOf in text.ts): file paths (a file shared by its path is not counted again by its name), code names (rankResults, auth_session, SessionStore), issue and pull request numbers (#123, ABC-42), web addresses. The session's side includes the files its runs changed. An anchor in more than limits.anchorMaxDocShare (20%) of the person's tasks, shared with more than half of the candidates, or a file every repository has (package.json, README.md, index.ts…) does not count;
    • topic (0–1): the cosine of TF-IDF vectors. Words are lower-cased, common English words and text.extraStopwords dropped, reduced to their stem by Porter's algorithm (ranking, ranked, ranks → rank), and each two neighbours also count as a phrase (1.5×). A term weighs (1 + ln count) × ln((N+1)/(df+1)) over the person's last limits.corpusTasks (2,000) tasks on this computer, so words in nearly every task weigh about nothing. The title counts 3×, labels 2×, the page, description, run reports and instructions 1×. It counts only with at least limits.topicMinShared (2) shared terms that are not common, and proportionally less while the person has fewer than limits.minCorpus (50) tasks;
    • follow-up or new work: the new task's title (text.followUp, text.newWork patterns) or labels (text.followUpLabels);
    • warm: the session was active in the last warmMinutes (60), so the provider's prompt cache is likely still there. Not a cut-off: a session days old is continued as readily, at the cost of the agent reading its conversation once uncached;
    • fill: how much of its context window the session uses, from its agent's last report (engine_context used/size: Claude Code's and Codex's last request, Copilot's and the ACP agents' own figure), and tasks in it.
  • Score (scoreCandidate): the sum of weight × signal. Defaults: link 10, deliverable 3, meeting 3, parent page 2, 3 per anchor up to 6, topic × 6, follow-up +1, new work −1 (only when no link or anchor ties it), warm +1, filling more than size.soft (40%) −2, unknown fill and more than size.longChainTasks (8) tasks −2. More than size.hard (70%) full, or unknown fill and more than size.hardChainTasks (15) tasks, is too full to continue: each more task re-sends the whole conversation on every request, cached or not, and near its window the agent compacts it and loses detail.
  • Bands (decide): the best score (then the most recently active) at or above bands.confident (6) is continued, silently: the run's card on the task page says which session and why ("Continued the agent's session: it mentions “Build the ranking API”"). When the best confident ones are too full, a new session gets the best one's last report in its instructions instead. At or above bands.unsure (3), and below 3, a new session, recorded with its band so near misses can be studied. Nothing asks the person: Assign → More options → Always start a new agent session (Assign.newSession, the assistant's newSession) skips it for a run, and Settings → Agents → Agent sessions → Continue a fitting agent session (orchestration.linkedSessions) turns it off.
  • Cost. Bounded by the limits above: the rarity table is built from titles, labels and the first part of descriptions and pages, kept per account for limits.corpusTtlMinutes (60), and word matching stops at limits.timeBudgetMs (3 s), after which links and structure decide alone. A decision with 20 candidates takes tens to a few hundred milliseconds.
  • Tuning (config.ts, SessionChoiceConfig). Every number and word list above is one versioned config with its defaults in code. On the web server, ENSEMBLE_SESSION_CHOICE_CONFIG names a JSON file with only the fields to change (for example {"version": "2026-12-01.1", "weights": {"topic": 8}, "bands": {"confident": 7}}); it is checked, read again within a minute of a change, and sent with every claim, so computers decide with it from their next run without a release. An invalid file is logged and the defaults apply. A local-only computer reads the same variable on itself. Change version with every change.
  • Records (SessionChoice, table session_choices). Every decision: the run, task, agent, computer and project, decision (continue or new), band, the session continued or the best candidate, its score and reason, note (new-session: asked for; too-full; unavailable: the session could not take the run; not-linked: the web refused the session the computer named), configVersion and configHash, and every candidate's signals, parts and score, best first. When the run ends, outcome: its status, duration, tokens, and for a continued session whether the agent really resumed it (the agent's session id stayed the same). A computer keeps the record and sends it to the web in the web's ids (POST /api/devices/self/jobs/:id/session-choice, under the job's lease), which links the run to the session it continued (only one of the same account and agent on that computer). Kept for the history window (historyRetentionDays). To calibrate, read the rows (bands near the thresholds, note = 'new-session', outcome->>'resumed') and score their stored signals again under other weights with scoreCandidate: no text is needed.

5. Assistant chats on an AI plan

One chat, one session (subscriptions/turn-sessions.ts). A chat (its conversation id, the spec's session.key) is one session of the agent on the computer that runs it; a new chat is a new session. The computer keeps <state>/assistant-sessions.json (chat → agent, session id, the instructions the session was given, its folder and how many days it is kept) and a folder per chat, <state>/assistant/<key>, for the turn's files, instructions and MCP config. A turn resumes the chat's session when the agent is the same and the chat is where that session left it (session.position, the chat's messages before this one that a session took part in, matches what the session's last turn left; a turn on another model or computer moves it on); it sends only the message. A message that got no answer (its computer was offline, the turn failed before replying) and a failed exchange (the message and its error) do not count (sessionPosition in assistant/agent.ts), so a routine whose run failed while its computer slept goes on in the same session. For an agent that keeps the instructions from a session's first message (Copilot, Cursor's agent, the ACP agents), changed instructions do not start a new session, since a node's instructions change on most messages (the skills matched to the message, a new memory, the room). The message is led by what changed instead (instructionsUpdate: "Your instructions changed since this conversation began", the lines now part of them and the lines no longer part of them). When more than half of the lines changed, or after 80 messages in one session (ROTATE_MESSAGES), it starts a new session with the chat so far. A session replaced in the store (a new one for the same chat) is deleted from the agent at once, with its folder when the folder is the chat's own.

A group of chats, one project folder. A turn may name a group (session.group {id, name}, with session.keepDays): its agent then works in the group's project folder (projectFolder in workspace/agent-projects.ts, <workspace>/projects/<name>), so the agent's own app lists the group's sessions together, as it groups sessions by folder; the turn's own files stay in the chat's folder. An Ensemble Node's turns use the node as the group ("<Name> (Ensemble Node)") and keep a session 7 days unused (30 §10.2). Turns of one chat run one at a time; otherwise it starts one, with the chat so far (session.history, the last 12 messages) before the message. A session the agent cannot reopen, or an agent too old to keep one, falls back to a new session or none, with the chat so far. Sessions unused for 30 days (or the chat's keepDays) are dropped and deleted from the agent. A computer before assistantTurns: 3 (ASSISTANT_TURN_SESSIONS) gets the chat so far in front of every message; a one-off completion (complete on a subscription: triage, a summary, a title) keeps no session, and leaves none in the agent: Claude Code runs it with --no-session-persistence and Codex in an ephemeral thread, while the session Copilot (named with --session-id from 1.0.78), Cursor's agent and the ACP agents save is deleted once the turn is over (dropOneOff), in the folder the turn ran in (Gemini CLI, OpenCode and Grok Build find a session by the folder it began in), which is removed after.

A session used outside Ensemble (subscriptions/session-touch.ts). The person can open an Ensemble chat's session in the agent's own app or terminal. After each turn the computer notes how many prompts the agent's own transcript of the session holds (prompts in the store: Claude Code's ~/.claude/projects/<folder>/<id>.jsonl user prompts, Copilot's user.message events in session-state/<id>/events.jsonl). When the count is higher before the next turn, someone sent it prompts of their own: the chat goes on in a new session with Ensemble's own history, so nothing said there enters the chat, and the touched session is kept unresumed (retired) and deleted with the chat or when its entry expires. Other agents, and a transcript the computer cannot read, are resumed without this check.

Ask Ensemble: one short session per page (assistant/ask-sessions.ts). An inline ask (@ensemble in a page, task, project, deliverable or meeting notes; POST /api/pages/:kind/:id/ensemble) runs on the tier in Settings → Assistant → Ask Ensemble model (assistant.askTier, Medium unless changed; the chat's default tier is separate). When that tier is an AI plan, each page has one session per person who asks there (session.key ask-<kind>-<hash of person, kind and id>, sent without position, so the computer resumes it however many asks came before): asking again continues it. Asks on one page by one person are answered one after another in the order they were made (askInLine, taken when the request arrives; the waiting ask shows "Waiting for the answer before this one…"), never two at once and never one ahead of an earlier one. When the page's session is gone (deleted in the agent's app), the new one is given the page's earlier asks and answers of that session (askHistory, at most 8). Thirty minutes after the last answer (fixed, not a setting) the session is deleted from the person's computers the way a deleted chat's is (§6): the account's pending ask sessions are kept in the preference assistant.ask-sessions and the scheduler's minute tick sweeps them, so a restart does not lose one; an ask that never reported back is cleared two hours after it began. The session is sent with keepDays: 1, so a computer that cannot delete on request yet drops it itself a day later. An ask on a surface (POST /api/ensemble/invoke: Today, a board, Needs me, Code; on the chat's default tier) follows the same rules, with one session per surface and the records it is about (anchorKey).

6. Deleting and cleaning up

Deleting a session (deleteAgentSession in workspace/delete-session.ts). The person deletes a session from Ensemble Node's Sessions page, and a node asks for it with an owner-only confirmation card (35 §8.9). Every computer that ran the session must be online and report sessionDelete: 1 (26); otherwise nothing is hidden and the person is told which computer to connect or update. Under the session's lock its queued runs are cancelled, then each computer deletes the agent's own saved history and Ensemble marks the session deleted. Ensemble's run records, project folders and files stay. A run that continues a deleted session's runs starts a session of its own (§1).

Cleaning up sessions (workspace/session-cleanup.ts). The scheduler sweeps each account every 15 minutes (orchestration.cleanSessions, on by default): a session goes from its agent (deleteAgentSession, ledgered as workspace.session.delete by system with reason: "retired") once every task in it is retired: deleted at least 10 minutes ago (so Undo still finds it), or done or dropped longer than the completed window and not pinned. A session with a run still queued or going waits for the next sweep; one with a task still in use is looked at again after 6 hours, and one on a computer that is offline or too old to delete sessions after an hour (context.sessionCleanup.nextAt on its runs), so it never holds up the sessions behind it. A failed deletion is tried again a day later. Each sweep reads a session once, however many of its runs match, looks at no more than 60 and deletes at most 20. Just before anything changes, under the session's lock, the deletion checks again that the session has the same runs, none of them going, and every task still retired (guard of deleteAgentSession); otherwise it leaves the session alone. Ensemble's run records, project folders and files stay. A computer deletes the sessions of its own local tasks itself and leaves those of runs claimed from the web to the web. An assistant chat's agent session that has gone unused for 30 days is deleted from the agent too, with its folder (subscriptions/turn-sessions.ts). A coding session an Ensemble Node started for itself (its task's sourceRef is node:<id>, which stays open) goes once none of its runs started or finished in 7 days (NODE_SESSION_IDLE_DAYS, ENSEMBLE_NODE_SESSION_IDLE_DAYS), with the same lock and checks; a person's own open task still keeps its session.

Deleting a chat deletes its session (assistant/delete-chat.ts, remote/assistant-session-delete.ts). DELETE /api/assistant/conversations/:id stops a reply still running, deletes the chat and its messages, then asks each of the person's computers that runs assistant turns to drop that chat's session: dropSession removes the store entry, deletes the session in the agent itself (deleteNativeSession, the delete used when a chat's session is replaced, for every subscription agent) and the chat's own folder, after any turn of that chat still running there has ended. A computer that is away, or does not confirm, is asked again when its event stream next opens, and while it is online every 5 minutes from the scheduler's minute tick (retryAssistantSessionDeletes); pending requests are kept per computer in the assistant.session-deletes preference for 30 days (the time after which a computer drops an unused session itself), and those of a computer no longer paired are dropped. On the desktop app or CLI runtime the session is dropped directly. Deleting a routine deletes its conversation the same way (dropRoutineChat in routines/service.ts), from the Routines page or the assistant; a routine's turns keep their session unused for twice the gap to the routine's next run, 30 to 90 days (routineKeepDays), so a weekly or monthly routine stays in one session.

Deleting a project. A project's folder and its agents' sessions stay; its Codex project in the ChatGPT app is removed once the project has been in Trash 10 minutes (§3, 25 §4.9).