14 KiB
The work cycle — what happens at the end of a piece of work
Status: Current
Owner: <who maintains this>
Last reviewed: <YYYY-MM-DD>
Governs: what must be true before a piece of work counts as finished
Review trigger: Any change to what the Command Center reads, or to which of
those channels a person rather than an agent has to write
This is a procedure, not a status board. Nothing here records what is open, what is next, or what is blocked — those live in the tracker and on privacyllc.dev, and a copy of them in this file would be the second disagreeing record that
DOC_TRUST_MAP.mdexists to prevent.
Why this file exists
A piece of work ends in more than one place. The code is committed; the issue that asked for it is still open; the document the change contradicts still says the old thing; and the project screen at privacyllc.dev still shows last week's next action to whoever opens it.
None of those catch up on their own. Two of them cannot be caught up later by anybody but the person who did the work, because by then nobody knows what the next action was meant to be.
The cycle
Run all of it, in this order, every time. It is short on purpose.
- Close what you finished.
closes #Nin the commit that does the work, so the record comes from the thing that happened rather than a date typed afterwards. If no single commit finished it, close it by hand with the evidence — a path, a symbol, a test name, or the command that proves it. "Done" is not a close. - File what you found. A defect noticed on the way past is an issue with a severity label and the build SHA it was seen at, not a memory. Filing it costs a minute; the alternative is finding it again from scratch, or shipping it.
- Close the milestone if the batch landed. A milestone with every issue closed and itself still open reads as a batch still in progress — see Open and closed are not bookkeeping.
- Update the documents this change triggered. Read the
Review triggerlines: a new module, a changed data shape, a new migration, a new boundary something crosses. Those edits go in the same commit as the code, for the reason in Docs travel with the push. - Push. Nothing above is visible off this machine until you do, and step 7 reports on what was pushed.
- Write the log entry in
docs/history/DEVELOPMENT_LOG.md: what changed, what it proved, Next action, and Blockers. Dated, append-only, newest first. - Tell the Command Center, which is three calls: reconcile, so it re-reads
the tracker and the pushed docs;
PATCHthe project's summary and next action, which nothing else writes; and a check-in if what changed is worth a timestamped note. Commands are at the bottom.
Steps 6 and 7 are the two that get skipped, and they are the two nobody else can do afterwards.
Next action — where it lives
| Copy | Where | What it is |
|---|---|---|
| The current one | nextAction on the project, privacyllc.dev |
the live answer to "what happens next", shown to whoever opens the project screen |
| The dated one | the newest entry in DEVELOPMENT_LOG.md |
what the next action was at that point — history, not status |
These are not two records of the same thing, and the distinction is worth holding on to. The field is overwritten every time and always describes now. The log entry is never edited and describes a moment — which is what makes it safe to keep, and why an old entry naming a next action that has since been done is not stale, it is a receipt.
Write the next action as an action: the thing a person would start on Monday, specific enough to begin without asking a question. "Continue the work" is not one. If the honest answer is that you do not know, that is a real answer — say what has to be decided and by whom.
Filing an issue can change what the project card says next, without anyone
choosing that. The dashboard's next action is the newest open issue in the
current milestone — not the most severe one; severity labels have no influence
on it at all. So a routine P2 filed into the batch you are working in replaces
whatever the card was showing, and it will keep showing that until something
newer arrives. The nextAction field is the only way to say something different
on purpose, which is most of why step 7 exists.
currentSummary, nextAction and description are write-only: they are
deliberately absent from every API response, because free text can name a
customer or an unannounced product. Only the admin screen shows them back. Never
report them as empty because a GET did not return them.
Blockers — where they live
A blocker is recorded in up to three places, and an agent can write only the first and the third:
| Where | What it holds | Who writes it |
|---|---|---|
| The tracker | the work itself — an issue labelled release-blocker, or P0 when it ships broken |
anyone, including an agent |
| The blockers table on privacyllc.dev | the business-facing blocker, with a severity, an owner and a resolution note | a human, in the admin UI |
A check-in's blockers field |
narrative: what is stuck, said in a timestamped note | anyone, including an agent |
The agent API has no route that creates, edits or resolves a blocker on the
site. Posting a check-in whose blockers field says something is stuck records
narrative — it does not touch the blockers table, and it does not clear
anything. An agent that reports "blocker filed" after a check-in has told the
truth about the note and a falsehood about the table.
So: file the issue — that is the copy the work is actually tracked in — and when it belongs in front of a stakeholder, say so plainly and let a human enter it. Before treating a site blocker as resolved, restate its title and status and get explicit confirmation; the admin UI requires a resolution note that the API cannot supply.
If the work is blocked and nothing is filed anywhere, the project simply looks slow.
Open and closed are not bookkeeping
Every open issue is a denominator, and not in the abstract: the percentage on the project screen is closed issues over all issues in this tracker. Nothing else produces it. That has three consequences worth stating in full:
- An issue left open after the work is done understates the project permanently, and the understatement compounds — a fortnight of finished work with unclosed issues reads as a fortnight of no progress.
- An issue closed without evidence cannot be reopened with confidence, because nothing in it says what "fixed" meant. That is why the close comment carries the path, symbol, test or command.
- Invented future work makes every percentage wrong, permanently and in one direction. Do not pad the tracker. If the real answer is one milestone and three issues, file exactly that.
Milestones are the same argument at batch scale, and they are counted the same way: milestones closed over milestones total is the second figure on the project screen. Closing the last issue under a milestone does not close the milestone, so a tracker full of complete-but-open batches reports a project as less finished than it is — and cannot answer "what shipped".
File every issue into a milestone. One filed outside still counts against the headline percentage while being invisible to the milestone figure, which is how two readings of the same project come to describe different amounts of work. The site measures the gap rather than ignoring it.
One trap that costs an afternoon, and it is about a card rather than a figure: the Milestones list on privacyllc.dev reads the Command Center's own table, which only an admin can write. A repository whose milestones are being counted in the figure above can still show "No milestones have been added yet" in that list. It is not a sync failure and no amount of reconciling changes it.
Docs travel with the push
The Command Center reads this repository's documents at a commit, and stores the SHA it read them from. When that SHA falls behind the repository's newest commit, the docs report is marked stale — not wrong, not missing, stale, which is the honest description of a document that was accurate at a commit nobody is running any more.
Two ways to produce it, and both are ordinary carelessness rather than bad luck:
- Code pushed, documents not updated. The report is recomputed at the new
SHA against prose describing the old behaviour. Nothing flags this; the
document is simply confidently wrong now, and its
Last reviewedline still looks recent. - Documents updated, not pushed. The site keeps reporting the old ones. A correction that exists only on your machine has not been made.
Hence step 4's insistence that documentation edits ride in the same commit as the change that caused them. It is not tidiness — a doc commit that comes "after this next thing" is the commit that does not get written, and the manual's migration table in the project this template came from sat six migrations behind before anybody noticed. Every reader in between trusted it.
What the Command Center reads, and who writes it
Four independent channels, and only two of them are automatic:
| On the project screen | Source | How it gets there |
|---|---|---|
| Headline % — issues closed / all issues | the repository's tracker | automatic — on reconcile, and immediately on a webhook delivery |
| Second figure — milestones closed / all milestones | the repository's milestones | same read, same moment |
| Milestone coverage — issues that sit in no milestone | the repository's tracker | same read |
| QA verdict — round, build SHA, overall sentence | docs/qa/ClaudeReport.md |
push the repository, then reconcile |
| Current summary / Next action | manual fields on the project | PATCH /agent/projects/<slug> — nothing else writes them |
| The Milestones card, and a typed weighted plan | the Command Center's own milestone table | admin UI only |
| Blockers | the Command Center's own blockers table | admin UI only |
The first three are why steps 1 and 3 are steps: closing an issue and closing a milestone each move a figure a stakeholder can see, that day. The last two are lists rather than figures, and they are the ones an agent cannot write — a repository whose milestones are counted in the second figure can still show "No milestones have been added yet" on the card, because that card reads a table only an admin fills in.
A repository that has not adopted the four label names is reported as not adopted rather than as zero defects, and one with its tracker switched off is reported as switched off rather than as an empty backlog. Absence is never rendered as a measurement — which is exactly why a real backlog nobody filed looks like nothing at all.
The commands
The tracker is the Forgejo instance at dream.scheller.ltd, and steps 1 to 3 happen there. Its credentials, the Cloudflare trap and the reason it has to be that instance rather than any tracker are in the project README's Where the tracker is — one copy, named once:
set -a; . ~/.openclaw/docker-registry.env; set +a
python3 docs/architecture/scripts/forgejo-issue.py list
python3 docs/architecture/scripts/forgejo-issue.py close 42 "Fixed in a1b2c3d; tests/foo.test.js covers it."
Step 7 talks to the Command Center instead:
# The token lives in this machine's credential store, never in the repository.
TOKEN="$PRIVACY_LLC_TOKEN"
BASE="https://privacyllc.dev/api/internal/v1"
AUTH="Authorization: Bearer $TOKEN"
Reconcile — re-read the tracker and the pushed documents. Do this after the push, not before:
curl -sk -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d '{"kind":"reconcile_all"}' "$BASE/agent/jobs"
Summary and next action — read the project first for its version, then
send a flat body carrying that version. A stale version is rejected rather than
silently overwriting somebody else's edit:
curl -sk -H "$AUTH" "$BASE/agent/projects/<slug>" # for version + state
curl -sk -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
-d '{"version":<n>,"health":"on_track",
"currentSummary":"<where the project stands, in a sentence or two>",
"nextAction":"<the next concrete thing, specific enough to start>"}' \
"$BASE/agent/projects/<slug>"
A check-in — a timestamped "what changed", when there is something real to
report. summary is required and must say something; the API refuses an empty
"no change" update, deliberately:
curl -sk -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d '{"projectId":"<slug>","health":"on_track",
"summary":"<what changed>","accomplishments":"<what landed>",
"blockers":"<what is stuck, or omit>","nextActions":"<what is next>"}' \
"$BASE/agent/updates"
Health is one of on_track, caution, off_track, unknown. Never invent
one, and never report progress the API did not return.
What this file is not
- Not the work list. That is the tracker: milestones are batches, issues are deliverables.
- Not the release procedure. That is
docs/architecture/scripts/release.shanddocs/security/SECURITY_CHECKLIST.md. - Not the QA procedure. That is
docs/qa/ClaudeQAPlan.md, which ends in its own version of step 7. - Not a place to record status. If you are about to add "current state" or a list of outstanding items below this line, the tracker is where it goes.