The first version of the task walker had four options per task. Complete the task. Update it (due date, priority, description). Skip it. Quit. We shipped it because each of those operations was either a write that needed code-level safety or a navigation command that did not need to live in a model's working memory.

An hour after it shipped, an attorney tried it. The first task on her queue was assigned to her paralegal. She wanted to reassign it. There was no reassign option. The second task was a 2018 note on a stipulated case — she wanted to leave it preserved without marking it skipped. There was no keep option. The third task referenced "the QME report" by description but she wanted to look at the report before deciding. There was no review-docs option. The fourth task was assigned to someone else entirely and she realized she was walking firmwide when she only wanted her own queue — there was no way to switch the filter without quitting and re-launching.

Four legitimate operations. None of them existed.

The original four-option set was correct in the sense that it covered the operations we had explicit safety guards for. It was wrong in the sense that it was not the set of operations attorneys actually perform when they go through a task list. The release that fixed this expanded the option set to match reality, while preserving every safety property the first version had.

What attorneys actually do when going through tasks

We sat with this for a day. The observation: when an attorney walks her open task list, the per-task decision is almost never "complete or skip." The decision tree is more like:

None of these is exotic. They are the same operations a paralegal does when they sit at MerusCase and grind through a task list, except faster and with the case context already pulled up next to each task.

The expanded option set

The walker now presents nine options per task. The letters are deliberately memorable:

OPTIONS:
  C. COMPLETE    — mark done, preserve the record
  K. KEEP        — leave as-is, advance (no write)
  S. SKIP        — same as Keep, advance without action
  R. REASSIGN    — change who owns this task
  U. UPDATE      — change due date / priority / description
  D. REVIEW DOCS — show uploads on this case
  A. REVIEW ACTS — show recent activities on this case
  F. SWITCH USER — change which user's tasks we're walking
  Q. QUIT        — stop and return to aaicase

Three of these write (C, R, U). Two are read-only context expansions (D, A). One swaps the working filter (F). Three are navigation only (K, S, Q). The walker treats every key deterministically — there is no model in the loop that could reinterpret a letter as something else.

Reassign, with the firm directory built in

When the attorney presses R, the walker fetches /users/index right then — not from a cache, freshly — and presents a numbered list of every user in the firm directory. She can pick by number or by typing part of a name. If there's exactly one match by name, the walker takes it. If there are multiple matches, the walker lists the candidates and asks her to pick the number.

Before writing, the walker shows the preview: "reassign task 12345678 from Sample Attorney → Sample Paralegal." She types yes. The walker writes user_id=NEW_ID via merus-fetch, re-fetches the task from /tasks/view/TASK_ID, and confirms the assignment actually persisted. If the API returned success but the field didn't stick, the walker stops and tells her. If it persisted, the walker prints "✓ Reassigned to Sample Paralegal" and re-renders the card with the new assignee in the assigned line.

The audit log gets one line: {"action":"task-reassign","task_id":"12345678","from":"42","to":"19"}.

Review Docs and Review Activities

The two read-only context expansions exist because attorneys make task decisions based on case state, not task state. A task that says "review the QME report" is meaningless without seeing whether the QME report has actually arrived, when it arrived, and what it says.

Press D and the walker fetches /activities/index/CASE_ID and pulls out every activity that has an upload attached. It shows the last 20 with date, upload ID, and the first 70 characters of the activity description. The walker is not reading the PDFs themselves — it is showing the case's upload manifest so the attorney can decide whether to pull a specific document via a separate read upload [id] command.

