agent-doc

Alpha software. Expect breaking changes between minor versions. See the changelog for migration notes.

agent-doc turns any markdown document into an interactive session with an AI agent. Edit the document, press a hotkey, and the tool diffs your changes, sends them to the agent, and writes the response back into the document. The document is the UI.

Why documents?

Terminal prompts are ephemeral. You type, the agent responds, the context scrolls away. Documents are persistent — you can reorganize, delete noise, annotate inline, and curate the conversation as a living artifact. The agent sees your edits as diffs, so every change carries intent.

How it works

  1. You edit a markdown document with ## User / ## Assistant blocks
  2. You run via hotkey or CLI — agent-doc computes a diff since the last run
  3. The agent responds — the response is appended as a new ## Assistant block
  4. Your editor reloads — the document now contains the full conversation

The diff-based approach means you can also edit previous responses, delete noise, add inline annotations, and restructure the document freely. The agent sees exactly what changed.

Features

  • Session continuity — YAML frontmatter tracks session ID for multi-turn conversations
  • Merge-safe writes — 3-way merge if you edit during an agent response
  • Git integration — auto-commit for diff gutter visibility in your editor
  • Agent-agnostic — Claude backend included, custom backends configurable
  • Editor integration — JetBrains, VS Code, Vim/Neovim via hotkey

Tech stack

  • Language: Rust (2021 edition)
  • CLI: clap (derive macros)
  • Diffing: similar crate (pure Rust)
  • Serialization: serde + serde_yaml / serde_json / toml
  • Hashing: sha2 for snapshot paths

Installation

pip / pipx (all platforms)

pip install agent-doc
# or
pipx install agent-doc

This installs a prebuilt wheel with the compiled binary — no Rust toolchain needed.

Shell installer (Linux & macOS)

curl -sSf https://raw.githubusercontent.com/btakita/agent-doc/main/install.sh | sh

This downloads a prebuilt binary to ~/.local/bin/agent-doc. Use --system to install to /usr/local/bin instead (requires sudo).

From source

cargo install --path .

Windows

pip install agent-doc is the easiest option. Alternatively, download .zip from GitHub Releases or build from source with cargo install --path ..

Quick Start

Create a session document

agent-doc init session.md "My Topic"

This creates a markdown file with YAML frontmatter and a ## User block ready for your first message.

Write your first message

Open session.md in your editor and write under ## User:

---
session: null
agent: null
model: null
branch: null
---

# Session: My Topic

## User

Explain how TCP three-way handshake works.

Run the agent

agent-doc run session.md

The tool computes a diff, sends it to the agent, and appends the response as a ## Assistant block. Your editor reloads the file with the response.

Continue the conversation

Add a new ## User block below the assistant's response, write your follow-up, and run again:

agent-doc run session.md

Preview before running

agent-doc diff session.md       # see what changed since last run
agent-doc run session.md --dry-run  # preview the prompt without sending

Basic workflow

agent-doc init session.md "Topic"   # scaffold a session doc
# edit session.md in your editor
agent-doc run session.md            # diff, send, append response
# edit again, add follow-up
agent-doc run session.md            # next turn
agent-doc clean session.md          # squash git history when done

Configuration

Config file

Location: ~/.config/agent-doc/config.toml

default_agent = "claude"

[agents.claude]
command = "claude"
args = ["-p", "--output-format", "json"]
result_path = ".result"
session_path = ".session_id"

[agents.codex]
command = "codex"
args = ["--prompt"]
result_path = ".output"
session_path = ".id"

Fields

FieldDescription
default_agentAgent backend used when not specified elsewhere
[agents.NAME]Agent backend configuration
commandExecutable name or path
argsArguments passed before the prompt
result_pathJSON path to extract the response text
session_pathJSON path to extract the session ID

Resolution order

The agent backend is resolved in this order:

  1. --agent CLI flag
  2. agent: field in document frontmatter
  3. default_agent in config
  4. Fallback: "claude"

Per-document overrides

Set agent and model in the document's YAML frontmatter:

---
session: null
agent: codex
model: gpt-4
---

These override the config file for that specific document.

Commands

All commands are available through the agent-doc CLI.

run

agent-doc run <FILE> [-b] [--agent NAME] [--model MODEL] [--dry-run] [--no-git]

Diff, send to agent, append response. The core command.

FlagDescription
-bAuto-create branch agent-doc/<filename> on first run
--agent NAMEOverride agent backend
--model MODELOverride model
--dry-runPreview diff and prompt size without sending
--no-gitSkip git operations (branch, commit)

Flow:

  1. Compute diff from snapshot
  2. Build prompt (diff + full document)
  3. Pre-commit user changes (unless --no-git)
  4. Send to agent
  5. Append response as ## Assistant block
  6. 3-way merge if file was edited during response
  7. Save snapshot (no post-commit — agent response stays as uncommitted changes)

init

agent-doc init <FILE> [TITLE] [--agent NAME]

Scaffold a new session document with YAML frontmatter and a ## User block. Fails if the file already exists.

diff

agent-doc diff <FILE>

Preview the unified diff that would be sent on the next run. Useful for checking what changed before running.

reset

agent-doc reset <FILE>

Clear the session ID from frontmatter and delete the snapshot. The next run starts a fresh session.

clean

agent-doc clean <FILE>

Squash all agent-doc: git commits for the file into a single commit. Useful for cleaning up history after a long session.

audit-docs

agent-doc audit-docs

Audit instruction files (CLAUDE.md, AGENTS.md, README.md, SKILL.md) against the codebase:

  • Referenced file paths exist on disk
  • Combined line budget under 1000 lines
  • Staleness detection (docs older than source)
  • Actionable content checks

route

agent-doc route <FILE>

Route a /agent-doc command to the correct tmux pane. Looks up the session UUID from frontmatter, finds the pane in sessions.json, and sends the command via tmux send-keys. If the pane is dead, auto-starts a new Claude session.

start

agent-doc start <FILE>

Start Claude in the current tmux pane and register the session. Ensures a session UUID exists in frontmatter, registers the pane in sessions.json, then execs claude.

claim

agent-doc claim <FILE>

Claim a document for the current tmux pane. Reads the session UUID from frontmatter and $TMUX_PANE, then updates sessions.json. Unlike start, does not launch Claude — use this when you're already inside a Claude session.

Last-call-wins: a subsequent claim for the same file overrides the previous pane mapping. Multiple files can be claimed for the same pane.

Also available as a Claude Code skill: /agent-doc claim <FILE>.

prompt

agent-doc prompt <FILE>
agent-doc prompt --all
agent-doc prompt --answer N <FILE>

Detect permission prompts from a Claude Code session by capturing tmux pane content.

FlagDescription
(none)Detect prompts for a single file
--allPoll all live sessions, return JSON array
--answer NAnswer prompt by selecting option N (1-based)

commit

agent-doc commit <FILE>

Git add + commit with an auto-generated agent-doc: YYYY-MM-DD HH:MM:SS timestamp message.

skill

agent-doc skill install
agent-doc skill check

Manage the Claude Code skill definition.

SubcommandDescription
installWrite the bundled SKILL.md to .claude/skills/agent-doc/SKILL.md. Idempotent.
checkCompare installed skill vs bundled version. Exit 0 if up to date, exit 1 if outdated.

The skill content is embedded in the binary at build time. After agent-doc upgrade, run agent-doc skill install in each project to update the skill definition.

patch

agent-doc patch <FILE> <COMPONENT> [CONTENT]

Replace content in a named component. Components are bounded regions marked with <!-- agent:name -->...<!-- /agent:name -->.

ArgumentDescription
FILEPath to the document
COMPONENTComponent name (e.g., status, log)
CONTENTReplacement content (reads from stdin if omitted)

Behavior depends on .agent-doc/components.toml config:

ConfigDefaultDescription
modereplacereplace, append, or prepend
timestampfalseAuto-prefix with ISO timestamp
max_entries0Trim entries in append/prepend (0 = unlimited)
pre_patchnoneShell hook: transform content (stdin → stdout)
post_patchnoneShell hook: fire-and-forget after write

See Components for full configuration and hook documentation.

watch

agent-doc watch [--stop] [--status] [--debounce MS] [--max-cycles N]

Watch session files for changes and auto-submit.

FlagDefaultDescription
--stopStop the running watch daemon
--statusShow daemon status
--debounce500Debounce delay in milliseconds
--max-cycles3Max agent-triggered cycles per file before pausing

The daemon watches all claimed files (from sessions.json), debounces per-file, and triggers agent-doc run on changes. PID stored in .agent-doc/watch.pid.

Loop prevention: bounded cycles (default 3) and convergence detection (stop if agent response matches previous). See Dashboard-as-Document for the full workflow.

upgrade

agent-doc upgrade

Check crates.io for the latest version and upgrade. Tries GitHub Releases binary download first, then cargo install, then pip install --upgrade.

Global flags

agent-doc --version    # Print version
agent-doc --help       # Show help

Document Format

Structure

Session documents are markdown files with YAML frontmatter:

---
session: 05304d74-90f1-46a1-8a79-55736341b193
agent: claude
model: null
branch: null
---

# Session: Topic Name

## User

Your question or instruction here.

## Assistant

(agent writes here)

## User

Follow-up. You can also annotate inline:

> What about edge cases?

Frontmatter fields

FieldRequiredDefaultDescription
sessionno(generated on first run)Session ID for continuity
agentnoclaudeAgent backend to use
modelno(agent default)Model override
branchno(none)Git branch for session commits

All fields are optional and default to null.

Frontmatter parsing

Delimited by ---\n at the start of the file and a closing \n---\n. If frontmatter is absent, all fields default to null and the entire content is treated as the body.

Interaction modes

Append mode

Structured ## User / ## Assistant blocks. Each run appends a new assistant response.

Inline mode

Annotations anywhere — blockquotes, edits to previous responses, comments in the body. The diff captures what changed; the agent addresses inline edits alongside new ## User content.

Both modes work simultaneously because the run sends a diff, not a parsed structure.

Components

Documents can contain components — named, re-renderable regions marked with HTML comment pairs:

<!-- agent:status -->
| Field | Value |
|-------|-------|
| build | passing |
<!-- /agent:status -->

Components are updated via agent-doc patch or by agents/scripts. Their content can be replaced, appended to, or prepended to based on configuration in .agent-doc/components.toml.

Regular HTML comments (<!-- like this -->) remain a private scratchpad — they're stripped during diff and never trigger responses. Component markers look like comments but are structural and preserved.

See the Components guide for full details.

History rewriting

Delete anything from the document. On next run, the diff shows deletions and the agent sees the cleaned-up document as ground truth. This lets you:

  • Remove irrelevant exchanges
  • Consolidate scattered notes
  • Restructure the conversation
  • Correct earlier context

Components

Components are bounded, named, re-renderable regions in a document — similar to web components or React components. They provide a way for agents and scripts to update specific sections of a document without touching the rest.

Syntax

Components use paired HTML comment markers:

<!-- agent:status -->
| Field | Value |
|-------|-------|
| build | passing |
<!-- /agent:status -->

The opening marker <!-- agent:NAME --> and closing marker <!-- /agent:NAME --> define the component boundary. Everything between the markers is the component's content.

Why paired markers?

A single marker (<!-- agent:x -->) has no boundary — after the first render inserts content, the next render can't distinguish the marker from rendered data. Paired markers create an unambiguous boundary. The closing marker makes re-rendering idempotent: agent-doc patch always knows exactly what to replace.

Naming rules

Component names must match [a-zA-Z0-9][a-zA-Z0-9-]* — start with alphanumeric, followed by alphanumerics or hyphens.

Valid: status, build-log, session2 Invalid: -start, _name, with spaces

Nesting

Components can nest. Inner components are parsed independently:

<!-- agent:dashboard -->
# System Overview

<!-- agent:status -->
All systems operational.
<!-- /agent:status -->

<!-- agent:metrics -->
CPU: 42%
<!-- /agent:metrics -->
<!-- /agent:dashboard -->

Patching status or metrics only affects that inner component. Patching dashboard replaces everything between its markers (including the inner components).

Patching components

The agent-doc patch command replaces a component's content:

# Replace from argument
agent-doc patch dashboard.md status "build: failing"

# Replace from stdin
echo "build: passing" | agent-doc patch dashboard.md status

# Replace from a script
curl -s https://api.example.com/status | agent-doc patch dashboard.md status

The markers are preserved — only the content between them changes.

Component configuration

Configure component behavior in .agent-doc/components.toml at the project root:

[log]
mode = "append"
timestamp = true
max_entries = 100

[status]
mode = "replace"       # default

[metrics]
pre_patch = "scripts/validate-metrics.sh"
post_patch = "scripts/notify-update.sh"

Modes

ModeBehavior
replaceFull content replacement (default)
appendNew content added at the bottom of existing content
prependNew content added at the top of existing content

Options

OptionTypeDefaultDescription
modestring"replace"Patch mode
timestampboolfalseAuto-prefix entries with ISO timestamp
max_entriesint0Auto-trim old entries in append/prepend modes (0 = unlimited)
pre_patchstringnoneShell command to transform content before patching
post_patchstringnoneShell command to run after patching (fire-and-forget)

Shell hooks

Hooks let you transform content or trigger side effects when a component is patched.

pre_patch

Runs before the content is written. Receives the new content on stdin, outputs transformed content on stdout:

[status]
pre_patch = "scripts/validate-status.sh"
#!/bin/bash
# scripts/validate-status.sh
# Transform content before it's written to the component

# Read incoming content from stdin
content=$(cat)

# Validate or transform
if echo "$content" | jq . > /dev/null 2>&1; then
    # Valid JSON — format it as a markdown table
    echo "$content" | jq -r 'to_entries[] | "| \(.key) | \(.value) |"'
else
    # Pass through unchanged
    echo "$content"
fi

Environment variables available:

  • COMPONENT — component name (e.g., status)
  • FILE — path to the document being patched

If the hook exits non-zero, the patch is aborted.

post_patch

Runs after the content is written. Fire-and-forget — output is inherited (prints to terminal), exit code is logged but doesn't affect the patch:

[metrics]
post_patch = "scripts/notify-update.sh"
#!/bin/bash
# scripts/notify-update.sh
echo "Component '$COMPONENT' updated in $FILE"
# Could trigger a webhook, send a notification, etc.

Components vs comments

Regular HTML comments are a user scratchpad — they're stripped during diff comparison and never trigger agent responses:

<!-- This is a regular comment — invisible to the agent -->

Component markers look like comments but are structural:

<!-- agent:status -->
This content is managed by the agent or scripts
<!-- /agent:status -->

The diff engine preserves component markers while stripping regular comments. This means:

  • Adding/removing regular comments does not trigger a response
  • Changing content inside a component does trigger a response
  • The markers themselves are never modified by patch

Dashboard-as-Document

A dashboard is a markdown document with agent-maintained components that display live data. Instead of a separate dashboard UI, the document is the dashboard — editable in any text editor, version-controlled with git, and updated by scripts or agents via agent-doc patch.

Quick start

1. Create the dashboard document

---
session: null
---

# Project Dashboard

## Status

<!-- agent:status -->
| Service | State |
|---------|-------|
| api     | unknown |
| worker  | unknown |
<!-- /agent:status -->

## Recent Activity

<!-- agent:log -->
<!-- /agent:log -->

2. Configure components

Create .agent-doc/components.toml:

[status]
mode = "replace"

[log]
mode = "append"
timestamp = true
max_entries = 50

3. Update components from scripts

# Update the status table
agent-doc patch dashboard.md status "$(cat <<'EOF'
| Service | State |
|---------|-------|
| api     | healthy |
| worker  | healthy |
EOF
)"

# Append to the log
agent-doc patch dashboard.md log "Deployment completed successfully"

The status component gets replaced entirely. The log component appends with a timestamp:

<!-- agent:log -->
[2026-03-04T18:30:00Z] Deployment completed successfully
<!-- /agent:log -->

4. Auto-update with watch

Start the watch daemon to auto-submit when the dashboard changes:

agent-doc watch

Now when external scripts update components via patch, the watch daemon detects the file change and can trigger agent-doc run to let the agent respond to the new data.

End-to-end flow

External script                agent-doc               Agent
     |                            |                      |
     |-- patch status ----------->|                      |
     |                            |-- (file changed) --->|
     |                            |   (watch detects)    |
     |                            |-- run (diff+send) -->|
     |                            |                      |-- responds
     |                            |<-- patch log --------|
     |                            |   (agent updates)    |
  1. An external script calls agent-doc patch to update a component
  2. The watch daemon detects the file change
  3. Watch triggers agent-doc run which diffs and sends to the agent
  4. The agent sees the change ("status went from unknown to healthy") and can respond — updating the log, adding analysis, or patching other components

Dashboard with multiple components

A real-world dashboard might look like:

---
session: null
---

# Build Monitor

<!-- agent:summary -->
**Last updated:** never
<!-- /agent:summary -->

## Build Status

<!-- agent:builds -->
No builds yet.
<!-- /agent:builds -->

## Test Results

<!-- agent:tests -->
No test results.
<!-- /agent:tests -->

## Activity Log

<!-- agent:log -->
<!-- /agent:log -->

With .agent-doc/components.toml:

[summary]
mode = "replace"

[builds]
mode = "replace"
post_patch = "scripts/check-failures.sh"

[tests]
mode = "replace"

[log]
mode = "append"
timestamp = true
max_entries = 200

Update from CI:

# After a build completes
agent-doc patch monitor.md builds "$(./scripts/format-builds.sh)"

# After tests run
agent-doc patch monitor.md tests "$(./scripts/format-tests.sh)"

# Log the event
agent-doc patch monitor.md log "Build #${BUILD_ID} completed: ${STATUS}"

User interaction with dashboards

Dashboards are still documents — users can write in them. Add a ## User block or annotate inline. The agent responds to the diff like any session document.

## User

Why did build #42 fail? Can you analyze the test results component?

The agent sees the full dashboard (all components) plus the user's question, and can respond in context.

Loop prevention

