The case names, parties, file numbers, and specific details in this post are fictitious demonstration data shown to illustrate the system's behavior. No real client information is shown.

The attorney typed three words: "go through my tasks." The system returned 178 open items and offered to start with the oldest one. The first task was years old, on a case that had been stipulated and closed. The description was three words: "$5710 DIFFERENCE."

The system presented three options. Complete the task. Look up the case for context. Delete the task.

The middle option was useful. The first option was acceptable. The third option — delete — was a quiet mistake. Not a catastrophic one. Not the kind that produces a malpractice claim. But the kind of mistake that compounds across years of case management: a system that treats audit-trail records as disposable when they get inconvenient.

This post is about why "delete" is almost never the right verb for a law firm's case management system, what the system should have offered instead, and the code we shipped to make sure deletion is no longer something the AI can casually suggest.

What that task actually was

Pull up the case. Sample, Alex v. Sample County Agency, file number [SAMPLE-FILE-ID], opened years ago, stipulated and closed somewhere along the way. The task was created July 2018 — twelve months into the case life — by someone on the staff who saw a number on a settlement worksheet that didn't add up. Five thousand seven hundred and ten dollars. Difference between what, and what? The task description didn't say. Probably the writer knew. Probably the writer meant to come back to it. Probably the case settled and the writer moved on, and the difference was either absorbed by the C&R, eaten by the firm, or paid by the carrier without much fuss.

Eight years later, with the case stipulated and inactive, what does that task tell us?

It tells us someone, somewhere in the firm's history, flagged a $5,710 discrepancy. The note exists. It is, in the most narrow and pedantic sense, evidence — evidence that the firm at one point in time noticed an inconsistency and noted it down. If the case ever resurfaces, if a subrogation claim arrives, if the client returns, if a malpractice question is raised about whether the firm noticed payment irregularities, that task is a small piece of historical fact.

Delete the task and that historical fact goes away. Complete the task and the record stays — marked done, dated, attributable, but preserved.

The difference between those two outcomes is invisible most of the time. It becomes visible exactly when someone needs to reconstruct what happened, and finds that the record they need was thrown away because an AI assistant suggested it.

Why this is a category, not a one-off

The Sample task isn't unusual. Every active law firm carries hundreds of stale tasks, old notes, abandoned reminders, leftover items from staff who left, tasks that were renamed or replaced but never closed. They look like clutter. They are not clutter — they are the silt at the bottom of an active practice, and silt has a habit of containing the one rock you need.

A few examples of "stale tasks" that turned out to matter:

None of these tasks were urgent. None of them looked important at the time someone might have cleaned them up. But each one became, years later, a piece of the case's history.

Delete the task and the history is gone. Complete the task and the history is preserved. The distinction is small in any individual case and enormous across a practice that runs for decades.

What the system should have offered

The correct response to "this task is eight years old on a stipulated case" is not three options including delete. It is three different options:

Notice what's not on the list. There is no delete option. There is no "are you sure this is really worth keeping" prompt. There is no fast path to disposal. The system's bias is toward preservation, because the asymmetry between a wrongly-preserved task (harmless clutter) and a wrongly-deleted task (lost history) is enormous.

Why we wrote it into the code

The system used to have an instruction in its prompt about preferring completion over deletion for routine tasks. The instruction was followed most of the time. The Sample task was one of the times it wasn't.

We followed the same pattern as the misfile guards. The fix is not a stronger instruction; the fix is code that refuses.

The Merus API endpoint for deleting a task is /tasks/del/{task_id}. The helper script that issues HTTP requests now intercepts every call to that endpoint. Unless the caller has set the environment variable AAI_ALLOW_TASK_DELETE=1, the helper refuses with exit code 3 and a printed explanation:

TASK DELETE GUARD — REFUSED
─────────────────────────────────────────────────────
Refusing to delete task 3741761.

Tasks should be COMPLETED, not deleted. Completing preserves
the audit trail (what was tracked, when, by whom).

To complete a task instead, edit it with completed=1:
  merus-fetch /tasks/edit/3741761 completed=1

To override (NOT recommended — logged):
  AAI_ALLOW_TASK_DELETE=1 merus-fetch /tasks/del/3741761

The model sees this output, knows the operation did not happen, and adjusts. There is an override path for the rare cases where a task genuinely needs to be deleted — test data created by a setup script, a duplicate from a bulk import, an obvious data entry error. The override exists for the attorney to use deliberately. The model has no instruction to use it on its own.

The other half of the bug

There was a second, smaller problem in the same exchange. The system's first response to "go through my tasks" included this line:

Case: 1234567

That number is the case_file_id — an internal Merus database identifier. It is meaningless to a human reading the screen. The attorney correctly pushed back: "explain it more it doesnt say the case." On the second pass, the system fetched the case details and showed "Sample, Alex v. Sample County Agency (file #[SAMPLE-FILE-ID])" — which is what should have been on the screen from the start.

