notes

agent-log manual

First published: Last updated: 5175 words · 26 lines of code

Overview

AI coding agents such as Claude Code and Codex store complete conversation transcripts as JSONL files. While these files contain a full record of every session, they are not human-readable: each line is a JSON object with nested content arrays, tool-use blocks, thinking sections, and system metadata. agent-log.el renders these transcripts into readable Markdown files and provides a dedicated Emacs interface for browsing, searching, and navigating them.

The package supports multiple backends through a generic interface based on cl-defstruct and cl-defmethod. Each backend knows how to discover sessions, parse its tool’s JSONL format, and normalize entries into a canonical representation that the rendering pipeline, session browser, and AI features operate on. Currently supported backends are Claude Code (agent-log-claude.el) and Codex (agent-log-codex.el). By default, the package auto-detects which tools are installed and activates the corresponding backends (Backend configuration).

The package maintains a mirror directory of pre-rendered Markdown files, organized by project. This means that standard tools such as consult-ripgrep, Dired, and grep work natively on readable content without any special integration.

The core capabilities of agent-log.el are:

  • Session browsing. A completing-read interface lists all sessions, grouped by project or as a flat list, sorted by recency. Each candidate shows a backend-specific icon (SVG in GUI frames, a short text label in terminal frames), the date, project name, and either an AI-generated summary or the first user message (Session browsing).
  • Lazy rendering. Sessions are rendered to .md files on first access. The package tracks JSONL file sizes in an index to avoid re-rendering files that have not changed (Sync and rendering).
  • Live updates. For active sessions, a file-notify watcher detects JSONL changes and appends new entries to both the .md file on disk and the open buffer in real time (Live updates).
  • Outline navigation. Rendered buffers use outline-minor-mode for folding tool calls, thinking blocks, and tool results. Dedicated keybindings jump between conversation turns (Navigation).
  • Smart tool summaries. Each tool call is summarized by its most relevant input—the file path for Read/Write, the command for Bash, the pattern for Grep, and so on.
  • AI-powered session summaries. An optional integration with gptel generates concise one-line and paragraph summaries for each session, displayed in the browser and at the top of rendered buffers (Summary generation).
  • AI-powered search. A two-stage LLM pipeline lets you search your session history with natural language queries. A scope-narrowing model filters sessions by project and date range, then a selection model identifies the most relevant sessions and presents a narrative with clickable links (AI search).
  • Session resumption. You can resume any session directly in the coding agent from within the rendered buffer, provided the corresponding Emacs integration package is installed (e.g. claude-code for Claude Code) (Session resumption).

The package requires Emacs 29.1 or later and markdown-mode 2.6 or later. The optional summary feature requires gptel. Session resumption for Claude Code sessions requires claude-code; for Codex sessions it requires codex.

The development repository is on GitHub.

Installation

Manual installation

Clone the repository to a directory on your Emacs load path and add the following to your init file:

(require 'agent-log)

Installation with use-package

If you use use-package, add one of the following snippets to your init file:

;; with vc
(use-package agent-log
  :vc (:url "https://github.com/benthamite/agent-log"))

;; with elpaca
(use-package agent-log
  :ensure (:host github :repo "benthamite/agent-log"))

;; with straight
(use-package agent-log
  :straight (:host github :repo "benthamite/agent-log"))

;; with quelpa
(use-package agent-log
  :quelpa (agent-log :fetcher github :repo "benthamite/agent-log"))

User options

All user options belong to the agent-log customization group. You can browse them interactively with M-x customize-group RET agent-log RET.

Backend configuration

The user option agent-log-backends is an alist mapping backend key symbols to their feature (file) names. Each entry is a cons cell (KEY . FEATURE) where KEY is a symbol identifying the backend (e.g. claude-code, codex) and FEATURE is the symbol passed to require to load the backend file. Backend files are loaded lazily on first use. The default value includes entries for Claude Code and Codex:

'((claude-code . agent-log-claude)
  (codex . agent-log-codex))

You would modify this to add a custom backend or to remove a backend you do not want available.

