If you have ever sat down with Claude Code on a Monday morning and realized the agent has no memory of what it tried on Friday, you have felt the problem ticketd was built to solve. It is a small, opinionated tracker exposed over the Model Context Protocol (MCP) - a single Go binary, a single SQLite file, six tools, and a worldview: the worklog is the product, not the status field.

This post walks through what ticketd is, what makes it interesting, and how to actually get value out of it day-to-day.

The Problem: Agents Have Amnesia

Modern coding agents are powerful inside a session and useless across sessions. They forget which approaches they already rejected, which files they touched, what the user asked them to leave alone. Traditional trackers like Jira or Linear are not the answer - they are designed for humans clicking through filtered views, with APIs bolted on as an afterthought.

ticketd inverts that priority. The agent is the first-class user. Humans get a read-only board on the side.

What ticketd Is

“Durable external memory for AI coding agents.”

That is the tagline, and it is accurate. Concretely:

  • Single static Go binary built with modernc.org/sqlite - no CGO, cross-compiles trivially to Linux and Darwin on amd64/arm64.
  • Single SQLite file with WAL journaling, so the MCP server and the CLI can read/write concurrently.
  • Six MCP tools exposed over stdio (for Claude Code) or HTTP (for remote agents).
  • Markdown outputs, not JSON, because the model reads them natively and it costs fewer tokens.
  • A read-only Kanban board at /board when running over HTTP, rendered by a Go template with zero JavaScript.

Architecture at a Glance

graph TB A[Claude Code] -->|stdio MCP| B[ticketd binary] R[Remote agent] -->|HTTP + Bearer| B H[Human via CLI] -->|ticketd ls/show| B W[Human via browser] -->|/board| B B --> T[Tool Router] T --> C[create_ticket] T --> U[update_ticket] T --> M[add_comment] T --> G[get_ticket] T --> S[search_tickets] T --> X[get_context] C --> D[(SQLite + FTS5)] U --> D M --> D G --> D S --> D X --> D style A fill:#3498db,color:#fff style B fill:#9b59b6,color:#fff style T fill:#e74c3c,color:#fff style D fill:#27ae60,color:#fff

Three things stand out in that diagram:

  1. The same binary serves agents, humans, and a browser - no separate API server or web app.
  2. The MCP tools and the SQLite file are the entire surface area. There is nothing else to operate.
  3. get_context sits next to the other CRUD tools, but as we will see, it is the centerpiece.

The Six Tools

Tool Purpose
get_context A working-state report: in-progress tickets (stale ones flagged after 7 idle days), blocked tickets with reasons, top of the todo queue. Call at session start.
create_ticket Start tracking a non-trivial task. Idempotent on exact open-title match, so the agent cannot spam duplicates.
update_ticket Status, priority, title, labels, links. Status transitions are validated against the FSM.
add_comment Append a worklog entry. This is the memory a future session resumes from.
get_ticket Full ticket - status, subtasks, links, complete worklog - in one call.
search_tickets Full-text search (FTS5) over titles, descriptions, and comments. Filterable by status, project, label.

A few things to notice:

  • Comments are append-only. You cannot edit or delete them. The worklog is treated as durable memory, and history integrity beats correction.
  • Validation errors are part of the API. When the agent tries an illegal status transition, the error message names the legal next states. The agent reads it and self-corrects.
  • get_context is opinionated. It is not a raw query dump. It picks what the agent needs to resume, in priority order.

The Status Workflow

backlog ──► todo ──► in_progress ──► in_review ──► done
              ▲        │   ▲              │
              │        ▼   │              ▼
              └───── blocked ◄────── (back to in_progress)

wont_do reachable from: backlog, todo, blocked

done and wont_do are terminal. The FSM is small enough to memorize, and the rejection messages make it self-teaching for agents.

Installing It

The fastest path, if you already have Go:

go install github.com/colossus21/ticketd/cmd/ticketd@latest

Otherwise grab a prebuilt archive from the releases page:

tar -xzf ticketd_*_linux_arm64.tar.gz
install ticketd ~/.local/bin/
ticketd --version

Or build from source - it really is just go build:

go build -o ticketd ./cmd/ticketd
go test ./...

Cross-compiling is the trivial Go story, no CGO toolchain to set up:

GOOS=linux GOARCH=arm64 go build -o ticketd-linux-arm64 ./cmd/ticketd

Wiring It Into Claude Code

Add the MCP server at user scope so it follows you across every project:

claude mcp add --scope user tickets -- \
  ticketd --db "$HOME/.local/share/ticketd/tickets.db"

Or, if you want a per-project tracker, copy .mcp.json.example into the project root as .mcp.json.

