feat(tasks): archiving a task stops and archives its agents

Merged
CH
2d ago
+661-55

Archiving a task used to stamp only the task row: its coding agents kept running, and a pull request event could still wake them. With this change, archiving a task stops every agent on it and archives them along with it. It also asks first when the task has any agents.

Service

  • Archive (PATCH /api/plans/plan, archived: true). First stops each unarchived agent the same way Stop and agent archive do (stoppedForGood). Then, holding the task lock, it archives the task and its agents together in updateTask.
    • The agents get the same timestamp as the task. Their archivedBy is { by: member, userId, withTask: true } for the member who archived the task.
    • Each agent's conversation is locked and read again (standsStopped). If an agent is still starting, has a message waiting for its turn, or got a message after it was stopped, the whole change rolls back and the request returns agent-not-ready (409). This matches how delete and single-agent archive already behave.
  • Unarchive. Brings back only the agents archived with this task, matched on withTask and the task's own timestamp. An agent that was archived on its own beforehand stays archived.
  • Steer. The send path reads the agent's and task's archive state again just before handing the message to eve, while holding the conversation lock. Since the task archive takes that lock too, a message and an archive can't cross. A message to an agent on an archived task now gets the task's plan-archived refusal instead of not-found.
  • Agents' update_task tool (added on main since): its archive takes the same guard, so it refuses with a clear reason while an agent on the task still works, rather than archiving it mid-turn.
  • Wire. archivedBy gains an optional withTask. Older desktops ignore it and show the archive as the member's own. The JSON-schema goldens were regenerated with LUKE_UPDATE_FIXTURES=1; the only diff is the new optional key.

Desktop

  • Single task. Archive asks "Archive task?" ("Archiving “” will stop and archive all its agents. Unarchiving it brings them back.") when the task has an agent that isn't archived. That covers the ⋯ menu, the hover button on the sidebar row, and ⇧⌘A. A task with no agents is archived straight away, as before.
  • Repository. "Archive tasks" asks the same question when any task in that repository has agents.
  • Banner. An agent archived with its task reads "Archived with its task by ".

Tests

  • task-store.test.ts (Postgres):
    • An archive archives the task's agents as the member who archived it.
    • An agent archived on its own keeps its own record.
    • An unarchive restores only the agents archived with the task.
    • A guard that finds an agent at work refuses the whole archive, and nothing is written.
  • coding-agents-app.test.ts:
    • Archiving a task with a running agent cancels the agent's turn on eve and archives it with the task.
    • A message to that agent is refused with plan-archived, and unarchiving the task brings the agent back.
    • An agent with a message waiting for its turn refuses the archive with agent-not-ready and leaves everything as it was.
  • task-actions.test.ts: the sidebar row's button and its menu ask first for a task with agents, and Cancel archives nothing. A task with no agents archives at once.
  • agent-archive.test.ts: the "Archived with its task by …" wording.

pnpm check:affected passed except for app-menu.test.ts, which can't load Electron in the sandbox. All 1,589 desktop tests ran and passed.

Made with Luke


Devin Review


Summary by cubic

Archiving a task now stops every coding agent on it and archives them with it, instead of leaving them running. Unarchiving the task restores only the agents archived that way, and an agent archived on its own stays archived. The desktop asks for confirmation before archiving a task, or a repository's tasks, that has agents.

Behavior

  • Every agent is checked before any Stop is sent, so an archive refused as not ready leaves every agent's work running.
  • Agents are stopped, then archived with the task under the task lock, marked as archived along with it by the member who archived it.
  • A send that races an archive reads the archive state again under the conversation lock, so a message to an agent on an archived task gets the task's plan-archived refusal.
  • An agent still starting, or with a message awaiting its turn, refuses the archive with agent-not-ready and leaves everything unchanged.
  • Re-archiving an already archived task archives any agent brought back on its own since, under the task's standing stamp.
  • The agent's archived banner reads "Archived with its task by …", and older clients show the archive as the member's own.