Files
claude-agent-acp/docs/goal-extension.md
nikita-ashihmin f8fd3ab822 fix: publish and replace Claude goals reliably (#967)
## Summary

- publish optimistic provider-neutral goal snapshots for submitted and
steered goal commands
- advertise and implement `set`, using steering for an active turn and
an ordinary prompt while idle
- suppress stale old-goal runtime updates while replacement/clear is
pending and roll back failed commands
- keep cancellation turn-scoped and orphan-account pending steers so
stale results cannot settle the next prompt
- reject unsupported pause/resume controls without host-side fallbacks

## Validation

- `npm test -- --run` (702 passed, 20 skipped)
- `npm run check`
- `npm run build`
- regression coverage for idle/active set and clear, stale runtime
updates, command failure rollback, cancellation, and stale steer
isolation
2026-08-07 17:01:18 +04:00

3.7 KiB

Goal extension

This document defines a provider-neutral experimental ACP extension implemented by claude-agent-acp. It is intentionally shaped like a possible future first-class ACP API: implementations publish _meta.goal, not provider-specific metadata such as _meta.claudeCode.goal.

Capability negotiation

An agent advertises support in its initialize response:

{
  "_meta": {
    "goal": {
      "version": 1,
      "controlMethod": "_session/goal",
      "actions": ["clear"]
    }
  }
}

actions is the implementation-supported subset of set, pause, resume, and clear. Clients must not infer support for an action that is not advertised. The control request contains sessionId and action; a future version may add action-specific fields such as an objective for set.

Session state

The current snapshot is published in session_info_update._meta.goal. Clearing a goal publishes goal: null.

{
  "objective": "Ship the change",
  "status": "active",
  "iterations": 3,
  "lastReason": "Tests still need work",
  "createdAt": 1710000000123,
  "controlMethod": "_session/goal"
}

Common statuses are active, paused, blocked, limited, and complete. Optional fields allow implementations to report budgets, usage, iteration count, and the last continuation reason. Timestamps are Unix milliseconds.

Lifecycle architecture

A goal belongs to the ACP session, not to an individual session/prompt request. Goal activity and prompt activity are independent:

  • status: active means the persistent objective can drive more work; it does not mean an ACP prompt is currently executing.
  • A prompt completes when its current backend turn reaches a quiescent boundary, even when the goal remains active.
  • A later autonomous cycle may publish more session updates outside that completed prompt.
  • While a turn is running, clients use steering or prompt queueing when advertised. While the session is quiescent, clients may send an ordinary session/prompt.

This separation prevents a persistent goal from monopolizing the session's prompt slot and lets clients model “working now” independently from “objective remains active.”

Claude mapping

Claude's session-scoped /goal command installs a Stop hook. The runtime emits internal active_goal messages when that hook updates or clears its state; the adapter maps them into the neutral snapshot before the exhaustive SDK message switch. condition, iterations, set_at, and last_reason become objective, iterations, createdAt, and lastReason. Provider-only bookkeeping such as tokens_at_start is not exposed.

Some Claude runtime versions persist the goal and begin the Stop-hook workflow without emitting the initial active_goal message on the SDK stream. To avoid leaving clients without visible state for the whole long-running prompt, the adapter publishes a minimal active snapshot as soon as it submits /goal <condition>, and publishes goal: null for /goal clear. While that command is pending, late updates for the previous goal are ignored. A matching runtime active_goal remains authoritative and enriches the optimistic snapshot with iteration, reason, and timestamp fields; a failed command restores the previously published state.

set and clear are advertised for out-of-band control; pause and resume are not. When a goal turn is running, the adapter injects the corresponding /goal command at steering priority now so it cannot remain queued behind the Stop-hook workflow; on an idle session it uses the ordinary prompt lifecycle. Cancelling a turn does not clear the session-scoped goal: clients use the goal control method when they intend to replace or remove it.