The user option agent-log-active-backends controls which backends are scanned for sessions. It accepts either the symbol auto or a list of backend key symbols. When set to auto (the default), the package checks whether each backend’s root directory exists on disk (e.g. ~/.claude for Claude Code, ~/.codex for Codex) and activates only the backends whose directories are found. Set it to an explicit list such as (claude-code) if you want to restrict the session browser to a specific backend regardless of what is installed.

Directory configuration

The user option agent-log-directory specifies the root directory of the Claude Code backend’s configuration. The package reads history.jsonl in this directory and looks for session files under the projects/ subdirectory. The default value is ~/.claude. You would change this only if you have configured Claude Code to use a non-standard location. Each backend has its own directory configuration.

The user option agent-log-rendered-directory specifies the directory where rendered Markdown files are stored. Each project gets its own subdirectory, and each session is rendered as a dated .md file within it (Sync and rendering). The default value is ~/.claude/rendered. You might change this if you want rendered files to live outside the default configuration directory, for example on a path indexed by a desktop search tool.

Display of thinking and tool sections

Agent conversations contain thinking blocks (the model’s chain-of-thought reasoning) and tool-use sections (file reads, bash commands, grep searches, and so on). These are valuable for understanding what the assistant did, but they can also be verbose and clutter the view when you are reading the high-level conversation.

Note that some backends store thinking blocks with empty content. Claude Code, for instance, redacts the thinking text from its JSONL logs, keeping only a cryptographic signature. When the thinking text is empty, the package omits the section entirely rather than rendering an empty heading.

The user option agent-log-show-thinking controls how thinking blocks appear. It accepts one of three symbols:

  • hidden: thinking blocks are omitted entirely from the rendered output.
  • collapsed: thinking blocks are rendered but folded under a heading, so you see the heading but not the content until you expand it.
  • visible: thinking blocks are rendered fully expanded.

The default value is collapsed.

The user option agent-log-show-tools works identically but controls tool-use and tool-result sections. It also defaults to collapsed.

When both options are set to hidden, assistant turns that contain only tool calls and thinking (no visible text) are hidden entirely, including their heading and separator.

You can toggle individual sections interactively using agent-log-toggle-section (Section visibility).

Timestamps

The user option agent-log-timestamp-format controls the format string used when displaying timestamps in rendered output. It follows the conventions of format-time-string. The default value is "%Y-%m-%d %H:%M:%S".

Tool output limits

Tool calls can have very long inputs (entire file contents for Write, lengthy commands for Bash) and even longer results. Two user options control how much of this content is shown in the rendered output.

The user option agent-log-max-tool-input-length sets the maximum number of characters displayed for tool input summaries. Text beyond this limit is truncated with an ellipsis. The default value is 200.

The user option agent-log-max-tool-result-length sets the maximum number of characters displayed for tool result content. The default value is 500.

Increase these values if you find that truncated tool content is hiding information you need. Decrease them if rendered files are too large or buffers are slow to navigate.

Session browser options

The user option agent-log-group-by-project controls whether agent-log-browse-sessions groups sessions by project (Session browsing). When non-nil (the default), the command first prompts for a project, then for a session within that project. When nil, all sessions are presented in a single flat list. The grouped view is more convenient when you have many sessions across several projects; the flat view is useful for quickly finding a recent session regardless of project.

The user option agent-log-slug-max-length sets the maximum length of the slug portion of rendered filenames. The slug is derived from the first user message and appears after the date in the filename (e.g., 2026-02-22_11-56_redesign-agent-log-package.md). The default value is 50.

Live updates

The user option agent-log-live-update controls whether the package watches the source JSONL file for changes when a rendered buffer is open. When non-nil (the default), a file-notify watcher detects new entries appended to the JSONL file and renders them into both the open buffer and the .md file on disk. This means you can have a rendered log open alongside an active coding agent session and see new turns appear as they happen.

Set this to nil if you experience performance issues with file watching, or if you are on a system where file-notify is not reliable (e.g., certain remote filesystems via TRAMP).

Summary generation

The summary feature uses gptel to generate concise descriptions of each session. Three user options control how summaries are generated.

The user option agent-log-summary-backend specifies the gptel backend name to use for summary requests, for example "Gemini" or "Claude". When nil (the default), the backend is inferred from agent-log-summary-model if set, otherwise it falls back to gptel-backend.

