lockstep-agents 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 +58 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +374 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# lockstep-agents (TypeScript)
|
|
2
|
+
|
|
3
|
+
Coordination for multi-agent shared state, **native in TypeScript** — no Python runtime. Pure logic,
|
|
4
|
+
zero dependencies. The same engine as the Python package: prove a parallel fan-out converges, price
|
|
5
|
+
each agent's *intent* in bits, capture footprints with zero declaration, adjudicate an incident, and
|
|
6
|
+
enforce exactly-once.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
node --experimental-strip-types test.ts # run the parity tests (Node >= 22.6)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## The four pieces
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { Orchestrator, marginalBits, adjudicate, ObservedState, Coordinator } from "lockstep-agents";
|
|
16
|
+
|
|
17
|
+
// 1. PROVE — declare reads/writes, prove the parallel result is order-independent
|
|
18
|
+
const plan = new Orchestrator()
|
|
19
|
+
.agent("a", { writes: { result: "from-a" } })
|
|
20
|
+
.agent("b", { writes: { result: "from-b" } }) // a & b race on `result`
|
|
21
|
+
.analyze();
|
|
22
|
+
plan.confluent; // false
|
|
23
|
+
plan.capacityBits; // 1 (blast radius, lock-aware)
|
|
24
|
+
plan.collisions; // [{ resource: "result", agents: ["a","b"], values: [...] }]
|
|
25
|
+
plan.minimalCoordination; // the fewest orderings that make it deterministic
|
|
26
|
+
|
|
27
|
+
// 2. PRICE — the cost of an agent's intent, before it runs (0 = free / value-agreeing)
|
|
28
|
+
marginalBits(plan2orch, "a");
|
|
29
|
+
|
|
30
|
+
// 3. OBSERVE — footprints for free; the agent just uses the view like an object
|
|
31
|
+
const state = new ObservedState({ plan: "v1" });
|
|
32
|
+
state.run("researcher", (s) => { s.findings = summarize(s.plan); }); // reads plan, writes findings
|
|
33
|
+
state.analyze().confluent;
|
|
34
|
+
|
|
35
|
+
// 4. ENFORCE — exactly-once (async, promise-dedup) + atomic per-resource serialization
|
|
36
|
+
const coord = new Coordinator();
|
|
37
|
+
await coord.run("card:user_42", () => charge(user42), { mode: "exec", key: "order_42" }); // fires once
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Adjudicate an incident
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
adjudicate(orch, observedState).verdict;
|
|
44
|
+
// REACHABLE -> a legal interleaving could produce it (pass { withPin:true } for the fix)
|
|
45
|
+
// JOINT_UNREACHABLE -> each field legal, the combination impossible (the correlated-lock class)
|
|
46
|
+
// OUT_OF_MODEL -> a value/resource no declaration explains
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Notes
|
|
50
|
+
|
|
51
|
+
- Runs today via Node's type-stripping (`--experimental-strip-types`, default-on in Node 23+). To
|
|
52
|
+
publish to npm, compile `src/` with your bundler of choice (tsup / esbuild / tsc) — the source is
|
|
53
|
+
standard, erasable TypeScript.
|
|
54
|
+
- This port covers the static engine, the reachable-set verbs, observe-mode, and the in-process
|
|
55
|
+
exactly-once coordinator. For a *durable* cross-process/host ledger, back the `Coordinator` with a
|
|
56
|
+
Redis/DB `Ledger` (same interface) or call the Python service over MCP.
|
|
57
|
+
- Governs **structure** (does the merge converge, does the side effect happen once) — not the agents'
|
|
58
|
+
semantics. Sound given the footprints, declared or observed.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
type Value = unknown;
|
|
2
|
+
interface AgentSpec {
|
|
3
|
+
reads: string[];
|
|
4
|
+
writes: Record<string, Value>;
|
|
5
|
+
after: string[];
|
|
6
|
+
}
|
|
7
|
+
interface Collision {
|
|
8
|
+
resource: string;
|
|
9
|
+
agents: string[];
|
|
10
|
+
values: Value[];
|
|
11
|
+
}
|
|
12
|
+
interface Plan {
|
|
13
|
+
confluent: boolean;
|
|
14
|
+
reachableStates: number;
|
|
15
|
+
capacityBits: number;
|
|
16
|
+
collisions: Collision[];
|
|
17
|
+
minimalCoordination: Array<[string, string]>;
|
|
18
|
+
waves: Record<number, string[]>;
|
|
19
|
+
work: number;
|
|
20
|
+
span: number;
|
|
21
|
+
width: number;
|
|
22
|
+
speedup: number;
|
|
23
|
+
}
|
|
24
|
+
declare function canon(x: Value): string;
|
|
25
|
+
declare function sameVal(a: Value, b: Value): boolean;
|
|
26
|
+
declare function product<T>(arrays: T[][]): T[][];
|
|
27
|
+
declare function acyclic(nodes: Set<string>, edges: Array<[string, string]>): boolean;
|
|
28
|
+
declare class Orchestrator {
|
|
29
|
+
agents: Map<string, AgentSpec>;
|
|
30
|
+
agent(name: string, spec?: {
|
|
31
|
+
reads?: string[];
|
|
32
|
+
writes?: Record<string, Value>;
|
|
33
|
+
after?: string[];
|
|
34
|
+
}): this;
|
|
35
|
+
writers(): Map<string, string[]>;
|
|
36
|
+
rawEdges(extra?: Array<[string, string]>): Array<[string, string]>;
|
|
37
|
+
variety(extra?: Array<[string, string]>): {
|
|
38
|
+
variety: Array<Record<string, string>>;
|
|
39
|
+
walls: Map<string, string[]>;
|
|
40
|
+
};
|
|
41
|
+
reachable(extra?: Array<[string, string]>): {
|
|
42
|
+
states: Set<string>;
|
|
43
|
+
walls: Map<string, string[]>;
|
|
44
|
+
};
|
|
45
|
+
minimalCoordination(): Array<[string, string]>;
|
|
46
|
+
analyze(): Plan;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface Footprint {
|
|
50
|
+
name: string;
|
|
51
|
+
reads?: string[];
|
|
52
|
+
writes?: Record<string, Value>;
|
|
53
|
+
after?: string[];
|
|
54
|
+
}
|
|
55
|
+
declare function fromFootprints(fps: Footprint[]): Orchestrator;
|
|
56
|
+
/** Bits of order-nondeterminism the joint outcome GAINS from this agent's writes. Zero on
|
|
57
|
+
* value-agreement; provably >= 0 in the writes-only regime (the default). */
|
|
58
|
+
declare function marginalBits(orch: Orchestrator, agent: string, writesOnlyMode?: boolean): number;
|
|
59
|
+
/** { agent: marginalBits } -- the admission-control prioritizer. */
|
|
60
|
+
declare function contentionPrices(orch: Orchestrator, writesOnlyMode?: boolean): Record<string, number>;
|
|
61
|
+
type Verdict = "REACHABLE" | "JOINT_UNREACHABLE" | "OUT_OF_MODEL" | "INCOMPLETE";
|
|
62
|
+
interface Adjudication {
|
|
63
|
+
verdict: Verdict;
|
|
64
|
+
reason: string;
|
|
65
|
+
witness?: {
|
|
66
|
+
resource: string;
|
|
67
|
+
value: Value;
|
|
68
|
+
declaredImage?: Value[];
|
|
69
|
+
};
|
|
70
|
+
preventingPin?: Array<[string, string]>;
|
|
71
|
+
missing?: string[];
|
|
72
|
+
observedProjection?: Array<[string, Value]>;
|
|
73
|
+
}
|
|
74
|
+
/** Is `observed` consistent with the declared concurrency model? A direct realizability check --
|
|
75
|
+
* no full enumeration, no fix search -- so it stays cheap even when the reachable set is huge. */
|
|
76
|
+
declare function adjudicate(orch: Orchestrator, observed: Record<string, Value>, withPin?: boolean): Adjudication;
|
|
77
|
+
|
|
78
|
+
declare class ObservedState {
|
|
79
|
+
private values;
|
|
80
|
+
private reads;
|
|
81
|
+
private writes;
|
|
82
|
+
constructor(initial?: Record<string, unknown>);
|
|
83
|
+
/** A recording handle for `agent` -- use it like a normal object: `s.plan`, `s.findings = x`. */
|
|
84
|
+
view(agent: string): Record<string, any>;
|
|
85
|
+
/** Convenience: run fn(view, ...args) with `agent`'s recording view. */
|
|
86
|
+
run<T>(agent: string, fn: (view: Record<string, any>, ...args: any[]) => T, ...args: any[]): T;
|
|
87
|
+
/** The OBSERVED footprints: [{ name, reads, writes }] per agent (values carried for value-agreement). */
|
|
88
|
+
footprints(): Footprint[];
|
|
89
|
+
orchestrator(): Orchestrator;
|
|
90
|
+
analyze(): Plan;
|
|
91
|
+
get state(): Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type Mode = "read" | "write" | "append" | "exec";
|
|
95
|
+
interface Ledger {
|
|
96
|
+
get(key: string): unknown | undefined;
|
|
97
|
+
has(key: string): boolean;
|
|
98
|
+
set(key: string, value: unknown): void;
|
|
99
|
+
}
|
|
100
|
+
declare class Coordinator {
|
|
101
|
+
private ledger;
|
|
102
|
+
private inflight;
|
|
103
|
+
private chains;
|
|
104
|
+
constructor(ledger?: Ledger);
|
|
105
|
+
/** Run fn() under coordination for `resource`. With `key`: exactly-once (repeated/concurrent calls
|
|
106
|
+
* with the same key run fn once and all receive that one result). Without `key`: serialized per
|
|
107
|
+
* resource (no interleaving with another op on the same resource) -- put a read-modify-write in
|
|
108
|
+
* fn for an atomic update. mode is advisory metadata. */
|
|
109
|
+
run<T>(resource: string, fn: () => T | Promise<T>, opts?: {
|
|
110
|
+
mode?: Mode;
|
|
111
|
+
key?: string;
|
|
112
|
+
}): Promise<T>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { type Adjudication, type AgentSpec, type Collision, Coordinator, type Footprint, type Ledger, type Mode, ObservedState, Orchestrator, type Plan, type Value, type Verdict, acyclic, adjudicate, canon, contentionPrices, fromFootprints, marginalBits, product, sameVal };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// src/orchestrator.ts
|
|
2
|
+
function canon(x) {
|
|
3
|
+
return typeof x + ":" + JSON.stringify(x ?? null);
|
|
4
|
+
}
|
|
5
|
+
function sameVal(a, b) {
|
|
6
|
+
return canon(a) === canon(b);
|
|
7
|
+
}
|
|
8
|
+
function product(arrays) {
|
|
9
|
+
return arrays.reduce((acc, arr) => acc.flatMap((c) => arr.map((x) => [...c, x])), [[]]);
|
|
10
|
+
}
|
|
11
|
+
function acyclic(nodes, edges) {
|
|
12
|
+
const adj = /* @__PURE__ */ new Map();
|
|
13
|
+
for (const [a, b] of edges) {
|
|
14
|
+
if (!adj.has(a)) adj.set(a, /* @__PURE__ */ new Set());
|
|
15
|
+
adj.get(a).add(b);
|
|
16
|
+
}
|
|
17
|
+
const color = /* @__PURE__ */ new Map();
|
|
18
|
+
const dfs = (u) => {
|
|
19
|
+
color.set(u, 1);
|
|
20
|
+
for (const v of adj.get(u) ?? []) {
|
|
21
|
+
const c = color.get(v) ?? 0;
|
|
22
|
+
if (c === 1) return false;
|
|
23
|
+
if (c === 0 && !dfs(v)) return false;
|
|
24
|
+
}
|
|
25
|
+
color.set(u, 2);
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
for (const n of nodes) if ((color.get(n) ?? 0) === 0 && !dfs(n)) return false;
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
function waves(nodes, edges) {
|
|
32
|
+
const succ = /* @__PURE__ */ new Map();
|
|
33
|
+
const indeg = /* @__PURE__ */ new Map();
|
|
34
|
+
for (const n of nodes) indeg.set(n, 0);
|
|
35
|
+
for (const [a, b] of edges) {
|
|
36
|
+
if (!succ.has(a)) succ.set(a, []);
|
|
37
|
+
succ.get(a).push(b);
|
|
38
|
+
indeg.set(b, (indeg.get(b) ?? 0) + 1);
|
|
39
|
+
}
|
|
40
|
+
const level = /* @__PURE__ */ new Map();
|
|
41
|
+
const q = [];
|
|
42
|
+
for (const n of nodes) if (indeg.get(n) === 0) {
|
|
43
|
+
level.set(n, 0);
|
|
44
|
+
q.push(n);
|
|
45
|
+
}
|
|
46
|
+
const ind = new Map(indeg);
|
|
47
|
+
while (q.length) {
|
|
48
|
+
const u = q.shift();
|
|
49
|
+
for (const v of succ.get(u) ?? []) {
|
|
50
|
+
level.set(v, Math.max(level.get(v) ?? 0, (level.get(u) ?? 0) + 1));
|
|
51
|
+
ind.set(v, ind.get(v) - 1);
|
|
52
|
+
if (ind.get(v) === 0) q.push(v);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const bywave = /* @__PURE__ */ new Map();
|
|
56
|
+
for (const n of nodes) {
|
|
57
|
+
const l = level.get(n) ?? 0;
|
|
58
|
+
if (!bywave.has(l)) bywave.set(l, []);
|
|
59
|
+
bywave.get(l).push(n);
|
|
60
|
+
}
|
|
61
|
+
return bywave;
|
|
62
|
+
}
|
|
63
|
+
var Orchestrator = class {
|
|
64
|
+
agents = /* @__PURE__ */ new Map();
|
|
65
|
+
agent(name, spec = {}) {
|
|
66
|
+
if (this.agents.has(name)) throw new Error("duplicate agent: " + name);
|
|
67
|
+
this.agents.set(name, { reads: spec.reads ?? [], writes: spec.writes ?? {}, after: spec.after ?? [] });
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
writers() {
|
|
71
|
+
const wr = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const [n, a] of this.agents)
|
|
73
|
+
for (const res of Object.keys(a.writes)) {
|
|
74
|
+
if (!wr.has(res)) wr.set(res, []);
|
|
75
|
+
wr.get(res).push(n);
|
|
76
|
+
}
|
|
77
|
+
for (const [res, ws] of wr) wr.set(res, ws.slice().sort());
|
|
78
|
+
return wr;
|
|
79
|
+
}
|
|
80
|
+
rawEdges(extra = []) {
|
|
81
|
+
const wr = this.writers();
|
|
82
|
+
const edges = /* @__PURE__ */ new Set();
|
|
83
|
+
const add = (a, b) => edges.add(a + "\0" + b);
|
|
84
|
+
for (const [a, b] of extra) add(a, b);
|
|
85
|
+
for (const [n, a] of this.agents) {
|
|
86
|
+
for (const res of a.reads) for (const w of wr.get(res) ?? []) if (w !== n) add(w, n);
|
|
87
|
+
for (const p of a.after) if (this.agents.has(p)) add(p, n);
|
|
88
|
+
}
|
|
89
|
+
return [...edges].map((s) => s.split("\0"));
|
|
90
|
+
}
|
|
91
|
+
variety(extra = []) {
|
|
92
|
+
const wr = this.writers();
|
|
93
|
+
const walls = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const [res, ws] of wr) if (ws.length >= 2) walls.set(res, ws);
|
|
95
|
+
if (walls.size === 0) return { variety: [{}], walls };
|
|
96
|
+
const names = [...walls.keys()].sort();
|
|
97
|
+
const raw = this.rawEdges(extra);
|
|
98
|
+
const wjobs = /* @__PURE__ */ new Set();
|
|
99
|
+
for (const ws of walls.values()) for (const w of ws) wjobs.add(w);
|
|
100
|
+
const rawW = raw.filter(([a, b]) => wjobs.has(a) && wjobs.has(b));
|
|
101
|
+
const out = [];
|
|
102
|
+
for (const combo of product(names.map((r) => walls.get(r)))) {
|
|
103
|
+
const win = {};
|
|
104
|
+
names.forEach((r, i) => win[r] = combo[i]);
|
|
105
|
+
const edges = [...rawW];
|
|
106
|
+
for (const r of names) for (const x of walls.get(r)) if (x !== win[r]) edges.push([x, win[r]]);
|
|
107
|
+
if (acyclic(wjobs, edges)) out.push(win);
|
|
108
|
+
}
|
|
109
|
+
return { variety: out, walls };
|
|
110
|
+
}
|
|
111
|
+
reachable(extra = []) {
|
|
112
|
+
const { variety, walls } = this.variety(extra);
|
|
113
|
+
if (walls.size === 0) return { states: /* @__PURE__ */ new Set(["[]"]), walls };
|
|
114
|
+
const names = [...walls.keys()].sort();
|
|
115
|
+
const states = /* @__PURE__ */ new Set();
|
|
116
|
+
for (const win of variety) states.add(JSON.stringify(names.map((r) => [r, canon(this.agents.get(win[r]).writes[r])])));
|
|
117
|
+
return { states, walls };
|
|
118
|
+
}
|
|
119
|
+
minimalCoordination() {
|
|
120
|
+
const extra = [];
|
|
121
|
+
for (; ; ) {
|
|
122
|
+
const { states, walls } = this.reachable(extra);
|
|
123
|
+
if (states.size <= 1) break;
|
|
124
|
+
let best = null;
|
|
125
|
+
for (const [, ws] of walls)
|
|
126
|
+
for (const a of ws)
|
|
127
|
+
for (const b of ws)
|
|
128
|
+
if (a !== b) {
|
|
129
|
+
const cand = [...extra, [a, b]];
|
|
130
|
+
if (!acyclic(new Set(this.agents.keys()), this.rawEdges(cand))) continue;
|
|
131
|
+
const n = this.reachable(cand).states.size;
|
|
132
|
+
if (best === null || n < best.n) best = { n, pin: [a, b] };
|
|
133
|
+
}
|
|
134
|
+
if (best === null || extra.some(([a, b]) => a === best.pin[0] && b === best.pin[1])) break;
|
|
135
|
+
extra.push(best.pin);
|
|
136
|
+
}
|
|
137
|
+
return extra;
|
|
138
|
+
}
|
|
139
|
+
analyze() {
|
|
140
|
+
const { variety, walls } = this.variety();
|
|
141
|
+
const names = [...walls.keys()].sort();
|
|
142
|
+
const stateKeys = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const win of variety) stateKeys.add(JSON.stringify(names.map((r) => canon(this.agents.get(win[r]).writes[r]))));
|
|
144
|
+
const reachableStates = walls.size === 0 ? 1 : stateKeys.size;
|
|
145
|
+
const collisions = [];
|
|
146
|
+
for (const [r, ws] of walls) {
|
|
147
|
+
const seen = /* @__PURE__ */ new Map();
|
|
148
|
+
for (const win of variety) {
|
|
149
|
+
const v = this.agents.get(win[r]).writes[r];
|
|
150
|
+
seen.set(canon(v), v);
|
|
151
|
+
}
|
|
152
|
+
if (seen.size > 1) collisions.push({ resource: r, agents: ws, values: [...seen.values()] });
|
|
153
|
+
}
|
|
154
|
+
const states = { size: reachableStates };
|
|
155
|
+
const coord = states.size > 1 ? this.minimalCoordination() : [];
|
|
156
|
+
const edges = this.rawEdges(coord);
|
|
157
|
+
const nodes = new Set(this.agents.keys());
|
|
158
|
+
const bywave = waves(nodes, edges);
|
|
159
|
+
const work = nodes.size;
|
|
160
|
+
const span = bywave.size ? Math.max(...bywave.keys()) + 1 : 0;
|
|
161
|
+
const width = bywave.size ? Math.max(...[...bywave.values()].map((v) => v.length)) : 0;
|
|
162
|
+
return {
|
|
163
|
+
confluent: states.size === 1,
|
|
164
|
+
reachableStates: states.size,
|
|
165
|
+
capacityBits: states.size ? Math.log2(states.size) : 0,
|
|
166
|
+
collisions,
|
|
167
|
+
minimalCoordination: coord,
|
|
168
|
+
waves: Object.fromEntries(bywave),
|
|
169
|
+
work,
|
|
170
|
+
span,
|
|
171
|
+
width,
|
|
172
|
+
speedup: span ? work / span : 1
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/reachability.ts
|
|
178
|
+
function fromFootprints(fps) {
|
|
179
|
+
const o = new Orchestrator();
|
|
180
|
+
for (const f of fps) o.agent(f.name, { reads: f.reads, writes: f.writes, after: f.after });
|
|
181
|
+
return o;
|
|
182
|
+
}
|
|
183
|
+
function writesOnly(orch) {
|
|
184
|
+
const o = new Orchestrator();
|
|
185
|
+
for (const [n, a] of orch.agents) o.agent(n, { writes: a.writes });
|
|
186
|
+
return o;
|
|
187
|
+
}
|
|
188
|
+
function reachCount(orch) {
|
|
189
|
+
return orch.reachable().states.size;
|
|
190
|
+
}
|
|
191
|
+
function marginalBits(orch, agent, writesOnlyMode = true) {
|
|
192
|
+
const base = writesOnlyMode ? writesOnly(orch) : orch;
|
|
193
|
+
if (!base.agents.has(agent)) throw new Error("unknown agent: " + agent);
|
|
194
|
+
const withCount = reachCount(base);
|
|
195
|
+
const without = new Orchestrator();
|
|
196
|
+
for (const [n, a] of base.agents) if (n !== agent) without.agent(n, { reads: a.reads, writes: a.writes, after: a.after });
|
|
197
|
+
return Math.log2(withCount) - Math.log2(reachCount(without));
|
|
198
|
+
}
|
|
199
|
+
function contentionPrices(orch, writesOnlyMode = true) {
|
|
200
|
+
const out = {};
|
|
201
|
+
for (const n of [...orch.agents.keys()].sort()) out[n] = marginalBits(orch, n, writesOnlyMode);
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
function realizable(orch, observed, walls) {
|
|
205
|
+
const raw = orch.rawEdges();
|
|
206
|
+
const wjobs = /* @__PURE__ */ new Set();
|
|
207
|
+
for (const ws of walls.values()) for (const w of ws) wjobs.add(w);
|
|
208
|
+
const rawW = raw.filter(([a, b]) => wjobs.has(a) && wjobs.has(b));
|
|
209
|
+
const names = [...walls.keys()].sort();
|
|
210
|
+
const cands = names.map((r) => walls.get(r).filter((w) => canon(orch.agents.get(w).writes[r]) === canon(observed[r])));
|
|
211
|
+
for (const combo of product(cands)) {
|
|
212
|
+
const win = {};
|
|
213
|
+
names.forEach((r, i) => win[r] = combo[i]);
|
|
214
|
+
const edges = [...rawW];
|
|
215
|
+
for (const r of names) for (const x of walls.get(r)) if (x !== win[r]) edges.push([x, win[r]]);
|
|
216
|
+
if (acyclic(wjobs, edges)) return true;
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
function adjudicate(orch, observed, withPin = false) {
|
|
221
|
+
const wr = orch.writers();
|
|
222
|
+
const image = /* @__PURE__ */ new Map();
|
|
223
|
+
for (const [res, ws] of wr) {
|
|
224
|
+
const m = /* @__PURE__ */ new Map();
|
|
225
|
+
for (const w of ws) {
|
|
226
|
+
const v = orch.agents.get(w).writes[res];
|
|
227
|
+
m.set(canon(v), v);
|
|
228
|
+
}
|
|
229
|
+
image.set(res, m);
|
|
230
|
+
}
|
|
231
|
+
const walls = /* @__PURE__ */ new Map();
|
|
232
|
+
for (const [res, ws] of wr) if (ws.length >= 2) walls.set(res, ws);
|
|
233
|
+
for (const [res, val] of Object.entries(observed)) {
|
|
234
|
+
if (!wr.has(res))
|
|
235
|
+
return { verdict: "OUT_OF_MODEL", reason: `resource '${res}' is written by no declared agent`, witness: { resource: res, value: val } };
|
|
236
|
+
if (!image.get(res).has(canon(val)))
|
|
237
|
+
return {
|
|
238
|
+
verdict: "OUT_OF_MODEL",
|
|
239
|
+
reason: `resource '${res}' holds ${JSON.stringify(val)}, outside its declared value image`,
|
|
240
|
+
witness: { resource: res, value: val, declaredImage: [...image.get(res).values()] }
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const missing = [...walls.keys()].filter((r) => !(r in observed)).sort();
|
|
244
|
+
if (missing.length) return { verdict: "INCOMPLETE", reason: `omits contested resource(s): ${missing.join(", ")}`, missing };
|
|
245
|
+
if (realizable(orch, observed, walls)) {
|
|
246
|
+
const out = { verdict: "REACHABLE", reason: "consistent with a legal interleaving under the declared footprints" };
|
|
247
|
+
if (withPin && walls.size) out.preventingPin = orch.minimalCoordination();
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
verdict: "JOINT_UNREACHABLE",
|
|
252
|
+
reason: "each field's value is individually declared, but this COMBINATION is unreachable (a correlated lock) -- per-field checks would wrongly pass it",
|
|
253
|
+
observedProjection: [...walls.keys()].sort().map((r) => [r, observed[r]])
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/observe.ts
|
|
258
|
+
var ObservedState = class {
|
|
259
|
+
values;
|
|
260
|
+
reads = /* @__PURE__ */ new Map();
|
|
261
|
+
writes = /* @__PURE__ */ new Map();
|
|
262
|
+
constructor(initial = {}) {
|
|
263
|
+
this.values = { ...initial };
|
|
264
|
+
}
|
|
265
|
+
/** A recording handle for `agent` -- use it like a normal object: `s.plan`, `s.findings = x`. */
|
|
266
|
+
view(agent) {
|
|
267
|
+
if (!this.reads.has(agent)) this.reads.set(agent, /* @__PURE__ */ new Set());
|
|
268
|
+
if (!this.writes.has(agent)) this.writes.set(agent, /* @__PURE__ */ new Map());
|
|
269
|
+
const self = this;
|
|
270
|
+
return new Proxy(this.values, {
|
|
271
|
+
get(_t, key) {
|
|
272
|
+
if (typeof key === "string") self.reads.get(agent).add(key);
|
|
273
|
+
return self.values[key];
|
|
274
|
+
},
|
|
275
|
+
set(_t, key, val) {
|
|
276
|
+
if (typeof key === "string") {
|
|
277
|
+
self.writes.get(agent).set(key, val);
|
|
278
|
+
self.values[key] = val;
|
|
279
|
+
}
|
|
280
|
+
return true;
|
|
281
|
+
},
|
|
282
|
+
has(_t, key) {
|
|
283
|
+
if (typeof key === "string") self.reads.get(agent).add(key);
|
|
284
|
+
return key in self.values;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/** Convenience: run fn(view, ...args) with `agent`'s recording view. */
|
|
289
|
+
run(agent, fn, ...args) {
|
|
290
|
+
return fn(this.view(agent), ...args);
|
|
291
|
+
}
|
|
292
|
+
/** The OBSERVED footprints: [{ name, reads, writes }] per agent (values carried for value-agreement). */
|
|
293
|
+
footprints() {
|
|
294
|
+
const agents = [.../* @__PURE__ */ new Set([...this.reads.keys(), ...this.writes.keys()])].sort();
|
|
295
|
+
return agents.map((a) => ({
|
|
296
|
+
name: a,
|
|
297
|
+
reads: [...this.reads.get(a) ?? []].sort(),
|
|
298
|
+
writes: Object.fromEntries(this.writes.get(a) ?? /* @__PURE__ */ new Map())
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
orchestrator() {
|
|
302
|
+
return fromFootprints(this.footprints());
|
|
303
|
+
}
|
|
304
|
+
analyze() {
|
|
305
|
+
return this.orchestrator().analyze();
|
|
306
|
+
}
|
|
307
|
+
get state() {
|
|
308
|
+
return { ...this.values };
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/coordinator.ts
|
|
313
|
+
var MemoryLedger = class {
|
|
314
|
+
d = /* @__PURE__ */ new Map();
|
|
315
|
+
get(key) {
|
|
316
|
+
return this.d.get(key);
|
|
317
|
+
}
|
|
318
|
+
has(key) {
|
|
319
|
+
return this.d.has(key);
|
|
320
|
+
}
|
|
321
|
+
set(key, value) {
|
|
322
|
+
this.d.set(key, value);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
var Coordinator = class {
|
|
326
|
+
ledger;
|
|
327
|
+
inflight = /* @__PURE__ */ new Map();
|
|
328
|
+
chains = /* @__PURE__ */ new Map();
|
|
329
|
+
constructor(ledger = new MemoryLedger()) {
|
|
330
|
+
this.ledger = ledger;
|
|
331
|
+
}
|
|
332
|
+
/** Run fn() under coordination for `resource`. With `key`: exactly-once (repeated/concurrent calls
|
|
333
|
+
* with the same key run fn once and all receive that one result). Without `key`: serialized per
|
|
334
|
+
* resource (no interleaving with another op on the same resource) -- put a read-modify-write in
|
|
335
|
+
* fn for an atomic update. mode is advisory metadata. */
|
|
336
|
+
async run(resource, fn, opts = {}) {
|
|
337
|
+
const key = opts.key;
|
|
338
|
+
if (key !== void 0) {
|
|
339
|
+
if (this.ledger.has(key)) return this.ledger.get(key);
|
|
340
|
+
const existing = this.inflight.get(key);
|
|
341
|
+
if (existing) return existing;
|
|
342
|
+
const p = (async () => {
|
|
343
|
+
try {
|
|
344
|
+
const r = await fn();
|
|
345
|
+
this.ledger.set(key, r);
|
|
346
|
+
return r;
|
|
347
|
+
} finally {
|
|
348
|
+
this.inflight.delete(key);
|
|
349
|
+
}
|
|
350
|
+
})();
|
|
351
|
+
this.inflight.set(key, p);
|
|
352
|
+
return p;
|
|
353
|
+
}
|
|
354
|
+
const prev = this.chains.get(resource) ?? Promise.resolve();
|
|
355
|
+
const result = prev.then(() => fn(), () => fn());
|
|
356
|
+
this.chains.set(resource, result.catch(() => {
|
|
357
|
+
}));
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
export {
|
|
362
|
+
Coordinator,
|
|
363
|
+
ObservedState,
|
|
364
|
+
Orchestrator,
|
|
365
|
+
acyclic,
|
|
366
|
+
adjudicate,
|
|
367
|
+
canon,
|
|
368
|
+
contentionPrices,
|
|
369
|
+
fromFootprints,
|
|
370
|
+
marginalBits,
|
|
371
|
+
product,
|
|
372
|
+
sameVal
|
|
373
|
+
};
|
|
374
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/orchestrator.ts","../src/reachability.ts","../src/observe.ts","../src/coordinator.ts"],"sourcesContent":["// The confluence engine, ported to TypeScript (parity with orchestrator.py). Pure logic, no deps:\n// declare each agent's reads/writes -> prove the reachable end-states of shared memory.\n\nexport type Value = unknown;\n\nexport interface AgentSpec {\n reads: string[];\n writes: Record<string, Value>;\n after: string[];\n}\n\nexport interface Collision {\n resource: string;\n agents: string[];\n values: Value[];\n}\n\nexport interface Plan {\n confluent: boolean;\n reachableStates: number;\n capacityBits: number;\n collisions: Collision[];\n minimalCoordination: Array<[string, string]>;\n waves: Record<number, string[]>;\n work: number;\n span: number;\n width: number;\n speedup: number;\n}\n\nexport function canon(x: Value): string {\n return typeof x + \":\" + JSON.stringify(x ?? null);\n}\nexport function sameVal(a: Value, b: Value): boolean {\n return canon(a) === canon(b);\n}\n\nexport function product<T>(arrays: T[][]): T[][] {\n return arrays.reduce<T[][]>((acc, arr) => acc.flatMap((c) => arr.map((x) => [...c, x])), [[]]);\n}\n\nexport function acyclic(nodes: Set<string>, edges: Array<[string, string]>): boolean {\n const adj = new Map<string, Set<string>>();\n for (const [a, b] of edges) {\n if (!adj.has(a)) adj.set(a, new Set());\n adj.get(a)!.add(b);\n }\n const color = new Map<string, number>();\n const dfs = (u: string): boolean => {\n color.set(u, 1);\n for (const v of adj.get(u) ?? []) {\n const c = color.get(v) ?? 0;\n if (c === 1) return false;\n if (c === 0 && !dfs(v)) return false;\n }\n color.set(u, 2);\n return true;\n };\n for (const n of nodes) if ((color.get(n) ?? 0) === 0 && !dfs(n)) return false;\n return true;\n}\n\nfunction waves(nodes: Set<string>, edges: Array<[string, string]>): Map<number, string[]> {\n const succ = new Map<string, string[]>();\n const indeg = new Map<string, number>();\n for (const n of nodes) indeg.set(n, 0);\n for (const [a, b] of edges) {\n if (!succ.has(a)) succ.set(a, []);\n succ.get(a)!.push(b);\n indeg.set(b, (indeg.get(b) ?? 0) + 1);\n }\n const level = new Map<string, number>();\n const q: string[] = [];\n for (const n of nodes) if (indeg.get(n) === 0) { level.set(n, 0); q.push(n); }\n const ind = new Map(indeg);\n while (q.length) {\n const u = q.shift()!;\n for (const v of succ.get(u) ?? []) {\n level.set(v, Math.max(level.get(v) ?? 0, (level.get(u) ?? 0) + 1));\n ind.set(v, ind.get(v)! - 1);\n if (ind.get(v) === 0) q.push(v);\n }\n }\n const bywave = new Map<number, string[]>();\n for (const n of nodes) {\n const l = level.get(n) ?? 0;\n if (!bywave.has(l)) bywave.set(l, []);\n bywave.get(l)!.push(n);\n }\n return bywave;\n}\n\nexport class Orchestrator {\n agents: Map<string, AgentSpec> = new Map();\n\n agent(name: string, spec: { reads?: string[]; writes?: Record<string, Value>; after?: string[] } = {}): this {\n if (this.agents.has(name)) throw new Error(\"duplicate agent: \" + name);\n this.agents.set(name, { reads: spec.reads ?? [], writes: spec.writes ?? {}, after: spec.after ?? [] });\n return this;\n }\n\n writers(): Map<string, string[]> {\n const wr = new Map<string, string[]>();\n for (const [n, a] of this.agents)\n for (const res of Object.keys(a.writes)) {\n if (!wr.has(res)) wr.set(res, []);\n wr.get(res)!.push(n);\n }\n for (const [res, ws] of wr) wr.set(res, ws.slice().sort());\n return wr;\n }\n\n rawEdges(extra: Array<[string, string]> = []): Array<[string, string]> {\n const wr = this.writers();\n const edges = new Set<string>();\n const add = (a: string, b: string) => edges.add(a + \"\u0000\" + b);\n for (const [a, b] of extra) add(a, b);\n for (const [n, a] of this.agents) {\n for (const res of a.reads) for (const w of wr.get(res) ?? []) if (w !== n) add(w, n);\n for (const p of a.after) if (this.agents.has(p)) add(p, n);\n }\n return [...edges].map((s) => s.split(\"\u0000\") as [string, string]);\n }\n\n variety(extra: Array<[string, string]> = []): { variety: Array<Record<string, string>>; walls: Map<string, string[]> } {\n const wr = this.writers();\n const walls = new Map<string, string[]>();\n for (const [res, ws] of wr) if (ws.length >= 2) walls.set(res, ws);\n if (walls.size === 0) return { variety: [{}], walls };\n const names = [...walls.keys()].sort();\n const raw = this.rawEdges(extra);\n const wjobs = new Set<string>();\n for (const ws of walls.values()) for (const w of ws) wjobs.add(w);\n const rawW = raw.filter(([a, b]) => wjobs.has(a) && wjobs.has(b));\n const out: Array<Record<string, string>> = [];\n for (const combo of product(names.map((r) => walls.get(r)!))) {\n const win: Record<string, string> = {};\n names.forEach((r, i) => (win[r] = combo[i]));\n const edges: Array<[string, string]> = [...rawW];\n for (const r of names) for (const x of walls.get(r)!) if (x !== win[r]) edges.push([x, win[r]]);\n if (acyclic(wjobs, edges)) out.push(win);\n }\n return { variety: out, walls };\n }\n\n reachable(extra: Array<[string, string]> = []): { states: Set<string>; walls: Map<string, string[]> } {\n const { variety, walls } = this.variety(extra);\n if (walls.size === 0) return { states: new Set([\"[]\"]), walls };\n const names = [...walls.keys()].sort();\n const states = new Set<string>();\n for (const win of variety) states.add(JSON.stringify(names.map((r) => [r, canon(this.agents.get(win[r])!.writes[r])])));\n return { states, walls };\n }\n\n minimalCoordination(): Array<[string, string]> {\n const extra: Array<[string, string]> = [];\n for (;;) {\n const { states, walls } = this.reachable(extra);\n if (states.size <= 1) break;\n let best: { n: number; pin: [string, string] } | null = null;\n for (const [, ws] of walls)\n for (const a of ws)\n for (const b of ws)\n if (a !== b) {\n const cand: Array<[string, string]> = [...extra, [a, b]];\n if (!acyclic(new Set(this.agents.keys()), this.rawEdges(cand))) continue;\n const n = this.reachable(cand).states.size;\n if (best === null || n < best.n) best = { n, pin: [a, b] };\n }\n if (best === null || extra.some(([a, b]) => a === best!.pin[0] && b === best!.pin[1])) break;\n extra.push(best.pin);\n }\n return extra;\n }\n\n analyze(): Plan {\n const { variety, walls } = this.variety();\n const names = [...walls.keys()].sort();\n const stateKeys = new Set<string>();\n for (const win of variety) stateKeys.add(JSON.stringify(names.map((r) => canon(this.agents.get(win[r])!.writes[r]))));\n const reachableStates = walls.size === 0 ? 1 : stateKeys.size;\n const collisions: Collision[] = [];\n for (const [r, ws] of walls) {\n const seen = new Map<string, Value>();\n for (const win of variety) {\n const v = this.agents.get(win[r])!.writes[r];\n seen.set(canon(v), v);\n }\n if (seen.size > 1) collisions.push({ resource: r, agents: ws, values: [...seen.values()] });\n }\n const states = { size: reachableStates };\n const coord = states.size > 1 ? this.minimalCoordination() : [];\n const edges = this.rawEdges(coord);\n const nodes = new Set(this.agents.keys());\n const bywave = waves(nodes, edges);\n const work = nodes.size;\n const span = bywave.size ? Math.max(...bywave.keys()) + 1 : 0;\n const width = bywave.size ? Math.max(...[...bywave.values()].map((v) => v.length)) : 0;\n return {\n confluent: states.size === 1,\n reachableStates: states.size,\n capacityBits: states.size ? Math.log2(states.size) : 0,\n collisions,\n minimalCoordination: coord,\n waves: Object.fromEntries(bywave),\n work,\n span,\n width,\n speedup: span ? work / span : 1,\n };\n }\n}\n","// The reachable-set verbs (parity with reachability.py): the cost of an agent's intent, and the\n// joint-reachability oracle. Both read the engine; neither enumerates more than it must.\n\nimport { Orchestrator, acyclic, product, canon, type Value } from \"./orchestrator.ts\";\n\nexport interface Footprint {\n name: string;\n reads?: string[];\n writes?: Record<string, Value>;\n after?: string[];\n}\n\nexport function fromFootprints(fps: Footprint[]): Orchestrator {\n const o = new Orchestrator();\n for (const f of fps) o.agent(f.name, { reads: f.reads, writes: f.writes, after: f.after });\n return o;\n}\n\nfunction writesOnly(orch: Orchestrator): Orchestrator {\n const o = new Orchestrator();\n for (const [n, a] of orch.agents) o.agent(n, { writes: a.writes });\n return o;\n}\n\nfunction reachCount(orch: Orchestrator): number {\n return orch.reachable().states.size;\n}\n\n/** Bits of order-nondeterminism the joint outcome GAINS from this agent's writes. Zero on\n * value-agreement; provably >= 0 in the writes-only regime (the default). */\nexport function marginalBits(orch: Orchestrator, agent: string, writesOnlyMode = true): number {\n const base = writesOnlyMode ? writesOnly(orch) : orch;\n if (!base.agents.has(agent)) throw new Error(\"unknown agent: \" + agent);\n const withCount = reachCount(base);\n const without = new Orchestrator();\n for (const [n, a] of base.agents) if (n !== agent) without.agent(n, { reads: a.reads, writes: a.writes, after: a.after });\n return Math.log2(withCount) - Math.log2(reachCount(without));\n}\n\n/** { agent: marginalBits } -- the admission-control prioritizer. */\nexport function contentionPrices(orch: Orchestrator, writesOnlyMode = true): Record<string, number> {\n const out: Record<string, number> = {};\n for (const n of [...orch.agents.keys()].sort()) out[n] = marginalBits(orch, n, writesOnlyMode);\n return out;\n}\n\nexport type Verdict = \"REACHABLE\" | \"JOINT_UNREACHABLE\" | \"OUT_OF_MODEL\" | \"INCOMPLETE\";\n\nexport interface Adjudication {\n verdict: Verdict;\n reason: string;\n witness?: { resource: string; value: Value; declaredImage?: Value[] };\n preventingPin?: Array<[string, string]>;\n missing?: string[];\n observedProjection?: Array<[string, Value]>;\n}\n\nfunction realizable(orch: Orchestrator, observed: Record<string, Value>, walls: Map<string, string[]>): boolean {\n const raw = orch.rawEdges();\n const wjobs = new Set<string>();\n for (const ws of walls.values()) for (const w of ws) wjobs.add(w);\n const rawW = raw.filter(([a, b]) => wjobs.has(a) && wjobs.has(b));\n const names = [...walls.keys()].sort();\n const cands = names.map((r) => walls.get(r)!.filter((w) => canon(orch.agents.get(w)!.writes[r]) === canon(observed[r])));\n for (const combo of product(cands)) {\n const win: Record<string, string> = {};\n names.forEach((r, i) => (win[r] = combo[i]));\n const edges: Array<[string, string]> = [...rawW];\n for (const r of names) for (const x of walls.get(r)!) if (x !== win[r]) edges.push([x, win[r]]);\n if (acyclic(wjobs, edges)) return true;\n }\n return false;\n}\n\n/** Is `observed` consistent with the declared concurrency model? A direct realizability check --\n * no full enumeration, no fix search -- so it stays cheap even when the reachable set is huge. */\nexport function adjudicate(orch: Orchestrator, observed: Record<string, Value>, withPin = false): Adjudication {\n const wr = orch.writers();\n const image = new Map<string, Map<string, Value>>();\n for (const [res, ws] of wr) {\n const m = new Map<string, Value>();\n for (const w of ws) { const v = orch.agents.get(w)!.writes[res]; m.set(canon(v), v); }\n image.set(res, m);\n }\n const walls = new Map<string, string[]>();\n for (const [res, ws] of wr) if (ws.length >= 2) walls.set(res, ws);\n\n for (const [res, val] of Object.entries(observed)) {\n if (!wr.has(res))\n return { verdict: \"OUT_OF_MODEL\", reason: `resource '${res}' is written by no declared agent`, witness: { resource: res, value: val } };\n if (!image.get(res)!.has(canon(val)))\n return {\n verdict: \"OUT_OF_MODEL\",\n reason: `resource '${res}' holds ${JSON.stringify(val)}, outside its declared value image`,\n witness: { resource: res, value: val, declaredImage: [...image.get(res)!.values()] },\n };\n }\n\n const missing = [...walls.keys()].filter((r) => !(r in observed)).sort();\n if (missing.length) return { verdict: \"INCOMPLETE\", reason: `omits contested resource(s): ${missing.join(\", \")}`, missing };\n\n if (realizable(orch, observed, walls)) {\n const out: Adjudication = { verdict: \"REACHABLE\", reason: \"consistent with a legal interleaving under the declared footprints\" };\n if (withPin && walls.size) out.preventingPin = orch.minimalCoordination();\n return out;\n }\n return {\n verdict: \"JOINT_UNREACHABLE\",\n reason: \"each field's value is individually declared, but this COMBINATION is unreachable (a correlated lock) -- per-field checks would wrongly pass it\",\n observedProjection: [...walls.keys()].sort().map((r) => [r, observed[r]] as [string, Value]),\n };\n}\n","// Observe-mode (parity with observe.py): footprints for free. Each agent gets a recording Proxy of\n// the shared state and uses it like a normal object; every read/write is attributed automatically.\n// Certify what ACTUALLY happened -- no declaration, no mis-declaration risk.\n\nimport { Orchestrator } from \"./orchestrator.ts\";\nimport { fromFootprints, type Footprint } from \"./reachability.ts\";\n\nexport class ObservedState {\n private values: Record<string, unknown>;\n private reads: Map<string, Set<string>> = new Map();\n private writes: Map<string, Map<string, unknown>> = new Map();\n\n constructor(initial: Record<string, unknown> = {}) {\n this.values = { ...initial };\n }\n\n /** A recording handle for `agent` -- use it like a normal object: `s.plan`, `s.findings = x`. */\n view(agent: string): Record<string, any> {\n if (!this.reads.has(agent)) this.reads.set(agent, new Set());\n if (!this.writes.has(agent)) this.writes.set(agent, new Map());\n const self = this;\n return new Proxy(this.values, {\n get(_t, key) {\n if (typeof key === \"string\") self.reads.get(agent)!.add(key);\n return self.values[key as string];\n },\n set(_t, key, val) {\n if (typeof key === \"string\") {\n self.writes.get(agent)!.set(key, val);\n self.values[key] = val;\n }\n return true;\n },\n has(_t, key) {\n if (typeof key === \"string\") self.reads.get(agent)!.add(key);\n return (key as string) in self.values;\n },\n });\n }\n\n /** Convenience: run fn(view, ...args) with `agent`'s recording view. */\n run<T>(agent: string, fn: (view: Record<string, any>, ...args: any[]) => T, ...args: any[]): T {\n return fn(this.view(agent), ...args);\n }\n\n /** The OBSERVED footprints: [{ name, reads, writes }] per agent (values carried for value-agreement). */\n footprints(): Footprint[] {\n const agents = [...new Set([...this.reads.keys(), ...this.writes.keys()])].sort();\n return agents.map((a) => ({\n name: a,\n reads: [...(this.reads.get(a) ?? [])].sort(),\n writes: Object.fromEntries(this.writes.get(a) ?? new Map()),\n }));\n }\n\n orchestrator(): Orchestrator {\n return fromFootprints(this.footprints());\n }\n\n analyze() {\n return this.orchestrator().analyze();\n }\n\n get state(): Record<string, unknown> {\n return { ...this.values };\n }\n}\n","// The runtime coordinator, ported to async TypeScript. JS is single-threaded, which makes the two\n// guarantees natural:\n// - exactly-once per `key` -> dedupe concurrent/repeated calls by their idempotency key (one fn\n// run; everyone awaits the same promise; failures stay retryable).\n// - per-resource serialize -> a promise chain per resource, so a read-modify-write in fn() can't\n// interleave with another on the same resource (atomic RMW).\n// Swap the in-memory ledger for Redis/a DB to make exactly-once cross-process (same shape).\n\nexport type Mode = \"read\" | \"write\" | \"append\" | \"exec\";\n\nexport interface Ledger {\n get(key: string): unknown | undefined;\n has(key: string): boolean;\n set(key: string, value: unknown): void;\n}\n\nclass MemoryLedger implements Ledger {\n private d = new Map<string, unknown>();\n get(key: string) { return this.d.get(key); }\n has(key: string) { return this.d.has(key); }\n set(key: string, value: unknown) { this.d.set(key, value); }\n}\n\nexport class Coordinator {\n private ledger: Ledger;\n private inflight: Map<string, Promise<unknown>> = new Map();\n private chains: Map<string, Promise<unknown>> = new Map();\n\n constructor(ledger: Ledger = new MemoryLedger()) {\n this.ledger = ledger;\n }\n\n /** Run fn() under coordination for `resource`. With `key`: exactly-once (repeated/concurrent calls\n * with the same key run fn once and all receive that one result). Without `key`: serialized per\n * resource (no interleaving with another op on the same resource) -- put a read-modify-write in\n * fn for an atomic update. mode is advisory metadata. */\n async run<T>(resource: string, fn: () => T | Promise<T>, opts: { mode?: Mode; key?: string } = {}): Promise<T> {\n const key = opts.key;\n if (key !== undefined) {\n if (this.ledger.has(key)) return this.ledger.get(key) as T;\n const existing = this.inflight.get(key);\n if (existing) return existing as Promise<T>;\n const p = (async () => {\n try {\n const r = await fn();\n this.ledger.set(key, r);\n return r;\n } finally {\n this.inflight.delete(key); // on failure: key stays absent -> retryable\n }\n })();\n this.inflight.set(key, p);\n return p as Promise<T>;\n }\n // no key: serialize on `resource`\n const prev = this.chains.get(resource) ?? Promise.resolve();\n const result = prev.then(() => fn(), () => fn());\n this.chains.set(resource, result.catch(() => {}));\n return result as Promise<T>;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lockstep-agents",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Coordination for multi-agent shared state, native in TypeScript: prove convergence, price an agent's intent (marginal bits), observe footprints with zero declaration, adjudicate an incident, and enforce exactly-once. Pure logic, no deps.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"test": "node --experimental-strip-types test.ts",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"agents",
|
|
27
|
+
"multi-agent",
|
|
28
|
+
"coordination",
|
|
29
|
+
"determinism",
|
|
30
|
+
"confluence",
|
|
31
|
+
"crdt",
|
|
32
|
+
"exactly-once",
|
|
33
|
+
"ai-agents",
|
|
34
|
+
"llm"
|
|
35
|
+
],
|
|
36
|
+
"author": "Ryan Thrower",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/rthrower1/lockstep-agents.git",
|
|
41
|
+
"directory": "ts"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"tsup": "^8.3.5",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
}
|
|
50
|
+
}
|