The current-task pointer
On this page
The current-task pointer is a single task slug cairn holds for your session and resolves against whenever a tool omits an explicit task. You call observe with a line of text and no slug, and cairn knows where to file it; you call handoff with a summary and no slug, and it scaffolds under the right task. Nothing in those calls named a task, yet every write landed on one — the pointer supplied the target. It is the difference between an agent that has to repeat its own slug on every call and one that can plan, observe, and hand off in a flow because cairn already knows what it is working on.
What the pointer is
The current-task pointer is one task slug, stored per session, that names the task in focus right now. It is not a property of the task graph — the graph does not have a "current" node. The pointer lives next to the session, alongside the open database handle, and it is what makes a slugless tool call meaningful. Without a pointer, "this task" has no referent and a current-scoped write fails rather than guessing.
Two sessions never share a pointer. The pointer is keyed in the active protocol's own storage, so a serving cairn over MCP keeps a separate current task for each connected client. One agent forking into a new phase does not move another agent's focus. Concurrency is real, and each session carries its own answer to "what am I on."
How a tool finds its task
The write tools that target a task — observe, task_link, task_sever, task_set_metadata, task_update_status, handoff — resolve their target the same way. The tool takes the task_id argument when you pass one; otherwise it falls back to the current pointer; and if neither names a task, it fails with:
No current task; create or select one first.
That fallback is the whole point. In the common case you set the pointer once and then write against it without repeating the slug. When you need to touch a different task without leaving the one you are on, you pass task_id — and that is the second half of the rule, the part that surprises people.
Acting on a task does not make it current
A task_id argument scopes one call. It does not move the pointer. You can drop an observation on a sibling task, read another task's state, or flip a blocker to completed, all while the pointer stays exactly where it was.
observe(text: "the parser handles the empty case", task_id: "2026-06-22-parser-edge-cases")
→ Observed on 2026-06-22-parser-edge-cases.
After that call, the current task is still whatever it was before. The observation went where you aimed it; your focus did not follow. This separation lets an agent record a quick note on a related task and return to its own work without an explicit switch-back step. The slug you passed is a one-shot address, not a new home.
Two surfaces deliberately break this rule, because their job is to move you.
What moves the pointer
Three operations set the current task. Each is explicit about it.
Creating the first task adopts it. task_create mints a date-prefixed slug and records the task. If — and only if — no task is current, it adopts the new one as current, so a fresh session that creates a task is immediately working on it. Create a second task and the pointer does not move; you are still on the first until you say otherwise. This is the gentle case: adoption fills an empty pointer, never overwrites a full one.
task_create(name: "rework the search backend")
→ Created 2026-06-22-rework-the-search-backend.
Forking always switches. task_fork spawns a child task and unconditionally makes the child current. Forking is how you descend into a phase of a plan, and the assumption is that you want to work in the phase you just opened. The parent comes from the from argument — a parent slug — and defaults to the current task when you omit it. Pass neither a from nor have a current task, and the fork fails:
No parent task; pass from or select a task first.
So task_fork reads the pointer (as the default parent) and then overwrites it (with the new child) in a single call. After a fork you are one level deeper, on the phase, with the parent recorded as the edge source.
task_fork(name: "migrate the index schema", from: "2026-06-22-rework-the-search-backend")
→ Forked 2026-06-22-migrate-the-index-schema from 2026-06-22-rework-the-search-backend (phase-of).
Bootstrapping orients and may switch. task_bootstrap is the set-current surface for resumption — the one call you make to re-enter work. Its pointer behavior splits on whether you name a task:
- With an explicit
task_id, it switches the pointer to that task. Orienting on a task makes it current, so any session you spawn and the per-turn context cairn injects agree on which task is in focus. - With no
task_id, it orients on the current task and leaves the pointer untouched; if no task is current, there is nothing to orient on and the call fails.
Either way, task_bootstrap records no event and switches the pointer only after it confirms the task exists. If you name a task that is not there, the pointer does not move; you get a not-found result and a list of recent slugs to aim at instead. With nothing to orient on at all:
No task to bootstrap; pass task_id or select a task first.
| Operation | Effect on the pointer |
|---|---|
task_create | Adopts the new task only when none is current |
task_fork | Always switches to the child |
task_bootstrap with task_id | Switches to the named task (after it is found) |
task_bootstrap without task_id | Orients on the current task; never moves the pointer (fails if none is current) |
Any tool's task_id argument | None — scopes one call, leaves the pointer alone |
Why the pointer feeds every turn
The pointer does more than route writes. Each model turn, cairn reads the current pointer live, renders that task's computed state and its open handoffs, and splices the block in as ephemeral context — present for that turn, never written to the durable log. The agent sees what it is working on without being told, and the block always reflects live state because it is rebuilt from the pointer and the store every turn rather than cached.
This is the recursive payoff of a per-session pointer. Move it with task_fork and the very next turn's injected context is the new phase. Resume a task with task_bootstrap and the context block follows your focus. The pointer is the one piece of session state that decides both where writes land and what the model is reminded of. That is why moving it is always explicit, never a side effect of touching another task.
When the pointer is more than you need
A single-task, single-session run barely exercises the pointer: you create one task, it is adopted, and every slugless call lands on it for the life of the session. The rules above earn their keep when work fans out — multiple phases, multiple sessions, a task_id aimed sideways while focus stays put. For one linear thread of observations, walk through your first session and let adoption do the work; the switch-versus-scope distinction can wait until you fork.
Related
- Plans, phases, and the frontier — how
task_forkdescends into the phase the pointer then follows - Tasks, observations, and handoffs — the writes that resolve against the pointer
- The cairn-method — where bootstrap sits in the orient, record, hand off loop
- Events, projection, and reconcile — the per-turn context block the pointer drives
- Tools reference — the exact signatures of
task_create,task_fork, andtask_bootstrap