@terreno/mcp 57.11.0 → 57.12.0
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.12.0.md +89 -0
- package/dist/docs/versioned/next/explanation/ai-workflows.md +4 -3
- package/dist/docs/versioned/next/explanation/roadmap-process.md +4 -2
- package/dist/docs/versioned/next/how-to/circleci.md +6 -2
- package/dist/docs/versioned/next/how-to/github-actions-ci.md +5 -6
- package/dist/docs/versioned/next/reference/ai-harness.md +33 -5
- package/dist/docs/versioned/next/reference/ai.md +33 -4
- package/dist/docs/versioned/next/reference/ui.md +10 -2
- package/package.json +3 -3
- package/src/docs/upgrades/57.12.0.md +89 -0
- package/src/docs/versioned/next/explanation/ai-workflows.md +4 -3
- package/src/docs/versioned/next/explanation/roadmap-process.md +4 -2
- package/src/docs/versioned/next/how-to/circleci.md +6 -2
- package/src/docs/versioned/next/how-to/github-actions-ci.md +5 -6
- package/src/docs/versioned/next/reference/ai-harness.md +33 -5
- package/src/docs/versioned/next/reference/ai.md +33 -4
- package/src/docs/versioned/next/reference/ui.md +10 -2
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Upgrading to 57.12.0
|
|
2
|
+
|
|
3
|
+
**Action required:** Yes
|
|
4
|
+
**Affected packages:** @terreno/ai
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
Harness tasks can record a trace scope, tags, and prompt versions, and `@terreno/ai` publishes official subpath exports. The chat `Project` model registers on first use instead of at import, and harness task HTTP responses no longer include the lease fencing token.
|
|
9
|
+
|
|
10
|
+
## Breaking changes
|
|
11
|
+
|
|
12
|
+
### `Project` is no longer registered on import
|
|
13
|
+
|
|
14
|
+
**What changed**
|
|
15
|
+
|
|
16
|
+
Importing `@terreno/ai` does not register mongoose model `Project`. The exported `Project` is a lazy proxy. The first property access, `new Project(...)`, or `getProjectModel()` registers the model. Statics and `instanceof Project` still work. These do not:
|
|
17
|
+
|
|
18
|
+
- `Project === mongoose.models.Project` (and `Project === mongoose.model("Project")`)
|
|
19
|
+
- `Object.keys(Project)` (always empty)
|
|
20
|
+
- reading `Project.prototype` (throws)
|
|
21
|
+
|
|
22
|
+
`new AiApp()` still mounts `/gpt/projects` and registers the model when those routes load. `new AiApp({projects: false})` skips those routes.
|
|
23
|
+
|
|
24
|
+
**Why**
|
|
25
|
+
|
|
26
|
+
An app that imports `@terreno/ai` and owns its own model named `Project` was hitting a mongoose overwrite error at import time.
|
|
27
|
+
|
|
28
|
+
**Migration**
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Before
|
|
32
|
+
import {Project} from "@terreno/ai";
|
|
33
|
+
|
|
34
|
+
const project = await Project.findExactlyOne({_id: id});
|
|
35
|
+
if (mongoose.models.Project === Project) {
|
|
36
|
+
// identity check
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// After
|
|
40
|
+
import {getProjectModel} from "@terreno/ai";
|
|
41
|
+
|
|
42
|
+
const Project = getProjectModel();
|
|
43
|
+
const project = await Project.findExactlyOne({_id: id});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
To keep a separate app model named `Project`, do not call `getProjectModel()` or touch the `Project` export, and skip the AI project routes:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
new AiApp({aiService, projects: false});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Harness task HTTP responses omit `lease`
|
|
53
|
+
|
|
54
|
+
**What changed**
|
|
55
|
+
|
|
56
|
+
`GET /harness/tasks/:id` no longer includes `lease`. The OpenAPI schema for that response omits it too. Collection list is disabled (`list: []`, 405). The token is still stored on the task document for runners.
|
|
57
|
+
|
|
58
|
+
**Why**
|
|
59
|
+
|
|
60
|
+
`lease.token` is the fencing token that decides which runner may commit. It is not a client field.
|
|
61
|
+
|
|
62
|
+
**Migration**
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Before
|
|
66
|
+
const token = task.lease?.token;
|
|
67
|
+
|
|
68
|
+
// After
|
|
69
|
+
// Do not read lease from the HTTP task. Runners load it on the server.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Regenerate the OpenAPI SDK after upgrading `@terreno/ai` so generated task types drop `lease`.
|
|
73
|
+
|
|
74
|
+
## Deprecations
|
|
75
|
+
|
|
76
|
+
`Project` from `@terreno/ai` still works for `find`, `create`, `new`, and `instanceof`. It is deprecated. Call `getProjectModel()` and use that model. A later major can remove the proxy.
|
|
77
|
+
|
|
78
|
+
## New capabilities
|
|
79
|
+
|
|
80
|
+
- `harness.createTask(def, input, {trace: {scope, tags}, prompts})` writes a scope (tenant, workspace), tags, and prompt versions onto the task's `ObsTrace`. Child tasks and subagents share that trace. The admin trace list filters with `?scope=`.
|
|
81
|
+
- `rt.runAgent` accepts `prompts` and a registry `PromptVersionRef` as `instructions`. It runs `body` and records the version on the trace.
|
|
82
|
+
- Subpath exports: `@terreno/ai/admin`, `@terreno/ai/harness/agentLoop`, `@terreno/ai/harness/commit`, `@terreno/ai/harness/events`, `@terreno/ai/harness/internalRuntime`, `@terreno/ai/observability/observabilityApp`, `@terreno/ai/observability/localPlugin`, `@terreno/ai/observability/promptStore`.
|
|
83
|
+
|
|
84
|
+
## Verification
|
|
85
|
+
|
|
86
|
+
- Code that imported `Project` from `@terreno/ai`: switch identity checks and `mongoose.models.Project` reads to `getProjectModel()`.
|
|
87
|
+
- An app that owns a model named `Project`: pass `projects: false` and do not call `getProjectModel()`.
|
|
88
|
+
- Harness clients: confirm `GET /harness/tasks/:id` JSON has no `lease`, then regenerate the SDK.
|
|
89
|
+
- `bun run compile` and `bun run lint` at the app root.
|
|
@@ -49,9 +49,10 @@ Maintains centralized agent guidance and generates the matching configuration fo
|
|
|
49
49
|
|
|
50
50
|
Rulesync generates native hooks from `.rulesync/hooks.json`. Biome checks changed files
|
|
51
51
|
after edits and staged files before commits. At agent stop,
|
|
52
|
-
`.rulesync/hooks/quality-check.sh` runs
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
`.rulesync/hooks/quality-check.sh` runs `bun install --frozen-lockfile` when
|
|
53
|
+
`node_modules/typescript` or any workspace package link is missing, then runs lint,
|
|
54
|
+
TypeScript compilation, Knip, and dependency-cruiser. It keeps stdout machine-readable
|
|
55
|
+
for each host and prevents retry-triggered Stop hooks from rerunning the commands.
|
|
55
56
|
|
|
56
57
|
Existing findings are ratcheted so the checks reject new debt without requiring unrelated
|
|
57
58
|
cleanup. See [Static analysis](static-analysis.md).
|
|
@@ -402,7 +402,7 @@ do not claim completion until done.
|
|
|
402
402
|
| Workflow | Trigger | Purpose |
|
|
403
403
|
| -------- | ------- | ------- |
|
|
404
404
|
| [`.github/workflows/triage.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/triage.yml) | Issue opened | `status:needs-triage` + `area:*` from package dropdown + `type:*` from Kind when present; `area:*` / `type:*` labels the issue was opened with win, so `roadmap:sync` issues are not asked for an area |
|
|
405
|
-
| [`.github/workflows/roadmap-generate.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/roadmap-generate.yml) | Daily + manual | `roadmap:sync --check` for board drift, then regenerate `ROADMAP.md` from the board and open a pull request when
|
|
405
|
+
| [`.github/workflows/roadmap-generate.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/roadmap-generate.yml) | Daily + manual | `roadmap:sync --check` for board drift, then regenerate `ROADMAP.md` from the board and open a pull request when roadmap content changes. A new **Last updated** timestamp alone does not open a pull request |
|
|
406
406
|
| [`.github/workflows/roadmap-sync.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/roadmap-sync.yml) | Taxonomy files change on `master` + manual | Apply labels and reconcile the board's fields and items |
|
|
407
407
|
| [`.github/workflows/roadmap-reconcile.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/roadmap-reconcile.yml) | IP or task files change on `master` + manual | Advance status from IP headers, add an entry for every new IP, open its tracking issue, push to the board, regenerate `ROADMAP.md` |
|
|
408
408
|
All three share the `roadmap` concurrency group. They write the same board, and interleaving
|
|
@@ -495,4 +495,6 @@ GITHUB_TOKEN=$(gh auth token) TERRENO_PROJECT_NUMBER=... bun run roadmap:generat
|
|
|
495
495
|
```
|
|
496
496
|
|
|
497
497
|
The generator exits non-zero when the project cannot be read, so a bad project number or a
|
|
498
|
-
token without `read:project` fails loudly instead of writing an empty `ROADMAP.md`.
|
|
498
|
+
token without `read:project` fails loudly instead of writing an empty `ROADMAP.md`. When the
|
|
499
|
+
rendered list matches the file already on disk, it keeps that file's **Last updated** stamp
|
|
500
|
+
and does not write. The daily workflow then sees a clean diff and does not open a pull request.
|
|
@@ -254,8 +254,12 @@ the review script so a PR cannot rewrite the reviewer.
|
|
|
254
254
|
Netlify and GCP jobs **validate their context as the first step** and fail,
|
|
255
255
|
before checkout or `bun install`, listing the missing variables. Preview jobs
|
|
256
256
|
halt earlier on fork PRs, where CircleCI withholds contexts. Docker Layer
|
|
257
|
-
Caching is off (200 credits per job). The docs Netlify target
|
|
258
|
-
|
|
257
|
+
Caching is off (200 credits per job). The docs Netlify target builds only the
|
|
258
|
+
current docs tree, disables Docusaurus minification, and pins SSG to one worker
|
|
259
|
+
thread so the build peaks near 3 GB and fits the 4 GB `medium` executor.
|
|
260
|
+
Released versions are not rebuilt: `archive-docs-version` (in `publish-release`,
|
|
261
|
+
`X.Y.0` tags only) builds each one once and uploads it to its GitHub release, and
|
|
262
|
+
`deploy-docs` unpacks those archives under `/<version>/`. See `website/README.md`.
|
|
259
263
|
|
|
260
264
|
`terreno-gcp` uses CircleCI OIDC (`CIRCLE_OIDC_TOKEN_V2`), never a JSON service
|
|
261
265
|
account key. Set `circleci_org_id`, `circleci_project_id`, and
|
|
@@ -7,24 +7,23 @@ describes how they behaved if one is ever re-enabled.
|
|
|
7
7
|
|
|
8
8
|
## Docs site (`docs-deploy.yml`)
|
|
9
9
|
|
|
10
|
-
`Build docs site` is often the longest PR job because
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
`Build docs site` is often the longest PR job because TypeDoc regenerates API
|
|
11
|
+
pages. The site builds only the current docs tree; `website/versioned_docs/` is
|
|
12
|
+
markdown for the MCP server.
|
|
13
13
|
|
|
14
14
|
PR builds set `DOCS_PREVIEW=true` and pass `--no-minify`. Both PR and
|
|
15
15
|
`master` use Docusaurus Faster (Rspack/SWC via `@docusaurus/faster`).
|
|
16
16
|
|
|
17
17
|
| Behavior | PR preview | `master` production |
|
|
18
18
|
| --- | --- | --- |
|
|
19
|
-
| Versioned docs (`57.1.0`, `0.30.0`, …) | Omitted
|
|
19
|
+
| Versioned docs (`57.1.0`, `0.30.0`, …) | Omitted | Prebuilt archives unpacked under `/<version>/` |
|
|
20
20
|
| JS minify | Off | On |
|
|
21
21
|
| Local search index | Skipped | Built |
|
|
22
22
|
| Generated API + component MDX | Restored from cache when `api`/`rtk`/`ui` hashes match | Same cache, then full generate on miss |
|
|
23
23
|
| TypeDoc workspace `tsc` | Once per generate (api+rtk deps share a process) | Same |
|
|
24
24
|
| Rspack cache (`node_modules/.cache/rspack`) | Restored per `pull_request` vs `push` | Separate production key |
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
PR deploy preview.
|
|
26
|
+
Do not rely on `/57.1.0/…` URLs in a PR deploy preview.
|
|
28
27
|
|
|
29
28
|
## Other high-cost jobs
|
|
30
29
|
|
|
@@ -142,7 +142,7 @@ The `task` view passed to `run`: `{id, name, version, input, state, phase, attem
|
|
|
142
142
|
| `Harness.open({registry, runner?, models?, env?, priceMap?, streaming?, testHooks?})` | Checks requirements, rejects a duplicate `name@version` or agent name, ensures collections and indexes exist. `runner` defaults to `new InProcessRunner()`. See [open options](#open-options). |
|
|
143
143
|
| `start()` | Throws, claiming nothing, when any non-terminal task uses a `name@version` missing from the registry (see [Versioning](#versioning)). Then starts the runner. Throws when already started. The runner recovers expired tasks once it owns execution. |
|
|
144
144
|
| `stop()` | Stops claiming work, waits (without a time limit) for every phase in flight while still renewing the owner lease, then releases it. No-op when not started. |
|
|
145
|
-
| `createTask(definition, input, {requestId?, userId?})` | Inserts a `pending` task, its `ObsTrace`, and its root span in one transaction. Wakes the runner. The definition must be in the registry. |
|
|
145
|
+
| `createTask(definition, input, {requestId?, userId?, trace?, prompts?})` | Inserts a `pending` task, its `ObsTrace`, and its root span in one transaction. Wakes the runner. The definition must be in the registry. See [Trace scope and prompt versions](#trace-scope-and-prompt-versions). |
|
|
146
146
|
| `resolveInterrupted(id, {action, reason, result?, userId?})` | Resolve an `interrupted` task. See [resolveInterrupted](#resolveinterrupted). |
|
|
147
147
|
| `abort(id, {reason, userId?})` | Abort a task and every non-terminal task it owns, bottom-up. See [abort](#abort). |
|
|
148
148
|
| `decideApproval(approvalId, {approved, reason?, userId?})` | Approve or reject a pending approval without checking approvers. See [Approvals](#approvals). |
|
|
@@ -158,6 +158,30 @@ create with the same id returns the first task and leaves no extra trace. Reusin
|
|
|
158
158
|
`requestId` for a different task name, or with a different `userId` (including none versus
|
|
159
159
|
some), throws. A soft-deleted task still owns its `requestId`.
|
|
160
160
|
|
|
161
|
+
### Trace scope and prompt versions
|
|
162
|
+
|
|
163
|
+
A task's `ObsTrace` is its run record. `createTask` can label it:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const prompt = await promptRegistry.get({label: "production", name: "intake-summary"});
|
|
167
|
+
await harness.createTask(intakeSummary, {patientId}, {
|
|
168
|
+
prompts: prompt ? [prompt] : [],
|
|
169
|
+
trace: {scope: String(workspaceId), tags: ["nightly"]},
|
|
170
|
+
userId,
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| Option | Type | Description |
|
|
175
|
+
| --- | --- | --- |
|
|
176
|
+
| `trace.scope` | `string` | App-defined scope, such as a tenant or workspace id. Stored as `ObsTrace.scope`, indexed with `created` for per-scope metering (sum `LLM` span `usage.costUsd` by scope and month). Blank throws `harness-invalid-request`. |
|
|
177
|
+
| `trace.tags` | `string[]` | App-defined labels, stored once each as `ObsTrace.tags`. A blank tag throws `harness-invalid-request`. |
|
|
178
|
+
| `prompts` | `{name, version, label?}[]` | Prompt versions the task uses, stored once each on `ObsTrace.prompts`. A `PromptVersionRef` from `promptRegistry.get` fits as-is; its `body` is not stored. A ref without a name, with a non-string `label`, or without a positive integer `version` throws `harness-invalid-request`. |
|
|
179
|
+
|
|
180
|
+
Child tasks and subagents write into the root task's trace, so they share its scope and
|
|
181
|
+
tags. `rt.runAgent` adds its own prompt versions to the same trace (see
|
|
182
|
+
[Subagents](#subagents-rtrunagent)). The admin trace list filters by `scope` and by
|
|
183
|
+
prompt.
|
|
184
|
+
|
|
161
185
|
### open options
|
|
162
186
|
|
|
163
187
|
| Option | Type | Description |
|
|
@@ -306,7 +330,7 @@ where `lease` is `{owner, duration, heartbeat}` (Luxon `Duration`s). With `maxPh
|
|
|
306
330
|
| `rt.signal` | `AbortSignal`. Aborts when the task is aborted (at once in this process, within one heartbeat elsewhere), or when this run loses its lease. Pass it to cancellable calls. |
|
|
307
331
|
| `rt.createTask(definition, input, {background?, key?})` | Create a child task. Returns its id. See [Child tasks and waitForTasks](#child-tasks-and-waitfortasks). |
|
|
308
332
|
| `rt.waitForTasks(ids, {policy?})` | Return child outcomes once they settle; until then the task waits. See [Child tasks and waitForTasks](#child-tasks-and-waitfortasks). |
|
|
309
|
-
| `rt.runAgent(agent, {input, output?, instructions?})` | Run a registered agent as a subagent and return its answer; until it finishes the task waits. See [Subagents (rt.runAgent)](#subagents-rtrunagent). |
|
|
333
|
+
| `rt.runAgent(agent, {input, output?, instructions?, prompts?})` | Run a registered agent as a subagent and return its answer; until it finishes the task waits. See [Subagents (rt.runAgent)](#subagents-rtrunagent). |
|
|
310
334
|
| `rt.memo(key)` / `rt.memo(key, value)` | Read, or first-write, a durable value scoped to this task. See [Memos](#memos). |
|
|
311
335
|
| `rt.waitFor(event, {timeout?})` | Return the payload of the next `event` sent to this task, or `undefined` once `timeout` passes; until then the task waits. See [Events, waits, and sleep](#events-waits-and-sleep). |
|
|
312
336
|
| `rt.sleep(duration)` | Return once `duration` has passed; until then the task waits. See [Events, waits, and sleep](#events-waits-and-sleep). |
|
|
@@ -817,7 +841,9 @@ is not offered to sections, `beforeModelRequest`, or `afterTool` (the tool alrea
|
|
|
817
841
|
`basePath` defaults to `/harness`; it must start with `/` and not end with `/`.
|
|
818
842
|
`heartbeatInterval` (default `{seconds: 15}`, must be positive) paces SSE heartbeats.
|
|
819
843
|
Conversation, task, and approval routes are `modelRouter`s with instance actions, so they
|
|
820
|
-
appear in `/openapi.json
|
|
844
|
+
appear in `/openapi.json`. Each route and its mutations use that collection's tag
|
|
845
|
+
(`harnessconversations`, `harnesstasks`, `harnessapprovals`), so a generated client
|
|
846
|
+
invalidates the list it just changed. The two SSE routes are plain Express routes
|
|
821
847
|
(the listed exception to modelRouter actions) and are not in the OpenAPI spec.
|
|
822
848
|
|
|
823
849
|
### Conversation routes
|
|
@@ -840,7 +866,7 @@ appear in `/openapi.json` (tag `harness`). The two SSE routes are plain Express
|
|
|
840
866
|
|
|
841
867
|
| Method | Path | Permissions | Behavior |
|
|
842
868
|
| --- | --- | --- | --- |
|
|
843
|
-
| GET | `/harness/tasks/:id` | Owner (`userId`) or admin | One task. Others: 403. |
|
|
869
|
+
| GET | `/harness/tasks/:id` | Owner (`userId`) or admin | One task, without `lease` (the fencing token stays on the server). Others: 403. |
|
|
844
870
|
| POST | `/harness/tasks/:id/abort` | Owner or admin | Body `{reason}` (required, non-blank). Runs `harness.abort` with the caller as `userId`; returns the aborted task. |
|
|
845
871
|
| POST | `/harness/tasks/:id/resolveInterrupted` | Admin | Body `{action: "retry" \| "abort" \| "complete", reason, result?}`. Runs `harness.resolveInterrupted` with the caller as `userId`; returns the task. |
|
|
846
872
|
| GET | `/harness/tasks/:id/events` | Owner or admin | SSE for the task and its descendants. See [Event stream (SSE)](#event-stream-sse). |
|
|
@@ -1320,7 +1346,8 @@ summarize: {
|
|
|
1320
1346
|
| --- | --- | --- | --- |
|
|
1321
1347
|
| `input` | `unknown` | Yes | The subagent's user message. A string is sent as-is (blank is rejected); anything else as `JSON.stringify(input)`. |
|
|
1322
1348
|
| `output` | zod schema | No | Structured output. Defaults to the agent's own `output`. |
|
|
1323
|
-
| `instructions` | `string` | No | Replaces the agent's instructions for this subagent conversation only. |
|
|
1349
|
+
| `instructions` | `string` or `PromptVersionRef` | No | Replaces the agent's instructions for this subagent conversation only. A registry prompt (`promptRegistry.get`) runs with its `body`, and its `{name, version, label}` is added to the trace's `prompts`. |
|
|
1350
|
+
| `prompts` | `{name, version, label?}[]` | No | More prompt versions this subagent used, added to the trace's `prompts`. Each version is recorded once per trace. An invalid ref, or an `instructions` prompt without a string `body`, fails the phase with a definition error. |
|
|
1324
1351
|
|
|
1325
1352
|
Returns the final assistant text, or, with a schema, the object the schema parsed. The
|
|
1326
1353
|
generic `rt.runAgent<T>(...)` types the result when the schema comes from the agent.
|
|
@@ -1331,6 +1358,7 @@ What one call does:
|
|
|
1331
1358
|
(`ownership: {kind: "task", id: <caller>}`, `status: "busy"`, agent snapshot with
|
|
1332
1359
|
`instructions` and `outputSchema`), its user message (`seq` 1), and the turn task
|
|
1333
1360
|
(`ownership: {kind: "task", id: <caller>}`) with an `AGENT` span named after the agent.
|
|
1361
|
+
Prompt versions from `instructions` and `prompts` are added to the trace's `prompts`.
|
|
1334
1362
|
2. Waits with `rt.waitForTasks([turn])`: the caller commits `waiting` and the phase stops.
|
|
1335
1363
|
3. When the turn settles the caller wakes and the phase re-runs from its checkpoint. The
|
|
1336
1364
|
same call finds the same turn and conversation (step 1 writes nothing) and returns.
|
|
@@ -91,7 +91,7 @@ src/
|
|
|
91
91
|
- **Plugins:** `AiApp`, `LangfuseApp`
|
|
92
92
|
- **Service:** `AIService`, `TemperaturePresets`, `FileStorageService`, `MCPService`,
|
|
93
93
|
`getMCPTools`
|
|
94
|
-
- **Models:** `AIRequest`, `GptHistory`, `FileAttachment`, `Project`
|
|
94
|
+
- **Models:** `AIRequest`, `GptHistory`, `FileAttachment`, `Project` (lazy; prefer `getProjectModel()`)
|
|
95
95
|
- **Routes:** `addGptRoutes`, `addGptHistoryRoutes`, `addAiRequestsExplorerRoutes`, `addFileRoutes`, `addProjectRoutes`, `addMcpRoutes`
|
|
96
96
|
- **Structured output:** `parseAiJson`, `normalizeLlmJsonTextForStructuredOutput`, re-exported `Output`, `jsonSchema`, `JSONValue`, `FlexibleSchema` from `ai`
|
|
97
97
|
- **Langfuse:** `initLangfuseClient`, `getLangfuseClient`, `shutdownLangfuseClient`, `compilePrompt`, `createPrompt`, `getPrompt`, `createTelemetryConfig`, `preparePromptForAI`, `initTracing`, `shutdownTracing`, `LangfuseCache`, cache helpers
|
|
@@ -249,7 +249,8 @@ Metadata for files stored in GCS.
|
|
|
249
249
|
|
|
250
250
|
### Project
|
|
251
251
|
|
|
252
|
-
GPT project with persistent context and memories.
|
|
252
|
+
GPT project with persistent context and memories. Registered lazily: `getProjectModel()`
|
|
253
|
+
(or any use of the deprecated `Project` export) registers it.
|
|
253
254
|
|
|
254
255
|
| Field | Type | Description |
|
|
255
256
|
|-------|------|-------------|
|
|
@@ -440,6 +441,7 @@ new AiApp({
|
|
|
440
441
|
| `maxSteps` | Max tool-calling steps (default 5) |
|
|
441
442
|
| `titleModelId` | Cheaper model for conversation title generation |
|
|
442
443
|
| `openApiOptions` | Passed to route OpenAPI builders |
|
|
444
|
+
| `projects` | Default `true`. `false` skips the `/gpt/projects` routes, so `AiApp` never registers the `Project` model |
|
|
443
445
|
|
|
444
446
|
## LangfuseApp plugin
|
|
445
447
|
|
|
@@ -508,7 +510,7 @@ server `AIService` when configured, otherwise a request-scoped service built fro
|
|
|
508
510
|
| `ObsPrompt` | Named prompt (`name` unique) with `folder` and `tags[]` |
|
|
509
511
|
| `ObsPromptVersion` | Immutable `vN` body, `variables[]`, schemas, `sensitive` (default false), `config` |
|
|
510
512
|
| `ObsPromptLabel` | Movable labels; unique `(promptId, label)` |
|
|
511
|
-
| `ObsTrace` | Root trace: user, session, status, `errorSummary`, `sensitive`, `prompts[]`, usage |
|
|
513
|
+
| `ObsTrace` | Root trace: user, session, status, `errorSummary`, `sensitive`, `prompts[]`, `scope`, `tags[]`, usage |
|
|
512
514
|
| `ObsSpan` | Nested span with `kind`, `status`, optional `error`, offsets, usage |
|
|
513
515
|
| `ObsScore` | Scores on a trace/span; many per trace, **no unique index** |
|
|
514
516
|
| `ObsEvaluator` | Evaluator: `type` (`human` \| `llm-judge` \| `json-assert`), `target`, `dimensions[]`, `runModes`, `instructions`, `judgePromptName` (judge), `assertion` (json-assert), `confidenceAlertBelow` (default 0.7) |
|
|
@@ -560,7 +562,7 @@ When `prompts.primary` is `local`, `ObservabilityApp.register` mounts admin-only
|
|
|
560
562
|
|
|
561
563
|
| Method | Path | Behavior |
|
|
562
564
|
| --- | --- | --- |
|
|
563
|
-
| GET | `/ai/observability/traces` | Admin list. Query `from`, `to`, `prompt`, `status`, `userId`, `sessionId`, `hasScore`, `sensitive`, `flaggedForDataset`, `page`, `limit`. Body is `{data, page, limit, more, total}` so pagination survives RTK `{data}` unwrap. Each row includes `spanCount` and `scoreCount`. `prompts.length` is the `N prompts` count |
|
|
565
|
+
| GET | `/ai/observability/traces` | Admin list. Query `from`, `to`, `prompt`, `status`, `userId`, `sessionId`, `scope`, `hasScore`, `sensitive`, `flaggedForDataset`, `page`, `limit`. Body is `{data, page, limit, more, total}` so pagination survives RTK `{data}` unwrap. Each row includes `spanCount` and `scoreCount`. `prompts.length` is the `N prompts` count |
|
|
564
566
|
| GET | `/ai/observability/traces/:id` | Span tree (kind, offsets, durations, I/O, cost) plus scores. `errorSummary` is the first span with `status: "error"` |
|
|
565
567
|
| POST | `/ai/observability/traces/:id/scores` | Persist a score and fan out to every `ScoreSink` |
|
|
566
568
|
| POST | `/ai/observability/traces/test-multi-stage` | Admin-only smoke workflow, registered only with the local trace sink. Uses `ObservabilityApp.aiService`, or `requestAiServiceFactory({apiKey})` when the server service is absent (`apiKey` comes from `x-ai-api-key`); answers **503** with the same missing-key title as playground when neither exists. Body `{input?: string}` (defaults to a built-in sample). Runs two `AIService.generateJsonObject` calls with `skipTrace: true` and named JSON output schemas (`obs-test-multi-stage-call-1` / `call-2`), a deterministic local `text-metrics` `TOOL` stage, then a final `generateJsonObject` synthesis against `obs-test-multi-stage-final`; exports exactly one parent trace with ordered child spans `LLM`, `LLM`, `TOOL`, `LLM` under a `CHAIN` root. LLM span input includes `outputSchema`. Returns `{traceId, output, stages[]}` where `output` is the final schema object (`sentence`, `phrase`, `keywords`, `metrics`). Child LLM failures export an error trace then rethrow |
|
|
@@ -638,6 +640,33 @@ Low-level exports (also used by `addGptRoutes` when `langfuseSystemPromptName` i
|
|
|
638
640
|
|
|
639
641
|
Subpath imports for tree-shaking: `@terreno/ai/langfuseClient`, `@terreno/ai/langfuseApp`.
|
|
640
642
|
|
|
643
|
+
## Subpath exports
|
|
644
|
+
|
|
645
|
+
Import these instead of `dist/` paths. Deep `dist/` imports break when a bundler (such as
|
|
646
|
+
Vercel's) inlines them and their bare `@terreno/api` imports no longer resolve. Each
|
|
647
|
+
subpath shares module instances with `@terreno/ai/harness`.
|
|
648
|
+
|
|
649
|
+
| Subpath | Exports |
|
|
650
|
+
| --- | --- |
|
|
651
|
+
| `@terreno/ai/harness` | Harness public API; see [AI harness reference](ai-harness.md) |
|
|
652
|
+
| `@terreno/ai/harness/jobsRunner` | `JobsRunner` |
|
|
653
|
+
| `@terreno/ai/harness/agentLoop` | `createAgentTasks` and the agent turn internals |
|
|
654
|
+
| `@terreno/ai/harness/commit` | `createTaskRecords`, `HarnessModels`, and other commit helpers, for custom commits that write domain records inside a task's fenced commit |
|
|
655
|
+
| `@terreno/ai/harness/events` | `insertMessages`, `resolveStreamingOptions` |
|
|
656
|
+
| `@terreno/ai/harness/internalRuntime` | `internalRuntime` |
|
|
657
|
+
| `@terreno/ai/admin` | `AIAdminApp` |
|
|
658
|
+
| `@terreno/ai/observability/observabilityApp` | `ObservabilityApp` |
|
|
659
|
+
| `@terreno/ai/observability/localPlugin` | `createLocalObservabilityPlugin`, `createLocalObservabilityBundle` |
|
|
660
|
+
| `@terreno/ai/observability/promptStore` | `LocalPromptStore` |
|
|
661
|
+
|
|
662
|
+
The internals subpaths follow the harness's own changes; they carry no compatibility
|
|
663
|
+
promise beyond the release they ship in.
|
|
664
|
+
|
|
665
|
+
The root `@terreno/ai` entry registers the `Project` model on first use
|
|
666
|
+
(`getProjectModel()` or the `Project` export), not on import. `AiApp` uses it for
|
|
667
|
+
`/gpt/projects` by default; an app that owns a model named `Project` passes
|
|
668
|
+
`new AiApp({projects: false})` and does not call `addProjectRoutes`.
|
|
669
|
+
|
|
641
670
|
## FileStorageService
|
|
642
671
|
|
|
643
672
|
Google Cloud Storage helper for uploads referenced by `addFileRoutes`.
|
|
@@ -725,8 +725,16 @@ Master-detail layout. Pass `listViewData` plus `renderListViewItem` for the list
|
|
|
725
725
|
screens the detail replaces the list until the user goes back.
|
|
726
726
|
|
|
727
727
|
`desktopChildrenMinWidth` opts into a minimum pixel width for each desktop child when there
|
|
728
|
-
are one or two children. `childColumnRounding` sets the border radius on each detail
|
|
729
|
-
|
|
728
|
+
are one or two children. `childColumnRounding` sets the border radius on each desktop detail
|
|
729
|
+
pane (defaults to `md`, 4px); the list column is excluded. The pane clips to that radius. The
|
|
730
|
+
column inside it scrolls: content taller than the pane scrolls in the column, and a child with
|
|
731
|
+
`height: "100%"` fills the visible pane so a scroll view inside that child scrolls on its own.
|
|
732
|
+
On a narrow web viewport the pager page is height-bounded to that same visible pane. Its rounded
|
|
733
|
+
corners still clip, and a `height: "100%"` child scrolls inside the page. On the labeled pager,
|
|
734
|
+
previous and next sit in fixed left and right slots in a row under the column. A missing direction
|
|
735
|
+
leaves that slot empty, so the other arrow stays put. The column height is the measured pane minus
|
|
736
|
+
top spacing, the navigation row, bottom spacing, and `bottomNavBarHeight`.
|
|
737
|
+
`narrowViewportChildLabels`
|
|
730
738
|
opts into a labeled full-width pager on the narrow viewport. `narrowBelowWidth` uses that
|
|
731
739
|
viewport when the window is at or below the given pixel width; when omitted, the narrow
|
|
732
740
|
viewport follows `isNarrowViewport()`. These props are web only; the native `SplitPage`
|
package/package.json
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"@modelcontextprotocol/node": "^2.0.0",
|
|
12
12
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
13
13
|
"@sentry/bun": "^10.39.0",
|
|
14
|
-
"@terreno/api": "57.
|
|
15
|
-
"@terreno/blocks": "57.
|
|
14
|
+
"@terreno/api": "57.12.0",
|
|
15
|
+
"@terreno/blocks": "57.12.0",
|
|
16
16
|
"express": "^5.2.1",
|
|
17
17
|
"luxon": "^3.7.2",
|
|
18
18
|
"minisearch": "^7.2.0",
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"test:coverage": "bun run ../scripts/check-coverage.ts"
|
|
106
106
|
},
|
|
107
107
|
"type": "module",
|
|
108
|
-
"version": "57.
|
|
108
|
+
"version": "57.12.0"
|
|
109
109
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Upgrading to 57.12.0
|
|
2
|
+
|
|
3
|
+
**Action required:** Yes
|
|
4
|
+
**Affected packages:** @terreno/ai
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
Harness tasks can record a trace scope, tags, and prompt versions, and `@terreno/ai` publishes official subpath exports. The chat `Project` model registers on first use instead of at import, and harness task HTTP responses no longer include the lease fencing token.
|
|
9
|
+
|
|
10
|
+
## Breaking changes
|
|
11
|
+
|
|
12
|
+
### `Project` is no longer registered on import
|
|
13
|
+
|
|
14
|
+
**What changed**
|
|
15
|
+
|
|
16
|
+
Importing `@terreno/ai` does not register mongoose model `Project`. The exported `Project` is a lazy proxy. The first property access, `new Project(...)`, or `getProjectModel()` registers the model. Statics and `instanceof Project` still work. These do not:
|
|
17
|
+
|
|
18
|
+
- `Project === mongoose.models.Project` (and `Project === mongoose.model("Project")`)
|
|
19
|
+
- `Object.keys(Project)` (always empty)
|
|
20
|
+
- reading `Project.prototype` (throws)
|
|
21
|
+
|
|
22
|
+
`new AiApp()` still mounts `/gpt/projects` and registers the model when those routes load. `new AiApp({projects: false})` skips those routes.
|
|
23
|
+
|
|
24
|
+
**Why**
|
|
25
|
+
|
|
26
|
+
An app that imports `@terreno/ai` and owns its own model named `Project` was hitting a mongoose overwrite error at import time.
|
|
27
|
+
|
|
28
|
+
**Migration**
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Before
|
|
32
|
+
import {Project} from "@terreno/ai";
|
|
33
|
+
|
|
34
|
+
const project = await Project.findExactlyOne({_id: id});
|
|
35
|
+
if (mongoose.models.Project === Project) {
|
|
36
|
+
// identity check
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// After
|
|
40
|
+
import {getProjectModel} from "@terreno/ai";
|
|
41
|
+
|
|
42
|
+
const Project = getProjectModel();
|
|
43
|
+
const project = await Project.findExactlyOne({_id: id});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
To keep a separate app model named `Project`, do not call `getProjectModel()` or touch the `Project` export, and skip the AI project routes:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
new AiApp({aiService, projects: false});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Harness task HTTP responses omit `lease`
|
|
53
|
+
|
|
54
|
+
**What changed**
|
|
55
|
+
|
|
56
|
+
`GET /harness/tasks/:id` no longer includes `lease`. The OpenAPI schema for that response omits it too. Collection list is disabled (`list: []`, 405). The token is still stored on the task document for runners.
|
|
57
|
+
|
|
58
|
+
**Why**
|
|
59
|
+
|
|
60
|
+
`lease.token` is the fencing token that decides which runner may commit. It is not a client field.
|
|
61
|
+
|
|
62
|
+
**Migration**
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Before
|
|
66
|
+
const token = task.lease?.token;
|
|
67
|
+
|
|
68
|
+
// After
|
|
69
|
+
// Do not read lease from the HTTP task. Runners load it on the server.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Regenerate the OpenAPI SDK after upgrading `@terreno/ai` so generated task types drop `lease`.
|
|
73
|
+
|
|
74
|
+
## Deprecations
|
|
75
|
+
|
|
76
|
+
`Project` from `@terreno/ai` still works for `find`, `create`, `new`, and `instanceof`. It is deprecated. Call `getProjectModel()` and use that model. A later major can remove the proxy.
|
|
77
|
+
|
|
78
|
+
## New capabilities
|
|
79
|
+
|
|
80
|
+
- `harness.createTask(def, input, {trace: {scope, tags}, prompts})` writes a scope (tenant, workspace), tags, and prompt versions onto the task's `ObsTrace`. Child tasks and subagents share that trace. The admin trace list filters with `?scope=`.
|
|
81
|
+
- `rt.runAgent` accepts `prompts` and a registry `PromptVersionRef` as `instructions`. It runs `body` and records the version on the trace.
|
|
82
|
+
- Subpath exports: `@terreno/ai/admin`, `@terreno/ai/harness/agentLoop`, `@terreno/ai/harness/commit`, `@terreno/ai/harness/events`, `@terreno/ai/harness/internalRuntime`, `@terreno/ai/observability/observabilityApp`, `@terreno/ai/observability/localPlugin`, `@terreno/ai/observability/promptStore`.
|
|
83
|
+
|
|
84
|
+
## Verification
|
|
85
|
+
|
|
86
|
+
- Code that imported `Project` from `@terreno/ai`: switch identity checks and `mongoose.models.Project` reads to `getProjectModel()`.
|
|
87
|
+
- An app that owns a model named `Project`: pass `projects: false` and do not call `getProjectModel()`.
|
|
88
|
+
- Harness clients: confirm `GET /harness/tasks/:id` JSON has no `lease`, then regenerate the SDK.
|
|
89
|
+
- `bun run compile` and `bun run lint` at the app root.
|
|
@@ -49,9 +49,10 @@ Maintains centralized agent guidance and generates the matching configuration fo
|
|
|
49
49
|
|
|
50
50
|
Rulesync generates native hooks from `.rulesync/hooks.json`. Biome checks changed files
|
|
51
51
|
after edits and staged files before commits. At agent stop,
|
|
52
|
-
`.rulesync/hooks/quality-check.sh` runs
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
`.rulesync/hooks/quality-check.sh` runs `bun install --frozen-lockfile` when
|
|
53
|
+
`node_modules/typescript` or any workspace package link is missing, then runs lint,
|
|
54
|
+
TypeScript compilation, Knip, and dependency-cruiser. It keeps stdout machine-readable
|
|
55
|
+
for each host and prevents retry-triggered Stop hooks from rerunning the commands.
|
|
55
56
|
|
|
56
57
|
Existing findings are ratcheted so the checks reject new debt without requiring unrelated
|
|
57
58
|
cleanup. See [Static analysis](static-analysis.md).
|
|
@@ -402,7 +402,7 @@ do not claim completion until done.
|
|
|
402
402
|
| Workflow | Trigger | Purpose |
|
|
403
403
|
| -------- | ------- | ------- |
|
|
404
404
|
| [`.github/workflows/triage.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/triage.yml) | Issue opened | `status:needs-triage` + `area:*` from package dropdown + `type:*` from Kind when present; `area:*` / `type:*` labels the issue was opened with win, so `roadmap:sync` issues are not asked for an area |
|
|
405
|
-
| [`.github/workflows/roadmap-generate.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/roadmap-generate.yml) | Daily + manual | `roadmap:sync --check` for board drift, then regenerate `ROADMAP.md` from the board and open a pull request when
|
|
405
|
+
| [`.github/workflows/roadmap-generate.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/roadmap-generate.yml) | Daily + manual | `roadmap:sync --check` for board drift, then regenerate `ROADMAP.md` from the board and open a pull request when roadmap content changes. A new **Last updated** timestamp alone does not open a pull request |
|
|
406
406
|
| [`.github/workflows/roadmap-sync.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/roadmap-sync.yml) | Taxonomy files change on `master` + manual | Apply labels and reconcile the board's fields and items |
|
|
407
407
|
| [`.github/workflows/roadmap-reconcile.yml`](https://github.com/TerrenoLabs/terreno/blob/master/.github/workflows/roadmap-reconcile.yml) | IP or task files change on `master` + manual | Advance status from IP headers, add an entry for every new IP, open its tracking issue, push to the board, regenerate `ROADMAP.md` |
|
|
408
408
|
All three share the `roadmap` concurrency group. They write the same board, and interleaving
|
|
@@ -495,4 +495,6 @@ GITHUB_TOKEN=$(gh auth token) TERRENO_PROJECT_NUMBER=... bun run roadmap:generat
|
|
|
495
495
|
```
|
|
496
496
|
|
|
497
497
|
The generator exits non-zero when the project cannot be read, so a bad project number or a
|
|
498
|
-
token without `read:project` fails loudly instead of writing an empty `ROADMAP.md`.
|
|
498
|
+
token without `read:project` fails loudly instead of writing an empty `ROADMAP.md`. When the
|
|
499
|
+
rendered list matches the file already on disk, it keeps that file's **Last updated** stamp
|
|
500
|
+
and does not write. The daily workflow then sees a clean diff and does not open a pull request.
|
|
@@ -254,8 +254,12 @@ the review script so a PR cannot rewrite the reviewer.
|
|
|
254
254
|
Netlify and GCP jobs **validate their context as the first step** and fail,
|
|
255
255
|
before checkout or `bun install`, listing the missing variables. Preview jobs
|
|
256
256
|
halt earlier on fork PRs, where CircleCI withholds contexts. Docker Layer
|
|
257
|
-
Caching is off (200 credits per job). The docs Netlify target
|
|
258
|
-
|
|
257
|
+
Caching is off (200 credits per job). The docs Netlify target builds only the
|
|
258
|
+
current docs tree, disables Docusaurus minification, and pins SSG to one worker
|
|
259
|
+
thread so the build peaks near 3 GB and fits the 4 GB `medium` executor.
|
|
260
|
+
Released versions are not rebuilt: `archive-docs-version` (in `publish-release`,
|
|
261
|
+
`X.Y.0` tags only) builds each one once and uploads it to its GitHub release, and
|
|
262
|
+
`deploy-docs` unpacks those archives under `/<version>/`. See `website/README.md`.
|
|
259
263
|
|
|
260
264
|
`terreno-gcp` uses CircleCI OIDC (`CIRCLE_OIDC_TOKEN_V2`), never a JSON service
|
|
261
265
|
account key. Set `circleci_org_id`, `circleci_project_id`, and
|
|
@@ -7,24 +7,23 @@ describes how they behaved if one is ever re-enabled.
|
|
|
7
7
|
|
|
8
8
|
## Docs site (`docs-deploy.yml`)
|
|
9
9
|
|
|
10
|
-
`Build docs site` is often the longest PR job because
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
`Build docs site` is often the longest PR job because TypeDoc regenerates API
|
|
11
|
+
pages. The site builds only the current docs tree; `website/versioned_docs/` is
|
|
12
|
+
markdown for the MCP server.
|
|
13
13
|
|
|
14
14
|
PR builds set `DOCS_PREVIEW=true` and pass `--no-minify`. Both PR and
|
|
15
15
|
`master` use Docusaurus Faster (Rspack/SWC via `@docusaurus/faster`).
|
|
16
16
|
|
|
17
17
|
| Behavior | PR preview | `master` production |
|
|
18
18
|
| --- | --- | --- |
|
|
19
|
-
| Versioned docs (`57.1.0`, `0.30.0`, …) | Omitted
|
|
19
|
+
| Versioned docs (`57.1.0`, `0.30.0`, …) | Omitted | Prebuilt archives unpacked under `/<version>/` |
|
|
20
20
|
| JS minify | Off | On |
|
|
21
21
|
| Local search index | Skipped | Built |
|
|
22
22
|
| Generated API + component MDX | Restored from cache when `api`/`rtk`/`ui` hashes match | Same cache, then full generate on miss |
|
|
23
23
|
| TypeDoc workspace `tsc` | Once per generate (api+rtk deps share a process) | Same |
|
|
24
24
|
| Rspack cache (`node_modules/.cache/rspack`) | Restored per `pull_request` vs `push` | Separate production key |
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
PR deploy preview.
|
|
26
|
+
Do not rely on `/57.1.0/…` URLs in a PR deploy preview.
|
|
28
27
|
|
|
29
28
|
## Other high-cost jobs
|
|
30
29
|
|
|
@@ -142,7 +142,7 @@ The `task` view passed to `run`: `{id, name, version, input, state, phase, attem
|
|
|
142
142
|
| `Harness.open({registry, runner?, models?, env?, priceMap?, streaming?, testHooks?})` | Checks requirements, rejects a duplicate `name@version` or agent name, ensures collections and indexes exist. `runner` defaults to `new InProcessRunner()`. See [open options](#open-options). |
|
|
143
143
|
| `start()` | Throws, claiming nothing, when any non-terminal task uses a `name@version` missing from the registry (see [Versioning](#versioning)). Then starts the runner. Throws when already started. The runner recovers expired tasks once it owns execution. |
|
|
144
144
|
| `stop()` | Stops claiming work, waits (without a time limit) for every phase in flight while still renewing the owner lease, then releases it. No-op when not started. |
|
|
145
|
-
| `createTask(definition, input, {requestId?, userId?})` | Inserts a `pending` task, its `ObsTrace`, and its root span in one transaction. Wakes the runner. The definition must be in the registry. |
|
|
145
|
+
| `createTask(definition, input, {requestId?, userId?, trace?, prompts?})` | Inserts a `pending` task, its `ObsTrace`, and its root span in one transaction. Wakes the runner. The definition must be in the registry. See [Trace scope and prompt versions](#trace-scope-and-prompt-versions). |
|
|
146
146
|
| `resolveInterrupted(id, {action, reason, result?, userId?})` | Resolve an `interrupted` task. See [resolveInterrupted](#resolveinterrupted). |
|
|
147
147
|
| `abort(id, {reason, userId?})` | Abort a task and every non-terminal task it owns, bottom-up. See [abort](#abort). |
|
|
148
148
|
| `decideApproval(approvalId, {approved, reason?, userId?})` | Approve or reject a pending approval without checking approvers. See [Approvals](#approvals). |
|
|
@@ -158,6 +158,30 @@ create with the same id returns the first task and leaves no extra trace. Reusin
|
|
|
158
158
|
`requestId` for a different task name, or with a different `userId` (including none versus
|
|
159
159
|
some), throws. A soft-deleted task still owns its `requestId`.
|
|
160
160
|
|
|
161
|
+
### Trace scope and prompt versions
|
|
162
|
+
|
|
163
|
+
A task's `ObsTrace` is its run record. `createTask` can label it:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const prompt = await promptRegistry.get({label: "production", name: "intake-summary"});
|
|
167
|
+
await harness.createTask(intakeSummary, {patientId}, {
|
|
168
|
+
prompts: prompt ? [prompt] : [],
|
|
169
|
+
trace: {scope: String(workspaceId), tags: ["nightly"]},
|
|
170
|
+
userId,
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| Option | Type | Description |
|
|
175
|
+
| --- | --- | --- |
|
|
176
|
+
| `trace.scope` | `string` | App-defined scope, such as a tenant or workspace id. Stored as `ObsTrace.scope`, indexed with `created` for per-scope metering (sum `LLM` span `usage.costUsd` by scope and month). Blank throws `harness-invalid-request`. |
|
|
177
|
+
| `trace.tags` | `string[]` | App-defined labels, stored once each as `ObsTrace.tags`. A blank tag throws `harness-invalid-request`. |
|
|
178
|
+
| `prompts` | `{name, version, label?}[]` | Prompt versions the task uses, stored once each on `ObsTrace.prompts`. A `PromptVersionRef` from `promptRegistry.get` fits as-is; its `body` is not stored. A ref without a name, with a non-string `label`, or without a positive integer `version` throws `harness-invalid-request`. |
|
|
179
|
+
|
|
180
|
+
Child tasks and subagents write into the root task's trace, so they share its scope and
|
|
181
|
+
tags. `rt.runAgent` adds its own prompt versions to the same trace (see
|
|
182
|
+
[Subagents](#subagents-rtrunagent)). The admin trace list filters by `scope` and by
|
|
183
|
+
prompt.
|
|
184
|
+
|
|
161
185
|
### open options
|
|
162
186
|
|
|
163
187
|
| Option | Type | Description |
|
|
@@ -306,7 +330,7 @@ where `lease` is `{owner, duration, heartbeat}` (Luxon `Duration`s). With `maxPh
|
|
|
306
330
|
| `rt.signal` | `AbortSignal`. Aborts when the task is aborted (at once in this process, within one heartbeat elsewhere), or when this run loses its lease. Pass it to cancellable calls. |
|
|
307
331
|
| `rt.createTask(definition, input, {background?, key?})` | Create a child task. Returns its id. See [Child tasks and waitForTasks](#child-tasks-and-waitfortasks). |
|
|
308
332
|
| `rt.waitForTasks(ids, {policy?})` | Return child outcomes once they settle; until then the task waits. See [Child tasks and waitForTasks](#child-tasks-and-waitfortasks). |
|
|
309
|
-
| `rt.runAgent(agent, {input, output?, instructions?})` | Run a registered agent as a subagent and return its answer; until it finishes the task waits. See [Subagents (rt.runAgent)](#subagents-rtrunagent). |
|
|
333
|
+
| `rt.runAgent(agent, {input, output?, instructions?, prompts?})` | Run a registered agent as a subagent and return its answer; until it finishes the task waits. See [Subagents (rt.runAgent)](#subagents-rtrunagent). |
|
|
310
334
|
| `rt.memo(key)` / `rt.memo(key, value)` | Read, or first-write, a durable value scoped to this task. See [Memos](#memos). |
|
|
311
335
|
| `rt.waitFor(event, {timeout?})` | Return the payload of the next `event` sent to this task, or `undefined` once `timeout` passes; until then the task waits. See [Events, waits, and sleep](#events-waits-and-sleep). |
|
|
312
336
|
| `rt.sleep(duration)` | Return once `duration` has passed; until then the task waits. See [Events, waits, and sleep](#events-waits-and-sleep). |
|
|
@@ -817,7 +841,9 @@ is not offered to sections, `beforeModelRequest`, or `afterTool` (the tool alrea
|
|
|
817
841
|
`basePath` defaults to `/harness`; it must start with `/` and not end with `/`.
|
|
818
842
|
`heartbeatInterval` (default `{seconds: 15}`, must be positive) paces SSE heartbeats.
|
|
819
843
|
Conversation, task, and approval routes are `modelRouter`s with instance actions, so they
|
|
820
|
-
appear in `/openapi.json
|
|
844
|
+
appear in `/openapi.json`. Each route and its mutations use that collection's tag
|
|
845
|
+
(`harnessconversations`, `harnesstasks`, `harnessapprovals`), so a generated client
|
|
846
|
+
invalidates the list it just changed. The two SSE routes are plain Express routes
|
|
821
847
|
(the listed exception to modelRouter actions) and are not in the OpenAPI spec.
|
|
822
848
|
|
|
823
849
|
### Conversation routes
|
|
@@ -840,7 +866,7 @@ appear in `/openapi.json` (tag `harness`). The two SSE routes are plain Express
|
|
|
840
866
|
|
|
841
867
|
| Method | Path | Permissions | Behavior |
|
|
842
868
|
| --- | --- | --- | --- |
|
|
843
|
-
| GET | `/harness/tasks/:id` | Owner (`userId`) or admin | One task. Others: 403. |
|
|
869
|
+
| GET | `/harness/tasks/:id` | Owner (`userId`) or admin | One task, without `lease` (the fencing token stays on the server). Others: 403. |
|
|
844
870
|
| POST | `/harness/tasks/:id/abort` | Owner or admin | Body `{reason}` (required, non-blank). Runs `harness.abort` with the caller as `userId`; returns the aborted task. |
|
|
845
871
|
| POST | `/harness/tasks/:id/resolveInterrupted` | Admin | Body `{action: "retry" \| "abort" \| "complete", reason, result?}`. Runs `harness.resolveInterrupted` with the caller as `userId`; returns the task. |
|
|
846
872
|
| GET | `/harness/tasks/:id/events` | Owner or admin | SSE for the task and its descendants. See [Event stream (SSE)](#event-stream-sse). |
|
|
@@ -1320,7 +1346,8 @@ summarize: {
|
|
|
1320
1346
|
| --- | --- | --- | --- |
|
|
1321
1347
|
| `input` | `unknown` | Yes | The subagent's user message. A string is sent as-is (blank is rejected); anything else as `JSON.stringify(input)`. |
|
|
1322
1348
|
| `output` | zod schema | No | Structured output. Defaults to the agent's own `output`. |
|
|
1323
|
-
| `instructions` | `string` | No | Replaces the agent's instructions for this subagent conversation only. |
|
|
1349
|
+
| `instructions` | `string` or `PromptVersionRef` | No | Replaces the agent's instructions for this subagent conversation only. A registry prompt (`promptRegistry.get`) runs with its `body`, and its `{name, version, label}` is added to the trace's `prompts`. |
|
|
1350
|
+
| `prompts` | `{name, version, label?}[]` | No | More prompt versions this subagent used, added to the trace's `prompts`. Each version is recorded once per trace. An invalid ref, or an `instructions` prompt without a string `body`, fails the phase with a definition error. |
|
|
1324
1351
|
|
|
1325
1352
|
Returns the final assistant text, or, with a schema, the object the schema parsed. The
|
|
1326
1353
|
generic `rt.runAgent<T>(...)` types the result when the schema comes from the agent.
|
|
@@ -1331,6 +1358,7 @@ What one call does:
|
|
|
1331
1358
|
(`ownership: {kind: "task", id: <caller>}`, `status: "busy"`, agent snapshot with
|
|
1332
1359
|
`instructions` and `outputSchema`), its user message (`seq` 1), and the turn task
|
|
1333
1360
|
(`ownership: {kind: "task", id: <caller>}`) with an `AGENT` span named after the agent.
|
|
1361
|
+
Prompt versions from `instructions` and `prompts` are added to the trace's `prompts`.
|
|
1334
1362
|
2. Waits with `rt.waitForTasks([turn])`: the caller commits `waiting` and the phase stops.
|
|
1335
1363
|
3. When the turn settles the caller wakes and the phase re-runs from its checkpoint. The
|
|
1336
1364
|
same call finds the same turn and conversation (step 1 writes nothing) and returns.
|
|
@@ -91,7 +91,7 @@ src/
|
|
|
91
91
|
- **Plugins:** `AiApp`, `LangfuseApp`
|
|
92
92
|
- **Service:** `AIService`, `TemperaturePresets`, `FileStorageService`, `MCPService`,
|
|
93
93
|
`getMCPTools`
|
|
94
|
-
- **Models:** `AIRequest`, `GptHistory`, `FileAttachment`, `Project`
|
|
94
|
+
- **Models:** `AIRequest`, `GptHistory`, `FileAttachment`, `Project` (lazy; prefer `getProjectModel()`)
|
|
95
95
|
- **Routes:** `addGptRoutes`, `addGptHistoryRoutes`, `addAiRequestsExplorerRoutes`, `addFileRoutes`, `addProjectRoutes`, `addMcpRoutes`
|
|
96
96
|
- **Structured output:** `parseAiJson`, `normalizeLlmJsonTextForStructuredOutput`, re-exported `Output`, `jsonSchema`, `JSONValue`, `FlexibleSchema` from `ai`
|
|
97
97
|
- **Langfuse:** `initLangfuseClient`, `getLangfuseClient`, `shutdownLangfuseClient`, `compilePrompt`, `createPrompt`, `getPrompt`, `createTelemetryConfig`, `preparePromptForAI`, `initTracing`, `shutdownTracing`, `LangfuseCache`, cache helpers
|
|
@@ -249,7 +249,8 @@ Metadata for files stored in GCS.
|
|
|
249
249
|
|
|
250
250
|
### Project
|
|
251
251
|
|
|
252
|
-
GPT project with persistent context and memories.
|
|
252
|
+
GPT project with persistent context and memories. Registered lazily: `getProjectModel()`
|
|
253
|
+
(or any use of the deprecated `Project` export) registers it.
|
|
253
254
|
|
|
254
255
|
| Field | Type | Description |
|
|
255
256
|
|-------|------|-------------|
|
|
@@ -440,6 +441,7 @@ new AiApp({
|
|
|
440
441
|
| `maxSteps` | Max tool-calling steps (default 5) |
|
|
441
442
|
| `titleModelId` | Cheaper model for conversation title generation |
|
|
442
443
|
| `openApiOptions` | Passed to route OpenAPI builders |
|
|
444
|
+
| `projects` | Default `true`. `false` skips the `/gpt/projects` routes, so `AiApp` never registers the `Project` model |
|
|
443
445
|
|
|
444
446
|
## LangfuseApp plugin
|
|
445
447
|
|
|
@@ -508,7 +510,7 @@ server `AIService` when configured, otherwise a request-scoped service built fro
|
|
|
508
510
|
| `ObsPrompt` | Named prompt (`name` unique) with `folder` and `tags[]` |
|
|
509
511
|
| `ObsPromptVersion` | Immutable `vN` body, `variables[]`, schemas, `sensitive` (default false), `config` |
|
|
510
512
|
| `ObsPromptLabel` | Movable labels; unique `(promptId, label)` |
|
|
511
|
-
| `ObsTrace` | Root trace: user, session, status, `errorSummary`, `sensitive`, `prompts[]`, usage |
|
|
513
|
+
| `ObsTrace` | Root trace: user, session, status, `errorSummary`, `sensitive`, `prompts[]`, `scope`, `tags[]`, usage |
|
|
512
514
|
| `ObsSpan` | Nested span with `kind`, `status`, optional `error`, offsets, usage |
|
|
513
515
|
| `ObsScore` | Scores on a trace/span; many per trace, **no unique index** |
|
|
514
516
|
| `ObsEvaluator` | Evaluator: `type` (`human` \| `llm-judge` \| `json-assert`), `target`, `dimensions[]`, `runModes`, `instructions`, `judgePromptName` (judge), `assertion` (json-assert), `confidenceAlertBelow` (default 0.7) |
|
|
@@ -560,7 +562,7 @@ When `prompts.primary` is `local`, `ObservabilityApp.register` mounts admin-only
|
|
|
560
562
|
|
|
561
563
|
| Method | Path | Behavior |
|
|
562
564
|
| --- | --- | --- |
|
|
563
|
-
| GET | `/ai/observability/traces` | Admin list. Query `from`, `to`, `prompt`, `status`, `userId`, `sessionId`, `hasScore`, `sensitive`, `flaggedForDataset`, `page`, `limit`. Body is `{data, page, limit, more, total}` so pagination survives RTK `{data}` unwrap. Each row includes `spanCount` and `scoreCount`. `prompts.length` is the `N prompts` count |
|
|
565
|
+
| GET | `/ai/observability/traces` | Admin list. Query `from`, `to`, `prompt`, `status`, `userId`, `sessionId`, `scope`, `hasScore`, `sensitive`, `flaggedForDataset`, `page`, `limit`. Body is `{data, page, limit, more, total}` so pagination survives RTK `{data}` unwrap. Each row includes `spanCount` and `scoreCount`. `prompts.length` is the `N prompts` count |
|
|
564
566
|
| GET | `/ai/observability/traces/:id` | Span tree (kind, offsets, durations, I/O, cost) plus scores. `errorSummary` is the first span with `status: "error"` |
|
|
565
567
|
| POST | `/ai/observability/traces/:id/scores` | Persist a score and fan out to every `ScoreSink` |
|
|
566
568
|
| POST | `/ai/observability/traces/test-multi-stage` | Admin-only smoke workflow, registered only with the local trace sink. Uses `ObservabilityApp.aiService`, or `requestAiServiceFactory({apiKey})` when the server service is absent (`apiKey` comes from `x-ai-api-key`); answers **503** with the same missing-key title as playground when neither exists. Body `{input?: string}` (defaults to a built-in sample). Runs two `AIService.generateJsonObject` calls with `skipTrace: true` and named JSON output schemas (`obs-test-multi-stage-call-1` / `call-2`), a deterministic local `text-metrics` `TOOL` stage, then a final `generateJsonObject` synthesis against `obs-test-multi-stage-final`; exports exactly one parent trace with ordered child spans `LLM`, `LLM`, `TOOL`, `LLM` under a `CHAIN` root. LLM span input includes `outputSchema`. Returns `{traceId, output, stages[]}` where `output` is the final schema object (`sentence`, `phrase`, `keywords`, `metrics`). Child LLM failures export an error trace then rethrow |
|
|
@@ -638,6 +640,33 @@ Low-level exports (also used by `addGptRoutes` when `langfuseSystemPromptName` i
|
|
|
638
640
|
|
|
639
641
|
Subpath imports for tree-shaking: `@terreno/ai/langfuseClient`, `@terreno/ai/langfuseApp`.
|
|
640
642
|
|
|
643
|
+
## Subpath exports
|
|
644
|
+
|
|
645
|
+
Import these instead of `dist/` paths. Deep `dist/` imports break when a bundler (such as
|
|
646
|
+
Vercel's) inlines them and their bare `@terreno/api` imports no longer resolve. Each
|
|
647
|
+
subpath shares module instances with `@terreno/ai/harness`.
|
|
648
|
+
|
|
649
|
+
| Subpath | Exports |
|
|
650
|
+
| --- | --- |
|
|
651
|
+
| `@terreno/ai/harness` | Harness public API; see [AI harness reference](ai-harness.md) |
|
|
652
|
+
| `@terreno/ai/harness/jobsRunner` | `JobsRunner` |
|
|
653
|
+
| `@terreno/ai/harness/agentLoop` | `createAgentTasks` and the agent turn internals |
|
|
654
|
+
| `@terreno/ai/harness/commit` | `createTaskRecords`, `HarnessModels`, and other commit helpers, for custom commits that write domain records inside a task's fenced commit |
|
|
655
|
+
| `@terreno/ai/harness/events` | `insertMessages`, `resolveStreamingOptions` |
|
|
656
|
+
| `@terreno/ai/harness/internalRuntime` | `internalRuntime` |
|
|
657
|
+
| `@terreno/ai/admin` | `AIAdminApp` |
|
|
658
|
+
| `@terreno/ai/observability/observabilityApp` | `ObservabilityApp` |
|
|
659
|
+
| `@terreno/ai/observability/localPlugin` | `createLocalObservabilityPlugin`, `createLocalObservabilityBundle` |
|
|
660
|
+
| `@terreno/ai/observability/promptStore` | `LocalPromptStore` |
|
|
661
|
+
|
|
662
|
+
The internals subpaths follow the harness's own changes; they carry no compatibility
|
|
663
|
+
promise beyond the release they ship in.
|
|
664
|
+
|
|
665
|
+
The root `@terreno/ai` entry registers the `Project` model on first use
|
|
666
|
+
(`getProjectModel()` or the `Project` export), not on import. `AiApp` uses it for
|
|
667
|
+
`/gpt/projects` by default; an app that owns a model named `Project` passes
|
|
668
|
+
`new AiApp({projects: false})` and does not call `addProjectRoutes`.
|
|
669
|
+
|
|
641
670
|
## FileStorageService
|
|
642
671
|
|
|
643
672
|
Google Cloud Storage helper for uploads referenced by `addFileRoutes`.
|
|
@@ -725,8 +725,16 @@ Master-detail layout. Pass `listViewData` plus `renderListViewItem` for the list
|
|
|
725
725
|
screens the detail replaces the list until the user goes back.
|
|
726
726
|
|
|
727
727
|
`desktopChildrenMinWidth` opts into a minimum pixel width for each desktop child when there
|
|
728
|
-
are one or two children. `childColumnRounding` sets the border radius on each detail
|
|
729
|
-
|
|
728
|
+
are one or two children. `childColumnRounding` sets the border radius on each desktop detail
|
|
729
|
+
pane (defaults to `md`, 4px); the list column is excluded. The pane clips to that radius. The
|
|
730
|
+
column inside it scrolls: content taller than the pane scrolls in the column, and a child with
|
|
731
|
+
`height: "100%"` fills the visible pane so a scroll view inside that child scrolls on its own.
|
|
732
|
+
On a narrow web viewport the pager page is height-bounded to that same visible pane. Its rounded
|
|
733
|
+
corners still clip, and a `height: "100%"` child scrolls inside the page. On the labeled pager,
|
|
734
|
+
previous and next sit in fixed left and right slots in a row under the column. A missing direction
|
|
735
|
+
leaves that slot empty, so the other arrow stays put. The column height is the measured pane minus
|
|
736
|
+
top spacing, the navigation row, bottom spacing, and `bottomNavBarHeight`.
|
|
737
|
+
`narrowViewportChildLabels`
|
|
730
738
|
opts into a labeled full-width pager on the narrow viewport. `narrowBelowWidth` uses that
|
|
731
739
|
viewport when the window is at or below the given pixel width; when omitted, the narrow
|
|
732
740
|
viewport follows `isNarrowViewport()`. These props are web only; the native `SplitPage`
|