When the watch daemon is running, a patch can trigger a run, which might patch again, creating a cycle. Watch prevents unbounded loops:

  • Bounded cycles (default 3): After 3 consecutive agent-triggered re-submits with no external change, watch pauses that file
  • Convergence detection: If the agent's response produces the same content as last time (hash match), the cycle stops
  • Configurable: agent-doc watch --max-cycles 5 --debounce 1000

Tips

  • Reference other files: Dashboards can reference other documents — use relative paths from the project root
  • Inline annotations: Edit within component content to ask questions — the diff captures your edits
  • Comments are private: <!-- regular comments --> are never sent to the agent. Use them for notes.
  • Snapshots reset on rename: Moving a file resets the diff baseline (snapshots are keyed by canonical path)
  • Git integration: agent-doc commit dashboard.md commits the current state with a timestamp

Run Flow

Overview

┌──────────┐  hotkey ┌────────────┐  diff + prompt  ┌───────┐
│  Editor  │ ──────> │ agent-doc  │ ──────────────> │ Agent │
│          │         │            │ <────────────── │  API  │
│  reload  │ <────── │ write+snap │                 └───────┘
└──────────┘         │ git commit │
                     └────────────┘

Step by step

  1. Read document and load snapshot (last-known state from previous run)
  2. Compute diff — if empty, exit early (double-run guard)
  3. Pre-commit user's changes via git add -f + git commit (baseline for diff gutters)
  4. Send diff + full document to agent, resuming session if one exists
  5. Build response — original content + session ID update + ## Assistant block + ## User block
  6. Check for concurrent edits — re-read the file
  7. Merge if needed — 3-way merge via git merge-file if file changed during agent response
  8. Write merged content back to file
  9. Save snapshot — no post-commit, so agent additions appear as uncommitted changes in the editor

Session continuity

  • Empty session: — forks from the most recent agent session (inherits context)
  • session: <uuid> — resumes that specific session
  • Delete session: value — next run starts fresh

Merge-safe writes

If you edit the document while the agent is responding:

  • Clean merge (edits in different regions) — merged automatically. Message: "Merge successful — user edits preserved."
  • Conflict (edits in the same region as the response) — conflict markers written to the file with labels agent-response, original, your-edits. Message: "WARNING: Merge conflicts detected."

The merge uses git merge-file -p --diff3, which handles edge cases (whitespace, encoding, partial overlaps) better than a custom implementation.

Git integration

FlagBehavior
-bAuto-create branch agent-doc/<filename> on first run
(none)Pre-commit user changes to current branch
--no-gitSkip git entirely

The two-phase git flow (pre-commit user, no post-commit agent) means your editor shows green diff gutters for everything the agent added. On the next run, those changes get committed as part of the pre-commit step.

Cleanup: agent-doc clean <file> squashes all session commits into one.

Editor Integration

agent-doc is designed to be triggered from your editor with a single hotkey.

JetBrains (IntelliJ, WebStorm, etc.)

Settings > Tools > External Tools > Add:

FieldValue
Programagent-doc
Argumentsrun $FilePath$
Working directory$ProjectFileDir$

Assign a keyboard shortcut (e.g. Ctrl+Shift+S). The External Tool shows output in the Run panel — progress messages, merge status, and errors all appear there.

VS Code

Add a task to .vscode/tasks.json:

{
    "label": "agent-doc run",
    "type": "shell",
    "command": "agent-doc run ${file}",
    "group": "build",
    "presentation": {
        "reveal": "silent",
        "panel": "shared"
    }
}

Bind to a keybinding in keybindings.json:

{
    "key": "ctrl+shift+s",
    "command": "workbench.action.tasks.runTask",
    "args": "agent-doc run"
}

Vim / Neovim

nnoremap <leader>as :!agent-doc run %<CR>:e<CR>

The :e<CR> reloads the file after the response is written.

General tips

  • Don't edit during run — the merge-safe flow handles it, but it's simpler to wait for the progress indicator to finish.
  • Auto-reload — JetBrains and VS Code auto-reload files changed on disk. Vim needs the :e reload.
  • Diff gutters — after run, your editor shows diff gutters for everything the agent added (because agent responses are left uncommitted).

Agent Backends

agent-doc has an agent-agnostic core. Only the "send prompt, get response" step varies per backend.

Claude (default)

The built-in Claude backend runs:

claude -p --output-format json --permission-mode acceptEdits

Session handling:

  • First run: --continue --fork-session (inherits context from the most recent session)
  • Subsequent runs: --resume <session_id> (continues the specific session)

The backend removes the CLAUDECODE environment variable to prevent nested session conflicts.

Custom backends

Configure in ~/.config/agent-doc/config.toml:

[agents.codex]
command = "codex"
args = ["--prompt"]
result_path = ".output"
session_path = ".id"
FieldDescription
commandExecutable name or path
argsArguments passed before the prompt
result_pathJSON path to extract the response text from output
session_pathJSON path to extract the session ID from output

Backend contract

Each agent backend implements: take a prompt string, return (response_text, session_id).

The prompt includes the diff and full document. The backend handles CLI invocation, JSON parsing, and session flags.

Per-document override

Set agent: in the document's YAML frontmatter to use a specific backend for that document:

---
agent: codex
model: gpt-4
---

Or override per-invocation:

agent-doc run session.md --agent codex --model gpt-4

Building

Developer setup

git clone https://github.com/btakita/agent-doc.git
cd agent-doc
make release    # build + symlink to .bin/agent-doc

Make targets

make build        # Debug build
make release      # Release build + symlink to .bin/agent-doc
make test         # Run tests
make clippy       # Lint
make check        # Lint + test
make precommit    # Full pre-commit checks (lint + test + audit-docs)
make install      # Install to ~/.cargo/bin
make init-python  # Set up Python venv with maturin
make wheel        # Build wheel and install into venv

.gitignore

The following are gitignored:

target/
.bin/
.agent-doc/
.venv/
CLAUDE.local.md
.idea/

Release build

The release profile optimizes for binary size and performance:

