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
- You edit a markdown document with
## User/## Assistantblocks - You run via hotkey or CLI — agent-doc computes a diff since the last run
- The agent responds — the response is appended as a new
## Assistantblock - 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:
similarcrate (pure Rust) - Serialization:
serde+serde_yaml/serde_json/toml - Hashing:
sha2for 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
| Field | Description |
|---|---|
default_agent | Agent backend used when not specified elsewhere |
[agents.NAME] | Agent backend configuration |
command | Executable name or path |
args | Arguments passed before the prompt |
result_path | JSON path to extract the response text |
session_path | JSON path to extract the session ID |
Resolution order
The agent backend is resolved in this order:
--agentCLI flagagent:field in document frontmatterdefault_agentin config- 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.
| Flag | Description |
|---|---|
-b | Auto-create branch agent-doc/<filename> on first run |
--agent NAME | Override agent backend |
--model MODEL | Override model |
--dry-run | Preview diff and prompt size without sending |
--no-git | Skip git operations (branch, commit) |
Flow:
- Compute diff from snapshot
- Build prompt (diff + full document)
- Pre-commit user changes (unless
--no-git) - Send to agent
- Append response as
## Assistantblock - 3-way merge if file was edited during response
- 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.
| Flag | Description |
|---|---|
| (none) | Detect prompts for a single file |
--all | Poll all live sessions, return JSON array |
--answer N | Answer 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.
| Subcommand | Description |
|---|---|
install | Write the bundled SKILL.md to .claude/skills/agent-doc/SKILL.md. Idempotent. |
check | Compare 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 -->.
| Argument | Description |
|---|---|
FILE | Path to the document |
COMPONENT | Component name (e.g., status, log) |
CONTENT | Replacement content (reads from stdin if omitted) |
Behavior depends on .agent-doc/components.toml config:
| Config | Default | Description |
|---|---|---|
mode | replace | replace, append, or prepend |
timestamp | false | Auto-prefix with ISO timestamp |
max_entries | 0 | Trim entries in append/prepend (0 = unlimited) |
pre_patch | none | Shell hook: transform content (stdin → stdout) |
post_patch | none | Shell 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.
| Flag | Default | Description |
|---|---|---|
--stop | Stop the running watch daemon | |
--status | Show daemon status | |
--debounce | 500 | Debounce delay in milliseconds |
--max-cycles | 3 | Max 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
| Field | Required | Default | Description |
|---|---|---|---|
session | no | (generated on first run) | Session ID for continuity |
agent | no | claude | Agent backend to use |
model | no | (agent default) | Model override |
branch | no | (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
| Mode | Behavior |
|---|---|
replace | Full content replacement (default) |
append | New content added at the bottom of existing content |
prepend | New content added at the top of existing content |
Options
| Option | Type | Default | Description |
|---|---|---|---|
mode | string | "replace" | Patch mode |
timestamp | bool | false | Auto-prefix entries with ISO timestamp |
max_entries | int | 0 | Auto-trim old entries in append/prepend modes (0 = unlimited) |
pre_patch | string | none | Shell command to transform content before patching |
post_patch | string | none | Shell 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) |
- An external script calls
agent-doc patchto update a component - The watch daemon detects the file change
- Watch triggers
agent-doc runwhich diffs and sends to the agent - 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.mdcommits 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
- Read document and load snapshot (last-known state from previous run)
- Compute diff — if empty, exit early (double-run guard)
- Pre-commit user's changes via
git add -f+git commit(baseline for diff gutters) - Send diff + full document to agent, resuming session if one exists
- Build response — original content + session ID update +
## Assistantblock +## Userblock - Check for concurrent edits — re-read the file
- Merge if needed — 3-way merge via
git merge-fileif file changed during agent response - Write merged content back to file
- 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
| Flag | Behavior |
|---|---|
-b | Auto-create branch agent-doc/<filename> on first run |
| (none) | Pre-commit user changes to current branch |
--no-git | Skip 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:
| Field | Value |
|---|---|
| Program | agent-doc |
| Arguments | run $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
:ereload. - 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"
| Field | Description |
|---|---|
command | Executable name or path |
args | Arguments passed before the prompt |
result_path | JSON path to extract the response text from output |
session_path | JSON 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
clapderive for CLI argument parsing - Use
serdederive for all data types - Use
serde_yamlfor frontmatter parsing - Use
similarcrate for diffing (pure Rust, no shelldiffdependency) - Use
serde_jsonfor agent response parsing - Use
std::process::Commandfor git operations (notgit2) - Use
toml + serdefor config file parsing - No async — sequential per-run
- Use
anyhowfor application errors
Instruction files
CLAUDE.mdis 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.mdwhen agent-doc functionality changes (commands, formats, algorithms).
Workflow
Follow a research, plan, implement cycle:
- Research — Read the relevant code deeply.
- Plan — Write a detailed implementation plan.
- Implement — Execute the plan. Run
make checkcontinuously. - Precommit — Run
make precommitbefore 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).appendaccepted as backward-compat alias forinline.agent_doc_write: Write strategy —mergeorcrdt(default:crdt).agent_doc_mode: Deprecated. Single field mapping:append→ format=append,template→ format=template,stream→ format=template+write=crdt. Explicitagent_doc_format/agent_doc_writetake precedence. Legacy aliases:mode,response_mode.agent: Agent backend name (overrides config default)model: Model override (passed to agent backend)branch: Reserved for branch trackingclaude_args: Additional CLI arguments for theclaudeprocess (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:
| Component | Default patch | Description |
|---|---|---|
exchange | append | Conversation history — each cycle appends |
findings | append | Accumulated research data — grows over time |
status | replace | Current state — updated at milestones |
pending | replace | Task queue — auto-cleaned each cycle |
output | replace | Latest agent response only |
input | replace | User prompt area |
| (custom) | replace | All 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-docClaude 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
- CLI
--agentflag - Frontmatter
agentfield - Config
default_agent - 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):
- Frontmatter:
claude_args: "--dangerously-skip-permissions"in the document's YAML frontmatter - Global config:
claude_args = "--dangerously-skip-permissions"in~/.config/agent-doc/config.toml - 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:
- File-change check: If the document file is unchanged on disk, the plugin failed to apply — falls back to disk write.
- 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.
- Force-disk cleanup: When
--force-diskis 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:
| State | Location | Owner | Purpose |
|---|---|---|---|
| Snapshot | .agent-doc/snapshots/<hash>.md | Binary | Last committed agent state. Used by diff::compute() to detect user changes since last response. |
| Baseline | .agent-doc/baselines/<hash>.md | Binary (preflight) | Document at start of response generation. Common ancestor for 3-way/CRDT merge. Saved by preflight after commit (step 2b). |
| File on disk | The document file | Editor (auto-save) | Last editor save. Lags behind the editor buffer. Used by non-IPC write paths. |
| Editor buffer | Editor memory | Editor (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 containssnapshot + (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]
- 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.
- Prerequisite check (unless
--skip-prereqs): verifiestmuxandclaudeare onPATH; prints ok or MISSING with install hint for each. Does not fail — only warns. - Editor plugin install (unless
--skip-plugins):- If
--editoris 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
--editorand exits without error. - Calls
crate::plugin::install(editor)for each detected editor. - Prints a summary of installed and failed editors.
- If
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.
- Ensure session UUID in frontmatter (generate if missing)
- Read
$TMUX_PANE(must be inside tmux) - Register session → pane in
sessions.json - Exec
claude(replaces process)
7.9 route
agent-doc route <FILE> [--pane P] — route a /agent-doc command to the correct tmux pane.
- Prune stale entries from
sessions.json - Ensure session UUID in frontmatter (generate if missing)
- Look up pane in
sessions.json - If pane alive → send
/agent-doc <FILE>viasend_keys, then Enter verification loop (polls for command text disappearance every 300ms, retries Enter on each poll, up to 5s timeout), focus pane - If pane dead (previously registered) → lazy-claim to active pane in
claudetmux session (or--pane P), register, send command, auto-sync layout for all files in the same window. Unregistered files skip lazy-claim entirely. - If no active pane available → auto-start cascade (see below), register, wait up to 30s for Claude
❯prompt viapane_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_sessionin frontmatter is deprecated. The tmux session is now determined at runtime:--windowargument (sync),current_tmux_session()(route/start), or future.agent-doc/config.tomlsettings. 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):
- 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. - Read
tmux_sessionfrom the document's frontmatter (fall back to defaultclaudesession name) - 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): searchsessions.jsonfor any registered pane alive in the target session.
- Sync path (
- If found →
tmux split-windowalongside that pane (-dbhfor left-column,-dhfor right-column) - If split-window fails → fall back to creating a new window
- 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.
- Ensure session UUID in frontmatter (generate if missing)
- Resolve effective window (see Window Resolution below)
- Determine pane:
--pane Poverrides, else--positionresolves via tmux pane geometry, else$TMUX_PANE - 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:
- Check if window
Wis alive (tmux list-panes -t W) - If alive → use
W(no change) - If dead → scan
sessions.jsonfor entries with matching projectcwdand non-emptywindowfield. For each, check liveness. Use first alive match. - 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— appendsClaimed {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.
- Read session UUID from file's YAML frontmatter (or use
--paneoverride) - Look up pane ID in
sessions.json - Run
tmux select-window -t <pane-id>thentmux 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.
- Resolve each file to its session pane via frontmatter →
sessions.json - If
--windowgiven, filter to panes registered for that window only - Pick the target window (the one containing the most wanted panes; tiebreak: most total panes)
- Break out only registered session panes that aren't wanted (shells and tool panes are left untouched)
- Join remaining wanted panes into the target window (
tmux join-pane) - 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):
- Load
sessions.json, prune entries with dead panes (delegates totmux_router::prune()) - Purge idle stash windows: kill
stash-named windows where all panes run idle shells (zsh,bash,sh,fish) and last activity was >30s ago - Log orphaned
claude/stashwindows (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. Nextrouteauto-starts in the correct session. - Wrong-process panes: removes registry entry only (does not kill the foreign process). Next
routeauto-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 nextsyncorlayoutrejoins 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 listN. label(Claude Code v2.1+) - Returns JSON:
{ "active": bool, "question": str, "options": [...], "selected": int } --answer Nnavigates to option N via arrow keys and confirms with Enter--allpolls all live sessions, returns JSON array ofPromptAllEntryobjects- Debug:
AGENT_DOC_PROMPT_DEBUG=1logs last 5 non-empty lines of each captured pane to stderr
7.15 commit
agent-doc commit <FILE> — selective commit with auto-generated timestamp.
- Load the snapshot for the file (the document state after the last
agent-doc write) - 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 viagit hash-object -w --stdinc. Stage viagit 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 - If no snapshot: fall back to
git add -f <file>(stages entire file) git commit -m "agent-doc(<stem>): <timestamp>" --no-verify- On successful commit: write
vcs-refresh.signalto.agent-doc/patches/— the IDE plugin watches this and triggersVcsDirtyScopeManager.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.
- Read file, skip YAML frontmatter
- Parse
#-prefixed headings into a section tree - For each section: heading text, depth, line number, content lines, approximate tokens (bytes/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
upgradeitself),warn_if_outdatedqueries 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):
- SNAPSHOT — query current pane order in target window
- FAST PATH — if current order matches desired, done
- ATTACH —
join-panemissing desired panes into target window (isolate from shared windows first, then join with correct split direction:-hfor columns,-vfor stacking) - SELECT — select focus pane before stashing (prevents tmux auto-selecting an unintended pane)
- DETACH — stash unwanted panes out of target window (panes stay alive in stash)
- REORDER — if all panes present but wrong order, break non-first panes out and rejoin in order
- VERIFY — confirm final layout matches desired order
7.21 patch
agent-doc patch <FILE> <COMPONENT> [CONTENT] — replace content in a named component.
- Read the document and parse component markers (
<!-- agent:name -->...<!-- /agent:name -->) - Find the named component (error if not found)
- Read replacement content from the positional argument or stdin
- Load component config from
.agent-doc/components.toml(if present) - Apply
pre_patchhook (stdin: content, stdout: transformed content; receivesCOMPONENTandFILEenv vars) - Apply mode:
replace(default),append(add after existing), orprepend(add before existing) - If
timestampis true, prefix entry with ISO 8601 UTC timestamp - If
max_entries > 0(append/prepend only), trim to last N non-empty lines - Write updated document
- Save snapshot relative to project root
- Run
post_patchhook (fire-and-forget; receivesCOMPONENTandFILEenv 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.
- Read response (patch blocks) from stdin
- Parse
<!-- patch:name -->...<!-- /patch:name -->blocks - Read document and baseline (from
--baseline-fileor current file) - 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)
- Mode resolution chain applies normally: inline attribute >
- CRDT merge: if the file was modified during response generation, merge
content_ours(baseline + patches) withcontent_current(file on disk) using Yrs CRDT - 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:
- Pre-patch cleanup: Remove ALL stale boundary markers from the entire document (not just the target component)
- Fresh insertion: Insert a new boundary at the END of the exchange component (after all user text)
- Patch application: Response content is inserted at the boundary position via
append_with_boundary() - 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.jsonfor modifications (vianotifycrate) - On file change (after debounce), runs
submit::run()on the changed file - Reactive mode: CRDT-mode documents (
agent_doc_write: crdt) are discovered withreactive: trueand use zero debounce (Duration::ZERO) for instant re-submit on file change. Reactive paths are tracked in aHashSet<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. --stopsends SIGTERM to the running daemon (via.agent-doc/watch.pid)--statusreports whether the daemon is running--debouncesets the debounce delay in milliseconds (default 500)
7.24 history
agent-doc history <FILE> — list exchange versions from git history.
- Scan git log for commits touching
<FILE> - Extract the
<!-- agent:exchange -->component content at each commit - Display a list of commits with timestamps and content previews
agent-doc history <FILE> --restore <COMMIT> — restore a previous exchange version.
- Read the exchange content from the specified commit
- Prepend the old exchange content into the current document's exchange component
- 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.
- Resolve tmux session name:
--sessionflag >tmux_sessionin document frontmatter > default"0" - Check if session exists and has an attached client — if so, print message and exit (no-op)
- If session exists but is detached, open terminal to attach
- If session does not exist, open terminal which creates and attaches
- Build tmux command:
tmux new-session -A -s <session>(attach-or-create) - Resolve terminal command (priority order):
a.
[terminal] commandin~/.config/agent-doc/config.toml— template with{tmux_command}placeholder b.$TERMINALenv var — used as$TERMINAL -e {tmux_command}c. Error with configuration instructions - 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):
- Recover orphaned pending responses (
agent-doc recover) - Commit previous cycle (
agent-doc commit) - Read and truncate
.agent-doc/claims.log3c. Check linked docs: inspectlinksfrom frontmatter — local files compared by git commit time, URLs fetched viaureqwith HTML-to-markdown conversion (htmd), cached in.agent-doc/links_cache/ - Compute diff between snapshot and current document
- 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_changesistruewhen the diff isNone(snapshot == document)diffisnullwhenno_changesistruedocumentalways contains the current HEAD contentlinked_changeslists changes in linked docs/URLs since last cycle (omitted when empty)- Progress/diagnostic messages go to stderr
URL link processing:
- URLs (
http:///https://) inlinksfrontmatter 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_sessionin 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
| # | Scenario | Command | What Happens |
|---|---|---|---|
| U1 | First session for a document | agent-doc start plan.md | Creates tmux pane, launches Claude, registers pane |
| U2 | Submit from JetBrains plugin | Plugin Ctrl+Shift+Alt+A | Calls agent-doc route <file> → sends to registered pane |
| U3 | Submit from Claude Code | /agent-doc plan.md | Skill invocation — diff, respond, write back |
| U4 | Claim file for current session | /agent-doc claim plan.md | Skill delegates to agent-doc claim → updates sessions.json |
| U5 | Claim after manual Claude start | /agent-doc claim plan.md | Fixes stale pane mapping without restarting |
| U6 | Claim multiple files | /agent-doc claim a.md then /agent-doc claim b.md | Both files route to same pane |
| U7 | Re-claim after reboot | /agent-doc claim plan.md | Overrides old pane mapping (last-call-wins) |
| U8 | Pane dies, plugin submits | Plugin Ctrl+Shift+Alt+A | route detects dead pane → auto-start cascade |
| U9 | Install skill in new project | agent-doc skill install | Writes bundled SKILL.md to .claude/skills/agent-doc/ |
| U10 | Check skill version after upgrade | agent-doc skill check | Reports "up to date" or "outdated" |
| U11 | Permission prompt from plugin | PromptPoller polls prompt --all | Shows bottom bar with numbered hotkeys in IDE |
| U12 | Claim notification in session | Skill reads .agent-doc/claims.log | Prints claim records, truncates log |
| U13 | Clean up dead pane mappings | agent-doc resync | Removes 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:
| Phase | Operation | Detail |
|---|---|---|
| DETACH | stash_pane() | Moves an unwanted pane into the stash window via tmux join-pane |
| — | target selection | Targets the LARGEST pane in the stash (by height) to avoid "pane too small" errors |
| — | overflow | If join fails, break_pane_to_stash() creates an overflow stash window (also named "stash") |
| ATTACH | reconcile() | Joins a stashed pane back into @0 when needed again |
| RESCUE | sync pre-resolution | Rescues 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
@0during stash operations (-dflags 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:
- 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 callreposition_boundary_to_end()in memory. This removes ALL stale boundaries and inserts a single fresh one. The repositioned document is used solely to extractboundary_idvalues — never written to disk. This ensures theboundary_idpoints to end-of-exchange (after the user's prompt), not the stale mid-exchange position. - During
agent-doc write: thereposition_boundary: trueIPC flag tells the plugin to move the boundary after applying the response patch. The plugin should callagent_doc_reposition_boundary_to_end()via FFI to ensure identical cleanup logic. - During
agent-doc commit: (a) the snapshot is cleaned viareposition_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. - 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:
- Sync fires — JB plugin sends
agent-doc sync --col <file1> --col <file2> --focus <focused_file> - Initialization —
ensure_initialized()runs for each file incol_args:- If file is empty (no frontmatter, no content) → auto-scaffold as template with frontmatter + exchange component
- If file has
agent_doc_formatbut noagent_doc_session→ assigns a UUID - If no snapshot exists → creates snapshot +
git add+git commit
- File resolution —
resolve_file()reads frontmatter. Files withagent_doc_session→FileResolution::Registered. Non-.mdfiles or files with content but no frontmatter →Unmanaged. - Reconciliation —
tmux_router::syncmatches 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
- Provisioning —
route::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
| Invariant | Enforcement |
|---|---|
| One document per pane | Registry check in claim::run() (line 142-156) |
| Document drives, pane follows | Sync resolves files first, then matches to panes |
| Never commandeer another document's pane | auto_start creates new panes; claim validates pane isn't already bound |
| Stashed panes stay alive | join-pane moves to stash, doesn't kill |
| Initialization is idempotent | ensure_initialized() checks snapshot existence first |
Terminology (Domain Ontology)
| Term | Definition | Module |
|---|---|---|
| Binding | Document→pane association in sessions.json | claim.rs, sessions.rs |
| Reconciliation | Matching editor layout to tmux layout | sync.rs |
| Provisioning | Creating a new pane + starting Claude | route.rs (auto_start) |
| Initialization | Assigning UUID + snapshot + git tracking | snapshot.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 frontmatterpoll <EVENT> [--since SECS]— read events newer than timestamp, clean expiredlisten [--root PATH]— start Unix socket listener at.agent-doc/hooks.sockgc [--root PATH]— clean expired events across all hooks
Lifecycle hooks fired by agent-doc:
post_write— after IPC write succeeds (fromwrite.rs)post_commit— after successful git commit (fromgit.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-permissionsexposure. When running with this flag (common in agent-doc sessions viaclaude_argsfrontmatter), 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
linksfrontmatter 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.
11.8 Recommended Improvements
-
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 -
Switch to cryptographic hashing (SHA256) for typing indicator and status file paths to eliminate collision risk entirely.
-
Make 30s status timeout configurable — either via config.toml or frontmatter.
-
Mtime fallback in route path — If mtime-detected change is stale (>1s), also check cross-process typing indicator as fallback.
-
CRDT merge monitoring — Log merge conflicts and convergence issues to
.agent-doc/logs/merge.logfor operator visibility. -
Stale typing indicator cleanup — Old
.agent-doc/typing/files are never deleted. Add a GC step (e.g., inagent-doc gcor 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:
- Deletes the unconsumed patch file
- Falls back to direct CRDT stream write (
run_stream()) - Logs
[write] IPC timeout — falling back to direct write
This makes --ipc safe to use unconditionally in the SKILL.md workflow.
Component Mapping
| Binary | Plugin | Purpose |
|---|---|---|
write.rs:run_ipc() | PatchWatcher.kt | End-to-end IPC flow |
template::parse_patches() | applyComponentPatch() | Patch extraction/application |
snapshot::save() | FileDocumentManager.saveDocument() | Persistence |
atomic_write() (patch JSON) | NIO WatchService | File-based IPC transport |
Pattern Expression
IDE Scope
The IPC pattern maps to IntelliJ's threading model:
- EDT (Event Dispatch Thread):
invokeLaterschedules 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,
claimnow provisions a new pane instead of erroring. Enforces SPEC §8.5: "never commandeer another document's pane." - Sync auto-scaffold (sync.rs): Empty
.mdfiles 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 transfernow automatically transfers thependingcomponent 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 asdiff_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 asannotated_diff. 5 tests. - Content-source annotation sidecar (P4): New
agent-doc annotatecommand generates.agent-doc/annotations/<hash>.jsonmapping each line to agent/user source. SHA256 cache invalidation. GC integration. 6 tests. - Reproducible operation logs (P5): New
.agent-doc/logs/cycles.jsonlwith 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_commentstocomponent.rs(shared between binary and eval-runner). eval-runner preprocesses diffs with comment stripping. - Transfer-source metadata:
PatchBlocknow supportsattrsfield.<!-- 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\\nunescape 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.mdkernel node. - Module-harness: New
ontology-referencesrunbook 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(). Compositeensure_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'sresolve_file. Files withagent_doc_formatbut 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()insnapshot.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_fileandagent_doc_await_idle_via_filefor CLI tools running in separate processes.is_idleandawait_idlenow bridge to file-based indicator when untracked in-process. - Diff stability fix:
wait_for_stable_contentcounter now tracks consecutive stable reads across outer iterations (was resetting within each pass). - IPC error propagation:
ipc_socket::send_messagenow returns proper errors instead of swallowing connection/timeout failures asOk(None). - Template patch boundary fix: Improved boundary marker handling in
apply_patches_with_overrides. - CI/build:
make releasetarget, idempotent release workflows, version-sync check inmake check.
0.31.9
- Transfer-extract runbook: New bundled runbook for cross-file content moves (
agent-doc transfer/extract). Installed viaskill 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 automaticprune()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
flockon.agent-doc/sync.lockto serialize concurrent sync calls. Prevents race conditions when rapid tab switches fire overlapping syncs. - Route sync removal: Removed redundant
sync::run_layout_onlyfrom Route command dispatch andsync_after_claimfrom route.rs. The JB plugin'sEditorTabSyncListeneris 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_debouncefrontmatter field. - Structured logging: Added
tracing+tracing-subscriber+tracing-appender. SetAGENT_DOC_LOG=debugto 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::Dropnow deletes the lock file (not just unlocks). CRDT lock acquisition cleans stale locks (>1 hour old). agent-doc gcsubcommand: Garbage-collects orphaned files in.agent-doc/directories. Supports--dry-runand--rootflags.- Auto-GC on preflight: Runs GC once per day via
.agent-doc/gc.stamptimestamp check. - Cleanup runbook: New
runbooks/cleanup.mddocumenting.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 extractandagent-doc transfernow 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
CRITICALif 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 claimnow 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-docworks on new files without claiming first. - VCS refresh after commit:
agent-doc commitwrites a VCS refresh signal file, prompting IDEs to update their git status display. - Preflight
--diff-onlyflag: Omits the full document from preflight JSON output, reducing token usage by ~80% on subsequent cycles. - Skill-bundled runbooks:
agent-doc skill installnow 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.rsfor 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=Nattribute: Component tags supportmax_lines=Nto 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 claimnow scaffolds a<!-- agent:console -->component for template-mode documents. - HEAD marker cleanup:
git.rsstrips stray(HEAD)markers from working tree after commit (defensive cleanup). - StreamConfig max_lines:
agent_doc_stream.max_linesfrontmatter 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 claimnow 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:
--originflag onagent-doc writelogs the write source (skill/watch/stream) to ops.log. Aids diagnosis when snapshot drift occurs. - Commit drift warning: Warns when
file_len - snap_len > 100bytes, 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+Cnow callsagent-doc claimon 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 claimrefuses to overwrite an existing live claim without--force, preventing silent pane corruption from fallback position detection. - HEAD marker duplicate fix:
add_head_markeruses 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.rsembeds a build timestamp. On sync, the binary compares against.agent-doc/build.stampand clears stale startup locks on new build detection. - Plugin binary resolution fix: EditorTabSyncListener and SyncLayoutAction now pass
basePathtoresolveAgentDoc(), correctly resolving.bin/agent-docinstead 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 sessionCLI: Show/set configured tmux session with pane migration (session_cmd.rs).- Stash pane safety:
purge_unregistered_stash_panesno 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.tswith 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.rsfollows pane session,claim.rsupdates config to match. - 2 new FFI tests: Coverage for
agent_doc_is_idleand related FFI surface. - Dependencies:
tmux-routerv0.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_panecallback 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>.lockwith 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/gcCLI. Cross-session event coordination viaagent-kithooks (v0.3).post_writeandpost_commitevents fired from write + commit paths. - HookTransport trait: Abstract delivery mechanism with
FileTransport,SocketTransport,ChainTransportimplementations. - Ops logging tests: 2 new tests for
.agent-doc/logs/ops.log. - Dependencies:
agent-kitv0.3 (hooks feature),tmux-routerv0.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_docs→links(backward-compat alias). URL links (http:///https://) are fetched viaureq, converted HTML→markdown viahtmd(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>.logwith timestamped events for session start, claude start/restart/exit, user quit, and session end. - Auto-trigger on restart: After
--continuerestart, background thread sends/agent-doc <file>viatmux send-keysafter 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:
htmdv0.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_streamdisk,run_streamIPC) skip the write when merged content is identical to the current file. Dedup events logged to/tmp/agent-doc-write-dedup.logwith backtrace. - Pane ownership verification:
verify_pane_ownership()called at entry ofrun,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.jsonsaves column→agent-doc mapping (carried from v0.28.1, now documented).
0.28.1
- Column memory:
.agent-doc/last_layout.jsonsaves 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:
syncnow filters out empty strings fromcol_argsbefore processing. Fixes phantom empty columns sent by the JetBrains plugin during rapid editor split changes. - Sync debug logging: Added
/tmp/agent-doc-sync.logtrace 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::syncalways 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 forresolved < 2bypassed 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 deaddoc_tmux_sessionpath - VERSIONS.md backfill: Added entries for v0.23.2 through v0.26.6
0.27.7
- Sync path column-aware split:
auto_start_no_waitnow acceptscol_argsand computessplit_beforeviais_first_column(). Previously hardcodedsplit_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()ingit.rsnow 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_sessionpicks 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 < 2early return intmux-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.rsnow 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_socketmodule using Unix domain sockets via theinterprocesscrate 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/unlockFFI exports for cross-editor concurrency control. Addedagent_doc_sync_bump/check_generationfor cross-editor event coalescing. - Layout debounce fix:
LayoutChangeDetectoruses 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
Unmanagedfor files without session UUIDs; onlyclaimadds frontmatter now. - Plugin mixed-layout sync: Uses focus-only when non-
.mdfiles 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_claimnow 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_busyfor 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 inEditorTabSyncListener+TerminalUtil.
0.26.1
- Sync layout authority:
sync_after_claimuses editor-providedcol_args, preventing 3-pane layout regression on file switch. - Clippy fixes:
doc_lazy_continuationfixes in sync.rs, upgrade.rs. Unused variable fix in tmux-routerbreak_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_panerefuses to destroy a session's last window (tmux-router v0.3.0). - IPC verification: Content verification catches partial plugin application failures.
--force-diskcleans 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_sessionfrontmatter 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.shwith platform detection and improved install paths. - Homebrew formula: Added
Formula/agent-doc.rbfor macOS/Linux Homebrew installation. - Deprecate
tmux_sessionfrontmatter: Sync strips the field on encounter instead of repairing it. Routeauto_startno longer attempts repair.
0.25.12
- Sync swap-pane atomic reconcile:
context_sessionoverrides frontmattertmux_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 --fixdisabled 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-panefor 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
--debounceflag: Opt-in mtime polling for coalescing rapid editor triggers. is_trackedFFI export: For editor plugins to check file tracking status.- Sync no-wait auto-start:
auto_start_no_waitfor 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_trackedbinding + 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
submittorun:submit.rsrenamed torun.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/--focusargs: Declarative layout sync from the route command. PluginsendToTerminalpasses editor layout in a single CLI call. - Layout change detection:
LayoutChangeDetectorusingContainerListenerwith 5s fallback poll in the JetBrains plugin. - EDT-safe threading: Plugin uses
invokeLaterfor 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
:summarysuffix (filename stem).new_boundary_id_with_summary()wired into all write paths. - Snapshot boundary cleanup: Commit path uses
remove_all_boundaries(). Working tree cleaned viaclean_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
repositionBoundaryToEndremoves 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/checknow 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 preflightcommand: 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: trueflag 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_startto 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 --fixon 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 installcommand: System-level setup that checks prerequisites (tmux, claude) and detects/installs editor plugins.agent-doc initproject mode: No-arginitnow 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()usesps(1)instead of/procfor Linux+macOS compatibility. Pre-sync auto-start checks alive panes before creating duplicates. - Clippy fixes: Fix
collapsible_ifwarnings in template.rs, git.rs, terminal.rs. Suppressdead_codewarnings 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 toexchange/output. Previously only explicit patches used boundary markers; unmatched content used plain append. - IPC snapshot correctness:
try_ipc()now accepts acontent_oursparameter (baseline + response, without user concurrent edits). On IPC success the snapshot is saved fromcontent_oursinstead 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/outputand 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-parsedComponentfor any boundary marker UUID, skipping matches inside code blocks. Used bytemplate.rsand 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_idwhen 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 commitright afteragent-doc write, replacing the old "Do NOT commit after writing" instruction. All sessions get the new behavior afteragent-doc skill install. - Plugin default modes:
exchangeandfindingscomponents now default toappendmode in the JetBrains plugin (matching the Rust binary'sdefault_mode()), so<!-- agent:exchange -->works without explicitpatch=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, writesvcs-refresh.signalto.agent-doc/patches/. Plugin watches for this and triggersVcsDirtyScopeManager.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 terminalsubcommand: Cross-platform terminal launch from editor plugins. Config-first (no hard-coded terminal list):[terminal] commandinconfig.tomlwith{tmux_command}placeholder. Fallback to$TERMINALenv var. Detects stale frontmatter sessions and scans registry for live panes.- Selective commit:
agent-doc commitstages only the snapshot content viagit 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()incomponent.rs. JNA binding inNativeLib.kt. - JetBrains plugin v0.2.7: Cursor-aware append ordering via native FFI with Kotlin fallback. Captures caret offset from
TextEditorbeforeWriteCommandAction.
0.21.0
agent-doc parallelsubcommand: 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-worktreefor read-only tasks.- CRDT post-merge reorder: Agent content ordered before human content at append boundary using Yrs per-character attribution (
Text::diffwithYChange::identity). - README: Added parallel fan-out documentation section.
0.20.3
agent-doc claimssubcommand: Read, print, and truncate.agent-doc/claims.login 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()whentmux_sessionis 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:frontmatternow 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:
claimscaffolds 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:
claimrefuses to overwritetmux_sessionunless--force. Prevents cross-session pane swapping.
0.17.29
- JNA FFI integration:
NativeLib.ktJNA 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
prependcase inapplyComponentPatch().
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 --waitfor truncation detection.
0.17.24
- Pulldown-cmark for code range detection: Replace hand-rolled code span/fence parser with
pulldown-cmarkin 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:pendingin claim for template documents.
0.17.20
- BREAKING CHANGE: Rename
modetopatchfor inline component attributes (patch=append|replace).mode=accepted as backward-compatible alias.
0.17.19
- Split-window in auto_start: Use
split-windowinstead ofnew-windowfor auto-started Claude sessions. Resync tests added.
0.17.18
- Resync
--fixenhancements: Detect wrong-session panes and wrong-process registrations. Renamed--dangerously-set-permissionsto--dangerously-skip-permissions.
0.17.17
- Parse fix:
parse_option_linematches[N]bracket format only. Fixfind_registered_pane_in_sessionlookup.
0.17.16
- Cursor editor support: Add Cursor as a supported editor.
claude_argsfrontmatter 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 historyshows exchange version history from git with restore support. IPC-priority writes with--force-diskflag 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_writesplit: Replaceagent_doc_modewith 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 compactnow 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 optionalthinking_targetrouting. Deferred commit workflow. Snapshot resolution prefers snapshot file over git.
0.14.9
- Multi-backtick code span support:
find_code_rangeshandles 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 convertworks 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 commandssubcommand: 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.converthandles 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
--reloadflag: 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 claimdefault to template format.agent-doc modeCLI 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 syncregisters 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
exchangecomponent uses append patch mode by default instead of replace.
0.12.1
- Lazy claim fallback:
agent-doc claimwithout--panefalls back to the active tmux pane.
0.12.0
agent-doc convertcommand: Convert between inline and template document formats. Lazy claim support.agent-doc compactfor git history squashing. Exchange component as default template target.
0.11.2
- Strip trailing
## Userheading: Also strip trailing## Userheading from agent responses (complement to v0.11.1).
0.11.1
- Strip duplicate
## Assistantheading: Remove duplicate## Assistantheading 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_modetoagent_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 writecommand: 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 installCLI: 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 patchfor programmatic updates,agent-doc watchdaemon 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-routeras a dependency for pane management. Fixrouteauto_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
--focuson sync:agent-doc syncaccepts--focusflag. 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 synccommand: 2D columnar tmux layout synced to editor split arrangement. Dynamic pane groups.
0.5.6
- Commit message includes doc name:
agent-doc commitmessage format now includes the document filename.agent-doc outlinecommand for markdown section structure with token counts.
0.5.5
- Window-scoped routing: Route commands scoped to tmux window (not just session).
--pane/--windowflags. 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 resyncvalidates 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.rsfor cross-platform compilation.
0.5.0
agent-doc focusandagent-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 DIRflag for audit-docs (§7.6),agent-doc-versionfrontmatter 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. Refactoredinstall/checkto 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.rsto 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-versionfrontmatter field enables pre-flight version comparison. If the installed binary is newer,agent-doc skill installruns automatically before proceeding. - PromptPanel: JDialog to JLayeredPane overlay: Replace
JDialogpopup with aJLayeredPaneoverlay 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, updatessessions.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.mdin the current project. The skill content is embedded in the binary viainclude_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 claimsupport. - 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 --allpolls all live sessions in one call, returns JSON array.SessionEntrynow includes afilefield 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 promptsubcommand added in v0.2.0 (unreleased). - send-keys fix: Literal text (
-l) + separate Enter,new-window -aappend flag (unreleased since v0.2.0).
0.1.4
agent-doc upgradeself-update: Downloads prebuilt binary from GitHub Releases as the primary upgrade strategy. Falls back tocargo install, thenpip install --upgrade, then manual instructions includingcurl | 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 triescargo installthenpip 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-fileif 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.
-bflag for auto-branch,--no-gitto 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.