# Same-session goals English | [中文](goal.zh.md) Types shared by the event-sourced goal service and its policy consumers. The [goal-domain Agent Note](../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md) owns the persistence and activation decisions; this page records the exact fields and variants from [`packages/goal/goal/src/types.ts`](../../packages/goal/goal/src/types.ts). ## Identity and lifecycle `GoalId` is a [branded id](core.md#branded-ids). A caller mutates one exact revision through `GoalRef`; every accepted durable mutation increments the revision. ```ts type-equiv /** Compare-and-set identity for one exact goal revision. */ interface GoalRef { /** Stable goal identity. */ readonly id: GoalId /** Positive revision; every durable mutation increments it. */ readonly revision: number } ``` The durable phase answers what happened to the objective. Process-local activation separately answers whether a continuation consumer may start another round. ```ts type-equiv /** Durable continuation phase. Activation is process-local and separate. */ type GoalPhase = | 'active' | 'paused' | 'blocked' | 'complete' ``` Blocking is the single durable stopped-by-a-problem state. Its policy-owned reason carries a stable lower-kebab-case code for routing and a free-form explanation for humans and models. ```ts type-equiv /** Machine-routable and human-readable explanation for a blocked goal. */ interface GoalBlockReason { /** Stable lower-kebab-case classification chosen by the blocking policy. */ readonly code: string /** Non-empty explanation shown to humans and models. */ readonly message: string } ``` ```ts type-equiv /** Full durable state written by every non-clear goal mutation. */ interface GoalSnapshot extends GoalRef { /** Human-requested completion objective. */ readonly objective: string /** Durable lifecycle phase. */ readonly phase: GoalPhase /** Present exactly while `phase` is `blocked`. */ readonly blockedReason?: GoalBlockReason /** Total admitted goal-round cap. */ readonly maxGoalRounds: number } ``` ```ts type-equiv /** Current goal projection, including values derived from the session log. */ interface GoalView extends GoalSnapshot { /** Highest admitted round number for this goal. */ readonly roundsStarted: number /** Epoch milliseconds of the create mutation. */ readonly createdAt: number /** Epoch milliseconds of the latest mutation. */ readonly updatedAt: number /** Process-local continuation eligibility; never persisted. */ readonly activation: GoalActivation } ``` ## Durable changes Every mutation is a durable `goal/change` session event whose payload is either a complete post-mutation snapshot or a clear tombstone. The strict fold and persisted projection derive lifecycle state only from these events; inbox mutations do not affect goal state. ```ts type-equiv /** Full-snapshot goal mutation committed by a durable `goal/change` event. */ interface GoalSnapshotChangeMeta { readonly kind: 'goal/change' readonly version: 1 readonly operation: Exclude readonly goal: GoalSnapshot readonly roundsStarted: number readonly createdAt: number readonly updatedAt: number } ``` ```ts type-equiv /** Tombstone retained when the current goal is cleared. */ interface GoalClearChangeMeta { readonly kind: 'goal/change' readonly version: 1 readonly operation: 'clear' readonly cleared: GoalRef readonly clearedAt: number } ``` A continuation consumer attributes each admitted user-message turn with a positive, sequential round number and the current revision; only these admitted `user/message` events advance `roundsStarted`. Replay rejects non-positive rounds, gaps, stale revisions, stopped phases, and cap overflow. ```ts type-equiv /** Message attribution for admitted continuation rounds. */ interface GoalMessageSource { readonly kind: 'goal' readonly goalId: GoalId readonly revision: number /** Positive admitted continuation round. */ readonly round: number } ``` ## Requests and notifications Creation separates caller omission from the deployment choice, which `create()` resolves internally. An edit is a partial replacement whose runtime validator requires at least one field. Every mutation notification carries the accepted operation and exact revision; clear omits `goal`. ```ts type-equiv /** Input whose omitted round cap is resolved by the service configuration. */ interface CreateGoalRequest { readonly objective: string readonly maxGoalRounds?: number } ``` ```ts type-equiv /** Fields changed by an edit; at least one must be present. */ interface EditGoalRequest { readonly objective?: string readonly maxGoalRounds?: number } ``` ```ts type-equiv /** Live notification after one durable goal mutation commits. */ interface GoalChanged { readonly operation: GoalOperation readonly ref: GoalRef /** Absent for a clear tombstone. */ readonly goal?: GoalView } ``` ## Service behavior [`GoalService`](../../packages/goal/goal/src/index.ts) resolves creation defaults, folds strict replay from durable `goal/change` events, enforces exact-live-agent identity and compare-and-set mutations, and emits contained `goal/changed` notifications. The package [README](../../packages/goal/goal/README.md) defines the callable API and model-visible contract. ## Cordis API Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). ### `ctx.goals` — `GoalService` Goal service (`ctx.goals`) backed exclusively by the owning session log. ```ts cordis-catalog /** * Read the current goal for one exact live agent. * @param agent - owning live agent. * @returns a fresh view or `undefined` when no goal is current. * @throws {@link GoalError} when the agent is not the registry's live instance. */ get(agent: Agent): GoalView | undefined /** * Remove process-local continuation authority without changing durable goal * phase or revision. Lifecycle owners use this before unloading a driver; * a later human-authorized {@link resume} records the new activation edge. * @param agent - owning live agent. * @returns a fresh disarmed view, or `undefined` when no goal is current. */ disarm(agent: Agent): GoalView | undefined /** * Create and arm a goal. A completed goal may be replaced; every other * current phase must be cleared or resumed instead. * @param agent - owning live agent. * @param request - objective and optional round cap. * @returns the created live view. */ create(agent: Agent, request: CreateGoalRequest): GoalView /** * Edit objective and/or round cap without changing phase. * @param agent - owning live agent. * @param ref - expected current revision. * @param request - at least one replacement field. * @returns the edited view. */ @Remote('edit') edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView /** * Pause an active goal and disarm automatic continuation. * @param agent - owning live agent. * @param ref - expected current revision. * @returns the paused view. */ @Remote('pause') pause(agent: Agent, ref: GoalRef): GoalView /** * Resume and arm a stopped goal, or rearm an active goal after a * session-start edge, while its round budget still has capacity. * @param agent - owning live agent. * @param ref - expected current revision. * @returns the active view. */ @Remote('resume') resume(agent: Agent, ref: GoalRef): GoalView /** * Mark a current non-complete goal complete and disarm it. * @param agent - owning live agent. * @param ref - expected current revision. * @returns the completed view. */ @Remote('complete') complete(agent: Agent, ref: GoalRef): GoalView /** * Mark an active goal blocked and disarm it. * @param agent - owning live agent. * @param ref - expected current revision. * @param reason - policy-owned stable code and human-readable explanation. * @returns the blocked view with its durable reason. */ block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView /** * Clear the current goal while retaining a durable tombstone and history. * @param agent - owning live agent. * @param ref - expected current revision. * @returns the tombstone ref whose revision is one past the cleared snapshot. */ @Remote('clear') clear(agent: Agent, ref: GoalRef): GoalRef /** * Create one Goal through the remote boundary. * @param agent - exact live Agent resolved from the wire identity. * @param request - objective and optional round cap. * @returns the created Goal identity. */ @Remote('create') remoteExportCreate(agent: Agent, request: CreateGoalRequest): CreateGoalResult ``` Types: [Agent](core.md) Source: [`packages/goal/goal/src/index.ts:183`](../../packages/goal/goal/src/index.ts) ### `goal/*` events #### `goal/changed` — emit Goal mutation accepted by one live agent. The matching `goal/change` session event has already committed. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. ```ts cordis-catalog /** * Goal mutation accepted by one live agent. The matching `goal/change` * session event has already committed. Listener failures are contained. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @param payload.agent - agent whose session owns the goal. * @param payload.change - fresh current projection or clear tombstone. * @mode emit */ 'goal/changed'(this: import('@deepseek-ai/dsh-scope').Scoped, payload: { agent: Agent; change: GoalChanged }): void ``` Types: [Agent](core.md) · [Scoped](scope.md) Source: [`packages/goal/goal/src/domain.ts:114`](../../packages/goal/goal/src/domain.ts)