Project Manager (agent-pm)

The PM is the user's assistant. It is off the critical path: removing PM does not break the workflow, it just makes delivery less optimized. PM exists to:

  • curate tailored context briefs for working agents so they don't burn tokens rediscovering the project
  • promote durable findings (cross-project patterns, lessons learned, reference notes) into the org-level Forgejo Wiki so knowledge survives outside any one repo

PM - actually fjx pm ... tooling, not the agent itself - is the sole interpreter of /<command> syntax in Forgejo comments. Working agents (dev, qa, builder) MUST NOT parse /commands as directives — those are reserved for PM, with the trust map as the single choke point for vetting. Natural-language directives in comments remain in scope for the working agents per PR conventions.

The PM maintains one canonical ledger comment per issue, which it edits in place.

  • Issue comments: incoming owner directives authored by humans
  • Ledger: record of directives PM has processed
  • Labels: applied by PM in service of authorized commands; agents also apply labels directly under their own protocols
  • Brief comments (<!-- pm:brief:<role> -->): tailored context for working agents, one per role

Constraints:

  • No external database
  • Human-readable
  • Idempotent across runs
  • Safe to resume after interruption
  • Durable in issue history
  • Trust map enforces authorization on /commands
  • removing PM degrades to manual mode without breaking the workflow

Non-Goals:

  • Full natural-language comment understanding
  • Tracking PM's internal scheduling or cron state
  • Replacing Developer/QA/Builder ledgers (see issue conventions)
  • Acting as a controller or invoker of working agents
  • Gating, policing, or accountability for working-agent activity
  • Tracking agent state in the PM ledger (Forgejo timeline + agent ledgers are the record)

Manual Mode

PM is off the critical path. Without PM, the user can:

  • apply labels directly in the Forgejo UI
  • write briefs by hand using the <!-- pm:brief --> marker (working agents read briefs by marker, regardless of author)
  • skip briefs entirely; working agents fall back to reading the issue body and labels
  • drop fully into a Claude Code shell and run the workflow by hand against AGENTS.md and the workflow docs

When pm next resumes after downtime, it processes only comments after the checkpoint and does not unwind manual label changes or briefs.

Canonical Homes

Artifact Lives on Quantity
Agent Brief Issue One per role (dev, qa, build, …)
Agent Ledger Issue One per role (see issue conventions)
Diff, code PR per Forgejo convention

Owner directives are issued on the issue. PM only watches issue comments — PR comments are out of scope. This keeps one canonical surface for both observation & implementation.

The brief markers are role-scoped: <!-- pm:brief:dev -->, <!-- pm:brief:qa -->, <!-- pm:brief:build -->. pm next upserts each by (issue, role).

Boundaries