Press A and the walker shows the recent activity timeline — the last 15 activities by date, with their tag IDs (matched against the firm's catalog) and descriptions. This is for "what's been happening on this case lately" decisions. A task that has been overdue for three months on a case that has had nothing happen in those three months is one kind of task. A task that has been overdue for three months on a case with QME reports, defense letters, and a notice of hearing in the last 60 days is a completely different kind of task. Same task ID, different reality.

Neither sub-flow writes. Both return to the same task card afterward, so the attorney can decide what to do now that she has the context.

Switching filters mid-walk

At startup the walker asks whose tasks the attorney wants to walk. Three options: yours (reads user_id from ~/.aaicase/config.json if configured), everyone's (no filter), or a specific person (picker against the firm directory).

Once chosen, every task card shows a small filter line in its header:

Task 5 of 178   [filter: Sample Attorney]

The attorney always knows whose work she is walking. If at any point she realizes she wanted a different filter — she started with her own queue but wants to spot-check the firm-wide list, or she's been walking everyone's but wants to narrow to a specific paralegal's — she presses F. The walker re-runs the picker. After she picks, the walker re-fetches the task list under the new filter, prints a new header, and starts walking the new list.

This sounds small. It changes the ergonomics of the walker substantially. Before, switching filter required quitting the walker entirely (losing the running counters), restarting it, and re-picking from scratch. After, it's one keystroke.

Keep versus Skip

The original walker had Skip. The new walker has Keep and Skip. They do the same thing — advance without writing — but record different intent in the audit log.

Keep means "this task is intentional. I see it. I'm preserving it. Move on." It writes a task-keep audit entry.

Skip means "I don't want to deal with this right now. Maybe later. Move on." It writes a task-skip audit entry.

The distinction matters for a future audit log viewer. An attorney who has kept the same task on every monthly walk for a year is signaling that the task is structurally part of the case file. An attorney who has skipped the same task on every monthly walk for a year is signaling that the task is unresolved and the walks are not actually addressing it. Same end state, different meaning.

This is the kind of distinction code can preserve and prompts cannot. The model would have collapsed them into "skip" because they look identical in effect. The walker keeps them separate because the audit trail benefits from the separation.

What stayed the same

Every safety property from the first version of the walker remains. Delete is still not an option — typing D is the review-docs branch, not delete, and typing the word "delete" produces a refusal message. The merus-fetch guard still refuses /tasks/del/ regardless of how it's invoked. Every write still re-fetches the task to verify the change persisted; the walker stops and surfaces the failure if it didn't. The fresh-state load before every iteration still re-reads the skill file, the bindings, the audit log, and the firm directory. The agent loop is still paused while the walker owns the terminal — the model is consulted only for the question branch, with no tools available.

The architecture is the same. The interface is richer. The walker is now what it should have been at launch — a tool that maps to the operations attorneys actually perform.

Why we did it this way and not the other way

There is a tempting alternative architecture for this kind of thing. Build a generic "task action" system where the model decides per-task what options to present, and the model's response is parsed into an action and arguments. The model can decide, on a stipulated case, to suggest "keep" over "complete." On an open case it can suggest "complete." On a case with a recent QME it can suggest "review docs." This is the agentic-flow approach, and it sounds appealing.

It is the approach that produced the original bugs. The model decided to batch two tasks. The model decided to offer delete as option 3. The model decided that "explpain more" was a skip. Each of those decisions was made for a reason that, in isolation, was probably defensible — the model saw two related tasks and thought it was being helpful by combining them; it knew delete existed and thought completeness of options was a virtue; it interpreted ambiguous input as a skip because skipping was the lower-risk path. The decisions were locally sensible and globally wrong.

The walker does not let the model make these decisions. The option set is hardcoded. The letter routing is a switch statement. The reassign target comes from a picker against a live API fetch. The update fields come from a structured parser. None of this is more powerful than a model-driven flow could be — it is more bounded. The bound is what we needed.

If the option set turns out to be missing something else attorneys actually do, we extend it. The discovery loop is "ship, watch, extend" rather than "ship and hope the model improvises correctly." This release was one cycle of that loop. There will be more.

The discipline of the small release

Five days, four releases. 4.5.10 moved the upload-content binding into code. 4.5.11 expanded the binding to email tags and removed content-interpretation regex. 4.5.12 added the task delete guard. 4.5.13 took the loop away from the model. 4.5.14 extended the walker to match how attorneys actually use the operation.

None of these is a major feature. Each is a small piece of a larger architecture. The architecture is becoming legible: code refuses what prompts ask, applied to one operation at a time, with the option set discovered by watching actual attorneys use the system.

That is not the only way to build software, but it is the only way we have found to build software that operates safely inside a law firm. The alternative — ship the powerful version, watch the bugs accumulate, fix them in patches — produces a system that is correct most of the time and unsafe occasionally. For workers' compensation case management, where unsafe occasionally means the wrong client's records go on the wrong case file, we cannot accept the alternative.

Version 4.5.14 is live. Walking your tasks now looks like walking your tasks, with the operations you actually need, in a loop the model is not running.