6.2 KiB
Plan: The work board surface
The decision V-431 asked for. Written 04-08-2026.
Verdict: build it, in a smaller shape than the task imagined. The board is worth
moving out of the file. The intake form belongs on the /tasks page, not on the voice
path. The argument is not built, now or later.
Why it is worth building
The reason is the one the task gives and it holds: company rules forbid pointing Claude at work repos, and Maven is the one assistant on the box that work material may reach. No telemetry, no cloud model, no third-party account. That is not a preference here, it is the whole permission.
The build is also small, because most of it landed already:
| Piece | Where | State |
|---|---|---|
| task rows, dedupe, status lifecycle | internal/store/migrations.go:149 and migration #15 |
done |
| capture from speech, urgency stripped | router.ParseTaskCapture, router.TaskCaptureGrammar |
done |
| recite the list on request | router.IsTaskListQuery |
done |
| a page to read and change the board | /tasks in cmd/mavweb |
done |
| counting a shape without judging it | internal/memory/behavior.go |
done, as precedent |
| a proposal he reads when he chooses | /routines, the proposed-routine queue |
done, as precedent |
tasks already carries status (candidate, open, done, dropped), due_ts, weight,
source, evidence, ext_id and resolved_by. Three things are missing. It has no
definition of done and no blocked-on. There is no way to edit a task after capture:
SetTaskStatus moves the status and nothing writes text, date or weight again. And
there is no grouping he controls, because order is computed by tasks.Rank alone.
Where the form lives, and why not voice
The task asks the form to refuse a capture with no definition of done. That refusal cannot live on the voice path, for two reasons.
The parked-state mechanism is binary. resolveConfirm in cmd/mavend/confirm.go
answers yes or no against a slot with a 90-second life. Filling four fields over four
turns is slot filling, which is a different mechanism and a new one. Nothing in the
daemon does it today.
The definition of done is the worst possible field to dictate. It is the one string that has to be exact, because its whole purpose is to be unarguable later. Whisper transcribing a sentence of Russian work vocabulary is where exactness goes to die, and the capture path already had to strip a question mark that whisper invented.
So: voice captures a line and recites the list. The page is where a line becomes an
item with a definition of done, a blocked-on and a date. A captured line lands as
candidate and stays there until it is filled in, which is what candidate was for.
The refusal the task wants survives, moved: the page will not promote a candidate to
open without a definition of done, the same way ParseTaskCapture will not file a
marker with nothing after it. And the field must close on either outcome, so "it already
works" counts as complete. A definition of done that only one result satisfies is a wish.
Does the stage-0 trick stretch
The task asks this before any shape is committed to. It was checked. The answer is partly.
TaskCaptureGrammar matches every utterance and lets ParseTaskCapture decide inside
Build, keeping the intent at note and leaving the frozen seven-intent contract alone.
That trick stretches to recite and to status change: both are a marker plus a
referent, both are a lookup, and a status change is a small closed verb set over a list
he can see. It does not stretch to intake, because intake is not one utterance, and
it does not need to, because intake moved to the page.
One cost to name. Each such grammar matches everything and runs its parser on every turn, ahead of the resident model. Two more of them is fine. A dozen would make stage 0 a second router with no evaluation behind it, and at that point the frozen enum is the smaller problem.
What is not built: the argument
Not now and not later behind a flag. The task is right about why, and
internal/memory/behavior.go already argued it for habits: a 1.7B asked whether evidence
proves anything will agree fluently and launder a guess into a decision. A wrong claim
about his work, stated confidently, is the most expensive kind of wrong Maven can be.
The line is the same line behaviour memory drew. She may count:
- no state change in eleven days
- blocked on a person, with no date
- four of nine waiting on two people
Those are queries over rows. She may not assess whether a build proves anything, whether a blocker is real, or whether a task should be dropped.
Persona
A progress tracker is a nag by default, and "not a nag" is hard. The line is already drawn twice in the codebase and it is drawn the same way here:
- A date he set becomes a reminder. He set it, so it is not her raising it.
- A stall becomes a proposal he reads when he chooses, on a page, like
/routines. - Ask what is on the board and she recites. She never opens with it.
The day plan is the place to watch. tickLoop.dayPlan reads calendar events, pending
reminders and checklist facts, and it does not read tasks. Adding the board to the
morning nudge is exactly the move that turns this into a nag, so the board goes on the
page and into the answer when asked, and not into the unprompted morning message.
The build, as tasks
- Two columns on
tasks: definition of done, and blocked-on. Blocked-on resolves through Nexus like any other person reference, because identity lives in Nexus. - An edit path. Today a task is write-once except for its status, so the form has nothing to save into.
/tasksgrows the form: promote candidate to open only with a definition of done, set a date, set blocked-on. A date set here writes a reminder.- A status-change grammar at stage 0, following
TaskCaptureGrammar. - Counted stall shapes on
/tasks, phrased as counts. No assessment.
Note for whoever picks up 3: /tasks accepts its POST without the step-up gate, while
/routines and /tools require a passkey. That was deliberate for capture. Adding an
edit path is the moment to re-argue it, not to inherit it silently.
Each is separable and each is worth stopping after.