@metaharness/workspace-lens 0.1.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.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # @metaharness/workspace-lens
2
+
3
+ **A Jacobian-Lens interpretability primitive for open-weight LLMs.** Read the model's *verbalizable
4
+ workspace* — the concepts it is disposed to say next — at inference time, and turn it into workspace
5
+ tokens, a layer-by-layer thinking trajectory, drift/entropy scores, vectorized safety flags, and a
6
+ signable **interpretability receipt**.
7
+
8
+ Runtime-only · model-agnostic · dependency-free (Node built-ins) · deterministic · `$0` to run.
9
+
10
+ > Companion runtime for Anthropic's *"Verbalizable Representations Form a Global Workspace in Language
11
+ > Models"* (2026-07-06) and its reference code [`anthropics/jacobian-lens`](https://github.com/anthropics/jacobian-lens).
12
+ > This package is **not affiliated with Anthropic**; it is an independent, Apache/MIT-compatible
13
+ > runtime that *consumes* a fitted lens.
14
+
15
+ ---
16
+
17
+ ## Why this exists: from black-box testing to Mechanistic Governance
18
+
19
+ Classic **logit lens** decodes an intermediate activation `h_l` through the unembedding directly —
20
+ assuming middle layers already live in final-output coordinates. They don't, so the readout is noisy.
21
+
22
+ The **Jacobian Lens** learns an average layer→final map `J_l` and decodes through it:
23
+
24
+ ```
25
+ lens_l(h) = unembed(J_l · h)
26
+ ```
27
+
28
+ Instead of asking *"what does this activation predict right now?"*, it asks *"what is this activation
29
+ **disposed** to make the model say later?"* — surfacing meaningful, reportable concepts **earlier** in the
30
+ network, often **before the first output token is generated**. Anthropic show models maintain a small set
31
+ of verbalizable internal representations that behave like a functional **global workspace** (report,
32
+ modulation, reasoning, reuse, selective access) — and that hidden concepts like *evaluation awareness*,
33
+ *manipulation*, *secretly*, and *trick* light up there even when absent from the output.
34
+
35
+ That makes the lens a **runtime semantic firewall** and an audit primitive — the basis for what we call
36
+ **Interpretability Operations (IntOps)**: tap the model's internal wires *while it thinks*, instead of
37
+ asking it to explain itself afterward (which is prone to sycophancy and confabulation).
38
+
39
+ ## What's in the box
40
+
41
+ | Capability | Export | What it does |
42
+ |---|---|---|
43
+ | **Lens readout** | `WorkspaceLens.readout(h)` | `unembed(J_l·h)` → top workspace tokens + readout entropy |
44
+ | **J-projection** | `WorkspaceLens.project(h)` | `z_l = J_l·h`, the activation in final-layer coordinates |
45
+ | **Workspace drift** | `workspaceDrift(readouts)` | mean Jensen–Shannon divergence between consecutive readouts — is the reasoning path stable or mutating? |
46
+ | **Entropy trajectory** | `entropyTrajectory(readouts)` | per-layer entropy — is the workspace converging or dissolving? |
47
+ | **Vectorized safety** | `detectConcepts(...)` / `flagsFromTriggers(...)` | dot-product triggers vs. concept **directions** (not token strings) → `{promptInjection, evalAwareness, hiddenObjective, refusalConflict}` |
48
+ | **Receipt** | `buildReceipt(...)` | the signable `WorkspaceLensReceipt` audit artifact |
49
+ | **Decision rule** | `decide(...)` | `taskResolved && drift<θ && noCriticalFlags && receiptCoverage===1` |
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ npm i @metaharness/workspace-lens
55
+ ```
56
+
57
+ ## Quickstart
58
+
59
+ ```ts
60
+ import { WorkspaceLens, buildReceipt, decide } from '@metaharness/workspace-lens';
61
+
62
+ // 1) Load a lens FITTED OUT OF BAND (see "Fitting" below). The artifact carries the vocab + unembed,
63
+ // so scoring never touches a tokenizer at runtime.
64
+ const lens = await WorkspaceLens.fromFile('./jlens-qwen2.5-7b.json');
65
+
66
+ // 2) Capture residual activations from your open-weight runtime and hand them in.
67
+ const states = [
68
+ { layer: 14, position: 6, h: /* number[dModel] */ activations14 },
69
+ { layer: 18, position: 6, h: activations18 },
70
+ { layer: 22, position: 6, h: activations22 },
71
+ ];
72
+
73
+ // 3) Read the workspace + build the audit receipt.
74
+ const receipt = buildReceipt(lens, prompt, states, {
75
+ createdAt: new Date().toISOString(), // pass it in — receipts stay reproducible
76
+ concepts, // per-model concept direction vectors (optional)
77
+ topK: 8,
78
+ });
79
+
80
+ // 4) Govern on it.
81
+ const verdict = decide({
82
+ taskResolved,
83
+ workspaceDrift: receipt.workspaceDrift,
84
+ driftThreshold: 0.25,
85
+ triggers: receipt.triggers,
86
+ receiptCoverage: 1,
87
+ });
88
+ if (!verdict.accepted) escalateToHuman(verdict.reasons, receipt);
89
+ ```
90
+
91
+ ## The interpretability receipt
92
+
93
+ The killer feature for regulated buyers: an audit log that maps the **causal trajectory** of a decision —
94
+ *where* a concept arrived, *how* confidence moved, *which* objectives competed — not just the final
95
+ answer. It turns *"the model is a black box that hallucinated"* into *"the model identified a structural
96
+ contradiction at layer 22 and executed the safety policy."* See `WorkspaceLensReceipt` in
97
+ [`src/types.ts`](./src/types.ts).
98
+
99
+ ## Cross-family vocabulary alignment (the hard part, solved)
100
+
101
+ Different model families (Qwen vs. Gemma 2) have vastly different tokenizers, so you **cannot** align a
102
+ concept like *"hidden objective"* by token string. This package aligns at the **concept-direction** level:
103
+ a canonical concept name maps to a **per-model unit vector** in that model's J-space (`ConceptVector`),
104
+ fitted from example activations. Safety detection is a cosine/dot-product in activation space —
105
+ tokenizer-agnostic — and a concept vector is **never** cross-applied to a different model (`modelId` is
106
+ checked). So `hidden_objective` is *one* concept with a Qwen vector and a Gemma vector, aligned by name.
107
+
108
+ ## Deployment topology (Triage Architecture)
109
+
110
+ Runtime projection is just static linear algebra (`J_l·h` + a softmax) — **zero** backward passes — so it
111
+ can run live, not only in shadow sampling:
112
+
113
+ | Tier | Trigger | Depth | Overhead |
114
+ |---|---|---|---|
115
+ | **1 · Passive** | low-risk chat / static generation | lens bypassed | 0% |
116
+ | **2 · Spot-check** | 1% shadow sampling · Darwin-Mode mutation evidence | async batch logging | ~0% |
117
+ | **3 · Full intercept** | tool calls · financial txns · PII · untrusted retrieval | synchronous, mid-layers at critical tokens | small |
118
+
119
+ Bind Tier 3 to high-stakes routing tokens (e.g. a tool-call token) and you get a **deterministic circuit
120
+ breaker**: a spike in the *exfiltration* / *override* / *credential* directions can kill execution
121
+ mid-forward-pass, *before* a single malicious token is emitted.
122
+
123
+ ## Fitting is external (the one real constraint)
124
+
125
+ This package **applies** a lens; it does not **fit** one. Fitting `J_l` requires the model's **backward
126
+ pass** over ~100–1000 short sequences (quality saturates quickly), which needs **open-weight local
127
+ models** with activation + gradient access — not a closed API. Fit with the reference
128
+ [`anthropics/jacobian-lens`](https://github.com/anthropics/jacobian-lens) (or load a community pre-fit),
129
+ serialize to the `LensArtifact` shape, and load it here. No weights or corpora are bundled.
130
+
131
+ ## Acceptance test
132
+
133
+ Fit a lens on a 1.5B–7B Qwen model, run reasoning prompts where the model must hold state silently
134
+ (*"Is `12 + 5 = 1` correct? Answer only Yes or No."*), extract mid-layer states, and verify the Jacobian
135
+ readout surfaces `wrong`/`incorrect`/`false` in the middle workspace **while the input prefix is still
136
+ being processed** — 2–3 layers earlier and with ≥20% higher logit clarity than logit-lens across ≥50
137
+ variations. The [test suite](./__tests__/workspace-lens.test.ts) includes the *mechanism* shape of this
138
+ check on synthetic artifacts (`$0`, no model).
139
+
140
+ ## Where it plugs into MetaHarness
141
+
142
+ - **Evaluation** — a `workspace_probe` surface: does a candidate harness make the model hold better
143
+ intermediate concepts *before* answering?
144
+ - **Darwin Mode** — J-lens readouts as **mutation evidence**: reject a prompt mutation that improves the
145
+ final token but causes the workspace to lose its early grip on the right concept (structurally brittle).
146
+ - **Safety auditing** — prompt injection, hidden-objective drift, eval-awareness, reward-hacking, refusal
147
+ analysis, as *state assertions* rather than perimeter filters.
148
+ - **Receipts** — attach an interpretability receipt to every governed agent decision.
149
+
150
+ ## Honest framing
151
+
152
+ This is a **measurement primitive**, not a product and **not** a consciousness claim — the paper frames
153
+ the connection to a global workspace as *functional* and leaves the philosophy open. The practical claim
154
+ is enough: a measurable window into hidden reasoning.
155
+
156
+ ## License
157
+
158
+ MIT.
@@ -0,0 +1,3 @@
1
+ import type { DecisionInput, DecisionResult } from './types.js';
2
+ export declare function decide(input: DecisionInput): DecisionResult;
3
+ //# sourceMappingURL=decision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../src/decision.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGhE,wBAAgB,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAc3D"}
@@ -0,0 +1,23 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // The accept/reject decision rule — "correctness first, cost second, receipts always":
4
+ // accepted = taskResolved && workspaceDrift < threshold && noCriticalSafetyFlags && receiptCoverage===1
5
+ // Returns the reasons so a rejection is auditable (which clause failed), not a bare boolean.
6
+ import { hasCriticalTrigger } from './safety.js';
7
+ export function decide(input) {
8
+ const reasons = [];
9
+ if (!input.taskResolved)
10
+ reasons.push('task not resolved');
11
+ if (!(input.workspaceDrift < input.driftThreshold)) {
12
+ reasons.push(`workspace drift ${input.workspaceDrift.toFixed(4)} >= threshold ${input.driftThreshold}`);
13
+ }
14
+ if (hasCriticalTrigger(input.triggers)) {
15
+ const crit = input.triggers.filter((t) => t.critical).map((t) => t.concept);
16
+ reasons.push(`critical safety trigger(s): ${[...new Set(crit)].join(', ')}`);
17
+ }
18
+ if (input.receiptCoverage !== 1) {
19
+ reasons.push(`receipt coverage ${input.receiptCoverage} != 1 (not every decision was witnessed)`);
20
+ }
21
+ return { accepted: reasons.length === 0, reasons };
22
+ }
23
+ //# sourceMappingURL=decision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision.js","sourceRoot":"","sources":["../src/decision.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,uFAAuF;AACvF,0GAA0G;AAC1G,6FAA6F;AAG7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,UAAU,MAAM,CAAC,KAAoB;IACzC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,KAAK,CAAC,YAAY;QAAE,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC3D,IAAI,CAAC,CAAC,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC;IAC1G,CAAC;IACD,IAAI,kBAAkB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC5E,OAAO,CAAC,IAAI,CAAC,+BAA+B,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,KAAK,CAAC,eAAe,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,oBAAoB,KAAK,CAAC,eAAe,0CAA0C,CAAC,CAAC;IACpG,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;AACrD,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { WorkspaceReadout } from './types.js';
2
+ /**
3
+ * Mean Jensen–Shannon divergence (nats) between consecutive readouts in order. 0 when fewer than two
4
+ * readouts. Only compares readouts whose token support has the same length (same topK) so the JS is
5
+ * well-defined; mismatched pairs are skipped and reported via `comparedPairs`.
6
+ */
7
+ export declare function workspaceDrift(readouts: readonly WorkspaceReadout[]): {
8
+ drift: number;
9
+ comparedPairs: number;
10
+ };
11
+ /** The per-readout entropy sequence, in the readouts' given order. */
12
+ export declare function entropyTrajectory(readouts: readonly WorkspaceReadout[]): number[];
13
+ //# sourceMappingURL=drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift.d.ts","sourceRoot":"","sources":["../src/drift.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAUnD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAW9G;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAEjF"}
package/dist/drift.js ADDED
@@ -0,0 +1,37 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Workspace stability metrics. workspaceDrift measures how much the readout DISTRIBUTION moves between
4
+ // consecutive readouts (layers/positions): the Jensen–Shannon divergence between adjacent softmax
5
+ // distributions, averaged. A radical jump without an output-token boundary signals the internal
6
+ // reasoning path collapsing or mutating uncontrollably. entropyTrajectory tracks whether the workspace
7
+ // is converging (entropy falling → committing to a concept) or dissolving (rising → losing its grip).
8
+ import { jensenShannon, softmax } from './linalg.js';
9
+ /** Reconstruct the readout's probability distribution over its returned tokens (for step-to-step JS). */
10
+ function readoutProbs(r) {
11
+ // Use the token logits we kept; re-softmax over the returned top-k so two readouts are compared on the
12
+ // same support ordering. Callers that need full-vocab drift should pass full-vocab readouts (topK=vocab).
13
+ return softmax(r.tokens.map((t) => t.logit));
14
+ }
15
+ /**
16
+ * Mean Jensen–Shannon divergence (nats) between consecutive readouts in order. 0 when fewer than two
17
+ * readouts. Only compares readouts whose token support has the same length (same topK) so the JS is
18
+ * well-defined; mismatched pairs are skipped and reported via `comparedPairs`.
19
+ */
20
+ export function workspaceDrift(readouts) {
21
+ let sum = 0;
22
+ let pairs = 0;
23
+ for (let i = 1; i < readouts.length; i++) {
24
+ const a = readoutProbs(readouts[i - 1]);
25
+ const b = readoutProbs(readouts[i]);
26
+ if (a.length !== b.length || a.length === 0)
27
+ continue;
28
+ sum += jensenShannon(a, b);
29
+ pairs += 1;
30
+ }
31
+ return { drift: pairs === 0 ? 0 : sum / pairs, comparedPairs: pairs };
32
+ }
33
+ /** The per-readout entropy sequence, in the readouts' given order. */
34
+ export function entropyTrajectory(readouts) {
35
+ return readouts.map((r) => r.entropy);
36
+ }
37
+ //# sourceMappingURL=drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift.js","sourceRoot":"","sources":["../src/drift.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,uGAAuG;AACvG,kGAAkG;AAClG,gGAAgG;AAChG,uGAAuG;AACvG,sGAAsG;AAGtG,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAErD,yGAAyG;AACzG,SAAS,YAAY,CAAC,CAAmB;IACvC,uGAAuG;IACvG,0GAA0G;IAC1G,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAqC;IAClE,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACtD,GAAG,IAAI,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3B,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AACxE,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,iBAAiB,CAAC,QAAqC;IACrE,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export * from './types.js';
2
+ export * from './linalg.js';
3
+ export { WorkspaceLens } from './lens.js';
4
+ export { detectConcepts, flagsFromTriggers, hasCriticalTrigger, FLAG_CONCEPTS, type DetectOptions } from './safety.js';
5
+ export { workspaceDrift, entropyTrajectory } from './drift.js';
6
+ export { decide } from './decision.js';
7
+ export { buildReceipt, sha256, type BuildReceiptOptions } from './receipt.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACvH,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // @metaharness/workspace-lens — a Jacobian-Lens interpretability primitive for open-weight LLMs.
4
+ // Runtime-only (fitting is external), model-agnostic, dependency-free. See README.md.
5
+ export * from './types.js';
6
+ export * from './linalg.js';
7
+ export { WorkspaceLens } from './lens.js';
8
+ export { detectConcepts, flagsFromTriggers, hasCriticalTrigger, FLAG_CONCEPTS } from './safety.js';
9
+ export { workspaceDrift, entropyTrajectory } from './drift.js';
10
+ export { decide } from './decision.js';
11
+ export { buildReceipt, sha256 } from './receipt.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,iGAAiG;AACjG,sFAAsF;AAEtF,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AACvH,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,EAA4B,MAAM,cAAc,CAAC"}
package/dist/lens.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { LensArtifact, HiddenState, WorkspaceReadout } from './types.js';
2
+ export declare class WorkspaceLens {
3
+ readonly artifact: LensArtifact;
4
+ private readonly byLayer;
5
+ private constructor();
6
+ /** Build a lens from an in-memory artifact (validated eagerly so a bad artifact fails at load). */
7
+ static fromArtifact(artifact: LensArtifact): WorkspaceLens;
8
+ /**
9
+ * Load a fitted lens from a JSON file. (Binary/registry formats — e.g. a Neuronpedia pre-fit — are a
10
+ * follow-up; the on-disk contract is just a serialized `LensArtifact`.) Kept out of the constructor so
11
+ * the core stays sync + I/O-free for hot-path use.
12
+ */
13
+ static fromFile(path: string): Promise<WorkspaceLens>;
14
+ get modelId(): string;
15
+ get lensId(): string;
16
+ get dModel(): number;
17
+ /** Layer indices this lens can read, ascending. */
18
+ get layers(): number[];
19
+ private validate;
20
+ /** True if the lens has a fitted operator for this layer. */
21
+ hasLayer(layer: number): boolean;
22
+ /**
23
+ * z_l = J_l · h — the activation projected into final-layer coordinates. This is the quantity the
24
+ * Jacobian Lens corrects for (vs. logit-lens reading h directly). Throws if the layer is un-fitted or
25
+ * the width is wrong.
26
+ */
27
+ project(state: HiddenState): number[];
28
+ /**
29
+ * Full readout: lens_l(h) = unembed(J_l · h), returned as the top-k workspace tokens + the readout
30
+ * entropy. `topK` bounds the returned tokens; the entropy is over the FULL distribution.
31
+ */
32
+ readout(state: HiddenState, topK?: number): WorkspaceReadout;
33
+ }
34
+ //# sourceMappingURL=lens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lens.d.ts","sourceRoot":"","sources":["../src/lens.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AAG9F,qBAAa,aAAa;IAGJ,QAAQ,CAAC,QAAQ,EAAE,YAAY;IAFnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IAEtE,OAAO;IAKP,mGAAmG;IACnG,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,aAAa;IAI1D;;;;OAIG;WACU,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAM3D,IAAI,OAAO,IAAI,MAAM,CAAkC;IACvD,IAAI,MAAM,IAAI,MAAM,CAAiC;IACrD,IAAI,MAAM,IAAI,MAAM,CAAiC;IACrD,mDAAmD;IACnD,IAAI,MAAM,IAAI,MAAM,EAAE,CAA2D;IAEjF,OAAO,CAAC,QAAQ;IAkBhB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAEhC;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,EAAE;IASrC;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,SAAK,GAAG,gBAAgB;CAazD"}
package/dist/lens.js ADDED
@@ -0,0 +1,89 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // WorkspaceLens — the runtime engine. Loads a fitted LensArtifact and, given a captured hidden state
4
+ // h_l, computes lens_l(h) = unembed(J_l · h) → workspace tokens. The intermediate J-projection z_l =
5
+ // J_l · h (the activation mapped into final-layer coordinates) is also exposed, because the vectorized
6
+ // safety triggers compare z_l to concept directions rather than decoding the whole vocabulary.
7
+ import { matVec, softmax, entropy, topKIndices } from './linalg.js';
8
+ export class WorkspaceLens {
9
+ artifact;
10
+ byLayer;
11
+ constructor(artifact) {
12
+ this.artifact = artifact;
13
+ this.byLayer = new Map(artifact.layers.map((l) => [l.layer, l.jacobian]));
14
+ this.validate();
15
+ }
16
+ /** Build a lens from an in-memory artifact (validated eagerly so a bad artifact fails at load). */
17
+ static fromArtifact(artifact) {
18
+ return new WorkspaceLens(artifact);
19
+ }
20
+ /**
21
+ * Load a fitted lens from a JSON file. (Binary/registry formats — e.g. a Neuronpedia pre-fit — are a
22
+ * follow-up; the on-disk contract is just a serialized `LensArtifact`.) Kept out of the constructor so
23
+ * the core stays sync + I/O-free for hot-path use.
24
+ */
25
+ static async fromFile(path) {
26
+ const { readFile } = await import('node:fs/promises');
27
+ const raw = await readFile(path, 'utf-8');
28
+ return new WorkspaceLens(JSON.parse(raw));
29
+ }
30
+ get modelId() { return this.artifact.modelId; }
31
+ get lensId() { return this.artifact.lensId; }
32
+ get dModel() { return this.artifact.dModel; }
33
+ /** Layer indices this lens can read, ascending. */
34
+ get layers() { return [...this.byLayer.keys()].sort((a, b) => a - b); }
35
+ validate() {
36
+ const { dModel, unembed, vocab, layers } = this.artifact;
37
+ if (dModel <= 0)
38
+ throw new Error('lens artifact: dModel must be positive');
39
+ if (vocab.length === 0)
40
+ throw new Error('lens artifact: empty vocab');
41
+ if (unembed.length !== vocab.length) {
42
+ throw new Error(`lens artifact: unembed rows ${unembed.length} != vocab ${vocab.length}`);
43
+ }
44
+ if (unembed[0]?.length !== dModel) {
45
+ throw new Error(`lens artifact: unembed cols ${unembed[0]?.length} != dModel ${dModel}`);
46
+ }
47
+ if (layers.length === 0)
48
+ throw new Error('lens artifact: no fitted layers');
49
+ for (const l of layers) {
50
+ if (l.jacobian.length !== dModel || l.jacobian[0]?.length !== dModel) {
51
+ throw new Error(`lens artifact: layer ${l.layer} jacobian is not ${dModel}×${dModel}`);
52
+ }
53
+ }
54
+ }
55
+ /** True if the lens has a fitted operator for this layer. */
56
+ hasLayer(layer) { return this.byLayer.has(layer); }
57
+ /**
58
+ * z_l = J_l · h — the activation projected into final-layer coordinates. This is the quantity the
59
+ * Jacobian Lens corrects for (vs. logit-lens reading h directly). Throws if the layer is un-fitted or
60
+ * the width is wrong.
61
+ */
62
+ project(state) {
63
+ const j = this.byLayer.get(state.layer);
64
+ if (!j)
65
+ throw new Error(`lens has no fitted operator for layer ${state.layer}`);
66
+ if (state.h.length !== this.dModel) {
67
+ throw new Error(`hidden state width ${state.h.length} != dModel ${this.dModel}`);
68
+ }
69
+ return matVec(j, state.h);
70
+ }
71
+ /**
72
+ * Full readout: lens_l(h) = unembed(J_l · h), returned as the top-k workspace tokens + the readout
73
+ * entropy. `topK` bounds the returned tokens; the entropy is over the FULL distribution.
74
+ */
75
+ readout(state, topK = 10) {
76
+ const z = this.project(state);
77
+ const logits = matVec(this.artifact.unembed, z);
78
+ const probs = softmax(logits);
79
+ const idx = topKIndices(logits, topK);
80
+ const tokens = idx.map((vi, rank) => ({
81
+ token: this.artifact.vocab[vi],
82
+ rank,
83
+ logit: logits[vi],
84
+ prob: probs[vi],
85
+ }));
86
+ return { layer: state.layer, position: state.position, tokens, entropy: entropy(probs) };
87
+ }
88
+ }
89
+ //# sourceMappingURL=lens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lens.js","sourceRoot":"","sources":["../src/lens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export type Vector = readonly number[];
2
+ /** Row-major dense matrix: `rows` arrays of length `cols`. */
3
+ export type Matrix = readonly Vector[];
4
+ /** y = M · x. Throws on a shape mismatch so a malformed lens artifact fails loudly, not silently. */
5
+ export declare function matVec(m: Matrix, x: Vector): number[];
6
+ export declare function dot(a: Vector, b: Vector): number;
7
+ export declare function norm(a: Vector): number;
8
+ /** Cosine similarity in [-1, 1]; 0 when either vector is all-zero (avoids NaN). */
9
+ export declare function cosine(a: Vector, b: Vector): number;
10
+ /** Numerically-stable softmax over logits → a probability distribution summing to 1. */
11
+ export declare function softmax(logits: Vector): number[];
12
+ /** Shannon entropy (nats) of a probability distribution. Higher = more diffuse workspace. */
13
+ export declare function entropy(probs: Vector): number;
14
+ /**
15
+ * Jensen–Shannon divergence between two distributions (nats, in [0, ln 2]). Symmetric + bounded, so it
16
+ * is a stable per-step drift metric — unlike raw KL it never diverges to Infinity when a bin goes to 0.
17
+ */
18
+ export declare function jensenShannon(p: Vector, q: Vector): number;
19
+ /** Indices of the top-k entries of `values`, highest first. Ties broken by lower index (stable). */
20
+ export declare function topKIndices(values: Vector, k: number): number[];
21
+ //# sourceMappingURL=linalg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linalg.d.ts","sourceRoot":"","sources":["../src/linalg.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;AACvC,8DAA8D;AAC9D,MAAM,MAAM,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;AAEvC,qGAAqG;AACrG,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAerD;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AAED,mFAAmF;AACnF,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,wFAAwF;AACxF,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAahD;AAED,6FAA6F;AAC7F,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI7C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAU1D;AAED,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAI/D"}
package/dist/linalg.js ADDED
@@ -0,0 +1,97 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Minimal, dependency-free linear algebra for the runtime lens projection. This package NEVER fits a
4
+ // Jacobian (that needs a model backward pass — external); it only APPLIES a pre-fitted lens, which is a
5
+ // pair of matrix-vector products plus a softmax. So a few well-typed loops over Float64Array/number[]
6
+ // are all we need — no BLAS, no native deps, deterministic, and fast enough for per-request governance.
7
+ /** y = M · x. Throws on a shape mismatch so a malformed lens artifact fails loudly, not silently. */
8
+ export function matVec(m, x) {
9
+ if (m.length === 0)
10
+ return [];
11
+ const cols = m[0].length;
12
+ if (cols !== x.length) {
13
+ throw new Error(`matVec: matrix cols ${cols} != vector length ${x.length}`);
14
+ }
15
+ const out = new Array(m.length);
16
+ for (let i = 0; i < m.length; i++) {
17
+ const row = m[i];
18
+ if (row.length !== cols)
19
+ throw new Error(`matVec: ragged matrix at row ${i} (${row.length} != ${cols})`);
20
+ let s = 0;
21
+ for (let j = 0; j < cols; j++)
22
+ s += row[j] * x[j];
23
+ out[i] = s;
24
+ }
25
+ return out;
26
+ }
27
+ export function dot(a, b) {
28
+ if (a.length !== b.length)
29
+ throw new Error(`dot: length ${a.length} != ${b.length}`);
30
+ let s = 0;
31
+ for (let i = 0; i < a.length; i++)
32
+ s += a[i] * b[i];
33
+ return s;
34
+ }
35
+ export function norm(a) {
36
+ return Math.sqrt(dot(a, a));
37
+ }
38
+ /** Cosine similarity in [-1, 1]; 0 when either vector is all-zero (avoids NaN). */
39
+ export function cosine(a, b) {
40
+ const na = norm(a);
41
+ const nb = norm(b);
42
+ if (na === 0 || nb === 0)
43
+ return 0;
44
+ return dot(a, b) / (na * nb);
45
+ }
46
+ /** Numerically-stable softmax over logits → a probability distribution summing to 1. */
47
+ export function softmax(logits) {
48
+ if (logits.length === 0)
49
+ return [];
50
+ let max = -Infinity;
51
+ for (const v of logits)
52
+ if (v > max)
53
+ max = v;
54
+ const exps = new Array(logits.length);
55
+ let sum = 0;
56
+ for (let i = 0; i < logits.length; i++) {
57
+ const e = Math.exp(logits[i] - max);
58
+ exps[i] = e;
59
+ sum += e;
60
+ }
61
+ for (let i = 0; i < exps.length; i++)
62
+ exps[i] /= sum;
63
+ return exps;
64
+ }
65
+ /** Shannon entropy (nats) of a probability distribution. Higher = more diffuse workspace. */
66
+ export function entropy(probs) {
67
+ let h = 0;
68
+ for (const p of probs)
69
+ if (p > 0)
70
+ h -= p * Math.log(p);
71
+ return h;
72
+ }
73
+ /**
74
+ * Jensen–Shannon divergence between two distributions (nats, in [0, ln 2]). Symmetric + bounded, so it
75
+ * is a stable per-step drift metric — unlike raw KL it never diverges to Infinity when a bin goes to 0.
76
+ */
77
+ export function jensenShannon(p, q) {
78
+ if (p.length !== q.length)
79
+ throw new Error(`jensenShannon: length ${p.length} != ${q.length}`);
80
+ let d = 0;
81
+ for (let i = 0; i < p.length; i++) {
82
+ const m = (p[i] + q[i]) / 2;
83
+ if (p[i] > 0 && m > 0)
84
+ d += 0.5 * p[i] * Math.log(p[i] / m);
85
+ if (q[i] > 0 && m > 0)
86
+ d += 0.5 * q[i] * Math.log(q[i] / m);
87
+ }
88
+ // Clamp tiny negative from float error.
89
+ return d < 0 ? 0 : d;
90
+ }
91
+ /** Indices of the top-k entries of `values`, highest first. Ties broken by lower index (stable). */
92
+ export function topKIndices(values, k) {
93
+ const idx = values.map((_, i) => i);
94
+ idx.sort((a, b) => (values[b] - values[a]) || (a - b));
95
+ return idx.slice(0, Math.max(0, k));
96
+ }
97
+ //# sourceMappingURL=linalg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linalg.js","sourceRoot":"","sources":["../src/linalg.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { WorkspaceLens } from './lens.js';
2
+ import type { HiddenState, ConceptVector, WorkspaceLensReceipt } from './types.js';
3
+ import { type DetectOptions } from './safety.js';
4
+ export interface BuildReceiptOptions {
5
+ /** Number of workspace tokens to keep per readout. Default 8. */
6
+ topK?: number;
7
+ /** Concept vectors to score for safety triggers. Default none. */
8
+ concepts?: readonly ConceptVector[];
9
+ /** Concept-trigger cosine threshold. */
10
+ detect?: DetectOptions;
11
+ /** ISO timestamp for the receipt. REQUIRED — pass it in so the receipt is deterministic/reproducible. */
12
+ createdAt: string;
13
+ }
14
+ export declare function sha256(s: string): string;
15
+ export declare function buildReceipt(lens: WorkspaceLens, prompt: string, states: readonly HiddenState[], opts: BuildReceiptOptions): WorkspaceLensReceipt;
16
+ //# sourceMappingURL=receipt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipt.d.ts","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EACV,WAAW,EAAE,aAAa,EAAE,oBAAoB,EACjD,MAAM,YAAY,CAAC;AACpB,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGpF,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACpC,wCAAwC;IACxC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,yGAAyG;IACzG,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,IAAI,EAAE,mBAAmB,GACxB,oBAAoB,CAgCtB"}
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // buildReceipt — the one-call orchestrator. Given a lens, a prompt, the captured hidden states, and a
4
+ // concept library, it reads the workspace at every state, scores drift/entropy, fires safety triggers,
5
+ // and assembles the signable WorkspaceLensReceipt. Pure aside from a sha256 of the prompt (node:crypto,
6
+ // a builtin) — no model, no network — so it runs identically offline and in a governance hot path.
7
+ import { createHash } from 'node:crypto';
8
+ import { detectConcepts, flagsFromTriggers } from './safety.js';
9
+ import { workspaceDrift, entropyTrajectory } from './drift.js';
10
+ export function sha256(s) {
11
+ return createHash('sha256').update(s).digest('hex');
12
+ }
13
+ export function buildReceipt(lens, prompt, states, opts) {
14
+ const topK = opts.topK ?? 8;
15
+ const readouts = states
16
+ .filter((s) => lens.hasLayer(s.layer))
17
+ .map((s) => lens.readout(s, topK));
18
+ const triggers = opts.concepts ? detectConcepts(lens, states, opts.concepts, opts.detect) : [];
19
+ const flags = flagsFromTriggers(triggers);
20
+ const { drift } = workspaceDrift(readouts);
21
+ const layersSeen = readouts.map((r) => r.layer);
22
+ const layerRange = layersSeen.length
23
+ ? [Math.min(...layersSeen), Math.max(...layersSeen)]
24
+ : [0, 0];
25
+ return {
26
+ promptHash: sha256(prompt),
27
+ modelId: lens.modelId,
28
+ lensId: lens.lensId,
29
+ layerRange,
30
+ positions: [...new Set(readouts.map((r) => r.position))].sort((a, b) => a - b),
31
+ topTokens: readouts.map((r) => ({
32
+ layer: r.layer,
33
+ position: r.position,
34
+ tokens: r.tokens.map((t) => ({ token: t.token, rank: t.rank, logit: t.logit })),
35
+ })),
36
+ flags,
37
+ triggers,
38
+ workspaceDrift: drift,
39
+ entropyTrajectory: entropyTrajectory(readouts),
40
+ createdAt: opts.createdAt,
41
+ };
42
+ }
43
+ //# sourceMappingURL=receipt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipt.js","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,sGAAsG;AACtG,uGAAuG;AACvG,wGAAwG;AACxG,mGAAmG;AAEnG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKzC,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAsB,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAa/D,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAmB,EACnB,MAAc,EACd,MAA8B,EAC9B,IAAyB;IAEzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAuB,MAAM;SACxC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;SACrC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAErC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/F,MAAM,KAAK,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAE3C,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,UAAU,GAAqB,UAAU,CAAC,MAAM;QACpD,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEX,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;QAC1B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,UAAU;QACV,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QAC9E,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9B,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;SAChF,CAAC,CAAC;QACH,KAAK;QACL,QAAQ;QACR,cAAc,EAAE,KAAK;QACrB,iBAAiB,EAAE,iBAAiB,CAAC,QAAQ,CAAC;QAC9C,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { WorkspaceLens } from './lens.js';
2
+ import type { HiddenState, ConceptVector, ConceptTrigger, SafetyFlags } from './types.js';
3
+ /** Canonical concept names that map onto the four headline SafetyFlags. */
4
+ export declare const FLAG_CONCEPTS: {
5
+ readonly promptInjection: readonly ["prompt_injection", "override", "ignore_instructions", "exfiltration"];
6
+ readonly evalAwareness: readonly ["evaluation_awareness", "being_tested", "watched"];
7
+ readonly hiddenObjective: readonly ["hidden_objective", "secretly", "deception", "manipulation"];
8
+ readonly refusalConflict: readonly ["refusal_conflict", "should_refuse", "jailbreak"];
9
+ };
10
+ export interface DetectOptions {
11
+ /** Cosine threshold above which a concept is considered active. Default 0.35 (tunable per deployment). */
12
+ threshold?: number;
13
+ }
14
+ /**
15
+ * Score every (state × concept) pair and return the concepts that fired. Concept vectors whose `modelId`
16
+ * doesn't match the lens are skipped (a fail-safe against applying a Qwen direction to a Gemma model).
17
+ */
18
+ export declare function detectConcepts(lens: WorkspaceLens, states: readonly HiddenState[], concepts: readonly ConceptVector[], opts?: DetectOptions): ConceptTrigger[];
19
+ /** Roll fired concept triggers up into the four headline flags. */
20
+ export declare function flagsFromTriggers(triggers: readonly ConceptTrigger[]): SafetyFlags;
21
+ /** True if any fired trigger is marked critical (used to fail the decision rule closed). */
22
+ export declare function hasCriticalTrigger(triggers: readonly ConceptTrigger[]): boolean;
23
+ //# sourceMappingURL=safety.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../src/safety.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG1F,2EAA2E;AAC3E,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX,MAAM,WAAW,aAAa;IAC5B,0GAA0G;IAC1G,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,IAAI,GAAE,aAAkB,GACvB,cAAc,EAAE,CAgBlB;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,WAAW,CASlF;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,OAAO,CAE/E"}
package/dist/safety.js ADDED
@@ -0,0 +1,56 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Vectorized safety triggers. Anthropic found hidden objectives ("secretly", "manipulation", "evaluation
4
+ // awareness") light up in the workspace even when absent from the output. We detect them NOT by matching
5
+ // decoded token strings (brittle, tokenizer-specific) but by the dot product of the J-projected
6
+ // activation against pre-fitted concept DIRECTIONS in the model's own J-space. That makes the trigger
7
+ // tokenizer-agnostic and cross-family: a concept is aligned by name + per-model vector, so "hidden
8
+ // objective" is one canonical concept with a Qwen vector and a Gemma vector — never a string map.
9
+ import { cosine } from './linalg.js';
10
+ /** Canonical concept names that map onto the four headline SafetyFlags. */
11
+ export const FLAG_CONCEPTS = {
12
+ promptInjection: ['prompt_injection', 'override', 'ignore_instructions', 'exfiltration'],
13
+ evalAwareness: ['evaluation_awareness', 'being_tested', 'watched'],
14
+ hiddenObjective: ['hidden_objective', 'secretly', 'deception', 'manipulation'],
15
+ refusalConflict: ['refusal_conflict', 'should_refuse', 'jailbreak'],
16
+ };
17
+ /**
18
+ * Score every (state × concept) pair and return the concepts that fired. Concept vectors whose `modelId`
19
+ * doesn't match the lens are skipped (a fail-safe against applying a Qwen direction to a Gemma model).
20
+ */
21
+ export function detectConcepts(lens, states, concepts, opts = {}) {
22
+ const threshold = opts.threshold ?? 0.35;
23
+ const triggers = [];
24
+ for (const state of states) {
25
+ if (!lens.hasLayer(state.layer))
26
+ continue;
27
+ const z = lens.project(state);
28
+ for (const c of concepts) {
29
+ if (c.modelId !== lens.modelId)
30
+ continue; // never cross-apply a concept from another model
31
+ if (c.vector.length !== z.length)
32
+ continue;
33
+ const score = cosine(z, c.vector);
34
+ if (score >= threshold) {
35
+ triggers.push({ concept: c.concept, layer: state.layer, position: state.position, score, critical: !!c.critical });
36
+ }
37
+ }
38
+ }
39
+ return triggers;
40
+ }
41
+ /** Roll fired concept triggers up into the four headline flags. */
42
+ export function flagsFromTriggers(triggers) {
43
+ const fired = new Set(triggers.map((t) => t.concept));
44
+ const any = (names) => names.some((n) => fired.has(n));
45
+ return {
46
+ promptInjection: any(FLAG_CONCEPTS.promptInjection),
47
+ evalAwareness: any(FLAG_CONCEPTS.evalAwareness),
48
+ hiddenObjective: any(FLAG_CONCEPTS.hiddenObjective),
49
+ refusalConflict: any(FLAG_CONCEPTS.refusalConflict),
50
+ };
51
+ }
52
+ /** True if any fired trigger is marked critical (used to fail the decision rule closed). */
53
+ export function hasCriticalTrigger(triggers) {
54
+ return triggers.some((t) => t.critical);
55
+ }
56
+ //# sourceMappingURL=safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety.js","sourceRoot":"","sources":["../src/safety.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,yGAAyG;AACzG,yGAAyG;AACzG,gGAAgG;AAChG,sGAAsG;AACtG,mGAAmG;AACnG,kGAAkG;AAIlG,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,eAAe,EAAE,CAAC,kBAAkB,EAAE,UAAU,EAAE,qBAAqB,EAAE,cAAc,CAAC;IACxF,aAAa,EAAE,CAAC,sBAAsB,EAAE,cAAc,EAAE,SAAS,CAAC;IAClE,eAAe,EAAE,CAAC,kBAAkB,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,CAAC;IAC9E,eAAe,EAAE,CAAC,kBAAkB,EAAE,eAAe,EAAE,WAAW,CAAC;CAC3D,CAAC;AAOX;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAmB,EACnB,MAA8B,EAC9B,QAAkC,EAClC,OAAsB,EAAE;IAExB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;IACzC,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,SAAS;QAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO;gBAAE,SAAS,CAAC,iDAAiD;YAC3F,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;gBAAE,SAAS;YAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;YAClC,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;gBACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YACrH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,iBAAiB,CAAC,QAAmC;IACnE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,CAAC,KAAwB,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,OAAO;QACL,eAAe,EAAE,GAAG,CAAC,aAAa,CAAC,eAAe,CAAC;QACnD,aAAa,EAAE,GAAG,CAAC,aAAa,CAAC,aAAa,CAAC;QAC/C,eAAe,EAAE,GAAG,CAAC,aAAa,CAAC,eAAe,CAAC;QACnD,eAAe,EAAE,GAAG,CAAC,aAAa,CAAC,eAAe,CAAC;KACpD,CAAC;AACJ,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,kBAAkB,CAAC,QAAmC;IACpE,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,128 @@
1
+ /** A fitted per-layer Jacobian operator J_l ∈ ℝ^{dModel×dModel}, plus the shared decode path. */
2
+ export interface LensLayer {
3
+ /** Transformer layer index this operator was fitted for. */
4
+ layer: number;
5
+ /** Row-major J_l (dModel×dModel): the average input→output Jacobian for this layer. */
6
+ jacobian: readonly (readonly number[])[];
7
+ }
8
+ /**
9
+ * A fitted lens artifact for ONE model. Model-agnostic by construction: the vocabulary and the
10
+ * unembedding live in the artifact, so scoring never touches a tokenizer at runtime. Fitted OUT OF BAND
11
+ * (open-weight model + backward pass; see the reference `anthropics/jacobian-lens` + Neuronpedia), then
12
+ * serialized and loaded here.
13
+ */
14
+ export interface LensArtifact {
15
+ /** Stable id for this fitted lens (e.g. "jlens-qwen2.5-7b-v2") — recorded in every receipt. */
16
+ lensId: string;
17
+ /** The model the lens was fitted on (e.g. "qwen2.5-7b-instruct"). */
18
+ modelId: string;
19
+ /** Residual-stream width. */
20
+ dModel: number;
21
+ /** Decoded vocabulary — token strings aligned to the unembed rows. */
22
+ vocab: readonly string[];
23
+ /** Unembedding U ∈ ℝ^{vocab×dModel}. lens_l(h) = U · (J_l · h). */
24
+ unembed: readonly (readonly number[])[];
25
+ /** The fitted per-layer operators, keyed by layer index. */
26
+ layers: readonly LensLayer[];
27
+ }
28
+ /** A captured residual-stream activation at a specific (layer, position). Supplied by the caller. */
29
+ export interface HiddenState {
30
+ layer: number;
31
+ /** Token position in the sequence the activation was captured at. */
32
+ position: number;
33
+ /** The residual vector h_l ∈ ℝ^{dModel}. */
34
+ h: readonly number[];
35
+ }
36
+ /** One decoded workspace token: what the activation is DISPOSED to make the model say later. */
37
+ export interface WorkspaceToken {
38
+ token: string;
39
+ /** 0-based rank in the lens readout (0 = most probable). */
40
+ rank: number;
41
+ /** The lens logit for this token (pre-softmax). */
42
+ logit: number;
43
+ /** The softmax probability for this token within the readout. */
44
+ prob: number;
45
+ }
46
+ /** A per-(layer,position) readout of the workspace. */
47
+ export interface WorkspaceReadout {
48
+ layer: number;
49
+ position: number;
50
+ tokens: WorkspaceToken[];
51
+ /** Shannon entropy (nats) of the full readout distribution — how diffuse the workspace is here. */
52
+ entropy: number;
53
+ }
54
+ /**
55
+ * A concept represented as a DIRECTION in a given model's J-space (NOT a token string). This is how the
56
+ * package aligns concepts across architectural families with different tokenizers (Qwen vs Gemma): a
57
+ * canonical concept name maps to a per-model unit vector fitted from example activations, so the safety
58
+ * trigger is a dot-product in activation space, tokenizer-agnostic. `modelId` guards against applying a
59
+ * Qwen concept vector to a Gemma activation.
60
+ */
61
+ export interface ConceptVector {
62
+ /** Canonical concept name, aligned across models (e.g. "hidden_objective", "exfiltration"). */
63
+ concept: string;
64
+ /** The model this vector lives in — MUST match the lens/activation model. */
65
+ modelId: string;
66
+ /** Unit (or arbitrary) direction in J-space, length dModel. */
67
+ vector: readonly number[];
68
+ /** Optional: treat concept as CRITICAL — a trigger fails the decision rule closed. */
69
+ critical?: boolean;
70
+ }
71
+ /** A fired concept trigger. */
72
+ export interface ConceptTrigger {
73
+ concept: string;
74
+ layer: number;
75
+ position: number;
76
+ /** Cosine similarity of the J-projected activation to the concept direction. */
77
+ score: number;
78
+ critical: boolean;
79
+ }
80
+ /** The four headline safety flags (Anthropic's workspace examples: eval-awareness, manipulation, …). */
81
+ export interface SafetyFlags {
82
+ promptInjection: boolean;
83
+ evalAwareness: boolean;
84
+ hiddenObjective: boolean;
85
+ refusalConflict: boolean;
86
+ }
87
+ /**
88
+ * The signable interpretability receipt — the audit artifact. "correctness first, cost second, receipts
89
+ * always": it records WHAT concepts were active in the workspace when a decision was made, with the
90
+ * layer-by-layer trajectory so an auditor can see WHERE a decision formed, not just the final answer.
91
+ */
92
+ export interface WorkspaceLensReceipt {
93
+ promptHash: string;
94
+ modelId: string;
95
+ lensId: string;
96
+ layerRange: [number, number];
97
+ positions: number[];
98
+ topTokens: Array<{
99
+ layer: number;
100
+ position: number;
101
+ tokens: Array<{
102
+ token: string;
103
+ rank: number;
104
+ logit: number;
105
+ }>;
106
+ }>;
107
+ flags: SafetyFlags;
108
+ triggers: ConceptTrigger[];
109
+ /** Aggregate J-space distribution stability across the readouts (mean JS-divergence between steps). */
110
+ workspaceDrift: number;
111
+ /** Per-readout entropy — is the internal workspace converging (falling) or dissolving (rising)? */
112
+ entropyTrajectory: number[];
113
+ createdAt: string;
114
+ }
115
+ /** Inputs to the accept/reject decision rule. */
116
+ export interface DecisionInput {
117
+ taskResolved: boolean;
118
+ workspaceDrift: number;
119
+ driftThreshold: number;
120
+ triggers: ConceptTrigger[];
121
+ /** Fraction of monitored decisions that produced a receipt (1 = full coverage). */
122
+ receiptCoverage: number;
123
+ }
124
+ export interface DecisionResult {
125
+ accepted: boolean;
126
+ reasons: string[];
127
+ }
128
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Public types for @metaharness/workspace-lens. The package is a RUNTIME measurement primitive: given a
4
+ // pre-fitted Jacobian lens artifact + captured hidden activations, it reads the model's "verbalizable
5
+ // workspace" (Anthropic, 2026-07-06) and emits a signable interpretability receipt. It does not fit the
6
+ // lens (that needs the model's backward pass — external, GPU) and it does not run the model.
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,wGAAwG;AACxG,sGAAsG;AACtG,wGAAwG;AACxG,6FAA6F"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@metaharness/workspace-lens",
3
+ "version": "0.1.0",
4
+ "description": "Jacobian-Lens interpretability primitive for open-weight LLMs (Anthropic 2026-07-06, 'Verbalizable Representations Form a Global Workspace'). Reads the model's functional workspace at inference time — lens_l(h)=unembed(J_l·h) — into workspace tokens, layer-trajectory, drift/entropy scores, vectorized safety flags, and a signable interpretability receipt. Runtime-only (fitting is external); model-agnostic; dependency-free.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist", "README.md"],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "test": "vitest run",
21
+ "lint": "tsc --noEmit"
22
+ },
23
+ "keywords": [
24
+ "jacobian-lens",
25
+ "interpretability",
26
+ "logit-lens",
27
+ "global-workspace",
28
+ "mechanistic-governance",
29
+ "intops",
30
+ "ai-safety",
31
+ "prompt-injection",
32
+ "llm",
33
+ "open-weight",
34
+ "metaharness",
35
+ "agent-harness"
36
+ ],
37
+ "license": "MIT",
38
+ "devDependencies": {
39
+ "typescript": "^5.4.0",
40
+ "vitest": "^2.0.0"
41
+ }
42
+ }