The user option agent-log-summary-model specifies the gptel model to use, for example claude-haiku-4-5-20251001. A smaller, faster model is often sufficient for summarization. When nil (the default), it uses gptel-model.

The user option agent-log-summary-max-content-length sets the maximum number of characters of conversation text sent to the LLM for summarization. The package extracts the plain text of user and assistant messages (ignoring tool calls and thinking blocks) and truncates the result to this limit. The default value is 8000. Increase this for longer, more detailed summaries at the cost of higher token usage; decrease it if you want to minimize API costs.

AI search options

The AI search feature (AI search) uses two LLM calls: a scope-narrowing stage and a selection stage. Each stage has its own backend and model options, allowing you to use a cheap, fast model for scope narrowing and a more capable model for the selection narrative.

The user option agent-log-search-scope-backend specifies the gptel backend for stage 1 (scope narrowing), for example "Gemini" or "Claude". When nil (the default), the backend is inferred from agent-log-search-scope-model if set, otherwise it falls back to gptel-backend.

The user option agent-log-search-scope-model specifies the model for stage 1. A small, fast model (e.g. claude-haiku-4-5-20251001) is recommended since the task is simple classification. When nil (the default), it uses gptel-model.

The user option agent-log-search-backend specifies the gptel backend for stage 2 (selection and narrative). When nil (the default), the backend is inferred from agent-log-search-model if set, otherwise it falls back to gptel-backend.

The user option agent-log-search-model specifies the model for stage 2. This model receives all filtered session summaries and must reason over them, so a more capable model may produce better results. When nil (the default), it uses gptel-model.

The user option agent-log-search-budget sets a per-request cost threshold as a (TYPE . LIMIT) cons cell, where TYPE is either tokens or dollars. Before stage 2 sends the filtered summaries to the LLM, the package estimates the prompt’s token count (heuristically, one token per four characters). If the estimate exceeds the limit, the user is asked to confirm with y-or-n-p. The default value is (tokens . 50000).

In dollars mode, cost estimation requires the gptel-plus package. If gptel-plus is not available, the budget is not enforced and a warning is displayed. Dollar estimates use the model’s input pricing from gptel-plus to convert the estimated token count to a dollar amount.

Commands

Session browsing

The command agent-log-browse-sessions is the primary entry point for exploring your session history. It reads history.jsonl, resolves each session to its JSONL file on disk, and presents the results in a completing-read prompt. Each candidate is formatted as:

[icon] 2026-02-22 14:06  my-project  "Fix the authentication bug in..."

Each candidate is prefixed with a backend-specific icon so you can tell at a glance which agent a session belongs to. In GUI frames the icon is an inline SVG image; in terminal frames it falls back to a short text label (e.g. CC for Claude Code, CX for Codex).

When agent-log-group-by-project is non-nil (the default), the command first prompts for a project, then for a session within that project (Session browser options). Sessions are sorted most-recent-first. If AI summaries have been generated for any sessions (Summary generation), the summary replaces the quoted first message in the candidate display.

Selecting a session lazily renders it to a .md file if needed, then opens the rendered buffer in agent-log-mode (agent-log-mode).

The command agent-log-open-latest opens the most recent session directly, without prompting. This is convenient when you want to review the last conversation you had with a coding agent.

The command agent-log-open-file opens a specific JSONL file by path. It prompts for a file path interactively. This is useful for automation, for opening session files you found via the filesystem, or when you know the exact file you want.

The command agent-log-open-session opens a session by its ID. It prompts for the session ID interactively.

The command agent-log-open-current-session opens the log for the coding agent session running in the current buffer. It dispatches on the current buffer’s backend: if the buffer is a Claude Code terminal, it reads the status file that Claude Code writes to /tmp/claude-code-status/ and uses the transcript_path field to locate the exact JSONL file for the session, which uniquely identifies the session even when multiple sessions (including branches created with /branch) share the same project directory; if the status file is unavailable, it falls back to the most recently modified JSONL file in the project’s session directory, or to searching history.jsonl. If the buffer is a Codex terminal, the command falls back to the most recent Codex session whose recorded working directory matches the buffer’s directory, since Codex does not publish a live session identifier. This lets you quickly jump from an active coding agent session to its rendered log.

