nexus-ai-pro 1.11.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/API_STABILITY.md +42 -0
  2. package/CHANGELOG.md +106 -1
  3. package/README.md +260 -3
  4. package/dist/agent/create-agent.d.ts +119 -0
  5. package/dist/agent/create-agent.js +176 -0
  6. package/dist/agent/index.d.ts +5 -0
  7. package/dist/agent/index.js +4 -0
  8. package/dist/agent/middleware.d.ts +46 -0
  9. package/dist/agent/middleware.js +77 -0
  10. package/dist/graph/graph.d.ts +31 -1
  11. package/dist/graph/graph.js +292 -32
  12. package/dist/graph/index.d.ts +2 -2
  13. package/dist/graph/index.js +1 -1
  14. package/dist/graph/visualize.d.ts +30 -0
  15. package/dist/graph/visualize.js +74 -0
  16. package/dist/mcp/client.d.ts +106 -0
  17. package/dist/mcp/client.js +243 -0
  18. package/dist/mcp/index.d.ts +3 -0
  19. package/dist/mcp/index.js +3 -0
  20. package/dist/mcp/protocol.d.ts +71 -0
  21. package/dist/mcp/protocol.js +68 -0
  22. package/dist/mcp/server.d.ts +46 -0
  23. package/dist/mcp/server.js +155 -0
  24. package/dist/store/index.d.ts +2 -0
  25. package/dist/store/index.js +1 -0
  26. package/dist/store/memory.d.ts +41 -0
  27. package/dist/store/memory.js +182 -0
  28. package/dist/store/redis.d.ts +49 -0
  29. package/dist/store/redis.js +128 -0
  30. package/dist/tracing/alerts.d.ts +60 -0
  31. package/dist/tracing/alerts.js +98 -0
  32. package/dist/tracing/compare.d.ts +31 -0
  33. package/dist/tracing/compare.js +69 -0
  34. package/dist/tracing/index.d.ts +6 -0
  35. package/dist/tracing/index.js +5 -0
  36. package/dist/tracing/instrument.d.ts +47 -0
  37. package/dist/tracing/instrument.js +138 -0
  38. package/dist/tracing/stores.d.ts +53 -0
  39. package/dist/tracing/stores.js +192 -0
  40. package/dist/tracing/tracer.d.ts +73 -0
  41. package/dist/tracing/tracer.js +182 -0
  42. package/dist/types/graph.d.ts +149 -4
  43. package/dist/types/graph.js +24 -0
  44. package/dist/types/store.d.ts +61 -0
  45. package/dist/types/store.js +9 -0
  46. package/dist/types/tracing.d.ts +102 -0
  47. package/dist/types/tracing.js +10 -0
  48. package/dist-cjs/agent/create-agent.d.ts +119 -0
  49. package/dist-cjs/agent/create-agent.js +181 -0
  50. package/dist-cjs/agent/index.d.ts +5 -0
  51. package/dist-cjs/agent/index.js +16 -0
  52. package/dist-cjs/agent/middleware.d.ts +46 -0
  53. package/dist-cjs/agent/middleware.js +82 -0
  54. package/dist-cjs/graph/graph.d.ts +31 -1
  55. package/dist-cjs/graph/graph.js +291 -31
  56. package/dist-cjs/graph/index.d.ts +2 -2
  57. package/dist-cjs/graph/index.js +2 -1
  58. package/dist-cjs/graph/visualize.d.ts +30 -0
  59. package/dist-cjs/graph/visualize.js +78 -0
  60. package/dist-cjs/mcp/client.d.ts +106 -0
  61. package/dist-cjs/mcp/client.js +282 -0
  62. package/dist-cjs/mcp/index.d.ts +3 -0
  63. package/dist-cjs/mcp/index.js +15 -0
  64. package/dist-cjs/mcp/protocol.d.ts +71 -0
  65. package/dist-cjs/mcp/protocol.js +77 -0
  66. package/dist-cjs/mcp/server.d.ts +46 -0
  67. package/dist-cjs/mcp/server.js +160 -0
  68. package/dist-cjs/store/index.d.ts +2 -0
  69. package/dist-cjs/store/index.js +6 -0
  70. package/dist-cjs/store/memory.d.ts +41 -0
  71. package/dist-cjs/store/memory.js +190 -0
  72. package/dist-cjs/store/redis.d.ts +49 -0
  73. package/dist-cjs/store/redis.js +132 -0
  74. package/dist-cjs/tracing/alerts.d.ts +60 -0
  75. package/dist-cjs/tracing/alerts.js +104 -0
  76. package/dist-cjs/tracing/compare.d.ts +31 -0
  77. package/dist-cjs/tracing/compare.js +73 -0
  78. package/dist-cjs/tracing/index.d.ts +6 -0
  79. package/dist-cjs/tracing/index.js +21 -0
  80. package/dist-cjs/tracing/instrument.d.ts +47 -0
  81. package/dist-cjs/tracing/instrument.js +142 -0
  82. package/dist-cjs/tracing/stores.d.ts +53 -0
  83. package/dist-cjs/tracing/stores.js +202 -0
  84. package/dist-cjs/tracing/tracer.d.ts +73 -0
  85. package/dist-cjs/tracing/tracer.js +187 -0
  86. package/dist-cjs/types/graph.d.ts +149 -4
  87. package/dist-cjs/types/graph.js +26 -1
  88. package/dist-cjs/types/store.d.ts +61 -0
  89. package/dist-cjs/types/store.js +10 -0
  90. package/dist-cjs/types/tracing.d.ts +102 -0
  91. package/dist-cjs/types/tracing.js +11 -0
  92. package/package.json +112 -11