The crucial second step is teaching the agent to use the tools without being asked. Paste the contents of CLAUDE.md.example from the repo into your project’s CLAUDE.md. That file instructs the agent to:

  • Call get_context at the start of every session.
  • Search before creating a new ticket, to avoid duplicates.
  • Comment whenever it forms a plan, rejects an approach, or hits a blocker.

Without that nudge the agent will technically have the tools available and ignore them. With it, the tracker fills itself.

A Day in the Life

Here is what proper use looks like.

Session start. The agent calls get_context. It gets back a Markdown report:

## In Progress
- T-42 Add metrics endpoint (last touched 2d ago)
- T-39 Refactor auth middleware (STALE - idle 9d)

## Blocked
- T-37 Migrate to pgx v5 — waiting on dep update from upstream

## Top of Todo
- T-44 Fix race in worker pool (priority: high)
- T-45 Document deploy steps

It now knows what it was doing, what is stuck, and what to pick up next. That is one call.

Mid-task. The agent decides to try a new approach to T-44. Before writing code, it appends a comment:

ticketd comment T-44 "Trying sync.Pool to amortize allocation; \
  earlier attempt with a buffered chan caused goroutine leaks - see T-44 #3"

The comment is written for a reader with zero context - because the next reader almost certainly is one.

Hit a blocker. Status moves to blocked with a reason in a comment. get_context will surface it next session, so nothing falls through.

Done. update_ticket flips to in_review, the human reviews via the board, and a final comment closes the loop.

The CLI Layer (for Humans)

You will not live in the CLI, but it is handy for inspection and the occasional manual entry:

ticketd create "Add metrics endpoint" --priority high --label obs
ticketd ls --status in_progress
ticketd show T-42
ticketd comment T-42 "looks good"
ticketd context --project voice
ticketd backup --dir ~/backups

ticketd backup is a VACUUM INTO to a timestamped copy - exactly what you want for a single-file database.

Running It Over HTTP

For remote agents, or for the Kanban board:

ticketd --transport http --addr 127.0.0.1:7333

Then http://127.0.0.1:7333/board gives you a read-only board, optionally filtered with ?project=name. It is a single Go template with no JavaScript, which is exactly the right amount of frontend for this job.

If you expose the box, protect the MCP endpoint with a bearer token:

TICKETD_TOKEN=$(openssl rand -hex 16) \
  ticketd --transport http --addr 0.0.0.0:7333

Remote clients send Authorization: Bearer <token>. The board stays unauthenticated and read-only - the assumption is you reach it over an SSH or Tailscale tunnel.

Why the Design Choices Land

A few decisions are worth calling out, because they are the kind of thing you only appreciate after using the tool for a while.

Markdown over JSON. Agents read Markdown natively. JSON costs more tokens and forces the model to re-render anyway. The output is the API.

Errors as text, not protocol failures. When the agent does something illegal, it gets a sentence telling it what is allowed. The agent reads, adjusts, and continues. No exception-handling dance.

Idempotent create_ticket. An agent that loses track of whether it already created a ticket will not generate duplicates. Same exact open title returns the existing ticket.

Append-only worklog. You cannot rewrite history. If a comment was wrong, you add another comment correcting it. The trail of reasoning stays intact, which is what makes the worklog trustworthy memory.

Short keys (T-42). Agents type them in commit messages and prose. Long UUIDs would never survive that.

How to Get Proper Use Out of It

A short checklist for adopting ticketd into a real workflow:

  1. Install it once at user scope. One DB, one binary, one tracker that follows you across every repository.
  2. Paste the CLAUDE.md snippet into every project you want tracked. Without it the agent will not use the tools unprompted.
  3. Make get_context the first thing the agent does each session. It is the entire point.
  4. Encourage comments on every plan, rejection, and blocker. A ticket with a thin worklog is a ticket the next session cannot resume from. Verbose worklogs are the goal, not noise.
  5. Use the CLI for spot checks. ticketd context and ticketd show T-42 are how you, the human, audit what the agent has been doing.
  6. Run HTTP mode with the board if your team needs visibility. Tunnel it; do not expose it.
  7. ticketd backup on a cron. It is one SQLite file. There is no excuse not to.

Closing Thought

ticketd is not trying to replace Jira. It is trying to give an agent the same thing an experienced engineer relies on: a notebook that remembers what was tried, what failed, what is waiting, and what is next. The fact that it is a single Go binary with a single SQLite file is what makes it usable. The fact that it speaks MCP is what makes it native to how Claude Code already works.

If you are running agents on real work and tired of explaining context to them every Monday, it is worth half an hour of setup.

Repo: github.com/colossus21/ticketd