Generated from
cat-harness/skills/process/workflow/process-state.md— do not edit here.
Process state — the task you are in, inside the process you are running
An agent working a BPMN process holds nested state: a task, inside a process instance, under a role that owns a swimlane. Losing track of the outer level while still acting on the inner one is the failure this skill exists to prevent — it looks like ordinary work and produces changes nobody authorised.
The three levels, and what each one answers
| level | answers | where it lives |
|---|---|---|
| process instance | which run of which process is this? | beans/workflows/ (state graph on the branch store, cat/cat-harness/beans) |
| swimlane / role | am I the actor who may do this? | the lane on the activity; checked by the executor (task-authorization) |
| task | which step am I on, and is it enabled? | workflow_next |
workflow_next reports what is enabled now, which lane owns it, and which
skill implements it. workflow_complete refuses a step that is not enabled.
Those two are the ground truth; your memory of where you were is not.
Name yourself when you complete a step. Pass actor as a declared actor
id, not a free-text name: workflow_complete checks that actor against the
lane’s role and the ODRL policies before recording anything, refuses a role
mismatch or a deny, and writes the verdict into the history. An actor you
type is recorded as asserted, not authenticated. Say so rather than
presenting it as identity. workflow_gate takes the same actor and target,
so ask it before doing the work
(task-authorization).
The history is checked again afterwards. bun run cat prov:qaqc turns every
instance’s history into a W3C PROV-O log and re-runs the same check on each
step; claude or a login in actor, rather than a declared actor id, shows up
there as undeclared-actor. It is advisory: the findings are listed on the
/prov-qaqc/ page, and CI fails only when that page is stale
(task-authorization §”The after-check”).
Say which process you are in — every turn
Claiming a work item says what you are working on. This says where in the process you are working, which is the question a reader cannot answer from an id.
Name the process, the lane and the task, and say when you switch:
Process:
crdm-requirements, Agent lane · Phase 6 — implement. CompletedA_Implement; next isA_Summary.
The machinery already answers this, and the habit is what was missing rather than the capability: the engine’s “what is enabled now” call reports the enabled step, the lane that owns it, and the skill that implements it — so the answer is something to act on rather than a bare step name.
Say which permission you are acting under. Entering a task in a lane
means performing it as that lane’s role, and since issue #1180 that needs an
ODRL rule that permits it there (permits() in schemas/odrl.ts: the actor,
the action, and the process, task and role as scope). If the answer is
unknown or deny, you are out of process for that task: route it to a lane
whose actor holds the permission, or stop and ask. The engine enforces this
before the task at the deterministic end of the spectrum; at the agentic end
nothing stops you, and the QA/QC report over the PROV-O record finds it
afterwards (agentic-harness.html#bpmn-execution).
Switching processes is the case that matters. Moving between processes changes who is accountable for the next step and which gates apply, and a reader who does not know you switched will assume the old lane’s rules still hold.
Naming it is not the same as recording it (advisory)
Measured 2026-09-20, bean vlhk. 54 proposals merged in one four-hour
window, and beans/workflows/ — the declared workflow-state graph — held
only .gitkeep. Not one session recorded a running instance. A reviewer
sweeping that window could not classify a single session by lane, which is the
one thing this section exists to make possible. supn and v49e measured the
same empty directory from other angles.
There were two readings — the processes are not being run, or they are run and the state is not committed — and they call for opposite remedies. The owner settled it, 2026-09-20: the processes are real, and the instance is recorded. So:
A turn that is in a process has an INSTANCE under the declared
workflow-stategraph. Naming the process in prose is the report; the recorded instance in the state graph on the branch store is the evidence, and a report with no evidence behind it is what produced 54 merges and an empty directory.
workflow_start creates it, workflow_next and workflow_complete advance
it, and the state lives on the branch store precisely so a sibling session
reads the same position — workflow-state.md §”Where running instances live”.
The session-start sweep reads the branch store and reports “no instance recorded” as a FINDING, not as silence, when the directory holds none. A finding is not a failure — plenty of turns are legitimately outside any process — it is the prompt to say which.
This rule is ADVISORY: nothing gates on it. Bean 5ulq measured the
remedy above on 2026-10-02: 9 instances against 755 merges since the ruling,
and no CI workflow reading the graph. That is the xom7 shape, a STRICT rule
whose breach looks exactly like compliance. Calling it STRICT taught readers
that the word is decoration, including where it is enforced. Offered the
choice between gating it, making it advisory, or scrapping it, the owner ruled
on 2026-10-10: make it advisory. So record the instance when the turn is
in a process, but no check refuses a merge without one, and none should be
added without a new ruling.
The gap is computed, never quoted. vlhk’s 54 went stale by ~14× in two
weeks with nothing to say so. bun run cat workflow:instances prints today’s
count: instances recorded, instances started since the ruling, first-parent
merges in the same window, and the CI workflows that read the graph. Merges
are an upper bound on the instances owed, since a steward’s train, a docs-only
fix or a revert may have none. The command reports the gap and exits 0; only
a count it cannot make (store unmounted, shallow clone, an undatable
instance) exits 2.
How to tell you are out of process
This is the part that makes the skill a procedure rather than an exhortation. You are outside the process if any of these is true, and each is cheap to check:
workflow_completerefused a step you believed was next. The refusal is not an obstacle to route around — it is the detector working.- You are about to change a corpus file and cannot name the instance that
authorised it.
scripts/check-corpus-gate.tsrefuses exactly this at the commit boundary; discovering it there is late. workflow_nextreturns a step in a lane whose role you are not playing.- You cannot say which process instance you are in without looking it up — and looking it up returns nothing, or more than one candidate.
- The work you are doing was not in the brief you opened the turn with. An unplanned detour is the most common way this happens, and the least likely to be noticed from inside it.
Recovering
When one of the five fires, stop making changes and recover in this order:
- Re-read the instance.
work_plan_primereports every instance’s position next to its bean. State is committed, so a sibling session’s progress is visible too. - Reconstruct from context what you were doing and which task it belongs to. You usually can: the branch name, the claimed bean, and the opening brief together pin it.
Say what you concluded, and confirm it with the user before resuming:
I completed the QA sweep but never completed
Task_ReviewFindings, so the instance is still sitting before the editor decision. I believe I am incontent-lifecycleinstancecl-2f9, in the editor lane, atGateway_EditorDecision. Confirm before I proceed?This confirmation is not optional and not a formality. An agent that silently re-enters a process it inferred its way back into is asserting authorisation nobody gave it. The whole point of the swimlane guardrail is that the actor is assigned, not self-declared.
The person owed the confirmation may give it in advance for a stated scope — see
confirmation-waiver.md.- If you cannot reconstruct it, say so and stop. A wrong instance is worse than none: completing a step in the wrong run records an authorisation that did not happen.
What you MOVED while you were out of process
Steps 1–3 reconstruct where you were. They do not reconstruct what you changed position of, and for notes and stickies that is a separate problem with a sharper edge:
A move is not self-documenting.
moveNote(schemas/note-anchor.ts) returns the note with its new anchor and nothing about the old one. A note that has been moved cannot say where it was.
So a sticky re-anchored during an off-process detour leaves no trace in the artefact. There is no field to read back, no diff that distinguishes “moved deliberately” from “moved by an agent that had lost the thread”, and — because a move keeps the id — nothing that even looks unusual.
The only possible record is what was written to a bean at the time. That is
not a convention this skill is adding; it is the consequence of two decisions
taken elsewhere. Beans carry no anchor (owner’s rule, 2026-09-19): a bean
is not a thing pinned to a block, it is the record that a pinning changed.
And workflow/bean-link.ts already has the note op that writes it.
So, as part of recovery, before step 3’s confirmation:
- Name every note or sticky you re-anchored during the detour, with its anchor before and after. Read them off the bean’s note trail — not off the notes, which no longer know.
- If the trail is silent and you moved things, say that. A gap is a finding. “I moved stickies and did not record where from” is recoverable by a person who remembers; “everything is fine” is not.
- Do not move anything back on your own judgement. Re-anchoring to undo is
another unlogged move, and it is a durable change made to cover one — see
deletion-requires-confirmation.md, which is the same rule about a different verb.
Considered and rejected: giving the note its own history. A movedFrom
field would make a move self-documenting and remove the need for any of this.
It was not taken, because the history then lives in the artefact being moved —
so a note deleted, or moved by a tool that does not maintain the field, takes
its own audit trail with it. The bean is a separate record, which is what an
audit trail has to be.
Worked example — reading a bean back as the trail
Bean 5oai, 2026-09-19, is one, and the episode is an agent’s rather than a
sticky’s — which is the point: the mechanism does not care what moved.
The bean opened by asserting “schemas/todo.ts carries no block field at
all”. That was wrong: TodoNodeSchema extends CarriedNoteSchema, which
declares targetLabel, and test/sticky-todos.e2e.ts exercises it across two
pages and an orphan label. The agent had grepped the file and not the base it
extends — the second time in that session it asserted an absence without
checking composition.
What makes it a usable trail is that the wrong claim was left in place, with the correction appended beside it rather than replacing it. Read back, the bean shows the premise, the disproof, and the re-scoped work in order. An agent resuming can see that the original framing was abandoned and why, which is exactly what it needs to avoid re-deriving the same mistake.
A bean that had been tidied to show only the correct conclusion would read as
though the work had always been aimed there. That is the failure mode
bean-coordination.md names when it says unwanted work
is scrapped with its reasons rather than deleted: a record that shows only
outcomes cannot distinguish a dead end somebody ruled out from one nobody tried.
What not to do
- Do not start a fresh instance to escape a confusing one. Two instances for one piece of work is two answers to “was this authorised”, free to disagree.
- Do not complete a step to “unstick” the process.
Task_ReviewFindings,Gateway_EditorDecision,Task_Commit,Task_AuthorizeReleaseandTask_PublishReleaseare markedrelaxable="false"precisely because they are the steps where skipping is most tempting and most costly. - Do not treat a refusal as a bug.
workflow_completerefusing a step is the system telling you your model of where you are is wrong.
Relationship to the opening brief
The brief you open a turn with (turn-reporting.md)
is what makes detector 5 usable: without a stated plan there is nothing for the
current work to have diverged from. The two skills are one loop — brief the
route, notice the divergence, confirm the recovery.