What a goal is
A goal is one durable completion objective attached to an existing session. It is state, not a scheduler — the session log is its source of truth, and a separate goal-round driver decides when an armed goal actually continues. The goal family lives under packages/goal/ and splits cleanly into four packages:
| Package | Role | ctx key |
|---|---|---|
@deepseek-ai/dsh-goal | Goal state, lifecycle, and replay fold | ctx.goals |
@deepseek-ai/dsh-goal-round-driver | Same-session continuation driver | — |
@deepseek-ai/dsh-tool-goal | Model-facing get_goal/create_goal/update_goal tools | registers on ctx.tools |
@deepseek-ai/dsh-command-goal | Human-facing /goal command | registers on ctx.commands |
Consumers depend on @deepseek-ai/dsh-goal, never on the concrete agent loop. Its subsystem reference is docs/subsystems/goal.md.
The domain model
The host vocabulary lives in packages/goal/goal/src/domain.ts (host-side) and its types in src/types.ts. Key types:
GoalId — branded id of one goal across its durable revisions
GoalRef — { id, revision } compare-and-set fence
GoalPhase — 'active' | 'paused' | 'blocked' | 'complete'
GoalBlockReason — { code: string, message: string } (present exactly while blocked)
GoalSnapshot — objective, phase, blockedReason?, maxGoalRounds (GoalRef + these)
GoalView — GoalSnapshot + { roundsStarted, createdAt, updatedAt, activation }
GoalActivation — 'armed' | 'disarmed' (process-local, never persisted)
GoalOperation — 'create' | 'edit' | 'pause' | 'resume' | 'complete' | 'block' | 'clear'GoalPhase is the durable phase; GoalActivation is deliberately separate and process-local. roundsStarted is the highest admitted continuation round; maxGoalRounds is the total cap. A blockedReason.code is a stable lower-kebab-case classification chosen by the blocking policy (the model tool always uses model-reported), blockedReason.message is the human/model explanation.
Lifecycle transitions and authority
ctx.goals accepts only the exact live Agent instance registered under its id. Mutations use a GoalRef { id, revision } compare-and-set fence and reject stale refs. Every mutation appends a durable goal/change session event carrying the complete post-mutation snapshot (or a revisioned clear tombstone), so goal state never depends on inbox placement, claim, admission, or discard.
Transitions and who may perform them:
| Operation | Effect | Who |
|---|---|---|
create | Active revision-one goal, armed | model tool / /goal |
edit | Retain phase, blocker reason, activation; edit a completed goal creates a fresh active goal | model tool / /goal |
pause | Paused + disarmed | model tool / /goal |
resume | Stopped phase or disarmed active → active + armed, clears blocker, only while cap has capacity | model tool / human-authorized /goal |
complete | Completed + disarmed | model tool (round-authorized) / human |
block | Blocked + disarmed, records code + explanation | model tool (round-authorized) / /goal |
clear | Tombstone; history retained | /goal |
A block uses one durable phase rather than multiplying lifecycle states. Resume is the only re-arm path: after session resume, fork, or driver replacement, an active goal is automatically disarmed, and only an explicit human-authorized resume (through /goal or the model tool) re-arms continuation. disarm() itself is the lifecycle-only exception — it removes process-local continuation authority without writing a revision or emitting a mutation.
Activation: armed vs disarmed
Activation is never persisted. A fresh cache and every agent/session-start edge disarm it even when replay finds an active durable phase; the continuation driver also calls disarm() before unload or after durability uncertainty. This is why activation is absent from durable replay: the durable phase says "this objective is open," but only a live, armed process may admit the next round.
Goal rounds
A goal round is one continuation cycle admitted for the current goal. The driver materializes it as one goal-sourced turn, which can contain zero or more steps. The glossary makes two essential clarifications:
- only an admitted, goal-sourced
user/messageincrementsroundsStarted; - unrelated human turns in the same session do not consume the goal-round cap.
The goal-round driver
@deepseek-ai/dsh-goal-round-driver is the same-session continuation driver with no tunable configuration — maxGoalRounds belongs to the goal definition, and the model-facing blocked threshold belongs to dsh-tool-goal, so the driver duplicates neither.
Round contract (from packages/goal/goal-round-driver/src/index.ts):
- when an exact live agent is idle with an active, armed goal and remaining capacity, it checkpoints pending goal mutations (awaits
ctx.sessions.flush()and rechecks revision + competing input after the await); - reserves
roundsStarted + 1for the current{ goalId, revision }(a stale reservation does not consume the round number); - queues one
<goal_round>prompt withGoalMessageSource { kind: 'goal', goalId, revision, round }; - the
agent/pre-steplistener verifies the claimed record and current goal before and after downstream listeners; only an entereduser/messageincrementsroundsStarted.
The retained prompt names the JSON-quoted objective and round/maxGoalRounds, treats current workspace, tool results, and durable session state as authoritative, and tells the model to leave the goal active when work remains. A flush failure arriving through agent/error disarms continuation before another round can start. If human work enters the inbox before a reservation, automatic work yields until the agent is idle. At the next idle checkpoint, a goal with a reserved or admitted attempt whose work was cancelled gets paused so cancellation cannot auto-restart it.
The model-facing tools: dsh-tool-goal
@deepseek-ai/dsh-tool-goal registers get_goal, create_goal, and update_goal:
get_goal()→ the current goal ornull, including id/revision, durable phase, admitted/capped rounds, blocker reason, and live activation.create_goal(objective, max_goal_rounds?)— creates one goal from a direct top-level human turn; non-human turns and subagents are rejected at execution.update_goal(goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?)—edit/pause/resume/complete/blocked;blocked_reasonrequired only forblocked, persisted with the stable codemodel-reported.
Authority. Execution requires the exact live exec.agent, and create/edit/pause/resume additionally require an accepted { kind: 'user' } message or steering event in a runtime-root agent's current turn. { kind: 'user' } is a host attestation: Agent.followup() and steer() assign it when the caller omits a source, so plugins/schedulers/non-human producers must pass their own source rather than inheriting human authority. Complete and blocked also accept the exact current goal round (id + revision + round equal the folded goal), but a goal-round blocked call is mechanically rejected until blockedAfterConsecutiveRounds (default 3).
An autonomous round that successfully reports complete or blocked calls concludeTurn(), stopping the physical turn after that step; direct-human mutations never do. Config: blockedAfterConsecutiveRounds (positive safe integer). The tools render a fixed goal policy into the system prompt.
The /goal human command
@deepseek-ai/dsh-command-goal registers one global command on ctx.commands:
| Input | Result |
|---|---|
/goal | Show objective, phase, round count/cap, activation, valid next commands |
/goal <objective> | Create and arm a goal / replace a completed goal (fresh id) |
/goal edit <objective> | Edit objective without changing phase/activation |
/goal pause | Pause + disarm |
/goal resume | Resume / re-arm subject to remaining cap |
/goal clear | Clear pointer, retaining durable history + tombstone |
Control words are case-insensitive only as the complete input; anything else is an objective. Because the command plane has no modal editor, edit takes its replacement inline.
Where goals persist
The session log is the only durable authority. Every mutation appends goal/change; a clear appends a revisioned tombstone. Strict replay derives lifecycle state only from these events, rejecting malformed shapes, discontinuous revisions, illegal transitions, non-monotonic timestamps, and non-sequential admitted rounds. goal/changed (scoped emit) fires after the durable event commits. @deepseek-ai/dsh-goal/checkpoint-style helpers are not involved — the checkpoint marker used by session references is a different concept (dsh-compaction).
Replay, the scoped event, and error codes
Strict replay recomputes FoldedGoal — the goal plus roundsStarted, createdAt, updatedAt, and the latest GoalRef — purely from goal/change events. It rejects malformed shapes, discontinuous revisions, illegal lifecycle transitions, non-monotonic timestamps, and non-sequential admitted rounds; incremental replay keeps its cursor at the first corrupt event so a bad record stops the fold without corrupting memory. The flow:
durable goal/change events ──► strict replay fold
│ ─ reject malformed / illegal / non-monotonic
▼
FoldedGoal ──► live GoalView (adds activation, roundsStarted counters)
│
▼
mutation request ──► GoalRef { id, revision } CAS fence
└ stale ref rejected (GOAL_STALE_REVISION)
│
▼
commit goal/change ──► goal/changed (scoped emit)The service exposes stable GoalErrorCode reasons: GOAL_AGENT_NOT_LIVE, GOAL_NOT_FOUND, GOAL_ALREADY_EXISTS, GOAL_STALE_REVISION, GOAL_INVALID_OBJECTIVE, GOAL_INVALID_MAX_ROUNDS, GOAL_INVALID_BLOCK_REASON, GOAL_INVALID_EDIT, GOAL_INVALID_TRANSITION. disarm() is the one operation that mutates process-local authority only: it removes continuation permission without writing a goal/change or advancing the revision, so it never appears in replay.
Recommended composition
The shipped dsh base enables the persisted-goal stack + /goal; the ACP automation app enables the domain + model tools without a command adapter; the UI-less agent-spine-demo requires explicit goals: {} so headless one-shot callers do not silently switch from one physical turn to a multi-round operation.
- id: goal
name: '@deepseek-ai/dsh-goal'
- id: tool-goal
name: '@deepseek-ai/dsh-tool-goal'
- id: goal-round-driver
name: '@deepseek-ai/dsh-goal-round-driver'Further reading
- Context Sources — the durable messages among which goal-round prompts sit.
- Compaction — how accumulated goal-round prompts eventually get shadowed.
- Interactions — the command plane and tool authority that
/goaland the goal tools rely on. packages/goal/goal/src/domain.tsandsrc/types.ts— the exact domain types andgoal/changepayload.docs/glossary.md— the goal, goal round, and goal activation definitions..agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md— the goal-domain design decision record.