The PM is singular per repo. Working agents maintain their own work ledgers on the same issue using their own markers (see issue conventions) — PM MUST NOT modify those. Working agents MAY apply agent/* labels directly when reporting their own state (agent/working, agent/review, etc.); PM does not gate this. Working agents MUST NOT modify the PM ledger or parse /<command> syntax from comments.

PM has no code-write authority

PM does not commit, does not modify files in the repo (including ./wiki/). Project-local memory in ./wiki/ is updated by Developers in their PRs, alongside the code change that motivated the update. PM's write surface is Forgejo only: comments, labels, its own work ledger, the brief comment, and the org-level Forgejo Wiki (fjx wiki set). This boundary is what makes PM safe to invoke on a cadence.

Why PM gets more judgment than DEV/QA

The DEV and QA prompts are tightly scoped — narrow output shapes, evidence-required blocker checklists, explicit anti-patterns. PM's prompt is looser, and deliberately so. PM's value is curation, which is judgment work that doesn't reduce cleanly to a checklist. Deciding whether a Context bullet earns its place, whether a note belongs in the project wiki, how to phrase a brief refresh — these are calls a tight imperative prompt would get wrong.

The constraint that keeps PM safe despite the loose prompt is the bounded write surface: PM has judgment inside (comments, labels, briefs, ledger, wiki), and none outside (the repo tree, working agents' ledgers, /<command> parsing).

Owner directives

Owner directives — the /<command> vocabulary owners type in issue comments — are parsed and executed by fjx pm next, not by the PM agent. See Slash commands for the owner-facing reference. The trust map (pm.trustMap in .fjx-cache.json, default ["erik"]) governs which authors fjx pm next accepts directives from.

Triggers

fjx pm next fires the following transitions without any directive:

  • agent/new + no <!-- pm:brief:dev --> → write initial dev brief (phase: simple), strip agent/new, assign issue to agent-dev
  • agent/review + no <!-- pm:brief:qa --> → auto-route to agent-qa only when all four guards hold: (1) a PR (open or merged) is linked to the issue, (2) assignee=agent-pm (so fjx dev done ran), (3) the agent/review label is present, and (4) there are zero new comments this tick. When any guard fails the trigger is suppressed and the issue is surfaced via a qa handoff held: … diagnostic so the PM LLM can apply judgment per src/prompts/pm-bootstrap.md. Any new comment defers — fjx never auto-routes past unread owner prose.

The two-part check (label present AND brief absent) prevents spurious re-fires when the same label appears at a later workflow stage. The additional QA guards prevent premature handoff while dev is still working or while the owner is steering the issue.

Dispatch is by assignment. PM hands work to a role by setting assignee=agent-<role>. Labels record state (agent/working, agent/review, agent/blocked); they do not name the next worker. The dispatch labels agent/dev, agent/qa, and agent/build are not used — fjx <role> next filters by assignee.

Brief management

A brief is a separate comment scoped to the issue, with a role-specific marker (<!-- pm:brief:dev -->, <!-- pm:brief:qa -->, <!-- pm:brief:build -->). See src/prompts/pm.md for authoring guidelines (shape, earn-its-place test). The minimal-shape brief points at the phase prompt by file path and lets the prompt file be the source of truth; brief authoring guidelines are in the PM prompt, not here.

pm next creates at most one brief per (issue, role). Refreshes are in-place edits. The user MAY write a brief by hand using the role-scoped marker; pm next detects the human-authored brief by marker and only refreshes when the owner explicitly requests it — never unprompted.

Ledger structure

<!-- agent-pm:ledger -->

## Ledger for: agent-pm

Status: watching | paused | error | closed Checkpoint: <ISO timestamp> Briefs:

- [dev](#issuecomment-M) @ <ISO timestamp>
- qa: absent
- build: absent

Depends: 12 14

### Activity

Format: `i<N>/<command> : <action>`

1. i4/needs-spec : refreshed dev brief
2. i4/spec-approved : applied
3. i4/refresh-brief : refreshed dev brief
4. i4/spec-approved : rejected — @agent-dev not authorized
5. i4/foo : unrecognized
6. i4/block : superseded by i4/unblock
7. i4/note : recorded in notes 7

### Notes

7. Freeform observations

The Activity section is a numbered list so entries can be cross-referenced by position. Each entry has a leading i<N>/<command> token (issue number + command), the literal : separator, then a free-form action. This token is the dedupe key — it MUST be parseable with a fixed regex.

For /note, the action is recorded in notes N where N is the Activity entry's own position number. The note text is appended to the Notes section at the same number so the cross-reference is navigable in both directions.

Brief lifecycle is captured in the metadata Briefs: block — pm next updates the per-role line on refresh rather than logging brief edits as Activity entries (Forgejo comment edit history covers per-edit detail). Present briefs use a markdown link [role](#issuecomment-ID) @ <ISO timestamp>; roles with no brief are listed as role: absent.

Issue Finalization

PRs use Refs #<N>, not Closes #N, so a PR merge does not auto-close the issue. The issue stays open through dev → qa → PM finalization. fjx pm close <N> is the canonical close path:

  1. Reads the PM ledger.
  2. Sets Status: closed and appends a closed Activity entry. Brief comments are left untouched — the issue is closing and no working agent will read them.
  3. Closes the issue on Forgejo (PATCH state=closed). Idempotent if already closed.
  4. Removes the issue's worktree if present: standard path is ../worktrees/<project>-<N> where <project> is the repo name and <N> is the issue number. Uses git worktree remove --force (safe because the issue is closed).
  5. Prints the ledger Notes verbatim under notes_for_review[]. Anything durable is promoted to the Forgejo Project Wiki with fjx wiki set <slug> --body-file ... (see src/prompts/pm.md for the pattern and fjx wiki --help for the command surface). set is an upsert — safe to re-run.

If a closed issue ever appears in closed_candidates[] (e.g. a human closed it manually before PM finalized), run fjx pm close <N> to finalize. Sweep filters out issues whose PM ledger already has Status: closed, so finalized issues do not re-surface. Untracked closed issues (no PM ledger) are also filtered out.

Forgejo API

Use fjx. Docs at <remote-url>/swagger.v1.json. Auth via env vars FJX_TOKEN (preferred) or FJX_USERNAME + FJX_PASSWORD. fjx derives server, owner, and repo from the origin remote.

Failure handling

  • Missing Ledger: pm next creates one on first write.
  • Corrupted or Duplicate Ledgers: PM stops processing the issue, posts a diagnostic, and applies agent/blocked. Manual intervention required.
  • Trust Map Drift: pm next honors the current trust map; in-flight commands from a removed author are rejected and recorded.
  • API Failure: On transient API failure, the checkpoint is not advanced (it's only saved after success). The next run re-processes the affected window safely thanks to Activity-based dedupe.

See also