Skip to content

tracker.md — your project's policy file

Every project Pigeon connects to gets a tracker.md file at its repo root. It’s the project’s policy contract — a small Markdown file with YAML frontmatter that tells Pigeon how agents should behave when they’re working on this project specifically.

If the human can’t see it and edit it in the surface where they’d naturally encounter it, the agent shouldn’t trust it. tracker.md is that surface.

A tracker.md is a single Markdown file with two parts:

  • Frontmatter (YAML) — structured policy: which write tools require an intent, per-column prompts the agent should respect when a card sits in that column.
  • Body (Markdown) — your project’s general orientation prompt. The current phase, key constraints, what the agent should know to make good calls.
---
schema_version: 1
project_slug: my-project
intent_required_on:
- moveCard
- deleteCard
columns:
In Progress:
prompt: |
Limit to 2-3 cards. Move here when you start writing code,
not when planning.
Review:
prompt: |
Code is written and needs human verification. Don't move
to Done without explicit approval in a comment.
---
# Project policy for my-project
Current phase: shipping the v2 onboarding flow. Treat anything
outside that as backlog unless it's blocking the release.
Start every session with `briefMe`. Prefer `source: 'pinned'`
work over `source: 'scored'`. End every session with `saveHandoff`
(or `/handoff` in Claude Code).

At the repo root of every connected project — alongside package.json, CLAUDE.md, README.md. Pigeon resolves it from Project.repoPath, which is set when you run scripts/connect.sh from inside the project (or call registerRepo from your agent).

  • Directoryyour-project/
    • tracker.md
    • CLAUDE.md
    • AGENTS.md
    • package.json
    • Directorysrc/

It’s git-versioned. It rolls back like any other file. It diffs in pull requests. The version your agent sees is exactly the version on the branch you’re working on.

Pigeon’s MCP tools call loadTrackerPolicy(projectId) whenever they need policy context. That’s:

  • briefMe — surfaces the body prompt as part of session start
  • getCardContext — includes the relevant column prompt when you ask about a specific card
  • planCard — feeds both the body and the column prompt into the planning protocol so the plan respects project constraints
  • Every write tool in intent_required_on — checks the call has a non-empty intent and refuses if not

The file is hot-reloaded on every tool call. Edit it, save, and the next agent call sees the change. No restart required.

The best tracker.md files share three properties.

If your file scrolls more than a screen, it’s too long. The body is read in full at session start; long bodies inflate every conversation. Keep it to:

  • A one-line statement of the project’s current focus
  • Two or three constraints the agent should default to
  • Any “rules of the house” specific to this project

If you find yourself writing tutorials or rationale, that belongs in AGENTS.md or docs/. The tracker.md is for runtime decisions.

tracker.md answers: what should the agent do, and not do, in this project?

It does not answer:

  • “How do I install this project?” → README.md
  • “What’s the build command?” → CLAUDE.md
  • “Why did we choose Postgres?” → architectural decisions (recordDecision)
  • “What does this codebase look like?” → the codebase

Crossing those lines makes tracker.md long, stale, and load-bearing for the wrong reasons.

Projects move through phases. The constraints that mattered in week 1 are noise in week 8. Update the body when the project does:

# Project policy
Current phase: **post-launch stabilization (week 3 of 4)**.
- Bug fixes only. No feature work without explicit go-ahead in a comment.
- All `moveCard` to In Progress requires linking to the GitHub issue.
- The reliability dashboard (link) is the source of truth for SLO regressions.

A future agent reads that and knows the right defaults without you re-explaining.

The columns.<name>.prompt block lets you teach the agent how each column behaves in your workflow. The prompt is surfaced when the agent reads or writes a card in that column.

columns:
In Progress:
prompt: WIP limit 2. Anything more is a smell — finish or unstick.
Review:
prompt: |
Don't merge without an approval comment from a human.
Auto-moves to Done when the linked PR merges.

Pigeon v4.x stored project policy as a projectPrompt DB column. v5.0 dropped that column and replaced it with tracker.md.

If you’re upgrading from v4.x:

  1. On v4.x, run briefMe() in any agent session. If _warnings[] mentions projectPrompt, you have content that needs migrating.

  2. For each project with content, call:

    runTool('migrateProjectPrompt', { projectId: '<your project id>' })

    The tool writes a tracker.md to the project’s repoPath containing whatever was in the column. Idempotent — refuses to overwrite an existing tracker.md.

  3. Review the new file, edit it for clarity, and commit it to your repo.

  4. Then pull v5.0. The column is dropped on npm run db:push. Anything still in the column when you pull is lost.

The full migration walkthrough lives in docs/archive/MIGRATING-TO-PIGEON.md.

The v4 model stored project policy as a DB string (Project.projectPrompt). The thinking was “one less file to manage.” In practice it had three problems:

  • Not reviewable. Edits happened invisibly, in the web UI or via an MCP tool call. No diff, no PR, no rollback.
  • Not versioned with the code. Branch out a feature; the policy didn’t follow. Merge back; the policy might have drifted under you.
  • Not visible. A new contributor didn’t know it existed. The agent appeared to have opinions out of nowhere.

tracker.md solves all three by making the policy a normal file in your repo. The agent reads from disk; the human reads the same file; both see the same thing.

The shared-surface principle: if the human can’t see it and correct it where they’d naturally encounter it, the agent shouldn’t trust it.