[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true

Conventions

Code style

  • Use clap derive for CLI argument parsing
  • Use serde derive for all data types
  • Use serde_yaml for frontmatter parsing
  • Use similar crate for diffing (pure Rust, no shell diff dependency)
  • Use serde_json for agent response parsing
  • Use std::process::Command for git operations (not git2)
  • Use toml + serde for config file parsing
  • No async — sequential per-run
  • Use anyhow for application errors

Instruction files

  • CLAUDE.md is the primary instruction file
  • Personal overrides: CLAUDE.local.md (gitignored)
  • Actionable over informational. Instruction files contain the minimum needed to generate correct code. Reference material belongs in README.md.
  • Update with the code. When a change affects patterns, conventions, or module boundaries, update instruction files as part of the same change.

Version management

  • Never bump versions automatically — the user will bump versions explicitly.
  • Commits that include a version change should include the version number in the commit message.
  • Use BREAKING CHANGE: prefix in VERSIONS.md entries for incompatible changes.
  • Update SPEC.md when agent-doc functionality changes (commands, formats, algorithms).

Workflow

Follow a research, plan, implement cycle:

  1. Research — Read the relevant code deeply.
  2. Plan — Write a detailed implementation plan.
  3. Implement — Execute the plan. Run make check continuously.
  4. Precommit — Run make precommit before committing.

agent-doc Functional Specification

Language-independent specification for the agent-doc interactive document session tool. This document captures the exact behavior a port must reproduce.

1. Overview

agent-doc manages interactive document sessions between a human and an AI agent. The human edits a markdown document, sends diffs to the agent, and the agent's response is appended. Session state is tracked via YAML frontmatter, snapshots, and git commits.

2. Document Format

2.1 Session Document

Frontmatter fields:

  • agent_doc_session: Document/routing UUID — permanent identifier for tmux pane routing. Legacy alias: session (read but not written).
  • agent_doc_format: Document format — inline (canonical), template (default: template). append accepted as backward-compat alias for inline.
  • agent_doc_write: Write strategy — merge or crdt (default: crdt).
  • agent_doc_mode: Deprecated. Single field mapping: append → format=append, template → format=template, stream → format=template+write=crdt. Explicit agent_doc_format/agent_doc_write take precedence. Legacy aliases: mode, response_mode.
  • agent: Agent backend name (overrides config default)
  • model: Model override (passed to agent backend)
  • branch: Reserved for branch tracking
  • claude_args: Additional CLI arguments for the claude process (space-separated string, see §6.1)

All fields are optional and default to null. Resolution: explicit agent_doc_format/agent_doc_write > deprecated agent_doc_mode > defaults (template + crdt). The body alternates ## User and ## Assistant blocks (append format) or uses named components (template format).

2.2 Frontmatter Parsing

Delimited by ---\n at file start and closing \n---\n. If absent, all fields default to null and entire content is the body.

2.3 Components

Documents can contain named, re-renderable regions called components:

<!-- agent:status -->
content here
<!-- /agent:status -->

Marker format: <!-- agent:{name} --> (open) and <!-- /agent:{name} --> (close). Names must match [a-zA-Z0-9][a-zA-Z0-9-]*. Components are patched via agent-doc patch.

Inline attributes: Open markers support inline attribute overrides: <!-- agent:name patch=append -->. mode= is accepted as a backward-compatible alias; patch= takes precedence if both are present. max_lines=N trims component content to the last N lines after patching (0 or absent = unlimited). Precedence chain: inline attribute > .agent-doc/components.toml > built-in default (replace for patch, unlimited for max_lines).

Code range exclusion: Component marker detection uses pulldown-cmark for CommonMark-compliant code range detection, replacing the previous regex-based approach. Markers inside inline code spans or fenced code blocks are excluded and never treated as component boundaries.

Standard component names:

ComponentDefault patchDescription
exchangeappendConversation history — each cycle appends
findingsappendAccumulated research data — grows over time
statusreplaceCurrent state — updated at milestones
pendingreplaceTask queue — auto-cleaned each cycle
outputreplaceLatest agent response only
inputreplaceUser prompt area
(custom)replaceAll other components default to replace

Per-component behavior is configured in .agent-doc/components.toml (see §7.21).

3. Snapshot System

3.1 Storage

Snapshots live in .agent-doc/snapshots/ relative to CWD. Path: sha256(canonical_path) + ".md".

3.2 Lifecycle

  • Save: After successful run, full content saved as snapshot
  • Load: On next run, loaded as "previous" state for diff
  • Delete: On reset, snapshot removed
  • Missing: Diff treats previous as empty (entire doc is the diff)

4. Diff Computation

Line-level unified diff via similar crate. Returns +/-/ prefixed lines, or None if unchanged.

Skill-level behavior: The /agent-doc Claude Code skill strips HTML comments (<!-- ... -->) and link reference comments ([//]: # (...)) from both the snapshot and current content before diff comparison. This ensures that comments serve as a user scratchpad without triggering agent responses. This stripping is performed by the skill workflow (SKILL.md §2), not by the CLI itself.

5. Agent Backend

5.1 Trait

fn send(prompt, session_id, fork, model) -> (text, session_id)

5.2 Resolution Order

  1. CLI --agent flag
  2. Frontmatter agent field
  3. Config default_agent
  4. Fallback: "claude"

5.3 Claude Backend

Default: claude -p --output-format json --permission-mode acceptEdits. Session handling: --resume {id} or --continue --fork-session. Appends --append-system-prompt with document-mode instructions. Removes CLAUDECODE env var. Parses JSON: result, session_id, is_error.

5.4 Custom Backends

Config overrides command and args for any agent name.

6. Config

Location: {XDG_CONFIG_HOME}/agent-doc/config.toml (default ~/.config/agent-doc/config.toml).

Fields: default_agent, claude_args, [agents.{name}] with command, args, result_path (reserved), session_path (reserved).

6.1 claude_args

Additional CLI arguments passed to the claude process when spawned by agent-doc start. Space-separated string.

Three sources, in precedence order (highest first):

  1. Frontmatter: claude_args: "--dangerously-skip-permissions" in the document's YAML frontmatter
  2. Global config: claude_args = "--dangerously-skip-permissions" in ~/.config/agent-doc/config.toml
  3. Environment variable: AGENT_DOC_CLAUDE_ARGS="--dangerously-skip-permissions"

The resolved args are split on whitespace and prepended to the claude command before other flags (e.g., --continue).

6.2 Project Config

Location: .agent-doc/config.toml (relative to project root).

Fields: tmux_session — the tmux session name bound to this project.

Auto-sync: When the configured tmux_session is dead (session no longer exists), the route path falls back to current_tmux_session() and auto-updates config.toml with the new session name. This prevents stale config after session destruction.

6.3 Socket IPC

Socket-based IPC via Unix domain sockets (.agent-doc/ipc.sock) is the primary IPC transport. The editor plugin starts a listener via agent_doc_start_ipc_listener() FFI call on project open. The CLI sender connects, sends NDJSON messages, and waits for ack.

Protocol: Newline-delimited JSON (NDJSON). Message types:

  • {"type": "patch", "file": "...", "patches": [...], ...} — apply component patches
  • {"type": "reposition", "file": "..."} — reposition boundary marker
  • {"type": "vcs_refresh"} — trigger VCS/VFS refresh

Fallback: If socket is unavailable (no listener), falls back to file-based IPC (JSON patch files in .agent-doc/patches/).

6.4 IPC Write Verification

After the IDE plugin consumes an IPC patch file:

  1. File-change check: If the document file is unchanged on disk, the plugin failed to apply — falls back to disk write.
  2. Content verification: If the document changed but none of the patch content appears in the result, the plugin partially failed — falls back to disk write.
  3. Force-disk cleanup: When --force-disk is set, any pending IPC patch files are deleted before disk write to prevent the plugin from applying stale patches (double-write prevention).

6.5 Sync Layout Authority

sync_after_claim() uses editor-provided col_args when available (authoritative layout from the IDE plugin). Only falls back to registry-based file discovery when no col_args given. This prevents stale registry entries from creating incorrect multi-pane layouts.

6.6 Document State Model (4 States)

A document has four concurrent representations during a write cycle:

StateLocationOwnerPurpose
Snapshot.agent-doc/snapshots/<hash>.mdBinaryLast committed agent state. Used by diff::compute() to detect user changes since last response.
Baseline.agent-doc/baselines/<hash>.mdBinary (preflight)Document at start of response generation. Common ancestor for 3-way/CRDT merge. Saved by preflight after commit (step 2b).
File on diskThe document fileEditor (auto-save)Last editor save. Lags behind the editor buffer. Used by non-IPC write paths.
Editor bufferEditor memoryEditor (Document API)Live content including unsaved edits. IPC writes target this via the Document API, preserving cursor position and undo history.

Consistency invariants:

  • After preflight step 2b: baseline == snapshot (minus boundary markers)
  • After agent-doc write: snapshot == baseline + response (content_ours)
  • After agent-doc commit: git HEAD contains snapshot + (HEAD) marker
  • The editor buffer may diverge from all three persistent states (unsaved user edits)

Staleness risk: If the baseline is saved before preflight (the old SKILL.md approach), it becomes stale when commit repositions the boundary marker. The binary guard in write.rs detects this via component-aware comparison:

  • Parses both snapshot and baseline into components (component::parse)
  • Only checks append-mode components (exchange, findings) — these grow monotonically
  • Skips replace-mode components (status, pending) — user-editable, expected to diverge
  • Falls back to prefix check for non-template (inline) documents
  • When stale: re-applies patches to current file content instead of the stale baseline

7. Commands

7.1 run

agent-doc run <FILE> [-b] [--agent NAME] [--model MODEL] [--dry-run] [--no-git]

  1. Compute diff → 2. Build prompt (diff + full doc) → 3. Branch if -b → 4. Send to agent → 5. Update session ID → 6. Append response → 7. Save snapshot → 8. git add -f + commit

First run prompt wraps full doc in <document> tags. Subsequent wraps diff in <diff> tags + full doc in <document>.

7.2 init

Two modes:

No-arg (project init): agent-doc init — checks prerequisites, creates .agent-doc/snapshots/ and .agent-doc/patches/ directories, and installs .claude/skills/agent-doc/SKILL.md. Idempotent. Run once per project before creating session documents.

With file (document scaffold): agent-doc init <FILE> [TITLE] [--agent NAME] — scaffolds frontmatter + ## User block. Fails if file already exists. Lazily runs project init first if .agent-doc/ does not exist.

7.3 install

agent-doc install [--editor jetbrains|vscode] [--skip-prereqs] [--skip-plugins] — system-level setup.

  1. Prerequisite check (unless --skip-prereqs): verifies tmux and claude are on PATH; prints ok or MISSING with install hint for each. Does not fail — only warns.
  2. Editor plugin install (unless --skip-plugins):
    • If --editor is given, installs only that editor's plugin.
    • Otherwise, auto-detects installed editors: JetBrains (checks ~/.local/share/JetBrains/ on Linux, /Applications/IntelliJ* on macOS) and VS Code family (cursor, codium, code).
    • If no editors detected, prints a hint to use --editor and exits without error.
    • Calls crate::plugin::install(editor) for each detected editor.
    • Prints a summary of installed and failed editors.

7.4 diff

agent-doc diff <FILE> — prints unified diff to stdout.

7.5 reset

agent-doc reset <FILE> — clears session ID, deletes snapshot.

7.6 clean

agent-doc clean <FILE> — squashes all agent-doc: commits for file into one via git reset --soft.

7.7 audit-docs

agent-doc audit-docs [--root DIR] — checks CLAUDE.md/AGENTS.md/README.md/SKILL.md for tree path accuracy, line budget (1000), staleness, and actionable content. Exit 1 on issues.

--root DIR overrides auto-detection of the project root directory. Without it, the root is resolved via project markers (Cargo.toml, package.json, etc.), then .git, then CWD fallback.

7.8 start

agent-doc start <FILE> — start Claude in a new tmux pane and register the session.

  1. Ensure session UUID in frontmatter (generate if missing)
  2. Read $TMUX_PANE (must be inside tmux)
  3. Register session → pane in sessions.json
  4. Exec claude (replaces process)

7.9 route

agent-doc route <FILE> [--pane P] — route a /agent-doc command to the correct tmux pane.

  1. Prune stale entries from sessions.json
  2. Ensure session UUID in frontmatter (generate if missing)
  3. Look up pane in sessions.json
  4. If pane alive → send /agent-doc <FILE> via send_keys, then Enter verification loop (polls for command text disappearance every 300ms, retries Enter on each poll, up to 5s timeout), focus pane
  5. If pane dead (previously registered) → lazy-claim to active pane in claude tmux session (or --pane P), register, send command, auto-sync layout for all files in the same window. Unregistered files skip lazy-claim entirely.
  6. If no active pane available → auto-start cascade (see below), register, wait up to 30s for Claude prompt via pane_has_prompt() with ANSI stripping, then send command

Session validation: If tmux_session references a non-existent tmux session, route logs a warning and falls back to the default session. It does NOT create new tmux sessions. The fallback order is: current tmux session (if running inside tmux) → default claude session.

Deprecation note: tmux_session in frontmatter is deprecated. The tmux session is now determined at runtime: --window argument (sync), current_tmux_session() (route/start), or future .agent-doc/config.toml settings. The field is still read for backward compatibility and auto-repaired by sync. It will be removed in a future version.

Auto-start algorithm (auto_start_in_session):

  1. Startup lock: Check .agent-doc/starting/<hash>.lock. If exists and age < 5s → skip (prevents double-spawn when sync fires twice rapidly). Create lock file before proceeding. Best-effort: skipped if file doesn't exist or hash fails.
  2. Read tmux_session from the document's frontmatter (fall back to default claude session name)
  3. Find a split target pane:
    • Sync path (skip_wait=true): pick the split target by column position — first pane in the agent-doc window for left-column files, last pane for right-column files. This places the new pane adjacent to its column neighbors.
    • Route path (skip_wait=false): search sessions.json for any registered pane alive in the target session.
  4. If found → tmux split-window alongside that pane (-dbh for left-column, -dh for right-column)
  5. If split-window fails → fall back to creating a new window
  6. If no split target found → create a new window via tmux new-window (the session may not exist yet, in which case a new session is created)

7.10 claim

agent-doc claim <FILE> [--position left|right|top|bottom] [--window W] [--pane P] — claim a document for a tmux pane.

  1. Ensure session UUID in frontmatter (generate if missing)
  2. Resolve effective window (see Window Resolution below)
  3. Determine pane: --pane P overrides, else --position resolves via tmux pane geometry, else $TMUX_PANE
  4. Register session → pane in sessions.json, including window ID

Unlike start, does not launch Claude — the caller is already inside a Claude session. --position is used by the JetBrains plugin to map editor split positions to tmux panes.

Binding invariant enforcement: If the target pane is already claimed by a different session (and the pane is alive), claim provisions a new pane for this document instead of erroring. This enforces the Binding invariant (§8.5): "never commandeer another document's pane." Use --force to explicitly overwrite the existing claim (discouraged — breaks the Binding invariant unless the old document is abandoned).

Default components on claim: For new template documents, agent-doc claim scaffolds <!-- agent:status patch=replace --> and <!-- agent:exchange patch=append --> components by default.

Window Resolution:

When --window W is provided:

  1. Check if window W is alive (tmux list-panes -t W)
  2. If alive → use W (no change)
  3. If dead → scan sessions.json for entries with matching project cwd and non-empty window field. For each, check liveness. Use first alive match.
  4. If no alive windows found → fall through to no-window behavior (position detection without window scoping)

This prevents the JetBrains plugin from hitting persistent error balloons when a tmux window dies. The same fallback pattern is used in sync.rs for dead --window handling.

Snapshot initialization: After registration, saves a snapshot with empty exchange content (via strip_exchange_content). This ensures existing user text in the exchange becomes a diff on the next run, rather than being absorbed into the baseline.

Notifications:

  • tmux display-message — 3-second overlay on the target pane showing "Claimed {file} (pane {id})"
  • .agent-doc/claims.log — appends Claimed {file} for pane {id} for deferred display by the SKILL.md workflow on next invocation

7.11 focus

agent-doc focus <FILE> [--pane P] — focus the tmux pane for a session document.

  1. Read session UUID from file's YAML frontmatter (or use --pane override)
  2. Look up pane ID in sessions.json
  3. Run tmux select-window -t <pane-id> then tmux select-pane -t <pane-id>

Exits with error if the pane is dead or no session is registered.

7.12 layout

agent-doc layout <FILE>... [--split h|v] [--window W] — arrange tmux panes to mirror editor split layout.

  1. Resolve each file to its session pane via frontmatter → sessions.json
  2. If --window given, filter to panes registered for that window only
  3. Pick the target window (the one containing the most wanted panes; tiebreak: most total panes)
  4. Break out only registered session panes that aren't wanted (shells and tool panes are left untouched)
  5. Join remaining wanted panes into the target window (tmux join-pane)
  6. Focus the first file's pane (the most recently selected file)

--split h (default): horizontal/side-by-side. --split v: vertical/stacked. Single file falls back to focus. Dead panes and files without sessions are skipped with warnings.

7.13 resync

agent-doc resync [--fix] — validate sessions.json against live tmux panes.

Always (dry-run and --fix):

  1. Load sessions.json, prune entries with dead panes (delegates to tmux_router::prune())
  2. Purge idle stash windows: kill stash-named windows where all panes run idle shells (zsh, bash, sh, fish) and last activity was >30s ago
  3. Log orphaned claude/stash windows (all panes unregistered) for diagnostics

Issue detection (alive panes only): 4. Wrong-process: Pane is running a process not in the allowlist (agent-doc, claude, node) and not an idle shell (zsh, bash, sh, fish) 5. Wrong-session: Pane is in a different tmux session than the document's tmux_session frontmatter field. Skipped if no file path or no tmux_session in frontmatter. Wrong-process panes are not also checked for wrong-session. 6. Wrong-window: Pane is in a different non-stash window from the majority of panes sharing the same tmux session. Majority-window is computed by count; ties broken arbitrarily. Panes already in a stash window (stash, stash-2, etc.) are excluded from this check.

Without --fix: Reports issues to stderr with "run with --fix to resolve".

With --fix:

  • Wrong-session panes: kills the pane via tmux kill-pane, removes registry entry. Next route auto-starts in the correct session.
  • Wrong-process panes: removes registry entry only (does not kill the foreign process). Next route auto-starts a new pane.
  • Wrong-window panes: moves the pane into the stash window via stash_pane (does not deregister). The pane stays alive; the next sync or layout rejoins it into the correct window.

Stash window naming: Stash windows are named stash. When tmux auto-deduplicates a name collision the window becomes stash-2, stash-3, etc. All names matching stash or stash-* are treated as stash windows (checked by is_stash_window_name). resync purges stash windows where all panes are idle shells and last activity was >30s ago.

Auto-start stash overflow (route): When auto_start_in_session tries split-window alongside a registered pane and the split fails (e.g. minimum pane size constraint), it falls back to tmux new-window then immediately calls stash_pane to move the new pane into the stash window — avoiding a visible throwaway window in the session.

Automatic pruning: resync::prune() (step 1 only — no issue detection or fixing) runs automatically before route, sync, and claim operations. Uses bulk metadata fetching (2 subprocess calls: list-windows -a + list-panes -a) instead of per-pane queries. Stranded panes (no valid return target) are deregistered on first failure to prevent repeated expensive lookups. Stash pane safety: unregistered agent processes (agent-doc, claude, node) in stash windows are never auto-killed — only idle shells are purged. This prevents loss of active Claude sessions when the registry goes stale.

7.14 prompt

agent-doc prompt <FILE> — detect permission prompts from a Claude Code session.

  • Captures tmux pane content, strips ANSI, searches bottom-up for footer containing "to cancel"
  • Supports two option formats: bracket [N] label (legacy) and numbered list N. label (Claude Code v2.1+)
  • Returns JSON: { "active": bool, "question": str, "options": [...], "selected": int }
  • --answer N navigates to option N via arrow keys and confirms with Enter
  • --all polls all live sessions, returns JSON array of PromptAllEntry objects
  • Debug: AGENT_DOC_PROMPT_DEBUG=1 logs last 5 non-empty lines of each captured pane to stderr

7.15 commit

agent-doc commit <FILE> — selective commit with auto-generated timestamp.

  1. Load the snapshot for the file (the document state after the last agent-doc write)
  2. If snapshot exists: a. Add (HEAD) suffix to all new markdown headings (any level #######) not present in git HEAD. Falls back to bold-text pseudo-headers (**...** on its own line) when no markdown headings are found. b. Write the modified snapshot to git's object database via git hash-object -w --stdin c. Stage via git update-index --add --cacheinfo 100644,<hash>,<file> — working tree is NOT modified d. Result: snapshot content (agent response) is committed; user edits in the working tree stay uncommitted
  3. If no snapshot: fall back to git add -f <file> (stages entire file)
  4. git commit -m "agent-doc(<stem>): <timestamp>" --no-verify
  5. On successful commit: write vcs-refresh.signal to .agent-doc/patches/ — the IDE plugin watches this and triggers VcsDirtyScopeManager.markEverythingDirty() + VFS refresh so git gutter updates immediately

HEAD marker: The committed version has (HEAD) appended to new root-level headings. When no markdown headings exist, bold-text pseudo-headers (**...** on its own line) receive the marker instead. The working tree does not have these markers. This creates a single modified-line gutter (blue) at each heading — a visual boundary between committed agent response and uncommitted user input.

Duplicate heading detection: Headings are identified as "new" by comparing occurrence counts between the current content and git HEAD. A heading is new if it appears more times in the current content than in HEAD. This correctly handles duplicate heading text across exchange cycles (e.g., multiple ### Re: Implementation complete headings from different responses).

Post-commit cleanup: After a successful commit, (HEAD) markers are stripped from headings and bold-text pseudo-headers in both the snapshot and the working tree file. This prevents stale markers from accumulating across commits.

7.16 skill

agent-doc skill install — write the bundled SKILL.md to .claude/skills/agent-doc/SKILL.md in the current project. Idempotent (skips if content matches).

agent-doc skill check — compare installed skill vs bundled version. Exit 0 if up to date, exit 1 if outdated or missing.

The bundled SKILL.md contains an agent-doc-version frontmatter field set to the binary's version at build time. When the skill is invoked via Claude Code, the pre-flight step compares this field against the installed binary version (agent-doc --version). If the binary is newer, agent-doc skill install runs automatically to update the skill before proceeding.

7.17 outline

agent-doc outline <FILE> [--json] — display markdown section structure with line counts and approximate token counts.

  1. Read file, skip YAML frontmatter
  2. Parse #-prefixed headings into a section tree
  3. For each section: heading text, depth, line number, content lines, approximate tokens (bytes/4)
  4. Content before the first heading appears as (preamble)

Default output: indented text table. --json outputs a JSON array of section objects (heading, depth, line, lines, tokens).

7.18 upgrade

agent-doc upgrade — check crates.io for latest version, upgrade via GitHub Releases binary download → cargo install → pip install (cascade).

Startup version check: On every invocation (except upgrade itself), warn_if_outdated queries crates.io (with a 24h cache at ~/.cache/agent-doc/version-cache.json) and prints a one-line stderr warning if a newer version is available. Errors are silently ignored so normal operation is never blocked.

7.19 plugin

agent-doc plugin install <EDITOR> — download and install the editor plugin from the latest GitHub Release.

agent-doc plugin update <EDITOR> — update an installed plugin to the latest version.

agent-doc plugin list — list available editor plugins and their install status.

Supported editors: jetbrains, vscode. Downloads plugin assets from GitHub Releases (btakita/agent-doc). Prefers signed assets (*-signed.zip) when available, falling back to unsigned. Auto-detects standard plugin directories for each editor (e.g., JetBrains plugin dir via idea.plugins.path or platform defaults, VS Code ~/.vscode/extensions/).

7.20 sync

agent-doc sync --col <FILES>,... [--col <FILES>,...] [--window W] [--focus FILE] — declarative 2D layout sync.

Mirrors a columnar editor layout in tmux. Each --col is a comma-separated list of files. Columns arrange left-to-right; files stack top-to-bottom within each column.

Pre-sync file resolution: Before the layout algorithm runs, sync parses file paths from --col args and resolves each file. Files without a session UUID in frontmatter are treated as unmanaged and skipped (no auto-initialization of frontmatter). Only agent-doc claim adds session UUIDs. Files with session UUIDs are always treated as registered, even if the registry entry was pruned (dead pane). This enables the declarative layout flow: navigating to a file in a split creates a tmux pane regardless of registry state. For managed files whose registered pane is in a stash window, sync rescues the pane back to the agent-doc window (via swap-pane, falling back to join-pane) — preserving the existing Claude session context. Only if rescue fails, or if no alive pane exists at all, does sync auto-start a fresh Claude session (via route::auto_start()).

Build stamp: On each sync invocation, the binary compares its embedded build timestamp (AGENT_DOC_BUILD_TIMESTAMP from build.rs) against .agent-doc/build.stamp. On mismatch (new build detected), all startup locks (.agent-doc/starting/*.lock) are cleared and the stamp is updated. This prevents stale locks from old binary instances from blocking auto-start.

Empty col_args filtering: Before processing, empty strings in col_args are filtered out. The JetBrains plugin sometimes sends phantom empty columns when editor splits change rapidly.

Column memory: .agent-doc/last_layout.json persists a column→agent-doc mapping across syncs. When a column has no agent doc (user switches to a non-session file), sync substitutes the last known agent doc for that column index. This preserves the 2-pane tmux layout when one editor column temporarily shows a non-agent file. The state file is written after each successful sync for columns that contain an agent doc.

No early exits: The full reconcile path always runs regardless of how many panes resolve (0, 1, or 2+). The DETACH phase stashes excess panes from previous layouts. Previous versions had early exits for resolved < 2 that bypassed stashing, leaving orphaned panes visible.

Busy pane guard (layout.rs only): The layout.rs break_pane path checks is_pane_busy() before breaking panes. The sync reconciler's DETACH phase does NOT use a busy pane guard — the SyncOptions.protect_pane callback exists in tmux-router but agent-doc passes default options (no guard). This was changed because the guard caused 3-pane accumulation when users switched documents in the same column. Column memory + stash rescue handle session preservation without the guard.

Reconciliation algorithm (attach-first order):

  1. SNAPSHOT — query current pane order in target window
  2. FAST PATH — if current order matches desired, done
  3. ATTACHjoin-pane missing desired panes into target window (isolate from shared windows first, then join with correct split direction: -h for columns, -v for stacking)
  4. SELECT — select focus pane before stashing (prevents tmux auto-selecting an unintended pane)
  5. DETACH — stash unwanted panes out of target window (panes stay alive in stash)
  6. REORDER — if all panes present but wrong order, break non-first panes out and rejoin in order
  7. VERIFY — confirm final layout matches desired order

7.21 patch

agent-doc patch <FILE> <COMPONENT> [CONTENT] — replace content in a named component.

  1. Read the document and parse component markers (<!-- agent:name -->...<!-- /agent:name -->)
  2. Find the named component (error if not found)
  3. Read replacement content from the positional argument or stdin
  4. Load component config from .agent-doc/components.toml (if present)
  5. Apply pre_patch hook (stdin: content, stdout: transformed content; receives COMPONENT and FILE env vars)
  6. Apply mode: replace (default), append (add after existing), or prepend (add before existing)
  7. If timestamp is true, prefix entry with ISO 8601 UTC timestamp
  8. If max_entries > 0 (append/prepend only), trim to last N non-empty lines
  9. Write updated document
  10. Save snapshot relative to project root
  11. Run post_patch hook (fire-and-forget; receives COMPONENT and FILE env vars)

Component markers: <!-- agent:name -->...<!-- /agent:name -->. Names must match [a-zA-Z0-9][a-zA-Z0-9-]*.

Component config (.agent-doc/components.toml):

[component-name]
mode = "replace"       # "replace" (default), "append", "prepend"
timestamp = false      # Auto-prefix with ISO timestamp
max_entries = 0        # Trim old entries (0 = unlimited)
max_lines = 0          # Trim to last N lines (0 = unlimited)
pre_patch = "cmd"      # Shell command: stdin→stdout transform
post_patch = "cmd"     # Shell command: fire-and-forget

7.22 write

agent-doc write <FILE> [--baseline-file PATH] [--stream] [--ipc] [--force-disk] [--origin ORIGIN] — apply patch blocks from stdin to a template document.

  1. Read response (patch blocks) from stdin
  2. Parse <!-- patch:name -->...<!-- /patch:name --> blocks
  3. Read document and baseline (from --baseline-file or current file)
  4. Apply patches to baseline:
    • Mode resolution chain applies normally: inline attribute > components.toml > built-in default (replace)
    • All components use their resolved mode (no hardcoded overrides for exchange)
  5. CRDT merge: if the file was modified during response generation, merge content_ours (baseline + patches) with content_current (file on disk) using Yrs CRDT
  6. Atomic write + snapshot save + CRDT state save

--stream flag: Enables CRDT write strategy. Required for template/CRDT documents.

--ipc flag: Writes a JSON patch file to .agent-doc/patches/ for IDE plugin consumption instead of modifying the document directly.

--force-disk flag: Bypasses IPC and writes directly to disk, even when .agent-doc/patches/ exists (plugin installed).

--origin flag: Write-origin identifier for tracing (e.g., skill, watch, stream). Logged to ops.log as write_origin file=<path> origin=<value>. Used with the commit drift warning to trace which process wrote to a file.

IPC-first behavior (v0.17.5): When .agent-doc/patches/ exists (plugin installed) and --force-disk is not set, IPC is tried first. try_ipc() handles component patches; try_ipc_full_content() handles full-document replacement (inline mode). Both check for .agent-doc/patches/ directory existence first — if absent (no plugin active), they return immediately without delay. On IPC timeout (2s), exits with code 75 (EX_TEMPFAIL) instead of falling back to disk write. On IPC success, snapshot is saved from content_ours (baseline + response), NOT the current file on disk. This ensures user edits typed after the boundary marker are not absorbed into the snapshot and remain visible to the next diff. CRDT state is also saved from content_ours.

Write dedup (v0.28.2): All four write paths skip the actual write when the merged/patched content is identical to the current file on disk. On dedup, pending state is cleared and the function returns early. Events are logged to stderr and appended (with backtrace) to /tmp/agent-doc-write-dedup.log.

Pane ownership verification (v0.28.2): verify_pane_ownership() is called at the top of run, run_template, and run_stream. It reads the document's session frontmatter field, looks up the owning pane in the session registry, and compares it to the current tmux pane. If a different pane definitively owns the session, the write is rejected. The check is lenient: it passes silently when not in tmux, when there is no session ID, or when the pane is indeterminate.

Snapshot invariant: All write paths (inline, template, stream, IPC) save the snapshot as content_ours — the baseline with the agent response applied. The working tree file may differ (due to concurrent user edits merged in), but the snapshot always reflects only the agent's contribution. This is the foundation of correct diff detection.

Boundary marker lifecycle (binary-owned): Boundary management is fully deterministic and handled by the binary — never by the SKILL workflow. The apply_patches() function manages the complete lifecycle:

  1. Pre-patch cleanup: Remove ALL stale boundary markers from the entire document (not just the target component)
  2. Fresh insertion: Insert a new boundary at the END of the exchange component (after all user text)
  3. Patch application: Response content is inserted at the boundary position via append_with_boundary()
  4. Post-patch re-insertion: A new boundary is inserted at the END of exchange (after the response)

Boundary marker format: <!-- agent:boundary:{id} --> where {id} is an 8-character hex string (first 8 chars of a UUID v4 with hyphens removed). Short IDs reduce visual noise while maintaining negligible collision probability (~4.3 billion values, self-correcting on collision via next cycle's cleanup).

Invariants:

  • At most ONE boundary marker exists in the document at any time (outside of code blocks)
  • User prompts typed while idle always appear before the response because the fresh boundary is placed after all user text
  • The boundary is the dividing line — content before boundary = before response, content after boundary = after response
  • Boundaries inside fenced code blocks are excluded from all scanning and cleanup operations

Cleanup scope: remove_all_boundaries() scans the ENTIRE document (not just the exchange component) and removes every <!-- agent:boundary:... --> line that is not inside a fenced code block. This prevents stale boundary accumulation from interrupted cycles or plugin bugs. A single fresh boundary is then inserted at end-of-exchange.

Design principle: Boundary insertion was initially implemented in the SKILL workflow (step 1b) but moved to the binary because: (1) it's deterministic (unit-testable with fixed inputs), (2) ALL write paths need it (SKILL, run, stream, watch), (3) non-SKILL paths bypassing step 1b caused stale boundary bugs. Rule: when adding deterministic operations, ask "will ALL write paths need this?" If yes, it belongs in the binary.

IPC boundary: Before building the IPC patch JSON, all IPC write paths call reposition_boundary_to_end() on the current document in memory. This removes stale boundaries and inserts a fresh one at the end of the exchange — the same pre-patch step that apply_patches_with_overrides() performs. The repositioned document is used only for boundary_id extraction (never written to disk by this step). Without this, the IPC path would read the old boundary position (above the user's new prompt), causing responses to be inserted before the prompt. When no explicit patches exist but unmatched content targets exchange/output and a boundary marker is present, try_ipc() synthesizes a boundary-aware exchange patch automatically.

FFI export: agent_doc_reposition_boundary_to_end(doc) — exposed via C ABI for editor plugins. Takes a document string, returns a cleaned document with all stale boundaries removed and a single fresh 8-char boundary at end-of-exchange. Plugins should call this via JNA/FFI rather than reimplementing boundary cleanup logic.

7.23 watch

agent-doc watch [--stop] [--status] [--debounce MS] [--max-cycles N] — watch session files for changes and auto-submit.

  • Watches files registered in sessions.json for modifications (via notify crate)
  • On file change (after debounce), runs submit::run() on the changed file
  • Reactive mode: CRDT-mode documents (agent_doc_write: crdt) are discovered with reactive: true and use zero debounce (Duration::ZERO) for instant re-submit on file change. Reactive paths are tracked in a HashSet<PathBuf>.
  • Loop prevention: changes within the debounce window after a submit are treated as agent-triggered; agent-triggered changes increment a cycle counter; if content hash matches previous submit, stop (convergence); hard cap at --max-cycles (default 3)
  • Busy guard: Before submitting, checks is_busy(file) via the debounce status signal. If the file has an active agent-doc operation (skill write, stream), the watch daemon skips the file. This prevents the watch daemon from competing with skill writes and causing duplicate responses.
  • --stop sends SIGTERM to the running daemon (via .agent-doc/watch.pid)
  • --status reports whether the daemon is running
  • --debounce sets the debounce delay in milliseconds (default 500)

7.24 history

agent-doc history <FILE> — list exchange versions from git history.

  1. Scan git log for commits touching <FILE>
  2. Extract the <!-- agent:exchange --> component content at each commit
  3. Display a list of commits with timestamps and content previews

agent-doc history <FILE> --restore <COMMIT> — restore a previous exchange version.

  1. Read the exchange content from the specified commit
  2. Prepend the old exchange content into the current document's exchange component
  3. The restored content appears above the current exchange, preserving both

7.25 terminal

agent-doc terminal <FILE> [--session NAME] — open an external terminal with tmux attached to the session.

Intended as a fallback for editor plugin commands when no terminal with tmux is open. Prevents duplicate terminal instances by checking for existing attached clients.

  1. Resolve tmux session name: --session flag > tmux_session in document frontmatter > default "0"
  2. Check if session exists and has an attached client — if so, print message and exit (no-op)
  3. If session exists but is detached, open terminal to attach
  4. If session does not exist, open terminal which creates and attaches
  5. Build tmux command: tmux new-session -A -s <session> (attach-or-create)
  6. Resolve terminal command (priority order): a. [terminal] command in ~/.config/agent-doc/config.toml — template with {tmux_command} placeholder b. $TERMINAL env var — used as $TERMINAL -e {tmux_command} c. Error with configuration instructions
  7. Spawn terminal process (detached)

Config example:

[terminal]
command = "wezterm start -- {tmux_command}"

Safety: The {tmux_command} uses tmux new-session -A which attaches to an existing session if it exists, or creates a new one. This means multiple calls to agent-doc terminal are idempotent — they either no-op (client already attached) or attach to the existing session.

7.26 preflight

agent-doc preflight <FILE> — run all pre-agent steps and output JSON.

Combines recover, commit, claims-log check, diff, and document HEAD read into a single call. The SKILL workflow consumes the structured JSON output instead of making separate CLI calls.

Steps (in order):

  1. Recover orphaned pending responses (agent-doc recover)
  2. Commit previous cycle (agent-doc commit)
  3. Read and truncate .agent-doc/claims.log 3c. Check linked docs: inspect links from frontmatter — local files compared by git commit time, URLs fetched via ureq with HTML-to-markdown conversion (htmd), cached in .agent-doc/links_cache/
  4. Compute diff between snapshot and current document
  5. Read document HEAD from disk

Output (JSON to stdout):

{
  "recovered": false,
  "committed": true,
  "claims": [],
  "diff": "unified diff text or null",
  "no_changes": false,
  "document": "full document content",
  "linked_changes": [{"path": "https://example.com", "summary": "content changed (1234 bytes)", "exists": true}]
}
  • no_changes is true when the diff is None (snapshot == document)
  • diff is null when no_changes is true
  • document always contains the current HEAD content
  • linked_changes lists changes in linked docs/URLs since last cycle (omitted when empty)
  • Progress/diagnostic messages go to stderr

URL link processing:

  • URLs (http:///https://) in links frontmatter are fetched with a 10s timeout
  • HTML responses are converted to markdown via htmd (stripping script, style, nav, footer, noscript, svg)
  • Content is cached at .agent-doc/links_cache/<sha256(url)>.txt
  • Changes detected by comparing fresh fetch against cached content

7.27 Preflight Mtime Debounce

The preflight command applies a 500ms mtime debounce gate: if the document's filesystem mtime is less than 500ms old, preflight waits until the file has been idle for at least 500ms. This prevents duplicate preflight runs caused by rapid sequential file saves from the editor.

7.28 Unified Diff Context Radius

Diff output now uses a 5-line context radius (unified diff with 5 lines of surrounding context around each hunk). This gives the agent better surrounding context to understand changes.

7.29 Route --debounce

agent-doc route <FILE> [--debounce MS] — optional debounce flag to coalesce rapid editor triggers. When set, route will skip execution if another route call for the same file completed within the debounce window.

7.30 is_tracked FFI Export

agent_doc_is_tracked(path) — C ABI export for editor plugins. Returns whether the given file path is tracked in sessions.json (has a registered session). Plugins use this via JNA/FFI to conditionally show UI elements for tracked documents.

7.31 Sync provision_pane

The sync path uses provision_pane instead of the standard auto-start. This variant accepts col_args: &[String] and computes split_before via is_first_column(file, col_args), so new panes split in the correct direction for their column position (left-column files split before, right-column files split after). It does not block waiting for the prompt to appear (unlike route which waits up to 30s), avoiding sync blocking on slow Claude startup when arranging multiple panes. The call site in sync.rs passes the col_args slice through from the CLI arguments.

7.32 Sync Swap-Pane Atomic Reconcile

The sync path uses swap-pane atomic transitions via tmux-router. When reconciling pane layout, provision_pane spawns sessions without blocking on prompt detection. A context_session parameter allows cross-session override — sync knows which session it's managing and passes that context to auto_start, which takes priority over the document's tmux_session frontmatter field.

7.33 Sync tmux_session Auto-Repair (Deprecated Field)

Note: tmux_session in frontmatter is deprecated. This auto-repair mechanism exists for backward compatibility during the deprecation period and will be removed when the field is removed.

When context_session (from sync --window) differs from the document's tmux_session frontmatter value, both auto_start and the sync loop automatically repair the frontmatter via direct string replacement. This avoids frontmatter round-trip issues (extra newlines) and ensures the document reflects the actual session assignment after cross-session moves.

7.34 Sync Resync Report-Only

The post-sync resync call runs with --fix disabled (report only). auto_start with context_session intentionally places panes in a different session than the frontmatter originally specified — resync --fix would incorrectly kill these cross-session panes. The resync still reports anomalies for operator awareness.

7.35 Sync Visible-Window Split

When the sync path (skip_wait=true) creates new panes, it prefers splitting in the visible agent-doc window of the target session rather than falling back to any registered pane (which may be in a stash window). This ensures new panes appear where the user can see them. Falls back to find_registered_pane_in_session if no panes exist in the agent-doc window.

7.36 Repair Layout

repair_layout normalizes the tmux window layout before every sync. It receives the tmux handle, session name, and target window name (always "agent-doc"). The plugin always passes --window agent-doc as a fallback so the target window name is known.

Phase 1 — Stash consolidation: Merges all secondary stash windows (stash-* and duplicate stash windows) into a single primary stash window. For each secondary, all panes are joined into the primary via join-pane -dv, targeting the largest pane to avoid "pane too small" errors. Empty secondary windows are killed after pane migration.

Phase 2 — Window rescue: If the target agent-doc window does not exist, attempts to recreate it by finding an alive registered pane in the stash (via sessions::load()), breaking it out with break-pane, and renaming the resulting window to agent-doc.

Phase 3 — Index normalization: Re-lists windows after Phases 1+2 and moves the agent-doc window to index 0 via move-window if it is not already there. This phase always runs.

Fast path: When the target window already exists and there is at most one stash window, Phases 1 and 2 are skipped entirely. Only Phase 3 (index normalization) executes, making the common case a lightweight check.

7.27 session

agent-doc session — show the configured tmux session. agent-doc session set <name> — update config.toml and migrate panes to the new session.

Show: Reads .agent-doc/config.toml tmux_session field and prints it (or "(none)").

Set: Updates config.toml, then moves the agent-doc window and stash window from the old session to the new one via tmux move-window. If the move fails (target session doesn't exist), config is still updated — subsequent route/claim operations will target the new session.

Session resolution (resolve_target_session): Single function in route.rs that all session-targeting code paths use. Priority: (1) context_session from sync --window, (2) config.toml if alive, (3) fallback to current session. Config is auto-updated only when the configured session is dead.

7.28 dedupe

agent-doc dedupe <FILE> — remove consecutive duplicate response blocks.

Detects consecutive ### Re: blocks with identical content (after stripping boundary markers) and removes the duplicate. Updates the snapshot after removal. Idempotent — running twice produces the same result.

8. Session Routing

8.1 Registry

sessions.json maps document session UUIDs to tmux panes:

{
  "cf853a21-...": {
    "pane": "%4",
    "pid": 12345,
    "cwd": "/path/to/project",
    "started": "2026-02-25T21:24:46Z",
    "file": "tasks/plan.md",
    "window": "1"
  }
}

Multiple documents can map to the same pane (one Claude session, multiple files). The window field (optional) enables window-scoped routing — claim --window and layout --window use it to filter panes to the correct IDE window.

8.2 Use Cases

#ScenarioCommandWhat Happens
U1First session for a documentagent-doc start plan.mdCreates tmux pane, launches Claude, registers pane
U2Submit from JetBrains pluginPlugin Ctrl+Shift+Alt+ACalls agent-doc route <file> → sends to registered pane
U3Submit from Claude Code/agent-doc plan.mdSkill invocation — diff, respond, write back
U4Claim file for current session/agent-doc claim plan.mdSkill delegates to agent-doc claim → updates sessions.json
U5Claim after manual Claude start/agent-doc claim plan.mdFixes stale pane mapping without restarting
U6Claim multiple files/agent-doc claim a.md then /agent-doc claim b.mdBoth files route to same pane
U7Re-claim after reboot/agent-doc claim plan.mdOverrides old pane mapping (last-call-wins)
U8Pane dies, plugin submitsPlugin Ctrl+Shift+Alt+Aroute detects dead pane → auto-start cascade
U9Install skill in new projectagent-doc skill installWrites bundled SKILL.md to .claude/skills/agent-doc/
U10Check skill version after upgradeagent-doc skill checkReports "up to date" or "outdated"
U11Permission prompt from pluginPromptPoller polls prompt --allShows bottom bar with numbered hotkeys in IDE
U12Claim notification in sessionSkill reads .agent-doc/claims.logPrints claim records, truncates log
U13Clean up dead pane mappingsagent-doc resyncRemoves stale entries from sessions.json

8.3 Claim Semantics

claim binds a document to a tmux pane, not a Claude session. The pane is the routing target — route sends keystrokes to the pane. Claude sessions come and go (restart, resume), but the pane persists. If Claude restarts on the same pane, routing still works without re-claiming.

Last-call-wins: any claim overwrites the previous mapping for that document's session UUID.

8.4 Stash Window Routing

The stash system preserves running Claude sessions when the user switches editor tabs. Panes are moved to a hidden stash window rather than killed, keeping the Claude session alive for later reuse.

Window-scoped routing: Each editor split maps to a tmux pane in the primary window (@0). When the user switches files, reconcile() swaps panes by detaching unwanted ones into the stash and attaching needed ones back.

Stash lifecycle:

PhaseOperationDetail
DETACHstash_pane()Moves an unwanted pane into the stash window via tmux join-pane
target selectionTargets the LARGEST pane in the stash (by height) to avoid "pane too small" errors
overflowIf join fails, break_pane_to_stash() creates an overflow stash window (also named "stash")
ATTACHreconcile()Joins a stashed pane back into @0 when needed again
RESCUEsync pre-resolutionRescues stashed panes back to agent-doc window via swap-pane/join-pane before layout

Discovery: find_all_stash_windows() returns all stash windows — both the primary stash and any overflow windows. All windows named "stash" or matching "stash-*" (tmux auto-deduplication) are treated as stash windows by is_stash_window_name().

Invariants:

  • Stashed panes keep running — the Claude session remains alive inside
  • Stash windows are named "stash" for consistent discovery
  • The stash window is resized to 200 rows before join operations to prevent minimum-size failures
  • Focus never leaves window @0 during stash operations (-d flags are always set)

Commit write contract: commit() only modifies the snapshot (appending HEAD markers and repositioning the boundary to end-of-exchange). The working tree file is NEVER written by commit(). All visible document changes are delivered via IPC through the plugin Document API. This prevents IDE file-cache conflicts and keystroke loss that would occur if commit() wrote to disk while the user is typing.

Snapshot boundary cleanup: After committing, commit() calls reposition_boundary_to_end() on the snapshot content. This uses remove_all_boundaries() to strip ALL stale boundaries from the snapshot (not just the last one), then inserts a single fresh 8-char boundary at end-of-exchange. The cleaned snapshot is saved back. This guarantees the snapshot never accumulates stale boundaries regardless of plugin behavior.

Boundary reposition lifecycle:

  1. Before IPC patch JSON (reposition_boundary_to_end()): All IPC write paths (run_ipc, try_ipc, IPC-timeout fallback) read the on-disk document and call reposition_boundary_to_end() in memory. This removes ALL stale boundaries and inserts a single fresh one. The repositioned document is used solely to extract boundary_id values — never written to disk. This ensures the boundary_id points to end-of-exchange (after the user's prompt), not the stale mid-exchange position.
  2. During agent-doc write: the reposition_boundary: true IPC flag tells the plugin to move the boundary after applying the response patch. The plugin should call agent_doc_reposition_boundary_to_end() via FFI to ensure identical cleanup logic.
  3. During agent-doc commit: (a) the snapshot is cleaned via reposition_boundary_to_end(), and (b) a standalone IPC signal (try_ipc_reposition_boundary) sends a lightweight reposition-only patch (no content changes, 500ms timeout). This ensures the boundary is at end-of-exchange immediately after commit, so user text typed before the next write cycle is positioned correctly.
  4. If no plugin is active, both IPC signals are silently skipped — the snapshot still has the correct boundary position

8.5 Pane Lifecycle — Binding Invariant

The editor-selected document drives pane resolution. It either finds an existing pane that already claims that document, or provisions a new one. It NEVER commandeers another document's pane.

This is the Binding invariant — the foundational rule of pane management.

Resolution Path

When the user navigates to a document in the editor:

  1. Sync fires — JB plugin sends agent-doc sync --col <file1> --col <file2> --focus <focused_file>
  2. Initializationensure_initialized() runs for each file in col_args:
    • If file is empty (no frontmatter, no content) → auto-scaffold as template with frontmatter + exchange component
    • If file has agent_doc_format but no agent_doc_session → assigns a UUID
    • If no snapshot exists → creates snapshot + git add + git commit
  3. File resolutionresolve_file() reads frontmatter. Files with agent_doc_sessionFileResolution::Registered. Non-.md files or files with content but no frontmatter → Unmanaged.
  4. Reconciliationtmux_router::sync matches the declared layout to tmux panes:
    • Pane exists for this session → focus it (Binding found)
    • Pane in stash → rescue it (swap-pane back to agent-doc window)
    • No pane exists → trigger Provisioning
  5. Provisioningroute::provision_pane() creates a new tmux pane:
    • Splits alongside an existing pane in the agent-doc window
    • Registers the session→pane Binding in sessions.json
    • Starts Claude asynchronously in the new pane

Invariants

InvariantEnforcement
One document per paneRegistry check in claim::run() (line 142-156)
Document drives, pane followsSync resolves files first, then matches to panes
Never commandeer another document's paneauto_start creates new panes; claim validates pane isn't already bound
Stashed panes stay alivejoin-pane moves to stash, doesn't kill
Initialization is idempotentensure_initialized() checks snapshot existence first

Terminology (Domain Ontology)

TermDefinitionModule
BindingDocument→pane association in sessions.jsonclaim.rs, sessions.rs
ReconciliationMatching editor layout to tmux layoutsync.rs
ProvisioningCreating a new pane + starting Clauderoute.rs (auto_start)
InitializationAssigning UUID + snapshot + git trackingsnapshot.rs (ensure_initialized)

9. Git Integration

  • Commit: git add -f {file} (bypasses .gitignore) + git commit -m "agent-doc: {timestamp}" --no-verify
  • Branch: git checkout -b agent-doc/{filestem}
  • Squash: soft-reset to before first agent-doc: commit, recommit as one

9.5 Hook System

Cross-session event coordination via agent-kit hooks (v0.3).

CLI: agent-doc hook fire|poll|listen|gc

  • fire <EVENT> <FILE> — write event JSON to .agent-doc/hooks/<event>/, auto-reads session ID from frontmatter
  • poll <EVENT> [--since SECS] — read events newer than timestamp, clean expired
  • listen [--root PATH] — start Unix socket listener at .agent-doc/hooks.sock
  • gc [--root PATH] — clean expired events across all hooks

Lifecycle hooks fired by agent-doc:

  • post_write — after IPC write succeeds (from write.rs)
  • post_commit — after successful git commit (from git.rs)
  • claim / layout_change — available via CLI, not yet wired into binary paths

Transport: HookTransport trait with FileTransport (default), SocketTransport (Unix socket), ChainTransport (fallback chain). Socket transport connects to .agent-doc/hooks.sock and expects ok\n ack.

Claude Code bridge: Add PostToolUse hook to settings.json:

{"hooks":{"PostToolUse":[{"matcher":"Write|Edit","command":"agent-doc hook fire post_write \"$TOOL_INPUT_FILE\""}]}}

10. Security

agent-doc is designed for single-user, local operation. There is no authentication, authorization, or multi-user access control.

10.1 Threat Model

  • Trusted user, untrusted content. The user is trusted; document content may contain prompt injection from external sources (pasted emails, web pages, chat logs).
  • Local filesystem scope. All data (documents, snapshots, exchange history, links cache) resides on the local filesystem. No network services are exposed.
  • Git as audit trail. All agent responses are committed to git, providing a complete audit trail. However, git history may contain sensitive content if documents reference private data.

10.2 Known Risks

  • Prompt injection via document content. Content pasted from external sources could contain injection attempts. The agent processes all document content as user input with no injection scanning. Mitigation: user awareness; planned content scanning in agent-doc write.
  • --dangerously-skip-permissions exposure. When running with this flag (common in agent-doc sessions via claude_args frontmatter), the agent has full filesystem access. Injected prompts could read files or execute commands.
  • Data divulgence through the response channel. Even with sandboxing, the agent's response IS the output channel. If the model has sensitive data in context, injection can convince it to include that data in the document response. The only real defense is context isolation (see ragie-web-doc security analysis).
  • Links cache may contain sensitive fetched content. URL content fetched via links frontmatter is cached at .agent-doc/links_cache/. This cache is not encrypted and persists until manually cleared.

10.3 Recommendations

  • Use a private git repository for the project containing session documents.
  • Avoid putting secrets (API keys, credentials) in documents or agent context.
  • For shared/collaborative use cases, wait for the planned multi-user security model (access control, session isolation, content scanning).
  • Review agent responses before sharing or publishing document content.

11. Debounce System Gaps and Limitations

The debounce subsystem manages multi-layer typing detection across editor plugins (JetBrains, VS Code, Neovim, Zed) and CLI invocations. While the architecture is sound, several known gaps exist that should inform operators and guide future improvements.

11.1 Mtime Granularity in Route Path

Gap: The route path relies on filesystem mtime for debouncing rapid edits. Filesystem mtime resolution varies:

  • Coarse-grained systems (e.g., HFS+ on macOS): 1-second resolution
  • Fine-grained systems (Linux ext4): ~100ms resolution

When multiple edits occur within the mtime granularity window, route may miss the intermediate change and only detect the final state.

Impact: Rare but real on macOS. User typing very fast may trigger a route call with an editor state that reflects only partial changes.

Mitigation: Route path uses a timeout cap (10× debounce duration) to prevent indefinite hangs. Cross-process typing indicator files provide additional fallback for preflight detection.

Test coverage: test_mtime_granularity_100ms_rapid_edits, test_mtime_granularity_1s_coarse_system. See tests/debounce_gaps_test_plan.rs.

11.2 Untracked File Edge Case

Gap: Files passed to document_changed() are tracked in the in-process LAST_CHANGE map. Files never passed to document_changed() return idle=true immediately (design choice to prevent await_idle blocking forever on unknown files).

This means the CLI cannot distinguish:

  • "File was tracked and has been idle for 2s"
  • "File was never tracked by any plugin"

Impact: Low. The is_tracked() function exists to distinguish these cases, but callers must explicitly check. Non-blocking probes may conservatively assume untracked files are NOT idle.

Mitigation: Use is_tracked(file) before making assumptions about untracked files. Preflight applies both mtime debounce AND typing indicator debounce (redundant but safe).

Test coverage: test_untracked_file_is_tracked_returns_false, test_tracked_file_is_tracked_returns_true, test_untracked_file_is_idle_returns_true, test_probe_pattern_untracked_skips_await. See tests/debounce_gaps_test_plan.rs.

11.3 Hash Collision in Typing Indicator Paths

Gap: Typing indicator files are stored in .agent-doc/typing/<hash> where hash is computed via std::collections::hash_map::DefaultHasher. DefaultHasher is non-cryptographic and designed for hash maps, not for unique identifiers.

Collision probability: ~1 in 4.3 billion for random inputs. Collision is possible but extremely unlikely.

Impact: Very low probability. If collision occurs, the most recent change wins (last write to the shared file). The collision is self-correcting in the next debounce cycle because file timestamps diverge.

Mitigation: No action needed. Collisions are rare and self-healing. If deterministic behavior is required, consider switching to SHA256 hashing in future.

Test coverage: test_hash_collision_no_collisions_for_common_paths (10k paths). test_hash_collision_cleanup_removes_stale_indicators blocked pending GC implementation. See tests/debounce_gaps_test_plan.rs.

11.4 Reactive Mode Assumes CRDT Merge Convergence

Gap: Watch daemon's reactive path (used for agent_doc_write: crdt documents) applies zero debounce, expecting instant re-submit on file change. This assumes the CRDT merge algorithm always converges to a consistent state.

If a CRDT merge produces unexpected results (e.g., text duplication, loss of edits), reactive mode could cause the watch daemon to re-submit with corrupted state repeatedly.

Impact: Medium (data loss risk if CRDT merge is broken). Mitigated by extensive CRDT testing in src/crdt.rs and src/merge.rs.

Mitigation: CRDT implementation is battle-tested with golden-answer test cases (20-30 cases per session diff). See agent-doc eval-runner for continuous validation.

Test coverage: test_reactive_mode_crdt_merge_failure_handling, test_reactive_mode_infinite_loop_prevention — both blocked pending crdt::merge and watch daemon API exposure. See tests/debounce_gaps_test_plan.rs.

11.5 Status File Staleness Timeout (30s Hardcoded)

Gap: Response status files (.agent-doc/status/<hash>) expire after 30 seconds with the assumption: "if no update after 30s, the operation probably crashed."

This timeout is hardcoded in get_status_via_file() and not configurable.

Impact: Medium. Long-running operations (slow CI, expensive LLM calls, network latency) may exceed 30s and be treated as crashed, allowing duplicate submissions.

Mitigation: For long-running scenarios, increase the timeout (currently not exposed via config). The binary also sends set_status() updates, so well-instrumented operations will keep the timeout alive.

Test coverage: test_status_file_staleness_30s_timeout (29s/30s/31s boundary). test_status_file_write_includes_current_timestamp blocked pending status_file_path pub exposure. See tests/debounce_gaps_test_plan.rs.

11.6 Hardcoded Timing Constants in Preflight

Gap: Preflight applies a hardcoded 1500ms debounce window via is_typing_via_file(&file_str, 1500) in preflight.rs:366.

Meanwhile, the poll-based debounce used elsewhere defaults to 500ms. This creates asymmetry:

  • Typing indicator requires 1500ms to expire
  • Poll-based debounce (watch, route) uses 500ms

Not configurable per-document; one-size-fits-all fails for slow CI systems or fast typists.

Impact: Low to Medium. CI systems that take >1500ms to write files will appear to be typing longer than expected, potentially delaying preflight. Conversely, fast typists may experience premature debounce expiry on poll-based paths.

Mitigation: Make timing constants configurable via frontmatter (agent_doc_debounce_ms, agent_doc_typing_indicator_ms). For now, operators can adjust via direct code modification if needed.

Test coverage: test_timing_constants_are_documented (code review pass). test_preflight_timing_1500ms_is_configurable, test_preflight_3s_timeout_is_sufficient_for_debounce blocked pending preflight::run() exposure and agent_doc_debounce_ms frontmatter wiring. See tests/debounce_gaps_test_plan.rs.

11.7 Directory-Walk Double-Pop Bug (Fixed in v0.28)

Gap (now fixed): typing_indicator_path() and status_file_path() contained a double-pop bug: each loop iteration called dir.pop() twice — once unconditionally at the top of the loop, and once at the bottom to advance to the next level. This caused every other directory level to be skipped when walking up to find .agent-doc/.

Files at odd depths from the project root (1, 3, 5 levels) failed to find .agent-doc/ and fell back to writing indicators in the file's immediate parent directory instead. For example, a file at tasks/file.md (1 level deep) would fail while tasks/software/file.md (2 levels deep) succeeded.

Root cause: The loop's end-of-iteration pop() double-counted the level already consumed by the next iteration's leading pop().

Fix: Pop the file component once before entering the loop, then pop exactly once per iteration. This ensures every directory level is checked.

Impact: Cross-process typing detection and status files were silently written to wrong paths for single-level-deep documents. Indicators were effectively lost from the plugin's perspective, causing premature debounce expiry.

Test coverage: typing_indicator_found_for_file_one_level_deep, typing_indicator_found_for_file_two_levels_deep, status_found_for_file_one_level_deep. See src/debounce.rs.

  1. Expose timing constants to frontmatter — Allow per-document control via:

    agent_doc_debounce_ms: 500
    agent_doc_typing_indicator_ms: 1500
    agent_doc_status_timeout_ms: 30000
    
  2. Switch to cryptographic hashing (SHA256) for typing indicator and status file paths to eliminate collision risk entirely.

  3. Make 30s status timeout configurable — either via config.toml or frontmatter.

  4. Mtime fallback in route path — If mtime-detected change is stale (>1s), also check cross-process typing indicator as fallback.

  5. CRDT merge monitoring — Log merge conflicts and convergence issues to .agent-doc/logs/merge.log for operator visibility.

  6. Stale typing indicator cleanup — Old .agent-doc/typing/ files are never deleted. Add a GC step (e.g., in agent-doc gc or on preflight) to remove indicators older than a configurable threshold (default 1h).

IPC

Ontology

IPC (Inter-Process Communication) in agent-doc is a System for delivering document patches from the agent-doc binary to an IDE Plugin without triggering external file change detection. It derives from Context (the IDE's document editing environment) and Resolution (applying changes at the component level rather than the whole-file level).

IPC is the bridge between the CLI Domain (agent-doc binary) and the IDE Domain (JetBrains/VS Code plugin), enabling conflict-free document updates that preserve user Focus — cursor position, selection, and editing flow.

Axiology

External file writes (tempfile + rename) trigger IDE reload behaviors:

  • Cursor displacement — IDE moves caret to the changed region during reload
  • "File changed externally" dialog — blocks user flow, risks data loss on Esc
  • Undo history disruption — external writes break the IDE's undo chain

IPC eliminates all three by routing patches through the IDE's native Document API, where changes are applied in-process with full cursor preservation and undo batching.

Epistemology

Architecture

                        .agent-doc/patches/
                        ┌──────────────────┐
agent-doc write --ipc ──┤  <hash>.json     │
                        │                  │
                        │  { file, patches,│
                        │    unmatched,    │
                        │    baseline }    │
                        └────────┬─────────┘
                                 │
                    NIO WatchService (inotify/FSEvents)
                                 │
                        ┌────────▼─────────┐
                        │  PatchWatcher.kt │
                        │                  │
                        │  1. Read JSON    │
                        │  2. Find Document│
                        │  3. Apply patches│
                        │  4. Save to disk │
                        │  5. Delete JSON  │
                        │     (ACK)        │
                        └────────┬─────────┘
                                 │
                    WriteCommandAction.runWriteCommandAction
                    (holds doc lock, batches undo, preserves cursor)
                                 │
                        ┌────────▼─────────┐
                        │  IntelliJ Doc    │
                        │  (in-memory)     │
                        │                  │
                        │  <!-- agent:X -->│
                        │  patched content │
                        │  <!-- /agent:X-->│
                        └──────────────────┘

Sequence

Binary                    Filesystem              Plugin
  │                          │                      │
  │  write <hash>.json       │                      │
  ├─────────────────────────>│                      │
  │                          │  ENTRY_CREATE event  │
  │                          ├─────────────────────>│
  │                          │                      │ read JSON
  │                          │                      │ find Document
  │                          │                      │ apply patches
  │                          │                      │ save document
  │                          │  delete <hash>.json  │
  │                          │<─────────────────────┤
  │  poll: file gone (ACK)   │                      │
  │<─────────────────────────│                      │
  │                          │                      │
  │  read file, save         │                      │
  │  snapshot + CRDT state   │                      │
  │                          │                      │

Patch JSON Format

{
  "file": "/absolute/path/to/document.md",
  "patches": [
    {
      "component": "exchange",
      "content": "Response content for the exchange component."
    },
    {
      "component": "status",
      "content": "**Version:** v0.17.0 | **Tests:** 303 passing"
    }
  ],
  "unmatched": "Content not targeting a specific component.",
  "baseline": "Document content at response generation time."
}

Each patch targets a <!-- agent:name -->...<!-- /agent:name --> component. The plugin replaces the content between markers with the patch content.

Fallback

If the patch file is not consumed within 2 seconds (plugin not installed or IDE not running), the binary:

  1. Deletes the unconsumed patch file
  2. Falls back to direct CRDT stream write (run_stream())
  3. Logs [write] IPC timeout — falling back to direct write

This makes --ipc safe to use unconditionally in the SKILL.md workflow.

Component Mapping

BinaryPluginPurpose
write.rs:run_ipc()PatchWatcher.ktEnd-to-end IPC flow
template::parse_patches()applyComponentPatch()Patch extraction/application
snapshot::save()FileDocumentManager.saveDocument()Persistence
atomic_write() (patch JSON)NIO WatchServiceFile-based IPC transport

Pattern Expression

IDE Scope

The IPC pattern maps to IntelliJ's threading model:

  • EDT (Event Dispatch Thread): invokeLater schedules patch application on the EDT
  • WriteCommandAction: Acquires the document write lock, groups changes as a single undo unit
  • FileDocumentManager: Flushes the in-memory Document to disk after patching

This is the same mechanism IntelliJ uses for its own refactoring operations — the cursor and selection are preserved because the change originates from within the IDE process, not from an external file modification.

CLI Scope

The binary side is deliberately minimal:

  • Parse patches from stdin (reuses existing template::parse_patches())
  • Serialize to JSON (serde_json)
  • Atomic write of patch file (same atomic_write() used everywhere)
  • Poll for deletion with timeout
  • Update snapshot from the file the plugin saved

No new dependencies, no new IPC protocol, no sockets — just a JSON file in a watched directory.

Versions

agent-doc is alpha software. Expect breaking changes between minor versions.

Use BREAKING CHANGE: prefix in version entries to flag incompatible changes.

0.31.14

  • Binding invariant enforcement (claim.rs): When target pane is already claimed by another document, claim now provisions a new pane instead of erroring. Enforces SPEC §8.5: "never commandeer another document's pane."
  • Sync auto-scaffold (sync.rs): Empty .md files in editor layout are automatically scaffolded with template frontmatter + status/exchange/pending components. Scaffold is saved as snapshot and committed to git immediately.
  • Transfer pending merge (extract.rs): agent-doc transfer now automatically transfers the pending component alongside the named component. Source pending is cleared after merge.
  • SPEC.md updates: §7.10 (claim provisions on occupied pane), §8.5 (empty file auto-scaffold in initialization step).
  • Tests: 6 sync scaffold tests (positive + negative), 2 pending merge tests. 458 total.
  • Runbook: code-enforced-directives.md — behavioral invariants enforced by binary, not agent instructions.

0.31.13

  • Diff-type classification (P1): classify_diff() classifies user diffs into 7 types (Approval, SimpleQuestion, BoundaryArtifact, Annotation, StructuralChange, MultiTopic, ContentAddition). Wired into preflight JSON as diff_type + diff_type_reason. 13 tests.
  • Annotated diff format (P3): annotate_diff() transforms unified diffs into [agent]/[user+]/[user-]/[user~] format. Wired into preflight JSON as annotated_diff. 5 tests.
  • Content-source annotation sidecar (P4): New agent-doc annotate command generates .agent-doc/annotations/<hash>.json mapping each line to agent/user source. SHA256 cache invalidation. GC integration. 6 tests.
  • Reproducible operation logs (P5): New .agent-doc/logs/cycles.jsonl with structured JSONL entries (op, file, timestamp, commit_hash, snapshot_hash, file_hash). Wired into all write paths + git commit. 2 tests.
  • Post-preflight eval diffs (P2): Moved strip_comments to component.rs (shared between binary and eval-runner). eval-runner preprocesses diffs with comment stripping.
  • Transfer-source metadata: PatchBlock now supports attrs field. <!-- patch:name key=value --> attributes parsed and preserved. 3 tests.
  • JB plugin Gson migration: Replaced hand-rolled JSON parser with com.google.gson.JsonParser. Fixes \\n unescape ordering bug. Plugin v0.2.44.
  • SKILL.md enhancements: Diff-type routing (0b), multi-topic --- separators (0c), process discipline clarification.
  • Domain ontology: Interaction Model section in README.md (Directive, Cycle, Diff, Annotation). directive.md kernel node.
  • Module-harness: New ontology-references runbook for cross-referencing domain ontology in module specs.

0.31.12

  • Refactor ensure_initialized(): Split into 3 focused functions: ensure_session_uuid(), ensure_snapshot(), ensure_git_tracked(). Composite ensure_initialized() calls all three.
  • Rename auto_start_no_wait()provision_pane(): Aligns with domain ontology (Provisioning = creating a new pane + starting Claude).
  • Tests: 8 new tests for ensure_session_uuid (3), ensure_snapshot (2), ensure_initialized (1), plus 2 helpers.

0.31.11

  • Sync auto-initialization: ensure_initialized() now called in sync's resolve_file. Files with agent_doc_format but no session UUID get one assigned automatically on editor navigation. Fixes: files created by skills (granola import) are no longer invisible to sync.
  • Binding invariant spec: SPEC.md section 8.5 documents the pane lifecycle invariant — document drives pane resolution, never commandeers another document's pane.
  • Domain ontology: README.md now has Document Lifecycle, Pane Lifecycle, and Integration Layer ontology tables (Binding, Reconciliation, Provisioning, Initialization).
  • Module docs: sync.rs, claim.rs, snapshot.rs, route.rs updated with ontology terminology.

0.31.10

  • Auto-init for new documents: ensure_initialized() in snapshot.rs — claim and preflight now auto-create snapshot + git baseline for files entering agent-doc. No more untracked files after import.
  • Cross-process typing detection: FFI exports agent_doc_is_typing_via_file and agent_doc_await_idle_via_file for CLI tools running in separate processes. is_idle and await_idle now bridge to file-based indicator when untracked in-process.
  • Diff stability fix: wait_for_stable_content counter now tracks consecutive stable reads across outer iterations (was resetting within each pass).
  • IPC error propagation: ipc_socket::send_message now returns proper errors instead of swallowing connection/timeout failures as Ok(None).
  • Template patch boundary fix: Improved boundary marker handling in apply_patches_with_overrides.
  • CI/build: make release target, idempotent release workflows, version-sync check in make check.

0.31.9

  • Transfer-extract runbook: New bundled runbook for cross-file content moves (agent-doc transfer/extract). Installed via skill install.
  • Compact-exchange runbook update: Added note about preserving unanswered user input during compaction.
  • SKILL.md Runbooks section: Added runbook links to SKILL.md so the skill knows about transfer/extract/compact procedures.
  • Housekeeping: Gitignore .cargo/config.toml, resolve clippy warnings, remove accidentally committed files.

0.31.8

  • CI fix: Removed path = "../tmux-router" override from Cargo.toml. CI runners don't have the local submodule; uses crates.io dependency exclusively.

0.31.7

  • Stash-bounce fix: Removed return_stashed_panes_bulk() from automatic prune() path. Active panes now stay in stash until the reconciler explicitly needs them, eliminating the stash→return→stash loop that caused visible pane bouncing.
  • Sync file lock: Added flock on .agent-doc/sync.lock to serialize concurrent sync calls. Prevents race conditions when rapid tab switches fire overlapping syncs.
  • Route sync removal: Removed redundant sync::run_layout_only from Route command dispatch and sync_after_claim from route.rs. The JB plugin's EditorTabSyncListener is now the sole authority for layout sync.
  • Diagnostic checkpoints: Added checkpoint logging in sync (post-repair, post-prune, pre-tmux_router) to pinpoint pane state at key transitions.

0.31.6

  • Debounce fix: Default mtime debounce increased from 500ms to 2000ms. Configurable per-document via agent_doc_debounce frontmatter field.
  • Structured logging: Added tracing + tracing-subscriber + tracing-appender. Set AGENT_DOC_LOG=debug to log to .agent-doc/logs/debug.log.<date>. Zero overhead when unset.
  • Pre-response cleanup bug: clear_pending() now deletes pre-response snapshots after successful writes. Previously accumulated indefinitely.
  • Lock file cleanup bug: SnapshotLock::Drop now deletes the lock file (not just unlocks). CRDT lock acquisition cleans stale locks (>1 hour old).
  • agent-doc gc subcommand: Garbage-collects orphaned files in .agent-doc/ directories. Supports --dry-run and --root flags.
  • Auto-GC on preflight: Runs GC once per day via .agent-doc/gc.stamp timestamp check.
  • Cleanup runbook: New runbooks/cleanup.md documenting .agent-doc/ directory structure and cleanup rules.
  • Tracing instrumentation: tracing::debug! at key decision points in sync, route, layout, and resync modules.
  • Source annotations for extract/transfer: agent-doc extract and agent-doc transfer now wrap content with [EXTRACT from ...] or [TRANSFER from ...] blockquote annotations including timestamp.
  • Post-sync session health check: After every sync, verifies the tmux session still exists. Logs CRITICAL if session was destroyed.
  • Route cleanup on failure: When route fails, orphaned panes created during the attempt are killed before the error propagates.

0.31.5

  • Commit on claim: agent-doc claim now commits the file after saving the initial snapshot. Ensures the first prompt appears as a diff against a committed baseline.
  • Auto-setup untracked files: Preflight auto-adds untracked files to git (snapshot + git add), so /agent-doc works on new files without claiming first.
  • VCS refresh after commit: agent-doc commit writes a VCS refresh signal file, prompting IDEs to update their git status display.
  • Preflight --diff-only flag: Omits the full document from preflight JSON output, reducing token usage by ~80% on subsequent cycles.
  • Skill-bundled runbooks: agent-doc skill install now installs runbooks alongside SKILL.md at .claude/skills/agent-doc/runbooks/. First runbook: compact-exchange.md.
  • JetBrains prompt button truncation: maxLabelLen reduced from 45 to 25 characters.
  • Debounce module: New src/debounce.rs for reusable debounce logic.

0.31.4

  • IPC reposition simplified: Removed file-based IPC fallback from try_ipc_reposition_boundary. Boundary reposition now uses socket IPC exclusively (through FFI listener callback). Non-fatal on failure.
  • Inline max_lines=N attribute: Component tags support max_lines=N to trim content to the last N lines after patching. Precedence: inline attr > components.toml > unlimited. Example: <!-- agent:exchange patch=append max_lines=50 -->.
  • Boundary-stripping in watch hash: hash_content() strips boundary markers before hashing, preventing reactive-mode feedback loops where boundary repositions trigger infinite re-runs.
  • Console component scaffolding: agent-doc claim now scaffolds a <!-- agent:console --> component for template-mode documents.
  • HEAD marker cleanup: git.rs strips stray (HEAD) markers from working tree after commit (defensive cleanup).
  • StreamConfig max_lines: agent_doc_stream.max_lines frontmatter field limits console capture lines (default: 50).
  • Tests: 612 total. New: 4 max_lines_* tests in template.rs.
  • Docs: SPEC.md, README.md, CLAUDE.md updated for max_lines and socket-only IPC.

0.31.3

  • Claim snapshot fix: agent-doc claim now saves the initial snapshot with empty exchange content. Existing user text in the exchange becomes a diff on the next run, preventing unresponded prompts from being absorbed into the baseline.
  • Tests: 608 total. New: strip_exchange_content_removes_user_text, strip_exchange_content_preserves_no_exchange.

0.31.2

  • agent-doc dedupe: New command removes consecutive duplicate response blocks. Ignores boundary markers in comparison. Used to fix duplicate responses caused by watch daemon race conditions.
  • Write-origin tracing: --origin flag on agent-doc write logs the write source (skill/watch/stream) to ops.log. Aids diagnosis when snapshot drift occurs.
  • Commit drift warning: Warns when file_len - snap_len > 100 bytes, indicating a possible out-of-band write that bypassed the snapshot pipeline.
  • Watch daemon busy guard: Skips files with active agent-doc operations (is_busy() check), preventing the watch daemon from generating duplicate responses when competing with the skill.
  • PatchWatcher EDT fix: Patch computation moved outside WriteCommandAction. No-op patches skip the write action entirely, eliminating EDT blocking and typing lag.
  • ClaimAction claim+sync: Ctrl+Shift+Alt+C now calls agent-doc claim on the focused file before syncing, handling unclaimed/empty files.
  • Single-char truncation fix: Single characters are treated as potentially truncated in looks_truncated(), requiring 1.5s stability check. Prevents partial typing (e.g., "S" from "Save as a draft.") from triggering premature runs.
  • SKILL.md: All write examples include --origin skill. Version 0.31.2.
  • JetBrains plugin: Version 0.2.40.
  • Tests: 606 total. New: truncated_single_chars, dedupe_* (4 tests).
  • Docs: SPEC.md §7.22 (--origin), §7.23 (busy guard), §7.28 (dedupe). CLAUDE.md module layout.

0.31.1

  • Declarative layout sync: Navigating to a file in a split editor now creates a tmux pane automatically. Files with session UUIDs are always treated as Registered by sync, even without a registry entry (reverses 0.31.0 Unmanaged guard). Auto-start phase also no longer requires registry entries.
  • ClaimAction simplified: JetBrains ClaimAction (Ctrl+Shift+Alt+C) now delegates entirely to SyncLayoutAction — removed 200+ lines of position detection, pane ID extraction, and independent auto-start logic.
  • Claim registry protection: agent-doc claim refuses to overwrite an existing live claim without --force, preventing silent pane corruption from fallback position detection.
  • HEAD marker duplicate fix: add_head_marker uses occurrence counting instead of substring matching, correctly marking new headings even when the same heading text exists earlier in the document.
  • Busy guard removed: EditorTabSyncListener no longer blocks sync when any visible file has an active session. The binary's own concurrency guards (startup locks, registry locks) are sufficient.
  • Build stamp: New build.rs embeds a build timestamp. On sync, the binary compares against .agent-doc/build.stamp and clears stale startup locks on new build detection.
  • Plugin binary resolution fix: EditorTabSyncListener and SyncLayoutAction now pass basePath to resolveAgentDoc(), correctly resolving .bin/agent-doc instead of falling through to ~/.cargo/bin/agent-doc.
  • JetBrains plugin: Version 0.2.38. Requires uninstall→restart→install→restart (structural class changes).
  • Tests: 602 total. New: add_head_marker_duplicate_heading_text.
  • Docs: SPEC.md §7.10 (claim protection), §7.15 (occurrence counting), §7.20 (UUID-always-registered, build stamp). Ontology claim.md updated.

0.31.0

  • agent-doc session CLI: Show/set configured tmux session with pane migration (session_cmd.rs).
  • Stash pane safety: purge_unregistered_stash_panes no longer kills agent processes (agent-doc, claude, node) in stash — only idle shells. Prevents loss of active Claude sessions when registry goes stale.
  • Session resolution consolidation: resolve_target_session() extracts duplicated session-targeting logic from route.rs into a single function. Config.toml is the source of truth; claim/route no longer auto-overwrite it.
  • Stale UUID handling: Files with frontmatter session UUID but no registry entry are treated as Unmanaged by sync — prevents auto-starting sessions for unclaimed files.
  • Unused variable cleanup: Fixed 8 warnings across route.rs and template.rs.
  • Docs: SPEC.md §7.27 (session command), CLAUDE.md module layout updated.
  • Tests: 601 total, 1 new (purge_preserves_unregistered_agent_process_in_stash).

0.30.1

  • FFI agent_doc_is_idle: Non-blocking typing check for editor plugins to query idle state before boundary reposition.
  • JetBrains plugin typing debounce: Boundary reposition deferred until typing stops, using FFI idle check.
  • VS Code koffi FFI bindings: native.ts with koffi-based native bindings for the shared FFI library.
  • VS Code reposition boundary handling: Boundary reposition with typing debounce via FFI idle check.
  • tmux_session config drift fix: route.rs follows pane session, claim.rs updates config to match.
  • 2 new FFI tests: Coverage for agent_doc_is_idle and related FFI surface.
  • Dependencies: tmux-router v0.3.8.

0.30.0

  • Stale baseline guard (component-aware): is_stale_baseline() now parses components and only checks append-mode (exchange, findings). Replace-mode components (status, pending) are skipped. Falls back to prefix check for inline docs. 11 new tests.
  • Busy pane guard: SyncOptions.protect_pane callback in tmux-router DETACH phase + layout.rs. Prevents stashing panes with active agent-doc/claude sessions during layout changes.
  • Auto-start startup lock: .agent-doc/starting/<hash>.lock with 5s TTL prevents double-spawn when sync fires twice in quick succession.
  • Bug 2A fix: IPC snapshot save failure after successful write is now non-fatal with warning. Commit auto-recovers via divergence detection.
  • Bug 2B fix: Removed commit-time divergence detection that was eating user edits into the snapshot.
  • Hook system: agent-doc hook fire/poll/listen/gc CLI. Cross-session event coordination via agent-kit hooks (v0.3). post_write and post_commit events fired from write + commit paths.
  • HookTransport trait: Abstract delivery mechanism with FileTransport, SocketTransport, ChainTransport implementations.
  • Ops logging tests: 2 new tests for .agent-doc/logs/ops.log.
  • Dependencies: agent-kit v0.3 (hooks feature), tmux-router v0.3.7 (SyncOptions).
  • Docs: SPEC.md §6.6/§7.9/§7.20/§9.5, README.md key features, CLAUDE.md module layout.
  • Tests: 595 total (16 new), 0 failures.

0.29.0

  • Links frontmatter: Renamed related_docslinks (backward-compat alias). URL links (http:///https://) are fetched via ureq, converted HTML→markdown via htmd (stripping script/style/nav/footer), cached in .agent-doc/links_cache/, and diffed on each preflight. Non-HTML content passes through unchanged.
  • Session logging: Persistent logs at .agent-doc/logs/<session-uuid>.log with timestamped events for session start, claude start/restart/exit, user quit, and session end.
  • Auto-trigger on restart: After --continue restart, background thread sends /agent-doc <file> via tmux send-keys after 5s delay to re-trigger the skill workflow.
  • Security documentation: README.md top-level security notice + detailed Security section. SPEC.md Section 10 with threat model, known risks, and recommendations.
  • New dependency: htmd v0.5.3 (HTML-to-markdown, ~13 new crates from html5ever ecosystem, no HTTP server).
  • Tests: 7 new tests for URL detection, HTML conversion, boilerplate stripping, cache paths. 361 total, 0 failures.

0.28.3

  • Write dedup boundary fix: Strip <!-- agent:boundary:XXXXXXXX --> markers before dedup comparison. Boundary marker IDs change on each write, causing false negatives in the dedup check (content appeared different when only the boundary ID changed).

0.28.2

  • Write dedup: All 4 write paths (run, run_template, run_stream disk, run_stream IPC) skip the write when merged content is identical to the current file. Dedup events logged to /tmp/agent-doc-write-dedup.log with backtrace.
  • Pane ownership verification: verify_pane_ownership() called at entry of run, run_template, run_stream. Rejects writes when a different tmux pane owns the session (lenient — passes silently when not in tmux or pane is indeterminate).
  • Column memory: .agent-doc/last_layout.json saves column→agent-doc mapping (carried from v0.28.1, now documented).

0.28.1

  • Column memory: .agent-doc/last_layout.json saves column→agent-doc mapping. When a column has no agent doc, sync substitutes the last known agent doc from the state file. Preserves 2 tmux panes when one column switches to a non-agent file.

0.28.0

  • Empty col_args filtering: sync now filters out empty strings from col_args before processing. Fixes phantom empty columns sent by the JetBrains plugin during rapid editor split changes.
  • Sync debug logging: Added /tmp/agent-doc-sync.log trace logging at key sync decision points (col_args, repair_layout, auto-start, pre/post tmux_router::sync pane counts).
  • Post-auto_start stash removed: The explicit stash after auto-start is no longer needed — tmux_router::sync always runs the full reconcile path (no early exits), so excess panes are stashed during the DETACH phase.
  • tmux-router v0.3.6: Early exits removed from sync — the full reconcile path now runs for 0, 1, or 2+ resolved panes uniformly. Previous early exits for resolved < 2 bypassed the DETACH phase, leaving orphaned panes from previous layouts visible.
  • JetBrains plugin v0.2.36: Filter empty columns in SyncLayoutAction.kt

0.27.9

  • tmux-router v0.3.5: Updated dependency — trace logging at key sync decision points + early-exit stash removal (preserves previous-column panes)

0.27.8

  • tmux-router v0.3.4: Updated dependency — early-exit stash now derives session from pane via pane_session() instead of dead doc_tmux_session path
  • VERSIONS.md backfill: Added entries for v0.23.2 through v0.26.6

0.27.7

  • Sync path column-aware split: auto_start_no_wait now accepts col_args and computes split_before via is_first_column(). Previously hardcoded split_before = false, causing new panes to always split alongside the rightmost pane regardless of column position. The sync path (editor tab switches) now matches the route path behavior.

0.27.6

  • Bold-text pseudo-header fallback for (HEAD) marker: add_head_marker() in git.rs now falls back to bold-text lines (**...**) when no markdown headings are found in new content. strip_head_markers() also handles stripping (HEAD) from bold-text lines.
  • SKILL.md header format guidance: Added "Response header format (template mode)" section instructing agents to use ### Re: headers. Bold-text pseudo-headers are supported as a fallback but real headings are preferred for outline visibility and sub-section nesting.

0.27.5

  • Column-aware split target: auto_start_in_session picks the split target based on column position — first pane (leftmost) for left-column files, last pane (rightmost) for right-column files. Fixes 3-pane layout bug where new panes split the wrong existing pane.
  • Early-exit stash: Before the resolved < 2 early return in tmux-router::sync, excess panes in the agent-doc window are now stashed. Previously, old panes from previous layouts stayed visible when only one file resolved.
  • tmux-router v0.3.3: Published with the early-exit stash fix.

0.27.4

  • Rescue stashed panes in sync: sync.rs now rescues stashed panes back to the agent-doc window via swap-pane/join-pane before falling back to auto-start. Preserves Claude session context across editor tab switches.

0.27.3

  • Revert auto-kill: Reverts v0.27.2 auto-kill of idle stashed Claude sessions. The prompt is the normal state of a stashed session waiting to be rescued — not an orphan indicator.

0.27.2

  • Auto-kill idle stashed Claude sessions: Added auto-cleanup in return_stashed_panes_bulk() for stashed panes running agent-doc/claude at the prompt with no return target. (Reverted in v0.27.3 — too aggressive, killed active sessions.)

0.27.1

  • Fix "externally modified" popup: Removed stale boundary disk write that caused spurious file modification notifications in editors.

0.27.0

  • Fix stash rescue deregistration: Fixed pane deregistration during stash rescue operations.
  • Socket IPC: Added ipc_socket module using Unix domain sockets via the interprocess crate for direct binary-to-plugin communication.
  • Bulk resync: return_stashed_panes_bulk() for batch stash rescue operations.

0.26.6

  • FFI sync lock/debounce: Added agent_doc_sync_try_lock/unlock FFI exports for cross-editor concurrency control. Added agent_doc_sync_bump/check_generation for cross-editor event coalescing.
  • Layout debounce fix: LayoutChangeDetector uses generation counter instead of spawning concurrent threads per event.
  • JetBrains plugin v0.2.35: Uses FFI sync primitives with local fallback.

0.26.5

  • Skip no-op IPC reposition: IPC reposition signal skipped when boundary position is unchanged, eliminating ~64% of no-op PatchWatcher operations.
  • Handle inotify overflow: PatchWatcher scans for missed files on inotify OVERFLOW events.
  • CI: crates.io-only dependencies: All path dependencies (instruction-files, tmux-router, agent-kit, module-harness, existence) replaced with crates.io versions in CI workflows.

0.26.4

  • Prompt detection for Claude Code v2.1+: Support numbered list format (N. label) in prompt option parsing alongside bracket format ([N] label).
  • Auto-start PromptPoller: Plugin auto-starts PromptPoller on project open.
  • JetBrains plugin v0.2.32: PromptPoller auto-start, .bin/ path resolution, diagnostic logging.

0.26.3

  • Sync no longer auto-inits frontmatter: Sync returns Unmanaged for files without session UUIDs; only claim adds frontmatter now.
  • Plugin mixed-layout sync: Uses focus-only when non-.md files are in editor splits, preventing stashing.
  • JetBrains plugin v0.2.25: Alt+Space popup, removed ActionPromoter (frees Alt+Enter for native JetBrains intentions).

0.26.2

  • Route single exit point: Refactored route to resolve_or_create_pane() eliminating propagation bugs. sync_after_claim now runs on ALL route paths.
  • Response status signals: File-based status signals (.agent-doc/status/<hash>) for cross-process visibility. FFI: set_status/get_status/is_busy for in-process plugin checks.
  • Auto-init unclaimed files in sync: Sync writes session UUID for unclaimed files.
  • agent_doc_version() FFI export: Runtime version tracking for plugins.
  • JetBrains plugin v0.2.24: is_busy() guard in EditorTabSyncListener + TerminalUtil.

0.26.1

  • Sync layout authority: sync_after_claim uses editor-provided col_args, preventing 3-pane layout regression on file switch.
  • Clippy fixes: doc_lazy_continuation fixes in sync.rs, upgrade.rs. Unused variable fix in tmux-router break_pane_to_stash.
  • SPEC.md updates: Added sections on project config, IPC write verification, and sync layout authority.

0.26.0

  • Kill pane safety: kill_pane refuses to destroy a session's last window (tmux-router v0.3.0).
  • IPC verification: Content verification catches partial plugin application failures. --force-disk cleans stale patches to prevent double-writes.
  • Module harness context: All 53+ modules annotated with Spec/Contracts/Evals doc comments (468 named evals, 68% coverage).
  • Existence-lang ontology: 9 domain terms defined (Document, Session, Component, Boundary, Snapshot, Patch, Exchange, Route, Claim). Dev dependencies: existence v0.4.0, module-harness v0.2.0.
  • README rewrite: Concise GitHub-facing guide.

0.25.15

  • Sync layout repair: Added repair_layout() to fix window index mismatches (agent-doc window not at index 0). Sync tests added for repair skip and move scenarios.
  • Blank line collapse on tmux_session strip: Collapsing 3+ consecutive newlines to 2 when stripping deprecated tmux_session frontmatter field.

0.25.14

  • Sync pane repair: Window index repair, pane state reconciliation, effective window tracking.
  • Resync enhancements: Enhanced dead pane detection and session validation.
  • Route improvements: Improved command routing logic.

0.25.13

  • Install script: Rewritten install.sh with platform detection and improved install paths.
  • Homebrew formula: Added Formula/agent-doc.rb for macOS/Linux Homebrew installation.
  • Deprecate tmux_session frontmatter: Sync strips the field on encounter instead of repairing it. Route auto_start no longer attempts repair.

0.25.12

  • Sync swap-pane atomic reconcile: context_session overrides frontmatter tmux_session, auto-repairs on mismatch.
  • Visible-window split: New panes split in the visible agent-doc window instead of stash.
  • Resync report-only in sync: resync --fix disabled in sync path to preserve cross-session panes.
  • tmux-router v0.2.9: Swap-pane atomic transitions.

0.25.11

  • Tmux-router swap-pane atomic transitions: Pane moves use swap-pane for flicker-free layout changes. CI fix for path dependencies (agent-kit, tmux-router).

0.25.10

  • Preflight mtime debounce: 500ms idle gate before computing diff.
  • Unified diff context: Diff output uses unified format with 5-line context radius.
  • Route --debounce flag: Opt-in mtime polling for coalescing rapid editor triggers.
  • is_tracked FFI export: For editor plugins to check file tracking status.
  • Sync no-wait auto-start: auto_start_no_wait for non-blocking session creation during sync.
  • JetBrains plugin v0.2.21: Sync logging improvements.

0.25.9

  • is_tracked() FFI export: Conservative debounce on untracked files (fallback to local tracking).
  • Untracked file debounce fix: Untracked files no longer bypass debounce.
  • JetBrains plugin v0.2.20: is_tracked binding + FFI logging tags.

0.25.8

  • Preflight debounce: Mtime-based 500ms idle gate before computing diff.
  • Unified diff context: Switch diff output to unified format with 5-line context radius.
  • Route --debounce: New flag for opt-in mtime polling to coalesce rapid editor triggers.
  • Truncation detection fix: Smarter dot handling for domain fragments in looks_truncated.

0.25.7

  • Rename submit to run: submit.rs renamed to run.rs; all internal "submit" terminology updated to "run".
  • FFI debounce module: document_changed() + await_idle() FFI exports for editor-side debounce.
  • Route sync fix: Route calls sync::run_layout_only() to prevent auto-start race conditions.
  • JetBrains plugin v0.2.19: FFI debounce, conditional typing wait, layout-only sync.

0.25.6

  • Route --col/--focus args: Declarative layout sync from the route command. Plugin sendToTerminal passes editor layout in a single CLI call.
  • Layout change detection: LayoutChangeDetector using ContainerListener with 5s fallback poll in the JetBrains plugin.
  • EDT-safe threading: Plugin uses invokeLater for Swing reads, background thread for CLI calls.
  • JetBrains plugin v0.2.17.

0.25.5

  • FFI boundary reposition: Export agent_doc_reposition_boundary_to_end() for plugin use.
  • Boundary ID summaries: 8-char hex IDs with optional :summary suffix (filename stem). new_boundary_id_with_summary() wired into all write paths.
  • Snapshot boundary cleanup: Commit path uses remove_all_boundaries(). Working tree cleaned via clean_stale_boundaries_in_working_tree() on commit.
  • JetBrains plugin v0.2.14: FFI-first reposition with Kotlin fallback.

0.25.4

  • Boundary accumulation fix: Plugin repositionBoundaryToEnd removes ALL boundaries, not just the last one.
  • Short boundary IDs: 8 hex chars instead of full UUID (centralized in lib.rs).
  • Autoclaim pruning: Validate file existence, prune stale entries on rename/delete.
  • Sync stale pane detection: Detect alive panes with non-existent registered files (rename), kill stale pane and auto-start new session.

0.25.3

  • Fix IPC boundary reposition for prompt ordering: All IPC write paths call reposition_boundary_to_end() before extracting boundary IDs. Previously the stale boundary position caused responses to appear before the prompt.

0.25.2

  • Fix skill install superproject root resolution: Added resolve_root() to detect git superproject when CWD is in a submodule. skill install/check now writes to the project root, not the submodule's .claude/skills/.

0.25.1

  • IPC boundary reposition from commit: After committing, send an IPC reposition signal to the plugin so it moves the boundary marker to end-of-exchange in its Document buffer. Avoids writing to the working tree (which would lose user keystrokes).

0.25.0

  • agent-doc preflight command: Consolidated pre-agent command (recover + commit + claims + diff + document read) returning JSON for skill consumption.
  • Boundary reposition fix: Snapshot-only reposition prevents losing user input; no working tree writes during reposition.
  • CRDT merge simplification: Removed reorder_agent_before_human(), deterministic client IDs.
  • Pulldown-cmark outline: CommonMark-compliant heading parser for outline.
  • Plugin boundary reposition via IPC: reposition_boundary: true flag in IPC payloads.
  • Stash window routing: Target largest pane, overflow to stash windows.
  • JetBrains plugin v0.2.12: Plugin-side boundary reposition.

0.24.4

  • Deterministic boundary re-insertion in apply_patches: Binary handles boundary re-insertion after checkpoint writes, removing the need for SKILL.md to manually re-insert boundaries.

0.24.3

  • Context session for auto_start: Pass context session to auto_start to prevent routing to the wrong tmux session. Post-sync resync for consistency.

0.24.2

  • SKILL.md step 3b: Added mandatory pending updates check each cycle.
  • plugin install --local: Install JetBrains/VS Code plugins from local build directory.
  • JetBrains plugin v0.2.10: resync --fix on startup.
  • JetBrains plugin v0.2.9: VCS refresh signal fix (ENTRY_MODIFY event).

0.24.1

  • SKILL.md heredoc examples: Updated bundled SKILL.md with heredoc examples for the write command.

0.24.0

  • agent-doc install command: System-level setup that checks prerequisites (tmux, claude) and detects/installs editor plugins.
  • agent-doc init project mode: No-arg init now initializes a project (creates .agent-doc/ directory structure, installs SKILL.md) instead of requiring a file argument.
  • SKILL.md content tests: CLI integration tests for skill install/check content verification.
  • Sync pane guard: Pre-sync alive pane check prevents duplicate session creation.

0.23.3

  • Cross-platform sync pane guard: find_alive_pane_for_file() uses ps(1) instead of /proc for Linux+macOS compatibility. Pre-sync auto-start checks alive panes before creating duplicates.
  • Clippy fixes: Fix collapsible_if warnings in template.rs, git.rs, terminal.rs. Suppress dead_code warnings for library-only boundary functions.

0.23.2

  • Explicit patch boundary-aware insertion: apply_patches_with_overrides() checks for boundary markers when applying explicit patch blocks in append mode, not just unmatched content. Prevents boundary markers from accumulating as orphans.
  • Version bump: Includes all v0.23.1 fixes (IPC snapshot, HEAD marker cleanup, boundary insertion).

0.23.1

  • Boundary-aware insertion for unmatched content: apply_patches_with_overrides() now uses boundary-aware insertion for both explicit append-mode patches and unmatched content routed to exchange/output. Previously only explicit patches used boundary markers; unmatched content used plain append.
  • IPC snapshot correctness: try_ipc() now accepts a content_ours parameter (baseline + response, without user concurrent edits). On IPC success the snapshot is saved from content_ours instead of re-reading the current file, preventing user edits typed after the boundary from being absorbed into the snapshot.
  • IPC synthesized exchange patch: When no explicit patches exist but unmatched content targets exchange/output and a boundary marker is present, try_ipc() synthesizes a boundary-aware component patch so the plugin inserts at the correct position.
  • boundary.insert() cleans stale markers: Before inserting a new boundary marker, insert() strips all existing boundary markers from the document. Prevents orphaned markers accumulating across interrupted sessions.
  • boundary::find_boundary_id_in_component(): New public function. Scans a pre-parsed Component for any boundary marker UUID, skipping matches inside code blocks. Used by template.rs and external callers without re-parsing components.
  • Post-commit working tree cleanup: After git.commit() succeeds, strip_head_markers() is applied to both the snapshot and the working tree file. Ensures (HEAD) markers never appear in the editor — they exist only in the committed version (creating the blue gutter diff).

0.23.0

  • Boundary marker for response ordering: New agent-doc boundary <FILE> command inserts <!-- agent:boundary:UUID --> at the end of append-mode component content. The marker acts as a physical anchor — responses are inserted at the marker position, ensuring correct ordering when the user types while a response is being generated. Replaces the fragile caret-offset approach.
  • Boundary-aware FFI: New agent_doc_apply_patch_with_boundary() C ABI export. JetBrains plugin (NativeLib.kt, PatchWatcher.kt) uses boundary markers with priority over caret-aware insertion.
  • Component parser: boundary marker exclusion: <!-- agent:boundary:* --> comments are now skipped by the component parser (no longer cause "invalid component name" errors).
  • IPC boundary_id: All IPC patch JSON payloads include boundary_id when a boundary marker is present in the target component.
  • SKILL.md: boundary marker step: Updated bundled SKILL.md to call agent-doc boundary <FILE> after reading the document (step 1b).
  • Claim auto-start: JetBrains plugin "Claim for Tmux Pane" action now auto-starts the agent session after successful claim.
  • JetBrains plugin v0.2.8: Boundary-aware patching + claim auto-start.

0.22.2

  • SKILL.md: immediate commit after write: Updated bundled SKILL.md to call agent-doc commit right after agent-doc write, replacing the old "Do NOT commit after writing" instruction. All sessions get the new behavior after agent-doc skill install.
  • Plugin default modes: exchange and findings components now default to append mode in the JetBrains plugin (matching the Rust binary's default_mode()), so <!-- agent:exchange --> works without explicit patch=append.

0.22.1

  • Any-level HEAD markers: (HEAD) marker now matches any heading level (#######), not just ###. Only root-level (shallowest) headings in the agent's appended content are marked.
  • Multi-heading markers: When the agent response has multiple sections, ALL new root headings get (HEAD) markers (comparing snapshot vs git HEAD).
  • VCS refresh signal: After agent-doc commit, writes vcs-refresh.signal to .agent-doc/patches/. Plugin watches for this and triggers VcsDirtyScopeManager.markEverythingDirty() + VFS refresh so git gutter updates immediately.
  • JetBrains plugin v0.2.7: VCS refresh signal handling, cursor-aware FFI, VFS refresh before dirty scope.

0.22.0

  • agent-doc terminal subcommand: Cross-platform terminal launch from editor plugins. Config-first (no hard-coded terminal list): [terminal] command in config.toml with {tmux_command} placeholder. Fallback to $TERMINAL env var. Detects stale frontmatter sessions and scans registry for live panes.
  • Selective commit: agent-doc commit stages only the snapshot content via git hash-object + git update-index, leaving user edits in the working tree as uncommitted. Agent response → committed (no gutter). User input → uncommitted (green gutter).
  • HEAD marker: Committed version of the last ### heading gets (HEAD) suffix, creating a single modified-line gutter as a visual boundary and navigation point.
  • First-submit snapshot fix: When no snapshot exists and git HEAD content matches the current file, treat as first submit (entire file is the diff) instead of "no changes detected".
  • Cursor-aware FFI: agent_doc_apply_patch_with_caret() in shared library — inserts append-mode patches before the cursor position. Component::append_with_caret() in component.rs. JNA binding in NativeLib.kt.
  • JetBrains plugin v0.2.7: Cursor-aware append ordering via native FFI with Kotlin fallback. Captures caret offset from TextEditor before WriteCommandAction.

0.21.0

  • agent-doc parallel subcommand: Fan-out parallel Claude sessions across isolated git worktrees. Each subtask gets its own worktree and tmux pane. Results collected as markdown with diffs. --no-worktree for read-only tasks.
  • CRDT post-merge reorder: Agent content ordered before human content at append boundary using Yrs per-character attribution (Text::diff with YChange::identity).
  • README: Added parallel fan-out documentation section.

0.20.3

  • agent-doc claims subcommand: Read, print, and truncate .agent-doc/claims.log in a single binary call. Replaces the shell one-liner (cat + truncate) that was prone to zombie process accumulation when the Bash tool auto-backgrounded it.

0.20.2

  • Fix: numeric session name ambiguity (tmux-router v0.2.8): new_window() now appends : to session name (-t "0:" instead of -t "0"). Without the colon, tmux interprets numeric names as window indices, creating windows in the wrong session. Root cause of persistent session 1 bleedover bug.

0.20.1

  • Session affinity enforcement: Route and auto_start bail with error instead of falling back to current_tmux_session() when tmux_session is set in frontmatter. Prevents pane creation in wrong tmux session.

0.20.0

  • CRDT conservative dedup (#15): Post-merge pass removes identical adjacent text blocks.
  • CRDT frontmatter patches (#16): patch:frontmatter now applied on disk write path (was IPC-only).
  • Binary-vs-agent responsibility documented in CLAUDE.md.

0.19.0

  • ExecutionMode in config.toml: execution_mode = "hybrid|parallel|sequential" in global config.
  • TmuxBatch: Command batching in tmux-router v0.2.7 — reduces flicker via \; separator. select_pane() uses batch (2 → 1 invocation).

0.18.1

  • Revert Gson: Hand-written JSON parser restored in JetBrains plugin (Gson causes ClassNotFoundException).
  • H2 scaffolding: claim scaffolds h2 headers before components for IDE code folding.
  • SKILL.md: Canonical pattern documented — h2 header before every component.

0.18.0

  • agent-doc undo: Restore document to pre-response state (one-deep).
  • agent-doc extract: Move last exchange entry between documents.
  • agent-doc transfer: Move entire component content between documents.
  • Pre-response snapshots: Saved before every write for undo support.

0.17.30

  • Immutable session binding: claim refuses to overwrite tmux_session unless --force. Prevents cross-session pane swapping.

0.17.29

  • JNA FFI integration: NativeLib.kt JNA bindings for JetBrains plugin with Kotlin fallback.
  • agent_doc_merge_frontmatter(): New FFI export for frontmatter patching.
  • agent-doc lib-path: Print path to shared library for plugin discovery.
  • VS Code prepend mode: Fixed missing prepend case in applyComponentPatch().

0.17.28

  • Validate tmux_session before routing: Guard against routing to a non-existent tmux session.

0.17.27

  • Plugin code-block fix: JetBrains and VS Code plugins skip component tags inside fenced code blocks. JB plugin 0.2.4, VSCode 0.2.2.

0.17.26

  • PLUGIN-SPEC docs update: Document recent plugin features in PLUGIN-SPEC.

0.17.25

  • Stash else-branch fix: Fix else-branch stash logic. Use diff --wait for truncation detection.

0.17.24

  • Pulldown-cmark for code range detection: Replace hand-rolled code span/fence parser with pulldown-cmark in component parser. Stash overflow panes instead of creating new windows.

0.17.23

  • Stash overflow fix: Overflow panes stashed instead of creating new tmux windows.

0.17.22

  • UTF-8 corruption fix: Sanitize component tags in response content before writing to prevent UTF-8 corruption in sanitize_component_tags.

0.17.21

  • Indented fenced code blocks: Component parser skips markers inside indented fenced code blocks. Scaffold agent:pending in claim for template documents.

0.17.20

  • BREAKING CHANGE: Rename mode to patch for inline component attributes (patch=append|replace). mode= accepted as backward-compatible alias.

0.17.19

  • Split-window in auto_start: Use split-window instead of new-window for auto-started Claude sessions. Resync tests added.

0.17.18

  • Resync --fix enhancements: Detect wrong-session panes and wrong-process registrations. Renamed --dangerously-set-permissions to --dangerously-skip-permissions.

0.17.17

  • Parse fix: parse_option_line matches [N] bracket format only. Fix find_registered_pane_in_session lookup.

0.17.16

  • Cursor editor support: Add Cursor as a supported editor. claude_args frontmatter field for custom CLI arguments. Tmux session routing fix. VS Code extension bumped to v0.2.1.

0.17.15

  • Route/sync improvements: Routing and sync refinements for multi-session workflows.

0.17.14

  • Plugin IPC fix: VS Code IPC parity with JetBrains. History command improvements. Documentation updates.

0.17.13

  • Fix exchange append mode: Remove hardcoded replace override in run_stream, allowing exchange component to use its configured patch mode.

0.17.12

  • Inline component attributes: <!-- agent:name mode=append --> — patch mode configurable directly on the component tag.

0.17.11

  • History command: agent-doc history shows exchange version history from git with restore support. IPC-priority writes with --force-disk flag to bypass.

0.17.10

  • Default component scaffolding: Auto-scaffold missing components on claim. Append-mode exchange default. Route flash notification via tmux display-message.

0.17.9

  • Fix CRDT character interleaving: Switch to line-level diffs to prevent character-level interleaving artifacts.

0.17.8

  • Template parser code block awareness: Component markers inside fenced code blocks are now skipped by the template parser.

0.17.7

  • Fix CWD drift: Recover and claim commands no longer drift from the project root working directory.

0.17.6

  • Documentation update: Align docs with IPC-first write architecture from v0.17.5.

0.17.5

  • IPC-first writes: All write paths (run, stream, write) try IPC to the IDE plugin via .agent-doc/patches/ before falling back to disk. Exit code 75 on IPC timeout.

0.17.4

  • Tmux pane orientation fix: Arrange files side-by-side (horizontal split) instead of stacking vertically.

0.17.3

  • Fix CRDT character-level interleaving bug: Resolve text corruption caused by character-level merge conflicts in CRDT state.

0.17.2

  • Fix CRDT shared prefix duplication bug: Prevent duplicate content when CRDT documents share a common prefix.

0.17.1

  • Fix stream snapshot: Use replace mode for exchange component in stream snapshot writes.

0.17.0

  • BREAKING CHANGE: agent_doc_format/agent_doc_write split: Replace agent_doc_mode with separate format (inline|template) and write strategy (disk|crdt) fields. IPC write path for IDE plugins. Layout fix.

0.16.1

  • Native compact for template/stream mode: agent-doc compact now works natively with template and stream mode documents.

0.16.0

  • Reactive stream mode: CRDT-mode documents get zero-debounce reactive file-watching from the watch daemon. Truncation detection and CRDT stale base fix.

0.15.1

  • Patch release: Version bump and minor fixes.

0.15.0

  • CRDT-based stream mode: Real-time streaming output with CRDT conflict-free merge (agent-doc stream). Chain-of-thought support with optional thinking_target routing. Deferred commit workflow. Snapshot resolution prefers snapshot file over git.

0.14.9

  • Multi-backtick code span support: find_code_ranges handles multi-backtick code spans (e.g., `` and ```).

0.14.8

  • Code-range awareness for strip_comments: Fix <!-- --> stripping inside code spans and fenced blocks. Stash window purge for orphaned idle shells.

0.14.7

  • Bidirectional convert: agent-doc convert works in both directions (inline <-> template). Autoclaim sync improvements.

0.14.6

  • Auto-sync on lazy claim: Automatically sync tmux layout after lazy claim in route. Plugin autocomplete fixes for JetBrains.

0.14.5

  • agent-doc commands subcommand: List available commands. Plugin autocomplete for JetBrains/VS Code. Remove auto-prune (moved to resync). Purge orphaned claude/stash tmux windows in resync.

0.14.4

  • Claim pane focus: Focus the claimed pane after agent-doc claim. convert handles documents with pre-set template mode.

0.14.3

  • Autoclaim pane refresh: Refresh pane info during autoclaim. Template missing-component recovery on write.

0.14.2

  • Skill reload via --reload flag: Compact and restart skill installation in a single command.

0.14.1

  • SKILL.md workflow fix: Move git commit to after write step in the skill workflow to prevent committing stale content.

0.14.0

  • Route focus fix + claim defaults to template mode: New documents claimed via agent-doc claim default to template format. agent-doc mode CLI command for inspecting/changing document mode.

0.13.3

  • Bump tmux-router to v0.2.4: Fix spare pane handling in tmux-router dependency.

0.13.2

  • Sync registers claims: agent-doc sync registers claims for previously unregistered files in the layout.

0.13.1

  • Sync updates registry file paths: Fix autoclaim file path tracking when sync moves files between panes.

0.13.0

  • Autoclaim + git-based snapshot fallback: Automatic claim on route when no claim exists. Fall back to git for snapshot when snapshot file is missing.

0.12.2

  • Exchange component defaults to append mode: The exchange component uses append patch mode by default instead of replace.

0.12.1

  • Lazy claim fallback: agent-doc claim without --pane falls back to the active tmux pane.

0.12.0

  • agent-doc convert command: Convert between inline and template document formats. Lazy claim support. agent-doc compact for git history squashing. Exchange component as default template target.

0.11.2

  • Strip trailing ## User heading: Also strip trailing ## User heading from agent responses (complement to v0.11.1).

0.11.1

  • Strip duplicate ## Assistant heading: Remove duplicate ## Assistant heading from agent responses when already present in the document.

0.11.0

  • Append-friendly merge strategy: Improved 3-way merge strategy optimized for append-style document workflows.

0.10.1

  • Bundle template-mode instructions in SKILL.md: SKILL.md now includes template-mode workflow instructions for the Claude Code skill.

0.10.0

  • BREAKING CHANGE: Rename response_mode to agent_doc_mode: Frontmatter field renamed with backward-compatible aliases.

0.9.10

  • Code-span parser fix: Component parser skips markers inside fenced code blocks and inline backticks. Template input/output component support.

0.9.9

  • Template mode + compaction recovery: New template mode for in-place response documents using <!-- agent:name --> components. Durable pending response store for crash recovery during compaction.

0.9.8

  • Relocate advisory locks: Move document advisory locks from project root to .agent-doc/locks/.

0.9.7

  • agent-doc write command: Atomic response write-back command for use by the Claude Code skill.

0.9.6

  • Race condition mitigations: Stale snapshot recovery, atomic file writes, and various race condition fixes.

0.9.5

  • Advisory file locking: Lock the session registry during writes. Stale claim auto-pruning.

0.9.4

  • Bump tmux-router to v0.2: Update tmux-router dependency.

0.9.3

  • Bump tmux-router to v0.1.3: Fix stash window handling in tmux-router.

0.9.2

  • agent-doc plugin install CLI: Install editor plugins from GitHub Releases. VS Code extension reaches feature parity with JetBrains.

0.9.1

  • Stash window resize fix: Bump tmux-router to v0.1.2 to fix stash window resize issues.

0.9.0

  • Dashboard-as-document: Component-based documents with <!-- agent:name --> markers, agent-doc patch for programmatic updates, agent-doc watch daemon for auto-submit on file change.

0.8.1

  • Auto-prune registry: Prune dead session entries before route/sync/claim operations.

0.8.0

  • Tmux-router integration: Wire tmux-router as a dependency for pane management. Fix route auto_start bug.

0.7.2

  • Attach-first reconciliation: Sync uses attach-first strategy with auto-register for untracked panes. Column-positional focus. Tmux session affinity.

0.7.1

  • Additive reconciliation: Convergent reconciliation loop (max 3 attempts) with deferred eviction and reorder phase. Nuclear rebuild fallback.

0.7.0

  • Snapshot-diff sync architecture: Rewrite sync to use snapshot-based diffing for tmux layout reconciliation. Dead window handling and column inversion fix.

0.6.6

  • --focus on sync: agent-doc sync accepts --focus flag. Inline hint notification at cursor position in JetBrains plugin.

0.6.5

  • Always use sync --col: Single-file sync uses column mode. Break out unwanted panes. Plugin notification balloon for detected layout.

0.6.4

  • Sync window filtering + layout equalization: Filter sync to target window only. Equalize pane sizes after layout.

0.6.3

  • LayoutDetector fix: Skip non-splitter Container children in JetBrains plugin 3-column layout detection.

0.6.2

  • Fire-and-forget Junie bridge: Junie bridge script resolved automatically. Plugin clipboard handoff for non-tmux editors.

0.6.1

  • Junie agent backend: Add Junie as an agent backend with JetBrains plugin action support.

0.6.0

  • agent-doc sync command: 2D columnar tmux layout synced to editor split arrangement. Dynamic pane groups.

0.5.6

  • Commit message includes doc name: agent-doc commit message format now includes the document filename. agent-doc outline command for markdown section structure with token counts.

0.5.5

  • Window-scoped routing: Route commands scoped to tmux window (not just session). --pane/--window flags. Layout safeguards. JetBrains plugin self-disabling Alt+Enter popup (removes ActionPromoter).

0.5.4

  • Positional claim: agent-doc claim <file> accepts file as positional argument. Editor plugin improvements and SPEC updates.

0.5.3

  • Bundled SKILL.md with absolute snapshot paths: Snapshot paths use absolute paths for reliability. Resync subcommand and claims log documentation.

0.5.2

  • Claim notifications + resync + plugin popup: Notification on claim. agent-doc resync validates sessions.json and removes dead panes. JetBrains and VS Code editor plugins added.

0.5.1

  • Windows build fix: Cfg-gate unix-only exec in start.rs for cross-platform compilation.

0.5.0

  • agent-doc focus and agent-doc layout: Focus a tmux pane for a session document. Layout arranges tmux panes to mirror editor split arrangement.

0.4.4

  • Rename SPECS.md to SPEC.md: Standardize specification filename.

0.4.3

  • Commit CWD fix: Fix working directory for agent-doc commit. SKILL.md prohibition rules.

0.4.2

  • SPEC.md gaps filled: Document comment stripping as skill-level behavior (§4), --root DIR flag for audit-docs (§7.6), agent-doc-version frontmatter field for auto-update detection (§7.12), and startup version check (warn_if_outdated).
  • Flaky test fix: Skill tests no longer use std::env::set_current_dir. Refactored install/check to accept an explicit root path (install_at/check_at), eliminating CWD races in parallel test execution.
  • CLAUDE.md module layout updated: Added claim.rs, prompt.rs, skill.rs, upgrade.rs to the documented module layout.

0.4.1

  • SKILL.md: comment stripping for diff: Strip HTML comments (<!-- ... -->) and link reference comments ([//]: # (...)) before comparing snapshot vs current content. Comments are a user scratchpad and no longer trigger agent responses.
  • SKILL.md: auto-update check: New agent-doc-version frontmatter field enables pre-flight version comparison. If the installed binary is newer, agent-doc skill install runs automatically before proceeding.
  • PromptPanel: JDialog to JLayeredPane overlay: Replace JDialog popup with a JLayeredPane overlay in the JetBrains plugin, eliminating window-manager popup leaks.

0.4.0

  • agent-doc claim <file>: New subcommand — claim a document for the current tmux pane. Reads session UUID from frontmatter + $TMUX_PANE, updates sessions.json. Last-call-wins semantics. Also invokable as /agent-doc claim <file> via the Claude Code skill.
  • agent-doc skill install: Install the bundled SKILL.md to .claude/skills/agent-doc/SKILL.md in the current project. The skill content is embedded in the binary via include_str!, ensuring version sync.
  • agent-doc skill check: Compare installed skill vs bundled version. Exit 0 if up to date, exit 1 if outdated or missing.
  • SKILL.md updated: Fixed stale $() pattern → agent-doc commit <FILE>. Added /agent-doc claim support.
  • SPEC.md expanded: Added §7.7–7.13 (all commands), §8 Session Routing with use case table (U1–U11), §8.3 Claim Semantics.

0.3.0

  • Multi-session prompt polling: agent-doc prompt --all polls all live sessions in one call, returns JSON array. SessionEntry now includes a file field for document path (backward-compatible).
  • agent-doc commit <file>: New subcommand — git add -f + commit with internally-generated timestamp. Replaces shell $() substitution in IDE/skill workflows.
  • Prompt detection: agent-doc prompt subcommand added in v0.2.0 (unreleased).
  • send-keys fix: Literal text (-l) + separate Enter, new-window -a append flag (unreleased since v0.2.0).

0.1.4

  • agent-doc upgrade self-update: Downloads prebuilt binary from GitHub Releases as the primary upgrade strategy. Falls back to cargo install, then pip install --upgrade, then manual instructions including curl | sh.

0.1.3

  • Upgrade check: Queries crates.io for latest version with a 24h cache. Prints a one-line stderr warning on startup if outdated.
  • agent-doc upgrade: New subcommand tries cargo install then pip install --upgrade, or prints manual instructions.

0.1.2

  • Language-agnostic audit-docs: Replace Cargo.toml-only root detection with 3-pass strategy (project markers → .git → CWD fallback). Scan 28 file extensions across 6 source dirs instead of .rs only.
  • --root CLI flag: Override auto-detection of project root for audit-docs.
  • Test coverage: Add unit tests for frontmatter, snapshot, and diff modules.

0.1.0

Initial release.

  • Interactive document sessions: Edit a markdown document, run an AI agent, response appended back into the document.
  • Session continuity: YAML frontmatter tracks session ID, agent backend, and model. Fork from current session on first run, resume on subsequent.
  • Diff-based runs: Only changed content is sent as a diff, with the full document for context. Double-run guard via snapshots.
  • Merge-safe writes: 3-way merge via git merge-file if the file is edited during agent response. Conflict markers written on merge failure.
  • Git integration: Pre-commit user changes before agent call, leave agent response uncommitted for editor diff gutters. -b flag for auto-branch, --no-git to skip.
  • Agent backends: Agent-agnostic core. Claude backend included. Custom backends configurable via ~/.config/agent-doc/config.toml.
  • Commands: run, init, diff, reset, clean, audit-docs.
  • Editor integration: JetBrains External Tool, VS Code task, Vim/Neovim mapping.