package/API_STABILITY.md CHANGED
@@ -313,6 +313,48 @@ Also since 1.11.0, all additive:
313
313
  - `context.taskId` and `context.attempt` are new. Step events carry `tasks` and `attempts` when there
314
314
  is something to report.
315
315
 
316
+ Also since 1.12.0, all additive:
317
+
318
+ - `Command` (with `Command.PARENT`) may be returned from a node; `NodeResult` names what a node may
319
+ return. `goto` routes are added to the node's outgoing edges, and a paused checkpoint records them in
320
+ the optional `gotos` field.
321
+ - `interruptBefore` and `interruptAfter` on compile and run options; a paused checkpoint and result
322
+ carry `breakpoint`, and step events may have type `breakpoint`.
323
+ - `NodeOptions.defer`, `GraphRunOptions.onEvent` with the `GraphEvent` union, and `context.emit()`.
324
+ New `GraphEvent` variants may be added in a minor release.
325
+ - `updateState()`, `fork()`, and `describe()` on a compiled graph, and the `nexus-ai-pro/graph/visualize`
326
+ subpath. The exact Mermaid text `toMermaid()` produces is not a guarantee — diagram layout may
327
+ improve in a minor release — but `GraphDescription` is a normalized shape.
328
+ - A subgraph node's thread id is `<parent thread>:<task id>`, which equals the previous
329
+ `<parent thread>:<node>` for any node not reached through `Send`.
330
+
331
+ ## Agent, store, MCP, and tracing stages (1.14.0)
332
+
333
+ The `nexus-ai-pro/agent`, `nexus-ai-pro/store`, `nexus-ai-pro/store/redis`, and `nexus-ai-pro/mcp`
334
+ subpaths are public and follow the 1.x rules. None is exported from the root import.
335
+
336
+ - `createAgent()` returns a compiled graph, so the graph guarantees above apply to an agent: a
337
+ superstep is atomic with respect to checkpointing, a finished task is never re-run by a resume, and
338
+ an approval survives a restart. The agent's state channels (`messages`, `iterations`, `answer`,
339
+ `stopReason`) are a normalized shape; what a model decides to put in them is not.
340
+ - `Store` is the contract; `MemoryStore` and `RedisStore` implement it. Ranking from a semantic search
341
+ depends on the embedding function a caller injects and is not a guarantee. `MemoryStore` is
342
+ process-local and bounded, and drops the least recently written item past `maxItems`.
343
+ - The MCP subpath implements a subset of the Model Context Protocol: initialize, tools, resources, and
344
+ prompts, over stdio and HTTP. Protocol versions and server behaviour are upstream contracts outside
345
+ this policy. Server-initiated streaming, sampling, and roots are not implemented; new methods may be
346
+ added in a minor release.
347
+ - `nexus-ai-pro/tracing` is public: `Tracer`, the `TraceStore` contract, `MemoryTraceStore`,
348
+ `JsonlTraceStore`, `traceGraph()`, `traceModelClient()`, `compareTraces()`, and `AlertEvaluator`.
349
+ The `Run`, `RunTree`, and `RunQuery` shapes are normalized; what a model puts in a run's inputs and
350
+ outputs is not. New `RunKind` values and query fields may be added in a minor release. Sampling is
351
+ probabilistic by definition, so which traces are kept is not a guarantee, but the tail rules are:
352
+ an error, a run past `keepSlowerThanMs`, or one past `keepCostlierThan` is always kept.
353
+ - `agentAsTool()` and the bundled middleware (`summarizeHistory`, `redactMessages`, `limitToolCalls`)
354
+ are public and additive.
355
+ - `AgentLoop`, `tool()`, and `ToolExecutor` keep their behaviour and are re-exported from
356
+ `nexus-ai-pro/agent` as well as the root.
357
+
316
358
  ## Deprecation process
317
359
 
318
360
  Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. Removals