Rendered directory

The command agent-log-open-directory opens the rendered Markdown directory in Dired. Since rendered files are plain Markdown organized by project, you can use standard Emacs tools to browse, search, and manage them. For example, you can run consult-ripgrep from this Dired buffer to search across all rendered sessions.

If the rendered directory does not yet exist, the command creates it.

Sync and rendering

By default, rendering is lazy: sessions are rendered to .md files on first access via agent-log-browse-sessions or agent-log-open-file. The package maintains an index file (_index.el) in the rendered directory that maps session IDs to rendered file paths and JSONL file sizes. When you access a session, the package compares the current JSONL file size against the indexed size; if they differ, the session is re-rendered.

The command agent-log-sync-sessions renders all unrendered or stale sessions in bulk. It processes sessions one at a time using run-with-timer to avoid blocking Emacs. A progress message reports how many sessions were rendered.

The command agent-log-refresh re-renders the session displayed in the current buffer from its JSONL source. This is useful if the rendered file appears stale or if you have changed display settings such as agent-log-show-thinking or agent-log-show-tools (Display of thinking and tool sections). The keybinding is g in agent-log-mode.

Navigation

The command agent-log-next-turn moves point to the next User or Assistant heading in the buffer. The command agent-log-previous-turn moves to the previous one. These commands search for ## User and ## Assistant headings, skipping over tool calls and thinking sections.

The keybindings are n and p in agent-log-mode, making it easy to step through a conversation turn by turn.

Section visibility

The command agent-log-toggle-section toggles the visibility of the section at point. When point is on a #### heading (tool call, tool result, or thinking block), it collapses or expands just that section. When point is on any other heading, it delegates to outline-toggle-children. The keybinding is TAB.

The command agent-log-collapse-all collapses all tool-use, tool-result, and thinking sections in the buffer at once. This gives you a clean view of just the user messages and assistant text responses. The keybinding is C.

The command agent-log-expand-all expands all sections, removing all section overlays and showing the full content. The keybinding is E.

Copying

The command agent-log-copy-turn copies the current turn to the kill ring. A “turn” is delimited by ## headings: from the current ## User or ## Assistant heading up to (but not including) the next one. This is useful for extracting a specific message from a conversation to paste elsewhere. The keybinding is w.

Summary generation

The command agent-log-summarize-sessions generates AI summaries for all sessions that do not yet have one. It requires the gptel package. The command processes sessions sequentially, sending each conversation’s text to the configured LLM (Summary generation options) and storing the response in the index. Summary requests disable gptel-use-tools so that globally configured tools (e.g. MCP servers) are not included in the request.

Each summary consists of two parts: a one-line summary (up to 80 characters) used in the session browser display, and a paragraph summary (3–5 sentences) shown at the top of rendered buffers. The LLM is instructed to return a JSON object with oneline and summary fields.

Progress is displayed in the echo area, showing which session is being summarized and the model in use. If you invoke agent-log-summarize-sessions while summarization is already running, it stops the current run instead. You can also stop explicitly with agent-log-stop-summarize-sessions.

Summaries are persisted in the index file, so they survive across Emacs sessions and do not need to be regenerated. When you open a session that has a summary, the paragraph summary is inserted as a blockquote below the session header.

Automatic sync and summarization on session end

Two user options control what happens automatically when a Claude Code session terminates. Both require the claude-code package and a Stop hook configured in Claude Code’s settings (see the claude-code documentation for hook setup).

When agent-log-auto-sync-sessions is non-nil, agent-log-sync-sessions runs automatically each time a session ends.

When agent-log-auto-summarize-sessions is non-nil, agent-log-summarize-sessions runs automatically each time a session ends. If auto-sync is also enabled, summarization runs after sync completes.

The hook handler (agent-log-claude--session-end-handler) is defined in agent-log-claude.el and is installed via eval-after-load when either defcustom is set before the backend is loaded.

