Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,38 @@ time-bounded.
specially. Adapters map provider-native names to these values; consumers never
infer behavior from a human-facing tool title.

## Goal control

A goal is durable session state, not a running prompt. Its two halves travel on
different transports because they need different things from ACP v1:

| Transport | Actions | Property |
| ------------------------------- | -------------------------- | ------------------------------------------- |
| `_lody/session/goal` request | status-only actions | Never starts a turn; works mid-prompt |
| `prompt._meta.lody.goalControl` | any advertised action | Runs inside the prompt the client owns |

The request exists for `pause` and `clear`: an active goal keeps a prompt open
across the agent's own continuations, so a client that could only speak through
prompts would have no way to reach a goal it wants to stop. Agents must accept
these mid-prompt and must not start a turn for them.

`set` and `resume` start work, and ACP v1 gives a client exactly one way to own
running work — its own prompt. The client sends a prompt carrying
`_meta.lody.goalControl` instead of user-visible command text; the agent applies
the action, adopts any turn the action started natively, and keeps that prompt
open for the goal's remaining turns. Status-only actions may travel this way
too, which is what lets a client reach a goal whose session is not running.

An agent may also accept work-starting actions on the request for clients that
cannot carry prompt metadata, but then the agent owns starting the work and the
client sees turns it never prompted. Clients that must attribute every turn to a
conversation entry use `promptActions` for exactly this reason.

`LodyGoalCapability` advertises `actions` (everything implemented),
`controlActions` (accepted on the request while a prompt is in flight), and
`promptActions` (accepted through prompt metadata). Clients must not infer an
action's transport from `actions` alone.

## Logical local project identity

An agent advertising `worktreeProject: { version: 1 }` accepts
Expand Down
13 changes: 13 additions & 0 deletions src/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,20 @@ export type LodySteeringCapability = LodyVersionOneCapability & {
export type LodyGoalAction = 'set' | 'pause' | 'resume' | 'clear';

export type LodyGoalCapability = LodyVersionOneCapability & {
/** Every action the agent implements, on any transport. */
actions: readonly LodyGoalAction[];
/**
* Actions the agent accepts on `_lody/session/goal` while a prompt is in
* flight. These only move durable goal state and never start a turn, so a
* client can send them without owning the session's prompt slot.
*/
controlActions?: readonly LodyGoalAction[];
/**
* Actions the agent accepts through `prompt._meta.lody.goalControl`. Actions
* that start work appear only here: running them inside the client's own
* prompt is what keeps the resulting turns attributable to a conversation.
*/
promptActions?: readonly LodyGoalAction[];
};

export type LodySubagentCapability = LodyVersionOneCapability & {
Expand Down
20 changes: 20 additions & 0 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ export type LodyGoalControlResponse = {
goal: LodyGoalSnapshot | null;
};

/**
* Client→agent prompt metadata that makes a prompt carry a goal action instead
* of user-visible command text.
*
* This is the general channel: any advertised action can travel here, and an
* action that starts work (`set`, `resume`) MUST, because ACP v1 gives a client
* exactly one way to own running work — its own prompt. A prompt carrying this
* metadata applies the action first and then stays open for the goal's turns,
* so the agent never has to start a turn nobody asked for.
*
* The prompt's content blocks are a fallback the agent may send when the action
* started no native turn; agents that need no fallback ignore them.
*/
export type LodyGoalPromptControl =
| { version: 1; action: 'set'; objective: string }
| { version: 1; action: 'pause' | 'resume' | 'clear' };

export type LodySteerPromptMeta = {
id: string;
};
Expand Down Expand Up @@ -149,7 +166,10 @@ export type LodySessionMeta = {
toolName?: string;
activity?: LodyActivityMeta;
task?: LodyTaskMeta;
/** Agent→client goal snapshot published on session updates. */
goal?: LodyGoalSnapshot | null;
/** Client→agent goal action carried by `session/prompt`. */
goalControl?: LodyGoalPromptControl;
notice?: LodyNotice;
titleSource?: 'explicit' | 'generated' | 'fallback' | 'unset';
messagePhase?: 'commentary' | 'final_answer';
Expand Down