@krishnadobhal/rewind-sdk-js 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +182 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # @krishnadobhal/rewind-sdk-js
2
+
3
+ Record every boundary a LangGraph agent crosses — each model call, each tool call, the
4
+ exact request and the exact response — so you can read back what actually happened
5
+ instead of guessing from logs.
6
+
7
+ ```bash
8
+ npm install @krishnadobhal/rewind-sdk-js
9
+ ```
10
+
11
+ ## Why
12
+
13
+ An agent that goes wrong leaves you a transcript and a bill. What you need is the
14
+ *request* — the fully-resolved prompt after every template, tool schema and message
15
+ rewrite — and there is no log line that contains it. Rewind sits at the boundary, hashes
16
+ the request canonically, and stores both halves of the exchange.
17
+
18
+ ## Quick start
19
+
20
+ ```ts
21
+ import { withRewind } from '@krishnadobhal/rewind-sdk-js/middleware';
22
+
23
+ const rw = await withRewind({ model, tools });
24
+
25
+ // Hand the instrumented pair to your graph. Nothing else changes.
26
+ const graph = buildGraph({ model: rw.model, tools: rw.tools });
27
+ ```
28
+
29
+ Then switch it on from the environment:
30
+
31
+ ```bash
32
+ REWIND_ENABLED=1 node agent.js
33
+ cat .rewind/runs/*/steps.jsonl | jq '{seq,node,kind,req_hash}'
34
+ ```
35
+
36
+ **With no variable set, `withRewind` hands back your original model and tools**, unwrapped.
37
+ An uninstrumented run costs nothing and takes no branches, so this is safe to leave in
38
+ production code.
39
+
40
+ ## The environment is the interface
41
+
42
+ There is no CLI. Configuration is the variables the SDK reads inside your process, because
43
+ redaction has to happen *before* anything leaves it — a wrapper watching from outside only
44
+ sees bytes on a socket, by which point the PII is already gone.
45
+
46
+ | | |
47
+ |---|---|
48
+ | `REWIND_ENABLED=1` | record this run |
49
+ | `REWIND_DIR` | where recordings go (default `.rewind`) |
50
+ | `REWIND_SERVER` | send writes to an ingest server instead of a directory |
51
+ | `REWIND_TOKEN` | bearer token for that server |
52
+ | `REWIND_CONFIG` | path to a `rewind.config.ts` |
53
+ | `REWIND_ENV_FILE` | a `.env` to load, or `0` to skip it |
54
+
55
+ A `.env` in the working directory is loaded automatically, *before* `REWIND_ENABLED` is
56
+ checked — so a value in the file can switch recording on without touching your shell.
57
+
58
+ ## What gets recorded
59
+
60
+ Every crossing becomes a `Step` plus a content-addressed `Cassette`:
61
+
62
+ ```jsonc
63
+ {
64
+ "seq": 0,
65
+ "node": "plan", // the LangGraph node it happened in
66
+ "kind": "model", // model · tool · clock · rng · human · env
67
+ "req_hash": "9c28dd0e…", // sha256 over the canonical request
68
+ "match_tier": "recorded",
69
+ "latency_ms": 420,
70
+ "tokens": 900,
71
+ "cost_usd": 0.004
72
+ }
73
+ ```
74
+
75
+ The hash is what makes a call *identifiable*. Two runs that asked the same thing share one
76
+ cassette, so 500 runs behind the same system prompt store it once. Canonicalization is
77
+ [RFC 8785 JCS](https://www.rfc-editor.org/rfc/rfc8785) plus normalization that scrubs
78
+ volatile ids to ordinals — which is why two recordings of the same question hash
79
+ identically even though LangChain stamps a fresh UUID on every message.
80
+
81
+ ## Redaction happens at write time
82
+
83
+ ```ts
84
+ // rewind.config.ts
85
+ import { defineConfig } from '@krishnadobhal/rewind-sdk-js/config';
86
+
87
+ export default defineConfig({
88
+ dir: '.rewind',
89
+ redact: {
90
+ preset: 'default', // emails, phones, cards, keys, JWTs
91
+ fields: ['ssn', 'dateOfBirth'], // by key name, at any depth
92
+ custom: [/ACC-\d{8}/g],
93
+ },
94
+ });
95
+ ```
96
+
97
+ A cassette is written already redacted. There is no unredacted copy on disk to leak,
98
+ because the plaintext never reaches the store — only the hash is computed over it, so
99
+ recordings stay matchable without holding the sensitive value.
100
+
101
+ The redactor is also callable directly:
102
+
103
+ ```ts
104
+ import { redact } from '@krishnadobhal/rewind-sdk-js/redact';
105
+
106
+ const { value, map } = redact({ to: 'a@b.com', note: 'call 555-123-4567' }, { preset: 'default' });
107
+
108
+ // value { to: '[redacted:email:0]', note: 'call [redacted:phone:0]' }
109
+ // map { '[redacted:email:0]': 'email', '[redacted:phone:0]': 'phone' }
110
+ ```
111
+
112
+ ## Recording somewhere other than a disk
113
+
114
+ ```bash
115
+ REWIND_ENABLED=1 REWIND_SERVER=http://localhost:4000 node agent.js
116
+ ```
117
+
118
+ The SDK swaps its directory store for a batching HTTP client and your graph is none the
119
+ wiser. `Recorder.record()` runs inside your nodes, where it may neither block nor be
120
+ awaited, so writes go through a bounded queue with a background flush:
121
+
122
+ ```ts
123
+ import { bufferedStore } from '@krishnadobhal/rewind-sdk-js/emitter';
124
+ import { httpStore } from '@krishnadobhal/rewind-sdk-js/http';
125
+
126
+ const store = bufferedStore({ target: httpStore({ url, token }), maxQueue: 1000 });
127
+ ```
128
+
129
+ See [`@krishnadobhal/rewind-server`](https://www.npmjs.com/package/@krishnadobhal/rewind-server)
130
+ for the other end, and [`@krishnadobhal/rewind-ui`](https://www.npmjs.com/package/@krishnadobhal/rewind-ui)
131
+ for the viewer.
132
+
133
+ ## It fails open, on purpose
134
+
135
+ A full disk, an unreachable server, a serialization bug — each is counted and logged, not
136
+ thrown. Your agent keeps running and the run is marked `partial`. Observability must never
137
+ take down the thing it observes, and a recorder that can crash your agent is worse than no
138
+ recorder.
139
+
140
+ A `partial` run is honestly labelled rather than quietly trusted: it dropped events, so
141
+ nothing may treat it as a complete account of what happened.
142
+
143
+ ## Reading recordings back
144
+
145
+ ```ts
146
+ import { readTrace, listRuns, readCassette } from '@krishnadobhal/rewind-sdk-js/store';
147
+
148
+ for (const id of listRuns('.rewind')) {
149
+ const { run, steps } = readTrace('.rewind', id)!;
150
+ console.log(id, run.status, steps.length);
151
+ }
152
+ ```
153
+
154
+ ## Exports
155
+
156
+ | | |
157
+ |---|---|
158
+ | `/middleware` | `withRewind` — the one function most code needs |
159
+ | `/recorder` | `Recorder` for driving it yourself |
160
+ | `/redact` | `redact()` and its config types |
161
+ | `/store` | `fileStore`, `readTrace`, `listRuns`, `readCassette` |
162
+ | `/emitter` | `bufferedStore` — the bounded queue |
163
+ | `/http` | `httpStore` — the ingest client |
164
+ | `/config` | `defineConfig`, `DEFAULT_DIR` |
165
+ | `/env` | the variable names, `loadEnvFile`, `recorderFromEnv` |
166
+
167
+ ## Requirements
168
+
169
+ Node 22.6+ (24 recommended) and ESM. TypeScript types ship with the package.
170
+
171
+ ## Scope
172
+
173
+ Recording is what this does. **Replay — re-running a recorded run against its cassettes
174
+ with the network blocked — is not in this release**; it is parked on the `replay-engine`
175
+ branch pending a way to replay a run that lives on a server rather than on local disk.
176
+
177
+ No clock or RNG shims yet either, so an agent that reads the wall clock or a random source
178
+ has a boundary Rewind does not see.
179
+
180
+ ## License
181
+
182
+ Apache-2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krishnadobhal/rewind-sdk-js",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Record every boundary an LLM agent crosses: withRewind, the recorder, the redactor, and the stores.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Krishna Dobhal",
@@ -38,7 +38,7 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "@krishnadobhal/rewind-core": "0.1.0"
41
+ "@krishnadobhal/rewind-core": "0.1.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^24.13.3"