Sessions with a live agent process are excluded from summarization by session ID. Each backend’s agent-log--active-session-ids method provides the IDs of currently running sessions. If summarization is already running when another session ends, the in-progress batch continues undisturbed.

Session rename

The user option agent-log-auto-rename-sessions controls whether sessions are renamed automatically when they receive an AI summary. When set to non-nil, each time agent-log-summarize-sessions successfully generates a summary for a session, the one-line summary is slugified and written as a custom-title entry in the session JSONL file. This makes the name immediately visible in Claude Code’s /resume picker without any manual step.

The default value is nil. Set it to t if you want session names to stay in sync with summaries automatically.

The command agent-log-rename-sessions renames all sessions that lack a custom title. This command is defined in agent-log-claude.el because custom-title entries are a Claude Code-specific JSONL convention. For each session that has a one-line summary in the index but no custom-title entry in its JSONL file, the command writes the summary directly as the custom title. With a prefix argument (C-u), it overwrites existing custom titles as well, which is useful after changing the title format.

This is a purely local, offline operation — no API calls are made. Sessions must be summarized first via agent-log-summarize-sessions (Summary generation). Sessions that already have a custom title (e.g. from Claude Code’s /rename command) are skipped.

After writing custom-title entries, the cached JSONL file sizes in the index are updated in a single bulk write so that agent-log-sync-sessions does not treat the modified files as stale and trigger unnecessary re-renders.

The command reports how many sessions were renamed, how many were skipped, and how many lacked a summary.

The command agent-log-search searches your session history using natural language. It prompts for a query string (e.g., “how did I implement the caching layer?” or “conversations about authentication in my web app last month”) and uses a two-stage LLM pipeline to find relevant sessions.

Stage 1: scope narrowing. The query, along with a summary of the archive’s metadata (project names with session counts, date range), is sent to a scope-narrowing model. This model returns structured JSON specifying which projects and date range to include. If the query does not mention a specific project or timeframe, all sessions are included. The model and backend for this stage are configured via agent-log-search-scope-model and agent-log-search-scope-backend (AI search options).

Stage 2: selection and narrative. The filtered session summaries (one-line and paragraph, from agent-log-summarize-sessions) are sent to a selection model along with the original query. If the number of matching sessions exceeds what fits within the token budget (agent-log-search-budget), only the most recent sessions are included. The prompt includes a human-readable description of the scope applied in stage 1 (projects, date range, how many sessions matched, and how many were actually sent). The model produces a Markdown narrative explaining which sessions are relevant and why, with inline links to specific sessions. If the scope is narrower than “all projects, all dates”, or if sessions were truncated for budget, the model notes this so you know some sessions may have been excluded. Session summaries are sanitized to valid UTF-8 before serialization so that non-ASCII content does not cause errors. The model and backend for this stage are configured via agent-log-search-model and agent-log-search-backend (AI search options).

The result is displayed in a *agent-log-search* buffer in agent-log-search-mode (agent-log-search-mode). Session references in the narrative are rendered as clickable links. Pressing RET or clicking on a link opens the corresponding rendered session log.

Before starting, the command checks for unsummarized sessions. If any exist, it warns the user that those sessions will be excluded and asks for confirmation. If no sessions have summaries at all, the command aborts with a message to run agent-log-summarize-sessions first (Summary generation).

Before stage 2, the estimated token cost is compared against agent-log-search-budget (AI search options). If the estimate exceeds the threshold, the user is asked to confirm.

When gptel-plus is available, the combined dollar cost of both stages is displayed at the bottom of the results buffer.

Session resumption

The command agent-log-resume-session resumes the session displayed in the current buffer in the coding agent. The command extracts the session ID from the buffer (either from the buffer-local variable or from the front-matter comment) and dispatches to the buffer’s backend via the agent-log--resume-session generic function. For Claude Code sessions, this requires the claude-code package and passes the session ID with the --resume flag, starting in the session’s project directory when possible. The project directory is normalized with a trailing slash to match the format returned by claude-code--directory, ensuring that buffer names, concurrent session detection, and modeline display all work correctly. For Codex sessions, this requires the codex package and opens the session in a proper terminal buffer (eat or vterm), starting in the session’s project directory when possible. The keybinding is r in agent-log-mode.

