logbook — kanban for ai agents
logbook is a file-system kanban board for autonomous AI agents. It tracks epics, stories, tasks, and context entries across a structured lifecycle so agents and humans share a single source of truth without context bloat.
→ new here? see quickstart.md to get running in 5 minutes.
why logbook
autonomous agents work in parallel, forget context across sessions, and have no shared task state. logbook solves three problems:
- human visibility — agents record every task they touch in a file the whole team can read and diff
- agent coordination —
task.current resolves FIFO per-session, so multiple agents can't claim the same task
- context budget — structured JSONL with a DuckDB optional query layer lets agents find relevant records without loading the whole store
v2 adds: epics → stories → tasks hierarchy, reusable context entries (knowledge that survives across tasks), and Linear two-way sync as a first-class plugin.
quickstart
npm install -g @bosun-sh/logbook
logbook init
logbook task:create \
--title "Implement login endpoint" \
--description "JWT auth, see docs/auth.md" \
--definition-of-done "Tests pass and endpoint is documented" \
--project myapp --milestone v1
logbook task:list --status "*"
see quickstart.md for the full walkthrough including Linear sync.
for one-off local use, run commands through your package manager, for example
npx @bosun-sh/logbook --help or bunx @bosun-sh/logbook --help.
workspace layout
logbook init creates the following structure through workspace.init:
.logbook/
├── config.json # workspace config (Linear credentials, hook overrides)
├── workspace.json # workspace metadata
├── hooks/
│ ├── review-spawn/ # spawns a reviewer agent on pending_review
│ │ ├── config.json
│ │ └── script.mjs
│ └── need-info-notify/ # notifies user when a task needs info
│ ├── config.json
│ └── script.mjs
└── storage/
├── epics.jsonl
├── stories.jsonl
├── tasks.jsonl
├── context-entries.jsonl
├── external-links.jsonl
├── sync-events.jsonl
└── sync-conflicts.jsonl
add .logbook/storage/ to .gitignore to keep runtime data out of version control:
.logbook/storage/
mcp tools by plugin
connect logbook mcp as an MCP server and call any of the 38 tools below.
task plugin
task.create | Create a task in backlog |
task.get | Load one task by id |
task.list | List tasks (default status: in_progress) |
task.current | Claim and return the highest-priority in-progress task for this session |
task.update | Transition task status, add comments, reply to need_info |
task.edit | Edit mutable fields without status change |
task.assign.session | Assign a session to a task |
task.assign.model | Assign a model to a task |
task.assign.phase-model | Set a per-phase model override |
task.estimate | Compute or re-compute a Fibonacci estimation |
task.current priority chain: session-owned in_progress → unassigned in_progress → orphaned in_progress (dead session) → highest-priority todo (auto-transitions) → no_current_task error.
epic plugin
epic.create | Create an epic |
epic.get | Load one epic |
epic.list | List epics |
epic.update | Update an epic |
epic.delete | Tombstone an epic |
story plugin
story.create | Create a story within an epic |
story.get | Load one story |
story.list | List stories |
story.update | Update a story |
story.delete | Tombstone a story |
context plugin
context.create | Create a reusable context entry |
context.get | Load one context entry |
context.list | List context entries |
context.update | Update a context entry |
context.delete | Tombstone a context entry |
context.attach | Attach a context entry to an epic, story, or task |
context.detach | Remove an attachment |
context.search | Full-text search over context entries |
sync plugin (Linear)
sync.linear.pull | Pull issues from Linear into logbook (since-cursor pagination) |
sync.linear.push | Push logbook tasks to Linear |
sync.linear.setup | Configure Linear sync from a team URL or explicit ids |
sync.linear.status | Check Linear configuration and connectivity |
sync.conflicts.list | List unresolved sync conflicts |
sync.conflicts.resolve | Resolve a conflict (use_local, use_remote, or manual) |
workspace plugin
workspace.init | Initialize or re-scaffold the .logbook/ workspace |
workspace.status | Report workspace health and provider status |
hook plugin
hook.list | List registered hooks |
hook.run | Run a hook manually |
plugin plugin
plugin.list | List all registered plugins and their tool IDs |
cli
every tool is available as logbook <tool-id-with-colons>:
logbook init
logbook init --mcp-client claude --no-linear
logbook workspace:init
logbook workspace:status
logbook task:create --title "x" --description "y" --definition-of-done "z" --project p --milestone m
logbook task:list --status "*"
logbook task:list --status in_progress
logbook task:current
logbook task:update --id <uuid> --new-status pending_review
logbook task:edit --id <uuid> --title "New title"
logbook epic:create --title "Auth" --description "Login and session management" --outcome "Users can log in"
logbook story:create --epic-id <uuid> --title "JWT login" --description "..." --user-value "Users can authenticate"
logbook context:create --title "Auth spec" --body "Use JWT RS256. See docs/auth.md."
logbook context:attach --context-entry-id <uuid> --task-id <uuid>
logbook sync:linear:setup --team-url https://linear.app/<workspace>/team/<team>
logbook sync:linear:pull
logbook sync:linear:push --dry-run
logbook sync:linear:status
logbook create-task --title "..." --definition-of-done "..." --predicted-k-tokens 3
logbook list-tasks --status in_progress
all commands write a single-line JSON envelope to stdout:
{"ok":true,"data":{"task":{...}}}
{"ok":false,"error":{"code":"not_found","message":"task abc was not found"}}
linear integration
setup
The preferred setup path is logbook init, which prompts for Linear sync setup.
To configure Linear separately:
The setup command resolves the workspace and team ids and writes the public config to .logbook/config.json.
It never writes the API key there. To let setup write .env for you, pass the token once:
logbook sync:linear:setup \
--team-url https://linear.app/bosun/team/BOSUN \
--api-token lin_api_... \
--write-env
Manual setup is also supported:
logbook sync:linear:setup \
--workspace-id <workspace-id> \
--team-id <team-id>
This produces a linear block like:
{
"linear": {
"apiTokenEnv": "LINEAR_API_KEY",
"workspaceId": "your-workspace-id",
"defaultTeamId": "your-team-id"
}
}
pull / push
when Logbook runs through MCP, task-facing tools automatically pull before the call and push successful task writes back to Linear. the explicit commands below are still available for manual refreshes, dry runs, and targeted syncs.
logbook sync:linear:pull
logbook sync:linear:pull --dry-run --team-id <id>
logbook sync:linear:push
logbook sync:linear:push --task-ids '["task_abc","task_xyz"]' --dry-run
logbook sync:linear:status --check-provider
conflicts
when the same record is modified in both logbook and Linear, a conflict entry is written to sync-conflicts.jsonl:
logbook sync:conflicts:list
logbook sync:conflicts:resolve --id <conflict-uuid> --resolution use_local
conflict states: open → resolved or ignored.
external links
bidirectional linear:<id> mappings are stored in external-links.jsonl and updated automatically by pull/push.
hooks
hooks execute shell commands on task lifecycle events. configuration lives in .logbook/hooks/<id>/config.json:
{
"id": "need-info-notify",
"event": "task.status_changed",
"condition": { "status": "need_info" },
"command": ["node", ".logbook/hooks/need-info-notify/script.mjs"],
"timeoutMs": 5000
}
two hooks are materialized by workspace.init:
need-info-notify — prints the blocking comment when a task moves to need_info
review-spawn — creates a review task and spawns a reviewer agent when a task moves to pending_review
hooks are stateless — execute and forget. the command field is an argv array; no shell expansion is performed.
environment variables
LOGBOOK_WORKSPACE_ROOT | process.cwd() | workspace root used by the compiled binaries |
LOGBOOK_LOG_LEVEL | warn | log level: debug, info, warn, error |
LINEAR_API_KEY | — | Linear API token (or the env var named in linear.apiTokenEnv) |
optional duckdb index
logbook uses @duckdb/node-api to run ad-hoc SQL over the canonical JSONL files. the index is in-memory — no separate index file is written or maintained:
SELECT id, title, status FROM read_json_auto('.logbook/storage/tasks.jsonl', format='newline_delimited')
WHERE status = 'in_progress' AND project = 'myapp'
the DuckDB path is opt-in via workspace.status and is non-canonical — the JSONL files remain the source of truth.
migrating from v1
if your project has a tasks.jsonl at the repository root, workspace.init detects it and migrates all records to .logbook/storage/tasks.jsonl automatically:
- field names renamed from
snake_case to camelCase
kind: "task" injected on every record
- v1 comment shape converted to v2 shape
v1 CLI commands (create-task, list-tasks, current-task, update-task, edit-task, init) remain registered and emit a compatibility_mapping_applied warning. remove the deprecated commands from your scripts when ready.
see CHANGELOG.md for the full v2.0.0 breaking-change list.
stack
- runtime: Node.js / TypeScript
- effect system: Effect.ts — all async operations and errors modeled as
Effect<A, E, R>
- architecture: ohtools plugin registry, hexagonal adapters (CLI + MCP), vertical slices per entity
- persistence: JSONL — append-only, one record per line, full-scan reads; DuckDB for optional ad-hoc queries
- validation: Zod at every public boundary (MCP input, CLI flags, filesystem reads)