Persistence is lazy: getSessionFile() returns an allocated path from the
start, but the JSONL is only materialized once an assistant message (or an
explicit ensureOnDisk()) crosses the persistence gate. The shutdown banner
printed the resume hint based solely on the path, so any session that ended
before the first assistant message advertised a copy-pasteable command that
always fails with Session not found.
Gate the hint on the new SessionManager#isSessionOnDisk() (file exists in
the active storage backend) and add unit tests.
Fixes#8860
Rolled partial file appends back to their pre-write size and marked malformed resumed sessions for an atomic rewrite.
Retried transient persistence failures from in-memory state and surfaced the first failure in the interactive TUI.
Fixes#8596
Reparent service-tier metadata before removing discarded empty turns. Persist a branch marker for content-bearing children so reload never selects the discarded subtree.
Fixes#5179
Empty assistant stops were reparented off the active leaf in memory only. The session loader rebuilds the active branch from the last physical journal entry, so a capped empty stop (or one killed mid-retry) resurfaced as the active leaf on reload. Added SessionManager.dropLeafEntry to physically remove the entry and rewrite the file, and route every empty-stop discard through it.
Fixes#5179
- Replaced blanket subagent advisor global settings with fine-grained per-agent configuration and frontmatter support.
- Added dashboard keybindings and inline override editors for managing agent advisor patterns.
- Implemented settings migration logic to convert legacy global options into per-agent settings.
- Updated session persistence and execution layers to restore and enforce per-agent advisor behaviors.
Why:
SessionManager.open() parses the complete journal for its header and then
setSessionFile() parses the same journal again.
Changes:
- Reuse the entries already loaded by open() through a private setup path.
- Keep the public setSessionFile() contract unchanged.
Evidence:
- A 19.98 MB, 12,000-entry journal improved from 48.011 ms to
25.406 ms median across 15 runs with identical restored state.
Refs #8117
- seal() now runs before the final dispose close() and bumps the disk
epoch: work an event handler enqueues while dispose awaits the closing
tail is superseded, and an already-running fenced or authoritative
rewrite fails its commit guard at the rename fence instead of
publishing over a file a revival reopened.
- The authoritative repair path resets the disk tail itself, escaping the
close() serialization, and would atomically publish the emptied entry
list; it now no-ops once sealed and commit guards also check the seal.
- Execution-time gates cover the queued title persist and fenced rewrite
callbacks; setSessionName/appendCustomEntry attempted by a handler that
outlives dispose are dropped and covered by the seal regression, and a
failed atomic batch across the seal can no longer truncate the file.
- AgentLifecycleManager.park() resolves as soon as dispose() returns, so
ensureLive() may reopen the same JSONL through a new manager while a
timed-out event handler still holds the old one; a late append reopened
a second writer on that file and the deferred finalize then closed it.
- A post-release rewrite was worse: it persisted the emptied entry list,
truncating the transcript on disk.
- releaseRetainedEntries() now seals the manager: appends, title changes,
and rewrites become dropped no-ops and the append writer is closed, so
the deferred dispose pass is in-memory only and can never touch the file.
- File-backed regression: dispose on the drain deadline, revive the JSONL
immediately, unpark the late handler, and prove the file is byte-stable
and the revival writer owns it exclusively.
Keep-alive subagents are handed to AgentLifecycleManager.adopt, which stores
their reviver closure in the process-global #adopted map. The closure is
defined inside runSubagent's scope, which also captures the live AgentSession
(extension-runner callbacks, buildSubagentSessionOptions), so its lexical
environment pins the whole session graph. park() disposes and detaches the
session but leaves the adoption record indefinitely, and #doDispose never
dropped the in-memory transcript, session-manager entries, or the raw-SSE
debug buffer (whose trimmed records retain slice() views of full wire frames),
so every completed subagent's heavy state leaked for the process lifetime.
dispose() is terminal and every revival path reopens from disk, so #doDispose
now sheds retained conversation memory via agent.reset(),
RawSseDebugBuffer.clear(), and SessionManager.releaseRetainedEntries(). The
adoption record can still reference the session, but only as a husk.
Fixes#8003
Completed transcript entries now write through to the OS page cache on
append instead of microtask-batching, supersede in-flight atomic rewrites
with a synchronous full-body publish, and land on the live moveTo path
(source pre-rename, destination post-rename) so a software crash no longer
drops finished user/assistant/tool events. Streaming text remains durable
only at message_end; no fsync/power-loss guarantee is claimed.
The move fence intentionally defers a flushSync landing in the rename window; full synchronous durability there is incompatible with orphan-avoidance for a rename-based, Windows-safe move. Document the caveat honestly instead of implying parity with the in-place rewrite path.
Refs #7270
The move fence bumped #diskEpoch, which no-oped any disk task already queued at the prior epoch (e.g. a header-only ensureOnDisk materializing rewrite), losing explicitly materialized ACP/draft sessions. Gate the append hot path on #sessionFileRelocating instead of a fresh epoch, so prior disk work still drains.
Fixes#7270
A fenced append followed by a Ctrl+C flushSync in the post-rename, pre-repoint window rewrote the full body to the old path, recreating the orphan. Sync rewrites now defer while the session file is relocating.
Fixes#7270
- Implemented session stores and metadata converters to import Claude and Codex sessions into OMP.
- Added `--from-claude` and `--from-codex` CLI flags and `/resume` command arguments for foreign session resolution.
- Updated session selector components and controllers to support listing and picking external agent sessions.
- Added comprehensive unit tests and documentation covering foreign session import functionality.
Kept superseded summaries and preserveData durable while deriving forward transcript elision from the active compaction.
Added branch and rewind coverage for snapcompact archives and OpenAI remote replacement history.
Fixes#4090
Seeding additionalDirectories at launch (via --add-dir or the
workspace.additionalDirectories setting) called #rewriteAtomically on a
brand-new session manager, which materialized a header-only JSONL and a
fresh breadcrumb before any assistant output. Launching and exiting with
configured roots therefore created an empty resumable session that
--continue picked over the previous conversation.
Gated all three workspace-directory mutators behind the existing
#shouldHaveSessionFile() lazy-persistence gate (one shared helper), and
made setAdditionalDirectories a no-op when the normalized list is
unchanged so resuming large sessions no longer rewrites the whole JSONL
on every startup. Roots set before the gate is crossed land in the
header with the first durable write; added a regression test.
Relative paths in workspace.additionalDirectories settings (e.g.
'../shared') were stored raw in the new-session header instead of
being expanded to absolute. Now all new-session roots go through the
same normalizer used at startup.
Co-authored-by: oh-my-pi <https://omp.sh>
- Add additionalDirectories to NewSessionOptions
- #resetToNewSession now seeds #additionalDirectories from options,
so all new-session transitions (handoff, branch, /new) get the roots
- AgentSession.newSession passes settings dirs via options instead of
calling setAdditionalDirectories after the fact
Co-authored-by: oh-my-pi <https://omp.sh>
- createBranchedSession (/branch, /btw) now copies additionalDirectories
to the new header so branches preserve multi-root state
- forkFrom filters source additionalDirectories against the target cwd
so the new cwd is never also listed as an additional root
Co-authored-by: oh-my-pi <https://omp.sh>
- Filter #additionalDirectories when moveTo changes cwd so the new cwd
is never also listed as an additional root (session-manager.ts)
- Build workspace tree for each additional root to discover nested
AGENTS.md files under added roots, merging agentsMdFiles into the
primary set (system-prompt.ts)
Co-authored-by: oh-my-pi <https://omp.sh>
restoreState() now syncs #additionalDirectories from the captured
snapshot header, so a failed switchSession (e.g. from a session_switch
hook or model-restore error) doesn't leave the original session with
the target's workspace roots.
Co-authored-by: oh-my-pi <https://omp.sh>
- Always augment context files with additional root context, even when
createAgentSession passes preloaded contextFiles (system-prompt.ts)
- Merge configured dirs with existing restored roots on resume instead
of replacing them (sdk.ts)
- Copy additionalDirectories from source header in forkFrom so forks
preserve the multi-root set (session-manager.ts)
- Add test for forkFrom preserving additionalDirectories
Co-authored-by: oh-my-pi <https://omp.sh>
- Seed workspace.additionalDirectories settings on initial launch session,
not just /new (sdk.ts)
- Always call setAdditionalDirectories on /new, even with empty list, to
clear stale roots from the previous session (agent-session.ts)
- Make setAdditionalDirectories async and trigger atomic rewrite when a
session file already exists, so --continue --add-dir persists (session-manager.ts)
- Route addWorkspaceDirectory/removeWorkspaceDirectory through
normalizeWorkspaceDirectory for consistent ~ expansion (session-manager.ts)
- Drop dead exports: workspaceRootForPath (no production callers),
getWorkspace (no production callers), and unused SessionWorkspace type
import from session-manager.ts (session-workspace.ts, session-manager.ts)
- Remove unnecessary as SettingPath / as string[] casts (agent-session.ts)
- Update tests: add ~ expansion coverage, root-clearing on /new,
persistence on resumed sessions, fix header line parsing
Co-authored-by: oh-my-pi <https://omp.sh>
A session now carries an ordered list of workspace directories beyond cwd,
managed live from the terminal. New /add-dir, /remove-dir, and /dirs slash
commands let you add and remove folders mid-session; the repeatable --add-dir
CLI flag seeds them at launch, and the workspace.additionalDirectories
setting persists defaults per project. Additional roots are persisted in the
session header, survive reopen/fork/move, and are surfaced to the agent in the
system prompt so it knows they exist and can read/grep/glob them by absolute
path. Design aligns with the endorsed community implementation on
feature/session-workspace.
Co-authored-by: oh-my-pi <https://omp.sh>
- Added per-invocation task schemas with strict and permissive validation.
- Shared task and eval agent policy, artifacts, isolation, and lifecycle handling.
- Enabled host-restricted plan-mode eval agents and persisted their capability clamp.
Fixes#5279
Late user-initiated bash results and minimized-output artifacts were
recorded against whichever session or branch was active when execution
finished, not the one that started it. executeBash() awaited the result
then wrote through the mutable live sessionManager, while pending bash
messages carried no session/branch ownership and the minimized-output
callback used the live manager. A session change during execution
redirected transcript entries and artifacts to the replacement session
or branch, and a dropped session could be recreated by a straggler.
Capture a bash ownership target when execution starts and transition it
whenever the active session or transcript leaf changes. Late results and
minimized artifacts route to the originating session/branch (via a
detached clone when the file changed, or an off-leaf branch append when
the leaf moved), are discarded for an intentional drop, and ownership is
released on failed transitions. RPC dispatch concurrency and immediate
abort_bash behavior are unchanged.
Fixes#5743