A board is a directory in OpenViking that holds many tasks. A task is one
markdown file. The file is both the ticket and the handoff: whoever reads it
can continue the work without the previous conversation. OpenViking stores
it, indexes it (find/grep), and shares it across every agent and user of
the account. There is no other state.
<root>/<board>/<id>.md the task: header + current state, rewritten every turn
<root>/<board>/archive/<id>.md its folded detail: append-only, never rewritten
<root>isviking://agent/kanban. Probe it once per session withov stat viking://agent/kanban(orov mkdirwhen it does not exist yet); if the server answersPERMISSION_DENIED(older servers block writes underviking://agentexcept skills), useviking://resources/kanbaninstead.$OV_KANBAN_ROOT, when set, overrides the probe. The protocol is identical, only the root changes.<board>is the isolation unit (a project, repo, or team). It is a plain directory:ov aclon it decides who may read or take tasks. Create one withov mkdir <root>/<board>andov mkdir <root>/<board>/archive(write --mode createneeds the parent directory to exist).<id>isYYYY-MM-DD-short-slug, unique inside the board.
Use the ov CLI (works from any agent that has a shell). If the OpenViking
MCP tools list, read, write, grep are registered, they are
equivalent; never fall back to raw HTTP. Prefer the CLI for writes: a
write that runs into the MCP client timeout has usually still landed, so
read the file back before writing again.
Read in layers
Detail is folded, not compressed: a lower layer holds the full text the upper layer points to. Read top-down and stop at the first layer that answers the question.
| layer | what it holds | how to read |
|---|---|---|
| boards | one abstract per board | ov ls <root> |
| board view | title, status, owner, updated of every task |
the ov grep below |
| task | current state, enough to continue the work | ov read <root>/<board>/<id>.md |
| archive | everything folded out of the task, verbatim | ov grep '<keyword>' -u <root>/<board>/archive/<id>.md, then ov read |
ov grep '^(title|status|owner|updated):' -u <root>/<board> -x <root>/<board>/archive -o json
Open the archive only when a Decisions or Progress line needs its
history or evidence. ov find "<topic>" -u <root> locates related tasks
across boards.
Task file
Copy references/task-template.md. Header:
| field | values |
|---|---|
id |
YYYY-MM-DD-short-slug, equals the file name |
title |
one line |
status |
open · in_progress · needs_user · blocked · done · cancelled |
owner |
agent id currently holding the task, empty when handed off |
board |
the board directory name |
updated |
output of date +%Y-%m-%dT%H:%M:%S%z, set on every write |
Body sections, in this order: Goal (done criteria checklist), Context
(cwd/repo/branch, key files or URIs, constraints), Decisions (dated, with
why; user answers land here), Progress (verified only, one line per item
with its evidence), Next (ordered checklist; first unchecked box is the
next action), Questions (concrete questions for the user; non-empty means
status: needs_user), Log (one dated line per turn, the last 10).
The file is the truth. If it disagrees with the live repo, trust the repo,
fix the file, and say so in Log.
Write the body in the task's main language: the language the user gave the task in, or the one an existing task already uses. Header keys and status values stay English.
Fold
The task file stays short enough to read in one call. Text leaves it by moving to the archive, never by deletion or by a shorter rewrite:
Loglines beyond the last 10;Nextsteps andDecisionsthat a later turn replaced;- evidence longer than one line (command output, diff summary, review
notes): the
Progressline keeps the result, the archive keeps the rest.
Append one entry per turn: a ### <updated> <agent id> heading, the status
change as from -> to when there is one, then the moved text verbatim.
ov write <root>/<board>/archive/<id>.md --append --from-file ./entry.md --wait
Create the archive with --mode create the first time. A turn that moves
nothing out and keeps the status writes no entry.
One turn
Every turn is bounded: pick, claim, one slice, verify, write back, stop.
- Pick. Read the board view (the
ov grepabove). Take the task the user named, else anopenone, else anin_progressone whoseupdatedis older than 2 hours (stale claim). Skipin_progresstasks another agent updated recently. - Claim.
ov readthe file. Setstatus: in_progress,owner: <your agent id>,updated: now, add aLogline, write back (step 5). Agent id is$OV_KANBAN_AGENTif set, else<tool>@<host>(e.g.codex@mbp). - Slice. Do the first unchecked item in
Next, or more while it stays one coherent, verifiable piece of work. ReadContextandDecisionsfirst; they are the contract from earlier turns and the user. - Verify. Run the check that proves the slice (test, build, command
output, diff). Only verified work goes into
Progress, with its evidence. - Write back. Fold first (see above), then rewrite the whole file with
the updated header and body:
Onov write <root>/<board>/<id>.md --from-file ./task.md --waitCONFLICT(path busy) wait a few seconds and retry once; onALREADY_EXISTSwhen creating (--mode create) pick another id. Then choose the exit state:- more to do and you continue next turn → keep
in_progress; - handing off (context nearly full, wrong tool, or the user asked) → clear
owner, setstatus: open, make sureNextis precise enough for a cold reader; - a decision only the user can make → write the question in
Questions, setstatus: needs_user, clearowner, then ask the user in the current session if there is one and stop; - waiting on something external (a PR review, another task) →
blocked, name what unblocks it inNext; - all
Goalboxes ticked with evidence →done; - the user dropped the task →
cancelled, clearowner, record who decided and why inDecisions.
- more to do and you continue next turn → keep
Creating a task is the same write with --mode create and status: open;
fill Goal and Context from the user's request and put the first concrete
step in Next.
Answering a needs_user task: record the answer in Decisions, clear
Questions, set status: open, write back. The next turn picks it up.
Long-running and multi-agent
- Unattended loop. references/loop.sh runs one
agent turn after another (
codex execby default, any CLI viaOV_KANBAN_CMD) until no runnable task is left or the tick budget is spent, and notifies through herdr when it stops.needs_usertasks are not runnable, so the loop drains around them and stops when only those remain.bash references/loop.sh <board> 5 /path/to/repo - Cross-agent handoff. Any agent that reads the file continues it;
Claude, Codex, or a human via
ov tui. Nothing is tool-specific except the agent id inowner. - Handoff to a person. A step only a person can do (approve a release,
run a privileged command) is a
needs_usergate: name the person and the exact action inQuestions, clearowner, stop. When they report back, record the outcome inDecisionsand setstatus: open. - Several tasks at once. Give each subagent its own agent id and one
task; they never share a file. Serialize tasks that touch the same files
by listing the dependency in
Nextand setting the later oneblocked.
Rules
- Never claim a task another agent updated within the last 2 hours.
- Never write secrets, tokens, or raw logs into a task or its archive; write conclusions and the evidence that backs them.
- Never mark
Progresswithout evidence, never markdonewith an uncheckedGoalbox. - Never drop text from a task file; fold it into the archive.
- Do not run a second task in the same turn; write back, then pick again.