@triangle-technology/noos
v0.1.2
Published
Reliability layer for LLM agents: scope drift, cost circuit breaks, procedural correction memory, tool-call loop detection as event-driven decisions.
Downloads
29
Maintainers
Readme
@triangle-technology/noos (Node.js / TypeScript)
Node.js native bindings for Noos, a reliability layer for LLM agents: scope drift, cost circuit breaks, procedural correction memory, and tool-call loop detection as event-driven decisions.
Built via napi-rs 3.x. TypeScript definitions auto-generated from the Rust source at build time.
Status
0.1.0 — code complete + CI validated on ubuntu-latest. Publish
workflow (.github/workflows/publish.yml) builds and publishes the
5 platform binaries (windows-msvc, macos x64 + arm64, linux x64 + arm64)
on every v* tag. Requires NPM_TOKEN repo secret.
Install (once published)
npm install @triangle-technology/noos
# or
pnpm add @triangle-technology/noos
# or
yarn add @triangle-technology/noosQuick start
import { Regulator, LLMEvent } from '@triangle-technology/noos'
const r = Regulator.forUser('alice')
r.withCostCap(2_000)
r.onEvent(LLMEvent.turnStart('Refactor fetch_user to be async'))
// ... call your LLM ...
r.onEvent(LLMEvent.turnComplete(responseText))
r.onEvent(LLMEvent.cost(25, 800, 500, 'anthropic'))
const decision = r.decide()
switch (decision.kind) {
case 'continue':
/* deliver response */
break
case 'scope_drift_warn':
console.log(`drift ${decision.driftScore?.toFixed(2)}: ${decision.driftTokens}`)
break
case 'circuit_break':
console.log(`halt: ${decision.suggestion} (${decision.reason?.kind})`)
break
case 'procedural_warning':
decision.patterns?.forEach((p) =>
console.log(`learned: ${p.patternName}`, p.exampleCorrections)
)
break
}API summary
| Class | Purpose |
|-------|---------|
| Regulator | Main API. forUser(id), withCostCap(n), withImplicitCorrectionWindowSecs(s), onEvent(e), decide(), exportJson(), fromJson(s) + accessors. |
| LLMEvent | Event factories. turnStart, token, turnComplete, cost, qualityFeedback, userCorrection, toolCall, toolResult. See also freestanding llmEventsFromOtelSpanJson(json). |
| Decision | Output of decide(). .kind + variant-specific getters returning T \| null. |
| CircuitBreakReason | Nested on Decision.reason when kind === 'circuit_break'. |
| CorrectionPattern | Items of Decision.patterns when kind === 'procedural_warning'. |
Decision variants
| .kind | Available getters |
|---------|-------------------|
| 'continue' | — |
| 'scope_drift_warn' | driftScore, driftTokens, taskTokens |
| 'circuit_break' | reason (a CircuitBreakReason), suggestion |
| 'procedural_warning' | patterns (an array of CorrectionPattern) |
| 'low_confidence_spans' | reserved |
CircuitBreakReason variants
| .kind | Available getters |
|---------|-------------------|
| 'cost_cap_reached' | tokensSpent, tokensCap, meanQualityLastN |
| 'quality_decline_no_recovery' | turns, meanDelta |
| 'repeated_failure_pattern' | cluster, failureCount |
| 'repeated_tool_call_loop' | toolName, consecutiveCount |
Persistence
// Save
const snapshot: string = r.exportJson()
await fs.writeFile('regulator.json', snapshot, 'utf-8')
// Restore
const snapshot = await fs.readFile('regulator.json', 'utf-8')
const r = Regulator.fromJson(snapshot)Cross-session learning (strategy EMA + correction patterns at threshold) survives the roundtrip. Malformed JSON throws.
Observability
One-call metrics snapshot for Prometheus / Datadog / StatsD:
const snap = r.metricsSnapshot()
for (const [key, value] of Object.entries(snap)) {
metricsClient.gauge(key, value)
}All keys are noos. prefixed (confidence, logprob_coverage,
total_tokens_out, cost_cap_tokens, tool_*, implicit_corrections_count).
OpenTelemetry GenAI ingestion
If your agent is already instrumented with the OTel GenAI semantic conventions, feed spans to Noos without wiring a separate event bus:
import { readFile } from 'node:fs/promises'
import { Regulator, llmEventsFromOtelSpanJson } from '@triangle-technology/noos'
const spanJson = await readFile('span.json', 'utf-8')
const r = Regulator.forUser('alice')
for (const event of llmEventsFromOtelSpanJson(spanJson)) {
r.onEvent(event)
}
const d = r.decide()Build from source
Requires: Rust toolchain (1.75+), Node.js 18+.
cd bindings/node
npm install
npm run build
# Produces an `index.js` + `index.d.ts` + `.node` native addon.
npm test # 15 behavioural tests via `node --test`
npm run example # Runs examples/basic.mjsWindows-GNU note: napi-build 2.3.1 requires libnode.dll for the
GNU ABI path, which standard Windows Node installs don't ship. Work
around with WSL, MSVC Rust toolchain (rustup default
stable-x86_64-pc-windows-msvc), or CI running ubuntu/macos. Linux and
macOS builds work out of the box.
License
MIT. Same as the Rust crate.
