Tasks & plans
The dependency-aware ready queue, lease-based claiming that lets parallel agents divide work, and plans as compositions rather than primitives.
Seamless gives coding agents a shared task queue that is dependency-aware and
claimed under leases: a task becomes ready when its depends_on blockers
finish, tasks_claim takes it atomically so two parallel agents never work the
same task, and a crashed agent's lease expires instead of stranding the work.
A plan is deliberately not a primitive - it is a composition of a narrative
note and step tasks keyed by plan:<slug>.
Tasks are how agents hand work to each other. Plans are how a design survives the agent that wrote it.
The ready queue
A task is ready when it has no unfinished blocker. tasks_ready returns
exactly those, so an agent asking "what can I do now?" never reasons about the
dependency graph itself - it asks, and gets an actionable list.
depends_on names the tasks that must finish first. Either done or dropped
unblocks a dependent: work that was abandoned deliberately should not wedge the
queue forever. Cycles are rejected at creation, not discovered at scheduling
time.
Claiming: the part that makes parallelism safe
Two agents that both call tasks_ready see the same task. Both will try to take
it. Exactly one wins:
ready → in_progresslease expires at now + 900s
Four rules carry the whole model:
- Claiming is atomic. The loser gets an error naming the holder, not a corrupted second claim.
- Re-claiming refreshes the lease. That is the heartbeat for long work - an agent still working keeps its claim by claiming again.
- An expired lease is reclaimable - but it does not re-queue itself. See the two clocks below. This is the subtlety most likely to bite you.
- Closing frees it.
tasks_release,tasks_updatetodone/dropped, orsession_end(which releases all of a session's claims).
(Lease-based task claiming defines the primitive on its own; the two-agents scenario shows a live claim collision.)
The two clocks
When an agent dies holding a task, two different timers matter, and conflating them is how a fleet quietly stalls.
| Lease expiry | Session reaper | |
|---|---|---|
| Controls | lease_seconds (default 900) |
gardener.session_idle_minutes (default 45) |
| Enforced | Lazily, inside tasks_claim |
By the gardener's periodic pass |
| Effect | The task becomes stealable by id | The task's status returns to open |
Visible in tasks_ready? |
No | Yes |
tasks_ready returns tasks whose status is open. A task with an expired lease
is still in_progress, so it is invisible to the queue - an agent polling
tasks_ready will never see it, even though a tasks_claim on that specific id
would now succeed.
What actually puts it back in the queue is the reaper: it expires the dead
agent's idle session and releases its claims, flipping the task back to open.
Two consequences worth internalizing:
- The lease is not the recovery mechanism; the reaper is. The lease only decides whether a claim can be taken.
- With
gardener.enabled: false, that second clock never runs, and a crashed agent's claims stayin_progressindefinitely. Nothing will surface them. If you turn the gardener off, you have also turned off task recovery - release them yourself withtasks_release, or from the console.
A lease is not a lock on the files. Nothing physically stops an agent from working on a task it did not claim. It is a coordination signal between cooperating agents - the point is that well-behaved agents do not collide, not that misbehaving ones cannot.
Plans are a composition, not a primitive
There is no plan object in Seamless. A plan is everything keyed by
plan:<slug>:
| Piece | How | Why |
|---|---|---|
| Narrative | A note tagged plan:<slug> |
The design and the reasoning behind it |
| Supporting context | More notes, same tag | The research that informed it |
| Steps | Tasks created with plan=<slug> |
The work, with dependencies |
| Progress | The steps' statuses | Rolled into the briefing automatically |
This composition is the reason a plan survives its author. The next agent inherits not just a checklist but why the checklist looks like that - which is the thing that usually evaporates between sessions.
Plan steps are excluded from the default queue. tasks_ready and
tasks_list skip them, so a twelve-step plan does not bury the handful of tasks
that are genuinely loose work. Pass plan=<slug> to either tool to see that
plan's steps instead.
The briefing surfaces each active plan as one rolled-up line:
PLAN: marketing -- 2/3 done, 1 claimable, 0 in flight
That is enough for an agent to decide whether to pick something up, without spending briefing budget listing every step.
Claude Code plan mode feeds this automatically
You do not have to build the composition by hand. Claude Code's plan mode is captured:
- Saving a plan file upserts a
cc-plan-<basename>note, tagged with its status (plan-status:draft|presented|approved|abandoned). - Planning subagents are cached as
cc-agent-<id>notes in the same composition. - Approving the plan creates the tracking task.
- Unapproved captures appear in the briefing as
PLAN (awaiting approval)lines, so a plan that was designed and then forgotten is visible rather than lost.
See the tasks reference for the tool surface.