agent-log-mode

agent-log-mode is the major mode used for rendered session buffers. It derives from markdown-view-mode, inheriting Markdown fontification in a read-only buffer. Buffers are never marked as modified, so q (quit-window) closes them without prompting to save. The mode activates outline-minor-mode with a regexp that matches Markdown headings (=## =, =### =, =#### =), enabling section folding.

Two invisibility specs are added: agent-log-collapsed (which shows the heading but hides the body) and agent-log-hidden (which hides both heading and body). These are used by the section visibility commands (Section visibility) and the display configuration options (Display of thinking and tool sections).

When a buffer in this mode is killed, the file watcher is cleaned up and the index is updated with the final JSONL file size.

The full keymap is:

KeyCommandDescription
nagent-log-next-turnJump to the next User/Assistant heading
pagent-log-previous-turnJump to the previous heading
TABagent-log-toggle-sectionToggle fold of section at point
Cagent-log-collapse-allCollapse all tool/thinking sections
Eagent-log-expand-allExpand all sections
gagent-log-refreshRe-render the buffer from scratch
wagent-log-copy-turnCopy the current turn to the kill ring
ragent-log-resume-sessionResume the session in the coding agent
?agent-log-menuOpen the transient menu
qquit-windowClose the buffer

agent-log-search-mode

agent-log-search-mode is the major mode used for the *agent-log-search* result buffer. It derives from markdown-view-mode, providing Markdown fontification in a read-only buffer. Session references in the LLM’s narrative are replaced with clickable links: pressing RET or clicking on them opens the corresponding rendered session log via agent-log-search-follow-link.

The keymap is minimal:

KeyCommandDescription
RETagent-log-search-follow-linkOpen the session link at point
qquit-windowClose the buffer

Transient menu

The command agent-log-menu opens a transient menu that arranges all agent-log commands in side-by-side columns: Open, Sync & AI, and Settings are always visible; a fourth Navigate column appears when the current buffer is in agent-log-mode. The keybinding is ? in agent-log-mode. The menu is also available globally via its autoload, so you can bind it to a key of your choice outside of agent-log-mode.

The Open column includes agent-log-resume-session (bound to s), which opens the session displayed in the current log in the coding agent. The Sync & AI column includes the agent-log-search command, bound to /.

The Navigate column groups movement commands (n, p for stepping through turns), section folding (TAB to toggle at point, C to collapse all, E to expand all), and buffer operations (G to re-render, w to copy the current turn). Note that the collapse and expand commands are one-shot operations on the current buffer view; the persistent per-type visibility defaults (hidden, collapsed, visible) are controlled by the Settings toggles (-t for thinking, -o for tools).

The Settings column displays the current value of each option (e.g., collapsed, on, off) highlighted with the transient-value face, matching the convention used by magit and other transient-based menus.

How it works

Backend architecture

The package uses a generic interface to support multiple AI coding agents. The base type agent-log-backend (defined with cl-defstruct) provides shared slots for the backend name, key symbol, root directory, and rendered directory. Each backend extends this type via :include and implements a set of generic functions (cl-defgeneric / cl-defmethod) that handle backend-specific operations: session discovery, JSONL entry normalization, conversation filtering, tool input summarization, and session resumption.

Backend files are loaded lazily. When a backend is first accessed (e.g. during agent-log-browse-sessions), the package calls require on the backend’s feature name and the backend registers a singleton instance in a global registry. The registry maps backend key symbols to struct instances, so all dispatch goes through the struct’s type.

The rendering pipeline, session browser, live update mechanism, AI summaries, and AI search all operate on a normalized intermediate representation. Each backend’s agent-log--normalize-entries method converts its native JSONL format into canonical plists that the generic code expects. Core functions such as agent-log--render-to-file and agent-log--extract-conversation-text receive a backend instance and thread it through to generic dispatch points (conversation filtering, entry classification, tool summarization, text extraction). The convenience function agent-log--read-all-sessions returns merged sessions from all active backends, sorted most-recent-first; individual buffers store their backend in the buffer-local variable agent-log--backend, set when a session is opened. This means the core of the package is backend-agnostic: adding a new backend requires only implementing the generic functions, with no changes to the core file.

Data sources

Each backend reads conversation data from a different directory layout.

Claude Code

The Claude Code backend reads from two places:

  1. ~/.claude/history.jsonl: a global index mapping session IDs to projects and timestamps.
  2. ~/.claude/projects/<encoded-path>/<uuid>.jsonl: one file per session, containing all messages as JSON lines.

The encoded path replaces /, ., and space characters with -. The package tries both the expanded path and its file-truename when locating a session directory, which handles symlinked project paths.

Codex

The Codex backend reads from:

  1. ~/.codex/history.jsonl: a global index with one line per user message, containing the session ID, a Unix epoch timestamp, and the message text.
  2. ~/.codex/sessions/YYYY/MM/DD/rollout-<date>-<uuid>.jsonl: one file per session, organized by date in a nested directory tree.

Each line in a Codex session file is an envelope {timestamp, type, payload}. The type field determines how the entry is processed: session_meta entries provide session metadata (working directory, model, CLI version); response_item entries carry the conversation content (user and assistant messages, function calls and their outputs, web searches, reasoning blocks); event_msg and turn_context entries are internal bookkeeping and are skipped during rendering. The normalization step converts this envelope format to the canonical flat representation used by the rendering pipeline.

Rendered directory structure

The rendered directory mirrors the project structure:

~/.claude/rendered/
  tango-wiki/
    2026-02-04_14-06_fix-the-authentication-bug.md
    2026-02-05_09-22_refactor-database-schema.md
  dotfiles/
    2026-02-22_11-56_redesign-agent-log-package.md
  _index.el

Each .md file contains HTML-comment front matter (session ID, source path, rendered timestamp, JSONL size) followed by the full conversation rendered as Markdown. The _index.el file is a hash table mapping session IDs to metadata (rendered file path, JSONL size, and optionally a summary).

Rendering pipeline

The rendering pipeline operates in three stages: parse, normalize, filter. First, the JSONL file is parsed into raw plists. Then each backend’s agent-log--normalize-entries method converts the raw entries to a canonical format (for Claude Code this is a no-op since the native format is already canonical; for Codex it unwraps the envelope, converts content types, strips system XML, and merges consecutive entries of the same role into single turns). Finally, the backend’s agent-log--filter-conversation and agent-log--conversation-entry-p methods select user and assistant entries while excluding system-generated messages. For Claude Code, system entries are those whose content starts with known XML tags such as <local-command-stdout> or <command-name>. For Codex, system entries are those starting with <environment_context>, <permissions>, <turn_aborted>, or <collaboration_mode>.

Filtered entries are rendered as Markdown with ## headings for conversation turns and #### headings for tool calls and thinking blocks. User turns show any text content followed by tool results. Assistant turns show thinking blocks, text content, and tool-use summaries.

The session header (the # Session: ... line at the top of each rendered file) derives its project name from the progress entry in the JSONL, which contains the working directory. Not all sessions have a progress entry; when one is absent, the package falls back to the project path recorded in history.jsonl.

Live update mechanism

When agent-log-live-update is non-nil and a rendered buffer is open, the package registers a file-notify watcher on the source JSONL file. When the file grows, the watcher reads only the new bytes (from the previously recorded offset to the new file size), handling incomplete UTF-8 sequences at chunk boundaries. New complete lines are parsed and rendered incrementally, appended to both the buffer and the .md file on disk. Partial lines (those not terminated by a newline) are buffered and completed when the next chunk arrives.

Troubleshooting

No sessions found

Ensure that agent-log-directory points to the correct configuration directory (Directory configuration). The package reads history.jsonl in that directory and looks for session files under projects/.

Rendered files not updating

If a rendered file appears stale, press g (agent-log-refresh) in the buffer to force a full re-render (Sync and rendering). You can also delete the index file (_index.el in the rendered directory) to force re-rendering of all sessions on next access.

Live updates not working

Verify that agent-log-live-update is set to t (Live updates). Live updates rely on file-notify, which requires OS-level file watching support. On some systems (e.g., remote filesystems via TRAMP), file notifications may not be available.