From 382c2056f78130de2c86a6ec1e1883ee862d298e Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Thu, 28 May 2026 16:27:31 +0300 Subject: [PATCH 1/2] =?UTF-8?q?docs(examples):=20with-intelligence-export?= =?UTF-8?q?=20=E2=80=94=20ship=20loop=20traces=20to=20Tangle=20Intelligenc?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds examples/with-intelligence-export/ showing both integration paths into the hosted intelligence backend: the built-in createOtelExporter (point endpoint at .../v1/otlp; it appends /v1/traces → the ingest route) wired via loopEventToOtelSpan per loop event, and a raw-OTLP POST for bring-your-own traces with no runtime. Insights read back via GET /v1/insights/*; tenant resolves from the Bearer key. Indexed in examples/README.md. --- examples/README.md | 1 + examples/with-intelligence-export/README.md | 22 ++++ .../with-intelligence-export.ts | 122 ++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 examples/with-intelligence-export/README.md create mode 100644 examples/with-intelligence-export/with-intelligence-export.ts diff --git a/examples/README.md b/examples/README.md index 011428c..aa3e1bd 100644 --- a/examples/README.md +++ b/examples/README.md @@ -39,6 +39,7 @@ These were standalone examples in an earlier release. The patterns are now folde - [`sse-stream/`](./sse-stream/) — SSE helpers for browser routes - [`sanitized-telemetry/`](./sanitized-telemetry/) — non-streaming counterpart to `sanitized-telemetry-streaming` - [`agent-into-reviewer/`](./agent-into-reviewer/) — pipe one runtime's stream into a reviewer agent (advanced 2-runtime topology) +- [`with-intelligence-export/`](./with-intelligence-export/) — ship loop traces to Tangle Intelligence (`createOtelExporter` + raw OTLP) for failure-correlation + quality insights ## Conventions diff --git a/examples/with-intelligence-export/README.md b/examples/with-intelligence-export/README.md new file mode 100644 index 0000000..b0ddf49 --- /dev/null +++ b/examples/with-intelligence-export/README.md @@ -0,0 +1,22 @@ +# with-intelligence-export + +Ship agent-runtime traces to **Tangle Intelligence** and get back insights: +failure correlations (relative risk + p-value), latency percentiles, and an +agent-eval quality report. + +Two paths, both into the same pipeline: + +- **Built-in exporter** — `createOtelExporter({ endpoint, headers })` + + `exporter.exportSpan(loopEventToOtelSpan(event, traceId))` per loop event. + The exporter POSTs to `${endpoint}/v1/traces`, so point `endpoint` at + `https://intelligence.tangle.tools/v1/otlp` (it becomes the + `/v1/otlp/v1/traces` ingest route). +- **Raw OTLP** — POST OTel spans straight to `/v1/otlp/v1/traces` with your + `sk-tan-*` key. Works with no runtime at all. + +The tenant is resolved from the Bearer key, never the payload. Read insights +back from the dashboard or `GET /v1/insights/outputs?kind=report`. + +```bash +TANGLE_API_KEY=sk-tan-... npx tsx examples/with-intelligence-export/with-intelligence-export.ts +``` diff --git a/examples/with-intelligence-export/with-intelligence-export.ts b/examples/with-intelligence-export/with-intelligence-export.ts new file mode 100644 index 0000000..182f5f4 --- /dev/null +++ b/examples/with-intelligence-export/with-intelligence-export.ts @@ -0,0 +1,122 @@ +/** + * Hook agent-runtime into Tangle Intelligence — one clean block. + * + * agent-runtime already records every loop event; this ships them to the + * hosted intelligence backend, which turns them into insights (failure + * correlations with p-values, latency percentiles, an agent-eval quality + * report). Two ways, both end in the same pipeline: + * + * BUILT-IN exporter — `createOtelExporter({ endpoint, headers })` then + * `exporter.exportSpan(loopEventToOtelSpan(event, traceId))` per event. + * IMPORTANT: the exporter POSTs to `${endpoint}/v1/traces`, so point it + * at `.../v1/otlp` (becomes `.../v1/otlp/v1/traces`, the ingest route). + * + * RAW OTLP — for anything agent-runtime doesn't emit, POST OTel spans + * yourself (see `sendTracesToTangle` below). Works with no runtime at + * all. + * + * Read insights back from the dashboard or `GET /v1/insights/outputs` with + * the same key. Tenant resolves from the Bearer key, never the payload. + * + * Run: TANGLE_API_KEY=sk-tan-... npx tsx examples/with-intelligence-export/with-intelligence-export.ts + */ +import { + type AgentBackendInput, + createIterableBackend, + createOtelExporter, + loopEventToOtelSpan, + type RuntimeStreamEvent, + runAgentTaskStream, +} from '@tangle-network/agent-runtime' + +const API_KEY = process.env.TANGLE_API_KEY ?? 'sk-tan-...' +// The exporter appends `/v1/traces`; the hosted ingest is `/v1/otlp/v1/traces`. +const INTELLIGENCE_BASE = + process.env.INTELLIGENCE_BASE ?? 'https://intelligence.tangle.tools/v1/otlp' + +// A synthetic backend so the example runs standalone. In a real product +// this is your sandbox, OpenAI-compatible router, or CLI bridge. +const backend = createIterableBackend({ + kind: 'intel-demo', + async *stream(_input, ctx) { + yield { type: 'text_delta', task: ctx.task, session: ctx.session, text: 'working...\n', timestamp: new Date().toISOString() } + yield { type: 'tool_call', task: ctx.task, session: ctx.session, toolName: 'web_search', args: {}, timestamp: new Date().toISOString() } + yield { type: 'tool_result', task: ctx.task, session: ctx.session, toolName: 'web_search', result: { ok: true }, timestamp: new Date().toISOString() } + yield { type: 'text_delta', task: ctx.task, session: ctx.session, text: 'done.\n', timestamp: new Date().toISOString() } + }, +}) + +async function main() { + // ── Built-in exporter → Tangle Intelligence ─────────────────────── + const exporter = createOtelExporter({ + endpoint: INTELLIGENCE_BASE, + headers: { authorization: `Bearer ${API_KEY}` }, + serviceName: 'agent-runtime-demo', + }) + if (!exporter) throw new Error('no OTLP endpoint configured') + + const traceId = 'run' + Date.now().toString(16).padStart(29, '0') + const runId = 'demo-run-1' + for await (const event of runAgentTaskStream({ + task: { id: runId, intent: 'demo task', domain: 'demo', inputs: {} }, + backend, + input: { message: 'go' } as Partial, + })) { + const e = event as RuntimeStreamEvent & { type: string; timestamp?: string } + exporter.exportSpan( + loopEventToOtelSpan( + { + kind: e.type, + runId, + timestamp: e.timestamp ? Date.parse(e.timestamp) : Date.now(), + payload: event as object, + }, + traceId, + ), + ) + } + await exporter.flush() + console.log('Shipped run to Tangle Intelligence via the built-in exporter.') + + // ── Or the universal raw block (no runtime required) ────────────── + await sendTracesToTangle([ + { + traceId: 'byo0000000000000000000000000001', + spanId: 'span000000000001', + name: 'llm.call', + startTimeUnixNano: String(BigInt(Date.now()) * 1_000_000n), + endTimeUnixNano: String(BigInt(Date.now() + 800) * 1_000_000n), + attributes: [ + { key: 'llm.model', value: { stringValue: 'gpt-4o-mini' } }, + { key: 'score', value: { doubleValue: 0.91 } }, + ], + status: { code: 'STATUS_CODE_OK' }, + }, + ]) + console.log('Also sent a BYO trace via raw OTLP.') +} + +// The raw OTLP one-block hook — POST straight to the ingest route. +async function sendTracesToTangle( + spans: Array>, + serviceName = 'byo-agent', +) { + const res = await fetch(`${INTELLIGENCE_BASE}/v1/traces`, { + method: 'POST', + headers: { 'content-type': 'application/json', authorization: `Bearer ${API_KEY}` }, + body: JSON.stringify({ + resourceSpans: [ + { + resource: { attributes: [{ key: 'service.name', value: { stringValue: serviceName } }] }, + scopeSpans: [{ scope: { name: 'byo' }, spans }], + }, + ], + }), + }) + if (!res.ok) throw new Error(`intelligence ingest failed: ${res.status} ${await res.text()}`) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) From 8fa16af11e5efb1ffb0815f3245e2e6d1995738f Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Thu, 28 May 2026 07:52:41 -0600 Subject: [PATCH 2/2] fix(lint): useTemplate in with-intelligence-export example (CI lint gate) --- examples/with-intelligence-export/with-intelligence-export.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/with-intelligence-export/with-intelligence-export.ts b/examples/with-intelligence-export/with-intelligence-export.ts index 182f5f4..7cb1da3 100644 --- a/examples/with-intelligence-export/with-intelligence-export.ts +++ b/examples/with-intelligence-export/with-intelligence-export.ts @@ -55,7 +55,7 @@ async function main() { }) if (!exporter) throw new Error('no OTLP endpoint configured') - const traceId = 'run' + Date.now().toString(16).padStart(29, '0') + const traceId = `run${Date.now().toString(16).padStart(29, '0')}` const runId = 'demo-run-1' for await (const event of runAgentTaskStream({ task: { id: runId, intent: 'demo task', domain: 'demo', inputs: {} },