Add goal app-server API (2 / 5) (#18074)

Adds the app-server v2 goal API on top of the persisted goal state from
PR 1.

## Why

Clients need a stable app-server surface for reading and controlling
materialized thread goals before the model tools and TUI can use them.
Goal changes also need to be observable by app-server clients, including
clients that resume an existing thread.

## What changed

- Added v2 `thread/goal/get`, `thread/goal/set`, and `thread/goal/clear`
RPCs for materialized threads.
- Added `thread/goal/updated` and `thread/goal/cleared` notifications so
clients can keep local goal state in sync.
- Added resume/snapshot wiring so reconnecting clients see the current
goal state for a thread.
- Added app-server handlers that reconcile persisted rollout state
before direct goal mutations.
- Updated the app-server README plus generated JSON and TypeScript
schema fixtures for the new API surface.

## Verification

- Added app-server v2 coverage for goal get/set/clear behavior,
notification emission, resume snapshots, and non-local thread-store
interactions.
This commit is contained in:
Eric Traut
2026-04-24 20:53:41 -07:00
committed by GitHub
parent 0ee737cea6
commit 6c874f9b34
29 changed files with 1973 additions and 5 deletions

View File

@@ -152,6 +152,11 @@ Example with notification opt-out:
- `thread/metadata/update` — patch stored thread metadata in sqlite; currently supports updating persisted `gitInfo` fields and returns the refreshed `thread`.
- `thread/memoryMode/set` — experimental; set a threads persisted memory eligibility to `"enabled"` or `"disabled"` for either a loaded thread or a stored rollout; returns `{}` on success.
- `memory/reset` — experimental; clear the current `CODEX_HOME/memories` directory and reset persisted memory stage data in sqlite while preserving existing thread memory modes; returns `{}` on success.
- `thread/goal/set` — create, replace, or update the single persisted goal for a materialized thread; returns the current goal and emits `thread/goal/updated`. Supplying a new `objective` replaces the goal and resets usage accounting. Supplying the current non-terminal objective or omitting `objective` updates the existing goals status and/or token budget while preserving usage.
- `thread/goal/get` — fetch the current persisted goal for a materialized thread; returns `goal: null` when no goal exists.
- `thread/goal/clear` — clear the current persisted goal for a materialized thread; returns whether a goal was removed and emits `thread/goal/cleared` when state changes.
- `thread/goal/updated` — notification emitted whenever a thread goal changes; includes the full current goal.
- `thread/goal/cleared` — notification emitted whenever a thread goal is removed.
- `thread/status/changed` — notification emitted when a loaded threads status changes (`threadId` + new `status`).
- `thread/archive` — move a threads rollout file into the archived directory and attempt to move any spawned descendant thread rollout files; returns `{}` on success and emits `thread/archived` for each archived thread.
- `thread/unsubscribe` — unsubscribe this connection from thread turn/item events. If this was the last subscriber, the server keeps the thread loaded and unloads it only after it has had no subscribers and no thread activity for 30 minutes, then emits `thread/closed`.
@@ -470,6 +475,70 @@ Experimental: use `memory/reset` to clear local memory artifacts and sqlite-back
{ "id": 27, "result": {} }
```
### Example: Set and update a thread goal
Use `thread/goal/set` with an `objective` to create or replace the current goal for a materialized thread. Supplying a new objective resets `tokensUsed`, `timeUsedSeconds`, and `createdAt`. Supplying the current non-terminal objective, or omitting `objective`, updates the existing goals status or token budget while preserving usage history. Clients can set `budgetLimited` when they stop because a token budget is exhausted or nearly exhausted; the system also sets it when accounting crosses a configured token budget.
```json
{ "method": "thread/goal/set", "id": 27, "params": {
"threadId": "thr_123",
"objective": "Keep improving the benchmark until p95 latency is under 120ms",
"tokenBudget": 200000
} }
{ "id": 27, "result": { "goal": {
"threadId": "thr_123",
"objective": "Keep improving the benchmark until p95 latency is under 120ms",
"status": "active",
"tokenBudget": 200000,
"tokensUsed": 0,
"timeUsedSeconds": 0,
"createdAt": 1776272400,
"updatedAt": 1776272400
} } }
{ "method": "thread/goal/updated", "params": { "threadId": "thr_123", "goal": {
"threadId": "thr_123",
"objective": "Keep improving the benchmark until p95 latency is under 120ms",
"status": "active",
"tokenBudget": 200000,
"tokensUsed": 0,
"timeUsedSeconds": 0,
"createdAt": 1776272400,
"updatedAt": 1776272400
} } }
```
```json
{ "method": "thread/goal/set", "id": 28, "params": {
"threadId": "thr_123",
"status": "paused"
} }
{ "id": 28, "result": { "goal": {
"threadId": "thr_123",
"objective": "Keep improving the benchmark until p95 latency is under 120ms",
"status": "paused",
"tokenBudget": 200000,
"tokensUsed": 10000,
"timeUsedSeconds": 60,
"createdAt": 1776272400,
"updatedAt": 1776272460
} } }
```
Use `thread/goal/get` to read the current goal without changing it.
```json
{ "method": "thread/goal/get", "id": 29, "params": { "threadId": "thr_123" } }
{ "id": 29, "result": { "goal": null } }
```
Use `thread/goal/clear` to remove the current goal.
```json
{ "method": "thread/goal/clear", "id": 30, "params": { "threadId": "thr_123" } }
{ "id": 30, "result": { "cleared": true } }
{ "method": "thread/goal/cleared", "params": { "threadId": "thr_123" } }
```
### Example: Archive a thread
Use `thread/archive` to move the persisted rollout (stored as a JSONL file on disk) into the archived sessions directory and attempt to move any spawned descendant thread rollouts.