Problem
On every agent-list refresh (every 3 s in ai-devkit agent console, and on each ai-devkit agent list), the session parsers read and JSON.parse the entire transcript of every live agent. Nothing is cached, even when the file hasn't changed.
ClaudeSessionParser.readSession (packages/agent-manager/src/providers/claude/ClaudeSessionParser.ts) reads the whole JSONL file. It only needs the first entry's timestamp, the last conversation entry type, the last cwd, the interrupted flag, and the first/last user message.
CodexSessionParser.readSession (packages/agent-manager/src/providers/codex/CodexSessionParser.ts) does the same. It is called from CodexAdapter.mapSessionMappingMatches, mapRegistryCache, mapDirectMatches and the legacy matches.
Cost grows linearly with total live transcript size: CPU and memory scale with history size, not with what changed.
Evidence
Measured on a machine with 28 live agents, using the 0.65.0 build:
| Measurement |
Value |
| Transcript bytes read per refresh |
147 MB across 47 files |
Share of that from Codex readSession |
72 MB (+ 8 MB via mapRegistryCache) |
Share of that from Claude readSession |
28 MB |
| CPU per refresh |
~350 ms |
| Event loop blocked per refresh |
~630 ms (synchronous reads) |
| Parser cost |
2.2–2.7 ms per MB |
| Peak RSS |
~4× bytes parsed; a 400 MB transcript peaks at 1.59 GB RSS |
A headless loop running listAgents every 3 s used 11.7% CPU and ~400 MB RSS. With the Claude, Codex and Antigravity adapters removed, the same loop used 0.5% CPU and 57 MB. That isolates transcript parsing as the dominant cost.
Proposed approach
- Add one shared utility in
packages/agent-manager/src/utils/ (e.g. IncrementalJsonlSummary), not a separate cache per adapter.
- Key it by file path and store
{dev, ino, size, mtimeMs, offset, pendingPartialLine, state}.
- Each parser supplies a pure reducer,
reduce(state, entry) → state, plus an initial state.
- Read rules:
dev, ino, size and mtimeMs all unchanged → return the cached state (0 bytes read).
- Same inode and size grew → read only
[offset, size), reduce complete lines, and keep any trailing partial line for the next read.
- Size shrank or inode changed → reset and rebuild.
- Keep the cache on the adapter or parser instance;
AgentManager keeps adapter instances alive across refreshes.
- Evict entries for paths not referenced in the latest refresh.
- Migrate the Claude and Codex adapters in this issue. The other adapters adopt the utility in follow-up issues.
Acceptance criteria
Out of scope
Problem
On every agent-list refresh (every 3 s in
ai-devkit agent console, and on eachai-devkit agent list), the session parsers read andJSON.parsethe entire transcript of every live agent. Nothing is cached, even when the file hasn't changed.ClaudeSessionParser.readSession(packages/agent-manager/src/providers/claude/ClaudeSessionParser.ts) reads the whole JSONL file. It only needs the first entry's timestamp, the last conversation entry type, the last cwd, the interrupted flag, and the first/last user message.CodexSessionParser.readSession(packages/agent-manager/src/providers/codex/CodexSessionParser.ts) does the same. It is called fromCodexAdapter.mapSessionMappingMatches,mapRegistryCache,mapDirectMatchesand the legacy matches.Cost grows linearly with total live transcript size: CPU and memory scale with history size, not with what changed.
Evidence
Measured on a machine with 28 live agents, using the
0.65.0build:readSessionmapRegistryCache)readSessionA headless loop running
listAgentsevery 3 s used 11.7% CPU and ~400 MB RSS. With the Claude, Codex and Antigravity adapters removed, the same loop used 0.5% CPU and 57 MB. That isolates transcript parsing as the dominant cost.Proposed approach
packages/agent-manager/src/utils/(e.g.IncrementalJsonlSummary), not a separate cache per adapter.{dev, ino, size, mtimeMs, offset, pendingPartialLine, state}.reduce(state, entry) → state, plus an initial state.dev,ino,sizeandmtimeMsall unchanged → return the cached state (0 bytes read).[offset, size), reduce complete lines, and keep any trailing partial line for the next read.AgentManagerkeeps adapter instances alive across refreshes.Acceptance criteria
agent-manager/src/utils, with unit tests.listAgents()reads 0 bytes of transcript content. A test verifies this by instrumentingfs.readSessionresult (property/fixture test).readFileSyncin their refresh path.Out of scope