@terreno/mcp 57.9.0 → 57.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/upgrades/57.10.1.md +54 -0
- package/dist/docs/versioned/next/README.md +1 -0
- package/dist/docs/versioned/next/explanation/README.md +4 -0
- package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +2 -2
- package/dist/docs/versioned/next/explanation/agent-ui-asks.md +299 -0
- package/dist/docs/versioned/next/explanation/agent-ui-blocks.md +67 -0
- package/dist/docs/versioned/next/explanation/ai-observability.md +101 -0
- package/dist/docs/versioned/next/explanation/durable-agent-harness.md +531 -0
- package/dist/docs/versioned/next/explanation/example-coverage.md +3 -1
- package/dist/docs/versioned/next/explanation/loop-engineering.md +1 -1
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +29 -1
- package/dist/docs/versioned/next/explanation/typescript-configuration.md +1 -1
- package/dist/docs/versioned/next/explanation/versioning-policy.md +1 -1
- package/dist/docs/versioned/next/how-to/README.md +6 -0
- package/dist/docs/versioned/next/how-to/agent-ui-asks.md +730 -0
- package/dist/docs/versioned/next/how-to/agent-ui-blocks.md +104 -0
- package/dist/docs/versioned/next/how-to/ai-feature-development.md +156 -0
- package/dist/docs/versioned/next/how-to/build-a-durable-workflow.md +232 -0
- package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +2 -0
- package/dist/docs/versioned/next/how-to/circleci.md +5 -3
- package/dist/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +2 -1
- package/dist/docs/versioned/next/how-to/ship-a-new-task-version.md +76 -0
- package/dist/docs/versioned/next/how-to/upgrade-terreno.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/agent-ui-asks.md +688 -0
- package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +10 -6
- package/dist/docs/versioned/next/implementationPlans/ai-observability.md +475 -157
- package/dist/docs/versioned/next/implementationPlans/durable-agent-harness.md +340 -0
- package/dist/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -1
- package/dist/docs/versioned/next/reference/README.md +3 -0
- package/dist/docs/versioned/next/reference/admin-config.md +2 -0
- package/dist/docs/versioned/next/reference/admin-frontend.md +216 -0
- package/dist/docs/versioned/next/reference/agent-ui-asks.md +1059 -0
- package/dist/docs/versioned/next/reference/ai-harness.md +1881 -0
- package/dist/docs/versioned/next/reference/ai.md +251 -25
- package/dist/docs/versioned/next/reference/api.md +46 -0
- package/dist/docs/versioned/next/reference/blocks.md +178 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +1 -0
- package/dist/docs/versioned/next/reference/mcp-server.md +17 -0
- package/dist/docs/versioned/next/reference/test.md +11 -2
- package/dist/docs/versioned/next/reference/ui.md +325 -1
- package/dist/docs/versioned/next/tasks/agent-ui-asks.md +213 -0
- package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +2 -1
- package/dist/docs/versioned/next/tasks/ai-observability.md +233 -104
- package/dist/docs/versioned/next/tasks/durable-agent-harness.md +183 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +37 -0
- package/dist/tools.js.map +1 -1
- package/package.json +3 -2
- package/src/docs/upgrades/57.10.1.md +54 -0
- package/src/docs/versioned/next/README.md +1 -0
- package/src/docs/versioned/next/explanation/README.md +4 -0
- package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +2 -2
- package/src/docs/versioned/next/explanation/agent-ui-asks.md +299 -0
- package/src/docs/versioned/next/explanation/agent-ui-blocks.md +67 -0
- package/src/docs/versioned/next/explanation/ai-observability.md +101 -0
- package/src/docs/versioned/next/explanation/durable-agent-harness.md +531 -0
- package/src/docs/versioned/next/explanation/example-coverage.md +3 -1
- package/src/docs/versioned/next/explanation/loop-engineering.md +1 -1
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +29 -1
- package/src/docs/versioned/next/explanation/typescript-configuration.md +1 -1
- package/src/docs/versioned/next/explanation/versioning-policy.md +1 -1
- package/src/docs/versioned/next/how-to/README.md +6 -0
- package/src/docs/versioned/next/how-to/agent-ui-asks.md +730 -0
- package/src/docs/versioned/next/how-to/agent-ui-blocks.md +104 -0
- package/src/docs/versioned/next/how-to/ai-feature-development.md +156 -0
- package/src/docs/versioned/next/how-to/build-a-durable-workflow.md +232 -0
- package/src/docs/versioned/next/how-to/charts-and-dashboards.md +2 -0
- package/src/docs/versioned/next/how-to/circleci.md +5 -3
- package/src/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +2 -1
- package/src/docs/versioned/next/how-to/ship-a-new-task-version.md +76 -0
- package/src/docs/versioned/next/how-to/upgrade-terreno.md +1 -1
- package/src/docs/versioned/next/implementationPlans/agent-ui-asks.md +688 -0
- package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +10 -6
- package/src/docs/versioned/next/implementationPlans/ai-observability.md +475 -157
- package/src/docs/versioned/next/implementationPlans/durable-agent-harness.md +340 -0
- package/src/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -1
- package/src/docs/versioned/next/reference/README.md +3 -0
- package/src/docs/versioned/next/reference/admin-config.md +2 -0
- package/src/docs/versioned/next/reference/admin-frontend.md +216 -0
- package/src/docs/versioned/next/reference/agent-ui-asks.md +1059 -0
- package/src/docs/versioned/next/reference/ai-harness.md +1881 -0
- package/src/docs/versioned/next/reference/ai.md +251 -25
- package/src/docs/versioned/next/reference/api.md +46 -0
- package/src/docs/versioned/next/reference/blocks.md +178 -0
- package/src/docs/versioned/next/reference/environment-variables.md +1 -0
- package/src/docs/versioned/next/reference/mcp-server.md +17 -0
- package/src/docs/versioned/next/reference/test.md +11 -2
- package/src/docs/versioned/next/reference/ui.md +325 -1
- package/src/docs/versioned/next/tasks/agent-ui-asks.md +213 -0
- package/src/docs/versioned/next/tasks/agent-ui-blocks.md +2 -1
- package/src/docs/versioned/next/tasks/ai-observability.md +233 -104
- package/src/docs/versioned/next/tasks/durable-agent-harness.md +183 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Observe LLM calls
|
|
2
|
+
|
|
3
|
+
Register observability on the backend. Registration **is** the feature flag. Then run product iteration in [Develop an AI feature](ai-feature-development.md).
|
|
4
|
+
|
|
5
|
+
## Register
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {access} from "./access";
|
|
9
|
+
|
|
10
|
+
const priceMap = parseObservabilityPriceMap(process.env.AI_OBS_PRICE_MAP_JSON);
|
|
11
|
+
|
|
12
|
+
server.register(new ObservabilityApp({
|
|
13
|
+
accessControl: access,
|
|
14
|
+
aiService,
|
|
15
|
+
plugins: [localPlugin, langfuseAdapter, otelSink].filter(Boolean),
|
|
16
|
+
control: {
|
|
17
|
+
datasets: process.env.AI_OBS_DATASETS_PRIMARY ?? "local",
|
|
18
|
+
experiments: process.env.AI_OBS_EXPERIMENTS_PRIMARY ?? "local",
|
|
19
|
+
prompts: process.env.AI_OBS_PROMPTS_PRIMARY ?? "local",
|
|
20
|
+
reviewQueue: "local",
|
|
21
|
+
},
|
|
22
|
+
priceMap,
|
|
23
|
+
sampleRate: Number(process.env.AI_OBS_SAMPLE_RATE ?? 0),
|
|
24
|
+
}));
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For an admin playground that accepts a user-supplied provider key, add
|
|
28
|
+
`requestAiServiceFactory: ({apiKey, modelId}) => ...`. The playground route passes
|
|
29
|
+
`x-ai-api-key` to that factory only when the server-wide `aiService` is absent. Never persist or
|
|
30
|
+
log the key. `GET /ai/observability/status` exposes `playgroundAi.source` so admin UIs can block
|
|
31
|
+
**Run once** with a host-specific hint (`request-key`) instead of surfacing the backend **503**
|
|
32
|
+
missing-key title as a server misconfiguration.
|
|
33
|
+
|
|
34
|
+
Validate the parsed object before registration: each model needs non-negative numeric
|
|
35
|
+
`inputPerMTok` and `outputPerMTok`. The example implementation is
|
|
36
|
+
`example-backend/src/utils/observabilityConfig.ts`; malformed values fail startup with the
|
|
37
|
+
variable name.
|
|
38
|
+
|
|
39
|
+
Keep existing `LangfuseApp` if you already use Langfuse keys. The Langfuse **adapter** must reuse `getLangfuseClient()` and must not start a second OpenTelemetry SDK.
|
|
40
|
+
|
|
41
|
+
Boot **fails** if `experiments.primary !== datasets.primary`, if `reviewQueue` is `langfuse`, or if a primary’s plugin is missing.
|
|
42
|
+
|
|
43
|
+
Pass the same `access` object you use for `TerrenoApp` and `AdminApp` so
|
|
44
|
+
`GET /ai/observability/status` permissions match `/admin/config` screen filtering and every
|
|
45
|
+
observability route enforces RBAC.
|
|
46
|
+
|
|
47
|
+
### Example consumer roles
|
|
48
|
+
|
|
49
|
+
The example backend seeds two optional roles in `example-backend/src/rbacRoles.ts`. They are
|
|
50
|
+
**not** assigned automatically — attach them in the admin **Roles** UI or on `User.roles`.
|
|
51
|
+
|
|
52
|
+
| Role | Grants |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `aiObservabilityViewer` | `admin:access` plus `list` and `read` on all six observability resources |
|
|
55
|
+
| `aiObservabilityOperator` | `admin:access` plus every approved action on those resources |
|
|
56
|
+
|
|
57
|
+
Compose your own roles the same way: grant `admin: ["access"]` for the admin shell, then add
|
|
58
|
+
only the `aiPrompt` / `aiTrace` / `aiReview` / `aiDataset` / `aiEvaluator` / `aiExperiment`
|
|
59
|
+
actions each persona needs. Prompt `folder` and `description` stay consumer-defined metadata —
|
|
60
|
+
Terreno does not ship domain-specific folder names or an in-app approval workflow.
|
|
61
|
+
|
|
62
|
+
## Env
|
|
63
|
+
|
|
64
|
+
| Variable | Role |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `AI_OBS_PROMPTS_PRIMARY` | `local` or `langfuse` (default `local`) |
|
|
67
|
+
| `AI_OBS_DATASETS_PRIMARY` | default `local` |
|
|
68
|
+
| `AI_OBS_EXPERIMENTS_PRIMARY` | must equal datasets |
|
|
69
|
+
| `AI_OBS_SAMPLE_RATE` | `0`–`1`, default `0` (live evals off) |
|
|
70
|
+
| `AI_OBS_PRICE_MAP_JSON` | `{ "gemini-2.5-flash": { "inputPerMTok": 0.1, "outputPerMTok": 0.4 } }` |
|
|
71
|
+
| `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY` | existing Langfuse plugin |
|
|
72
|
+
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | optional OTLP trace sink |
|
|
73
|
+
|
|
74
|
+
## App generate calls
|
|
75
|
+
|
|
76
|
+
Pass identity and the production prompt ref so traces feed the SOP loop:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
await aiService.generateText({
|
|
80
|
+
prompt: text,
|
|
81
|
+
promptLabel: "production",
|
|
82
|
+
promptName: "example-summarize",
|
|
83
|
+
sessionId: req.header("x-ai-session-id"),
|
|
84
|
+
userId: req.user?.id,
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Opt out of tracing with `skipTrace: true`. Sink failures are logged and never fail the generate. `AIRequest` is still written.
|
|
89
|
+
|
|
90
|
+
## Admin
|
|
91
|
+
|
|
92
|
+
Phase 1 admin ships **Prompts**, **Traces**, and **Review**. Datasets, Evaluators, and
|
|
93
|
+
Experiments screens arrive in phase 2. **Open in Langfuse** appears in phase 3 when the
|
|
94
|
+
Langfuse plugin is registered. Review is hidden when the local plugin is off.
|
|
95
|
+
|
|
96
|
+
On **Traces**, **Run multi-stage trace test** (local sink only) runs two schema-backed
|
|
97
|
+
`generateJsonObject` calls, a deterministic tool stage, and a final schema-backed combine
|
|
98
|
+
call, then opens the nested trace. Each LLM span input includes `outputSchema`. It uses the
|
|
99
|
+
server `aiService` when one is configured; otherwise it sends the admin's saved key as
|
|
100
|
+
`x-ai-api-key` to `requestAiServiceFactory`, and the button stays disabled with a hint until
|
|
101
|
+
that key exists.
|
|
102
|
+
|
|
103
|
+
## Run the example locally
|
|
104
|
+
|
|
105
|
+
1. Set `MONGO_URI` to a replica set and the auth secrets from
|
|
106
|
+
`example-backend/.env.example`.
|
|
107
|
+
2. Run `bun run backend:seed`. The idempotent seed creates `examples/example-summarize`
|
|
108
|
+
v1 with `production` pointing to v1 and candidate v2, plus the human
|
|
109
|
+
`correctness-human` evaluator, automatic `schema-assert` evaluator, and
|
|
110
|
+
two-item `example-gold` dataset. It also creates `chat-safety-synthetic` (12 proofread
|
|
111
|
+
two-person chats), `chat-safety-screen` v1 and v2, and the `chat-safety-agreement`
|
|
112
|
+
evaluator. On a database that already booted, run Admin → Scripts →
|
|
113
|
+
`seedChatSafetyDataset` as a wet run, or
|
|
114
|
+
`cd example-backend && bun run script seedChatSafetyDataset --wet`.
|
|
115
|
+
3. Start `bun run backend:dev` and `bun run frontend:web`, then sign in as the seeded
|
|
116
|
+
admin.
|
|
117
|
+
4. Open **AI Observability → Prompts** to inspect or save a new immutable version. Move
|
|
118
|
+
`production` explicitly; the seed never overwrites an existing label.
|
|
119
|
+
5. Configure `GOOGLE_VERTEX_PROJECT` or `GEMINI_API_KEY`, then sign in with Better Auth
|
|
120
|
+
and call the seeded route:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
API=http://localhost:4000
|
|
124
|
+
COOKIE_JAR="$(mktemp)"
|
|
125
|
+
curl -sS -c "$COOKIE_JAR" \
|
|
126
|
+
-H "Content-Type: application/json" \
|
|
127
|
+
-d '{"email":"admin@example.com","password":"testpassword123"}' \
|
|
128
|
+
"$API/api/auth/sign-in/email" >/dev/null
|
|
129
|
+
|
|
130
|
+
curl -X POST "$API/ai/example-summarize" \
|
|
131
|
+
-b "$COOKIE_JAR" \
|
|
132
|
+
-H "Content-Type: application/json" \
|
|
133
|
+
-H "x-ai-session-id: docs-walkthrough" \
|
|
134
|
+
-d '{"text":"Terreno records local nested traces for every AI call."}'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
6. Open its `example-summarize` trace, send it to Review, score **correct**, and submit
|
|
138
|
+
until the queue reports **Queue clear**.
|
|
139
|
+
|
|
140
|
+
The example app also calls this route from the UI: **Todos → Summarize** sends the user's todo
|
|
141
|
+
list, so each press produces a trace with the production prompt reference, `userId`, and a
|
|
142
|
+
per-app-run `x-ai-session-id`. Without server credentials the screen forwards the Gemini key
|
|
143
|
+
saved in **Profile** as `x-ai-api-key`; otherwise the route answers **503**.
|
|
144
|
+
|
|
145
|
+
The route sends `text` as the user prompt. The labelled registry version supplies the
|
|
146
|
+
system prompt and the route uses `TemperaturePresets.LOW`.
|
|
147
|
+
|
|
148
|
+
The example registers the local plugin even when no Langfuse keys exist. Set
|
|
149
|
+
`AI_OBS_PRICE_MAP_JSON` to calculate USD cost; unlisted models show tokens with no cost,
|
|
150
|
+
never `$0`.
|
|
151
|
+
|
|
152
|
+
## Create a prompt and pin production
|
|
153
|
+
|
|
154
|
+
Admin-only routes live under `/ai/observability`. Create the prompt in a folder, save later edits as `vN+1` (v1 stays immutable), then move `production` — the response names the outgoing version for the confirm modal.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# 1. Create v1 in a folder
|
|
158
|
+
curl -X POST "$API/ai/observability/prompts" \
|
|
159
|
+
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
160
|
+
-H "Content-Type: application/json" \
|
|
161
|
+
-d '{"folder":"examples","name":"example-summarize","system":"Summarize {{text}}","template":"{{text}}","type":"text"}'
|
|
162
|
+
|
|
163
|
+
# 2. Pin production (first pin has no outgoing version)
|
|
164
|
+
curl -X POST "$API/ai/observability/prompts/example-summarize/labels" \
|
|
165
|
+
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
166
|
+
-H "Content-Type: application/json" \
|
|
167
|
+
-d '{"label":"production","version":1}'
|
|
168
|
+
|
|
169
|
+
# 3. Save as v2, then move production (response.outgoingVersion is 1)
|
|
170
|
+
curl -X POST "$API/ai/observability/prompts/example-summarize/versions" \
|
|
171
|
+
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
172
|
+
-H "Content-Type: application/json" \
|
|
173
|
+
-d '{"system":"Summarize {{text}} in one sentence","template":"{{text}}","type":"text"}'
|
|
174
|
+
curl -X POST "$API/ai/observability/prompts/example-summarize/labels" \
|
|
175
|
+
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
176
|
+
-H "Content-Type: application/json" \
|
|
177
|
+
-d '{"label":"production","version":2}'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`GET /ai/observability/prompts?folder=examples&search=sum&include=usage7d` lists folder matches with 7-day call/cost rollups. A prompt with no `production` label returns `production: "—"`. Playground `POST /ai/observability/prompts/:name/playground` compiles `{{var}}`, runs one `AIService` call, and does not create a version.
|
|
181
|
+
|
|
182
|
+
The example backend registers `requestAiServiceFactory` but no server `aiService`, so status reports
|
|
183
|
+
`playgroundAi.source: "request-key"`. In **AI Observability → Prompts → Playground**, save a Gemini
|
|
184
|
+
API key on **Profile** first; the admin UI blocks **Run once** with that hint until the key loads
|
|
185
|
+
from storage and is sent as `x-ai-api-key`. **Traces → Run multi-stage trace test** uses the same
|
|
186
|
+
saved key and is blocked the same way.
|
|
187
|
+
|
|
188
|
+
## Review a trace
|
|
189
|
+
|
|
190
|
+
1. Use the seeded human `correctness-human` evaluator. If you skipped `bun run backend:seed`,
|
|
191
|
+
install it once with `POST /ai/observability/evaluators/templates/correctness-human`; a
|
|
192
|
+
duplicate install returns 409.
|
|
193
|
+
2. Enqueue traces: `POST /ai/observability/traces/review` with `{evaluatorId, traceIds, reason: "manual"}`.
|
|
194
|
+
3. List oldest-first: `GET /ai/observability/review?status=pending` (response includes per-status counts).
|
|
195
|
+
4. Open an item: `GET /ai/observability/review/:id` returns evaluator dimensions plus `given` / `wrote` panels (variable labels when present, otherwise raw keys).
|
|
196
|
+
5. `POST /ai/observability/review/:id` with `action: "submit"` writes dimension-keyed scores through every ScoreSink and marks the item `done` (it leaves the pending list). `skip` and `assign` move status to `skipped` / `in_progress`.
|
|
197
|
+
|
|
@@ -48,6 +48,7 @@ bun test --only-failures <path>
|
|
|
48
48
|
| --- | --- |
|
|
49
49
|
| `api-ci` | `bun run api:test` |
|
|
50
50
|
| `ai-ci` | `cd ai && bun run test:ci` |
|
|
51
|
+
| `blocks-ci` | `bun run blocks:test` |
|
|
51
52
|
| `ui-ci` | `bun run ui:test` |
|
|
52
53
|
| `rtk-ci` | `bun run rtk:test` |
|
|
53
54
|
| `syncdb-ci` | `bun run syncdb:test` |
|
|
@@ -81,7 +82,7 @@ MONGO_URI="mongodb://127.0.0.1:27017/terreno-e2e?replicaSet=rs0" \
|
|
|
81
82
|
| `e2e` `auth` | `login` `signup` `consents` `forgot-password` `reset-password` `verify-email` |
|
|
82
83
|
| `e2e` `app` | `todos` `profile` `realtime` `ai-chat` `pdf` |
|
|
83
84
|
| `e2e` `admin-core` | `admin` `admin-home` `admin-form` `admin-todo-crud` `admin-title-update-depth` |
|
|
84
|
-
| `e2e` `admin-table` | `admin-table-search-filter` `admin-table-bulk-actions` `admin-custom-screens` `admin-comms-back` |
|
|
85
|
+
| `e2e` `admin-table` | `admin-table-search-filter` `admin-table-bulk-actions` `admin-custom-screens` `admin-comms-back` `harness-approvals` `harness-intake` |
|
|
85
86
|
| `e2e` `syncdb` | `syncdb-load-delta` `syncdb-offline` `syncdb-conflicts` `syncdb-storage` `syncdb-chaos` |
|
|
86
87
|
|
|
87
88
|
Full local suite: `bun run frontend:e2e`. Nightly load (`e2e-load` / `syncdb-loadlab`) is
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Ship a new task version
|
|
2
|
+
|
|
3
|
+
Change a durable harness task without breaking runs that already started on the old code.
|
|
4
|
+
Rules: [Versioning](../reference/ai-harness.md#versioning). Why:
|
|
5
|
+
[Version pinning](../explanation/durable-agent-harness.md#version-pinning).
|
|
6
|
+
|
|
7
|
+
## When to bump `version`
|
|
8
|
+
|
|
9
|
+
Bump it on any change an in-flight task could notice:
|
|
10
|
+
|
|
11
|
+
- A phase is renamed, removed, or added between existing phases.
|
|
12
|
+
- The shape of `state`, `input`, or the result changes.
|
|
13
|
+
- A phase's side effects or `replay` policy change.
|
|
14
|
+
|
|
15
|
+
A fix that keeps the same phases, state shape, and side effects can ship under the same
|
|
16
|
+
version.
|
|
17
|
+
|
|
18
|
+
## Steps
|
|
19
|
+
|
|
20
|
+
1. Copy the definition and bump `version`. Keep the old one unchanged.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
export const intakeV1 = defineTask<IntakeInput, IntakeStateV1, IntakeResult>({
|
|
24
|
+
name: "clinic.intake",
|
|
25
|
+
version: 1,
|
|
26
|
+
// ...the old phases, untouched
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const intakeV2 = defineTask<IntakeInput, IntakeStateV2, IntakeResult>({
|
|
30
|
+
name: "clinic.intake",
|
|
31
|
+
version: 2,
|
|
32
|
+
// ...the new phases
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. Register both versions. Point new work at v2.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const harness = await Harness.open({registry: [intakeV1, intakeV2]});
|
|
40
|
+
await harness.start();
|
|
41
|
+
|
|
42
|
+
await harness.createTask(intakeV2, {patientId});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Tasks created on v1 finish on v1's phases and v1's `abort` handler. New tasks run on
|
|
46
|
+
v2. Parent tasks that call `rt.createTask(intakeV1, ...)` keep making v1 children
|
|
47
|
+
until you change that call.
|
|
48
|
+
|
|
49
|
+
3. Deploy. Every process that runs the harness needs the same registry.
|
|
50
|
+
|
|
51
|
+
4. Wait until no v1 task is left in flight. Check in a Mongo shell:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
db.harnesstasks.countDocuments({
|
|
55
|
+
name: "clinic.intake",
|
|
56
|
+
version: 1,
|
|
57
|
+
status: {$in: ["pending", "running", "waiting", "interrupted"]},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Resolve stuck `interrupted` v1 tasks with `harness.resolveInterrupted`, or stop them
|
|
62
|
+
with `harness.abort`.
|
|
63
|
+
|
|
64
|
+
5. When the count is 0, remove `intakeV1` from the registry and deploy again.
|
|
65
|
+
|
|
66
|
+
## If you remove a version too early
|
|
67
|
+
|
|
68
|
+
`Harness.start()` throws an `APIError` with code `harness-config-invalid` and claims
|
|
69
|
+
nothing. Its `detail` reads:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
Harness.start: in-flight tasks use task versions this registry does not register: clinic.intake@1 (4 tasks). Register those definitions (keep old versions until their tasks finish) or resolve the tasks first.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Fix: add the old definition back to `registry` and redeploy. Its tasks resume from their
|
|
76
|
+
last checkpoint.
|
|
@@ -38,7 +38,7 @@ Maintainers watching the next SDK beta use `track-upstream-expo` / [`track-upstr
|
|
|
38
38
|
|
|
39
39
|
### Backend vs frontend packages
|
|
40
40
|
|
|
41
|
-
From `publish-on-tag.yml`: **backend** `api`, `test`, `admin-backend`, `ai`, `api-health`, `comms`, `feature-flags`, `mcp`. **frontend** `ui`, `rtk`, `syncdb`, `admin-frontend`, `admin-spa`.
|
|
41
|
+
From `publish-on-tag.yml`: **backend** `api`, `test`, `admin-backend`, `blocks`, `ai`, `api-health`, `comms`, `feature-flags`, `mcp`. **frontend** `ui`, `rtk`, `syncdb`, `admin-frontend`, `admin-spa`. `blocks` holds contracts that both `ai` and `ui` depend on, so it moves with the backend step.
|
|
42
42
|
|
|
43
43
|
## 4. RTK → syncdb is not a version bump
|
|
44
44
|
|