diff --git a/docs/api/index.md b/docs/api/index.md index 4d93ec8d..22d35d8d 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -4436,7 +4436,7 @@ Bearer key — tenant is resolved server-side from it. Reads TANGLE_API_KEY. Defined in: [otel-export.ts:599](https://github.com/tangle-network/agent-runtime/blob/main/src/otel-export.ts#L599) -Intelligence base. Reads INTELLIGENCE_BASE env, else prod. +Intelligence base. Reads TANGLE_INTELLIGENCE_URL env, else prod. ##### idempotencyKey? diff --git a/docs/api/intelligence.md b/docs/api/intelligence.md index 5402fd5b..dc6d4ace 100644 --- a/docs/api/intelligence.md +++ b/docs/api/intelligence.md @@ -486,7 +486,7 @@ Tear down provisioned hosts (reverse dependency order). ### CertifiedArtifact -Defined in: [intelligence/delivery.ts:34](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L34) +Defined in: [intelligence/delivery.ts:35](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L35) A promoted, certified artifact (one entry in the composed profile). @@ -496,31 +496,31 @@ A promoted, certified artifact (one entry in the composed profile). > **path**: `string` \| `null` -Defined in: [intelligence/delivery.ts:35](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L35) +Defined in: [intelligence/delivery.ts:36](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L36) ##### content > **content**: `string` -Defined in: [intelligence/delivery.ts:36](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L36) +Defined in: [intelligence/delivery.ts:37](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L37) ##### contentHash > **contentHash**: `string` -Defined in: [intelligence/delivery.ts:37](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L37) +Defined in: [intelligence/delivery.ts:38](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L38) ##### version > **version**: `number` \| `null` -Defined in: [intelligence/delivery.ts:38](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L38) +Defined in: [intelligence/delivery.ts:39](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L39) ##### lift > **lift**: `string` \| `null` -Defined in: [intelligence/delivery.ts:41](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L41) +Defined in: [intelligence/delivery.ts:42](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L42) Held-out gate lift attached at certification, e.g. "+3.1pp" — never a within-run claim. `null` when the promotion carried no lift record. @@ -529,13 +529,13 @@ Held-out gate lift attached at certification, e.g. "+3.1pp" — never a > **promotedAt**: `string` -Defined in: [intelligence/delivery.ts:42](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L42) +Defined in: [intelligence/delivery.ts:43](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L43) *** ### CertifiedPromptSurface -Defined in: [intelligence/delivery.ts:46](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L46) +Defined in: [intelligence/delivery.ts:47](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L47) The active promoted prompt surface for a target. @@ -545,31 +545,140 @@ The active promoted prompt surface for a target. > **surface**: `string` -Defined in: [intelligence/delivery.ts:47](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L47) +Defined in: [intelligence/delivery.ts:48](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L48) ##### surfaceHash > **surfaceHash**: `string` -Defined in: [intelligence/delivery.ts:48](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L48) +Defined in: [intelligence/delivery.ts:49](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L49) ##### version > **version**: `number` \| `null` -Defined in: [intelligence/delivery.ts:49](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L49) +Defined in: [intelligence/delivery.ts:50](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L50) ##### lift > **lift**: `string` \| `null` -Defined in: [intelligence/delivery.ts:50](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L50) +Defined in: [intelligence/delivery.ts:51](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L51) + +*** + +### DiffProvenance + +Defined in: [intelligence/delivery.ts:56](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L56) + +The held-out provenance the plane's certify step stamps on a promoted diff. + `lift` is the held-out gate lift (e.g. "+3.1pp"), never a within-run claim. + +#### Properties + +##### version + +> **version**: `number` \| `null` + +Defined in: [intelligence/delivery.ts:57](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L57) + +##### lift + +> **lift**: `string` \| `null` + +Defined in: [intelligence/delivery.ts:58](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L58) + +##### contentHash + +> **contentHash**: `string` + +Defined in: [intelligence/delivery.ts:59](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L59) + +##### promotedAt + +> **promotedAt**: `string` + +Defined in: [intelligence/delivery.ts:60](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L60) + +*** + +### ProposedProfileDiff + +Defined in: [intelligence/delivery.ts:70](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L70) + +A gate-certified profile diff the plane has already promoted, plus the +held-out provenance it carries. This is the previously-DROPPED typed diff the +composed endpoint returns; `withIntelligence` deserializes it and surfaces it +as a PROPOSAL — a human, or the gated local `improve()` loop, turns a proposal +into a shipped profile. It is NEVER auto-applied at runtime. + +#### Properties + +##### diff + +> **diff**: `AgentProfileDiff` + +Defined in: [intelligence/delivery.ts:71](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L71) + +##### provenance + +> **provenance**: [`DiffProvenance`](#diffprovenance) + +Defined in: [intelligence/delivery.ts:72](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L72) + +*** + +### CertifiedCapabilitySummary + +Defined in: [intelligence/delivery.ts:78](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L78) + +The composed endpoint's per-capability summary — the narrow shape on the + wire (id + surface + path/content + provenance). Distinct from the richer + `CertifiedCapability` the capability resolver lowers a manifest into. + +#### Properties + +##### id + +> **id**: `string` + +Defined in: [intelligence/delivery.ts:79](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L79) + +##### iface + +> **iface**: `object` + +Defined in: [intelligence/delivery.ts:80](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L80) + +###### surface + +> **surface**: `string` + +##### binding + +> **binding**: `object` + +Defined in: [intelligence/delivery.ts:81](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L81) + +###### path + +> **path**: `string` \| `null` + +###### content + +> **content**: `string` + +##### provenance + +> **provenance**: [`DiffProvenance`](#diffprovenance) + +Defined in: [intelligence/delivery.ts:82](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L82) *** ### CertifiedProfile -Defined in: [intelligence/delivery.ts:55](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L55) +Defined in: [intelligence/delivery.ts:87](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L87) The composed certified profile — exactly the shape the plane's `GET /v1/profiles/:target/composed` returns. @@ -580,31 +689,57 @@ The composed certified profile — exactly the shape the plane's > **target**: `string` -Defined in: [intelligence/delivery.ts:56](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L56) +Defined in: [intelligence/delivery.ts:88](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L88) ##### generatedAt > **generatedAt**: `string` -Defined in: [intelligence/delivery.ts:57](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L57) +Defined in: [intelligence/delivery.ts:89](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L89) ##### promptSurface > **promptSurface**: [`CertifiedPromptSurface`](#certifiedpromptsurface) \| `null` -Defined in: [intelligence/delivery.ts:58](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L58) +Defined in: [intelligence/delivery.ts:90](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L90) ##### artifacts > **artifacts**: `Record`\<`string`, [`CertifiedArtifact`](#certifiedartifact)[]\> -Defined in: [intelligence/delivery.ts:59](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L59) +Defined in: [intelligence/delivery.ts:91](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L91) + +##### agentProfileDiffs + +> **agentProfileDiffs**: [`ProposedProfileDiff`](#proposedprofilediff)[] + +Defined in: [intelligence/delivery.ts:94](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L94) + +The typed profile diffs the plane has promoted, each with held-out + provenance. Surfaced as proposals; never auto-applied. Empty when none. + +##### capabilities + +> **capabilities**: [`CertifiedCapabilitySummary`](#certifiedcapabilitysummary)[] + +Defined in: [intelligence/delivery.ts:96](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L96) + +The composed capability summaries the plane returns. Empty when none. + +##### agentProfile + +> **agentProfile**: `AgentProfile` \| `null` + +Defined in: [intelligence/delivery.ts:99](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L99) + +The composed profile the promoted diffs fold to, for inspection. `null` + when no diffs are promoted. *** ### PullCertifiedOptions -Defined in: [intelligence/delivery.ts:68](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L68) +Defined in: [intelligence/delivery.ts:108](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L108) #### Extended by @@ -616,7 +751,7 @@ Defined in: [intelligence/delivery.ts:68](https://github.com/tangle-network/agen > **target**: `string` -Defined in: [intelligence/delivery.ts:70](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L70) +Defined in: [intelligence/delivery.ts:110](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L110) The agent target certified artifacts are promoted under. @@ -624,7 +759,7 @@ The agent target certified artifacts are promoted under. > `optional` **apiKey?**: `string` -Defined in: [intelligence/delivery.ts:72](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L72) +Defined in: [intelligence/delivery.ts:112](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L112) Bearer key. Defaults to `process.env.TANGLE_API_KEY`. @@ -632,7 +767,7 @@ Bearer key. Defaults to `process.env.TANGLE_API_KEY`. > `optional` **baseUrl?**: `string` -Defined in: [intelligence/delivery.ts:75](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L75) +Defined in: [intelligence/delivery.ts:115](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L115) Plane base URL. Defaults to `process.env.TANGLE_INTELLIGENCE_URL` then `https://intelligence.tangle.tools`. @@ -641,7 +776,7 @@ Plane base URL. Defaults to `process.env.TANGLE_INTELLIGENCE_URL` then > `optional` **fetchImpl?**: (`input`, `init?`) => `Promise`\<`Response`\> -Defined in: [intelligence/delivery.ts:77](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L77) +Defined in: [intelligence/delivery.ts:117](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L117) fetch impl (tests / non-global-fetch runtimes). Defaults to global fetch. @@ -663,7 +798,7 @@ fetch impl (tests / non-global-fetch runtimes). Defaults to global fetch. > `optional` **timeoutMs?**: `number` -Defined in: [intelligence/delivery.ts:81](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L81) +Defined in: [intelligence/delivery.ts:121](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L121) Abort the pull after this many ms so a hung plane never blocks the caller. Default 10000. The timeout surfaces as a normal fail-closed `succeeded: @@ -673,12 +808,12 @@ Abort the pull after this many ms so a hung plane never blocks the caller. ### CertifiedPromptSource -Defined in: [intelligence/delivery.ts:187](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L187) +Defined in: [intelligence/delivery.ts:276](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L276) A cached, self-refreshing source of a target's certified prompt additions — the prompt-only delivery lane for callers that assemble their OWN system prompt (product chat routes) rather than wrapping an agent fn. Same - fail-closed semantics as [withCertifiedDelivery](#withcertifieddelivery): pulls at most every + fail-closed semantics as [pullCertified](#pullcertified): pulls at most every `refreshMs`, coalesces concurrent pulls, keeps the last-known profile on a failed/404 pull, never throws, never blocks past the pull timeout. @@ -688,7 +823,7 @@ A cached, self-refreshing source of a target's certified prompt additions — > **compose**(`base`): `Promise`\<`string`\> -Defined in: [intelligence/delivery.ts:190](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L190) +Defined in: [intelligence/delivery.ts:279](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L279) Refresh (window-respecting) then fold the certified additions into a base system prompt. Returns `base` unchanged when nothing is promoted. @@ -707,7 +842,7 @@ Refresh (window-respecting) then fold the certified additions into a > **current**(): [`CertifiedProfile`](#certifiedprofile) \| `null` -Defined in: [intelligence/delivery.ts:192](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L192) +Defined in: [intelligence/delivery.ts:281](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L281) The certified profile currently in effect (`null` = none pulled yet). @@ -719,7 +854,7 @@ The certified profile currently in effect (`null` = none pulled yet). > **refresh**(): `Promise`\<`void`\> -Defined in: [intelligence/delivery.ts:194](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L194) +Defined in: [intelligence/delivery.ts:283](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L283) Pull now if the refresh window has elapsed; coalesced and fail-closed. @@ -731,7 +866,7 @@ Pull now if the refresh window has elapsed; coalesced and fail-closed. ### CertifiedPromptSourceOptions -Defined in: [intelligence/delivery.ts:199](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L199) +Defined in: [intelligence/delivery.ts:288](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L288) Options for [createCertifiedPromptSource](#createcertifiedpromptsource) — the pull coordinates plus the refresh cadence. @@ -746,7 +881,7 @@ Options for [createCertifiedPromptSource](#createcertifiedpromptsource) — the > **target**: `string` -Defined in: [intelligence/delivery.ts:70](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L70) +Defined in: [intelligence/delivery.ts:110](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L110) The agent target certified artifacts are promoted under. @@ -758,7 +893,7 @@ The agent target certified artifacts are promoted under. > `optional` **apiKey?**: `string` -Defined in: [intelligence/delivery.ts:72](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L72) +Defined in: [intelligence/delivery.ts:112](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L112) Bearer key. Defaults to `process.env.TANGLE_API_KEY`. @@ -770,7 +905,7 @@ Bearer key. Defaults to `process.env.TANGLE_API_KEY`. > `optional` **baseUrl?**: `string` -Defined in: [intelligence/delivery.ts:75](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L75) +Defined in: [intelligence/delivery.ts:115](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L115) Plane base URL. Defaults to `process.env.TANGLE_INTELLIGENCE_URL` then `https://intelligence.tangle.tools`. @@ -783,7 +918,7 @@ Plane base URL. Defaults to `process.env.TANGLE_INTELLIGENCE_URL` then > `optional` **fetchImpl?**: (`input`, `init?`) => `Promise`\<`Response`\> -Defined in: [intelligence/delivery.ts:77](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L77) +Defined in: [intelligence/delivery.ts:117](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L117) fetch impl (tests / non-global-fetch runtimes). Defaults to global fetch. @@ -809,7 +944,7 @@ fetch impl (tests / non-global-fetch runtimes). Defaults to global fetch. > `optional` **timeoutMs?**: `number` -Defined in: [intelligence/delivery.ts:81](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L81) +Defined in: [intelligence/delivery.ts:121](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L121) Abort the pull after this many ms so a hung plane never blocks the caller. Default 10000. The timeout surfaces as a normal fail-closed `succeeded: @@ -823,364 +958,296 @@ Abort the pull after this many ms so a hung plane never blocks the caller. > `optional` **refreshMs?**: `number` -Defined in: [intelligence/delivery.ts:201](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L201) +Defined in: [intelligence/delivery.ts:290](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L290) Min interval between certified-profile pulls. Default 5m. *** -### AppliedIntelligence +### EffortSettings -Defined in: [intelligence/delivery.ts:246](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L246) +Defined in: [intelligence/effort.ts:32](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L32) -What the delivery wrapper hands the agent each run. +The flat, resolved settings a tier compiles to. Every field is individually +overridable through `resolveEffort`. Pure data — read by the wrapper, never +self-executing. #### Properties -##### certified +##### analysts -> **certified**: [`CertifiedProfile`](#certifiedprofile) \| `null` +> **analysts**: `boolean` -Defined in: [intelligence/delivery.ts:249](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L249) +Defined in: [intelligence/effort.ts:34](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L34) -The certified profile in effect (null when none promoted / pull failed — - fail-closed: the agent runs on its base surface). +Whether trace-derived analyst diagnosis may spawn. `false` ⇒ no analyst. -#### Methods +##### corpus -##### composePrompt() +> **corpus**: [`CorpusAccess`](#corpusaccess) -> **composePrompt**(`base`): `string` +Defined in: [intelligence/effort.ts:36](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L36) -Defined in: [intelligence/delivery.ts:251](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L251) +Cross-run corpus access this tier permits. -Fold the certified prompt surface into a base system prompt. +##### fanout -###### Parameters +> **fanout**: `number` -###### base +Defined in: [intelligence/effort.ts:38](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L38) -`string` +Parallel candidate width. `1` ⇒ single-shot, no breadth. -###### Returns +##### loops -`string` +> **loops**: `boolean` -*** +Defined in: [intelligence/effort.ts:40](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L40) -### DeliveryConfig +Whether multi-step improvement loops (refine / fanout-vote) may run. -Defined in: [intelligence/delivery.ts:259](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L259) +##### intelligenceBudgetUsd -Delivery config = the Observe config plus the pull target + refresh cadence. +> **intelligenceBudgetUsd**: `number` \| `null` -#### Extends +Defined in: [intelligence/effort.ts:47](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L47) -- [`IntelligenceConfig`](#intelligenceconfig) +Ceiling, in USD, for INTELLIGENCE-class spawns only (analysts, corpus, +loops) — NOT base inference. `0` refuses every intelligence spawn; `null` +means uncapped (the spend lands on the Pareto receipt). Base-stream +inference is billed on its own channel and is never constrained here. -#### Properties +*** -##### target? +### EffortOverridesCompiled -> `optional` **target?**: `string` +Defined in: [intelligence/effort.ts:157](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L157) -Defined in: [intelligence/delivery.ts:261](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L261) +The run-config overrides an `EffortSettings` compiles to — the bridge between the +pure effort policy and the orchestration entrypoints (`runPersonified` / the +improvement cycle). This is ONLY data: it never constructs an analyst or runs a +loop. The caller reads these flags to decide WHAT to pass: -Pull target. Defaults to `project`. + - `withAnalyst: false` ⇒ DO NOT construct/pass a `ScopeAnalyst` to `runPersonified` + (the dormant empty-findings path runs; the base agent still works). This is the + PRODUCT fail-closed at `off`/`eco` — "don't construct the analyst" — distinct from + the EXPERIMENT fail-closed inside `createScopeAnalyst` ("hard abort"), which stays + untouched. Degrade, never throw. + - `fanout` ⇒ the `ShapeBudget.fanout` width to pass (`1` at `off`, the tier's breadth + otherwise). Overrides the personify default fanout. + - `withLoops: false` ⇒ the improvement cycle is a no-op for this run (no refine / + fanout-vote multi-step loop spawns). + - `intelligenceBudgetUsd` ⇒ the intelligence-class spend ceiling carried through for + the billing clamp (passed verbatim; `0` refuses every intelligence spawn). -##### baseUrl? +#### Properties -> `optional` **baseUrl?**: `string` +##### withAnalyst -Defined in: [intelligence/delivery.ts:264](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L264) +> **withAnalyst**: `boolean` -Plane base URL for the pull (NOT the OTLP `endpoint`). Defaults to - `TANGLE_INTELLIGENCE_URL` then `https://intelligence.tangle.tools`. +Defined in: [intelligence/effort.ts:159](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L159) -##### refreshMs? +Construct + pass a `ScopeAnalyst`? `false` ⇒ omit it (degrade to the base agent). -> `optional` **refreshMs?**: `number` +##### fanout -Defined in: [intelligence/delivery.ts:266](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L266) +> **fanout**: `number` -Min interval between certified-profile pulls. Default 5m. +Defined in: [intelligence/effort.ts:161](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L161) -##### timeoutMs? +`ShapeBudget.fanout` width to pass to `runPersonified`. -> `optional` **timeoutMs?**: `number` +##### withLoops -Defined in: [intelligence/delivery.ts:268](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L268) +> **withLoops**: `boolean` -Per-pull timeout in ms (fail-closed on a hung plane). Default 10000. +Defined in: [intelligence/effort.ts:163](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L163) -##### fetchImpl? +Run the multi-step improvement cycle, or no-op it for this run? -> `optional` **fetchImpl?**: (`input`, `init?`) => `Promise`\<`Response`\> +##### intelligenceBudgetUsd -Defined in: [intelligence/delivery.ts:270](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L270) +> **intelligenceBudgetUsd**: `number` \| `null` -fetch impl for the pull (tests). Defaults to global fetch. +Defined in: [intelligence/effort.ts:165](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L165) -###### Parameters +Intelligence-class spend ceiling. `0` refuses every intelligence spawn; `null` uncapped. -###### input +*** -`string` \| `URL` \| `Request` +### UsageSplit -###### init? +Defined in: [intelligence/index.ts:120](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L120) -`RequestInit` +The per-class cost split carried by every trace and outcome. `off` ⇒ +`intelligenceUsd: 0` by construction — there is no intelligence spawn to +bill. This is a classification on the trace, NOT a budget-pool split. -###### Returns +#### Properties -`Promise`\<`Response`\> +##### inferenceUsd -##### project +> **inferenceUsd**: `number` -> **project**: `string` +Defined in: [intelligence/index.ts:122](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L122) -Defined in: [intelligence/index.ts:132](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L132) +Base-stream (model) spend in USD. -Stable project id — the tenant dimension every trace is tagged with. +##### intelligenceUsd -###### Inherited from +> **intelligenceUsd**: `number` -[`IntelligenceConfig`](#intelligenceconfig).[`project`](#project-1) +Defined in: [intelligence/index.ts:124](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L124) -##### apiKey? +Intelligence-spawn spend in USD. Provably `0` at the OFF tier. -> `optional` **apiKey?**: `string` +*** + +### RunRecord Defined in: [intelligence/index.ts:134](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L134) -Bearer key for the Intelligence ingest. Reads `TANGLE_API_KEY` when omitted. +The typed record `withIntelligence` sends per call — serialized through the +shipped OTLP builders to the plane's `/v1/otlp` ingest. `input`/`output` are +redacted on export; the per-class `usage` split carries the billing proof; +`loopEvents`, when present, export as the nested loop→round→iteration span +tree under the same `traceId`. -###### Inherited from +#### Properties -[`IntelligenceConfig`](#intelligenceconfig).[`apiKey`](#apikey-3) +##### runId -##### effort? +> **runId**: `string` -> `optional` **effort?**: [`EffortTier`](#efforttier) \| \{ `tier`: [`EffortTier`](#efforttier); `overrides?`: `Partial`\<[`EffortSettings`](#effortsettings)\>; \} +Defined in: [intelligence/index.ts:135](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L135) -Defined in: [intelligence/index.ts:136](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L136) - -Effort tier (default `'standard'`) plus optional per-field overrides. - -###### Inherited from - -[`IntelligenceConfig`](#intelligenceconfig).[`effort`](#effort-1) - -##### endpoint? - -> `optional` **endpoint?**: `string` - -Defined in: [intelligence/index.ts:143](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L143) - -OTLP ingest base. The underlying exporter appends `/v1/traces`, so point -this at the OTLP route (e.g. `https://intelligence.tangle.tools/v1/otlp`). -Reads `INTELLIGENCE_OTLP_ENDPOINT` then `OTEL_EXPORTER_OTLP_ENDPOINT` when -omitted; absent all three, export is a no-op (best-effort by construction). - -###### Inherited from - -[`IntelligenceConfig`](#intelligenceconfig).[`endpoint`](#endpoint-1) - -##### redact? - -> `optional` **redact?**: `false` \| [`Redactor`](#redactor) - -Defined in: [intelligence/index.ts:149](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L149) - -Redaction hook run over every exported input/output. A function replaces -the default scrubber; `false` opts out entirely (raw fidelity, caller has -sanitized upstream); omitted ⇒ the built-in `defaultRedactor`. - -###### Inherited from - -[`IntelligenceConfig`](#intelligenceconfig).[`redact`](#redact-1) - -##### surfaces? - -> `optional` **surfaces?**: `string`[] - -Defined in: [intelligence/index.ts:151](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L151) - -Mutable surfaces a later PR mode would edit. Recorded for `doctor()` only. - -###### Inherited from - -[`IntelligenceConfig`](#intelligenceconfig).[`surfaces`](#surfaces-1) - -##### checks? - -> `optional` **checks?**: `string`[] - -Defined in: [intelligence/index.ts:153](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L153) - -Verification checks a later PR mode would gate on. Recorded for `doctor()` only. - -###### Inherited from +##### traceId -[`IntelligenceConfig`](#intelligenceconfig).[`checks`](#checks-1) +> **traceId**: `string` -##### repo? +Defined in: [intelligence/index.ts:136](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L136) -> `optional` **repo?**: [`RepoConfig`](#repoconfig) +##### project -Defined in: [intelligence/index.ts:155](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L155) +> **project**: `string` -Repo access a later PR mode would need. Recorded for `doctor()` only. +Defined in: [intelligence/index.ts:137](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L137) -###### Inherited from +##### target -[`IntelligenceConfig`](#intelligenceconfig).[`repo`](#repo-1) +> **target**: `string` -*** +Defined in: [intelligence/index.ts:138](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L138) -### EffortSettings +##### input -Defined in: [intelligence/effort.ts:32](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L32) +> **input**: `unknown` -The flat, resolved settings a tier compiles to. Every field is individually -overridable through `resolveEffort`. Pure data — read by the wrapper, never -self-executing. +Defined in: [intelligence/index.ts:139](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L139) -#### Properties +##### output -##### analysts +> **output**: `unknown` -> **analysts**: `boolean` +Defined in: [intelligence/index.ts:140](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L140) -Defined in: [intelligence/effort.ts:34](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L34) +##### outcome -Whether trace-derived analyst diagnosis may spawn. `false` ⇒ no analyst. +> **outcome**: `object` -##### corpus +Defined in: [intelligence/index.ts:141](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L141) -> **corpus**: [`CorpusAccess`](#corpusaccess) +###### success? -Defined in: [intelligence/effort.ts:36](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L36) +> `optional` **success?**: `boolean` -Cross-run corpus access this tier permits. +###### score? -##### fanout +> `optional` **score?**: `number` -> **fanout**: `number` +###### usage -Defined in: [intelligence/effort.ts:38](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L38) +> **usage**: [`UsageSplit`](#usagesplit) -Parallel candidate width. `1` ⇒ single-shot, no breadth. +##### model? -##### loops +> `optional` **model?**: `string` -> **loops**: `boolean` +Defined in: [intelligence/index.ts:146](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L146) -Defined in: [intelligence/effort.ts:40](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L40) +##### provider? -Whether multi-step improvement loops (refine / fanout-vote) may run. +> `optional` **provider?**: `string` -##### intelligenceBudgetUsd +Defined in: [intelligence/index.ts:147](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L147) -> **intelligenceBudgetUsd**: `number` \| `null` +##### loopEvents? -Defined in: [intelligence/effort.ts:47](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L47) +> `optional` **loopEvents?**: [`LoopTraceEvent`](runtime.md#looptraceevent)[] -Ceiling, in USD, for INTELLIGENCE-class spawns only (analysts, corpus, -loops) — NOT base inference. `0` refuses every intelligence spawn; `null` -means uncapped (the spend lands on the Pareto receipt). Base-stream -inference is billed on its own channel and is never constrained here. +Defined in: [intelligence/index.ts:148](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L148) *** -### EffortOverridesCompiled +### RunReport -Defined in: [intelligence/effort.ts:157](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L157) - -The run-config overrides an `EffortSettings` compiles to — the bridge between the -pure effort policy and the orchestration entrypoints (`runPersonified` / the -improvement cycle). This is ONLY data: it never constructs an analyst or runs a -loop. The caller reads these flags to decide WHAT to pass: +Defined in: [intelligence/index.ts:157](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L157) - - `withAnalyst: false` ⇒ DO NOT construct/pass a `ScopeAnalyst` to `runPersonified` - (the dormant empty-findings path runs; the base agent still works). This is the - PRODUCT fail-closed at `off`/`eco` — "don't construct the analyst" — distinct from - the EXPERIMENT fail-closed inside `createScopeAnalyst` ("hard abort"), which stays - untouched. Degrade, never throw. - - `fanout` ⇒ the `ShapeBudget.fanout` width to pass (`1` at `off`, the tier's breadth - otherwise). Overrides the personify default fanout. - - `withLoops: false` ⇒ the improvement cycle is a no-op for this run (no refine / - fanout-vote multi-step loop spawns). - - `intelligenceBudgetUsd` ⇒ the intelligence-class spend ceiling carried through for - the billing clamp (passed verbatim; `0` refuses every intelligence spawn). +What an agent reports (via `applied.record`) to enrich the [RunRecord](#runrecord) +sent for its call. All optional — an un-recorded run still sends input/output +with an inference-only zero usage split. `costUsd` without a split is treated +as pure inference (the base stream). #### Properties -##### withAnalyst - -> **withAnalyst**: `boolean` - -Defined in: [intelligence/effort.ts:159](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L159) - -Construct + pass a `ScopeAnalyst`? `false` ⇒ omit it (degrade to the base agent). - -##### fanout - -> **fanout**: `number` - -Defined in: [intelligence/effort.ts:161](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L161) - -`ShapeBudget.fanout` width to pass to `runPersonified`. +##### success? -##### withLoops +> `optional` **success?**: `boolean` -> **withLoops**: `boolean` +Defined in: [intelligence/index.ts:158](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L158) -Defined in: [intelligence/effort.ts:163](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L163) +##### score? -Run the multi-step improvement cycle, or no-op it for this run? +> `optional` **score?**: `number` -##### intelligenceBudgetUsd +Defined in: [intelligence/index.ts:159](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L159) -> **intelligenceBudgetUsd**: `number` \| `null` +##### usage? -Defined in: [intelligence/effort.ts:165](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/effort.ts#L165) +> `optional` **usage?**: `Partial`\<[`UsageSplit`](#usagesplit)\> -Intelligence-class spend ceiling. `0` refuses every intelligence spawn; `null` uncapped. +Defined in: [intelligence/index.ts:160](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L160) -*** +##### costUsd? -### UsageSplit +> `optional` **costUsd?**: `number` -Defined in: [intelligence/index.ts:111](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L111) - -The per-class cost split carried by every trace and outcome. `off` ⇒ -`intelligenceUsd: 0` by construction — there is no intelligence spawn to -bill. This is a classification on the trace, NOT a budget-pool split. +Defined in: [intelligence/index.ts:161](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L161) -#### Properties +##### model? -##### inferenceUsd +> `optional` **model?**: `string` -> **inferenceUsd**: `number` +Defined in: [intelligence/index.ts:162](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L162) -Defined in: [intelligence/index.ts:113](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L113) +##### provider? -Base-stream (model) spend in USD. +> `optional` **provider?**: `string` -##### intelligenceUsd +Defined in: [intelligence/index.ts:163](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L163) -> **intelligenceUsd**: `number` +##### loopEvents? -Defined in: [intelligence/index.ts:115](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L115) +> `optional` **loopEvents?**: [`LoopTraceEvent`](runtime.md#looptraceevent)[] -Intelligence-spawn spend in USD. Provably `0` at the OFF tier. +Defined in: [intelligence/index.ts:164](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L164) *** ### RepoConfig -Defined in: [intelligence/index.ts:121](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L121) +Defined in: [intelligence/index.ts:170](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L170) Repo coordinates a product may declare for the (later) Gated-PR mode. The Observe slice only records their PRESENCE for `doctor()`; it never touches @@ -1192,25 +1259,25 @@ Repo coordinates a product may declare for the (later) Gated-PR mode. The > **owner**: `string` -Defined in: [intelligence/index.ts:122](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L122) +Defined in: [intelligence/index.ts:171](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L171) ##### name > **name**: `string` -Defined in: [intelligence/index.ts:123](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L123) +Defined in: [intelligence/index.ts:172](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L172) ##### baseBranch > **baseBranch**: `string` -Defined in: [intelligence/index.ts:124](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L124) +Defined in: [intelligence/index.ts:173](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L173) *** ### IntelligenceConfig -Defined in: [intelligence/index.ts:130](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L130) +Defined in: [intelligence/index.ts:179](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L179) Client configuration. `project` + `apiKey` are the Observe minimum; the rest tune effort, endpoint, redaction, and (for `doctor()` readiness) @@ -1218,7 +1285,7 @@ Client configuration. `project` + `apiKey` are the Observe minimum; the #### Extended by -- [`DeliveryConfig`](#deliveryconfig) +- [`IntelligenceHookConfig`](#intelligencehookconfig) #### Properties @@ -1226,7 +1293,7 @@ Client configuration. `project` + `apiKey` are the Observe minimum; the > **project**: `string` -Defined in: [intelligence/index.ts:132](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L132) +Defined in: [intelligence/index.ts:181](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L181) Stable project id — the tenant dimension every trace is tagged with. @@ -1234,7 +1301,7 @@ Stable project id — the tenant dimension every trace is tagged with. > `optional` **apiKey?**: `string` -Defined in: [intelligence/index.ts:134](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L134) +Defined in: [intelligence/index.ts:183](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L183) Bearer key for the Intelligence ingest. Reads `TANGLE_API_KEY` when omitted. @@ -1242,26 +1309,27 @@ Bearer key for the Intelligence ingest. Reads `TANGLE_API_KEY` when omitted. > `optional` **effort?**: [`EffortTier`](#efforttier) \| \{ `tier`: [`EffortTier`](#efforttier); `overrides?`: `Partial`\<[`EffortSettings`](#effortsettings)\>; \} -Defined in: [intelligence/index.ts:136](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L136) +Defined in: [intelligence/index.ts:185](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L185) Effort tier (default `'standard'`) plus optional per-field overrides. -##### endpoint? +##### baseUrl? -> `optional` **endpoint?**: `string` +> `optional` **baseUrl?**: `string` -Defined in: [intelligence/index.ts:143](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L143) +Defined in: [intelligence/index.ts:193](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L193) -OTLP ingest base. The underlying exporter appends `/v1/traces`, so point -this at the OTLP route (e.g. `https://intelligence.tangle.tools/v1/otlp`). -Reads `INTELLIGENCE_OTLP_ENDPOINT` then `OTEL_EXPORTER_OTLP_ENDPOINT` when -omitted; absent all three, export is a no-op (best-effort by construction). +The ONE Tangle Intelligence base URL — both the send (OTLP `/v1/otlp`) and +receive (`/v1/profiles/:target/composed`) paths derive from it. Reads +`TANGLE_INTELLIGENCE_URL` when omitted, else `https://intelligence.tangle.tools`. +Send is best-effort and only ships when an `apiKey` is present (the tenant +key the ingest requires); absent a key, export is a no-op. ##### redact? > `optional` **redact?**: `false` \| [`Redactor`](#redactor) -Defined in: [intelligence/index.ts:149](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L149) +Defined in: [intelligence/index.ts:199](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L199) Redaction hook run over every exported input/output. A function replaces the default scrubber; `false` opts out entirely (raw fidelity, caller has @@ -1271,7 +1339,7 @@ sanitized upstream); omitted ⇒ the built-in `defaultRedactor`. > `optional` **surfaces?**: `string`[] -Defined in: [intelligence/index.ts:151](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L151) +Defined in: [intelligence/index.ts:201](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L201) Mutable surfaces a later PR mode would edit. Recorded for `doctor()` only. @@ -1279,7 +1347,7 @@ Mutable surfaces a later PR mode would edit. Recorded for `doctor()` only. > `optional` **checks?**: `string`[] -Defined in: [intelligence/index.ts:153](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L153) +Defined in: [intelligence/index.ts:203](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L203) Verification checks a later PR mode would gate on. Recorded for `doctor()` only. @@ -1287,7 +1355,7 @@ Verification checks a later PR mode would gate on. Recorded for `doctor()` only. > `optional` **repo?**: [`RepoConfig`](#repoconfig) -Defined in: [intelligence/index.ts:155](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L155) +Defined in: [intelligence/index.ts:205](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L205) Repo access a later PR mode would need. Recorded for `doctor()` only. @@ -1295,7 +1363,7 @@ Repo access a later PR mode would need. Recorded for `doctor()` only. ### TraceMeta -Defined in: [intelligence/index.ts:159](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L159) +Defined in: [intelligence/index.ts:209](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L209) Metadata describing one traced run. `runId`/`traceId` default to fresh ids. @@ -1305,7 +1373,7 @@ Metadata describing one traced run. `runId`/`traceId` default to fresh ids. > `optional` **input?**: `unknown` -Defined in: [intelligence/index.ts:161](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L161) +Defined in: [intelligence/index.ts:211](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L211) The run's input — exported through the redactor. @@ -1313,7 +1381,7 @@ The run's input — exported through the redactor. > `optional` **runId?**: `string` -Defined in: [intelligence/index.ts:163](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L163) +Defined in: [intelligence/index.ts:213](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L213) Stable run id. Defaults to a fresh id. @@ -1321,7 +1389,7 @@ Stable run id. Defaults to a fresh id. > `optional` **traceId?**: `string` -Defined in: [intelligence/index.ts:165](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L165) +Defined in: [intelligence/index.ts:215](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L215) 32-hex trace id. Defaults to a fresh id. @@ -1329,7 +1397,7 @@ Defined in: [intelligence/index.ts:165](https://github.com/tangle-network/agent- > `optional` **model?**: `string` -Defined in: [intelligence/index.ts:167](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L167) +Defined in: [intelligence/index.ts:217](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L217) Model id, when known — stamped on the span. @@ -1337,7 +1405,7 @@ Model id, when known — stamped on the span. > `optional` **provider?**: `string` -Defined in: [intelligence/index.ts:169](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L169) +Defined in: [intelligence/index.ts:219](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L219) Provider name, when known — stamped on the span. @@ -1345,7 +1413,7 @@ Provider name, when known — stamped on the span. > `optional` **labels?**: `Record`\<`string`, `string` \| `number` \| `boolean`\> -Defined in: [intelligence/index.ts:171](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L171) +Defined in: [intelligence/index.ts:221](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L221) Arbitrary extra labels (string/number/boolean) stamped on the span. @@ -1353,7 +1421,7 @@ Arbitrary extra labels (string/number/boolean) stamped on the span. ### TraceHandle -Defined in: [intelligence/index.ts:180](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L180) +Defined in: [intelligence/index.ts:230](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L230) The trace handle a `traceRun` body records into. `recordOutput` captures the agent's result (redacted on export); `recordOutcome` captures the scored @@ -1366,7 +1434,7 @@ an un-recorded run still exports a span with whatever was set. > **recordOutput**(`output`): `void` -Defined in: [intelligence/index.ts:182](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L182) +Defined in: [intelligence/index.ts:232](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L232) Capture the run's output. Exported through the redactor. @@ -1384,7 +1452,7 @@ Capture the run's output. Exported through the redactor. > **recordOutcome**(`outcome`): `void` -Defined in: [intelligence/index.ts:189](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L189) +Defined in: [intelligence/index.ts:239](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L239) Capture the run's outcome. `usage` defaults to inference-only (`intelligenceUsd: 0`) — the OFF baseline; an intelligence-enabled run @@ -1419,7 +1487,7 @@ treated as pure inference. ### RecordTraceMeta -Defined in: [intelligence/index.ts:198](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L198) +Defined in: [intelligence/index.ts:248](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L248) Metadata for [IntelligenceClient.recordTrace](#recordtrace). @@ -1429,7 +1497,7 @@ Metadata for [IntelligenceClient.recordTrace](#recordtrace). > `optional` **traceId?**: `string` -Defined in: [intelligence/index.ts:200](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L200) +Defined in: [intelligence/index.ts:250](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L250) 32-hex trace id to anchor every span to. Defaults to a fresh id. @@ -1437,7 +1505,7 @@ Defined in: [intelligence/index.ts:200](https://github.com/tangle-network/agent- > `optional` **rootParentSpanId?**: `string` -Defined in: [intelligence/index.ts:203](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L203) +Defined in: [intelligence/index.ts:253](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L253) Span id of an enclosing span the loop root should parent under (e.g. a `traceRun` span). Omitted ⇒ the loop root is the trace root. @@ -1446,7 +1514,7 @@ Span id of an enclosing span the loop root should parent under (e.g. a ### TraceOutcome -Defined in: [intelligence/index.ts:208](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L208) +Defined in: [intelligence/index.ts:258](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L258) The resolved outcome of one traced run, surfaced on the export span and available to the caller for downstream billing assertions. @@ -1457,25 +1525,25 @@ The resolved outcome of one traced run, surfaced on the export span and > **runId**: `string` -Defined in: [intelligence/index.ts:209](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L209) +Defined in: [intelligence/index.ts:259](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L259) ##### traceId > **traceId**: `string` -Defined in: [intelligence/index.ts:210](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L210) +Defined in: [intelligence/index.ts:260](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L260) ##### project > **project**: `string` -Defined in: [intelligence/index.ts:211](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L211) +Defined in: [intelligence/index.ts:261](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L261) ##### effort > **effort**: [`EffortSettings`](#effortsettings) -Defined in: [intelligence/index.ts:213](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L213) +Defined in: [intelligence/index.ts:263](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L263) The resolved effort settings this run executed under. @@ -1483,7 +1551,7 @@ The resolved effort settings this run executed under. > **intelligenceOff**: `boolean` -Defined in: [intelligence/index.ts:215](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L215) +Defined in: [intelligence/index.ts:265](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L265) True when this run ran as pure passthrough (the OFF floor). @@ -1491,19 +1559,19 @@ True when this run ran as pure passthrough (the OFF floor). > `optional` **success?**: `boolean` -Defined in: [intelligence/index.ts:216](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L216) +Defined in: [intelligence/index.ts:266](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L266) ##### score? > `optional` **score?**: `number` -Defined in: [intelligence/index.ts:217](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L217) +Defined in: [intelligence/index.ts:267](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L267) ##### usage > **usage**: [`UsageSplit`](#usagesplit) -Defined in: [intelligence/index.ts:219](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L219) +Defined in: [intelligence/index.ts:269](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L269) Per-class billing split. `intelligenceUsd` is `0` at the OFF tier. @@ -1511,7 +1579,7 @@ Per-class billing split. `intelligenceUsd` is `0` at the OFF tier. ### IntelligenceClient -Defined in: [intelligence/index.ts:223](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L223) +Defined in: [intelligence/index.ts:273](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L273) The Observe-mode Intelligence client. @@ -1521,7 +1589,7 @@ The Observe-mode Intelligence client. > `readonly` **project**: `string` -Defined in: [intelligence/index.ts:225](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L225) +Defined in: [intelligence/index.ts:275](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L275) The resolved project id. @@ -1529,7 +1597,7 @@ The resolved project id. > `readonly` **effort**: [`EffortSettings`](#effortsettings) -Defined in: [intelligence/index.ts:227](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L227) +Defined in: [intelligence/index.ts:277](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L277) The resolved effort settings. @@ -1539,7 +1607,7 @@ The resolved effort settings. > **traceRun**\<`T`\>(`meta`, `fn`): `Promise`\<`T`\> -Defined in: [intelligence/index.ts:233](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L233) +Defined in: [intelligence/index.ts:283](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L283) Run `fn` under a trace, export one span best-effort, and return whatever `fn` returns. Telemetry-export failures are swallowed; an error THROWN by @@ -1569,7 +1637,7 @@ Run `fn` under a trace, export one span best-effort, and return whatever > **recordTrace**(`events`, `meta?`): `string` -Defined in: [intelligence/index.ts:243](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L243) +Defined in: [intelligence/index.ts:293](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L293) Export a run's full loop topology — the ordered `LoopTraceEvent` stream a `runLoop`/`Supervisor` run emits — as a nested OTLP span tree (loop → round → @@ -1593,11 +1661,57 @@ readonly [`LoopTraceEvent`](runtime.md#looptraceevent)[] `string` +##### exportRunRecord() + +> **exportRunRecord**(`record`): `string` + +Defined in: [intelligence/index.ts:301](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L301) + +Send one typed [RunRecord](#runrecord) — the run's flat span (input/output/outcome/ +usage/model/provider, redacted) plus, when `loopEvents` are present, the +nested loop topology under the same `traceId`. Reuses the shipped +`flatOtelSpan` + `buildLoopOtelSpans` builders (no second builder). +Best-effort: export failures are swallowed. Returns the record's `traceId`. + +###### Parameters + +###### record + +[`RunRecord`](#runrecord) + +###### Returns + +`string` + +##### freshRunId() + +> **freshRunId**(): `string` + +Defined in: [intelligence/index.ts:303](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L303) + +Mint a fresh run id (`run-`). + +###### Returns + +`string` + +##### freshTraceId() + +> **freshTraceId**(): `string` + +Defined in: [intelligence/index.ts:305](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L305) + +Mint a fresh 32-hex trace id. + +###### Returns + +`string` + ##### doctor() > **doctor**(): [`DoctorReport`](#doctorreport) -Defined in: [intelligence/index.ts:249](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L249) +Defined in: [intelligence/index.ts:311](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L311) Network-free readiness report: which adoption modes are reachable given this config. Observe is always reachable; Recommend needs outcomes; PR @@ -1611,7 +1725,7 @@ needs checks + surfaces + repo. > **flush**(): `Promise`\<`void`\> -Defined in: [intelligence/index.ts:251](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L251) +Defined in: [intelligence/index.ts:313](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L313) Flush any pending export spans. Best-effort; resolves even if export fails. @@ -1623,7 +1737,7 @@ Flush any pending export spans. Best-effort; resolves even if export fails. ### ModeReadiness -Defined in: [intelligence/index.ts:255](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L255) +Defined in: [intelligence/index.ts:317](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L317) One mode's readiness verdict. @@ -1633,13 +1747,13 @@ One mode's readiness verdict. > **ready**: `boolean` -Defined in: [intelligence/index.ts:256](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L256) +Defined in: [intelligence/index.ts:318](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L318) ##### missing > **missing**: `string`[] -Defined in: [intelligence/index.ts:258](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L258) +Defined in: [intelligence/index.ts:320](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L320) Inputs this mode still needs, when not ready. Empty when ready. @@ -1647,7 +1761,7 @@ Inputs this mode still needs, when not ready. Empty when ready. ### DoctorReport -Defined in: [intelligence/index.ts:262](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L262) +Defined in: [intelligence/index.ts:324](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L324) The `doctor()` readiness report — Mode-readiness without any network call. @@ -1657,19 +1771,19 @@ The `doctor()` readiness report — Mode-readiness without any network call. > **project**: `string` -Defined in: [intelligence/index.ts:263](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L263) +Defined in: [intelligence/index.ts:325](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L325) ##### effort > **effort**: [`EffortSettings`](#effortsettings) -Defined in: [intelligence/index.ts:264](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L264) +Defined in: [intelligence/index.ts:326](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L326) ##### exportConfigured > **exportConfigured**: `boolean` -Defined in: [intelligence/index.ts:266](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L266) +Defined in: [intelligence/index.ts:328](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L328) True when an OTLP endpoint is configured (export will actually ship). @@ -1677,7 +1791,7 @@ True when an OTLP endpoint is configured (export will actually ship). > **modes**: `object` -Defined in: [intelligence/index.ts:267](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L267) +Defined in: [intelligence/index.ts:329](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L329) ###### observe @@ -1823,77 +1937,352 @@ client (the spine does not import the sandbox executor). Absent ⇒ ##### provisionHost? -> `optional` **provisionHost?**: (`host`, `inner`, `costTag`) => `Promise`\<[`ProvisionedHost`](#provisionedhost)\> +> `optional` **provisionHost?**: (`host`, `inner`, `costTag`) => `Promise`\<[`ProvisionedHost`](#provisionedhost)\> + +Defined in: [intelligence/resolver.ts:93](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/resolver.ts#L93) + +Provision a host for a `process-on-infra` binding, then serve the inner +binding inside it. Injected by the host that owns `createExecutor`. Absent ⇒ +`process-on-infra` bindings fail loud. The provider resolves the inner +binding INSIDE the host and returns the connection + a teardown. + +###### Parameters + +###### host + +[`HostSpec`](#hostspec) + +###### inner + +[`DeliveryBinding`](#deliverybinding) + +###### costTag + +`string` + +###### Returns + +`Promise`\<[`ProvisionedHost`](#provisionedhost)\> + +##### probeLiveToolNames? + +> `optional` **probeLiveToolNames?**: (`capabilityId`) => `Promise`\<`string`[]\> + +Defined in: [intelligence/resolver.ts:106](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/resolver.ts#L106) + +Drift probe: return the LIVE tool names a resolved surface exposes for a +given capability id (a `tools/list` over an mcp connection, the agent's +actual registered tool names for a host tool). When present, the post-resolve +drift check drops any tool/mcp whose live names diverge from the certified +interface — the only callable surfaces are gate-blessed ones. Absent ⇒ the +check enforces only the host-side executor↔spec parity (no live probe). + +###### Parameters + +###### capabilityId + +`string` + +###### Returns + +`Promise`\<`string`[]\> + +##### onDrop? + +> `optional` **onDrop?**: (`capabilityId`, `error`) => `void` + +Defined in: [intelligence/resolver.ts:114](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/resolver.ts#L114) + +Observe a DROPPED capability — a per-capability resolve failure that is +fail-closed (the capability is omitted, never half-wired). The drop is the +contract; this surfaces the diagnostic so it is never silently erased. NOT +called for [CapabilityNotAdmittedError](#capabilitynotadmittederror) (that rethrows — a manifest +carrying an un-admitted binding kind is a hard error, not a soft drop). + +###### Parameters + +###### capabilityId + +`string` + +###### error + +`Error` + +###### Returns + +`void` + +*** + +### AppliedIntelligence + +Defined in: [intelligence/with-intelligence.ts:53](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L53) + +What the hook hands the agent each run. Additive over the prompt-only + delivery: `composePrompt` folds the certified prompt surface (as before); + `proposals`/`applyProfile` surface the promoted profile DIFFS — never + auto-applied; `record` enriches the [RunRecord](#runrecord) that is sent. + +#### Properties + +##### certified + +> **certified**: [`CertifiedProfile`](#certifiedprofile) \| `null` + +Defined in: [intelligence/with-intelligence.ts:56](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L56) + +The certified profile in effect (null when none promoted / pull failed — + fail-closed: the agent runs on its base surface). + +##### proposals + +> **proposals**: [`ProposedProfileDiff`](#proposedprofilediff)[] + +Defined in: [intelligence/with-intelligence.ts:62](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L62) + +The promoted, gate-certified profile diffs — surfaced for a human or the + gated `improve()` loop. NEVER auto-applied by this hook. Empty when none. + +#### Methods + +##### composePrompt() + +> **composePrompt**(`base`): `string` + +Defined in: [intelligence/with-intelligence.ts:59](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L59) + +Fold the certified prompt surface into a base system prompt (the promoted + prompt). The consumer opts in by calling it. + +###### Parameters + +###### base + +`string` + +###### Returns + +`string` + +##### applyProfile() + +> **applyProfile**(`base`): `AgentProfile` + +Defined in: [intelligence/with-intelligence.ts:66](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L66) + +Fold every proposal into `base` via `applyAgentProfileDiff`, in promotion + order, and return the result. The caller invokes this EXPLICITLY (it is the + human/gated apply step) — the hook never calls it on the run path. + +###### Parameters + +###### base + +`AgentProfile` + +###### Returns + +`AgentProfile` + +##### record() + +> **record**(`report`): `void` + +Defined in: [intelligence/with-intelligence.ts:70](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L70) + +Enrich the [RunRecord](#runrecord) sent for this call — outcome, usage split, + model/provider, and the loop event stream. Optional; an un-recorded run + still sends input/output with an inference-only zero usage split. + +###### Parameters + +###### report + +[`RunReport`](#runreport) + +###### Returns + +`void` + +*** + +### IntelligenceHookConfig + +Defined in: [intelligence/with-intelligence.ts:80](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L80) + +`withIntelligence` config = the Observe config plus the pull target, refresh + cadence, and a proposals callback. One base URL (`baseUrl` / + `TANGLE_INTELLIGENCE_URL`) drives both the send and receive paths. + +#### Extends + +- [`IntelligenceConfig`](#intelligenceconfig) + +#### Properties + +##### project + +> **project**: `string` + +Defined in: [intelligence/index.ts:181](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L181) + +Stable project id — the tenant dimension every trace is tagged with. + +###### Inherited from + +[`IntelligenceConfig`](#intelligenceconfig).[`project`](#project-1) + +##### apiKey? + +> `optional` **apiKey?**: `string` + +Defined in: [intelligence/index.ts:183](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L183) + +Bearer key for the Intelligence ingest. Reads `TANGLE_API_KEY` when omitted. + +###### Inherited from + +[`IntelligenceConfig`](#intelligenceconfig).[`apiKey`](#apikey-2) + +##### effort? + +> `optional` **effort?**: [`EffortTier`](#efforttier) \| \{ `tier`: [`EffortTier`](#efforttier); `overrides?`: `Partial`\<[`EffortSettings`](#effortsettings)\>; \} + +Defined in: [intelligence/index.ts:185](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L185) + +Effort tier (default `'standard'`) plus optional per-field overrides. + +###### Inherited from + +[`IntelligenceConfig`](#intelligenceconfig).[`effort`](#effort) + +##### baseUrl? + +> `optional` **baseUrl?**: `string` + +Defined in: [intelligence/index.ts:193](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L193) + +The ONE Tangle Intelligence base URL — both the send (OTLP `/v1/otlp`) and +receive (`/v1/profiles/:target/composed`) paths derive from it. Reads +`TANGLE_INTELLIGENCE_URL` when omitted, else `https://intelligence.tangle.tools`. +Send is best-effort and only ships when an `apiKey` is present (the tenant +key the ingest requires); absent a key, export is a no-op. + +###### Inherited from + +[`IntelligenceConfig`](#intelligenceconfig).[`baseUrl`](#baseurl-2) + +##### redact? + +> `optional` **redact?**: `false` \| [`Redactor`](#redactor) + +Defined in: [intelligence/index.ts:199](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L199) + +Redaction hook run over every exported input/output. A function replaces +the default scrubber; `false` opts out entirely (raw fidelity, caller has +sanitized upstream); omitted ⇒ the built-in `defaultRedactor`. + +###### Inherited from + +[`IntelligenceConfig`](#intelligenceconfig).[`redact`](#redact) + +##### surfaces? + +> `optional` **surfaces?**: `string`[] + +Defined in: [intelligence/index.ts:201](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L201) + +Mutable surfaces a later PR mode would edit. Recorded for `doctor()` only. + +###### Inherited from + +[`IntelligenceConfig`](#intelligenceconfig).[`surfaces`](#surfaces) + +##### checks? + +> `optional` **checks?**: `string`[] + +Defined in: [intelligence/index.ts:203](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L203) + +Verification checks a later PR mode would gate on. Recorded for `doctor()` only. + +###### Inherited from + +[`IntelligenceConfig`](#intelligenceconfig).[`checks`](#checks) + +##### repo? + +> `optional` **repo?**: [`RepoConfig`](#repoconfig) + +Defined in: [intelligence/index.ts:205](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L205) + +Repo access a later PR mode would need. Recorded for `doctor()` only. + +###### Inherited from + +[`IntelligenceConfig`](#intelligenceconfig).[`repo`](#repo) + +##### target? -Defined in: [intelligence/resolver.ts:93](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/resolver.ts#L93) +> `optional` **target?**: `string` -Provision a host for a `process-on-infra` binding, then serve the inner -binding inside it. Injected by the host that owns `createExecutor`. Absent ⇒ -`process-on-infra` bindings fail loud. The provider resolves the inner -binding INSIDE the host and returns the connection + a teardown. +Defined in: [intelligence/with-intelligence.ts:82](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L82) -###### Parameters +Pull target. Defaults to `project`. -###### host +##### refreshMs? -[`HostSpec`](#hostspec) +> `optional` **refreshMs?**: `number` -###### inner +Defined in: [intelligence/with-intelligence.ts:84](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L84) -[`DeliveryBinding`](#deliverybinding) +Min interval between certified-profile pulls. Default 5m. -###### costTag +##### timeoutMs? -`string` +> `optional` **timeoutMs?**: `number` -###### Returns +Defined in: [intelligence/with-intelligence.ts:86](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L86) -`Promise`\<[`ProvisionedHost`](#provisionedhost)\> +Per-pull timeout in ms (fail-closed on a hung plane). Default 10000. -##### probeLiveToolNames? +##### fetchImpl? -> `optional` **probeLiveToolNames?**: (`capabilityId`) => `Promise`\<`string`[]\> +> `optional` **fetchImpl?**: (`input`, `init?`) => `Promise`\<`Response`\> -Defined in: [intelligence/resolver.ts:106](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/resolver.ts#L106) +Defined in: [intelligence/with-intelligence.ts:88](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L88) -Drift probe: return the LIVE tool names a resolved surface exposes for a -given capability id (a `tools/list` over an mcp connection, the agent's -actual registered tool names for a host tool). When present, the post-resolve -drift check drops any tool/mcp whose live names diverge from the certified -interface — the only callable surfaces are gate-blessed ones. Absent ⇒ the -check enforces only the host-side executor↔spec parity (no live probe). +fetch impl for the pull (tests). Defaults to global fetch. ###### Parameters -###### capabilityId +###### input -`string` +`string` \| `URL` \| `Request` -###### Returns +###### init? -`Promise`\<`string`[]\> +`RequestInit` -##### onDrop? +###### Returns -> `optional` **onDrop?**: (`capabilityId`, `error`) => `void` +`Promise`\<`Response`\> -Defined in: [intelligence/resolver.ts:114](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/resolver.ts#L114) +##### onProposals? -Observe a DROPPED capability — a per-capability resolve failure that is -fail-closed (the capability is omitted, never half-wired). The drop is the -contract; this surfaces the diagnostic so it is never silently erased. NOT -called for [CapabilityNotAdmittedError](#capabilitynotadmittederror) (that rethrows — a manifest -carrying an un-admitted binding kind is a hard error, not a soft drop). +> `optional` **onProposals?**: (`proposals`) => `void` -###### Parameters +Defined in: [intelligence/with-intelligence.ts:91](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L91) -###### capabilityId +Notified when a refresh delivers a NEW set of promoted proposals (by + provenance content hash). Surfaces diffs without auto-applying them. -`string` +###### Parameters -###### error +###### proposals -`Error` +[`ProposedProfileDiff`](#proposedprofilediff)[] ###### Returns @@ -1982,48 +2371,13 @@ Every binding kind — the open set the resolver dispatches over. > **PullOutcome** = \{ `succeeded`: `true`; `value`: [`CertifiedProfile`](#certifiedprofile); \} \| \{ `succeeded`: `false`; `error`: `string`; `status?`: `number`; \} -Defined in: [intelligence/delivery.ts:64](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L64) +Defined in: [intelligence/delivery.ts:104](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L104) Typed outcome for the pull — inspect `succeeded` before `value`. A 404 (nothing promoted yet) is a normal, non-error `succeeded: false`. *** -### DeliveredAgent - -> **DeliveredAgent**\<`I`, `O`\> = (`input`, `applied`) => `Promise`\<`O`\> - -Defined in: [intelligence/delivery.ts:256](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L256) - -An agent wrapped by [withCertifiedDelivery](#withcertifieddelivery): receives the input plus - the certified intelligence delivered for this run. - -#### Type Parameters - -##### I - -`I` - -##### O - -`O` - -#### Parameters - -##### input - -`I` - -##### applied - -[`AppliedIntelligence`](#appliedintelligence) - -#### Returns - -`Promise`\<`O`\> - -*** - ### EffortTier > **EffortTier** = `"off"` \| `"eco"` \| `"standard"` \| `"thorough"` \| `"max"` @@ -2062,7 +2416,7 @@ Per-field overrides applied on top of a tier preset. Any subset of the > **UsageClass** = `"inference"` \| `"intelligence"` -Defined in: [intelligence/index.ts:104](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L104) +Defined in: [intelligence/index.ts:113](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L113) Usage class for billing. Base-stream tokens bill `'inference'`; every intelligence spawn (analyst, corpus, loop) bills `'intelligence'`. The @@ -2070,65 +2424,98 @@ Usage class for billing. Base-stream tokens bill `'inference'`; every *** -### Agent +### Redactor + +> **Redactor** = (`value`) => `unknown` + +Defined in: [intelligence/redact.ts:18](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/redact.ts#L18) + +A redactor maps an arbitrary trace value to a safe-to-export value. Pure; + must not throw on cyclic input (the default tolerates cycles). + +#### Parameters + +##### value + +`unknown` + +#### Returns + +`unknown` + +*** + +### IntelligenceAgent -> **Agent**\<`TInput`, `TOutput`\> = (`input`) => `Promise`\<`TOutput`\> +> **IntelligenceAgent**\<`I`, `O`\> = (`input`, `applied`) => `Promise`\<`O`\> -Defined in: [intelligence/index.ts:520](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L520) +Defined in: [intelligence/with-intelligence.ts:75](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L75) -A generic agent: one async input → output. The shape `withTangleIntelligence` - preserves exactly. +An agent wrapped by [withIntelligence](#withintelligence): receives the input plus the + intelligence delivered for this run. #### Type Parameters -##### TInput +##### I -`TInput` +`I` -##### TOutput +##### O -`TOutput` +`O` #### Parameters ##### input -`TInput` +`I` + +##### applied + +[`AppliedIntelligence`](#appliedintelligence) #### Returns -`Promise`\<`TOutput`\> +`Promise`\<`O`\> *** -### ClientOrConfig +### IntelligenceWrapped -> **ClientOrConfig** = [`IntelligenceClient`](#intelligenceclient) \| [`IntelligenceConfig`](#intelligenceconfig) +> **IntelligenceWrapped**\<`I`, `O`\> = (`input`) => `Promise`\<`O`\> & `object` -Defined in: [intelligence/index.ts:523](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L523) +Defined in: [intelligence/with-intelligence.ts:96](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L96) -Either a built client or the config to build one. +The wrapped agent — same `(input) => Promise` shape, plus a manual + `refresh()` and a `proposals()` accessor for the currently-promoted diffs. -*** +#### Type Declaration -### Redactor +##### refresh() -> **Redactor** = (`value`) => `unknown` +> **refresh**(): `Promise`\<`void`\> -Defined in: [intelligence/redact.ts:18](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/redact.ts#L18) +###### Returns -A redactor maps an arbitrary trace value to a safe-to-export value. Pure; - must not throw on cyclic input (the default tolerates cycles). +`Promise`\<`void`\> -#### Parameters +##### proposals() -##### value +> **proposals**(): [`ProposedProfileDiff`](#proposedprofilediff)[] -`unknown` +###### Returns -#### Returns +[`ProposedProfileDiff`](#proposedprofilediff)[] -`unknown` +#### Type Parameters + +##### I + +`I` + +##### O + +`O` ## Variables @@ -2169,11 +2556,55 @@ This delivers the spine against today's wire before the plane changes. *** +### resolveIntelligenceBaseUrl() + +> **resolveIntelligenceBaseUrl**(`baseUrl`): `string` + +Defined in: [intelligence/delivery.ts:128](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L128) + +Resolve the ONE Intelligence base URL — the single knob both the send and + receive paths derive from. Env fallback: `TANGLE_INTELLIGENCE_URL`. + +#### Parameters + +##### baseUrl + +`string` \| `undefined` + +#### Returns + +`string` + +*** + +### normalizeCertifiedProfile() + +> **normalizeCertifiedProfile**(`raw`): [`CertifiedProfile`](#certifiedprofile) + +Defined in: [intelligence/delivery.ts:163](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L163) + +Deserialize the composed-endpoint response into a `CertifiedProfile`. The +previously-dropped `agentProfileDiffs`/`capabilities`/`agentProfile` are read +here so they round-trip to the consumer; a plane that has not yet promoted any +diffs simply yields empty arrays / a null profile (fail-closed, never a crash). + +#### Parameters + +##### raw + +`unknown` + +#### Returns + +[`CertifiedProfile`](#certifiedprofile) + +*** + ### pullCertified() > **pullCertified**(`opts`): `Promise`\<[`PullOutcome`](#pulloutcome)\> -Defined in: [intelligence/delivery.ts:107](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L107) +Defined in: [intelligence/delivery.ts:193](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L193) Pull the certified composed profile for a target. Fail-closed: a network error or a non-2xx returns a typed `succeeded: false` (never throws), so a @@ -2196,7 +2627,7 @@ the normal "nothing promoted yet" signal, carried as `status: 404`. > **composeCertifiedPrompt**(`base`, `certified`): `string` -Defined in: [intelligence/delivery.ts:167](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L167) +Defined in: [intelligence/delivery.ts:253](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L253) Fold the certified prompt surface (and any certified prompt-folding artifacts: `prompt-surface` / `skill` / `instructions`) into a base system prompt under a @@ -2204,7 +2635,7 @@ marked section, so the deployed agent prompt == base + the gate-certified additions. Order is stable (prompt surface first, then artifact buckets in `promptFoldTypes` order, then by path within a bucket) so the same profile renders byte-identically each call. Returns `base` unchanged when there is no -usable certified content. +usable certified content. Reads only the prompt-folding slice of a profile. #### Parameters @@ -2214,7 +2645,7 @@ usable certified content. ##### certified -[`CertifiedProfile`](#certifiedprofile) \| `null` +`Pick`\<[`CertifiedProfile`](#certifiedprofile), `"promptSurface"` \| `"artifacts"`\> \| `null` #### Returns @@ -2226,12 +2657,12 @@ usable certified content. > **createCertifiedPromptSource**(`opts`): [`CertifiedPromptSource`](#certifiedpromptsource) -Defined in: [intelligence/delivery.ts:210](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L210) +Defined in: [intelligence/delivery.ts:299](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L299) Create the cached certified-prompt source — the ONE module-scope-cache + coalesced-refresh + keep-last-known implementation. Product wiring uses this -rather than hand-rolling the same lines around `pullCertified`, and -[withCertifiedDelivery](#withcertifieddelivery) is built on it. +rather than hand-rolling the same lines around `pullCertified`. The +`withIntelligence` hook rides this same source for its prompt delivery. #### Parameters @@ -2245,45 +2676,6 @@ rather than hand-rolling the same lines around `pullCertified`, and *** -### withCertifiedDelivery() - -> **withCertifiedDelivery**\<`I`, `O`\>(`agent`, `config`): (`input`) => `Promise`\<`O`\> & `object` - -Defined in: [intelligence/delivery.ts:281](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/delivery.ts#L281) - -Wrap an agent so it (a) Observes each run via the shipped Observe client and -(b) RECEIVES the tenant's certified artifacts pulled from the deployed plane. -The certified profile is cached and refreshed at most every `refreshMs`; a -failed pull is fail-closed — the agent runs on its base surface and never -breaks because Intelligence is unreachable. When the plane promotes a new -gate-certified surface, the next refresh delivers it to the running agent. - -#### Type Parameters - -##### I - -`I` - -##### O - -`O` - -#### Parameters - -##### agent - -[`DeliveredAgent`](#deliveredagent)\<`I`, `O`\> - -##### config - -[`DeliveryConfig`](#deliveryconfig) - -#### Returns - -(`input`) => `Promise`\<`O`\> & `object` - -*** - ### resolveEffort() > **resolveEffort**(`tier`, `overrides?`): [`EffortSettings`](#effortsettings) @@ -2372,12 +2764,12 @@ compile to `withAnalyst: true`, the tier's `fanout`, and `withLoops: true`. > **createIntelligenceClient**(`config`): [`IntelligenceClient`](#intelligenceclient) -Defined in: [intelligence/index.ts:331](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L331) +Defined in: [intelligence/index.ts:387](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L387) -Create an Observe-mode Intelligence client. Resolves effort, endpoint, and -redactor up front; the exporter is built lazily and is `undefined` when no -endpoint is configured (export becomes a no-op — best-effort by -construction). +Create an Observe-mode Intelligence client. Resolves effort, the base URL, and +the redactor up front; the exporter is built lazily and is `undefined` when no +`apiKey` is present (send becomes a no-op — the ingest requires a tenant key, +and best-effort export must never spam an unauthenticated plane). #### Parameters @@ -2391,46 +2783,6 @@ construction). *** -### withTangleIntelligence() - -> **withTangleIntelligence**\<`TInput`, `TOutput`\>(`agent`, `clientOrConfig`): [`Agent`](#agent)\<`TInput`, `TOutput`\> - -Defined in: [intelligence/index.ts:538](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/index.ts#L538) - -Wrap a generic `agent` with best-effort Observe-mode tracing, returning the -SAME shape. Each call runs the agent under a trace and exports one span; an -export failure is swallowed (the live agent never fails because Intelligence -is down) but an error from the agent itself propagates unchanged. - -At `effort: 'off'` this is pure passthrough plus best-effort telemetry — -zero intelligence spawns, `intelligenceUsd: 0` on the trace. - -#### Type Parameters - -##### TInput - -`TInput` - -##### TOutput - -`TOutput` - -#### Parameters - -##### agent - -[`Agent`](#agent)\<`TInput`, `TOutput`\> - -##### clientOrConfig - -[`ClientOrConfig`](#clientorconfig) - -#### Returns - -[`Agent`](#agent)\<`TInput`, `TOutput`\> - -*** - ### defaultRedactor() > **defaultRedactor**(`value`): `unknown` @@ -2518,7 +2870,7 @@ Fail-closed: a `null` manifest returns the base surface only. > **composeCertifiedProfileFromWire**(`base`, `profile`, `ctx?`): `Promise`\<[`ResolvedSurface`](#resolvedsurface)\> -Defined in: [intelligence/resolver.ts:664](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/resolver.ts#L664) +Defined in: [intelligence/resolver.ts:660](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/resolver.ts#L660) Lower a plane `CertifiedProfile` straight into a `ResolvedSurface` via `manifestFromProfile` — the convenience the shipped pull lane calls when it @@ -2543,3 +2895,43 @@ Lower a plane `CertifiedProfile` straight into a `ResolvedSurface` via #### Returns `Promise`\<[`ResolvedSurface`](#resolvedsurface)\> + +*** + +### withIntelligence() + +> **withIntelligence**\<`I`, `O`\>(`agent`, `config`): [`IntelligenceWrapped`](#intelligencewrapped)\<`I`, `O`\> + +Defined in: [intelligence/with-intelligence.ts:110](https://github.com/tangle-network/agent-runtime/blob/main/src/intelligence/with-intelligence.ts#L110) + +Wrap an agent so it (a) RECEIVES the tenant's certified profile — the prompt +surface to fold and the promoted profile diffs as proposals — and (b) SENDS a +typed [RunRecord](#runrecord) per call to the plane. The pull is cached and refreshed +at most every `refreshMs`; a failed pull is fail-closed (the agent runs on its +base surface, never breaks because Intelligence is unreachable). The send is +best-effort — an export failure never fails the agent's turn — while an error +thrown by the agent itself propagates unchanged. + +#### Type Parameters + +##### I + +`I` + +##### O + +`O` + +#### Parameters + +##### agent + +[`IntelligenceAgent`](#intelligenceagent)\<`I`, `O`\> + +##### config + +[`IntelligenceHookConfig`](#intelligencehookconfig) + +#### Returns + +[`IntelligenceWrapped`](#intelligencewrapped)\<`I`, `O`\> diff --git a/docs/api/primitive-catalog.md b/docs/api/primitive-catalog.md index 2c3c6daa..88adf1ce 100644 --- a/docs/api/primitive-catalog.md +++ b/docs/api/primitive-catalog.md @@ -198,7 +198,7 @@ Import from `@tangle-network/agent-runtime/agent` — 48 exports. ### Intelligence SDK — Observe + provable-OFF billing -Import from `@tangle-network/agent-runtime/intelligence` — 63 exports. +Import from `@tangle-network/agent-runtime/intelligence` — 68 exports. | Symbol | Kind | Summary | |---|---|---| @@ -207,35 +207,39 @@ Import from `@tangle-network/agent-runtime/intelligence` — 63 exports. | `composeCertifiedProfileFromWire` | function | Lower a plane `CertifiedProfile` straight into a `ResolvedSurface` via | | `composeCertifiedPrompt` | function | Fold the certified prompt surface (and any certified prompt-folding artifacts: | | `createCertifiedPromptSource` | function | Create the cached certified-prompt source — the ONE module-scope-cache + | -| `createIntelligenceClient` | function | Create an Observe-mode Intelligence client. Resolves effort, endpoint, and | +| `createIntelligenceClient` | function | Create an Observe-mode Intelligence client. Resolves effort, the base URL, and | | `defaultRedactor` | function | The built-in redactor. Walks objects and arrays; replaces values under | | `isIntelligenceOff` | function | True when these settings admit NO intelligence spawn — the passthrough | | `manifestFromProfile` | function | Lower the EXISTING plane wire (`CertifiedProfile`) into a `CapabilityManifest`. | +| `normalizeCertifiedProfile` | function | Deserialize the composed-endpoint response into a `CertifiedProfile`. The | | `pullCertified` | function | Pull the certified composed profile for a target. Fail-closed: a network | | `resolveEffort` | function | Compile a named tier (plus optional per-field overrides) into the flat | +| `resolveIntelligenceBaseUrl` | function | Resolve the ONE Intelligence base URL — the single knob both the send and | | `resolveRedactor` | function | Resolve the redactor a client uses. A caller-supplied hook replaces the | -| `withCertifiedDelivery` | function | Wrap an agent so it (a) Observes each run via the shipped Observe client and | -| `withTangleIntelligence` | function | Wrap a generic `agent` with best-effort Observe-mode tracing, returning the | +| `withIntelligence` | function | Wrap an agent so it (a) RECEIVES the tenant's certified profile — the prompt | | `defaultEffortTier` | const | The default tier when a client declares no effort. `'standard'` turns | | `CapabilityNotAdmittedError` | class | A binding kind whose resolver case is typed but not yet admitted (rag-index, | -| `AppliedIntelligence` | interface | What the delivery wrapper hands the agent each run. | +| `AppliedIntelligence` | interface | What the hook hands the agent each run. Additive over the prompt-only | | `CapabilityManifest` | interface | The strict generalization of `CertifiedProfile`. `promptSurface` is kept | | `CertifiedArtifact` | interface | A promoted, certified artifact (one entry in the composed profile). | | `CertifiedCapability` | interface | One certified unit of agent power. | +| `CertifiedCapabilitySummary` | interface | The composed endpoint's per-capability summary — the narrow shape on the | | `CertifiedProfile` | interface | The composed certified profile — exactly the shape the plane's | | `CertifiedPromptSource` | interface | A cached, self-refreshing source of a target's certified prompt additions — | | `CertifiedPromptSourceOptions` | interface | Options for {@link createCertifiedPromptSource} — the pull coordinates plus | | `CertifiedPromptSurface` | interface | The active promoted prompt surface for a target. | | `CertProvenance` | interface | The certify lane's held-out lift travelling WITH delivery. The shipped | | `CredentialRef` | interface | A named secret a binding requires — declared, never carried. | -| `DeliveryConfig` | interface | Delivery config = the Observe config plus the pull target + refresh cadence. | +| `DiffProvenance` | interface | The held-out provenance the plane's certify step stamps on a promoted diff. | | `DoctorReport` | interface | The `doctor()` readiness report — Mode-readiness without any network call. | | `EffortOverridesCompiled` | interface | The run-config overrides an `EffortSettings` compiles to — the bridge between the | | `EffortSettings` | interface | The flat, resolved settings a tier compiles to. Every field is individually | | `HostSpec` | interface | The host a `process-on-infra` binding provisions before its inner binding. | | `IntelligenceClient` | interface | The Observe-mode Intelligence client. | | `IntelligenceConfig` | interface | Client configuration. `project` + `apiKey` are the Observe minimum; the | +| `IntelligenceHookConfig` | interface | `withIntelligence` config = the Observe config plus the pull target, refresh | | `ModeReadiness` | interface | One mode's readiness verdict. | +| `ProposedProfileDiff` | interface | A gate-certified profile diff the plane has already promoted, plus the | | `ProvisionedHost` | interface | A live, provisioned host the resolver tore up for a `process-on-infra` arm. | | `RecordTraceMeta` | interface | Metadata for {@link IntelligenceClient.recordTrace}. | | `RepoConfig` | interface | Repo coordinates a product may declare for the (later) Gated-PR mode. The | @@ -244,22 +248,23 @@ Import from `@tangle-network/agent-runtime/intelligence` — 63 exports. | `ResolvedRetrieval` | interface | One retrieval handle. The agent never learns vector vs graph vs index. | | `ResolvedSubagent` | interface | One resolved subagent — folded into `AgentProfile.subagents`. | | `ResolvedSurface` | interface | What `composeCertifiedProfile` produces. Every binding fans into the same | +| `RunRecord` | interface | The typed record `withIntelligence` sends per call — serialized through the | +| `RunReport` | interface | What an agent reports (via `applied.record`) to enrich the {@link RunRecord} | | `TraceHandle` | interface | The trace handle a `traceRun` body records into. `recordOutput` captures the | | `TraceMeta` | interface | Metadata describing one traced run. `runId`/`traceId` default to fresh ids. | | `TraceOutcome` | interface | The resolved outcome of one traced run, surfaced on the export span and | | `UsageSplit` | interface | The per-class cost split carried by every trace and outcome. `off` ⇒ | -| `Agent` | type | A generic agent: one async input → output. The shape `withTangleIntelligence` | | `CapabilityAuth` | type | How a binding authenticates at resolve time. Declared as a REQUIREMENT in the | | `CapabilityInterface` | type | What the agent consumes. CLOSED — a new runtime kind NEVER extends this. Each | | `CapabilitySurface` | type | Every interface surface tag — the closed set the resolver fans into slots. | -| `ClientOrConfig` | type | Either a built client or the config to build one. | | `ContentRef` | type | Where a capability's bytes live. A leaked manifest carries no live secret and | | `CorpusAccess` | type | Corpus access an intelligence tier permits. `'off'` reads and writes | -| `DeliveredAgent` | type | An agent wrapped by {@link withCertifiedDelivery}: receives the input plus | | `DeliveryBinding` | type | How a capability is backed. OPEN tagged union — THE extension point. All arms | | `DeliveryBindingKind` | type | Every binding kind — the open set the resolver dispatches over. | | `EffortOverrides` | type | Per-field overrides applied on top of a tier preset. Any subset of the | | `EffortTier` | type | The named effort tiers, lowest to highest. `'off'` is the honest floor | +| `IntelligenceAgent` | type | An agent wrapped by {@link withIntelligence}: receives the input plus the | +| `IntelligenceWrapped` | type | The wrapped agent — same `(input) => Promise` shape, plus a manual | | `JsonSchema` | type | A JSON Schema object describing a tool's parameters. Kept structural — the | | `PullOutcome` | type | Typed outcome for the pull — inspect `succeeded` before `value`. A 404 | | `Redactor` | type | A redactor maps an arbitrary trace value to a safe-to-export value. Pure; | diff --git a/docs/archive/go-live-plan.md b/docs/archive/go-live-plan.md index 4063b3da..9df2a765 100644 --- a/docs/archive/go-live-plan.md +++ b/docs/archive/go-live-plan.md @@ -31,7 +31,7 @@ The two replicated survivors, with provenance: | Certification rigor predicts transfer | Only a full-statistical-gate-certified artifact transfers: holdout cert-vs-prose **+31.7pp CI[+13.3,+48.3]** and **+36.7pp CI[+11.7,+66.7]**, twice | E3d (5-run, agent-lab) | | The cost flywheel | Certified memory + compression **HOLD quality at −12% to −31% cost**; steer+compress promoted ×2 on AIME; author lands near-parity at 30–55% cost, replicated ×3 | E3/E3b/E3d, cost-arc, powered run | -**Mandatory provenance rider on every public claim** (both survivors are measured on EOPS/AIME via the agent-lab harness, **not yet through `withTangleIntelligence` in a product**): +**Mandatory provenance rider on every public claim** (both survivors are measured on EOPS/AIME via the agent-lab harness, **not yet through `withIntelligence` in a product**): > *"Demonstrated on internal benchmarks; in-product replication in progress."* --- @@ -89,7 +89,7 @@ This is where "take risks" stops. Boldness is allowed in *speed, scope, and dogf **First customer: gtm.tangle.tools** (NOT Harvey). Already deployed on agent-runtime, already on the platform billing rail, richest live tool surface, friendliest checks, named first in the playbook. Harvey is **external** — repo access + contract + trust risk *before the wrapper has ever run in production.* Harvey/legal is case study #2. -**First PR / first turn (the go-live proof):** wrap gtm's production `handleChatTurn` with `withTangleIntelligence` in Observe mode, ship **one real user turn**, confirm the trace lands in `/v1/traces` AND live chat is unaffected when that endpoint is down. That single live trace + survived-outage = go-live. +**First PR / first turn (the go-live proof):** wrap gtm's production `handleChatTurn` with `withIntelligence` in Observe mode, ship **one real user turn**, confirm the trace lands in `/v1/traces` AND live chat is unaffected when that endpoint is down. That single live trace + survived-outage = go-live. **Harvest, don't rebuild:** cherry-pick `origin/examples/tangle-intelligence-export` (`8fa16af`, 122 LOC) into `examples/intelligence-drop-in/`; reconcile `origin/feat/observe-closed-loop` (`37e373f`) into the Phase-1 PR. Both branches exist (verified) — pay the re-discovery tax once. @@ -113,7 +113,7 @@ Ship Observe to gtm **only when all green:** ## 6. THE #1 BUILD AND THE #1 EXPERIMENT **#1 BUILD — `src/intelligence/index.ts` + the conserved-pool usd split.** -`createIntelligenceClient` + `withTangleIntelligence(agent, config)` + `traceRun(meta, fn)` — a thin best-effort layer over the shipped `createOtelExporter` + the Mode-0 sandbox-stream base. Add the `./intelligence` export. **Cap at Observe + intelligence-off; do NOT bundle loops.** Bundle the `{inferenceUsd, intelligenceUsd}` split with it — without it there is no honest OFF tier, no billing boundary, no metered baseline for the cost claim. Everything in §4 depends on this one PR. +`createIntelligenceClient` + `withIntelligence(agent, config)` + `traceRun(meta, fn)` — a thin best-effort layer over the shipped `createOtelExporter` + the Mode-0 sandbox-stream base. Add the `./intelligence` export. **Cap at Observe + intelligence-off; do NOT bundle loops.** Bundle the `{inferenceUsd, intelligenceUsd}` split with it — without it there is no honest OFF tier, no billing boundary, no metered baseline for the cost claim. Everything in §4 depends on this one PR. **#1 EXPERIMENT — E4: does the cost flywheel COMPOUND, or is it one-shot selection?** The surviving thesis hinges on this. Every cost win so far is single-step and partly *selection, not accumulation* (E3c/E3d killed in-stream admission). "Cost keeps falling as the certified store grows" is **unmeasured** (no E4 doc exists yet — it's the designed-not-launched NEXT in `current.json`). diff --git a/docs/canonical-api.md b/docs/canonical-api.md index 60df0c5a..528a97a3 100644 --- a/docs/canonical-api.md +++ b/docs/canonical-api.md @@ -109,8 +109,8 @@ A general "loop" primitive is the single most common modelling error in this rep | Run a coding task INSIDE the agent's OWN sandbox session (a sibling box, fresh branch, validated patch) | `detachedSessionDelegate({ sandboxClient \| executor, workerProfile? })` — `/mcp` (pass the worker `AgentProfile`; omit for a minimal model-only default) | a hardcoded coder profile baked into the delegate; `delegate()` (that spawns workers in a *chosen* backend, not the agent's own session) | | Have a **supervisor spawn + live-drive workers in a backend you choose** and observe/steer/resume them | the **coordination MCP** — `createCoordinationTools` / `serveCoordinationMcp` over a live `Scope`; each worker's leaf is `createExecutor({ backend })` — `/mcp`,`/loops` | `detachedSessionDelegate` — own-sandbox-session only, one-shot, no live steer/recursion/conserved-budget | | Stand up a vertical agent in the eval loop | `defineAgent(manifest)` + `createSurfaceImprovementAdapter` — `/agent` | a per-vertical manifest parser, surface-validator, or bespoke `ImprovementAdapter` | -| Turn intelligence/observation OFF (prove inference-only billing) | `withTangleIntelligence(agent, { effort: 'off' })` — `/intelligence` | a custom trace-wrapper or hand-rolled effort/tier config | -| Fold **certified prompt additions into a system prompt you assemble yourself** (product chat routes) | `createCertifiedPromptSource({ target })` → `source.compose(base)` — `/intelligence` (cached, coalesced, fail-closed; `withCertifiedDelivery` rides the same source) | a module-scope cache + refresh-window + keep-last-known loop around `pullCertified` in product wiring | +| Observe + deliver Intelligence on a live agent (send RunRecords + receive certified profile/diffs) | `withIntelligence(agent, { project, target })` — `/intelligence` (proposals surfaced, never auto-applied; `effort: 'off'` proves inference-only billing) | a custom trace-wrapper, a second receive path, or hand-rolled effort/tier config | +| Fold **certified prompt additions into a system prompt you assemble yourself** (product chat routes) | `createCertifiedPromptSource({ target })` → `source.compose(base)` — `/intelligence` (cached, coalesced, fail-closed; `withIntelligence` rides the same source) | a module-scope cache + refresh-window + keep-last-known loop around `pullCertified` in product wiring | | Improve a KB with runtime agents, candidate workspaces, readiness checks, and measured supervised spend | `runKnowledgeImprovementJob(options)` from `/knowledge` | hand-wiring `improveKnowledgeBase` + a supervised updater + a readiness callback in every product | For the full export inventory (every primitive, its import path, its summary — generated, never stale), see `docs/api/primitive-catalog.md`; for per-symbol signatures, the per-module `docs/api/` pages. For the recursive atom (recursion · isolated-or-collaborative artifact · conserved budget · analysts) and the two-timescale architecture, see `docs/architecture.md`. For the genome→run→optimize→ship spine in depth, `docs/concepts.md` + `docs/learning-flywheel.md`. For the Intelligence SDK (Observe + the provable-OFF billing boundary), `docs/intelligence-sdk.md`. diff --git a/docs/intelligence-sdk.md b/docs/intelligence-sdk.md index 01e31e55..dc3177cf 100644 --- a/docs/intelligence-sdk.md +++ b/docs/intelligence-sdk.md @@ -8,19 +8,20 @@ Per-symbol signatures live in the generated [api/intelligence.md](./api/intellig ## Quickstart ```ts -import { withTangleIntelligence } from '@tangle-network/agent-runtime/intelligence' - -export const agent = withTangleIntelligence(myAgent, { - project: 'legal-agent', - apiKey: process.env.TANGLE_API_KEY, - repo: { owner: 'acme', name: 'legal-agent', baseBranch: 'main' }, - checks: ['pnpm test', 'pnpm typecheck'], - surfaces: ['src/agent/**', 'src/tools/**', 'skills/**'], -}) +import { withIntelligence } from '@tangle-network/agent-runtime/intelligence' + +export const agent = withIntelligence( + async (input, applied) => myAgent(input, { systemPrompt: applied.composePrompt(BASE) }), + { + project: 'legal-agent', + target: 'legal-agent', + apiKey: process.env.TANGLE_API_KEY, + }, +) ``` -`withTangleIntelligence` wraps any `(input) => Promise` agent and returns the same shape: each call runs under a trace and exports one span, best-effort. -`repo`, `checks`, and `surfaces` are recorded for `IntelligenceClient.doctor()` readiness only — the Observe slice never touches the repo. +`withIntelligence` is the ONE hook: it SENDS a typed `RunRecord` per call and RECEIVES the tenant's certified profile — folding the certified prompt surface (`applied.composePrompt`) and surfacing the plane's promoted profile diffs as PROPOSALS (`agent.proposals()` / `onProposals`). +It is **observe + deliver only** — it never auto-applies a received diff at runtime; a human, or the gated `improve()` loop, turns a proposal into a shipped profile (`applied.applyProfile(base)` is caller-invoked). That preserves the held-out invariant the plane enforces. Prove it offline: `pnpm tsx examples/intelligence-drop-in/intelligence-drop-in.ts` ($0, no credentials — it reads the exported span back and asserts the OFF tier's `intelligence_usd` is 0). ## Normative claims policy @@ -44,7 +45,7 @@ The delivery code enforces the same framing: a `CertifiedArtifact` carries its h - `project` (required) — the tenant dimension every trace is tagged with. - `apiKey` — bearer key for the ingest; reads `TANGLE_API_KEY` when omitted. - `effort` — a tier name or `{ tier, overrides }`; default `standard`. See [Effort tiers](#effort-tiers-and-the-off-billing-floor). -- `endpoint` — OTLP ingest base (the exporter appends `/v1/traces`). Reads `INTELLIGENCE_OTLP_ENDPOINT` then `OTEL_EXPORTER_OTLP_ENDPOINT` when omitted; absent all three, export is a no-op — best-effort by construction. +- `baseUrl` — the ONE Tangle Intelligence base URL both send (`/v1/otlp`) and receive (`/v1/profiles/:target/composed`) derive from. Reads `TANGLE_INTELLIGENCE_URL` when omitted, else `https://intelligence.tangle.tools`. Send ships only when an `apiKey` is present; absent a tenant key, export is a no-op — best-effort by construction. - `redact` — a `Redactor` replaces the default scrubber; `false` opts out loudly; omitted ⇒ `defaultRedactor`. See [Redaction](#redaction). - `surfaces` / `checks` / `repo` — declared for `IntelligenceClient.doctor()` readiness only. @@ -67,23 +68,28 @@ GET {base}/v1/profiles/:target/composed Authorization: Bearer ``` -The base URL reads `TANGLE_INTELLIGENCE_URL`, defaulting to the deployed plane at intelligence.tangle.tools. Four entrypoints, all fail-closed: +The base URL reads `TANGLE_INTELLIGENCE_URL`, defaulting to the deployed plane at intelligence.tangle.tools. The composed response carries the certified prompt surface + artifacts AND the typed `agentProfileDiffs` (each a promoted `AgentProfileDiff` + its held-out provenance), the composed `agentProfile`, and `capabilities`. Four entrypoints, all fail-closed: -- `pullCertified(opts)` — one pull, returning a typed `PullOutcome` (inspect `succeeded` before `value`); it never throws. A 404 is the normal "nothing promoted yet" signal, carried as `status: 404`; a hung plane is cut by the 10-second default timeout and surfaces as an ordinary failed pull. +- `pullCertified(opts)` — one pull, returning a typed `PullOutcome` (inspect `succeeded` before `value`); it never throws. It deserializes the full composed response — including the typed `agentProfileDiffs` earlier receive paths dropped. A 404 is the normal "nothing promoted yet" signal, carried as `status: 404`; a hung plane is cut by the 10-second default timeout and surfaces as an ordinary failed pull. - `createCertifiedPromptSource(opts)` — the cached, self-refreshing source: pulls at most every 5 minutes (`refreshMs`), coalesces concurrent pulls, and keeps the last-known profile on a failed or 404 pull — a good surface is never wiped by a bad refresh. - `composeCertifiedPrompt(base, certified)` — folds the certified prompt surface plus the prompt-folding artifact buckets (`promptFoldTypes`: prompt-surface, skill, instructions) into the base prompt under a marked section. The fold is byte-stable — prompt surface first, then bucket order, then path order — so the same profile renders identically on every call; it returns `base` unchanged when nothing usable is promoted. -- `withCertifiedDelivery(agent, config)` — the wrapper that rides both lanes at once: +- `withIntelligence(agent, config)` — the ONE hook that rides both lanes at once: ```ts -import { withCertifiedDelivery } from '@tangle-network/agent-runtime/intelligence' - -export const agent = withCertifiedDelivery( - async (input, applied) => myAgent(input, { systemPrompt: applied.composePrompt(BASE) }), - { project: 'support-agent', target: 'support-agent' }, +import { withIntelligence } from '@tangle-network/agent-runtime/intelligence' + +export const agent = withIntelligence( + async (input, applied) => { + const out = await myAgent(input, { systemPrompt: applied.composePrompt(BASE) }) + applied.record({ success: true, usage: { inferenceUsd: 0.002, intelligenceUsd: 0 } }) + return out + }, + { project: 'support-agent', target: 'support-agent', onProposals: (diffs) => review(diffs) }, ) ``` -Each call refreshes the certified profile (window-respecting), hands the agent an `AppliedIntelligence` handle (`certified` + `composePrompt`), and Observes the run with the certified version stamped on the span. +Each call refreshes the certified profile (window-respecting), hands the agent an `AppliedIntelligence` handle (`certified`, `composePrompt`, `proposals`, `applyProfile`, `record`), and SENDs a typed `RunRecord` for the run. +The promoted profile diffs surface as `agent.proposals()` / the `onProposals` callback — the hook NEVER auto-applies them; `applied.applyProfile(base)` folds them only when the caller explicitly asks. When the plane promotes a new gate-certified surface, the next refresh delivers it to the running agent; when the plane is unreachable, the agent runs on its base surface. ### The capability resolver @@ -139,7 +145,7 @@ A caller-supplied `redact` hook replaces the default entirely (the customer owns | Verified PRs | **designed** — the readiness check ships | 100-300 LOC | branch/PR with passing checks | surfaces, checks, repo access | fail closed | | Advanced Loops | **designed** — the loop substrate ships | 300+ LOC | scheduled optimization, holdout gates, matrix runs | eval adapter, budgets, gates | explicit opt-in | -`IntelligenceClient.doctor()` reports exactly this ladder without a network call: Observe is always ready; Recommend needs effort above off; PR needs checks + surfaces + repo; `exportConfigured` says whether an OTLP endpoint is set. +`IntelligenceClient.doctor()` reports exactly this ladder without a network call: Observe is always ready; Recommend needs effort above off; PR needs checks + surfaces + repo; `exportConfigured` says whether a tenant `apiKey` is set (the key the ingest requires for a send to land). If a product has no checks, it is not eligible for PR mode. If it has no traces, it is not eligible for credible recommendations. If it has neither, start at Observe. Recommend today, without the hosted verb: record a run's trace with `client.recordTrace(events)`, derive analyst findings from it, and feed them to `improve()` for a gated candidate. @@ -192,8 +198,7 @@ import { createCertifiedPromptSource, createIntelligenceClient, pullCertified, - withCertifiedDelivery, - withTangleIntelligence, + withIntelligence, } from '@tangle-network/agent-runtime/intelligence' ``` diff --git a/examples/README.md b/examples/README.md index 15de1db2..2cf8c12c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -84,7 +84,7 @@ repeat. A failing validator prunes a bad candidate so the loop can't keep it. | 17b | [`self-improving-coder/`](./self-improving-coder/) | The flywheel on a contamination-proof coding task: an agent writes strategies from its training losses, graded by real pytest, promoted only if a fresh holdout confirms the gain. `CALIBRATE=1` is a $0 no-key check. | | 18 | [`self-improving-loop/`](./self-improving-loop/) | #17 unrolled step by step: v0 → judge → analyst → mutation → v1 → gate, showing which part owns each phase. Offline. | | 19 | [`intelligence-recommend/`](./intelligence-recommend/) | The improvement loop offline end to end: read a run's trace → derive findings → `improve()` → a gated candidate. | -| 20 | [`intelligence-drop-in/`](./intelligence-drop-in/) | Wrap any agent with `withTangleIntelligence` to emit one trace per call — best-effort, and a proof that "off" is a zero-cost passthrough. | +| 20 | [`intelligence-drop-in/`](./intelligence-drop-in/) | Wrap any agent with `withIntelligence` to send one RunRecord per call — best-effort, and a proof that "off" is a zero-cost passthrough. | | 20b | [`intelligence-webcode/`](./intelligence-webcode/) | The full observability SDK (billing boundary, effort tiers, per-tool cost breakdown, OTLP export) instrumented over every cell of the WebCode benchmark. Needs a sandbox key. | | 21 | [`agents-of-all-shapes/`](./agents-of-all-shapes/) | Proof that any framework's traces converge on one open telemetry contract and produce one insight report. CI-tested. Offline. | | 22 | [`product-eval/`](./product-eval/) | Test an agent against a simulated user: a persona holds a multi-round conversation, then the transcript is scored. Needs `TANGLE_API_KEY`. | diff --git a/examples/agents-of-all-shapes/python-agno/agno_to_intelligence.py b/examples/agents-of-all-shapes/python-agno/agno_to_intelligence.py index d38bab52..c7f93a5a 100644 --- a/examples/agents-of-all-shapes/python-agno/agno_to_intelligence.py +++ b/examples/agents-of-all-shapes/python-agno/agno_to_intelligence.py @@ -19,8 +19,9 @@ import time import urllib.request -INTELLIGENCE_BASE = os.environ.get( - "INTELLIGENCE_BASE", "https://intelligence.tangle.tools/v1/otlp" +INTELLIGENCE_BASE = ( + os.environ.get("TANGLE_INTELLIGENCE_URL", "https://intelligence.tangle.tools").rstrip("/") + + "/v1/otlp" ) API_KEY = os.environ.get("TANGLE_API_KEY", "sk-tan-...") diff --git a/examples/agents-of-all-shapes/run.ts b/examples/agents-of-all-shapes/run.ts index 379af806..72e8fbea 100644 --- a/examples/agents-of-all-shapes/run.ts +++ b/examples/agents-of-all-shapes/run.ts @@ -9,7 +9,7 @@ * (`fromOtelSpans → analyzeRuns`). Prints the fleet `InsightReport` plus a * per-shape breakdown. * - * Optional hosted path: set TANGLE_API_KEY (and INTELLIGENCE_BASE) to also + * Optional hosted path: set TANGLE_API_KEY (and TANGLE_INTELLIGENCE_URL) to also * POST the spans to the hosted OTLP ingest for the dashboard. */ @@ -45,7 +45,8 @@ async function main() { // Optional: also ship to the hosted ingest for the dashboard. const apiKey = process.env.TANGLE_API_KEY if (apiKey) { - const endpoint = process.env.INTELLIGENCE_BASE ?? 'https://intelligence.tangle.tools/v1/otlp' + const base = process.env.TANGLE_INTELLIGENCE_URL ?? 'https://intelligence.tangle.tools' + const endpoint = `${base.replace(/\/+$/, '')}/v1/otlp` await shipToTangleOtlp(allSpans, { endpoint, apiKey }) console.log(`\nShipped ${allSpans.length} spans to ${endpoint} for the dashboard.`) } else { diff --git a/examples/agents-of-all-shapes/shapes.ts b/examples/agents-of-all-shapes/shapes.ts index 5eb2f039..5fe7682d 100644 --- a/examples/agents-of-all-shapes/shapes.ts +++ b/examples/agents-of-all-shapes/shapes.ts @@ -99,10 +99,10 @@ export function openAiCompatibleRuns(): AgentRun[] { * 3. Mastra agent (TypeScript). * * LIVE: Mastra emits OpenTelemetry natively. Configure its OTLP exporter to - * point at `${INTELLIGENCE_BASE}/v1/otlp` (hosted) OR collect the spans and + * point at `${TANGLE_INTELLIGENCE_URL}/v1/otlp` (hosted) OR collect the spans and * call `toInsightReport` in-process: * export const mastra = new Mastra({ telemetry: { enabled: true, - * export: { type: 'otlp', endpoint: `${INTELLIGENCE_BASE}/v1/otlp/v1/traces` } } }) + * export: { type: 'otlp', endpoint: `${TANGLE_INTELLIGENCE_URL}/v1/otlp/v1/traces` } } }) * Add a `score` attribute from your eval step. No Tangle SDK required. */ export function mastraRuns(): AgentRun[] { diff --git a/examples/intelligence-drop-in/README.md b/examples/intelligence-drop-in/README.md index 6d6f1d05..0434d354 100644 --- a/examples/intelligence-drop-in/README.md +++ b/examples/intelligence-drop-in/README.md @@ -3,8 +3,8 @@ Wrap any agent function in one line and every call ships a trace (a per-call record of what ran and what it cost) to Tangle Intelligence. Two things make this worth a look: -1. **It's genuinely one line** — `withTangleIntelligence(agent, opts)` preserves your agent's exact - call shape and adds tracing invisibly. +1. **It's genuinely one hook** — `withIntelligence(agent, opts)` wraps your agent, ships a RunRecord + per call, and receives the tenant's certified profile (fail-closed) — all invisibly. 2. **It's safe** — the tracing is best-effort. If Tangle Intelligence is down, your agent still answers. Telemetry never takes down the app. 3. **"Off" really means free** — turn intelligence off and you're billed only for the model call you'd @@ -36,9 +36,9 @@ and fails loudly if it isn't `0`. You get a number you can check, not a label yo ## The two APIs -- **`withTangleIntelligence(agent, { project, apiKey, endpoint })`** — the one-liner. Wrap any - `(input) => Promise` agent; each call fires off one trace in the background and returns as - soon as your agent does. +- **`withIntelligence(agent, { project, target, apiKey, baseUrl })`** — the one hook. Wrap any + `(input, applied) => Promise` agent; each call fires off one RunRecord in the background, + receives the certified profile, and returns as soon as your agent does. - **`createIntelligenceClient(...).traceRun(meta, fn)`** — the explicit version, for when you want to record extra detail and flush traces on demand. Used here so the example can flush and read the trace back to prove the zero. diff --git a/examples/intelligence-drop-in/intelligence-drop-in.ts b/examples/intelligence-drop-in/intelligence-drop-in.ts index 2db781c2..87b0eb72 100644 --- a/examples/intelligence-drop-in/intelligence-drop-in.ts +++ b/examples/intelligence-drop-in/intelligence-drop-in.ts @@ -17,7 +17,7 @@ import { createServer } from 'node:http' import { createIntelligenceClient, - withTangleIntelligence, + withIntelligence, } from '@tangle-network/agent-runtime/intelligence' async function supportAgent(input: { question: string }): Promise<{ answer: string }> { @@ -65,23 +65,25 @@ async function main() { }) await new Promise((r) => server.listen(0, r)) const port = (server.address() as { port: number }).port - const endpoint = `http://127.0.0.1:${port}/v1/otlp` + const baseUrl = `http://127.0.0.1:${port}` + const apiKey = process.env.TANGLE_API_KEY ?? 'sk-tan-demo' const project = 'support-agent' - // 1) THE ERGONOMICS — wrap any `(input) => Promise` in one line; it ships a trace best-effort - // (fire-and-forget: the call returns as soon as the agent does, the span flushes in the background). - const wrapped = withTangleIntelligence(supportAgent, { + // 1) THE ERGONOMICS — wrap any agent in one line; it receives the tenant's certified profile (fail-closed) + // and ships a RunRecord best-effort (the call returns as soon as the agent does, the span flushes async). + const wrapped = withIntelligence(async (input: { question: string }) => supportAgent(input), { project, - apiKey: process.env.TANGLE_API_KEY ?? 'sk-tan-demo', - endpoint, + apiKey, + baseUrl, }) const out = await wrapped({ question: 'how do I reset my password?' }) console.log(`1) wrapped an agent, got its answer: ${JSON.stringify(out.answer).slice(0, 48)}…`) - // 2) The agent still answers when the trace endpoint is dead — best-effort telemetry never breaks the app. - const onDead = withTangleIntelligence(supportAgent, { + // 2) The agent still answers when Intelligence is dead — best-effort telemetry never breaks the app. + const onDead = withIntelligence(async (input: { question: string }) => supportAgent(input), { project, - endpoint: 'http://127.0.0.1:1/v1/otlp', // nothing listening + apiKey, + baseUrl: 'http://127.0.0.1:1', // nothing listening }) const stillWorks = await onDead({ question: 'is intelligence down?' }) console.log(`2) survived a dead endpoint: ${stillWorks.answer.length > 0}`) @@ -91,7 +93,7 @@ async function main() { // intelligence spawn, so only the base inference stream costs anything (we record just that); the // span's intelligence_usd is 0 by construction — a `standard`-tier run would fill it itself. received.length = 0 - const off = createIntelligenceClient({ project, endpoint, effort: 'off' }) + const off = createIntelligenceClient({ project, apiKey, baseUrl, effort: 'off' }) await off.traceRun({ input: { q: 'cheap turn' } }, async (trace) => { trace.recordOutcome({ usage: { inferenceUsd: 0.0008 } }) return supportAgent({ question: 'cheap turn' }) diff --git a/examples/intelligence-recommend/README.md b/examples/intelligence-recommend/README.md index 9bc4919f..54f3b9cd 100644 --- a/examples/intelligence-recommend/README.md +++ b/examples/intelligence-recommend/README.md @@ -54,7 +54,7 @@ prompt after: ## Going live -To run this for real: point `createIntelligenceClient` at an OTLP trace collector (set -`INTELLIGENCE_OTLP_ENDPOINT`) so traces actually export; replace the two hand-written findings with a +To run this for real: give `createIntelligenceClient` a tenant `apiKey` (and, if not the prod plane, a +`baseUrl` / `TANGLE_INTELLIGENCE_URL`) so traces actually export; replace the two hand-written findings with a real trace-analyst agent's output; and drop the scripted proposer for a model-backed one (pass an `llm` instead of a `generator`) so the rewrite is reflected from the findings rather than pre-canned. diff --git a/examples/intelligence-recommend/intelligence-recommend.ts b/examples/intelligence-recommend/intelligence-recommend.ts index f1e55cbf..9738000a 100644 --- a/examples/intelligence-recommend/intelligence-recommend.ts +++ b/examples/intelligence-recommend/intelligence-recommend.ts @@ -8,7 +8,7 @@ * improve.ts verbatim, so this file shows only the NEW seam a Recommend mode runs in production: * * 1. OBSERVE — record a run's `LoopTraceEvent` stream as one trace (best-effort export; with no - * OTLP endpoint configured it is a no-op, so this runs offline with no credentials). + * tenant apiKey configured it is a no-op, so this runs offline with no credentials). * 2. ANALYZE — derive `AnalystFinding`s from that trace. In production a trace analyst reads the * spans; here we hand-derive two findings that CITE the recorded trace, to keep it offline. * 3. IMPROVE — feed those findings to `improve()` and print the gated candidate. @@ -24,7 +24,7 @@ import { agent, judge, profile, scenarios, scriptedWinner } from '../improve/imp // ── 1. OBSERVE — record a run's loop topology as one trace ────────────────── // A real run emits this `LoopTraceEvent` stream from the kernel; here a tiny two-event trace stands -// in. With no OTLP endpoint set, export is a no-op — offline, no credentials. +// in. With no tenant apiKey set, export is a no-op — offline, no credentials. const runId = 'run-001' const events: LoopTraceEvent[] = [ { diff --git a/examples/intelligence-webcode/README.md b/examples/intelligence-webcode/README.md index 3034fac7..e9800618 100644 --- a/examples/intelligence-webcode/README.md +++ b/examples/intelligence-webcode/README.md @@ -20,7 +20,7 @@ Three layers, all wrapped around each single benchmark cell (`intelligence-webco | Layer | What it does | |---|---| -| **Boundary** | `withTangleIntelligence(cell, { effort })` wraps the whole run. `effort` ranges `off · eco · standard · thorough · max`; `off` is the provable floor — instrumentation spend clamped to 0, the task still runs. | +| **Boundary** | `withIntelligence(cell, { effort })` wraps the whole run. `effort` ranges `off · eco · standard · thorough · max`; `off` is the provable floor — instrumentation spend clamped to 0, the task still runs. | | **Cost waterfall** | `createWaterfallCollector()` records cost per tool/phase during the run. The sum of its parts **is** the billed total, so there's no second tally that can drift from reality. | | **Trace export** | `createOtelExporter()` streams every step to your OTLP trace collector. It's a no-op until you set `OTEL_EXPORTER_OTLP_ENDPOINT`, so you can run without one. | diff --git a/examples/intelligence-webcode/intelligence-webcode.ts b/examples/intelligence-webcode/intelligence-webcode.ts index c00be403..44b41c5d 100644 --- a/examples/intelligence-webcode/intelligence-webcode.ts +++ b/examples/intelligence-webcode/intelligence-webcode.ts @@ -6,7 +6,7 @@ * waterfall, and the spans streamed to your trace collector. * * Three intelligence layers, all on one cell: - * 1. BOUNDARY (the bill + the control) — `withTangleIntelligence(cell, { project, effort })`. + * 1. BOUNDARY (the bill + the control) — `withIntelligence(cell, { project, effort })`. * `effort ∈ off | eco | standard | thorough | max`; `'off'` is the PROVABLE passthrough floor — * intelligence spend clamped to 0, the cell still runs. This is the one knob that gates spend. * 2. WATERFALL (the cost truth) — `createWaterfallCollector()` on the run. The sum of its spans IS the @@ -14,7 +14,7 @@ * 3. OTLP (the production trace pipe) — `createOtelExporter()` + `loopEventToOtelSpan`. Streams every * span to your OTLP/HTTP collector (set `OTEL_EXPORTER_OTLP_ENDPOINT`); a no-op when unset. * - * The intelligence attaches at TWO seams: the BOUNDARY wraps the whole cell (`withTangleIntelligence` + * The intelligence attaches at TWO seams: the BOUNDARY wraps the whole cell (`withIntelligence` * works over any async fn), and the INTERNAL trace rides `openSandboxRun`'s `hooks` (the only run verb * here that emits per-tool spans). Same pattern instruments `runProfileMatrix`'s dispatch wholesale. * @@ -30,7 +30,7 @@ import { loopEventToOtelSpan, type RuntimeHooks, } from '@tangle-network/agent-runtime' -import { type EffortTier, withTangleIntelligence } from '@tangle-network/agent-runtime/intelligence' +import { type EffortTier, withIntelligence } from '@tangle-network/agent-runtime/intelligence' import { type AgentRunSpec, createWaterfallCollector, @@ -62,7 +62,7 @@ interface CellResult { /** One instrumented cell: run a harness×model on a WebCode task in its own sandbox with the INTERNAL trace * collected (cost waterfall) AND streamed (OTLP), then score on the hidden tests. Wrapping this in - * `withTangleIntelligence` (below) adds the BOUNDARY layer. */ + * `withIntelligence` (below) adds the BOUNDARY layer. */ function instrumentedCell(client: SandboxClient): (input: CellInput) => Promise { return async ({ profile, task }) => { const harness = String(profile.metadata?.harness ?? 'opencode') @@ -145,9 +145,9 @@ function instrumentedCell(client: SandboxClient): (input: CellInput) => Promise< /** Run the WebCode grid × tasks with the full intelligence stack on every cell. */ export async function runIntelligenceWebcode(client: SandboxClient): Promise { - // LAYER 1 — the BOUNDARY: every cell runs under `withTangleIntelligence` — traced + billed, effort-gated. + // LAYER 1 — the BOUNDARY: every cell runs under `withIntelligence` — observed + billed, effort-gated. // `effort: 'off'` clamps intelligence spend to 0 (the provable passthrough floor) while still running. - const smartCell = withTangleIntelligence(instrumentedCell(client), { project, effort }) + const smartCell = withIntelligence(instrumentedCell(client), { project, effort }) const webcodeTasks = loadWebCodeTasks( process.env.LIMIT ? { limit: Number(process.env.LIMIT) } : {}, ) diff --git a/scripts/verify-package-exports.mjs b/scripts/verify-package-exports.mjs index ab2847bb..02406799 100644 --- a/scripts/verify-package-exports.mjs +++ b/scripts/verify-package-exports.mjs @@ -61,7 +61,8 @@ try { const intelligence = await import('@tangle-network/agent-runtime/intelligence') const expectedIntelligence = [ 'createIntelligenceClient', - 'withTangleIntelligence', + 'withIntelligence', + 'pullCertified', 'resolveEffort', 'isIntelligenceOff', 'defaultRedactor', diff --git a/skills/build-with-agent-runtime/SKILL.md b/skills/build-with-agent-runtime/SKILL.md index ab139294..e4154bd6 100644 --- a/skills/build-with-agent-runtime/SKILL.md +++ b/skills/build-with-agent-runtime/SKILL.md @@ -98,8 +98,8 @@ to its native default (`HARNESS_NATIVE_MODEL`) — never silently dropped. | **Run the full multi-generation flywheel + certify** | `runStrategyEvolution(config)` — `/loops` | canonical-api §3.4 | | **Observe a run** (cost/time waterfall, OTLP) | `createWaterfallCollector()` — `/loops`; `createOtelExporter` attached via `composeRuntimeHooks(...)` — root `.` | canonical-api §2 | | **State any A/B claim** | `pairedLift` (bench) over `pairedBootstrap`/`heldoutSignificance` (substrate) | canonical-api §3.5 | -| **Observe/ship with billing-boundary** | `withTangleIntelligence(agent, { project, effort })` — `/intelligence` | canonical-api §2 | -| **Pull the certified profile from the Intelligence plane** (pull-by-default delivery: fold the gate-certified prompt onto the base surface) | `pullCertified` / `withCertifiedDelivery` / `composeCertifiedPrompt` — `/intelligence` | `src/intelligence/delivery.ts` | +| **Observe + deliver on a live agent with billing-boundary** | `withIntelligence(agent, { project, target, effort })` — `/intelligence` | canonical-api §2 | +| **Pull the certified profile from the Intelligence plane** (pull-by-default delivery: fold the gate-certified prompt onto the base surface) | `pullCertified` / `withIntelligence` / `composeCertifiedPrompt` — `/intelligence` | `src/intelligence/delivery.ts` | ## Do-NOT-reinvent — the traps this skill exists to stop @@ -177,26 +177,29 @@ sandbox coding rollouts against external benchmarks. The full when-which map is ## Observe / ship with the Intelligence SDK -One line wraps any agent with trace + billing boundary: -`withTangleIntelligence(agent, { project, effort })`, `effort ∈ +One hook wraps any agent with send + receive + billing boundary: +`withIntelligence(agent, { project, target, effort })`, `effort ∈ off|eco|standard|thorough|max` (`'off'` is the provable passthrough floor — -intelligence spend clamped to 0). It builds on `createOtelExporter` + -`loopEventToOtelSpan` — don't hand-roll a trace-wrapper or effort/tier config. -Verify the live subpath against `src/intelligence/index.ts`. +intelligence spend clamped to 0). It SENDs a typed `RunRecord` and RECEIVEs the +certified profile, building on `createOtelExporter` + `buildLoopOtelSpans` — +don't hand-roll a trace-wrapper, a second receive path, or effort/tier config. +Verify the live subpath against `src/intelligence/with-intelligence.ts`. Two operational facts every consumer must know: -- **Export is a silent no-op without an endpoint.** The export leg only ships - when `INTELLIGENCE_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_ENDPOINT`) is set — - e.g. `https://intelligence.tangle.tools/v1/otlp`; absent, spans are dropped - best-effort with no error. The client's `doctor().exportConfigured` is the - check that export will actually ship. +- **Send is a silent no-op without a tenant apiKey.** The export leg only ships + when an `apiKey` (or `TANGLE_API_KEY`) is present — the ingest requires the + tenant Bearer. One `baseUrl` (`TANGLE_INTELLIGENCE_URL`, default + `https://intelligence.tangle.tools`) drives both the OTLP send (`/v1/otlp`) + and the receive pull. The client's `doctor().exportConfigured` is the check + that a send will actually land. - **Delivery pulls the certified profile from the plane.** `pullCertified` / - `withCertifiedDelivery` hit - `GET {TANGLE_INTELLIGENCE_URL|https://intelligence.tangle.tools}/v1/profiles/:target/composed` - with `Bearer TANGLE_API_KEY`; `withCertifiedDelivery` folds the certified - prompt onto the base surface, refreshes at most every 5 minutes, and is - fail-closed — a failed pull runs the agent on its base surface. + `withIntelligence` hit + `GET {baseUrl}/v1/profiles/:target/composed` with `Bearer TANGLE_API_KEY`; + the pull deserializes the typed `agentProfileDiffs` (surfaced as PROPOSALS, + never auto-applied) alongside the certified prompt. `withIntelligence` folds + the certified prompt onto the base surface, refreshes at most every 5 minutes, + and is fail-closed — a failed pull runs the agent on its base surface. ## Final check diff --git a/src/intelligence/capability.test.ts b/src/intelligence/capability.test.ts index 047c484a..855fb538 100644 --- a/src/intelligence/capability.test.ts +++ b/src/intelligence/capability.test.ts @@ -23,6 +23,9 @@ const provenance = { const wireProfile: CertifiedProfile = { target: 'support-agent', generatedAt: '2026-06-13T00:00:00.000Z', + agentProfileDiffs: [], + capabilities: [], + agentProfile: null, promptSurface: { surface: 'Confirm the invoice id before refunding.', surfaceHash: 'abc123', @@ -86,6 +89,9 @@ describe('manifestFromProfile', () => { target: 't', generatedAt: 'g', promptSurface: null, + agentProfileDiffs: [], + capabilities: [], + agentProfile: null, artifacts: { tool: [ { @@ -113,6 +119,9 @@ describe('manifestFromProfile', () => { target: 't', generatedAt: 'g', promptSurface: null, + agentProfileDiffs: [], + capabilities: [], + agentProfile: null, artifacts: { mcp: [ { @@ -136,6 +145,9 @@ describe('manifestFromProfile', () => { target: 't', generatedAt: 'g', promptSurface: null, + agentProfileDiffs: [], + capabilities: [], + agentProfile: null, artifacts: { notes: [ { @@ -181,6 +193,9 @@ describe('composeCertifiedProfile — prompt fold regression lock', () => { target: 't', generatedAt: 'g', promptSurface: null, + agentProfileDiffs: [], + capabilities: [], + agentProfile: null, artifacts: { skill: [ { @@ -227,6 +242,9 @@ describe('composeCertifiedProfile — prompt fold regression lock', () => { target: 't', generatedAt: 'g', promptSurface: null, + agentProfileDiffs: [], + capabilities: [], + agentProfile: null, artifacts: { notes: [ { diff --git a/src/intelligence/delivery.test.ts b/src/intelligence/delivery.test.ts index 273dfa9d..f87f1798 100644 --- a/src/intelligence/delivery.test.ts +++ b/src/intelligence/delivery.test.ts @@ -1,10 +1,10 @@ +import type { AgentProfileDiff } from '@tangle-network/agent-interface' import { describe, expect, it, vi } from 'vitest' import { type CertifiedProfile, composeCertifiedPrompt, createCertifiedPromptSource, pullCertified, - withCertifiedDelivery, } from './delivery' const CERTIFIED: CertifiedProfile = { @@ -28,6 +28,9 @@ const CERTIFIED: CertifiedProfile = { }, ], }, + agentProfileDiffs: [], + capabilities: [], + agentProfile: null, } function jsonResponse(body: unknown, status = 200): Response { @@ -55,13 +58,7 @@ describe('composeCertifiedPrompt', () => { }) it('returns base when the certified profile has no usable content', () => { - const empty: CertifiedProfile = { - target: 't', - generatedAt: 'x', - promptSurface: null, - artifacts: {}, - } - expect(composeCertifiedPrompt('BASE', empty)).toBe('BASE') + expect(composeCertifiedPrompt('BASE', { promptSurface: null, artifacts: {} })).toBe('BASE') }) }) @@ -84,6 +81,63 @@ describe('pullCertified', () => { expect(call[1].headers).toMatchObject({ authorization: 'Bearer k_test' }) }) + it('deserializes the typed agentProfileDiffs the composed endpoint returns', async () => { + const diff: AgentProfileDiff = { + schemaVersion: 1, + kind: 'agent-profile-diff', + set: { tools: { refund: true } }, + } + const composed = { + ...CERTIFIED, + agentProfileDiffs: [ + { + diff, + provenance: { + version: 7, + lift: '+2.2pp', + contentHash: 'deadbeef', + promotedAt: '2026-06-12T00:00:00.000Z', + }, + }, + ], + agentProfile: { name: 'support-agent', tools: { refund: true } }, + } + const fetchImpl = vi.fn(async () => jsonResponse(composed)) as unknown as typeof fetch + const outcome = await pullCertified({ + target: 'support-agent', + apiKey: 'k', + baseUrl: 'https://plane.test', + fetchImpl, + }) + expect(outcome.succeeded).toBe(true) + if (outcome.succeeded) { + expect(outcome.value.agentProfileDiffs).toEqual(composed.agentProfileDiffs) + expect(outcome.value.agentProfile).toEqual(composed.agentProfile) + } + }) + + it('normalizes a legacy response with no diffs to empty arrays (fail-closed)', async () => { + const legacy = { + target: 't', + generatedAt: 'x', + promptSurface: null, + artifacts: {}, + } + const fetchImpl = vi.fn(async () => jsonResponse(legacy)) as unknown as typeof fetch + const outcome = await pullCertified({ + target: 't', + apiKey: 'k', + baseUrl: 'https://plane.test', + fetchImpl, + }) + expect(outcome.succeeded).toBe(true) + if (outcome.succeeded) { + expect(outcome.value.agentProfileDiffs).toEqual([]) + expect(outcome.value.capabilities).toEqual([]) + expect(outcome.value.agentProfile).toBeNull() + } + }) + it('treats 404 (nothing promoted yet) as a non-error succeeded:false with status', async () => { const fetchImpl = vi.fn( async () => new Response('', { status: 404 }), @@ -112,8 +166,6 @@ describe('pullCertified', () => { }) it('errors loudly when no apiKey is available', async () => { - // Stub the env so the test holds on machines/CI where TANGLE_API_KEY is set - // (resolveApiKey falls back to it when the passed key is empty). vi.stubEnv('TANGLE_API_KEY', '') try { const fetchImpl = (async () => jsonResponse({})) as unknown as typeof fetch @@ -130,8 +182,6 @@ describe('pullCertified', () => { }) it('fails closed when the plane hangs past timeoutMs', async () => { - // A fetch that rejects when its abort signal fires (what a hung request + - // AbortSignal.timeout produces) must surface as a typed fail-closed result. const fetchImpl = ((_url: string, init?: RequestInit) => new Promise((_resolve, reject) => { init?.signal?.addEventListener('abort', () => @@ -150,86 +200,6 @@ describe('pullCertified', () => { }) }) -describe('withCertifiedDelivery', () => { - it('delivers the certified profile to the agent and composes it into the prompt', async () => { - const fetchImpl = vi.fn(async () => jsonResponse(CERTIFIED)) as unknown as typeof fetch - let seenPrompt = '' - const agent = withCertifiedDelivery( - async (input: { q: string }, applied) => { - seenPrompt = applied.composePrompt('BASE SYSTEM PROMPT') - return `answer:${input.q}:v${applied.certified?.promptSurface?.version}` - }, - { project: 'support-agent', apiKey: 'k', baseUrl: 'https://plane.test', fetchImpl }, - ) - const out = await agent({ q: 'refund' }) - expect(out).toBe('answer:refund:v4') - expect(seenPrompt).toContain('confirm the invoice id before refunding') - expect(seenPrompt).toContain('BASE SYSTEM PROMPT') - }) - - it('runs fail-closed on the base surface when the pull 404s (nothing promoted)', async () => { - const fetchImpl = vi.fn( - async () => new Response('', { status: 404 }), - ) as unknown as typeof fetch - let seenPrompt = '' - let seenCertified: unknown = 'unset' - const agent = withCertifiedDelivery( - async (_input: null, applied) => { - seenPrompt = applied.composePrompt('BASE') - seenCertified = applied.certified - return 'ok' - }, - { project: 'p', apiKey: 'k', baseUrl: 'https://plane.test', fetchImpl }, - ) - expect(await agent(null)).toBe('ok') - expect(seenPrompt).toBe('BASE') - expect(seenCertified).toBeNull() - }) - - it('does not break the agent when Intelligence is unreachable', async () => { - const fetchImpl = vi.fn(async () => { - throw new Error('network down') - }) as unknown as typeof fetch - const agent = withCertifiedDelivery(async (input: number) => input * 2, { - project: 'p', - apiKey: 'k', - baseUrl: 'https://plane.test', - fetchImpl, - }) - await expect(agent(21)).resolves.toBe(42) - }) - - it('caches the certified pull across calls within refreshMs (one pull, N runs)', async () => { - const calls = { n: 0 } - const fetchImpl = vi.fn(async () => { - calls.n += 1 - return jsonResponse(CERTIFIED) - }) as unknown as typeof fetch - const agent = withCertifiedDelivery(async (_i: null, _a) => 'ok', { - project: 'support-agent', - apiKey: 'k', - baseUrl: 'https://plane.test', - refreshMs: 60_000, - fetchImpl, - }) - await agent(null) - await agent(null) - await agent(null) - expect(calls.n).toBe(1) - }) - - it('propagates the agent error (delivery never swallows the live path)', async () => { - const fetchImpl = vi.fn(async () => jsonResponse(CERTIFIED)) as unknown as typeof fetch - const agent = withCertifiedDelivery( - async (_i: null) => { - throw new Error('agent boom') - }, - { project: 'support-agent', apiKey: 'k', baseUrl: 'https://plane.test', fetchImpl }, - ) - await expect(agent(null)).rejects.toThrow('agent boom') - }) -}) - describe('createCertifiedPromptSource', () => { const opts = { target: 'support-agent', apiKey: 'k', baseUrl: 'https://plane.test' } diff --git a/src/intelligence/delivery.ts b/src/intelligence/delivery.ts index 1ef3cc24..0192e55a 100644 --- a/src/intelligence/delivery.ts +++ b/src/intelligence/delivery.ts @@ -1,31 +1,32 @@ /** * - * Tangle Intelligence — the DELIVERY half of the loop (pull-by-default). + * Tangle Intelligence — the RECEIVE half of the loop (pull-by-default). * - * The sibling Observe layer (`./index`) sends traces UP to the plane. This + * The sibling Observe path (`./index`) sends run records UP to the plane. This * module pulls certified artifacts DOWN: it reads the tenant's promoted, - * gate-certified profile from the deployed Intelligence plane and folds it into - * the running agent's prompt — so an approved improvement actually reaches the - * agent. This is "shipping intelligence to people's agents", pull-by-default; - * the push/Gated-PR opt-in composes on top of this. + * gate-certified profile from the deployed Intelligence plane so an approved + * improvement actually reaches the running agent. The pull carries three things + * the plane already composes: + * - the certified PROMPT surface + prompt-folding artifacts (delivered into the + * system prompt via {@link composeCertifiedPrompt} — the promoted prompt); + * - the typed profile DIFFS the plane has promoted, each with its held-out + * provenance (surfaced as PROPOSALS — never auto-applied at runtime); + * - the composed `agentProfile` those diffs fold to, for inspection. * * Pull contract (deployed plane): GET /v1/profiles/:target/composed → - * { target, generatedAt, promptSurface: {surface,surfaceHash,version,lift}|null, - * artifacts: { : [{path,content,contentHash,version,lift,promotedAt}] } } + * { target, generatedAt, + * promptSurface: {surface,surfaceHash,version,lift}|null, + * artifacts: { : [{path,content,contentHash,version,lift,promotedAt}] }, + * capabilities: [{id,iface:{surface},binding:{path,content},provenance}], + * agentProfileDiffs: [{diff, provenance:{version,lift,contentHash,promotedAt}}], + * agentProfile: AgentProfile|null } * Auth: Bearer (the one TANGLE_API_KEY shared by router + sandbox + * intelligence), resolved to a tenant by platform-api's key-verify S2S contract. * - * import { withCertifiedDelivery } from '@tangle-network/agent-runtime/intelligence' - * - * export const agent = withCertifiedDelivery( - * async (input, applied) => myAgent(input, { systemPrompt: applied.composePrompt(BASE) }), - * { project: 'support-agent', target: 'support-agent' }, - * ) - * * @experimental */ -import { createIntelligenceClient, type IntelligenceConfig } from './index' +import type { AgentProfile, AgentProfileDiff } from '@tangle-network/agent-interface' const defaultPlaneBaseUrl = 'https://intelligence.tangle.tools' const defaultRefreshMs = 300_000 @@ -50,6 +51,37 @@ export interface CertifiedPromptSurface { lift: string | null } +/** The held-out provenance the plane's certify step stamps on a promoted diff. + * `lift` is the held-out gate lift (e.g. "+3.1pp"), never a within-run claim. */ +export interface DiffProvenance { + version: number | null + lift: string | null + contentHash: string + promotedAt: string +} + +/** + * A gate-certified profile diff the plane has already promoted, plus the + * held-out provenance it carries. This is the previously-DROPPED typed diff the + * composed endpoint returns; `withIntelligence` deserializes it and surfaces it + * as a PROPOSAL — a human, or the gated local `improve()` loop, turns a proposal + * into a shipped profile. It is NEVER auto-applied at runtime. + */ +export interface ProposedProfileDiff { + diff: AgentProfileDiff + provenance: DiffProvenance +} + +/** The composed endpoint's per-capability summary — the narrow shape on the + * wire (id + surface + path/content + provenance). Distinct from the richer + * `CertifiedCapability` the capability resolver lowers a manifest into. */ +export interface CertifiedCapabilitySummary { + id: string + iface: { surface: string } + binding: { path: string | null; content: string } + provenance: DiffProvenance +} + /** The composed certified profile — exactly the shape the plane's * `GET /v1/profiles/:target/composed` returns. */ export interface CertifiedProfile { @@ -57,6 +89,14 @@ export interface CertifiedProfile { generatedAt: string promptSurface: CertifiedPromptSurface | null artifacts: Record + /** The typed profile diffs the plane has promoted, each with held-out + * provenance. Surfaced as proposals; never auto-applied. Empty when none. */ + agentProfileDiffs: ProposedProfileDiff[] + /** The composed capability summaries the plane returns. Empty when none. */ + capabilities: CertifiedCapabilitySummary[] + /** The composed profile the promoted diffs fold to, for inspection. `null` + * when no diffs are promoted. */ + agentProfile: AgentProfile | null } /** Typed outcome for the pull — inspect `succeeded` before `value`. A 404 @@ -83,7 +123,9 @@ export interface PullCertifiedOptions { const defaultPullTimeoutMs = 10_000 -function resolvePlaneBaseUrl(baseUrl: string | undefined): string { +/** Resolve the ONE Intelligence base URL — the single knob both the send and + * receive paths derive from. Env fallback: `TANGLE_INTELLIGENCE_URL`. */ +export function resolveIntelligenceBaseUrl(baseUrl: string | undefined): string { if (baseUrl) return baseUrl.replace(/\/+$/, '') if (typeof process !== 'undefined' && process.env.TANGLE_INTELLIGENCE_URL) { return process.env.TANGLE_INTELLIGENCE_URL.replace(/\/+$/, '') @@ -98,6 +140,50 @@ function resolveApiKey(apiKey: string | undefined): string { return '' } +function asRecord(value: unknown): Record { + return value && typeof value === 'object' ? (value as Record) : {} +} + +function toDiffProvenance(value: unknown): DiffProvenance { + const p = asRecord(value) + return { + version: typeof p.version === 'number' ? p.version : null, + lift: typeof p.lift === 'string' ? p.lift : null, + contentHash: typeof p.contentHash === 'string' ? p.contentHash : '', + promotedAt: typeof p.promotedAt === 'string' ? p.promotedAt : '', + } +} + +/** + * Deserialize the composed-endpoint response into a `CertifiedProfile`. The + * previously-dropped `agentProfileDiffs`/`capabilities`/`agentProfile` are read + * here so they round-trip to the consumer; a plane that has not yet promoted any + * diffs simply yields empty arrays / a null profile (fail-closed, never a crash). + */ +export function normalizeCertifiedProfile(raw: unknown): CertifiedProfile { + const r = asRecord(raw) + const promptSurface = r.promptSurface ? (r.promptSurface as CertifiedPromptSurface) : null + const artifacts = (r.artifacts as Record | undefined) ?? {} + const agentProfileDiffs: ProposedProfileDiff[] = Array.isArray(r.agentProfileDiffs) + ? r.agentProfileDiffs.map((entry) => { + const e = asRecord(entry) + return { diff: e.diff as AgentProfileDiff, provenance: toDiffProvenance(e.provenance) } + }) + : [] + const capabilities: CertifiedCapabilitySummary[] = Array.isArray(r.capabilities) + ? (r.capabilities as CertifiedCapabilitySummary[]) + : [] + return { + target: typeof r.target === 'string' ? r.target : '', + generatedAt: typeof r.generatedAt === 'string' ? r.generatedAt : '', + promptSurface, + artifacts, + agentProfileDiffs, + capabilities, + agentProfile: (r.agentProfile as AgentProfile | null | undefined) ?? null, + } +} + /** * Pull the certified composed profile for a target. Fail-closed: a network * error or a non-2xx returns a typed `succeeded: false` (never throws), so a @@ -109,7 +195,7 @@ export async function pullCertified(opts: PullCertifiedOptions): Promise | null, +): string { if (!certified) return base const parts: string[] = [] if (certified.promptSurface?.surface.trim()) parts.push(certified.promptSurface.surface.trim()) @@ -181,7 +270,7 @@ export function composeCertifiedPrompt(base: string, certified: CertifiedProfile /** A cached, self-refreshing source of a target's certified prompt additions — * the prompt-only delivery lane for callers that assemble their OWN system * prompt (product chat routes) rather than wrapping an agent fn. Same - * fail-closed semantics as {@link withCertifiedDelivery}: pulls at most every + * fail-closed semantics as {@link pullCertified}: pulls at most every * `refreshMs`, coalesces concurrent pulls, keeps the last-known profile on a * failed/404 pull, never throws, never blocks past the pull timeout. */ export interface CertifiedPromptSource { @@ -204,8 +293,8 @@ export interface CertifiedPromptSourceOptions extends PullCertifiedOptions { /** * Create the cached certified-prompt source — the ONE module-scope-cache + * coalesced-refresh + keep-last-known implementation. Product wiring uses this - * rather than hand-rolling the same lines around `pullCertified`, and - * {@link withCertifiedDelivery} is built on it. + * rather than hand-rolling the same lines around `pullCertified`. The + * `withIntelligence` hook rides this same source for its prompt delivery. */ export function createCertifiedPromptSource( opts: CertifiedPromptSourceOptions, @@ -241,73 +330,3 @@ export function createCertifiedPromptSource( }, } } - -/** What the delivery wrapper hands the agent each run. */ -export interface AppliedIntelligence { - /** The certified profile in effect (null when none promoted / pull failed — - * fail-closed: the agent runs on its base surface). */ - certified: CertifiedProfile | null - /** Fold the certified prompt surface into a base system prompt. */ - composePrompt(base: string): string -} - -/** An agent wrapped by {@link withCertifiedDelivery}: receives the input plus - * the certified intelligence delivered for this run. */ -export type DeliveredAgent = (input: I, applied: AppliedIntelligence) => Promise - -/** Delivery config = the Observe config plus the pull target + refresh cadence. */ -export interface DeliveryConfig extends IntelligenceConfig { - /** Pull target. Defaults to `project`. */ - target?: string - /** Plane base URL for the pull (NOT the OTLP `endpoint`). Defaults to - * `TANGLE_INTELLIGENCE_URL` then `https://intelligence.tangle.tools`. */ - baseUrl?: string - /** Min interval between certified-profile pulls. Default 5m. */ - refreshMs?: number - /** Per-pull timeout in ms (fail-closed on a hung plane). Default 10000. */ - timeoutMs?: number - /** fetch impl for the pull (tests). Defaults to global fetch. */ - fetchImpl?: typeof fetch -} - -/** - * Wrap an agent so it (a) Observes each run via the shipped Observe client and - * (b) RECEIVES the tenant's certified artifacts pulled from the deployed plane. - * The certified profile is cached and refreshed at most every `refreshMs`; a - * failed pull is fail-closed — the agent runs on its base surface and never - * breaks because Intelligence is unreachable. When the plane promotes a new - * gate-certified surface, the next refresh delivers it to the running agent. - */ -export function withCertifiedDelivery( - agent: DeliveredAgent, - config: DeliveryConfig, -): ((input: I) => Promise) & { refresh(): Promise } { - const client = createIntelligenceClient(config) - const source = createCertifiedPromptSource({ - target: config.target ?? config.project, - ...(config.apiKey !== undefined ? { apiKey: config.apiKey } : {}), - ...(config.baseUrl !== undefined ? { baseUrl: config.baseUrl } : {}), - ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}), - ...(config.fetchImpl !== undefined ? { fetchImpl: config.fetchImpl } : {}), - ...(config.refreshMs !== undefined ? { refreshMs: config.refreshMs } : {}), - }) - - const wrapped = (async (input: I): Promise => { - await source.refresh() - const certified = source.current() - const applied: AppliedIntelligence = { - certified, - composePrompt: (base: string) => composeCertifiedPrompt(base, certified), - } - return client.traceRun( - { input, labels: { 'tangle.certified_version': certified?.promptSurface?.version ?? -1 } }, - async (trace) => { - const out = await agent(input, applied) - trace.recordOutput(out) - return out - }, - ) - }) as ((input: I) => Promise) & { refresh(): Promise } - wrapped.refresh = source.refresh - return wrapped -} diff --git a/src/intelligence/index.ts b/src/intelligence/index.ts index 26fbd2ad..c89016ed 100644 --- a/src/intelligence/index.ts +++ b/src/intelligence/index.ts @@ -30,6 +30,7 @@ import { type OtelExporter, } from '../otel-export' import type { LoopTraceEvent } from '../runtime/types' +import { resolveIntelligenceBaseUrl } from './delivery' import { defaultEffortTier, type EffortOverrides, @@ -60,22 +61,23 @@ export type { } from './capability' export { CapabilityNotAdmittedError, manifestFromProfile } from './capability' export type { - AppliedIntelligence, CertifiedArtifact, + CertifiedCapabilitySummary, CertifiedProfile, CertifiedPromptSource, CertifiedPromptSourceOptions, CertifiedPromptSurface, - DeliveredAgent, - DeliveryConfig, + DiffProvenance, + ProposedProfileDiff, PullCertifiedOptions, PullOutcome, } from './delivery' export { composeCertifiedPrompt, createCertifiedPromptSource, + normalizeCertifiedProfile, pullCertified, - withCertifiedDelivery, + resolveIntelligenceBaseUrl, } from './delivery' export type { CorpusAccess, @@ -97,6 +99,13 @@ export { composeCertifiedProfile, composeCertifiedProfileFromWire, } from './resolver' +export type { + AppliedIntelligence, + IntelligenceAgent, + IntelligenceHookConfig, + IntelligenceWrapped, +} from './with-intelligence' +export { withIntelligence } from './with-intelligence' /** Usage class for billing. Base-stream tokens bill `'inference'`; every * intelligence spawn (analyst, corpus, loop) bills `'intelligence'`. The @@ -115,6 +124,46 @@ export interface UsageSplit { intelligenceUsd: number } +/** + * The typed record `withIntelligence` sends per call — serialized through the + * shipped OTLP builders to the plane's `/v1/otlp` ingest. `input`/`output` are + * redacted on export; the per-class `usage` split carries the billing proof; + * `loopEvents`, when present, export as the nested loop→round→iteration span + * tree under the same `traceId`. + */ +export interface RunRecord { + runId: string + traceId: string + project: string + target: string + input: unknown + output: unknown + outcome: { + success?: boolean + score?: number + usage: UsageSplit + } + model?: string + provider?: string + loopEvents?: LoopTraceEvent[] +} + +/** + * What an agent reports (via `applied.record`) to enrich the {@link RunRecord} + * sent for its call. All optional — an un-recorded run still sends input/output + * with an inference-only zero usage split. `costUsd` without a split is treated + * as pure inference (the base stream). + */ +export interface RunReport { + success?: boolean + score?: number + usage?: Partial + costUsd?: number + model?: string + provider?: string + loopEvents?: LoopTraceEvent[] +} + /** Repo coordinates a product may declare for the (later) Gated-PR mode. The * Observe slice only records their PRESENCE for `doctor()`; it never touches * the repo. */ @@ -135,12 +184,13 @@ export interface IntelligenceConfig { /** Effort tier (default `'standard'`) plus optional per-field overrides. */ effort?: EffortTier | { tier: EffortTier; overrides?: EffortOverrides } /** - * OTLP ingest base. The underlying exporter appends `/v1/traces`, so point - * this at the OTLP route (e.g. `https://intelligence.tangle.tools/v1/otlp`). - * Reads `INTELLIGENCE_OTLP_ENDPOINT` then `OTEL_EXPORTER_OTLP_ENDPOINT` when - * omitted; absent all three, export is a no-op (best-effort by construction). + * The ONE Tangle Intelligence base URL — both the send (OTLP `/v1/otlp`) and + * receive (`/v1/profiles/:target/composed`) paths derive from it. Reads + * `TANGLE_INTELLIGENCE_URL` when omitted, else `https://intelligence.tangle.tools`. + * Send is best-effort and only ships when an `apiKey` is present (the tenant + * key the ingest requires); absent a key, export is a no-op. */ - endpoint?: string + baseUrl?: string /** * Redaction hook run over every exported input/output. A function replaces * the default scrubber; `false` opts out entirely (raw fidelity, caller has @@ -241,6 +291,18 @@ export interface IntelligenceClient { * Best-effort: export failures are swallowed. Returns the resolved `traceId`. */ recordTrace(events: ReadonlyArray, meta?: RecordTraceMeta): string + /** + * Send one typed {@link RunRecord} — the run's flat span (input/output/outcome/ + * usage/model/provider, redacted) plus, when `loopEvents` are present, the + * nested loop topology under the same `traceId`. Reuses the shipped + * `flatOtelSpan` + `buildLoopOtelSpans` builders (no second builder). + * Best-effort: export failures are swallowed. Returns the record's `traceId`. + */ + exportRunRecord(record: RunRecord): string + /** Mint a fresh run id (`run-`). */ + freshRunId(): string + /** Mint a fresh 32-hex trace id. */ + freshTraceId(): string /** * Network-free readiness report: which adoption modes are reachable given * this config. Observe is always reachable; Recommend needs outcomes; PR @@ -277,12 +339,6 @@ function resolveEffortConfig(effort: IntelligenceConfig['effort']): EffortSettin return resolveEffort(effort.tier, effort.overrides) } -function resolveEndpoint(endpoint: string | undefined): string | undefined { - if (endpoint) return endpoint - if (typeof process === 'undefined') return undefined - return process.env.INTELLIGENCE_OTLP_ENDPOINT ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT -} - function freshTraceId(): string { return randomHex(32) } @@ -323,10 +379,10 @@ function randomHex(chars: number): string { } /** - * Create an Observe-mode Intelligence client. Resolves effort, endpoint, and - * redactor up front; the exporter is built lazily and is `undefined` when no - * endpoint is configured (export becomes a no-op — best-effort by - * construction). + * Create an Observe-mode Intelligence client. Resolves effort, the base URL, and + * the redactor up front; the exporter is built lazily and is `undefined` when no + * `apiKey` is present (send becomes a no-op — the ingest requires a tenant key, + * and best-effort export must never spam an unauthenticated plane). */ export function createIntelligenceClient(config: IntelligenceConfig): IntelligenceClient { if (!config.project) { @@ -337,18 +393,20 @@ export function createIntelligenceClient(config: IntelligenceConfig): Intelligen const redactor = resolveRedactor(config.redact) const apiKey = config.apiKey ?? (typeof process !== 'undefined' ? process.env.TANGLE_API_KEY : undefined) - const endpoint = resolveEndpoint(config.endpoint) + // The ONE base URL drives both send and receive; the OTLP ingest lives at + // `${base}/v1/otlp` and the exporter appends `/v1/traces` → `${base}/v1/otlp/v1/traces`. + const otlpEndpoint = `${resolveIntelligenceBaseUrl(config.baseUrl)}/v1/otlp` - // Built lazily: a client with no endpoint never allocates an exporter timer. + // Built lazily: a client with no tenant key never allocates an exporter timer. let exporter: OtelExporter | undefined let exporterResolved = false function getExporter(): OtelExporter | undefined { if (exporterResolved) return exporter exporterResolved = true - if (!endpoint) return undefined + if (!apiKey) return undefined exporter = createOtelExporter({ - endpoint, - headers: apiKey ? { authorization: `Bearer ${apiKey}` } : {}, + endpoint: otlpEndpoint, + headers: { authorization: `Bearer ${apiKey}` }, serviceName: config.project, resourceAttributes: { 'tangle.project': config.project }, }) @@ -392,9 +450,66 @@ export function createIntelligenceClient(config: IntelligenceConfig): Intelligen } } + function exportRunRecord(record: RunRecord): string { + const ex = getExporter() + if (!ex) return record.traceId + // Clamp the OFF billing invariant on export — the proof holds even if a + // caller mis-reports an intelligence split at the OFF tier. + const intelligenceUsd = intelligenceOff ? 0 : record.outcome.usage.intelligenceUsd + const labels: Record = { + project: record.project, + 'tangle.target': record.target, + 'tangle.effort.intelligence_off': intelligenceOff, + 'tangle.usage.inference_usd': record.outcome.usage.inferenceUsd, + 'tangle.usage.intelligence_usd': intelligenceUsd, + ...(record.model ? { 'gen_ai.request.model': record.model } : {}), + ...(record.provider ? { 'provider.name': record.provider } : {}), + ...(typeof record.outcome.success === 'boolean' + ? { 'tangle.outcome.success': record.outcome.success } + : {}), + ...(typeof record.outcome.score === 'number' + ? { 'tangle.outcome.score': record.outcome.score } + : {}), + } + const redactedInput = record.input !== undefined ? redactor(record.input) : undefined + const redactedOutput = record.output !== undefined ? redactor(record.output) : undefined + if (redactedInput !== undefined) labels['tangle.input'] = previewJson(redactedInput) + if (redactedOutput !== undefined) labels['tangle.output'] = previewJson(redactedOutput) + try { + const runSpan = flatOtelSpan( + 'tangle.intelligence.run', + { 'tangle.runId': record.runId, ...labels }, + record.traceId, + Date.now(), + ) + ex.exportSpan(runSpan) + // The loop topology (when present) exports under the SAME traceId, parented + // under the run span — reusing the shipped builder, never a second one. + if (record.loopEvents && record.loopEvents.length > 0) { + const spans = buildLoopOtelSpans( + record.loopEvents as ReadonlyArray<{ + kind: string + runId: string + timestamp: number + payload: object + }>, + record.traceId, + runSpan.spanId, + ) + for (const span of spans) ex.exportSpan(span) + } + } catch { + // Best-effort — a send failure must never fail the agent's turn. + } + return record.traceId + } + return { project: config.project, effort, + exportRunRecord, + freshRunId, + freshTraceId, async traceRun(meta: TraceMeta, fn: (trace: TraceHandle) => Promise): Promise { const runId = meta.runId ?? freshRunId() @@ -491,7 +606,9 @@ export function createIntelligenceClient(config: IntelligenceConfig): Intelligen return { project: config.project, effort, - exportConfigured: Boolean(endpoint), + // Send ships only with a tenant key — the honest "will export actually + // land" signal (the base URL always resolves to the plane default). + exportConfigured: Boolean(apiKey), modes: { observe: { ready: true, missing: [] }, recommend: { @@ -514,40 +631,3 @@ export function createIntelligenceClient(config: IntelligenceConfig): Intelligen }, } } - -/** A generic agent: one async input → output. The shape `withTangleIntelligence` - * preserves exactly. */ -export type Agent = (input: TInput) => Promise - -/** Either a built client or the config to build one. */ -export type ClientOrConfig = IntelligenceClient | IntelligenceConfig - -function isClient(value: ClientOrConfig): value is IntelligenceClient { - return typeof (value as IntelligenceClient).traceRun === 'function' -} - -/** - * Wrap a generic `agent` with best-effort Observe-mode tracing, returning the - * SAME shape. Each call runs the agent under a trace and exports one span; an - * export failure is swallowed (the live agent never fails because Intelligence - * is down) but an error from the agent itself propagates unchanged. - * - * At `effort: 'off'` this is pure passthrough plus best-effort telemetry — - * zero intelligence spawns, `intelligenceUsd: 0` on the trace. - */ -export function withTangleIntelligence( - agent: Agent, - clientOrConfig: ClientOrConfig, -): Agent { - const client = isClient(clientOrConfig) - ? clientOrConfig - : createIntelligenceClient(clientOrConfig) - - return async (input: TInput): Promise => { - return client.traceRun({ input }, async (trace) => { - const output = await agent(input) - trace.recordOutput(output) - return output - }) - } -} diff --git a/src/intelligence/intelligence.test.ts b/src/intelligence/intelligence.test.ts index 2a6e9913..88ec642e 100644 --- a/src/intelligence/intelligence.test.ts +++ b/src/intelligence/intelligence.test.ts @@ -7,10 +7,9 @@ import { isIntelligenceOff, resolveEffort, type UsageSplit, - withTangleIntelligence, } from './index' -const endpoint = 'https://intelligence.test/v1/otlp' +const baseUrl = 'https://intelligence.test' const apiKey = 'sk-tan-test-key' /** Capture every OTLP POST body the exporter flushes. */ @@ -42,9 +41,9 @@ function installFetchSpy(mode: 'ok' | 'throw'): { calls: FetchCall[] } { return { calls } } -function stubNoEndpointEnv(): void { - vi.stubEnv('INTELLIGENCE_OTLP_ENDPOINT', '') - vi.stubEnv('OTEL_EXPORTER_OTLP_ENDPOINT', '') +/** Stub away any ambient tenant key so a "no apiKey" client truly has none. */ +function stubNoApiKey(): void { + vi.stubEnv('TANGLE_API_KEY', '') } /** Pull every span attribute across an OTLP export body into one flat map. */ @@ -69,8 +68,24 @@ function attrsOf(body: unknown): Record { return out } +/** Pull every span's `name` + `traceId` across an OTLP export body. */ +function spansOf(body: unknown): Array<{ name: string; traceId: string }> { + const out: Array<{ name: string; traceId: string }> = [] + const resourceSpans = (body as { resourceSpans?: unknown[] })?.resourceSpans ?? [] + for (const rs of resourceSpans) { + for (const ss of (rs as { scopeSpans?: unknown[] }).scopeSpans ?? []) { + for (const span of (ss as { spans?: unknown[] }).spans ?? []) { + const s = span as { name?: string; traceId?: string } + out.push({ name: String(s.name), traceId: String(s.traceId) }) + } + } + } + return out +} + afterEach(() => { vi.unstubAllGlobals() + vi.unstubAllEnvs() vi.restoreAllMocks() }) @@ -107,7 +122,6 @@ describe('resolveEffort', () => { const s = resolveEffort('off', { analysts: true, fanout: 4 }) expect(s.analysts).toBe(true) expect(s.fanout).toBe(4) - // Overriding any axis lifts a run off the OFF floor. expect(isIntelligenceOff(s)).toBe(false) }) @@ -151,7 +165,7 @@ describe('createIntelligenceClient / traceRun — Observe', () => { it('exports one span on a successful run and returns the agent output', async () => { const { calls } = installFetchSpy('ok') - const client = createIntelligenceClient({ project: 'support-agent', apiKey, endpoint }) + const client = createIntelligenceClient({ project: 'support-agent', apiKey, baseUrl }) const result = await client.traceRun({ input: { q: 'hi' } }, async (trace) => { trace.recordOutput({ answer: 'hello' }) trace.recordOutcome({ success: true, score: 0.9, costUsd: 0.002 }) @@ -168,7 +182,7 @@ describe('createIntelligenceClient / traceRun — Observe', () => { it('survives a dead endpoint — agent output is returned, no throw', async () => { installFetchSpy('throw') - const client = createIntelligenceClient({ project: 'support-agent', apiKey, endpoint }) + const client = createIntelligenceClient({ project: 'support-agent', apiKey, baseUrl }) const result = await client.traceRun({ input: { q: 'hi' } }, async (trace) => { trace.recordOutput({ answer: 'still here' }) return 42 @@ -179,7 +193,7 @@ describe('createIntelligenceClient / traceRun — Observe', () => { it('propagates an error thrown by the agent body (not swallowed)', async () => { installFetchSpy('ok') - const client = createIntelligenceClient({ project: 'support-agent', apiKey, endpoint }) + const client = createIntelligenceClient({ project: 'support-agent', apiKey, baseUrl }) await expect( client.traceRun({ input: {} }, async () => { throw new Error('agent exploded') @@ -189,7 +203,7 @@ describe('createIntelligenceClient / traceRun — Observe', () => { it('redacts secrets in the exported input/output', async () => { const { calls } = installFetchSpy('ok') - const client = createIntelligenceClient({ project: 'p', apiKey, endpoint }) + const client = createIntelligenceClient({ project: 'p', apiKey, baseUrl }) await client.traceRun( { input: { prompt: 'my key is sk-tan-leakedsecretvalue999' } }, async (trace) => { @@ -203,11 +217,11 @@ describe('createIntelligenceClient / traceRun — Observe', () => { expect(blob).not.toContain('alice@acme.com') }) - it('is a no-op (no fetch) when no endpoint is configured', async () => { - stubNoEndpointEnv() + it('is a no-op (no fetch) when no tenant apiKey is present', async () => { + stubNoApiKey() const spy = vi.fn() vi.stubGlobal('fetch', spy) - const client = createIntelligenceClient({ project: 'p', apiKey }) + const client = createIntelligenceClient({ project: 'p', baseUrl }) const out = await client.traceRun({ input: {} }, async () => 'x') await client.flush() expect(out).toBe('x') @@ -221,11 +235,10 @@ describe('billing classification — OFF proves inference-only', () => { const client = createIntelligenceClient({ project: 'p', apiKey, - endpoint, + baseUrl, effort: 'off', }) await client.traceRun({ input: { q: 'x' } }, async (trace) => { - // Even a caller that mis-reports intelligence spend at OFF is clamped. trace.recordOutcome({ usage: { inferenceUsd: 0.01, intelligenceUsd: 0.05 } satisfies UsageSplit, }) @@ -235,13 +248,12 @@ describe('billing classification — OFF proves inference-only', () => { const attrs = attrsOf(calls[0]?.body) expect(attrs['tangle.effort.intelligence_off']).toBe(true) expect(attrs['tangle.usage.intelligence_usd']).toBe(0) - // Inference is still billed — OFF is not "free", it is inference-only. expect(attrs['tangle.usage.inference_usd']).toBe(0.01) }) it('a non-off tier preserves a reported intelligence split', async () => { const { calls } = installFetchSpy('ok') - const client = createIntelligenceClient({ project: 'p', apiKey, endpoint, effort: 'standard' }) + const client = createIntelligenceClient({ project: 'p', apiKey, baseUrl, effort: 'standard' }) await client.traceRun({ input: {} }, async (trace) => { trace.recordOutcome({ usage: { inferenceUsd: 0.01, intelligenceUsd: 0.03 } }) return 'ok' @@ -253,38 +265,9 @@ describe('billing classification — OFF proves inference-only', () => { }) }) -describe('withTangleIntelligence', () => { - it('preserves the agent shape and traces each call', async () => { - const { calls } = installFetchSpy('ok') - const agent = async (input: { name: string }) => `hi ${input.name}` - const wrapped = withTangleIntelligence(agent, { project: 'p', apiKey, endpoint }) - const out = await wrapped({ name: 'drew' }) - // Flush is internal to the client; force one export round. - expect(out).toBe('hi drew') - // The wrapper builds its own client; give the batch a manual flush via a fresh call path. - expect(calls.length).toBeGreaterThanOrEqual(0) - }) - - it('never fails the agent when telemetry export is down', async () => { - installFetchSpy('throw') - const agent = async () => 'result' - const wrapped = withTangleIntelligence(agent, { project: 'p', apiKey, endpoint }) - await expect(wrapped(undefined)).resolves.toBe('result') - }) - - it('propagates an agent error through the wrapper', async () => { - installFetchSpy('ok') - const agent = async () => { - throw new Error('boom') - } - const wrapped = withTangleIntelligence(agent, { project: 'p', apiKey, endpoint }) - await expect(wrapped(undefined)).rejects.toThrow('boom') - }) -}) - describe('doctor()', () => { it('reports observe always reachable, pr blocked without checks/surfaces/repo', () => { - const client = createIntelligenceClient({ project: 'p', apiKey, endpoint }) + const client = createIntelligenceClient({ project: 'p', apiKey, baseUrl }) const report = client.doctor() expect(report.modes.observe.ready).toBe(true) expect(report.modes.pr.ready).toBe(false) @@ -296,7 +279,7 @@ describe('doctor()', () => { const client = createIntelligenceClient({ project: 'p', apiKey, - endpoint, + baseUrl, checks: ['pnpm test'], surfaces: ['src/**'], repo: { owner: 'acme', name: 'p', baseBranch: 'main' }, @@ -307,34 +290,19 @@ describe('doctor()', () => { }) it('flags recommend as blocked at the off floor', () => { - const client = createIntelligenceClient({ project: 'p', apiKey, endpoint, effort: 'off' }) + const client = createIntelligenceClient({ project: 'p', apiKey, baseUrl, effort: 'off' }) const report = client.doctor() expect(report.modes.recommend.ready).toBe(false) expect(report.modes.recommend.missing).toContain('effort above off') }) - it('reports exportConfigured:false when no endpoint resolves', () => { - stubNoEndpointEnv() - const client = createIntelligenceClient({ project: 'p', apiKey }) + it('reports exportConfigured:false when no tenant apiKey resolves', () => { + stubNoApiKey() + const client = createIntelligenceClient({ project: 'p', baseUrl }) expect(client.doctor().exportConfigured).toBe(false) }) }) -/** Pull every span's `name` + `traceId` across an OTLP export body. */ -function spansOf(body: unknown): Array<{ name: string; traceId: string }> { - const out: Array<{ name: string; traceId: string }> = [] - const resourceSpans = (body as { resourceSpans?: unknown[] })?.resourceSpans ?? [] - for (const rs of resourceSpans) { - for (const ss of (rs as { scopeSpans?: unknown[] }).scopeSpans ?? []) { - for (const span of (ss as { spans?: unknown[] }).spans ?? []) { - const s = span as { name?: string; traceId?: string } - out.push({ name: String(s.name), traceId: String(s.traceId) }) - } - } - } - return out -} - /** A minimal but real loop event stream: a plan round over two iterations. */ function loopStream(runId = 'loop-run'): LoopTraceEvent[] { return [ @@ -400,20 +368,16 @@ function loopStream(runId = 'loop-run'): LoopTraceEvent[] { describe('recordTrace — loop topology via buildLoopOtelSpans (gap 2)', () => { it('exports a nested loop→round→iteration span tree under ONE traceId', async () => { const { calls } = installFetchSpy('ok') - const client = createIntelligenceClient({ project: 'support-agent', apiKey, endpoint }) + const client = createIntelligenceClient({ project: 'support-agent', apiKey, baseUrl }) const traceId = client.recordTrace(loopStream(), { traceId: 'a'.repeat(32) }) await client.flush() expect(traceId).toBe('a'.repeat(32)) const spans = calls.flatMap((c) => spansOf(c.body)) const names = spans.map((s) => s.name) - // The TREE builder emits topology-level names; a flat per-event builder would emit the - // raw event kinds (loop.started/loop.iteration.ended). Asserting these proves reuse of - // buildLoopOtelSpans, not a second span builder. expect(names).toContain('loop') expect(names).toContain('loop.round') expect(names.filter((n) => n === 'loop.iteration').length).toBe(2) - // Every span shares the supplied traceId (one trace, not N). const traceIds = new Set(spans.map((s) => s.traceId)) expect(traceIds.size).toBe(1) expect([...traceIds][0]).toBe('a'.repeat(32)) @@ -421,23 +385,98 @@ describe('recordTrace — loop topology via buildLoopOtelSpans (gap 2)', () => { it('mints a fresh traceId when none is supplied and survives a dead endpoint', async () => { installFetchSpy('throw') - const client = createIntelligenceClient({ project: 'p', apiKey, endpoint }) + const client = createIntelligenceClient({ project: 'p', apiKey, baseUrl }) const traceId = client.recordTrace(loopStream()) expect(traceId).toMatch(/^[0-9a-f]{32}$/) - // Export failure is swallowed — recordTrace never throws. await expect(client.flush()).resolves.toBeUndefined() }) - it('is a no-op (no fetch) on an empty event stream or with no endpoint', async () => { - stubNoEndpointEnv() + it('is a no-op (no fetch) on an empty event stream or with no tenant key', async () => { + stubNoApiKey() const spy = vi.fn() vi.stubGlobal('fetch', spy) - const withEndpoint = createIntelligenceClient({ project: 'p', apiKey, endpoint }) - withEndpoint.recordTrace([]) - await withEndpoint.flush() - const noEndpoint = createIntelligenceClient({ project: 'p', apiKey }) - noEndpoint.recordTrace(loopStream()) - await noEndpoint.flush() + const withKey = createIntelligenceClient({ project: 'p', apiKey, baseUrl }) + withKey.recordTrace([]) + await withKey.flush() + const noKey = createIntelligenceClient({ project: 'p', baseUrl }) + noKey.recordTrace(loopStream()) + await noKey.flush() + expect(spy).not.toHaveBeenCalled() + }) +}) + +describe('exportRunRecord — typed RunRecord send (the withIntelligence SEND path)', () => { + it('ships one flat run span with target/usage/model + the loop topology under one traceId', async () => { + const { calls } = installFetchSpy('ok') + const client = createIntelligenceClient({ project: 'support-agent', apiKey, baseUrl }) + const traceId = client.exportRunRecord({ + runId: 'run-1', + traceId: 'b'.repeat(32), + project: 'support-agent', + target: 'support-agent', + input: { q: 'hi' }, + output: { a: 'ok' }, + outcome: { + success: true, + score: 0.9, + usage: { inferenceUsd: 0.002, intelligenceUsd: 0.001 }, + }, + model: 'kimi-k2', + provider: 'moonshot', + loopEvents: loopStream(), + }) + await client.flush() + + expect(traceId).toBe('b'.repeat(32)) + const attrs = attrsOf(calls[0]?.body) + expect(attrs['tangle.target']).toBe('support-agent') + expect(attrs['tangle.usage.inference_usd']).toBe(0.002) + expect(attrs['tangle.usage.intelligence_usd']).toBe(0.001) + expect(attrs['tangle.outcome.success']).toBe(true) + expect(attrs['gen_ai.request.model']).toBe('kimi-k2') + + const spans = calls.flatMap((c) => spansOf(c.body)) + const names = spans.map((s) => s.name) + expect(names).toContain('tangle.intelligence.run') + expect(names).toContain('loop') + const traceIds = new Set(spans.map((s) => s.traceId)) + expect(traceIds.size).toBe(1) + }) + + it('clamps intelligence_usd to 0 at the OFF tier even if the record reports spend', async () => { + const { calls } = installFetchSpy('ok') + const client = createIntelligenceClient({ project: 'p', apiKey, baseUrl, effort: 'off' }) + client.exportRunRecord({ + runId: 'r', + traceId: 'c'.repeat(32), + project: 'p', + target: 'p', + input: {}, + output: {}, + outcome: { usage: { inferenceUsd: 0.01, intelligenceUsd: 0.05 } }, + }) + await client.flush() + const attrs = attrsOf(calls[0]?.body) + expect(attrs['tangle.usage.intelligence_usd']).toBe(0) + expect(attrs['tangle.usage.inference_usd']).toBe(0.01) + }) + + it('is a no-op (no fetch) when no tenant apiKey is present', async () => { + stubNoApiKey() + const spy = vi.fn() + vi.stubGlobal('fetch', spy) + const client = createIntelligenceClient({ project: 'p', baseUrl }) + const traceId = client.exportRunRecord({ + runId: 'r', + traceId: 'd'.repeat(32), + project: 'p', + target: 'p', + input: {}, + output: {}, + outcome: { usage: { inferenceUsd: 0, intelligenceUsd: 0 } }, + }) + await client.flush() + expect(traceId).toBe('d'.repeat(32)) expect(spy).not.toHaveBeenCalled() }) }) @@ -451,7 +490,6 @@ describe('compileEffort — EffortSettings → run-config overrides (gap 3/4)', withLoops: false, intelligenceBudgetUsd: 0, }) - // The product fail-closed: at off the caller omits the analyst (degrade, not throw). expect(compiled.withAnalyst).toBe(false) }) @@ -470,7 +508,6 @@ describe('compileEffort — EffortSettings → run-config overrides (gap 3/4)', }) it('carries a per-field override through to the compiled overrides', () => { - // Overriding analysts back on at off lifts the analyst-construction gate. const compiled = compileEffort(resolveEffort('off', { analysts: true, fanout: 4 })) expect(compiled.withAnalyst).toBe(true) expect(compiled.fanout).toBe(4) diff --git a/src/intelligence/resolver.ts b/src/intelligence/resolver.ts index f1068b63..ed2bc7ae 100644 --- a/src/intelligence/resolver.ts +++ b/src/intelligence/resolver.ts @@ -182,8 +182,6 @@ export async function composeCertifiedProfile( const promptAdditions = collectPromptAdditions(manifest.promptSurface, acc.contextArtifacts) const systemPrompt = composeCertifiedPrompt(base.systemPrompt, { - target: manifest.target, - generatedAt: manifest.generatedAt, promptSurface: manifest.promptSurface, artifacts: acc.contextArtifacts, }) @@ -219,8 +217,6 @@ function baseSurface( promptSurface: CapabilityManifest['promptSurface'], ): ResolvedSurface { const folded = composeCertifiedPrompt(systemPrompt, { - target: '', - generatedAt: '', promptSurface, artifacts: {}, }) diff --git a/src/intelligence/with-intelligence.test.ts b/src/intelligence/with-intelligence.test.ts new file mode 100644 index 00000000..09d6538d --- /dev/null +++ b/src/intelligence/with-intelligence.test.ts @@ -0,0 +1,270 @@ +import type { AgentProfile, AgentProfileDiff } from '@tangle-network/agent-interface' +import { applyAgentProfileDiff } from '@tangle-network/agent-interface' +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { ProposedProfileDiff } from './delivery' +import type { AppliedIntelligence } from './with-intelligence' +import { withIntelligence } from './with-intelligence' + +/** A valid, promoted profile diff — the previously-DROPPED typed artifact the + * composed endpoint returns alongside prompt/skill artifacts. */ +const DIFF: AgentProfileDiff = { + schemaVersion: 1, + kind: 'agent-profile-diff', + id: 'diff-1', + title: 'certified refund tool', + set: { tools: { refund: true }, metadata: { certified: true } }, +} + +/** The composed-endpoint fixture, carrying `agentProfileDiffs[]`. */ +const COMPOSED = { + target: 'support-agent', + generatedAt: '2026-06-13T00:00:00.000Z', + promptSurface: { + surface: 'Confirm the invoice id before refunding.', + surfaceHash: 'abc123', + version: 4, + lift: '+3.1pp', + }, + artifacts: {}, + capabilities: [ + { + id: 'prompt-surface', + iface: { surface: 'context' }, + binding: { path: null, content: 'Confirm the invoice id before refunding.' }, + provenance: { + contentHash: 'abc123', + version: 4, + lift: '+3.1pp', + promotedAt: '2026-06-12T00:00:00.000Z', + }, + }, + ], + agentProfileDiffs: [ + { + diff: DIFF, + provenance: { + version: 7, + lift: '+2.2pp', + contentHash: 'deadbeef', + promotedAt: '2026-06-12T00:00:00.000Z', + }, + }, + ], + agentProfile: { name: 'support-agent', tools: { refund: true }, metadata: { certified: true } }, +} + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { 'content-type': 'application/json' }, + }) +} + +afterEach(() => { + vi.unstubAllGlobals() + vi.restoreAllMocks() +}) + +describe('withIntelligence — RECEIVE (the previously-dropped typed diffs)', () => { + it('deserializes agentProfileDiffs and surfaces them as proposals (round-trip)', async () => { + const fetchImpl = vi.fn(async () => jsonResponse(COMPOSED)) as unknown as typeof fetch + let applied: AppliedIntelligence | undefined + const agent = withIntelligence( + async (_input: null, a) => { + applied = a + return 'ok' + }, + { project: 'support-agent', apiKey: 'k', baseUrl: 'https://plane.test', fetchImpl }, + ) + await agent(null) + + // The typed diffs the OLD receive path dropped now round-trip verbatim. + expect(agent.proposals()).toEqual(COMPOSED.agentProfileDiffs) + expect(applied?.proposals).toEqual(COMPOSED.agentProfileDiffs) + }) + + it('applyProfile folds the proposals exactly as applyAgentProfileDiff would', async () => { + const fetchImpl = vi.fn(async () => jsonResponse(COMPOSED)) as unknown as typeof fetch + let applied: AppliedIntelligence | undefined + const agent = withIntelligence( + async (_input: null, a) => { + applied = a + return 'ok' + }, + { project: 'support-agent', apiKey: 'k', baseUrl: 'https://plane.test', fetchImpl }, + ) + await agent(null) + + const base: AgentProfile = { name: 'support-agent' } + // The hook's fold == the canonical single-diff application. + expect(applied?.applyProfile(base)).toEqual(applyAgentProfileDiff(base, DIFF)) + }) + + it('fires onProposals once with the promoted set (silent on an unchanged refresh)', async () => { + const fetchImpl = vi.fn(async () => jsonResponse(COMPOSED)) as unknown as typeof fetch + const seen: ProposedProfileDiff[][] = [] + const agent = withIntelligence(async () => 'ok', { + project: 'support-agent', + apiKey: 'k', + baseUrl: 'https://plane.test', + fetchImpl, + onProposals: (p) => seen.push(p), + }) + await agent(null) + await agent(null) + expect(seen).toHaveLength(1) + expect(seen[0]).toEqual(COMPOSED.agentProfileDiffs) + }) +}) + +describe('withIntelligence — SAFETY (observe + deliver only, never auto-apply)', () => { + it('delivers ONLY the certified prompt surface into the prompt, never the raw diff', async () => { + const fetchImpl = vi.fn(async () => jsonResponse(COMPOSED)) as unknown as typeof fetch + let composed = '' + const agent = withIntelligence( + async (_input: null, a) => { + composed = a.composePrompt('BASE PROMPT') + return 'ok' + }, + { project: 'support-agent', apiKey: 'k', baseUrl: 'https://plane.test', fetchImpl }, + ) + await agent(null) + + expect(composed).toContain('BASE PROMPT') + expect(composed).toContain('Confirm the invoice id before refunding') + // The typed diff is a PROPOSAL — it must not leak into the delivered prompt. + expect(composed).not.toContain('agent-profile-diff') + // Proposals are surfaced, but the hook applied nothing on the run path. + expect(agent.proposals()).toHaveLength(1) + }) + + it('runs fail-closed on the base surface when the pull 404s (nothing promoted)', async () => { + const fetchImpl = vi.fn( + async () => new Response('', { status: 404 }), + ) as unknown as typeof fetch + let sawCertified: unknown = 'unset' + const agent = withIntelligence( + async (_input: null, a) => { + sawCertified = a.certified + return a.certified === null && a.proposals.length === 0 ? 'base' : 'x' + }, + { project: 'p', apiKey: 'k', baseUrl: 'https://plane.test', fetchImpl }, + ) + expect(await agent(null)).toBe('base') + expect(sawCertified).toBeNull() + expect(agent.proposals()).toEqual([]) + }) + + it('never breaks the agent when Intelligence is unreachable', async () => { + const fetchImpl = vi.fn(async () => { + throw new Error('network down') + }) as unknown as typeof fetch + const agent = withIntelligence(async (input: number) => input * 2, { + project: 'p', + apiKey: 'k', + baseUrl: 'https://plane.test', + fetchImpl, + }) + await expect(agent(21)).resolves.toBe(42) + }) + + it('propagates an agent error (delivery never swallows the live path)', async () => { + const fetchImpl = vi.fn(async () => jsonResponse(COMPOSED)) as unknown as typeof fetch + const agent = withIntelligence( + async (_i: null) => { + throw new Error('agent boom') + }, + { project: 'support-agent', apiKey: 'k', baseUrl: 'https://plane.test', fetchImpl }, + ) + await expect(agent(null)).rejects.toThrow('agent boom') + }) +}) + +/** Pull every span attribute across an OTLP export body into one flat map. */ +function attrsOf(body: unknown): Record { + const out: Record = {} + const resourceSpans = (body as { resourceSpans?: unknown[] })?.resourceSpans ?? [] + for (const rs of resourceSpans) { + for (const ss of (rs as { scopeSpans?: unknown[] }).scopeSpans ?? []) { + for (const span of (ss as { spans?: unknown[] }).spans ?? []) { + for (const a of (span as { attributes?: unknown[] }).attributes ?? []) { + const attr = a as { key: string; value: Record } + const v = attr.value + out[attr.key] = + v.stringValue ?? + (v.intValue !== undefined ? Number(v.intValue) : undefined) ?? + v.doubleValue ?? + v.boolValue + } + } + } + } + return out +} + +describe('withIntelligence — SEND (a typed RunRecord to /v1/otlp)', () => { + it('ships one run span carrying target + usage split + model, best-effort', async () => { + vi.useFakeTimers() + try { + const posts: unknown[] = [] + const otlpSpy = vi.fn(async (_url: unknown, init: unknown) => { + const body = (init as { body?: string })?.body + if (body) posts.push(JSON.parse(body)) + return { ok: true, status: 200, async json() {} } as unknown as Response + }) + vi.stubGlobal('fetch', otlpSpy) + // The pull rides its own fetchImpl; SEND rides global fetch (the exporter). + const pull = vi.fn(async () => jsonResponse(COMPOSED)) as unknown as typeof fetch + const agent = withIntelligence( + async (_input: { q: string }, a) => { + a.record({ + success: true, + usage: { inferenceUsd: 0.002, intelligenceUsd: 0 }, + model: 'kimi-k2', + provider: 'moonshot', + }) + return 'answer' + }, + { + project: 'support-agent', + target: 'support-agent', + apiKey: 'k', + baseUrl: 'https://plane.test', + fetchImpl: pull, + }, + ) + await agent({ q: 'refund please' }) + // Force the exporter's interval flush so the batched span POSTs. + await vi.advanceTimersByTimeAsync(6000) + + expect(posts.length).toBeGreaterThan(0) + const attrs = attrsOf(posts[0]) + expect(attrs.project).toBe('support-agent') + expect(attrs['tangle.target']).toBe('support-agent') + expect(attrs['tangle.usage.inference_usd']).toBe(0.002) + expect(attrs['tangle.usage.intelligence_usd']).toBe(0) + expect(attrs['tangle.outcome.success']).toBe(true) + expect(attrs['gen_ai.request.model']).toBe('kimi-k2') + } finally { + vi.useRealTimers() + } + }) + + it('does not send when no tenant apiKey is present', async () => { + const otlpSpy = vi.fn() + vi.stubGlobal('fetch', otlpSpy) + vi.stubEnv('TANGLE_API_KEY', '') + try { + const pull = vi.fn(async () => new Response('', { status: 404 })) as unknown as typeof fetch + const agent = withIntelligence(async () => 'ok', { + project: 'p', + baseUrl: 'https://plane.test', + fetchImpl: pull, + }) + expect(await agent(null)).toBe('ok') + expect(otlpSpy).not.toHaveBeenCalled() + } finally { + vi.unstubAllEnvs() + } + }) +}) diff --git a/src/intelligence/with-intelligence.ts b/src/intelligence/with-intelligence.ts new file mode 100644 index 00000000..2c1fe939 --- /dev/null +++ b/src/intelligence/with-intelligence.ts @@ -0,0 +1,188 @@ +/** + * + * `withIntelligence` — the ONE agent-runtime hook that unifies Tangle + * Intelligence send + receive. + * + * It does two things per call, both fail-open: + * 1. RECEIVE — pull the tenant's certified profile from the deployed plane + * (cached + refreshed), hand the agent the certified prompt surface to fold + * (`applied.composePrompt`) and the promoted, gate-certified profile DIFFS + * as PROPOSALS (`applied.proposals` / `applied.applyProfile`). + * 2. SEND — serialize a typed {@link RunRecord} for the call through the shipped + * OTLP builders to the plane's `/v1/otlp` ingest, best-effort. + * + * SAFETY — observe + deliver ONLY. This hook NEVER auto-applies a received diff + * at runtime. A promoted diff is surfaced as a proposal; a human, or the gated + * local `improve()` loop, turns a proposal into a shipped profile. That + * preserves the held-out invariant the plane enforces — a runtime that silently + * folded an un-gated diff into itself would defeat the very gate the diff cleared. + * + * import { withIntelligence } from '@tangle-network/agent-runtime/intelligence' + * + * export const agent = withIntelligence( + * async (input, applied) => { + * const out = await myAgent(input, { systemPrompt: applied.composePrompt(BASE) }) + * applied.record({ success: true, usage: { inferenceUsd: 0.002, intelligenceUsd: 0 } }) + * return out + * }, + * { project: 'support-agent', target: 'support-agent' }, + * ) + * + * @experimental + */ + +import type { AgentProfile } from '@tangle-network/agent-interface' +import { applyAgentProfileDiff } from '@tangle-network/agent-interface' +import { + type CertifiedProfile, + composeCertifiedPrompt, + createCertifiedPromptSource, + type ProposedProfileDiff, +} from './delivery' +import { + createIntelligenceClient, + type IntelligenceConfig, + type RunRecord, + type RunReport, +} from './index' + +/** What the hook hands the agent each run. Additive over the prompt-only + * delivery: `composePrompt` folds the certified prompt surface (as before); + * `proposals`/`applyProfile` surface the promoted profile DIFFS — never + * auto-applied; `record` enriches the {@link RunRecord} that is sent. */ +export interface AppliedIntelligence { + /** The certified profile in effect (null when none promoted / pull failed — + * fail-closed: the agent runs on its base surface). */ + certified: CertifiedProfile | null + /** Fold the certified prompt surface into a base system prompt (the promoted + * prompt). The consumer opts in by calling it. */ + composePrompt(base: string): string + /** The promoted, gate-certified profile diffs — surfaced for a human or the + * gated `improve()` loop. NEVER auto-applied by this hook. Empty when none. */ + proposals: ProposedProfileDiff[] + /** Fold every proposal into `base` via `applyAgentProfileDiff`, in promotion + * order, and return the result. The caller invokes this EXPLICITLY (it is the + * human/gated apply step) — the hook never calls it on the run path. */ + applyProfile(base: AgentProfile): AgentProfile + /** Enrich the {@link RunRecord} sent for this call — outcome, usage split, + * model/provider, and the loop event stream. Optional; an un-recorded run + * still sends input/output with an inference-only zero usage split. */ + record(report: RunReport): void +} + +/** An agent wrapped by {@link withIntelligence}: receives the input plus the + * intelligence delivered for this run. */ +export type IntelligenceAgent = (input: I, applied: AppliedIntelligence) => Promise + +/** `withIntelligence` config = the Observe config plus the pull target, refresh + * cadence, and a proposals callback. One base URL (`baseUrl` / + * `TANGLE_INTELLIGENCE_URL`) drives both the send and receive paths. */ +export interface IntelligenceHookConfig extends IntelligenceConfig { + /** Pull target. Defaults to `project`. */ + target?: string + /** Min interval between certified-profile pulls. Default 5m. */ + refreshMs?: number + /** Per-pull timeout in ms (fail-closed on a hung plane). Default 10000. */ + timeoutMs?: number + /** fetch impl for the pull (tests). Defaults to global fetch. */ + fetchImpl?: typeof fetch + /** Notified when a refresh delivers a NEW set of promoted proposals (by + * provenance content hash). Surfaces diffs without auto-applying them. */ + onProposals?: (proposals: ProposedProfileDiff[]) => void +} + +/** The wrapped agent — same `(input) => Promise` shape, plus a manual + * `refresh()` and a `proposals()` accessor for the currently-promoted diffs. */ +export type IntelligenceWrapped = ((input: I) => Promise) & { + refresh(): Promise + proposals(): ProposedProfileDiff[] +} + +/** + * Wrap an agent so it (a) RECEIVES the tenant's certified profile — the prompt + * surface to fold and the promoted profile diffs as proposals — and (b) SENDS a + * typed {@link RunRecord} per call to the plane. The pull is cached and refreshed + * at most every `refreshMs`; a failed pull is fail-closed (the agent runs on its + * base surface, never breaks because Intelligence is unreachable). The send is + * best-effort — an export failure never fails the agent's turn — while an error + * thrown by the agent itself propagates unchanged. + */ +export function withIntelligence( + agent: IntelligenceAgent, + config: IntelligenceHookConfig, +): IntelligenceWrapped { + const client = createIntelligenceClient(config) + const target = config.target ?? config.project + const source = createCertifiedPromptSource({ + target, + ...(config.apiKey !== undefined ? { apiKey: config.apiKey } : {}), + ...(config.baseUrl !== undefined ? { baseUrl: config.baseUrl } : {}), + ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}), + ...(config.fetchImpl !== undefined ? { fetchImpl: config.fetchImpl } : {}), + ...(config.refreshMs !== undefined ? { refreshMs: config.refreshMs } : {}), + }) + + const currentProposals = (): ProposedProfileDiff[] => source.current()?.agentProfileDiffs ?? [] + + // Fire `onProposals` only when the promoted set actually changes (keyed by + // provenance content hash) — a refresh that re-delivers the same diffs is silent. + let lastSignal = '' + function signalProposals(): void { + const proposals = currentProposals() + if (proposals.length === 0) return + const sig = proposals.map((p) => p.provenance.contentHash).join('|') + if (sig === lastSignal) return + lastSignal = sig + config.onProposals?.(proposals) + } + + async function refresh(): Promise { + await source.refresh() + signalProposals() + } + + const wrapped = (async (input: I): Promise => { + await refresh() + const certified = source.current() + const proposals = currentProposals() + const report: RunReport = {} + const applied: AppliedIntelligence = { + certified, + composePrompt: (base: string) => composeCertifiedPrompt(base, certified), + proposals, + applyProfile: (base: AgentProfile) => + proposals.reduce((profile, p) => applyAgentProfileDiff(profile, p.diff), base), + record: (r: RunReport) => Object.assign(report, r), + } + + const output = await agent(input, applied) + + const usage = { + inferenceUsd: report.usage?.inferenceUsd ?? report.costUsd ?? 0, + intelligenceUsd: report.usage?.intelligenceUsd ?? 0, + } + const record: RunRecord = { + runId: client.freshRunId(), + traceId: client.freshTraceId(), + project: config.project, + target, + input, + output, + outcome: { + ...(report.success !== undefined ? { success: report.success } : {}), + ...(report.score !== undefined ? { score: report.score } : {}), + usage, + }, + ...(report.model !== undefined ? { model: report.model } : {}), + ...(report.provider !== undefined ? { provider: report.provider } : {}), + ...(report.loopEvents !== undefined ? { loopEvents: report.loopEvents } : {}), + } + // Best-effort: a send failure never fails the agent's turn. + client.exportRunRecord(record) + return output + }) as IntelligenceWrapped + + wrapped.refresh = refresh + wrapped.proposals = currentProposals + return wrapped +} diff --git a/src/otel-export.ts b/src/otel-export.ts index 18119b8a..92509aac 100644 --- a/src/otel-export.ts +++ b/src/otel-export.ts @@ -595,7 +595,7 @@ export interface EvalRunEvent { export interface EvalRunsExportConfig { /** Bearer key — tenant is resolved server-side from it. Reads TANGLE_API_KEY. */ apiKey?: string - /** Intelligence base. Reads INTELLIGENCE_BASE env, else prod. */ + /** Intelligence base. Reads TANGLE_INTELLIGENCE_URL env, else prod. */ base?: string /** Idempotency-Key header (e.g. the runId) — safe retries + upsert. */ idempotencyKey?: string @@ -627,7 +627,7 @@ export async function exportEvalRuns( throw new Error('exportEvalRuns: apiKey required (pass config.apiKey or set TANGLE_API_KEY)') const base = config?.base ?? - (typeof process !== 'undefined' ? process.env.INTELLIGENCE_BASE : undefined) ?? + (typeof process !== 'undefined' ? process.env.TANGLE_INTELLIGENCE_URL : undefined) ?? DEFAULT_INTELLIGENCE_BASE const url = `${base.replace(/\/+$/, '')}/v1/ingest/eval-runs` const res = await fetch(url, { diff --git a/tests/otel-export.test.ts b/tests/otel-export.test.ts index b5949953..44702fa7 100644 --- a/tests/otel-export.test.ts +++ b/tests/otel-export.test.ts @@ -492,7 +492,7 @@ describe('otel-export', () => { describe('exportEvalRuns (Intelligence self-improvement provenance)', () => { afterEach(() => { delete process.env.TANGLE_API_KEY - delete process.env.INTELLIGENCE_BASE + delete process.env.TANGLE_INTELLIGENCE_URL vi.unstubAllGlobals() })