AIC JSONL caches under .aic/
April 21, 2026 · View on GitHub
When to update this document
Update this document when:
- You add another project-root JSONL log under
.aic/that participates in the same append and prune story assession-models.jsonl,prompt-log.jsonl, orsession-log.jsonl. - You change append implementation (
appendJsonlinintegrations/shared/aic-dir.cjs) or retention and prune wiring for those logs, includingshared/src/maintenance/or scheduling inmcp/src/server.ts. - You change line shape, schema, or read semantics for those JSONL files.
- You introduce or redesign a shared JSONL façade across those logs; rewrite this document to match the new shape.
- Inventory and caller details for shared modules live in Integrations shared modules reference; update that document when those change.
Scope
This document describes how session-models.jsonl, prompt-log.jsonl, and session-log.jsonl under .aic/ are written, pruned, and read from integration hooks and MCP maintenance code. It records why the repository does not use one generic JsonlCache abstraction across all three files.
Marker-file layout under .aic/ for Claude SessionStart is documented in Session start lock and session context marker. Edited-files temp storage under os.tmpdir() is out of scope here.
Layout and wiring
| File | Append path | Read API in shared CJS | Prune |
|---|---|---|---|
.aic/session-models.jsonl | integrations/shared/session-model-cache.cjs calls appendJsonl | readSessionModelCache uses a bounded tail read with deterministic full-file fallback while preserving last-match selection | MCP startup via pruneJsonlByTimestamp with filename session-models.jsonl |
.aic/prompt-log.jsonl | integrations/shared/prompt-log.cjs | none (append-only from shared CJS) | MCP startup via prunePromptLog delegating to pruneJsonlByTimestamp |
.aic/session-log.jsonl | integrations/shared/session-log.cjs | none (append-only from shared CJS) | MCP startup via pruneSessionLog delegating to pruneJsonlByTimestamp |
Pruning is scheduled from mcp/src/server.ts inside createMcpServer using setImmediate alongside cache purge. Implementation lives under shared/src/maintenance/.
Shared mechanisms
- Append: All three files use
appendJsonlfromintegrations/shared/aic-dir.cjs, which creates.aic/at mode0o700and appends one JSON object per line. - Retention:
shared/src/maintenance/prune-jsonl-by-timestamp.tsremoves lines whosetimestampfield is older than twenty-four hours measured from the injectedClockat prune time. The sameRETENTION_MINUTESvalue applies to every filename passed into this helper. - Field validation: CJS writers use
integrations/shared/cache-field-validators.cjs. TypeScript maintenance usesshared/src/maintenance/cache-field-validators.tswith aligned rules, summarized in Integrations shared modules reference.
Schema comparison
| JSONL file | Line shape | timestamp for retention |
|---|---|---|
.aic/session-models.jsonl | Compact keys: c (conversation id), m (model id), e (editor id), plus timestamp (ISO string from the caller or from writeSessionModelCache when the caller omits it). | Required on each kept line; parsed by pruneJsonlByTimestamp. |
.aic/prompt-log.jsonl | Discriminated type: prompt or session_end. Prompt lines: editorId, conversationId, generationId, title, model, timestamp. Session-end lines: editorId, conversationId, reason, timestamp. | Same. |
.aic/session-log.jsonl | session_id, reason, duration_ms, timestamp; printable ASCII enforced on session_id before append. | Same. |
All three schemas expose a timestamp string that pruneJsonlByTimestamp can parse for retention.
Read paths versus append-only writers
Only session-model-cache.cjs implements integration-layer reads. On the hot path it reads only a bounded tail of .aic/session-models.jsonl (currently up to 262,144 bytes, matching SESSION_MODEL_JSONL_MAX_TAIL_BYTES in shared/src/maintenance/read-session-model-jsonl.ts), then applies the same reducer as a full-file read. When the tail omits the winning line (a conversation-specific match earlier in the file, or a per-editor line when conversationId is empty), it falls back to reading the entire file so results stay identical to a full scan. Prompt and session logs are write-only from integrations/shared/; historical analysis reads the JSONL files on disk outside these modules.
Why there is no single JsonlCache type
Append uses fs.appendFileSync in UTF-8 text mode with one JSON object serialized per line; age-based pruning is shared through pruneJsonlByTimestamp. A thin wrapper around appendJsonl alone would touch few call sites and mostly adds indirection.
Schemas differ: One shared class with a single record shape would need wide unions or would drop the per-file validation that runs before append.
Read semantics differ: Session models use last-match selection (and conversation-specific preference) shared with MCP via select-session-model-from-jsonl / read-session-model-jsonl, while prompt and session logs have no shared read helper in shared CJS. A generic cache focused on append and prune still needs that dedicated reader path; uniform query APIs would misrepresent prompt and session logs.
Runtime split: Hooks run CommonJS without the MCP Clock. Pruning uses Clock in TypeScript maintenance. One abstraction spanning both sides would blur the boundary between editor integration scripts and the MCP composition root unless split into thin adapters on each side.
The shared core remains appendJsonl and pruneJsonlByTimestamp. Small duplicated helpers, if they appear, should stay line-parse or timestamp-check utilities without a single domain type for all three logs.