package/CHANGELOG.md CHANGED
@@ -4,6 +4,109 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.14.0] - 2026-09-20
8
+
9
+ The memory, agent, and MCP work planned for 1.13.0 ships here together with tracing, so no 1.13.0 was
10
+ published.
11
+
12
+ Memory, agents, MCP, and traces. An agent is now a graph rather than a loop, so it inherits
13
+ approvals, parallel tool calls, and durability. A store gives it memory that outlives a thread, MCP
14
+ connects it to tools this package does not have to ship, and every run can be recorded as a tree you
15
+ can search, compare, and alert on.
16
+
17
+ ### Added
18
+
19
+ - **`nexus-ai-pro/agent` and `createAgent()`.** Returns a compiled graph, so an agent gets
20
+ checkpoints, resume, forks, events, and a diagram from the graph runtime rather than from code
21
+ written twice.
22
+ - Tool calls run as parallel tasks, bounded by `toolConcurrency`.
23
+ - `interruptOn` makes a tool wait for a human. The approval is a checkpoint, so it can arrive days
24
+ later from another process, and it can approve, refuse with a reason the model sees, or approve
25
+ with corrected arguments.
26
+ - Middleware wraps the model call (`beforeModel`, `afterModel`) and each tool call
27
+ (`wrapToolCall`).
28
+ - `stopReason` distinguishes a finished answer from one that ran out of iterations, and malformed
29
+ tool arguments never reach a tool.
30
+ - `AgentLoop` is unchanged, for the case that wants none of this.
31
+ - **`nexus-ai-pro/store`, long-term memory.** Namespaced tuples, `put`/`get`/`delete`/`search`/
32
+ `listNamespaces`, TTLs, and field filters.
33
+ - Semantic search ranks by any embedding function you inject, so the store never imports the
34
+ embeddings runtime; without an index, a query matches text.
35
+ - `MemoryStore` is bounded, like the graph's memory checkpointer. `RedisStore`
36
+ (`nexus-ai-pro/store/redis`) shares memory across processes through a client-like interface.
37
+ - `compile({ store })` hands it to every node as `context.store`.
38
+ - **`nexus-ai-pro/tracing`, run trees you can query.** Metrics say how it is going; a trace says what
39
+ happened.
40
+ - Every family has a run kind: model, tool, graph, node, agent, retriever, embedding, image, voice,
41
+ realtime, operation. A run carries inputs, outputs, timing, tokens, cost, tags, and errors.
42
+ - `traceGraph()` turns the graph's own event stream into a run tree, so nothing inside the graph
43
+ knows about tracing and an untraced run pays nothing. `traceModelClient()` nests each model call
44
+ inside the node that made it, with its tokens and cost.
45
+ - Tail sampling keeps every error, slow run, and expensive run whatever the head rate is, because
46
+ the decision is made when a trace finishes.
47
+ - Redaction runs before storage: hidden fields are never written, and a `redact` hook gets the last
48
+ word.
49
+ - `MemoryTraceStore` is bounded; `JsonlTraceStore` appends to a file that survives a restart and can
50
+ be read back, pruned, and rotated.
51
+ - Queries filter by kind, status, name, model, tags, metadata, latency, cost, time, and feedback.
52
+ `tree()` assembles a trace, `formatTree()` prints it, and `compareTraces()` reports what changed
53
+ between two runs of the same shape.
54
+ - `recordFeedback()` attaches scores from people or from evaluators.
55
+ - `AlertEvaluator` runs rules over stored runs — error rate, latency percentiles, cost, count — and
56
+ every alert carries the runs that tripped it. `createWebhookNotifier()` posts them.
57
+ - **Bundled agent middleware.** `summarizeHistory` keeps a long transcript from growing without
58
+ bound, `redactMessages` keeps secrets out of provider requests and responses, and `limitToolCalls`
59
+ caps how often a tool may run.
60
+ - **`agentAsTool()`.** Gives one agent to another as a tool, so a supervisor can delegate while
61
+ keeping control. Each agent keeps its own memory, approvals, and thread.
62
+
63
+ - **`nexus-ai-pro/mcp`, both directions of the Model Context Protocol.** Implemented directly, with
64
+ no protocol SDK dependency.
65
+ - `McpClient` lists and calls tools, reads resources and prompts, and `toNexusTools()` turns a
66
+ server's tools into tools an agent can call, with optional name prefixes.
67
+ - `McpServer` exposes this application's tools and resources to other assistants.
68
+ - Stdio and HTTP transports are included, and the transport is an interface, so a client and a
69
+ server can be wired together in memory for a test.
70
+
71
+ ## [1.12.0] - 2026-09-20
72
+
73
+ Graph control and introspection. A node can route itself, a run can pause at breakpoints and be
74
+ edited or forked, every task and custom event can be watched as it happens, and a graph can draw
75
+ itself.
76
+
77
+ ### Added
78
+
79
+ - **`Command`.** A node returns `new Command({ update, goto })` to write state and choose its
80
+ successors in one step, instead of splitting the decision into a separate router. `goto` takes node
81
+ names or `Send`s. A route chosen by a task that finished before its step paused is kept on the
82
+ checkpoint, so it is still followed when the step resumes.
83
+ - **`Command.PARENT`.** A node inside a subgraph hands control back to the graph that contains it. The
84
+ subgraph's own writes and the update it addressed to the parent are reduced separately, in order,
85
+ so neither overwrites the other.
86
+ - **Breakpoints.** `interruptBefore` and `interruptAfter`, per compile or per run, pause a run at
87
+ chosen nodes with status `interrupted` and a `breakpoint` describing where. `continue()` carries on,
88
+ and does not pause again at the breakpoint it resumes from.
89
+ - **Deferred nodes.** `addNode(name, fn, { defer: true })` holds a node until every other pending task
90
+ has finished, so an aggregator after branches of different lengths runs once.
91
+ - **`updateState()`.** Merges a correction into a thread's latest checkpoint in place, keeping any
92
+ pending question; with `asNode`, applies it as that node's output and continues along its edges.
93
+ - **`fork()`.** Copies a thread's history up to a step into a new thread, leaving the original
94
+ untouched and runnable, and records `metadata.forkedFrom`.
95
+ - **`onEvent` and `context.emit()`.** A run can observe each task starting, retrying, and finishing
96
+ with its update, every checkpoint written, and custom events a node emits, such as model tokens. A
97
+ listener that throws never fails the run.
98
+ - **`describe()` and `nexus-ai-pro/graph/visualize`.** `describe()` returns a graph's nodes, edges, and
99
+ run-time routes as plain JSON, including subgraphs. `toMermaid()` draws it, with optional
100
+ highlighting of where a thread is; `toGraphJSON()` returns the data. The visualizer imports no
101
+ runtime code.
102
+
103
+ ### Fixed
104
+
105
+ - **Parallel copies of a subgraph node no longer share a thread.** `asNode()` keyed the subgraph's
106
+ thread by node name, so two `Send` copies of one subgraph node in the same step wrote to the same
107
+ thread. It is now keyed by task; a plain node's task id is its name, so existing threads are
108
+ unaffected.
109
+
7
110
  ## [1.11.0] - 2026-09-18
