@spendgraph/graph 0.7.0 → 0.8.1

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.
@@ -13,6 +13,15 @@ export interface GraphResult {
13
13
  /** Summed across every step, nested ones included. */
14
14
  inputTokens: number;
15
15
  outputTokens: number;
16
+ /**
17
+ * The counts a provider bills apart from input and output, summed the same
18
+ * way — each present only where some node actually reported it, so absence
19
+ * is the claim that nothing was billed under it rather than a zero.
20
+ */
21
+ cacheReadTokens?: number;
22
+ cacheWriteTokens?: number;
23
+ citationTokens?: number;
24
+ reasoningTokens?: number;
16
25
  /** The question a node stopped to ask, where one did; `status` still says the graph itself ran. */
17
26
  waitingOn?: Wait;
18
27
  /** The nested run to resume, where it named one. */
package/docs/nodes.mdx CHANGED
@@ -1,5 +1,5 @@
1
1
  export const meta = {
2
- title: "Nodes spendgraph docs",
2
+ title: "Nodes: spendgraph docs",
3
3
  description:
4
4
  "One unit of work. Declare its arguments with `as const` and the handler types itself; say where they come from with `input`, and the node stays reusable.",
5
5
  };
@@ -22,7 +22,7 @@ const classify = node({
22
22
 
23
23
  ## `input` is what makes it reusable
24
24
 
25
- The node says what it **needs**; the graph says where it **comes from**. Without that split, every node has to know about the whole run and a node that reads `ctx.values.question` directly is a node you cannot drop into a second graph where the question is called something else.
25
+ The node says what it **needs**; the graph says where it **comes from**. Without that split, every node has to know about the whole run, and a node that reads `ctx.values.question` directly is a node you cannot drop into a second graph where the question is called something else.
26
26
 
27
27
  ```ts
28
28
  input: (ctx) => ({ text: ctx.values.question }); // from the run's values
@@ -40,7 +40,7 @@ run: ({ text }) => text.toUpperCase(), // `text` is a string, here and now
40
40
 
41
41
  The handler types itself from the args: `text` a string, a `number` arg a number, an optional one optional. Rename an argument and the handler stops compiling.
42
42
 
43
- **Without `as const` inference falls back to `Record<string, unknown>` silently.** Nothing errors; the types simply stop meaning anything, and a typo that used to be a compile error becomes `undefined` halfway through a graph the most expensive place to find one.
43
+ **Without `as const` inference falls back to `Record<string, unknown>` silently.** Nothing errors; the types simply stop meaning anything, and a typo that used to be a compile error becomes `undefined` halfway through a graph, the most expensive place to find one.
44
44
 
45
45
  <Callout tone="trap" title="A node cannot rewrite what an earlier node did">
46
46
  The steps a node is handed are a frozen copy: writing to one changes nothing, and it will not throw to tell you so. Pass what the next node needs through the return value, which is what `input` reads.
@@ -56,4 +56,4 @@ A node name must be letters, digits and underscores starting with a letter, and
56
56
 
57
57
  Whatever `run` returns becomes the node's output: a string is itself, anything else is JSON.
58
58
 
59
- A value that cannot be written down a circular object, a `BigInt`, something whose own `toJSON` throws is recorded as `[not recordable: …]` rather than `""`. An empty output reads as a node that produced nothing, and this is a node that produced something the record could not hold. The two are different facts and the step keeps them apart.
59
+ A value that cannot be written down, a circular object, a `BigInt`, something whose own `toJSON` throws, is recorded as `[not recordable: …]` rather than `""`. An empty output reads as a node that produced nothing, and this is a node that produced something the record could not hold. The two are different facts and the step keeps them apart.
package/docs/overview.mdx CHANGED
@@ -1,5 +1,5 @@
1
1
  export const meta = {
2
- title: "Graph spendgraph docs",
2
+ title: "Graph: spendgraph docs",
3
3
  description:
4
4
  "Wire nodes into a graph, run it, and get back a rollout with every step priced. Four exports, compilation that catches the wiring mistakes, and a run that never throws.",
5
5
  };
@@ -8,12 +8,14 @@ export const meta = {
8
8
 
9
9
  Multi-step work hand-rolled in `if`/`await` runs fine and leaves nothing behind: when step four fails at 2am, what ran, in what order, and what it cost are all gone.
10
10
 
11
- A graph is nodes and the edges between them. You describe the units of work and where control goes next; running it hands back a rollout every step, in order, with what each one cost.
11
+ A graph is nodes and the edges between them. You describe the units of work and where control goes next; running it hands back a rollout: every step, in order, with what each one cost.
12
12
 
13
13
  ```sh
14
14
  npm install @spendgraph/graph
15
15
  ```
16
16
 
17
+ <GraphToRollout />
18
+
17
19
  Four things are exported. Everything else hangs off what they return.
18
20
 
19
21
  ```ts
@@ -76,21 +78,21 @@ await prompt.call(values, () => flow.execute(values));
76
78
 
77
79
  ## Three things worth knowing up front
78
80
 
79
- **Compilation is the one place this package throws.** An edge to a node that does not exist, an entry that is not a node, two nodes with the same name, a node nothing reaches, a node whose edges are all conditional all of it is checked before anything runs. See [wiring](/docs/graph/wiring).
81
+ **Compilation is the one place this package throws.** An edge to a node that does not exist, an entry that is not a node, two nodes with the same name, a node nothing reaches, a node whose edges are all conditional, all of it is checked before anything runs. See [wiring](/spendgraph/graph/wiring).
80
82
 
81
- **A run never throws.** A node that fails halts the run and comes back as a result with the steps that did run and the failed one last. A thrown exception cannot tell you which three nodes ran; a result can. See [running a graph](/docs/graph/running).
83
+ **A run never throws.** A node that fails halts the run and comes back as a result with the steps that did run and the failed one last. A thrown exception cannot tell you which three nodes ran; a result can. See [running a graph](/spendgraph/graph/running).
82
84
 
83
- **`outputs` is the only channel between nodes.** No shared mutable bag, so node four cannot come to depend on a key node two happens to set a dependency the graph does not declare and compilation cannot check.
85
+ **`outputs` is the only channel between nodes.** No shared mutable bag, so node four cannot come to depend on a key node two happens to set: a dependency the graph does not declare and compilation cannot check.
84
86
 
85
87
  <Callout tone="trap" title="Compilation is the only place mistakes are cheap">
86
- An edge pointing at a node that does not exist, an entry that is not a node, a node nothing reaches all refused by `graph()`, before a single model call. The same mistakes found at run time are found on the branch that reaches them, which may be the one that only runs at month end.
88
+ An edge pointing at a node that does not exist, an entry that is not a node, a node nothing reaches, all refused by `graph()`, before a single model call. The same mistakes found at run time are found on the branch that reaches them, which may be the one that only runs at month end.
87
89
  </Callout>
88
90
 
89
91
  ## Where to go next
90
92
 
91
93
  | | |
92
94
  | --- | --- |
93
- | [Nodes](/docs/graph/nodes) | declaring one, and the types you get for free |
94
- | [Wiring](/docs/graph/wiring) | edges, branching, and what compilation refuses |
95
- | [Running a graph](/docs/graph/running) | failure, the step ceiling, and what comes back |
96
- | [Streaming](/docs/graph/streaming) | the same run, narrated |
95
+ | [Nodes](/spendgraph/graph/nodes) | declaring one, and the types you get for free |
96
+ | [Wiring](/spendgraph/graph/wiring) | edges, branching, and what compilation refuses |
97
+ | [Running a graph](/spendgraph/graph/running) | failure, the step ceiling, and what comes back |
98
+ | [Streaming](/spendgraph/graph/streaming) | the same run, narrated |
package/docs/pausing.mdx CHANGED
@@ -1,5 +1,5 @@
1
1
  export const meta = {
2
- title: "Pausing a graph spendgraph docs",
2
+ title: "Pausing a graph: spendgraph docs",
3
3
  description:
4
4
  "A node that runs something needing a person stops the graph rather than walking past the question. The pause arrives as an event, and `entry` picks the run back up where it stopped.",
5
5
  };
@@ -29,7 +29,7 @@ That is the same argument as reading a node's outcome structurally: a node may
29
29
  return anything, and a rule to wrap a paused run is one you learn by having the
30
30
  graph walk past the question and call it success. Every node's return goes
31
31
  through it, so a `waitingOn` that is data rather than a question stops the run
32
- too nothing else in a reply has that shape, and the alternative fails the
32
+ too, nothing else in a reply has that shape, and the alternative fails the
33
33
  other way, silently.
34
34
 
35
35
  The check itself is exported, so your own code can ask the same question the
@@ -44,7 +44,7 @@ paused?.runId; // the inner run to resume, where there i
44
44
  ```
45
45
 
46
46
  Reach for it inside a node that runs a nested graph, or in a harness of your
47
- own. `Wait` is declared here rather than imported for the same reason harness
47
+ own. `Wait` is declared here rather than imported for the same reason: harness
48
48
  owns durability and depends on this package, so the shape lives on this side and
49
49
  harness's own `Wait` satisfies it.
50
50
 
@@ -56,18 +56,18 @@ for await (const event of flow.stream(values)) {
56
56
  }
57
57
  ```
58
58
 
59
- The event carries the `Wait` whole the question, and whatever answering it
59
+ The event carries the `Wait` whole, the question, and whatever answering it
60
60
  needs. A thrown error would carry only its message, which is why this is an
61
61
  event and not an exception.
62
62
 
63
63
  <Callout tone="trap" title="Whatever a node returned has to survive JSON">
64
- A paused run is stored and handed back to running code. A `Date` comes back a string, a `Map` comes back `{}`, and neither throws so the refusal happens at the pause, naming the node whose return would not survive, rather than three days later in whatever read it.
64
+ A paused run is stored and handed back to running code. A `Date` comes back a string, a `Map` comes back `{}`, and neither throws, so the refusal happens at the pause, naming the node whose return would not survive, rather than three days later in whatever read it.
65
65
  </Callout>
66
66
 
67
67
  ## The graph owns no durability
68
68
 
69
69
  It reports the pause and stops. Storing the run, holding it while somebody
70
- thinks, and handing the answer back is `@spendgraph/harness`'s job it has the
70
+ thinks, and handing the answer back is `@spendgraph/harness`'s job: it has the
71
71
  store, the expiry and the resume.
72
72
 
73
73
  ## Picking it back up
@@ -103,7 +103,7 @@ resuming a cyclic graph again and again is the one holding the ceiling.
103
103
  ## A run that paused still ran
104
104
 
105
105
  `status` says whether the graph itself ran, and a paused run ran correctly as
106
- far as it got so it comes back `completed`, carrying the question.
106
+ far as it got, so it comes back `completed`, carrying the question.
107
107
 
108
108
  Every workflow in this repo signals a pause the same way: not with a status, but
109
109
  by carrying what it stopped to ask.
package/docs/running.mdx CHANGED
@@ -1,5 +1,5 @@
1
1
  export const meta = {
2
- title: "Running a graph spendgraph docs",
2
+ title: "Running a graph: spendgraph docs",
3
3
  description:
4
4
  "A run never throws: a failed node halts it and comes back as a result with the steps that did run. The step ceiling, the context between nodes, and what a rollout carries.",
5
5
  };
@@ -20,7 +20,7 @@ result.error; // '"lookup" failed: contract MSA 2.4 not found'
20
20
  result.steps; // the steps that did run, with the failed one last
21
21
  ```
22
22
 
23
- Continuing would leave every later node reading an output that was never written. Stopping with the steps intact is what lets a failed run say which three nodes ran and where it stopped which a thrown exception cannot.
23
+ Continuing would leave every later node reading an output that was never written. Stopping with the steps intact is what lets a failed run say which three nodes ran and where it stopped, which a thrown exception cannot.
24
24
 
25
25
  ## The step ceiling
26
26
 
@@ -32,10 +32,10 @@ A backwards edge is a feature and also how a graph hangs, and nothing tells the
32
32
  graph({ entry: "draft", nodes, edges, maxSteps: 50 });
33
33
  ```
34
34
 
35
- A ceiling that is not finite is **refused at compile time**. `NaN` would make the count never fire, and `Number(process.env.MAX_STEPS)` on an unset variable is `NaN` which is how an unbounded run arrives without anyone typing one.
35
+ A ceiling that is not finite is **refused at compile time**. `NaN` would make the count never fire, and `Number(process.env.MAX_STEPS)` on an unset variable is `NaN`, which is how an unbounded run arrives without anyone typing one.
36
36
 
37
37
  <Callout tone="trap" title="The ceiling counts nodes, and defaults to 25">
38
- `maxSteps` bounds how many nodes a run may visit, not how many tokens it may spend a cycle between two cheap nodes hits it, an expensive straight line never does. It defaults to 25, and a run that reaches it comes back as a *failed* result rather than a truncated success.
38
+ `maxSteps` bounds how many nodes a run may visit, not how many tokens it may spend, a cycle between two cheap nodes hits it, an expensive straight line never does. It defaults to 25, and a run that reaches it comes back as a *failed* result rather than a truncated success.
39
39
  </Callout>
40
40
 
41
41
  ## What comes back
@@ -60,7 +60,7 @@ await prompt.call(values, () => flow.execute(values));
60
60
 
61
61
  ## Context
62
62
 
63
- `outputs` is the **only** channel between nodes. A shared mutable bag would let node four depend on a key node two happens to set a dependency the graph does not declare and compilation cannot check.
63
+ `outputs` is the **only** channel between nodes. A shared mutable bag would let node four depend on a key node two happens to set: a dependency the graph does not declare and compilation cannot check.
64
64
 
65
65
  ```ts
66
66
  ctx.values; // what execute() was called with
@@ -1,12 +1,12 @@
1
1
  export const meta = {
2
- title: "Streaming a graph spendgraph docs",
2
+ title: "Streaming a graph: spendgraph docs",
3
3
  description:
4
4
  "The same run, narrated: each node's start and end, whatever its nodes emit while running, and the finished rollout last.",
5
5
  };
6
6
 
7
7
  # Streaming
8
8
 
9
- `stream` is the same run as `execute`, narrated a node's start and end, whatever its nodes emit while running, and the finished result last.
9
+ `stream` is the same run as `execute`, narrated, a node's start and end, whatever its nodes emit while running, and the finished result last.
10
10
 
11
11
  ```ts
12
12
  const running = flow.stream({ question });
@@ -29,7 +29,7 @@ const answer = node({
29
29
  });
30
30
  ```
31
31
 
32
- `emit` takes the text and nothing else the graph tags it with the node it came
32
+ `emit` takes the text and nothing else, the graph tags it with the node it came
33
33
  from, because a stream carrying tokens from four nodes is unreadable without it.
34
34
  It is always present and does nothing when nobody is listening, so a node never
35
35
  has to ask whether anyone is.
@@ -44,7 +44,7 @@ const result = await running.result; // no iteration, and it still ran
44
44
  A caller who wants the rollout and not the commentary gets it. Nothing is deferred until somebody reads the stream, so a run cannot sit there un-started because a `for await` was never reached.
45
45
 
46
46
  <Callout tone="trap" title="One stream has one reader">
47
- Two `for await` loops over the same `graph.stream()` take from the same queue, so each sees roughly half the run which reads as a graph dropping tokens rather than as the mistake it is. A second reader is refused. More than one watcher wants one reader writing into something they can both read.
47
+ Two `for await` loops over the same `graph.stream()` take from the same queue, so each sees roughly half the run, which reads as a graph dropping tokens rather than as the mistake it is. A second reader is refused. More than one watcher wants one reader writing into something they can both read.
48
48
  </Callout>
49
49
 
50
50
  ## What the events are
@@ -53,10 +53,10 @@ A caller who wants the rollout and not the commentary gets it. Nothing is deferr
53
53
  | --- | --- |
54
54
  | a node starting | its name, and the step it is |
55
55
  | a node finishing | what it returned, what it cost, how long it took |
56
- | whatever a node emitted | passed through untouched tokens, progress, anything |
57
- | a node stopping to ask | the question, and the nested run to resume see [pausing](/docs/graph/pausing) |
56
+ | whatever a node emitted | passed through untouched: tokens, progress, anything |
57
+ | a node stopping to ask | the question, and the nested run to resume. See [pausing](/spendgraph/graph/pausing) |
58
58
  | the result | last, and the same object `execute` would have returned |
59
59
 
60
60
  One reader. Events are handed out and dropped, so a second `for await` over the
61
- same stream takes half of them rather than seeing the run twice fan out
61
+ same stream takes half of them rather than seeing the run twice: fan out
62
62
  downstream of it, not by iterating it twice.
package/docs/wiring.mdx CHANGED
@@ -1,7 +1,7 @@
1
1
  export const meta = {
2
- title: "Wiring spendgraph docs",
2
+ title: "Wiring: spendgraph docs",
3
3
  description:
4
- "Edges say what runs next. Branching is several edges tried in order, not one clever edge and compilation refuses the wiring mistakes before anything runs.",
4
+ "Edges say what runs next. Branching is several edges tried in order, not one clever edge, and compilation refuses the wiring mistakes before anything runs.",
5
5
  };
6
6
 
7
7
  # Wiring
@@ -23,7 +23,7 @@ const flow = graph({
23
23
 
24
24
  ## Branching is several edges, not one clever edge
25
25
 
26
- `edge(from, to, when)` takes a predicate. The edges leaving a node are tried **in declaration order**, and the first match wins so an unconditional edge is the default branch and belongs last.
26
+ `edge(from, to, when)` takes a predicate. The edges leaving a node are tried **in declaration order**, and the first match wins, so an unconditional edge is the default branch and belongs last.
27
27
 
28
28
  ```ts
29
29
  edge("triage", "refund", (ctx) => ctx.outputs.triage === "billing"),
@@ -46,14 +46,14 @@ Compilation happens once, when you call `graph()`, and it is the one place this
46
46
  | a node whose edges are **all conditional** | the half-specified case, below |
47
47
 
48
48
  <Callout tone="trap" title="A second default edge is refused, not ignored">
49
- Edges are tried in order and a default matches everything, so a second one after it can never be taken. That reads as a branch you wired and never see fire compilation refuses it instead, naming the node.
49
+ Edges are tried in order and a default matches everything, so a second one after it can never be taken. That reads as a branch you wired and never see fire: compilation refuses it instead, naming the node.
50
50
  </Callout>
51
51
 
52
52
  ## The half-specified node
53
53
 
54
54
  A node with **no** edges is the end of the run: the author wrote none and meant it.
55
55
 
56
- A node whose edges are *all* conditional is different. A run arriving there when no condition matches has nowhere to go so it would stop, and report **success**, having skipped every node after it. Saying half of it is the mistake, and compilation refuses it:
56
+ A node whose edges are *all* conditional is different. A run arriving there when no condition matches has nowhere to go, so it would stop, and report **success**, having skipped every node after it. Saying half of it is the mistake, and compilation refuses it:
57
57
 
58
58
  ```ts
59
59
  edges: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spendgraph/graph",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Wire nodes into a graph, run it, and get back a rollout with every step.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -36,9 +36,10 @@
36
36
  "README.md"
37
37
  ],
38
38
  "dependencies": {
39
- "@spendgraph/sdk": "^0.7.0"
39
+ "@spendgraph/sdk": "^0.8.1"
40
40
  },
41
41
  "devDependencies": {
42
+ "@spendgraph/config": "0.8.1",
42
43
  "typescript": "^5"
43
44
  },
44
45
  "engines": {