Bare ID numbers are a symptom of a system that prioritizes its own internal structure over the human reading the output. The model knew the case_file_id because it had read the task list from the API; the case name required a second API call to resolve. The model skipped the second call because it was trying to be fast across 178 tasks.

This is a tradeoff worth making explicit. There are two ways to handle ID-to-name resolution in a list-walking flow:

  1. Per-task lookup. For each task as you present it, fetch the case name. Slow per-task. Always complete.
  2. Pre-fetch once. Before the walk starts, fetch the case-name lookup for the unique set of case_file_ids in the list. One extra call up front, then every presentation uses the cached lookup. Fast per-task, always complete.

Option 2 is obviously correct. The new task-walking skill does option 2: it runs /caseFiles/index once at startup, builds a map of case_file_id → {name, file_number, status}, and uses it for every task presentation. The same map handles user_id → assignee name lookup via /users/index.

We also promoted "never show a bare case_file_id" from a guideline buried in the system prompt's general guidance section to an explicit hard rule. The new rule says: if you ever find yourself about to put a raw ID in front of the attorney, that's a bug. Fetch the name first. If the attorney asks "what case is that?" in response to your output, treat it as a bug report, not a clarification question.

The new walking flow

The skill that handles "go through my tasks one at a time" was rebuilt around these constraints. The flow now looks like:

  1. Optionally ask whose tasks to walk (the attorney's own, everyone's, or a specific person's).
  2. Fetch open tasks. Filter out auto-generated review-task noise. Sort overdue-first, with oldest overdue at the top.
  3. Pre-fetch the case-name lookup once for every unique case_file_id in the list.
  4. Pre-fetch the user-name lookup once for every unique user_id in the list.
  5. Walk the tasks one at a time. Each presentation shows: task ID, case name and file number, case status, due date, priority, assignee name, full description. Options are COMPLETE, UPDATE, SKIP. Never delete.
  6. If the attorney says "update," they can change the assignee, due date, priority, or description in plain English ("reassign to the paralegal", "change due date to next Friday", "priority high", "change description to: $5710 difference — verify with adjuster"). The proposed update is re-shown with changes highlighted, awaiting explicit approval before the write.
  7. After every write, verify by re-fetching the task and confirming the fields stuck. Append a record to the audit log.
  8. If the attorney explicitly says "delete," refuse politely: "I don't delete tasks — it drops the audit trail. Want to complete it (mark done, keep the record), or update it instead?"

The flow is intentionally slow. One hundred and seventy-eight tasks at maybe twenty seconds per task is an hour of work. That's fine. The alternative is a fast batch process that produces decisions the attorney didn't really make, or worse, decisions that erase information the firm needed.

Why we keep writing these posts

The bug here is small. Nobody was harmed. The system offered an option it shouldn't have, the attorney noticed, and the option got removed. The fix is a few hundred lines of new code and a new skill file.

What's interesting is the pattern. Every week or so, we find another place where the AI assistant did something that was technically allowed and culturally wrong. Each time, the fix is the same shape: code that refuses, not a prompt that requests.

Law firm software is built on quiet assumptions about how records work. The default behavior of most systems is to preserve everything by default and only delete on explicit, confirmed, audited operator action. AI assistants didn't grow up in that culture. They grew up in a culture where files are routinely deleted, drafts are routinely discarded, intermediate work is routinely thrown away. When an AI assistant operates inside a law firm, those instincts need to be contradicted — not just suggested against, but structurally blocked.

The way you tell a system "preserve by default" is not to write "please preserve by default" in its instructions. The way you tell a system "preserve by default" is to remove the delete option from its interface and make the override path require deliberate effort.

That is what we shipped in version 4.5.12. The task delete guard refuses by default. The new task-walking skill never offers delete. The system prompt has explicit hard rules: never offer delete for tasks, never show a bare case_file_id. The model still has the same capabilities it had before — it can edit tasks, complete tasks, reassign tasks, update descriptions. What it can no longer do is suggest, casually, that you throw away an eight-year-old note about a $5,710 discrepancy on a case that might someday matter again.

What still needs to happen

This is a specific fix to a specific class of bug. There are others. We have not yet built a guard for activity deletion (soft delete is fine, but we should still surface it as a deliberate decision). We have not yet built a guard for case status changes that close out cases without a documented reason. We have not yet built the audit log viewer that would let an attorney scan all writes the AI made in a given day.

Each of those is its own piece of work. Each will follow the same pattern: identify the operation that should be slowed down, encode the slowdown as a guard in the API helper, remove the option from the model's interface, write a hard rule into the system prompt as documentation, and ship.

The work is gradual and unglamorous. It also seems to be the only way to build an AI assistant that operates safely inside the kind of long-memory, audit-heavy system that a law firm actually is.