8
111
 
9
112
  Parallel graphs. A fan-out that looks parallel now runs in parallel, a graph can fan out over data it
@@ -614,7 +717,9 @@ shared policy vocabulary is in place for a future release that revisits this.
614
717
  - URL fetching rejects unsafe private-network targets and limits response reads.
615
718
  - CLI and audit findings no longer disclose detected secret values.
616
719
 
617
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.11.0...HEAD
720
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.14.0...HEAD
721
+ [1.14.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.12.0...v1.14.0
722
+ [1.12.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.11.0...v1.12.0
618
723
  [1.11.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.10.0...v1.11.0
619
724
  [1.10.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.9.0...v1.10.0
620
725
  [1.9.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.8.0...v1.9.0
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # nexus-ai-pro
2
2
 
3
- The Universal AI Pipeline for Node.js: one typed API for routing, guardrails, caching, context-window management, tools, RAG, evals, jobs, and provider failover.
3
+ The typed AI framework for TypeScript. One API for provider routing and failover, agent graphs that
4
+ run branches in parallel and survive a restart, durable background operations, guardrails, cost
5
+ control, images, voice, and evals.
4
6
 
5
- Use the whole pipeline for production AI features, or turn pieces off when you only need a thin provider wrapper.
7
+ Import the whole runtime, or one piece: every capability has its own entry point with a size budget
8
+ CI enforces, and the table below publishes what each one costs. A graph-only application loads 60 KB
9
+ and installs no third-party package at all.
6
10
 
7
11
  - NPM: https://www.npmjs.com/package/nexus-ai-pro
8
12
  - GitHub: https://github.com/mkhitar-abrahamyan/nexus-ai
@@ -533,6 +537,98 @@ await graph.history(threadId); // newest first
533
537
  graph.resumeFrom(threadId, 3); // rewind and run forward
534
538
  ```
535
539
 
540
+ **Route from inside a node.** When a node already knows where to go — an agent that just picked a
541
+ tool, a triage step that classified a ticket — return a `Command` instead of splitting the decision
542
+ into a separate router:
543
+
544
+ ```ts
545
+ import { Command } from 'nexus-ai-pro/graph';
546
+
547
+ .addNode('triage', (ctx) =>
548
+ new Command({ update: { label: classify(ctx.state) }, goto: isUrgent(ctx.state) ? 'page-oncall' : 'queue' }),
549
+ { ends: ['page-oncall', 'queue'] },
550
+ )
551
+ ```
552
+
553
+ `goto` takes node names or `Send`s, and `ends` keeps compile-time checks and diagrams exact. A node
554
+ inside a subgraph can return `new Command({ graph: Command.PARENT, goto: 'human' })` to hand control
555
+ back to the graph that contains it — how a nested agent escalates.
556
+
557
+ **Aggregate after branches of different lengths.** `{ defer: true }` holds a node until every other
558
+ pending task has finished, so an aggregator after a two-step branch and a one-step branch runs once,
559
+ after both, rather than once per arrival.
560
+
561
+ **Pause for inspection.** Breakpoints stop a run before or after chosen nodes, checkpointed, without
562
+ changing the nodes themselves:
563
+
564
+ ```ts
565
+ const graph = builder.compile({ interruptBefore: ['publish'] });
566
+ const paused = await graph.invoke(input, { threadId }); // status: 'interrupted', breakpoint: { when: 'before', ... }
567
+ await graph.updateState(threadId, { draft: fixedDraft }); // correct something first, if needed
568
+ for await (const _ of graph.continue(threadId)) {} // then carry on
569
+ ```
570
+
571
+ Set them per compile or per run; a run's list replaces the compiled one.
572
+
573
+ **Edit state, or fork a thread.** `updateState(threadId, update)` merges a correction into the latest
574
+ checkpoint through the channel reducers, leaving any pending question in place.
575
+ `updateState(threadId, update, { asNode: 'research' })` applies it as if that node had produced it,
576
+ so the next step follows that node's edges. `fork(threadId, { step })` copies the history up to a step
577
+ into a new thread and leaves the original untouched, so two answers to the same question can be run
578
+ side by side.
579
+
580
+ **Watch it run.** `onEvent` receives every task starting, retrying, and finishing with its update,
581
+ every checkpoint written, and whatever a node passes to `context.emit()` — model tokens as they
582
+ stream, a status line, an intermediate result:
583
+
584
+ ```ts
585
+ .addNode('write', async (ctx) => {
586
+ for await (const token of streamAnswer(ctx.state)) ctx.emit({ token });
587
+ return { answer };
588
+ })
589
+
590
+ await graph.invoke(input, {
591
+ onEvent: (event) => event.type === 'custom' && process.stdout.write((event.data as { token: string }).token),
592
+ });
593
+ ```
594
+
595
+ A listener that throws never fails the run it is watching.
596
+
597
+ **See the shape.** `graph.describe()` returns nodes, edges, and which routes are decided at run
598
+ time, as plain JSON. `toMermaid()` from `nexus-ai-pro/graph/visualize` draws it — solid arrows are
599
+ always taken, dotted ones are chosen at run time, and subgraphs are drawn inside the node that runs
600
+ them:
601
+
602
+ ```ts
603
+ import { toMermaid } from 'nexus-ai-pro/graph/visualize';
604
+
605
+ console.log(toMermaid(graph, { highlight: checkpoint.next }));
606
+ ```
607
+
608
+ This diagram is that function's actual output for a research-and-review graph:
609
+
610
+ ```mermaid
611
+ flowchart TD
612
+ __start__([start])
613
+ __end__([end])
614
+ n_plan["plan"]
615
+ n_research["research<br/><small>retries</small>"]
616
+ n_summarize["summarize<br/><small>deferred</small>"]
617
+ n_review["review"]
618
+ n_publish["publish"]
619
+ __start__ --> n_plan
620
+ n_summarize --> n_review
621
+ n_publish --> __end__
622
+ n_plan -.-> n_research
623
+ n_research -.-> n_summarize
624
+ n_review -.-> n_publish
625
+ n_review -.-> n_plan
626
+ ```
627
+
628
+ The visualizer is its own subpath and imports no runtime code, so drawing a graph costs nothing to an
629
+ application that only runs one. No image renderer is bundled; any Mermaid renderer turns the output
630
+ into PNG or SVG.
631
+
536
632
  **Durable by construction.** The default checkpointer is in-process, holding up to 1,000 threads;
537
633
  pass `checkpointer: false` to turn checkpointing off. Point it at the operation store
538
634
  that already backs durable operations and a thread survives a restart — a different worker resumes
@@ -559,6 +655,161 @@ private to the subgraph.
559
655
 
560
656
  The graph is not in the root import. It costs nothing to a user who does not build graphs.
561
657
 
658
+ ## Agents
659
+
660
+ An agent is a graph, so everything above applies to it: checkpoints, human approval, parallel work,
661
+ forks, events, and a diagram.
662
+
663
+ ```ts
664
+ import { createAgent, agentInput, tool } from 'nexus-ai-pro/agent';
665
+
666
+ const agent = createAgent({
667
+ client: ai, // anything with complete(); NexusAI qualifies
668
+ systemPrompt: 'You are a support engineer.',
669
+ tools: [refundTool, emailTool],
670
+ interruptOn: { send_email: true }, // this one waits for a human
671
+ store, // long-term memory, below
672
+ checkpointer, // survives a restart
673
+ });
674
+
675
+ const run = await agent.invoke(agentInput('Refund order 1182 and tell the customer'), { threadId });
676
+ run.state.answer; // the final text
677
+ run.state.stopReason; // 'completed' | 'max_iterations'
678
+ ```
679
+
680
+ **Tool calls run in parallel.** Each call the model requests becomes its own task, bounded by
681
+ `toolConcurrency`, so three lookups take as long as the slowest one.
682
+
683
+ **Approval is an interrupt, not a callback.** A tool listed in `interruptOn` pauses the run and
684
+ checkpoints it. The answer can approve, refuse with a reason the model sees, or approve with
685
+ corrected arguments:
686
+
687
+ ```ts
688
+ if (run.status === 'awaiting_input') {
689
+ await agent.resumeWith(threadId, { approved: true, args: { to: 'billing@example.com' } });
690
+ }
691
+ ```
692
+
693
+ Because it is a checkpoint, the approval can arrive days later, from another process.
694
+
695
+ **Middleware** wraps the parts worth controlling: `beforeModel` adjusts the request, `afterModel`
696
+ inspects or replaces the response, and `wrapToolCall` surrounds each tool call for logging, timing,
697
+ or policy. `AgentLoop` remains for the simple case that needs none of this.
698
+
699
+ ## Long-term Memory
700
+
701
+ A checkpoint remembers one conversation. A store remembers across them:
702
+
703
+ ```ts
704
+ import { MemoryStore } from 'nexus-ai-pro/store';
705
+
706
+ const store = new MemoryStore({ index: { embed, fields: ['text'] } }); // embed is yours
707
+ await store.put(['tenant-7', 'users', 'alice'], 'tone', { text: 'prefers brief answers' });
708
+
709
+ // In any node or tool, in any thread, later:
710
+ const memories = await context.store.search(['tenant-7', 'users', 'alice'], { query: 'how do they like answers?' });
711
+ ```
712
+
713
+ Namespaces are tuples, so `['tenant-7', 'users', 'alice']` is both a place to put something and a
714
+ prefix to search. Items can expire with `ttlMs`, be filtered by field, and be ranked semantically by
715
+ any embedding function you inject — the store never imports the embeddings runtime, and without an
716
+ index a query falls back to matching text. `RedisStore` from `nexus-ai-pro/store/redis` carries the
717
+ same contract across processes through a client-like interface, so no Redis package is a dependency
718
+ here.
719
+
720
+ ## MCP
721
+
722
+ The Model Context Protocol, both directions, implemented directly rather than through an SDK:
723
+
724
+ ```ts
725
+ import { McpClient, createStdioTransport } from 'nexus-ai-pro/mcp';
726
+ import { Tracer, MemoryTraceStore } from 'nexus-ai-pro/tracing';
727
+
728
+ const client = new McpClient(createStdioTransport({ command: 'npx', args: ['-y', 'some-mcp-server'] }));
729
+ const agent = createAgent({ client: ai, tools: await client.toNexusTools({ prefix: 'files' }) });
730
+ ```
731
+
732
+ That is how this package reaches a broad tool ecosystem without maintaining a catalogue of
733
+ integrations: anything exposed over MCP becomes tools an agent can call. The server direction lends
734
+ your tools out the same way, so a tool written once with `tool()` serves an agent here and an editor
735
+ elsewhere:
736
+
737
+ ```ts
738
+ import { McpServer, createStdioServerTransport } from 'nexus-ai-pro/mcp';
739
+
740
+ await new McpServer({ name: 'my-app', tools: [refundTool] }).connect(createStdioServerTransport());
741
+ ```
742
+
743
+ Stdio and HTTP transports are included, and the transport is an interface, so a test can wire a
744
+ client to a server in memory.
745
+
746
+ ## Traces
747
+
748
+ Metrics say how it is going. A trace says what happened: the run tree of one request, with its
749
+ inputs, outputs, tokens, cost, and errors, stored where you can search it.
750
+
751
+ ```ts
752
+ import { Tracer, MemoryTraceStore, traceGraph, traceModelClient } from 'nexus-ai-pro/tracing';
753
+
754
+ const tracer = new Tracer({
755
+ store: new MemoryTraceStore(), // or JsonlTraceStore({ file })
756
+ sampling: { rate: 0.05, keepErrors: true }, // 5% of traces, plus every failure
757
+ redaction: { hideFields: ['apiKey', 'user.email'] },
758
+ });
759
+
760
+ const tracing = traceGraph(tracer, { name: 'support-agent', kind: 'agent' });
761
+ const agent = createAgent({ client: traceModelClient(ai, tracer, { parent: () => tracing.runFor('model') }) });
762
+
763
+ const run = await agent.invoke(agentInput(question), { threadId, ...tracing.runOptions });
764
+ await tracing.finish(run);
765
+ ```
766
+
767
+ **Nothing inside the graph knows about tracing.** The graph already reports every task starting,
768
+ retrying, and finishing; a trace is that stream written down as a tree. An application that does not
769
+ trace creates no context, allocates nothing, and does no per-call work.
770
+
771
+ **Tail sampling keeps what matters.** A 5% rate still records every error, every run slower than a
772
+ threshold, and every run more expensive than one, because the decision is made when the trace
773
+ finishes rather than when it starts.
774
+
775
+ **Redaction happens before storage**, so a field you hide is never written, and a `redact` hook gets
776
+ the last word on every run.
777
+
778
+ **Then ask questions of it:**
779
+
780
+ ```ts
781
+ const failures = await store.query({ status: 'error', kind: 'model', since: yesterday, minLatencyMs: 2000 });
782
+ const tree = await store.tree(failures[0].traceId);
783
+ console.log(formatTree(tree));
784
+
785
+ await tracer.recordFeedback(runId, { key: 'thumbs', score: 0, source: 'user' });
786
+ ```
787
+
788
+ `compareTraces(a, b)` lays two runs of the same shape side by side and reports what changed — the
789
+ step that got slower, the tool that stopped being called, the output that differs — which is how
790
+ "it worked yesterday" becomes an answerable question.
791
+
792
+ **Alerts** run the same queries on a timer, and carry the runs that tripped them:
793
+
794
+ ```ts
795
+ import { AlertEvaluator, createWebhookNotifier } from 'nexus-ai-pro/tracing';
796
+
797
+ const alerts = new AlertEvaluator(
798
+ store,
799
+ [
800
+ { name: 'error rate', metric: 'errorRate', threshold: 0.05, minRuns: 20 },
801
+ { name: 'p95 latency', metric: 'latencyP95', threshold: 8000 },
802
+ { name: 'hourly spend', metric: 'cost', threshold: 25, windowMs: 3_600_000 },
803
+ ],
804
+ { notifier: createWebhookNotifier({ url: process.env.ALERT_WEBHOOK! }) },
805
+ );
806
+
807
+ setInterval(() => void alerts.evaluate(), 60_000);
808
+ ```
809
+
810
+ An alert names the rule, the measured value, and three runs that contributed, so the next step is
811
+ reading them rather than starting an investigation.
812
+
562
813
  ## Provider Batch Tiers
563
814
 
564
815
  Both OpenAI and Anthropic sell an asynchronous tier at roughly half price, in exchange for a
@@ -1829,21 +2080,23 @@ only, so it never appears in an import graph.
1829
2080
  | `nexus-ai-pro/providers/llamacpp` | 77 KB | 13% | none |
1830
2081
  | `nexus-ai-pro/providers/lmstudio` | 77 KB | 13% | none |
1831
2082
  | `nexus-ai-pro/providers/openai` | 77 KB | 13% | none |
2083
+ | `nexus-ai-pro/agent` | 76 KB | 12% | none |
1832
2084
  | `nexus-ai-pro/providers/anthropic` | 70 KB | 11% | none |
1833
2085
  | `nexus-ai-pro/providers/google` | 66 KB | 11% | none |
1834
2086
  | `nexus-ai-pro/images` | 65 KB | 11% | none |
2087
+ | `nexus-ai-pro/graph` | 60 KB | 10% | none |
1835
2088
  | `nexus-ai-pro/providers/ollama` | 58 KB | 9% | none |
1836
2089
  | `nexus-ai-pro/batch/openai` | 52 KB | 8% | none |
1837
2090
  | `nexus-ai-pro/batch/anthropic` | 52 KB | 8% | none |
1838
2091
  | `nexus-ai-pro/operations` | 49 KB | 8% | none |
1839
2092
  | `nexus-ai-pro/batch/mock` | 47 KB | 8% | none |
1840
2093
  | `nexus-ai-pro/providers/cohere` | 46 KB | 8% | none |
1841
- | `nexus-ai-pro/graph` | 46 KB | 8% | none |
1842
2094
  | `nexus-ai-pro/realtime/openai-webrtc` | 46 KB | 8% | none |
1843
2095
  | `nexus-ai-pro/security` | 40 KB | 7% | +3.4 MB |
1844
2096
  | `nexus-ai-pro/models` | 35 KB | 6% | none |
1845
2097
  | `nexus-ai-pro/images/inputs` | 31 KB | 5% | none |
1846
2098
  | `nexus-ai-pro/realtime/openai-websocket` | 29 KB | 5% | none |
2099
+ | `nexus-ai-pro/tracing` | 26 KB | 4% | none |
1847
2100
  | `nexus-ai-pro/evals` | 23 KB | 4% | none |
1848
2101
  | `nexus-ai-pro/images/transform` | 22 KB | 4% | none |
1849
2102
  | `nexus-ai-pro/images/stores` | 22 KB | 4% | none |
@@ -1852,6 +2105,7 @@ only, so it never appears in an import graph.
1852
2105
  | `nexus-ai-pro/images/openai` | 19 KB | 3% | none |
1853
2106
  | `nexus-ai-pro/realtime/mock` | 19 KB | 3% | none |
1854
2107
  | `nexus-ai-pro/voice` | 18 KB | 3% | none |
2108
+ | `nexus-ai-pro/mcp` | 18 KB | 3% | none |
1855
2109
  | `nexus-ai-pro/images/comfyui` | 17 KB | 3% | none |
1856
2110
  | `nexus-ai-pro/embeddings/adapters` | 17 KB | 3% | none |
1857
2111
  | `nexus-ai-pro/realtime/conversation` | 16 KB | 3% | none |
@@ -1865,6 +2119,7 @@ only, so it never appears in an import graph.
1865
2119
  | `nexus-ai-pro/images/mock` | 12 KB | 2% | none |
1866
2120
  | `nexus-ai-pro/optimizer` | 11 KB | 2% | none |
1867
2121
  | `nexus-ai-pro/voice/session` | 11 KB | 2% | none |
2122
+ | `nexus-ai-pro/store/redis` | 11 KB | 2% | none |
1868
2123
  | `nexus-ai-pro/providers` | 10 KB | 2% | none |
1869
2124
  | `nexus-ai-pro/providers/base` | 10 KB | 2% | none |
1870
2125
  | `nexus-ai-pro/embeddings/models` | 10 KB | 2% | none |
@@ -1874,6 +2129,8 @@ only, so it never appears in an import graph.
1874
2129
  | `nexus-ai-pro/realtime/openai-server` | 8 KB | 1% | none |
1875
2130
  | `nexus-ai-pro/capabilities` | 8 KB | 1% | none |
1876
2131
  | `nexus-ai-pro/telephony/realtime-bridge` | 7 KB | 1% | none |
2132
+ | `nexus-ai-pro/graph/visualize` | 6 KB | 1.0% | none |
2133
+ | `nexus-ai-pro/store` | 6 KB | 1.0% | none |
1877
2134
  | `nexus-ai-pro/providers/errors` | 5 KB | 0.8% | none |
1878
2135
  | `nexus-ai-pro/embeddings/mock` | 5 KB | 0.8% | none |
1879
2136
  | `nexus-ai-pro/cache/semantic-cache` | 5 KB | 0.8% | none |
@@ -0,0 +1,119 @@
1
+ import { type CompiledGraph } from '../graph/graph.js';
2
+ import type { GraphCheckpointer, NodeContext } from '../types/graph.js';
3
+ import type { CompletionRequest, Message, ToolDefinition } from '../types/messages.js';
4
+ import type { NexusResponse } from '../types/response.js';
5
+ import type { Store } from '../types/store.js';
6
+ /**
7
+ * An agent as a graph.
8
+ *
9
+ * `AgentLoop` is a `while` loop: it cannot be paused, resumed after a restart, inspected halfway, or
10
+ * made to run two tool calls at once, because a loop has nowhere to keep that state. The same agent
11
+ * expressed as a graph inherits all of it — checkpoints, human approval through `interrupt()`,
12
+ * parallel tool tasks through `Send`, forks, events, and a Mermaid diagram — without this module
13
+ * implementing any of them. `AgentLoop` stays for the simple case that wants none of that.
14
+ */
15
+ /** The model call an agent makes. Injected, so this module needs no provider runtime. */
16
+ export interface AgentModelClient {
17
+ complete(request: CompletionRequest): Promise<NexusResponse>;
18
+ }
19
+ export interface AgentToolCall {
20
+ id: string;
21
+ name: string;
22
+ args: Record<string, unknown>;
23
+ }
24
+ /** What an approver may answer: allow, refuse with a reason, or allow with corrected arguments. */
25
+ export type AgentApproval = boolean | {
26
+ approved: true;
27
+ args?: Record<string, unknown>;
28
+ } | {
29
+ approved: false;
30
+ reason?: string;
31
+ };
32
+ export interface AgentApprovalPolicy {
33
+ /** The question an operator sees. Defaults to naming the tool and its arguments. */
34
+ reason?: (call: AgentToolCall) => string;
35
+ }
36
+ export interface AgentMiddleware {
37
+ name?: string;
38
+ /** Adjusts the request before it is sent: trim history, add context, swap the model. */
39
+ beforeModel?(context: {
40
+ request: CompletionRequest;
41
+ state: AgentState;
42
+ store?: Store;
43
+ }): CompletionRequest | void | Promise<CompletionRequest | void>;
44
+ /** Inspects or replaces the response: redact, validate, count. */
45
+ afterModel?(context: {
46
+ response: NexusResponse;
47
+ state: AgentState;
48
+ store?: Store;
49
+ }): NexusResponse | void | Promise<NexusResponse | void>;
50
+ /** Wraps a tool call, so a policy can log it, time it, or refuse it. */
51
+ wrapToolCall?(call: AgentToolCall, next: () => Promise<AgentToolResult>): Promise<AgentToolResult> | AgentToolResult;
52
+ }
53
+ export interface AgentToolResult {
54
+ ok: boolean;
55
+ result?: unknown;
56
+ error?: string;
57
+ }
58
+ export interface CreateAgentOptions {
59
+ client: AgentModelClient;
60
+ model?: string;
61
+ tools?: ToolDefinition[];
62
+ systemPrompt?: string;
63
+ /** Model calls before the agent stops with `stopReason: 'max_iterations'`. Defaults to 8. */
64
+ maxIterations?: number;
65
+ /** Tool calls run at once. Defaults to 8; `1` runs them one at a time. */
66
+ toolConcurrency?: number;
67
+ /** Tools that need human approval before they run, keyed by tool name. */
68
+ interruptOn?: Record<string, AgentApprovalPolicy | true>;
69
+ middleware?: AgentMiddleware[];
70
+ /** Long-term memory, available to tools and middleware as `store`. */
71
+ store?: Store;
72
+ checkpointer?: GraphCheckpointer | false;
73
+ name?: string;
74
+ temperature?: number;
75
+ maxTokens?: number;
76
+ responseFormat?: CompletionRequest['responseFormat'];
77
+ metadata?: Record<string, unknown>;
78
+ }
79
+ declare const agentChannels: () => {
80
+ messages: import("../types/graph.js").Channel<Message[]>;
81
+ iterations: import("../types/graph.js").Channel<number>;
82
+ answer: import("../types/graph.js").Channel<string>;
83
+ stopReason: import("../types/graph.js").Channel<AgentStopReason | undefined>;
84
+ };
85
+ export type AgentChannels = ReturnType<typeof agentChannels>;
86
+ export type AgentState = {
87
+ messages: Message[];
88
+ iterations: number;
89
+ answer: string;
90
+ stopReason?: AgentStopReason;
91
+ };
92
+ export type AgentStopReason = 'completed' | 'max_iterations';
93
+ export type AgentGraph = CompiledGraph<AgentChannels>;
94
+ /** Wraps a question into the state an agent starts from. */
95
+ export declare function agentInput(goal: string): {
96
+ messages: Message[];
97
+ };
98
+ /**
99
+ * Builds an agent and returns it as a compiled graph.
100
+ *
101
+ * Run it like any graph: `agent.invoke(agentInput('...'), { threadId })`. The result's `answer` is
102
+ * the final text, and `stopReason` says whether the agent finished or ran out of iterations.
103
+ */
104
+ export declare function createAgent(options: CreateAgentOptions): AgentGraph;
105
+ /**
106
+ * Turns an agent into a tool another agent can call.
107
+ *
108
+ * The simplest multi-agent shape: a supervisor keeps control and delegates, rather than handing over
109
+ * the conversation. The specialist runs as its own graph, with its own memory and approvals, and
110
+ * returns its answer as the tool result.
111
+ */
112
+ export declare function agentAsTool(options: {
113
+ agent: AgentGraph;
114
+ name: string;
115
+ description: string;
116
+ /** Threads the specialist's runs, so its work is resumable too. Defaults to a fresh thread. */
117
+ threadId?: (goal: string) => string;
118
+ }): ToolDefinition;
119
+ export type { NodeContext };