@terreno/mcp 57.9.0 → 57.11.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.10.1.md +54 -0
- package/dist/docs/upgrades/57.11.0.md +80 -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 +544 -0
- package/dist/docs/versioned/next/explanation/example-coverage.md +3 -1
- package/dist/docs/versioned/next/explanation/local-first-data.md +1 -0
- 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 +7 -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/call-external-apis.md +1 -1
- 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/migrate-rtk-to-syncdb.md +31 -1
- package/dist/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
- package/dist/docs/versioned/next/how-to/preview-demo.md +7 -3
- package/dist/docs/versioned/next/how-to/run-harness-on-multiple-instances.md +93 -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 +1974 -0
- package/dist/docs/versioned/next/reference/ai.md +251 -25
- package/dist/docs/versioned/next/reference/api.md +110 -2
- package/dist/docs/versioned/next/reference/blocks.md +178 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +2 -1
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +11 -0
- package/dist/docs/versioned/next/reference/mcp-server.md +17 -0
- package/dist/docs/versioned/next/reference/syncdb.md +156 -6
- package/dist/docs/versioned/next/reference/test.md +11 -2
- package/dist/docs/versioned/next/reference/ui.md +331 -6
- 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 +185 -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/upgrades/57.11.0.md +80 -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 +544 -0
- package/src/docs/versioned/next/explanation/example-coverage.md +3 -1
- package/src/docs/versioned/next/explanation/local-first-data.md +1 -0
- 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 +7 -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/call-external-apis.md +1 -1
- 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/migrate-rtk-to-syncdb.md +31 -1
- package/src/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
- package/src/docs/versioned/next/how-to/preview-demo.md +7 -3
- package/src/docs/versioned/next/how-to/run-harness-on-multiple-instances.md +93 -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 +1974 -0
- package/src/docs/versioned/next/reference/ai.md +251 -25
- package/src/docs/versioned/next/reference/api.md +110 -2
- package/src/docs/versioned/next/reference/blocks.md +178 -0
- package/src/docs/versioned/next/reference/environment-variables.md +2 -1
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +11 -0
- package/src/docs/versioned/next/reference/mcp-server.md +17 -0
- package/src/docs/versioned/next/reference/syncdb.md +156 -6
- package/src/docs/versioned/next/reference/test.md +11 -2
- package/src/docs/versioned/next/reference/ui.md +331 -6
- 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 +185 -0
|
@@ -1,78 +1,146 @@
|
|
|
1
|
-
# Implementation Plan: AI observability (Langfuse
|
|
1
|
+
# Implementation Plan: AI observability (Langfuse replacement, pluggable)
|
|
2
2
|
|
|
3
|
-
**Status:**
|
|
3
|
+
**Status:** Approved
|
|
4
4
|
**Roadmap:** Area=`ai`, Target=`Future`, Impact=`Feature`
|
|
5
5
|
**Created:** 2026-08-24
|
|
6
|
+
**Updated:** 2026-10-02 (domain-neutral RBAC and prompt relationship hub approved)
|
|
6
7
|
**Owner:** unassigned
|
|
7
8
|
**Branch:** `cursor/ai-observability-ip-64ca`
|
|
8
9
|
**Task list:** [docs/tasks/ai-observability.md](../tasks/ai-observability.md)
|
|
10
|
+
**Design:** [AI Observability Prototype](https://claude.ai/design/p/36eec001-f2f0-4161-b60d-20dace4d0a9c?file=AI+Observability+Prototype.dc.html) — 21 screens, normative for UI and for the field/badge vocabulary below
|
|
9
11
|
**Linear:** none
|
|
10
12
|
**Roadmap issue:** none yet (handoff after Approved)
|
|
11
13
|
|
|
12
|
-
**Depends on:** current `@terreno/ai` (`AIService`, `AIRequest`, `LangfuseApp`, `AIAdminApp`).
|
|
14
|
+
**Depends on:** current `@terreno/ai` (`AIService`, `AIRequest`, `LangfuseApp`, `AIAdminApp`).
|
|
13
15
|
|
|
14
|
-
**Related:** [terreno-langfuse-integration.md](./terreno-langfuse-integration.md) (`LangfuseApp` stays). [FH-48](https://linear.app/flourish-health/issue/FH-48/ai-improvements)
|
|
16
|
+
**Related:** [terreno-langfuse-integration.md](./terreno-langfuse-integration.md) (`LangfuseApp` stays). [FH-48](https://linear.app/flourish-health/issue/FH-48/ai-improvements).
|
|
15
17
|
|
|
16
18
|
## Goal
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
Replace Langfuse as the **primary** AI observability product for Terreno apps: prompt versioning with a
|
|
21
|
+
production label, nested traces with per-span cost, multidimensional evaluators, datasets, experiments with
|
|
22
|
+
gates, and a **human review queue** — all local (Mongo) with an operator UI in `admin-frontend`, and Langfuse
|
|
23
|
+
demoted to an optional **extra sink** an app can keep.
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
The design prototype is the destination. Its 21 screens divide cleanly into three shippable products:
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
| Phase | Ships | Design screens |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| **1 — MVP** | Prompt library + production label, tracing, human review queue | Prompts, Prompt editor (+ Playground), Traces, Trace detail, Review queue, Review item |
|
|
30
|
+
| **2** | Evaluation loop: evaluators, datasets, experiments, in-app feedback | Evaluators (+ detail, new), Datasets (+ detail), Experiments (+ new, results) |
|
|
31
|
+
| **3** | Analytics + pluggable backends: Langfuse/OTel fan-out | Sessions (+ timeline), Users (+ AI profile), Costs, status chip primaries, Open in Langfuse |
|
|
32
|
+
|
|
33
|
+
Phase 1 alone must let a team stop copying prompt strings into route code, see what every AI call did and cost,
|
|
34
|
+
and get a clinician's judgement recorded against a real output. Phases 2 and 3 are additive — no Phase 1
|
|
35
|
+
schema or route is rewritten later.
|
|
26
36
|
|
|
27
37
|
## Non-goals
|
|
28
38
|
|
|
29
39
|
- Removing or rewriting `LangfuseApp`.
|
|
30
40
|
- `admin-spa` screens (spa already embeds `admin-frontend` widgets).
|
|
31
41
|
- First-class Braintrust, Helicone, LangSmith, Weave, Opik, Portkey adapters (document how they fit; do not code stubs).
|
|
32
|
-
- Langfuse **annotation queues** as `reviewQueue.primary` (local Mongo queue only
|
|
42
|
+
- Langfuse **annotation queues** as `reviewQueue.primary` (local Mongo queue only).
|
|
33
43
|
- Dual-write of prompt versions, datasets, or review items.
|
|
34
|
-
- Retention / redaction / retry **jobs** (full I/O stored;
|
|
44
|
+
- Retention / redaction / retry **jobs** (full I/O stored; see Risks).
|
|
35
45
|
- Replacing `AIRequest` (cheap call log remains).
|
|
36
46
|
- Pixel-clone of Langfuse Cloud chrome.
|
|
47
|
+
- A "minimum datapoints" calculator. Operators keep their volume spreadsheet; Terreno stores provenance/tags so they can filter for balance.
|
|
48
|
+
- Auto-promoting a prompt to `production` when experiment gates pass. Promote stays an explicit admin action.
|
|
37
49
|
|
|
38
50
|
## Decisions
|
|
39
51
|
|
|
52
|
+
### Carried forward
|
|
53
|
+
|
|
40
54
|
| ID | Question | Choice |
|
|
41
55
|
| --- | --- | --- |
|
|
42
|
-
| Q1 | System of record | Mongo **local plugin** + **pluggable** backends
|
|
56
|
+
| Q1 | System of record | Mongo **local plugin** + **pluggable** backends, not wrap-only |
|
|
43
57
|
| Q2 | Package | Grow `@terreno/ai` |
|
|
44
|
-
| Q3 | Existing Langfuse | **Two plugins**: keep `LangfuseApp`; add
|
|
58
|
+
| Q3 | Existing Langfuse | **Two plugins**: keep `LangfuseApp`; add `ObservabilityApp` |
|
|
45
59
|
| Q4 | Operator UI | **`admin-frontend` only** |
|
|
46
|
-
| Q5 | IP shape | **One IP**, full destination, **phased tasks** |
|
|
47
|
-
| Q6 / Q11 | Dual receive | **Traces + scores + OTel** fan-out. Not prompts/datasets/queue |
|
|
48
60
|
| Q7 | SDK | Capability interfaces + `capabilities` (comms-style) |
|
|
49
|
-
| Q8 |
|
|
50
|
-
| Q9 | PHI / I/O | Persist **full I/O**;
|
|
51
|
-
|
|
|
52
|
-
| Q12 / Q16 / Q21 | Primaries | Four flags: `prompts`, `datasets`, `experiments`, `reviewQueue` |
|
|
61
|
+
| Q8 | Adapters | `local` (full) + `langfuse` (mapped, phase 3) + `otel` TraceSink (phase 3) |
|
|
62
|
+
| Q9 | PHI / I/O | Persist **full I/O**; no retention job in this IP |
|
|
63
|
+
| Q12/Q16/Q21 | Primaries | Four flags: `prompts`, `datasets`, `experiments`, `reviewQueue` |
|
|
53
64
|
| Q22 | Mix rule | `experiments.primary` **must equal** `datasets.primary` (fail config) |
|
|
54
|
-
| Q23 | Defaults | All four primaries **`local
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
| Q14 / Q17 | Experiments | Runner follows **`experiments.primary`**: local → always **`BackgroundTask`**; Langfuse → Langfuse experiment API |
|
|
65
|
+
| Q23 | Defaults | All four primaries **`local`**; Langfuse/OTel are extra **sinks** |
|
|
66
|
+
| Q25 | Review queue | **Local-only**; `reviewQueue.primary: "langfuse"` is a config error |
|
|
67
|
+
| Q13 | Secondary down | Primary write **succeeds**; secondary sink errors **logged, never thrown** |
|
|
68
|
+
| Q14/Q17 | Experiments | Local → always **`BackgroundTask`**; Langfuse → Langfuse experiment API |
|
|
59
69
|
| Q15 | Mongo | **New collections**; keep `AIRequest` |
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
70
|
+
| Q19 | Cost | `inputTokens` / `outputTokens` / `model`; USD from config price map (missing model → **tokens only**, never `$0`) |
|
|
71
|
+
| Q20 | Instrumentation | If observability is registered, `AIService` emits traces on every `generate*` (opt-out per call) |
|
|
72
|
+
| Q31 | Deploy plumbing | `AIService` `promptName` + `promptLabel` (default `"production"`) resolves via `PromptRegistry` before generate |
|
|
73
|
+
|
|
74
|
+
### New — from the design prototype
|
|
75
|
+
|
|
76
|
+
| ID | Question | Choice |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| Q34 | Prompt organization | Prompts live in a **folder** (`clinical-notes`, `safety`, …) plus free tags. Folder rail with counts is the primary filter; search matches name and tags |
|
|
79
|
+
| Q35 | Prompt list columns | Name (`folder/name`), type, latest, production, last used, **calls 7d**, **cost 7d** — the usage columns are a rollup over `ObsTrace`, computed on read with a short cache, not denormalized onto the prompt |
|
|
80
|
+
| Q36 | Version immutability affordance | The editor never edits in place: the only save action is **"Save as vN+1"**, and **"Set vN as production"** is a separate confirm modal naming the outgoing version. One production label per prompt |
|
|
81
|
+
| Q37 | Playground | Compiles the selected version with operator-supplied variables and runs **one** `AIService` call. Shows compiled messages, output, latency / tokens / cost, and **"Save this run to dataset"** (phase 2 wires the save) |
|
|
82
|
+
| Q38 | Sensitive content | Traces and spans carry `sensitive: boolean` (set per call, or inherited from the prompt version). Drives the `sensitive` badge, collapsed-by-default I/O disclosures, and the bulk-add warning. **Never** shown on the Users screen |
|
|
83
|
+
| Q39 | Evaluator shape | An evaluator declares `target` (`generation span` \| `full trace` \| `dataset item`) and **typed dimensions** `{key, dataType: numeric\|boolean\|categorical, range, required}`. Scores written by it carry exactly those keys |
|
|
84
|
+
| Q40 | Judge schema check | For `llm-judge`, the judge prompt version's `outputSchema` **must declare every required dimension**. Validated on create and on save, with the failing dimension named in the error. Not a runtime-only surprise |
|
|
85
|
+
| Q41 | Run modes | Per-evaluator, not global: `liveSampleRate` (0–100%, **default 0**), `availableInExperiments`, `allowManualRun`. Human evaluators **cannot** sample live traffic (rejected at save). Replaces the single global `AI_OBS_SAMPLE_RATE`, which becomes a **ceiling** |
|
|
86
|
+
| Q42 | Dataset provenance | Two operator-facing states: **human-annotated** (a person wrote or approved the expected output) and **auto-captured** (a rule or bulk add wrote it, unreviewed). Backed by `origin` (`manual` \| `trace` \| `synthetic`) + `proofread` + `annotatedBy`. Dataset detail tabs: All / Human / Auto / **Needs review** |
|
|
87
|
+
| Q43 | Sensitive → needs review | Adding a `sensitive` trace to a dataset always lands the item as auto-captured / needs review, whatever the bulk action was. The bulk bar states the count before the operator confirms |
|
|
88
|
+
| Q44 | Dataset input schema | A dataset may bind to a prompt (`inputSchemaPromptName`); items validate against that prompt version's `inputSchema` on add, and the detail header says so |
|
|
89
|
+
| Q45 | Experiment shape | Compare **2–3 prompt versions** on one dataset, not a strict A/B. Results show gate tiles, an outliers list, and a per-item side-by-side table with **failed items floated to the top** |
|
|
90
|
+
| Q46 | Promote gating | **Promote is blocked while any gate fails** — the button renders disabled with the failing gate named. Passing gates do not auto-promote; promote stays an explicit click plus a confirm modal |
|
|
91
|
+
| Q47 | Experiment wizard | Four steps — Dataset → Prompt versions → Evaluators → Review & run — with a completed-step rail, per-evaluator cost hints, and a **pre-run estimate** (generations, USD, wall clock) on the last step |
|
|
92
|
+
| Q48 | Review item presentation | The review screen is plain-language, not a JSON viewer: **"What the AI was given"** and **"What the AI wrote"** panels, per-field labels and reviewer notes, long fields collapsed with a word count, structured outputs as key/value rows, raw JSON behind a disclosure |
|
|
93
|
+
| Q49 | Review field metadata | Those labels and notes come from the prompt version: `variables[].label` / `.reviewerNote` for the given panel, `outputSchema` field titles + `outputFieldNotes` for the wrote panel. Falls back to raw keys when absent |
|
|
94
|
+
| Q50 | Review score form | Generated from the evaluator's dimensions: numeric → slider with end labels, boolean → Pass/Fail, categorical → pills. Plus an optional comment. Actions: **Submit & next**, Skip, Assign to me |
|
|
95
|
+
| Q51 | Queue flow | Tabs Pending / In progress / Done / Skipped with counts; oldest-first; **"Start reviewing — oldest first"**; item screen shows "Item N of M pending" with prev/next; explicit empty state naming both intake paths |
|
|
96
|
+
| Q52 | Queue intake | Two paths in phase 1: **"Send to human review"** on a trace (single and bulk) and manual assignment. Queue intake requires a human evaluator; its dimensions become reviewer score fields and its instructions guide scoring. Phase 2 adds experiments and in-app feedback flags |
|
|
97
|
+
| Q53 | Chrome | Breadcrumb `Admin / AI Observability / <Section> / <leaf>` on every screen, plus a status chip: local plugin state · Langfuse sink health · active primaries. **Open in Langfuse** appears only when the Langfuse plugin is registered (phase 3) |
|
|
98
|
+
| Q54 | Nav | One `AI Observability` sidebar group. **Review queue is hidden when the local plugin is off.** Existing `AI Requests` stays in its own group, untouched |
|
|
99
|
+
| Q55 | Evaluator templates | Seeded: `correctness`, `hallucination`, `helpfulness`, `toxicity` (llm-judge), `schema-assert` (json-assert, validates against the prompt version `outputSchema`, free/in-process), and one **human** review template. Custom evaluators still allowed |
|
|
100
|
+
| Q56 | Trace errors | A failed trace shows `error` status plus a one-line human cause on the row (`failed at span 3 of 4 — escalation-notify timed out`), derived from the first failed span |
|
|
101
|
+
| Q57 | Multi-prompt traces | A trace touching more than one prompt shows `N prompts` in the prompt column rather than one `name@version` |
|
|
102
|
+
| Q58 | Domain language | Observability workflows stay domain-neutral. Consumers supply folder meanings, tags, roles, and descriptions |
|
|
103
|
+
| Q59 | Approval workflow | Prompt approval remains outside Terreno for now. Terreno does not add an approval state or two-person promotion rule |
|
|
104
|
+
| Q60 | RBAC shape | Separate resources for `aiPrompt`, `aiTrace`, `aiReview`, `aiDataset`, `aiExperiment`, and `aiEvaluator`; consumers compose roles from their actions |
|
|
105
|
+
| Q61 | Read-only experience | A caller with read access sees the screen and data, while denied actions are hidden. A denied screen is omitted from admin config/navigation |
|
|
106
|
+
| Q62 | Prompt actions | `list`, `read`, `create`, `update`, `promote`, `playground` |
|
|
107
|
+
| Q63 | Other actions | Traces: `list`, `read`; reviews: `list`, `read`, `score`, `assign`; datasets/evaluators: CRUD; experiments: `list`, `read`, `create`, `promote` |
|
|
108
|
+
| Q64 | Existing roles | Auditor receives read/list through the existing read-only sentinel; Super Admin receives all; Admin is seeded with full observability access |
|
|
109
|
+
| Q65 | Existing admin users | Legacy `admin: true` remains a full-access fallback. RBAC-only users receive exactly their granted observability actions |
|
|
110
|
+
| Q66 | Prompt description | Prompt metadata gains an optional consumer-authored description. Folder remains consumer-defined |
|
|
111
|
+
| Q67 | Prompt hub | Prompt detail shows description plus related traces and experiments. Live relationships are derived from stored prompt name/version evidence rather than manually maintained usage records |
|
|
63
112
|
|
|
64
113
|
### Competitor coverage (why the SDK is split)
|
|
65
114
|
|
|
66
115
|
| Backend | TraceSink | PromptRegistry | Dataset / experiment | ScoreSink | ReviewQueue |
|
|
67
116
|
| --- | --- | --- | --- | --- | --- |
|
|
68
|
-
| Local Mongo | yes | yes | yes | yes | yes (
|
|
69
|
-
| Langfuse | yes | yes | yes | yes |
|
|
70
|
-
| OTLP / OpenInference | yes | no | no | no | no |
|
|
117
|
+
| Local Mongo | yes (P1) | yes (P1) | yes (P2) | yes (P1) | yes (P1) |
|
|
118
|
+
| Langfuse | yes (P3) | yes (P3) | yes (P3) | yes (P3) | never |
|
|
119
|
+
| OTLP / OpenInference | yes (P3) | no | no | no | no |
|
|
71
120
|
| Phoenix / Laminar / Datadog | via OTel | no | no | no | no |
|
|
72
121
|
| Helicone / Portkey | later (gateway) | no | no | no | no |
|
|
73
122
|
| Braintrust / LangSmith | later | later | later | later | later |
|
|
74
123
|
|
|
75
|
-
Traces use **OpenTelemetry + OpenInference** span kinds (`LLM`, `AGENT`, `CHAIN`, `TOOL`, `
|
|
124
|
+
Traces use **OpenTelemetry + OpenInference** span kinds (`LLM`, `AGENT`, `CHAIN`, `TOOL`, `RETRIEVER`,
|
|
125
|
+
`EVALUATOR`). That is the honest "works with competitors" layer. Prompt/dataset/queue APIs stay vendor CRUD
|
|
126
|
+
behind Terreno interfaces.
|
|
127
|
+
|
|
128
|
+
## Operator workflow (AI Feature Development SOP)
|
|
129
|
+
|
|
130
|
+
Normative operator path; how-to lives in [ai-feature-development.md](../how-to/ai-feature-development.md).
|
|
131
|
+
|
|
132
|
+
| SOP step | Operator action | Terreno provides | Phase |
|
|
133
|
+
| --- | --- | --- | --- |
|
|
134
|
+
| 1 Gather gold set | Live traces; bulk add to dataset; manual items; AI drafts | Tracing (P1); add-from-trace, import JSON, synthetic generate (P2) | 1 / 2 |
|
|
135
|
+
| 2 Label expected output | Hand-label outcomes; input/output schema | Dataset items, provenance, needs-review tab | 2 |
|
|
136
|
+
| 3 Create prompt | Product + optional AI draft | Prompt editor + playground; immutable versions | **1** |
|
|
137
|
+
| 4 Evaluators | Flags → correctness; open-ended → hallucination, helpfulness, toxicity, custom | Template install + evaluator builder | 2 (human type in **1**) |
|
|
138
|
+
| 5 Experiments | Run versions on a dataset; gates; outliers; iterate | Experiment wizard + results | 2 |
|
|
139
|
+
| 6 Deploy | Label winning version `production` | Label API + `promptName`/`promptLabel` resolution | **1** |
|
|
140
|
+
| 7 Live tracing + feedback | Watch production; thumbs and outcome class from the app | Auto-trace (P1); `POST feedback` (P2) | 1 / 2 |
|
|
141
|
+
| 8 Deficient traces → dataset | Filter weak traces; add; relabel; repeat 5–8 | Trace filters (P1); bulk add-to-dataset (P2) | 1 / 2 |
|
|
142
|
+
|
|
143
|
+
Phase 1 covers SOP steps 3, 6, 7 (tracing half) and the human-judgement loop; phase 2 closes 1, 2, 4, 5, 8.
|
|
76
144
|
|
|
77
145
|
## Architecture
|
|
78
146
|
|
|
@@ -96,7 +164,8 @@ ScoreSinks[] → fan-out with traces
|
|
|
96
164
|
| Telemetry | all registered `TraceSink` / `ScoreSink` | fan-out | **local** if local plugin on; else Langfuse |
|
|
97
165
|
| Control | primary per capability | **one** writer | that primary |
|
|
98
166
|
|
|
99
|
-
Mirrors `@terreno/comms`: interfaces in `ai/src/observability/types.ts`; adapters
|
|
167
|
+
Mirrors `@terreno/comms`: interfaces in `ai/src/observability/types.ts`; adapters add no vendor SDKs to core
|
|
168
|
+
beyond what `LangfuseApp` already uses. The OTLP exporter is an optional adapter file.
|
|
100
169
|
|
|
101
170
|
### Public types (normative)
|
|
102
171
|
|
|
@@ -129,6 +198,8 @@ interface ObservabilityPlugin {
|
|
|
129
198
|
traceSink?: TraceSink;
|
|
130
199
|
}
|
|
131
200
|
|
|
201
|
+
type SpanKind = "AGENT" | "CHAIN" | "EVALUATOR" | "LLM" | "RETRIEVER" | "TOOL";
|
|
202
|
+
|
|
132
203
|
interface TraceRecord {
|
|
133
204
|
id: string;
|
|
134
205
|
name: string;
|
|
@@ -136,214 +207,461 @@ interface TraceRecord {
|
|
|
136
207
|
endedAt?: string;
|
|
137
208
|
userId?: string;
|
|
138
209
|
sessionId?: string;
|
|
139
|
-
status: "
|
|
210
|
+
status: "error" | "ok";
|
|
211
|
+
/** First failing span rendered as one human line on the trace row (Q56). */
|
|
212
|
+
errorSummary?: string;
|
|
140
213
|
input?: unknown;
|
|
141
214
|
output?: unknown;
|
|
142
215
|
metadata?: Record<string, unknown>;
|
|
216
|
+
/** Collapses I/O disclosures and forces dataset items to needs-review (Q38, Q43). */
|
|
217
|
+
sensitive: boolean;
|
|
143
218
|
usage?: {
|
|
144
219
|
inputTokens?: number;
|
|
145
220
|
outputTokens?: number;
|
|
146
221
|
model?: string;
|
|
222
|
+
/** Absent when the price map has no entry for `model` — render "tokens only", never $0. */
|
|
147
223
|
costUsd?: number;
|
|
148
224
|
};
|
|
149
|
-
|
|
225
|
+
/** Multiple entries render as "N prompts" in the trace list (Q57). */
|
|
226
|
+
prompts: {name: string; version: number; label?: string}[];
|
|
227
|
+
flaggedForDataset?: boolean;
|
|
150
228
|
spans: SpanRecord[];
|
|
151
229
|
}
|
|
152
230
|
|
|
153
231
|
interface ScoreRecord {
|
|
232
|
+
/** Must be a declared dimension key of `evaluatorId` when one is set (Q39). */
|
|
154
233
|
name: string;
|
|
155
|
-
dataType: "
|
|
156
|
-
value:
|
|
234
|
+
dataType: "boolean" | "categorical" | "numeric";
|
|
235
|
+
value: boolean | number | string;
|
|
157
236
|
comment?: string;
|
|
158
|
-
|
|
237
|
+
/** 0–1. Experiment outlier if below the evaluator's `confidenceAlertBelow` (default 0.7). */
|
|
238
|
+
confidence?: number;
|
|
239
|
+
source: "code" | "human" | "llm-judge" | "user-feedback";
|
|
159
240
|
evaluatorId?: string;
|
|
160
241
|
traceId: string;
|
|
161
242
|
spanId?: string;
|
|
162
243
|
}
|
|
244
|
+
|
|
245
|
+
interface EvaluatorDimension {
|
|
246
|
+
dataType: "boolean" | "categorical" | "numeric";
|
|
247
|
+
key: string;
|
|
248
|
+
/** Numeric bounds, categorical values, or a free-text hint. Drives the review form control (Q50). */
|
|
249
|
+
range?: string;
|
|
250
|
+
required: boolean;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
interface EvaluatorRunModes {
|
|
254
|
+
allowManualRun: boolean;
|
|
255
|
+
availableInExperiments: boolean;
|
|
256
|
+
/** 0–100. Default 0. Rejected as non-zero for `human` evaluators (Q41). */
|
|
257
|
+
liveSampleRate: number;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface EvaluatorRecord {
|
|
261
|
+
confidenceAlertBelow?: number; // default 0.7
|
|
262
|
+
description?: string;
|
|
263
|
+
dimensions: EvaluatorDimension[];
|
|
264
|
+
/** json-assert only. */
|
|
265
|
+
assertion?: {constraint: string; path: string};
|
|
266
|
+
/** human only — shown atop the review score form. */
|
|
267
|
+
instructions?: string;
|
|
268
|
+
/** llm-judge only; its version's outputSchema must declare every required dimension (Q40). */
|
|
269
|
+
judgePromptName?: string;
|
|
270
|
+
name: string;
|
|
271
|
+
runModes: EvaluatorRunModes;
|
|
272
|
+
target: "dataset item" | "full trace" | "generation span";
|
|
273
|
+
type: "human" | "json-assert" | "llm-judge";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
interface DatasetItemRecord {
|
|
277
|
+
/** Who wrote/approved the expected output; renders the provenance sub-line (Q42). */
|
|
278
|
+
annotatedBy?: {label: string; reviewItemId?: string; userId?: string};
|
|
279
|
+
datasetId: string;
|
|
280
|
+
expectedOutput?: unknown;
|
|
281
|
+
input: unknown;
|
|
282
|
+
metadata?: Record<string, unknown>;
|
|
283
|
+
origin: "manual" | "synthetic" | "trace";
|
|
284
|
+
outcomeClass?: "fn" | "fp" | "tn" | "tp";
|
|
285
|
+
/** true → "human-annotated"; false → "auto-captured" / needs review (Q42). */
|
|
286
|
+
proofread: boolean;
|
|
287
|
+
sourceTraceId?: string;
|
|
288
|
+
tags: string[];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
interface ScoreThreshold {
|
|
292
|
+
aggregate: "mean" | "trueRate";
|
|
293
|
+
dimension: string;
|
|
294
|
+
evaluatorName: string;
|
|
295
|
+
op: "eq" | "gte" | "lte";
|
|
296
|
+
value: number;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Defaults applied to a new experiment. Boolean dimensions use trueRate; numeric use mean. */
|
|
300
|
+
const SOP_DEFAULT_THRESHOLDS: ScoreThreshold[] = [
|
|
301
|
+
{aggregate: "trueRate", dimension: "correct", evaluatorName: "correctness", op: "eq", value: 1},
|
|
302
|
+
{
|
|
303
|
+
aggregate: "trueRate",
|
|
304
|
+
dimension: "hallucinated",
|
|
305
|
+
evaluatorName: "hallucination",
|
|
306
|
+
op: "eq",
|
|
307
|
+
value: 0,
|
|
308
|
+
},
|
|
309
|
+
{aggregate: "mean", dimension: "helpfulness", evaluatorName: "helpfulness", op: "gte", value: 0.9},
|
|
310
|
+
{aggregate: "trueRate", dimension: "toxic", evaluatorName: "toxicity", op: "eq", value: 0},
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
interface TraceFeedback {
|
|
314
|
+
comment?: string;
|
|
315
|
+
/** When true, mark trace `flaggedForDataset` and optionally enqueue review. */
|
|
316
|
+
flagDataset?: boolean;
|
|
317
|
+
kind: "flag_dataset" | "outcome" | "thumbs";
|
|
318
|
+
value: "down" | "fn" | "fp" | "tn" | "tp" | "up";
|
|
319
|
+
}
|
|
163
320
|
```
|
|
164
321
|
|
|
165
322
|
`ObservabilityApp` (new `TerrenoPlugin`):
|
|
166
323
|
|
|
167
|
-
- Validates config (Q22, Q25, plugin present for each primary).
|
|
324
|
+
- Validates config (Q22, Q25, plugin present for each primary) and fails boot loudly.
|
|
168
325
|
- Holds the sink lists and primaries.
|
|
169
|
-
- Contributes admin screens
|
|
170
|
-
- Does **not** replace `LangfuseApp.register()
|
|
326
|
+
- Contributes admin screens for the capabilities that are actually available.
|
|
327
|
+
- Does **not** replace `LangfuseApp.register()`. The Langfuse **adapter** wraps the existing client from
|
|
328
|
+
`LangfuseApp` / `getLangfuseClient()` and must not start a second OTel SDK.
|
|
171
329
|
|
|
172
|
-
### `AIService` hook
|
|
330
|
+
### `AIService` prompt resolution and trace hook
|
|
173
331
|
|
|
174
|
-
|
|
332
|
+
Before every generate path:
|
|
175
333
|
|
|
176
|
-
1.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
7. If `sampleRate > 0` and `Math.random() < sampleRate`, enqueue live evaluators (same BackgroundTask path as experiments, one trace).
|
|
334
|
+
1. If `options.promptName` is set, resolve `PromptRegistry.get({name, label: options.promptLabel ?? "production"})`
|
|
335
|
+
**before calling the model**, independent of `skipTrace` and trace-sink registration. Use the resolved body and
|
|
336
|
+
retain its ref. Missing registry, missing label, or missing version → `APIError` 400 and no model call.
|
|
337
|
+
2. Inherit `sensitive` from the resolved prompt version unless the generate option explicitly sets it.
|
|
338
|
+
|
|
339
|
+
After each successful or failed generate path (same place as `logRequest`):
|
|
183
340
|
|
|
184
|
-
|
|
341
|
+
1. Skip **trace export only** if no `ObservabilityApp` or `options.skipTrace === true`; prompt resolution above still ran.
|
|
342
|
+
2. Build a root span (`CHAIN` or `LLM`) plus children for tool rounds when present.
|
|
343
|
+
3. Attach `userId`, `sessionId`, and resolved/effective `sensitive` from generate options.
|
|
344
|
+
4. Attach the resolved ref to `trace.prompts`.
|
|
345
|
+
5. Compute `costUsd` from `options.priceMap ?? observability.priceMap` keyed by `model`; leave undefined when absent.
|
|
346
|
+
6. Set each span `status` and, for failures, its short `error` cause; derive trace `errorSummary` from the first failed
|
|
347
|
+
span in execution order.
|
|
348
|
+
7. `Promise.allSettled` on each `TraceSink.export`; log rejections; **never throw**.
|
|
349
|
+
8. Phase 2: for each evaluator with `liveSampleRate > 0` (capped by `AI_OBS_SAMPLE_RATE`), roll and enqueue a
|
|
350
|
+
live eval on the same `BackgroundTask` path experiments use.
|
|
351
|
+
|
|
352
|
+
`AIRequest` keeps `parentRequestId` / `subRequestIds`. Traces use `traceId` + `parentSpanId`. Multi-agent
|
|
353
|
+
`logMultiAgentRequest` also emits a parent `AGENT`/`CHAIN` span wrapping children.
|
|
185
354
|
|
|
186
355
|
## Models (local plugin)
|
|
187
356
|
|
|
188
|
-
All new models: `createdUpdatedPlugin`, `isDeletedPlugin`, `findOneOrNone`, `findExactlyOne`, **description on
|
|
357
|
+
All new models: `createdUpdatedPlugin`, `isDeletedPlugin`, `findOneOrNone`, `findExactlyOne`, **description on
|
|
358
|
+
every field**. Types in `ai/src/types/` (five-type pattern). Default mongoose connection. Not registered when
|
|
359
|
+
the local plugin is absent.
|
|
189
360
|
|
|
190
|
-
| Model | Role |
|
|
191
|
-
| --- | --- |
|
|
192
|
-
| `ObsPrompt` | Named prompt (`name` unique).
|
|
193
|
-
| `ObsPromptVersion` | Immutable version: `type` text\|chat,
|
|
194
|
-
| `ObsPromptLabel` | Movable labels: `production`, `latest`, optional `staging`. Unique `(promptId, label)`. |
|
|
195
|
-
| `ObsTrace` | Root trace: user, session, status, usage,
|
|
196
|
-
| `ObsSpan` | Nested spans: `traceId`, `parentSpanId`, OpenInference `kind`, input/output, model, tokens, cost. |
|
|
197
|
-
| `ObsScore` | Scores on trace/span. |
|
|
198
|
-
| `ObsEvaluator` |
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
|
|
203
|
-
|
|
361
|
+
| Model | Phase | Role |
|
|
362
|
+
| --- | --- | --- |
|
|
363
|
+
| `ObsPrompt` | 1 | Named prompt (`name` unique). **`folder`**, `tags[]`, optional domain-neutral `description`. |
|
|
364
|
+
| `ObsPromptVersion` | 1 | Immutable version: `type` text\|chat, `system`, `template`, `variables[]` (`{key, required, label?, reviewerNote?}`), `inputSchema`/`outputSchema` (JSON Schema), `outputFieldNotes` (Q49), **`sensitive: boolean`** (default false, inherited by calls), `config` (temperature preset, model hint). |
|
|
365
|
+
| `ObsPromptLabel` | 1 | Movable labels: `production`, `latest`, optional `staging`. Unique `(promptId, label)`. |
|
|
366
|
+
| `ObsTrace` | 1 | Root trace: user, session, status, `errorSummary`, `sensitive`, usage, `prompts[]`, timestamps. |
|
|
367
|
+
| `ObsSpan` | 1 | Nested spans: `traceId`, `parentSpanId`, OpenInference `kind`, input/output, model, tokens, cost, `sensitive`, **`status` (`ok`\|`error`) and optional short `error`**, `startOffsetMs`/`durationMs` for the waterfall. |
|
|
368
|
+
| `ObsScore` | 1 | Scores on trace/span. Many per trace — **no unique index**. |
|
|
369
|
+
| `ObsEvaluator` | 1 (human) / 2 (judge, assert) | `type`, `target`, `dimensions[]`, `runModes`, judge/assertion/instructions, `confidenceAlertBelow`. |
|
|
370
|
+
| `ObsReviewItem` | 1 | Queue: `status` pending\|in_progress\|done\|skipped, `evaluatorId`, `traceId`, `spanId?`, `datasetItemId?`, `assigneeId`, `reason` (`manual` \| `eval` \| `feedback` \| `dataset_candidate`), scores payload, `comment`, `enqueuedAt`. |
|
|
371
|
+
| `ObsDataset` | 2 | Name, tags, `inputSchemaPromptName`, `expectedOutputSchema`. |
|
|
372
|
+
| `ObsDatasetItem` | 2 | `input`, `expectedOutput`, `sourceTraceId`, `origin`, `proofread`, `annotatedBy`, `tags[]`, `outcomeClass`, `metadata`. |
|
|
373
|
+
| `ObsExperiment` / `ObsExperimentItem` | 2 | Run metadata + per-item outputs/scores per version. `versions[]` (2–3), optional `modelOverride`, `thresholds[]`, status, `backgroundTaskId`, aggregates incl. gate pass/fail, outlier ids, low-confidence ids, `estimate`. |
|
|
374
|
+
|
|
375
|
+
Indexes: `{created: -1, userId: 1}`, `{sessionId: 1, created: -1}` and `{status: 1, created: -1}` on traces;
|
|
376
|
+
`{"prompts.name": 1, "prompts.version": 1}` on traces (prompt rollups, Q35); `{traceId: 1}` on spans and scores;
|
|
377
|
+
`{status: 1, enqueuedAt: 1}` on review items; `{flaggedForDataset: 1, created: -1}` on traces;
|
|
378
|
+
`{datasetId: 1, proofread: 1, origin: 1}` on dataset items.
|
|
204
379
|
|
|
205
380
|
No backfill: new collections. `AIRequest` unchanged.
|
|
206
381
|
|
|
207
382
|
## APIs
|
|
208
383
|
|
|
209
|
-
|
|
384
|
+
OpenAPI uses `createOpenApiBuilder`. Base path is `/ai/observability`. When an
|
|
385
|
+
`accessControl` is supplied, each route enforces the resource/action matrix in Q60–Q63. Without
|
|
386
|
+
RBAC, the legacy `admin: true` check remains the compatibility path.
|
|
387
|
+
|
|
388
|
+
### Phase 1
|
|
389
|
+
|
|
390
|
+
| Method | Path | Behavior |
|
|
391
|
+
| --- | --- | --- |
|
|
392
|
+
| GET | `/status` | Plugin ids, capabilities, primaries, local-on flag (drives chip + nav, Q53/Q54) |
|
|
393
|
+
| GET/POST | `/prompts` | List (folder, search, `include=usage7d` for calls/cost rollups) / create |
|
|
394
|
+
| GET | `/prompts/:name` | Prompt + versions + labels |
|
|
395
|
+
| POST | `/prompts/:name/versions` | Creates `vN+1`; never mutates an existing version |
|
|
396
|
+
| POST | `/prompts/:name/labels` | Move `production` / `staging`; returns the outgoing version for the confirm modal |
|
|
397
|
+
| POST | `/prompts/:name/playground` | Compile + one `AIService` call; returns compiled messages, output, latency, tokens, cost. Creates no version |
|
|
398
|
+
| GET | `/traces` | Filters: time range, prompt, status, user or session id, `hasScore`, `sensitive`, `flaggedForDataset`. Paginated |
|
|
399
|
+
| GET | `/traces/:id` | Span tree (kind, offsets, durations) + per-span I/O + scores |
|
|
400
|
+
| POST | `/traces/review` | Enqueue one or many traces against a human evaluator (Q52) |
|
|
401
|
+
| POST | `/traces/:id/scores` | Human or code score → all ScoreSinks |
|
|
402
|
+
| CRUD | `/evaluators` | Human type in phase 1; `GET /evaluators/templates` lists the seeds |
|
|
403
|
+
| GET | `/review` | Queue by status with counts; oldest-first |
|
|
404
|
+
| GET | `/review/:id` | Item + rendered `given` / `wrote` panels (Q48/Q49) + evaluator dimensions for the form |
|
|
405
|
+
| POST | `/review/:id` | `submit` (scores + comment), `skip`, or `assign` |
|
|
406
|
+
|
|
407
|
+
### Phase 2
|
|
408
|
+
|
|
409
|
+
| Method | Path | Behavior |
|
|
410
|
+
| --- | --- | --- |
|
|
411
|
+
| POST | `/traces/:id/feedback` | **IsAuthenticated** (owner or admin). Thumbs / outcome / flag-for-dataset → ScoreSinks; optional review enqueue |
|
|
412
|
+
| POST | `/traces/add-to-dataset` | `{datasetId, traceIds[]}`. Copies I/O; `origin: "trace"`; sensitive traces forced to `proofread: false` (Q43) |
|
|
413
|
+
| CRUD | `/datasets` + `/datasets/:id/items` | Item PATCH for expectedOutput / tags / outcomeClass / proofread |
|
|
414
|
+
| POST | `/datasets/:id/import` | JSON or CSV upload; validates against `inputSchemaPromptName` when set. CSV: `text/csv` body or `{format: "csv", content}` |
|
|
415
|
+
| POST | `/datasets/:id/generate` | Synthetic drafts via named generator prompt; `proofread: false` |
|
|
416
|
+
| POST | `/experiments/estimate` | Wizard step 4: generation count, USD, wall-clock estimate |
|
|
417
|
+
| POST | `/experiments` | 2–3 versions × dataset × evaluators + thresholds; local → `BackgroundTask` |
|
|
418
|
+
| GET | `/experiments/:id` | Status, progress, aggregates, gate pass/fail, outliers, per-item side-by-side |
|
|
419
|
+
| POST | `/experiments/:id/promote` | **409 while any gate fails** (Q46); otherwise moves the production label |
|
|
420
|
+
|
|
421
|
+
### Phase 3
|
|
210
422
|
|
|
211
423
|
| Method | Path | Behavior |
|
|
212
424
|
| --- | --- | --- |
|
|
213
|
-
| GET | `/
|
|
214
|
-
| GET
|
|
215
|
-
| GET
|
|
216
|
-
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
| CRUD | `/ai/observability/evaluators` | Dimension definitions |
|
|
224
|
-
| POST | `/ai/observability/traces/:id/scores` | Human or code score → **all ScoreSinks** |
|
|
225
|
-
| CRUD | `/ai/observability/datasets` + items | Dataset **primary** |
|
|
226
|
-
| POST | `/ai/observability/experiments` | Creates run; local → `BackgroundTask`; Langfuse → vendor API |
|
|
227
|
-
| GET | `/ai/observability/experiments/:id` | Status + aggregates |
|
|
228
|
-
| GET/POST | `/ai/observability/review` | Local queue only |
|
|
229
|
-
|
|
230
|
-
GPT routes: pass `userId` / `sessionId` / optional `promptRef` into `AIService`.
|
|
231
|
-
|
|
232
|
-
Langfuse-primary prompt/dataset/experiment routes **proxy** existing Langfuse admin routes where they already exist (`langfuseRoutesPrompts.ts` etc.) so we do not duplicate HTTP. ObservabilityApp may mount a thin facade that delegates.
|
|
425
|
+
| GET | `/sessions`, `/sessions/:id` | Session list + timeline |
|
|
426
|
+
| GET | `/users`, `/users/:id` | AI usage per user; masked identifiers, **no prompt content** (Q38) |
|
|
427
|
+
| GET | `/costs` | KPI row + by-model (`tokens only` when unpriced) + by-prompt |
|
|
428
|
+
| GET | `/status` | Extended with sink health (last error per sink) and Langfuse deep-link bases |
|
|
429
|
+
|
|
430
|
+
GPT routes pass `userId` / `sessionId` into `AIService`; request `sensitive: true` may upgrade handling,
|
|
431
|
+
but `false` never downgrades a sensitive registry version. Client `promptName` + `promptLabel`
|
|
432
|
+
selection is admin-only; application routes select production prompts server-side.
|
|
433
|
+
Langfuse-primary prompt/dataset/experiment routes **proxy** the existing Langfuse admin routes rather than
|
|
434
|
+
duplicating HTTP.
|
|
233
435
|
|
|
234
436
|
## UI (`admin-frontend`)
|
|
235
437
|
|
|
236
|
-
New screens in `AI_ADMIN_WIDGETS` / `customScreens
|
|
438
|
+
New screens registered in `AI_ADMIN_WIDGETS` / `customScreens`, same pattern as `AIRequestsScreenWidget`.
|
|
439
|
+
Chrome for every screen: breadcrumb `Admin / AI Observability / <Section> / <leaf>` and the status chip (Q53).
|
|
440
|
+
Built from `@terreno/ui` (`Page`, `Box`, `DataTable`, `Modal`, `TextField`, `Badge`, `Banner`) — the design's
|
|
441
|
+
palette is already the Terreno theme (`#2B6072` rail, `#0E9DCD` primary, Titillium/Nunito).
|
|
442
|
+
|
|
443
|
+
### Phase 1
|
|
444
|
+
|
|
445
|
+
| Screen | Name | Design requirements |
|
|
446
|
+
| --- | --- | --- |
|
|
447
|
+
| Prompts | `ai-prompts` | Folder rail with counts, search, table (folder/name, type badge, latest, production, last used, calls 7d, cost 7d). Column tooltips explain latest vs production. `—` when no production label |
|
|
448
|
+
| Prompt editor | `ai-prompt-editor` | Full-width version history with one selectable row per version (`vN`, all attached labels, creation time, selected highlight); Editor / Playground tabs; system + user template; variables and output schema summary; temperature preset + model hint; **Save as vN+1**; **Set vN as production…** modal naming the outgoing version; "one production label per prompt" hint |
|
|
449
|
+
| Playground | (tab) | Variable inputs from `variables[]`; Run once with running state; compiled-messages disclosure; output; latency / tokens / cost; **Save this run to dataset** (disabled until phase 2) |
|
|
450
|
+
| Traces | `ai-traces` | Filter bar (time, prompt/status/score/sensitivity dropdowns, user/session); checkbox selection with a bulk bar — **Send to human review** opens an explanatory human-evaluator picker, Add to dataset (phase 2), sensitive count warning, Clear; rows show status dot, `sensitive` badge, error line, `N prompts` for multi-prompt, spans / tokens / cost / latency / scores; pagination footer |
|
|
451
|
+
| Trace detail | `ai-trace-detail` | Header (name, status, sensitive, ids, user/session links, span/token/cost/latency summary) + actions; left span list with kind badge, indent, and duration bar; right span detail (model, prompt link, tokens, cost, latency) with **collapsed** Input/Output disclosures marked sensitive; scores panel with value + source |
|
|
452
|
+
| Review queue | `ai-review` | Tabs Pending / In progress / Done / Skipped with counts; table (item, what the AI did, prompt, assignee, waiting, status); **Start reviewing — oldest first**; empty state naming both intake paths. **Hidden when the local plugin is off** |
|
|
453
|
+
| Review item | `ai-review-item` | Header with position "Item N of M pending" + prev/next; read-only **What the AI was given** / **What the AI wrote** panels (long fields collapsed with word count and expand, structured output as key/value rows, per-field reviewer notes); raw JSON disclosure; score form generated from the evaluator's dimensions (slider / Pass-Fail / pills) with the evaluator's instructions on top; comment; **Submit & next**, Skip, Assign to me; toast reports remaining count and "Queue clear" at zero |
|
|
454
|
+
|
|
455
|
+
### Phase 2
|
|
237
456
|
|
|
238
|
-
| Screen | Name |
|
|
457
|
+
| Screen | Name | Design requirements |
|
|
458
|
+
| --- | --- | --- |
|
|
459
|
+
| Evaluators | `ai-evaluators` | Table (name, type badge, dimensions summary, target, run-mode chips) + the "schema is checked on save / live N% bills judge calls" footnote |
|
|
460
|
+
| Evaluator detail | `ai-evaluator-detail` | Dimensions table (key, data type, range, required); type-specific panel (judge prompt link + model + **schema-match check**, assertion path/constraint, reviewer instructions); run modes with the live-sampling slider and its cost warning; **Used by** list with 30-day runs and cost |
|
|
461
|
+
| New evaluator | `ai-evaluator-new` | Intro explaining what an evaluator is; type and target pickers with per-choice helper text; dimension builder (key, data type pills, range, required, remove) with field helpers; type-specific panel with helpers; **inline schema mismatch error naming the undeclared dimension**; run modes; Create |
|
|
462
|
+
| Datasets | `ai-datasets` | Legend for human-annotated vs auto-captured; table (name, items, provenance bar + counts, source, updated); Import JSON or CSV; New dataset |
|
|
463
|
+
| Dataset detail | `ai-dataset-detail` | Header with human/auto counts and the input-schema binding line; Add item; Run experiment; tabs All / Human / Auto / **Needs review** (red count); items table (id, input, expected output, provenance + attribution line, trace link) |
|
|
464
|
+
| Experiments | `ai-experiments` | Table (id, name, dataset, status badge, progress bar for running, cost); footnote on BackgroundTask / Langfuse deep-link |
|
|
465
|
+
| New experiment | `ai-experiment-new` | 4-step rail with completed summaries; dataset picker showing item counts and schema match; version checkboxes tagged latest/production/superseded; **Next and later rail steps disabled until 2–3 versions are selected**; evaluator checkboxes with per-item cost; **Review & run** summary with the estimate |
|
|
466
|
+
| Experiment results | `ai-experiment-results` | Gate tiles (value, gate verdict); "N gates failing" badge; **Promote blocked** state naming the failing gate; outliers list with reason chips; side-by-side per-item table with failed rows floated and highlighted; promote confirm modal |
|
|
467
|
+
|
|
468
|
+
### Phase 3
|
|
469
|
+
|
|
470
|
+
| Screen | Name | Design requirements |
|
|
239
471
|
| --- | --- | --- |
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
472
|
+
| Sessions | `ai-sessions` | Traces/Sessions toggle; table (session, user, started, traces, duration, total cost) |
|
|
473
|
+
| Session timeline | `ai-session-detail` | Header totals; vertical timeline of traces with time, name, human note, cost, latency |
|
|
474
|
+
| Users | `ai-users` | Subtitle "AI cost and usage per user — not the user CRUD table"; table (id, name, traces 7d, tokens 7d, cost 7d) |
|
|
475
|
+
| User AI profile | `ai-user-detail` | Avatar + masked email + "no raw prompt content on this page"; 7d / 30d / all-time rollups; recent sessions and recent traces |
|
|
476
|
+
| Costs | `ai-costs` | KPI row (today, 7d, 30d, avg/trace); by-model table with **tokens only** for unpriced models plus the "no $0 lie" note; by-prompt table |
|
|
477
|
+
| Chip / deep links | chrome | Sink health in the chip; **Open in Langfuse** when the Langfuse plugin is present |
|
|
478
|
+
|
|
479
|
+
Keep the existing **AI Requests** explorer as-is.
|
|
480
|
+
|
|
481
|
+
### Domain-neutral access and prompt hub
|
|
249
482
|
|
|
250
|
-
|
|
483
|
+
`ObservabilityApp` accepts the application's Terreno access object. Its admin contribution declares
|
|
484
|
+
`adminAccess` per screen, so `/admin/config` and the sidebar omit resources the caller cannot list or
|
|
485
|
+
read. Detail and mutation routes enforce the corresponding action independently; hiding a button is
|
|
486
|
+
not authorization.
|
|
251
487
|
|
|
252
|
-
|
|
488
|
+
The prompt detail screen is the relationship hub:
|
|
489
|
+
|
|
490
|
+
- Overview shows folder, tags, optional description, and current production/latest versions.
|
|
491
|
+
- Versions keeps the immutable editor and history.
|
|
492
|
+
- Traces lists calls related to this prompt and supports version filtering.
|
|
493
|
+
- Experiments lists runs related to this prompt without loading unrelated experiments.
|
|
494
|
+
- Read-only callers see these tabs but no create-version, playground, or promote controls.
|
|
495
|
+
|
|
496
|
+
Folders, descriptions, tags, and role names remain consumer-defined. Terreno introduces no
|
|
497
|
+
clinical, finance, or other domain vocabulary.
|
|
253
498
|
|
|
254
499
|
## Example app
|
|
255
500
|
|
|
256
501
|
`example-backend`:
|
|
257
502
|
|
|
258
|
-
- Register `ObservabilityApp` with **local plugin always
|
|
259
|
-
|
|
260
|
-
-
|
|
261
|
-
- Seed
|
|
503
|
+
- Register `ObservabilityApp` with the **local plugin always** (phase 1) and pass the same
|
|
504
|
+
`accessControl: access` object as `TerrenoApp` / `AdminApp` (phase 4).
|
|
505
|
+
- Seed prompt `example-summarize` v1 labelled production, in folder `examples`.
|
|
506
|
+
- Seed the human evaluator template so the review queue is walkable on a fresh DB (phase 1).
|
|
507
|
+
- Seed the judge/assert evaluator templates and one dataset `example-gold` (phase 2).
|
|
508
|
+
- Add Langfuse sinks when `LANGFUSE_*` is set and `OtelTraceSink` when `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is set (phase 3).
|
|
509
|
+
- Seed optional example roles `aiObservabilityViewer` and `aiObservabilityOperator` in
|
|
510
|
+
`rbacRoles.ts` without auto-assigning them (phase 4); integration tests prove seeded `admin` /
|
|
511
|
+
`superadmin` observability access and that the viewer role is read-only on HTTP.
|
|
262
512
|
|
|
263
|
-
`example-frontend`:
|
|
513
|
+
`example-frontend`: admin-only; regenerate the SDK only if it ends up calling new routes.
|
|
264
514
|
|
|
265
515
|
## Feature flags & env
|
|
266
516
|
|
|
267
|
-
|
|
517
|
+
Registration is the flag — no OpenFeature gate.
|
|
268
518
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
|
272
|
-
|
|
|
273
|
-
| `AI_OBS_PROMPTS_PRIMARY` | `local` \| `langfuse` (default `local`) |
|
|
274
|
-
| `AI_OBS_DATASETS_PRIMARY` | default `local` |
|
|
275
|
-
| `AI_OBS_EXPERIMENTS_PRIMARY` | must equal datasets |
|
|
276
|
-
|
|
|
277
|
-
| `
|
|
278
|
-
| existing Langfuse keys | unchanged |
|
|
279
|
-
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | optional OTLP |
|
|
519
|
+
| Variable | Phase | Role |
|
|
520
|
+
| --- | --- | --- |
|
|
521
|
+
| `AI_OBS_PRICE_MAP_JSON` | 1 | `{"gemini-2.5-flash": {"inputPerMTok": 0.1, "outputPerMTok": 0.4}}` |
|
|
522
|
+
| `AI_OBS_SAMPLE_RATE` | 2 | Global **ceiling** on per-evaluator `liveSampleRate`; default `0` |
|
|
523
|
+
| `AI_OBS_PROMPTS_PRIMARY` | 3 | `local` \| `langfuse` (default `local`) |
|
|
524
|
+
| `AI_OBS_DATASETS_PRIMARY` | 3 | default `local` |
|
|
525
|
+
| `AI_OBS_EXPERIMENTS_PRIMARY` | 3 | must equal datasets |
|
|
526
|
+
| existing Langfuse keys | 3 | unchanged |
|
|
527
|
+
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | 3 | optional OTLP |
|
|
280
528
|
|
|
281
|
-
Validate dataset/experiment primary equality and reject `
|
|
529
|
+
Validate dataset/experiment primary equality and reject a `langfuse` review queue at boot with a clear
|
|
530
|
+
`logger.error` and `APIError` throw, the way a missing SendGrid key fails.
|
|
282
531
|
|
|
283
532
|
## Activity log
|
|
284
533
|
|
|
285
|
-
|
|
534
|
+
No `UserUpdate` spam per trace. Admin audit only for prompt **production** label moves and experiment
|
|
535
|
+
**promote** (reuse `onAdminAudit` when AdminApp is present; otherwise `logger.info` with prompt name + version).
|
|
286
536
|
|
|
287
537
|
## Testing
|
|
288
538
|
|
|
289
539
|
- Bun tests, `expect`, `@terreno/test` Mongo preload. **Never mock `@terreno/api` or models.**
|
|
290
540
|
- Fake TraceSink / ScoreSink in unit tests (in-memory).
|
|
291
|
-
- `AIService
|
|
292
|
-
- Config
|
|
293
|
-
-
|
|
294
|
-
-
|
|
295
|
-
-
|
|
296
|
-
-
|
|
541
|
+
- `AIService`: emit called; `skipTrace`; a throwing sink does not fail generate; `AIRequest` still written; cost undefined for unpriced models.
|
|
542
|
+
- Config: illegal experiment/dataset mix; langfuse review queue; missing plugin for a primary.
|
|
543
|
+
- Prompts: v1 unchanged after v2; label move; `promptName` resolve uses the production body; missing label → 400.
|
|
544
|
+
- Review: enqueue from trace, submit writes dimension-keyed scores, skip, assign, position/counts, empty state.
|
|
545
|
+
- Evaluators (P2): judge output schema missing a required dimension is rejected on save with that key named; human evaluator with non-zero `liveSampleRate` rejected.
|
|
546
|
+
- Experiments (P2): 1-item dataset still uses `BackgroundTask`; gate pass/fail; promote 409 while a gate fails; unproofread items excluded unless opted in.
|
|
547
|
+
- Langfuse/OTel (P3): mock client and mock exporter; secondary failure never surfaces to the caller.
|
|
548
|
+
- `verify-ui-changes` on the admin screens each phase, before Brew.
|
|
297
549
|
|
|
298
550
|
## Documentation (same slices as code)
|
|
299
551
|
|
|
300
552
|
| Page | Kind |
|
|
301
553
|
| --- | --- |
|
|
302
554
|
| `docs/explanation/ai-observability.md` | Why two planes, plugins, vs `LangfuseApp` / `AIRequest` |
|
|
303
|
-
| `docs/how-to/observe-llm-calls.md` | Register
|
|
304
|
-
| `docs/
|
|
305
|
-
| `
|
|
306
|
-
| `
|
|
555
|
+
| `docs/how-to/observe-llm-calls.md` | Register the plugin, price map, sampling ceiling, primaries, Open in Langfuse |
|
|
556
|
+
| `docs/how-to/ai-feature-development.md` | SOP steps mapped to Terreno screens + APIs (operator source of truth) |
|
|
557
|
+
| `docs/reference/ai.md` | Models, routes, `AIService` `promptName`/`promptLabel`/`skipTrace`/`sensitive`, env |
|
|
558
|
+
| `terreno-langfuse-integration.md` | Pointer: the native path is this IP; `LangfuseApp` remains the vendor plugin |
|
|
559
|
+
| `ai-prompt-governance` skill | Runtime prompts come from the registry's production label; judge bodies are registry prompts or named constants — never inline |
|
|
307
560
|
|
|
308
561
|
## Phases
|
|
309
562
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
563
|
+
### Phase 1 — MVP: prompt library, tracing, review queue
|
|
564
|
+
|
|
565
|
+
Contracts, `ObservabilityApp`, `AIService` trace emit and production-label resolution, the local Mongo plugin
|
|
566
|
+
for prompts / traces / spans / scores / human evaluators / review items, the phase-1 routes, and the seven
|
|
567
|
+
phase-1 admin screens. Example-backend registers the plugin and seeds one prompt plus the human evaluator.
|
|
568
|
+
|
|
569
|
+
Exit: an operator can create a prompt, save v2, pin production, watch real traced calls with per-span cost,
|
|
570
|
+
send a weak one to the review queue, and record a clinician's dimension scores against it — with no Langfuse
|
|
571
|
+
keys anywhere.
|
|
572
|
+
|
|
573
|
+
### Phase 2 — Evaluation loop: evaluators, datasets, experiments
|
|
574
|
+
|
|
575
|
+
llm-judge and json-assert evaluator types with the dimension/schema contract and per-evaluator run modes; live
|
|
576
|
+
sampling execution; datasets with provenance, import, add-from-trace, and synthetic generation; the experiment
|
|
577
|
+
wizard, `BackgroundTask` runner, gates, outliers, side-by-side results, and gate-blocked promote; authenticated
|
|
578
|
+
in-app trace feedback. Eight more admin screens.
|
|
579
|
+
|
|
580
|
+
Exit: the full SOP loop runs end to end in Terreno — gather → label → prompt → evaluators → experiment →
|
|
581
|
+
promote → observe → add deficient traces back.
|
|
582
|
+
|
|
583
|
+
### Phase 3 — Analytics and pluggable backends
|
|
584
|
+
|
|
585
|
+
Sessions, Users, and Costs screens over the trace store; the Langfuse adapter (trace, score, prompt, dataset,
|
|
586
|
+
experiment) with the four primary flags and **Open in Langfuse**; the OpenInference `OtelTraceSink`; sink
|
|
587
|
+
health in the status chip.
|
|
588
|
+
|
|
589
|
+
Exit: an existing Langfuse app can register both plugins, keep Langfuse as an extra sink or flip individual
|
|
590
|
+
primaries to it, and a third-party collector receives OpenInference spans.
|
|
591
|
+
|
|
592
|
+
### Phase 4 — Domain-neutral access and prompt relationships
|
|
593
|
+
|
|
594
|
+
Add the six observability RBAC resources, route-level action enforcement, caller-filtered admin
|
|
595
|
+
navigation, compatibility grants for existing Admin/Super Admin/admin-flag users, prompt
|
|
596
|
+
descriptions, version-aware related traces, prompt-filtered experiments, and the read-only prompt
|
|
597
|
+
hub. Approval workflows and domain-specific folder semantics remain outside Terreno.
|
|
598
|
+
|
|
599
|
+
Exit: a consumer can compose a read-only observability role, a reviewer role, or an operator role;
|
|
600
|
+
each user sees only authorized screens/actions, and opening a prompt explains its purpose and shows
|
|
601
|
+
the traces and experiments that use it.
|
|
315
602
|
|
|
316
603
|
## Acceptance criteria
|
|
317
604
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
- [ ]
|
|
321
|
-
- [ ]
|
|
322
|
-
- [ ]
|
|
323
|
-
- [ ]
|
|
324
|
-
- [ ]
|
|
325
|
-
- [ ]
|
|
326
|
-
- [ ]
|
|
605
|
+
### Phase 1
|
|
606
|
+
|
|
607
|
+
- [ ] `AIService.generateText` with `ObservabilityApp` registered writes a nested-capable trace to every `TraceSink`; a throwing sink does not fail the generate; `AIRequest` still exists.
|
|
608
|
+
- [ ] Creating prompt v2 does not mutate v1; the editor offers no in-place save; moving `production` is an explicit labelled action that names the outgoing version.
|
|
609
|
+
- [ ] `generateText({promptName, promptLabel: "production"})` uses the labelled body; a missing label fails with 400 and no model call.
|
|
610
|
+
- [ ] Prompt list groups by folder with counts and shows calls 7d / cost 7d rolled up from traces; a prompt with no production label shows `—`.
|
|
611
|
+
- [ ] Playground compiles and runs one call, shows compiled messages plus latency / tokens / cost, and creates no version.
|
|
612
|
+
- [ ] Trace list filters by time, prompt, status, user, session, and has-score; a failed trace shows a one-line human cause; a multi-prompt trace shows `N prompts`; cost is blank (not `$0`) for models missing from the price map.
|
|
613
|
+
- [ ] Trace detail renders the span tree with OpenInference kinds and per-span cost; `sensitive` I/O is collapsed by default and badged.
|
|
614
|
+
- [ ] Selecting traces and choosing **Send to review queue** enqueues them; the queue lists pending oldest-first with per-status counts.
|
|
615
|
+
- [ ] The review item screen renders given/wrote panels from prompt-version metadata, generates the score form from the evaluator's dimensions (numeric / boolean / categorical), and **Submit & next** advances, writes dimension-keyed scores, and decrements the pending count to an explicit empty state.
|
|
616
|
+
- [ ] Review queue and its nav entry are hidden when the local plugin is off.
|
|
617
|
+
- [ ] Docs for phase 1 match shipped behavior; `bun run --filter=@terreno/ai compile test lint` green; `verify-ui-changes` evidence attached.
|
|
618
|
+
|
|
619
|
+
### Phase 2
|
|
620
|
+
|
|
621
|
+
- [ ] An `llm-judge` evaluator whose judge prompt output schema omits a required dimension is rejected on save with that dimension named; a `human` evaluator with `liveSampleRate > 0` is rejected.
|
|
622
|
+
- [ ] Per-evaluator `liveSampleRate` drives live evals, capped by `AI_OBS_SAMPLE_RATE`; default 0 produces no judge calls.
|
|
623
|
+
- [ ] Dataset items distinguish human-annotated from auto-captured, carry attribution, and the Needs-review tab counts unproofread items; adding a `sensitive` trace always lands as needs-review.
|
|
624
|
+
- [ ] Import JSON and add-from-trace (single and bulk) validate against the bound prompt's `inputSchema` and retain `sourceTraceId`.
|
|
625
|
+
- [ ] An experiment compares 2–3 versions on a dataset, always via `BackgroundTask` locally (even for one item), reports gate pass/fail plus outliers and low-confidence items, and floats failed items to the top of the side-by-side table.
|
|
626
|
+
- [ ] Promote returns 409 and renders disabled while any gate fails; with gates passing it moves the production label after an explicit confirm.
|
|
627
|
+
- [ ] Owner `POST feedback` (thumbs down + flagDataset) writes a `user-feedback` score and sets `flaggedForDataset`; a non-owner non-admin gets 403.
|
|
628
|
+
- [ ] The wizard's pre-run estimate reports generation count and USD before the run starts.
|
|
629
|
+
|
|
630
|
+
### Phase 3
|
|
631
|
+
|
|
632
|
+
- [ ] With both plugins registered, one generate dual-writes traces and scores; prompts stay on the configured primary; boot fails if `experiments.primary !== datasets.primary` or the review queue is `langfuse`.
|
|
633
|
+
- [ ] The OTel sink's mock exporter receives an OpenInference `LLM` span carrying model and token attributes.
|
|
634
|
+
- [ ] Costs shows `tokens only` for unpriced models; Users shows masked identifiers and no prompt content; Sessions renders a timeline.
|
|
635
|
+
- [ ] The status chip reflects local state, Langfuse sink health, and active primaries, and **Open in Langfuse** appears only when the Langfuse plugin is registered.
|
|
636
|
+
|
|
637
|
+
### Phase 4
|
|
638
|
+
|
|
639
|
+
- [ ] The RBAC catalog exposes all six observability resources and their exact actions; Auditor gets only list/read, Super Admin gets all, and the seeded Admin role receives full observability access without deleting consumer-defined permissions.
|
|
640
|
+
- [ ] Every observability HTTP route enforces its resource/action when access control is configured; legacy `admin: true` callers retain full access when RBAC is absent or the compatibility fallback applies.
|
|
641
|
+
- [ ] `/admin/config` omits unauthorized observability screens. Read-only users can open granted screens but cannot see or invoke denied create, update, playground, scoring, assignment, or promotion actions.
|
|
642
|
+
- [ ] Prompts store and return an optional description without changing folder semantics.
|
|
643
|
+
- [ ] Prompt detail returns and renders related traces and experiments. Trace relationships can be filtered by prompt version; experiment relationships are filtered server-side by prompt name.
|
|
644
|
+
- [ ] Public explanation, operator, AI reference, admin reference, and RBAC reference docs describe the domain-neutral permission and prompt-hub contracts.
|
|
327
645
|
|
|
328
646
|
## Risks
|
|
329
647
|
|
|
330
648
|
| Risk | Mitigation |
|
|
331
649
|
| --- | --- |
|
|
332
|
-
|
|
|
333
|
-
|
|
|
334
|
-
|
|
|
335
|
-
|
|
|
336
|
-
|
|
|
337
|
-
|
|
|
650
|
+
| Phase 1 review queue needs evaluator dimensions before evaluators ship | Human evaluator type lands in phase 1; judge and assert types are additive fields, not a rewrite |
|
|
651
|
+
| Prompt-version metadata for the review panels is easy to skip | `variables[].label` / `reviewerNote` / `outputFieldNotes` are optional with raw-key fallback, so a bare prompt still reviews |
|
|
652
|
+
| Langfuse experiment API vs Terreno dataset | Config equality rule; no sync job |
|
|
653
|
+
| Double OTel SDK vs LangfuseApp | Adapter shares the existing tracing init |
|
|
654
|
+
| PHI in Mongo | `sensitive` flag, collapsed I/O, no content on the Users screen; retention is a follow-on IP |
|
|
655
|
+
| Live eval cost | Per-evaluator rate defaults 0 and is capped by a global ceiling; wizard shows cost before running |
|
|
656
|
+
| Admin SPA drift | Widgets in admin-frontend appear in the spa shell automatically |
|
|
338
657
|
|
|
339
658
|
## Files to create / modify (expected)
|
|
340
659
|
|
|
341
|
-
- `ai/src/observability/**`
|
|
342
|
-
- `ai/src/service/aiService.ts` — emit
|
|
343
|
-
- `ai/src/routes/gpt.ts` — pass user/session
|
|
344
|
-
- `ai/src/aiAdminApp.ts`
|
|
660
|
+
- `ai/src/observability/**` — types, `observabilityApp.ts`, `local/` models + stores, `evaluate.ts`, `experimentRunner.ts`, `otel/`, `langfuse/`
|
|
661
|
+
- `ai/src/service/aiService.ts` — trace emit; resolve `promptName`/`promptLabel`; `sensitive`, `skipTrace`
|
|
662
|
+
- `ai/src/routes/gpt.ts` — pass user / session / sensitive; optional prompt name
|
|
663
|
+
- `ai/src/aiAdminApp.ts` (or new `observabilityAdmin.ts`) — screen contributions
|
|
345
664
|
- `ai/src/index.ts` — exports
|
|
346
|
-
- `admin-frontend/src/widgets/*` —
|
|
347
|
-
- `example-backend/src/server.ts`
|
|
348
|
-
- docs listed above
|
|
349
|
-
- tests colocated
|
|
665
|
+
- `admin-frontend/src/widgets/aiObservability/*` — the screens above + registry
|
|
666
|
+
- `example-backend/src/server.ts` + seed script + `.env.example`
|
|
667
|
+
- docs listed above; tests colocated
|