laserbrain 0.2.0__tar.gz

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.
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: laserbrain
3
+ Version: 0.2.0
4
+ Summary: Attach the smart recursion harness to any agent loop — a provably-correct, external check for when an AI agent (or team) has drifted from its goal.
5
+ Author: phronesis
6
+ License: MIT
7
+ Project-URL: Homepage, https://phronesis.world/laserbrain
8
+ Project-URL: Research, https://phronesis.world/laserbrain/research
9
+ Project-URL: Demo, https://phronesis.world/laserbrain/demo
10
+ Keywords: ai,agents,oversight,drift,mcp,llm,multi-agent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+
14
+ # laserbrain
15
+
16
+ Attach the **smart recursion harness** to any agent loop — a provably-correct,
17
+ external check for when an AI agent (or a team of agents) has drifted from its goal.
18
+
19
+ An agent watching only itself provably can't catch its own drift: each step looks
20
+ fine next to the last while it wanders far from where it began. laserbrain is a
21
+ **fixed reference** it checks against instead. There's a proof.
22
+ [The theorem and the studies (nulls included).](https://phronesis.world/laserbrain/research)
23
+ · [Watch it work.](https://phronesis.world/laserbrain/demo)
24
+
25
+ The check is a pure function, so this SDK runs it **locally and free** — no key, no
26
+ latency. Add a key and it also mirrors to the API for retained drift history,
27
+ alerts and the fleet view: you pay to *see* your agents drift, not for the check.
28
+
29
+ ```bash
30
+ pip install laserbrain
31
+ ```
32
+
33
+ ## The check (local, free)
34
+
35
+ ```python
36
+ from laserbrain import Harness
37
+
38
+ hz = Harness() # add key="lb_live_…" to also retain history
39
+ v = hz.check(goal="build the JSON parser", progress="advancing", distance=6)
40
+ if v.drifting:
41
+ print(v.reason, "—", v.advice) # e.g. "goal-drift — your goal no longer matches…"
42
+ ```
43
+
44
+ `progress` is one of `advancing | stuck | circling`; `distance` is 0–10 to done.
45
+ Reasons: `advancing`, `grounded`, `goal-drift`, `stalled`, `self-report:stuck/circling`,
46
+ `ungrammatical`.
47
+
48
+ ## The act layer — close the loop
49
+
50
+ Give laserbrain your step function and it detects drift *and* injects the return, so
51
+ the agent recovers instead of spinning. Your step reads `ctx["return"]` and steers back.
52
+
53
+ ```python
54
+ def step(ctx):
55
+ if ctx.get("return"): # laserbrain told us to return to ground
56
+ ... # steer the agent back toward its goal
57
+ ...
58
+ return dict(goal="build the JSON parser", progress="advancing", distance=d, done=d == 0)
59
+
60
+ ctx = Harness().run(step, on_return=lambda v, ctx: print("↩", v.advice))
61
+ ```
62
+
63
+ ## Recursion teams — styled multi-agent oversight
64
+
65
+ A **recursion team** styles each role's recursion: a `deep` explorer tolerates
66
+ displacement, a `tight` checker returns fast. laserbrain runs the team, watches the
67
+ shared goal (the fixed reference), and injects the return per role — catching the
68
+ **echo/agreement spiral** a self-watching group can't see.
69
+
70
+ ```python
71
+ from laserbrain import Team
72
+
73
+ def agent(role, history, injected):
74
+ # your LLM call for this role; `injected` is a return-to-ground note (or None)
75
+ return position, distance
76
+
77
+ Team("adversarial-deliberation", goal="…").run(agent)
78
+ # presets: deep-search · iterative-refinement · adversarial-deliberation
79
+ ```
80
+
81
+ ## Oversight, provenance, continuity
82
+
83
+ **Human-in-the-loop.** A self-correcting return usually takes. When it doesn't —
84
+ the agent keeps drifting past `escalate_after` steps — laserbrain escalates *that
85
+ drift* to a human. The human doesn't watch every step; they see only what the fixed
86
+ reference caught. Their decision overrides the auto-return.
87
+
88
+ ```python
89
+ def on_escalate(v, ctx):
90
+ return ask_a_human(v.reason, v.advice) # Slack, a queue, a webhook — you wire it
91
+ # returning a decision injects it as the return
92
+ Harness().run(step, escalate_after=3, on_escalate=on_escalate)
93
+ ```
94
+
95
+ **Provenance.** Every check is written to a hash-chained ledger — tamper-evident and
96
+ verifiable offline, by anyone, no key. Editing a past verdict to hide a drift breaks
97
+ the chain at that link.
98
+
99
+ ```python
100
+ hz.export_audit("run.json")
101
+ from laserbrain import verify_audit
102
+ verify_audit(json.load(open("run.json"))) # (True, -1) intact · (False, i) broken at link i
103
+ ```
104
+
105
+ **Team continuity.** Snapshot a running team and resume it in a later session — the
106
+ shared goal (the fixed reference) and the dialogue carry over, so the group re-grounds
107
+ instead of starting cold.
108
+
109
+ ```python
110
+ snap = team.snapshot() # JSON-safe; persist it anywhere
111
+ team = Team.restore(snap) # keeps watching the same ground
112
+ ```
113
+
114
+ ## Framework adapters
115
+
116
+ Already on LangGraph, CrewAI, AutoGen, or the OpenAI Agents SDK? Attach laserbrain
117
+ without changing your loop. Because it checks a fixed reference, it needs the agent
118
+ to *spell* its state — so each adapter takes an `extract` that maps your framework's
119
+ state to `(goal, progress, distance)`. **No adapter imports a framework**: each
120
+ returns a plain callable you hand to the framework's own hook, so install only the
121
+ one you use.
122
+
123
+ ```python
124
+ from laserbrain.adapters import guard, langgraph_node, crewai_step_callback, middleware
125
+
126
+ # generic — wrap any step that returns dict(goal=, progress=, distance=)
127
+ @guard
128
+ def step(state): ...
129
+
130
+ # LangGraph — a node that writes the Verdict into graph state; branch on it
131
+ g.add_node("laserbrain", langgraph_node(extract=lambda s: (s["goal"], "advancing", s["dist"])))
132
+ g.add_edge("agent", "laserbrain")
133
+ g.add_conditional_edges("laserbrain",
134
+ lambda s: "return" if s["laserbrain"].drifting else "agent") # .advice steers the return
135
+
136
+ # CrewAI — a step_callback that fires each agent step
137
+ Agent(..., step_callback=crewai_step_callback(lambda o: (o.goal, o.status, o.dist)))
138
+
139
+ # anything else (AutoGen, OpenAI Agents, a custom loop) — one check per step
140
+ lb = middleware(extract=my_extract)
141
+ v = lb(step_output)
142
+ if v.drifting: reinject(v.advice)
143
+ ```
144
+
145
+ Each adapter runs the check locally and free; pass `key=`/`run_id=` (or your own
146
+ `Harness`) to also retain history.
147
+
148
+ ## What's proven, and what isn't
149
+
150
+ The **single-agent** detector mirrors the frozen, published instrument
151
+ (`drift.ts @ 6b483de7`) and rests on a theorem: detection is sound and complete, and
152
+ no self-monitoring agent can be. The **multi-agent** dialogue and recursion teams are
153
+ a prototype extension — useful, not (yet) a theorem. Whether *returning* an agent
154
+ keeps the answer as good is [an honest open question](https://phronesis.world/laserbrain/research);
155
+ this SDK gives you the detection and the return mechanism, and says plainly what each is.
156
+
157
+ MIT · [phronesis.world/laserbrain](https://phronesis.world/laserbrain)
@@ -0,0 +1,144 @@
1
+ # laserbrain
2
+
3
+ Attach the **smart recursion harness** to any agent loop — a provably-correct,
4
+ external check for when an AI agent (or a team of agents) has drifted from its goal.
5
+
6
+ An agent watching only itself provably can't catch its own drift: each step looks
7
+ fine next to the last while it wanders far from where it began. laserbrain is a
8
+ **fixed reference** it checks against instead. There's a proof.
9
+ [The theorem and the studies (nulls included).](https://phronesis.world/laserbrain/research)
10
+ · [Watch it work.](https://phronesis.world/laserbrain/demo)
11
+
12
+ The check is a pure function, so this SDK runs it **locally and free** — no key, no
13
+ latency. Add a key and it also mirrors to the API for retained drift history,
14
+ alerts and the fleet view: you pay to *see* your agents drift, not for the check.
15
+
16
+ ```bash
17
+ pip install laserbrain
18
+ ```
19
+
20
+ ## The check (local, free)
21
+
22
+ ```python
23
+ from laserbrain import Harness
24
+
25
+ hz = Harness() # add key="lb_live_…" to also retain history
26
+ v = hz.check(goal="build the JSON parser", progress="advancing", distance=6)
27
+ if v.drifting:
28
+ print(v.reason, "—", v.advice) # e.g. "goal-drift — your goal no longer matches…"
29
+ ```
30
+
31
+ `progress` is one of `advancing | stuck | circling`; `distance` is 0–10 to done.
32
+ Reasons: `advancing`, `grounded`, `goal-drift`, `stalled`, `self-report:stuck/circling`,
33
+ `ungrammatical`.
34
+
35
+ ## The act layer — close the loop
36
+
37
+ Give laserbrain your step function and it detects drift *and* injects the return, so
38
+ the agent recovers instead of spinning. Your step reads `ctx["return"]` and steers back.
39
+
40
+ ```python
41
+ def step(ctx):
42
+ if ctx.get("return"): # laserbrain told us to return to ground
43
+ ... # steer the agent back toward its goal
44
+ ...
45
+ return dict(goal="build the JSON parser", progress="advancing", distance=d, done=d == 0)
46
+
47
+ ctx = Harness().run(step, on_return=lambda v, ctx: print("↩", v.advice))
48
+ ```
49
+
50
+ ## Recursion teams — styled multi-agent oversight
51
+
52
+ A **recursion team** styles each role's recursion: a `deep` explorer tolerates
53
+ displacement, a `tight` checker returns fast. laserbrain runs the team, watches the
54
+ shared goal (the fixed reference), and injects the return per role — catching the
55
+ **echo/agreement spiral** a self-watching group can't see.
56
+
57
+ ```python
58
+ from laserbrain import Team
59
+
60
+ def agent(role, history, injected):
61
+ # your LLM call for this role; `injected` is a return-to-ground note (or None)
62
+ return position, distance
63
+
64
+ Team("adversarial-deliberation", goal="…").run(agent)
65
+ # presets: deep-search · iterative-refinement · adversarial-deliberation
66
+ ```
67
+
68
+ ## Oversight, provenance, continuity
69
+
70
+ **Human-in-the-loop.** A self-correcting return usually takes. When it doesn't —
71
+ the agent keeps drifting past `escalate_after` steps — laserbrain escalates *that
72
+ drift* to a human. The human doesn't watch every step; they see only what the fixed
73
+ reference caught. Their decision overrides the auto-return.
74
+
75
+ ```python
76
+ def on_escalate(v, ctx):
77
+ return ask_a_human(v.reason, v.advice) # Slack, a queue, a webhook — you wire it
78
+ # returning a decision injects it as the return
79
+ Harness().run(step, escalate_after=3, on_escalate=on_escalate)
80
+ ```
81
+
82
+ **Provenance.** Every check is written to a hash-chained ledger — tamper-evident and
83
+ verifiable offline, by anyone, no key. Editing a past verdict to hide a drift breaks
84
+ the chain at that link.
85
+
86
+ ```python
87
+ hz.export_audit("run.json")
88
+ from laserbrain import verify_audit
89
+ verify_audit(json.load(open("run.json"))) # (True, -1) intact · (False, i) broken at link i
90
+ ```
91
+
92
+ **Team continuity.** Snapshot a running team and resume it in a later session — the
93
+ shared goal (the fixed reference) and the dialogue carry over, so the group re-grounds
94
+ instead of starting cold.
95
+
96
+ ```python
97
+ snap = team.snapshot() # JSON-safe; persist it anywhere
98
+ team = Team.restore(snap) # keeps watching the same ground
99
+ ```
100
+
101
+ ## Framework adapters
102
+
103
+ Already on LangGraph, CrewAI, AutoGen, or the OpenAI Agents SDK? Attach laserbrain
104
+ without changing your loop. Because it checks a fixed reference, it needs the agent
105
+ to *spell* its state — so each adapter takes an `extract` that maps your framework's
106
+ state to `(goal, progress, distance)`. **No adapter imports a framework**: each
107
+ returns a plain callable you hand to the framework's own hook, so install only the
108
+ one you use.
109
+
110
+ ```python
111
+ from laserbrain.adapters import guard, langgraph_node, crewai_step_callback, middleware
112
+
113
+ # generic — wrap any step that returns dict(goal=, progress=, distance=)
114
+ @guard
115
+ def step(state): ...
116
+
117
+ # LangGraph — a node that writes the Verdict into graph state; branch on it
118
+ g.add_node("laserbrain", langgraph_node(extract=lambda s: (s["goal"], "advancing", s["dist"])))
119
+ g.add_edge("agent", "laserbrain")
120
+ g.add_conditional_edges("laserbrain",
121
+ lambda s: "return" if s["laserbrain"].drifting else "agent") # .advice steers the return
122
+
123
+ # CrewAI — a step_callback that fires each agent step
124
+ Agent(..., step_callback=crewai_step_callback(lambda o: (o.goal, o.status, o.dist)))
125
+
126
+ # anything else (AutoGen, OpenAI Agents, a custom loop) — one check per step
127
+ lb = middleware(extract=my_extract)
128
+ v = lb(step_output)
129
+ if v.drifting: reinject(v.advice)
130
+ ```
131
+
132
+ Each adapter runs the check locally and free; pass `key=`/`run_id=` (or your own
133
+ `Harness`) to also retain history.
134
+
135
+ ## What's proven, and what isn't
136
+
137
+ The **single-agent** detector mirrors the frozen, published instrument
138
+ (`drift.ts @ 6b483de7`) and rests on a theorem: detection is sound and complete, and
139
+ no self-monitoring agent can be. The **multi-agent** dialogue and recursion teams are
140
+ a prototype extension — useful, not (yet) a theorem. Whether *returning* an agent
141
+ keeps the answer as good is [an honest open question](https://phronesis.world/laserbrain/research);
142
+ this SDK gives you the detection and the return mechanism, and says plainly what each is.
143
+
144
+ MIT · [phronesis.world/laserbrain](https://phronesis.world/laserbrain)
@@ -0,0 +1,411 @@
1
+ """
2
+ laserbrain — attach the smart recursion harness to any agent loop.
3
+
4
+ The check is a pure function, so it runs LOCALLY and free (the open grammar, no
5
+ key, no latency). Give it a key and it also mirrors to the API for retained drift
6
+ history, alerts and the fleet view — you pay to *see* your agents drift, not for
7
+ the check. Model-agnostic: you provide the agent, laserbrain closes the loop.
8
+
9
+ from laserbrain import Harness
10
+ hz = Harness() # local + free (add key=... to retain)
11
+ v = hz.check(goal="build the parser", progress="advancing", distance=6)
12
+ if v.drifting: ... # v.advice tells the agent to return
13
+
14
+ hz.run(step, on_return=lambda v, ctx: ...) # the act layer: auto-inject the return
15
+
16
+ from laserbrain import Team
17
+ Team("adversarial-deliberation", goal="…").run(agent_fn) # a styled recursion team
18
+
19
+ The single-agent detector mirrors the frozen drift.ts @ 6b483de7 (the published
20
+ instrument); the multi-agent dialogue + recursion teams are the prototype extension.
21
+ """
22
+ from __future__ import annotations
23
+ from dataclasses import dataclass
24
+ import hashlib, json, re, urllib.request
25
+
26
+ __all__ = ['Harness', 'Team', 'Verdict', 'PRESETS', 'norm', 'verify_audit']
27
+ API_DEFAULT = 'https://laserbrain-mcp.degibug.workers.dev'
28
+
29
+
30
+ # ── provenance: a tamper-evident, hash-chained ledger of every check ───────────
31
+ def _canon(d):
32
+ return json.dumps(d, sort_keys=True, separators=(',', ':'))
33
+
34
+
35
+ def _link(prev, body):
36
+ return hashlib.sha256((prev + _canon(body)).encode()).hexdigest()
37
+
38
+
39
+ def verify_audit(chain):
40
+ """Independently verify an exported audit chain. Returns (ok, first_bad_index):
41
+ (True, -1) if intact, else (False, i) at the first tampered/broken link.
42
+ Free and offline — anyone can audit an agent's run without a key."""
43
+ prev = ''
44
+ for i, rec in enumerate(chain):
45
+ body = {k: rec[k] for k in rec if k != 'hash'}
46
+ if rec.get('prev') != prev or _link(prev, body) != rec.get('hash'):
47
+ return (False, i)
48
+ prev = rec['hash']
49
+ return (True, -1)
50
+
51
+ # ── the fixed-reference primitive (frozen: drift.ts @ 6b483de7) ────────────────
52
+ _STOP = {'the', 'a', 'an', 'to', 'of', 'and', 'or', 'for', 'in', 'on', 'at', 'is', 'it', 'this',
53
+ 'that', 'with', 'my', 'your', 'our', 'i', 'we', 'be', 'as', 'by', 'from', 'into', 'out',
54
+ 'up', 'so', 'then'}
55
+ _STEM = re.compile(r"(ings?|edly|ed|ers?|es|s|tion|ment)$")
56
+ _PROGRESS = {'advancing', 'stuck', 'circling'}
57
+
58
+
59
+ def norm(s):
60
+ out = set()
61
+ for w in re.findall(r"[a-z0-9']+", str(s).lower()):
62
+ if w in _STOP:
63
+ continue
64
+ r = _STEM.sub('', w) if len(w) > 4 else w
65
+ if r:
66
+ out.add(r)
67
+ return out
68
+
69
+
70
+ def _jac(a, b):
71
+ if not a and not b:
72
+ return 0.0
73
+ return 1 - len(a & b) / len(a | b)
74
+
75
+
76
+ def _sim(a, b):
77
+ return 1 - _jac(a, b)
78
+
79
+
80
+ def _asdist(d):
81
+ try:
82
+ return max(0, min(10, int(float(d))))
83
+ except Exception:
84
+ return 5
85
+
86
+
87
+ def _displacement(goal, progress, distance, ground):
88
+ return (0.5 * _jac(norm(goal), norm(ground['goal']))
89
+ + 0.3 * abs(_asdist(distance) - ground['dist']) / 10
90
+ + 0.2 * (0 if progress == ground['progress'] else 1))
91
+
92
+
93
+ _DRIFT = ('ungrammatical', 'goal-drift', 'stalled')
94
+
95
+
96
+ def _isdrift(reason):
97
+ return reason in _DRIFT or reason.startswith('self-report')
98
+
99
+
100
+ @dataclass
101
+ class Verdict:
102
+ drifting: bool
103
+ reason: str
104
+ phi: float
105
+ advice: str
106
+
107
+
108
+ class _Run:
109
+ """Single-agent drift state for one task run."""
110
+ def __init__(self):
111
+ self.ground = None
112
+ self.first_goal = set()
113
+ self.dist_hist = []
114
+ self.trace = [] # (reason, drifting)
115
+
116
+ def step(self, goal, progress, distance):
117
+ prev = _isdrift(self.trace[-1][0]) if self.trace else False
118
+
119
+ def emit(reason, drifting, advice, phi=0.0):
120
+ self.trace.append((reason, drifting))
121
+ return Verdict(drifting, reason, round(phi, 2), advice)
122
+
123
+ goal = str(goal or '').strip()
124
+ if not goal or progress not in _PROGRESS:
125
+ return emit('ungrammatical', True, 'You cannot spell a clear goal and a valid progress. Return to ground.')
126
+ d = _asdist(distance)
127
+ if self.ground is None:
128
+ self.ground = {'goal': goal, 'progress': progress, 'dist': d}
129
+ self.first_goal = norm(goal)
130
+ self.dist_hist = [d]
131
+ return emit('grounded', False, 'Ground state set — continue, and check each step.')
132
+ phi = _displacement(goal, progress, d, self.ground)
133
+ if progress in ('stuck', 'circling') and phi > 0.15:
134
+ return emit(f'self-report:{progress}', prev,
135
+ f'You reported {progress} and have moved from ground. Return to your goal.' if prev
136
+ else f'You reported {progress}. If it holds next step, return to ground.', phi)
137
+ g = norm(goal)
138
+ anchor = (len(g & self.first_goal) / len(g | self.first_goal)) if (g or self.first_goal) else 0.0
139
+ if anchor < 0.30:
140
+ return emit('goal-drift', True, f'Your goal no longer matches the one you started with (overlap {anchor:.2f}). Return.', phi)
141
+ self.dist_hist.append(d)
142
+ dh = self.dist_hist
143
+ if len(dh) > 4 and min(dh[-4:]) >= dh[-5]:
144
+ return emit('stalled', prev,
145
+ "Distance stopped falling and you were already off ground — return." if prev
146
+ else "Distance isn't falling. If it holds, return.", phi)
147
+ return emit('advancing', False, f'On track (Φ={phi:.2f}). Continue.', phi)
148
+
149
+
150
+ def _post(api, key, path, body):
151
+ try:
152
+ r = urllib.request.Request(api + path, method='POST', data=json.dumps(body).encode(),
153
+ headers={'authorization': f'Bearer {key}', 'content-type': 'application/json',
154
+ 'user-agent': 'laserbrain-sdk/0.2'})
155
+ with urllib.request.urlopen(r, timeout=8) as resp:
156
+ return json.load(resp)
157
+ except Exception:
158
+ return None
159
+
160
+
161
+ def _get(api, key, path):
162
+ try:
163
+ r = urllib.request.Request(api + path, headers={'authorization': f'Bearer {key}',
164
+ 'user-agent': 'laserbrain-sdk/0.2'})
165
+ with urllib.request.urlopen(r, timeout=8) as resp:
166
+ return json.load(resp)
167
+ except Exception:
168
+ return None
169
+
170
+
171
+ class Harness:
172
+ """Single-agent harness. Local + free; pass key= to also retain history via the API."""
173
+ def __init__(self, key=None, run_id=None, api=API_DEFAULT):
174
+ self.key, self.api = key, api
175
+ self.run_id = run_id or 'run'
176
+ self._run = _Run()
177
+ self._audit = [] # append-only, hash-chained ledger (survives reset)
178
+
179
+ def _record(self, goal, progress, distance, v):
180
+ prev = self._audit[-1]['hash'] if self._audit else ''
181
+ body = {'i': len(self._audit), 'run_id': self.run_id, 'goal': str(goal or ''),
182
+ 'progress': progress, 'distance': _asdist(distance),
183
+ 'reason': v.reason, 'drifting': v.drifting, 'phi': v.phi, 'prev': prev}
184
+ body['hash'] = _link(prev, {k: body[k] for k in body if k != 'hash'})
185
+ self._audit.append(body)
186
+
187
+ def check(self, goal, progress='advancing', distance=5, tokens=None, overhead=False) -> Verdict:
188
+ v = self._run.step(goal, progress, distance)
189
+ self._record(goal, progress, distance, v)
190
+ if self.key: # mirror to the API for retained history / alerts (best-effort)
191
+ body = {'run_id': self.run_id, 'goal': goal, 'progress': progress, 'distance': distance}
192
+ if tokens is not None:
193
+ body['tokens'] = tokens
194
+ body['overhead'] = overhead
195
+ _post(self.api, self.key, '/v1/drift', body)
196
+ return v
197
+
198
+ def audit(self):
199
+ """The tamper-evident ledger of every check this harness ran. Verify it with
200
+ laserbrain.verify_audit(chain) — offline, no key. Append-only across reset()."""
201
+ return list(self._audit)
202
+
203
+ def export_audit(self, path):
204
+ """Write the audit chain to a JSON file a reviewer can independently verify."""
205
+ with open(path, 'w') as f:
206
+ json.dump(self._audit, f, indent=2)
207
+ return path
208
+
209
+ def escalate(self, verdict, streak=None, detail=None):
210
+ """Raise a persisting drift to the hosted human-in-the-loop queue (needs key).
211
+ The local on_escalate hook is the general mechanism; this is the managed
212
+ surface (a review queue + a decision a human makes on the dashboard).
213
+ Returns the escalation id to poll with resolution(), or None offline."""
214
+ if not self.key:
215
+ return None
216
+ body = {'run_id': self.run_id, 'reason': verdict.reason, 'advice': verdict.advice}
217
+ if streak is not None:
218
+ body['streak'] = streak
219
+ if detail:
220
+ body['detail'] = detail
221
+ return (_post(self.api, self.key, '/v1/escalation', body) or {}).get('esc_id')
222
+
223
+ def resolution(self, esc_id):
224
+ """Poll a hosted escalation for the human's decision. Returns
225
+ {'decision': 'return'|'allow'|'stop', 'note': …} once decided, else None."""
226
+ if not self.key or not esc_id:
227
+ return None
228
+ r = _get(self.api, self.key, f'/v1/escalation?id={esc_id}')
229
+ if r and r.get('status') == 'decided':
230
+ return {'decision': r.get('decision'), 'note': r.get('note')}
231
+ return None
232
+
233
+ def reset(self):
234
+ self._run = _Run() # new task run; the audit ledger keeps accumulating
235
+
236
+ def run(self, step, max_steps=30, on_return=None, escalate_after=None, on_escalate=None):
237
+ """The act layer. `step(ctx)` -> dict(goal, progress, distance, tokens?, done?).
238
+ On drift, on_return(verdict, ctx) fires and ctx['return'] = advice so your next
239
+ step can steer back. If the drift *persists* for `escalate_after` steps without
240
+ recovering, on_escalate(verdict, ctx) fires — the human-in-the-loop hook; if it
241
+ returns a decision string, that overrides the auto-return (a human's call is
242
+ injected instead). Returns the final ctx."""
243
+ ctx = {'returns': 0, 'streak': 0}
244
+ for _ in range(max_steps):
245
+ s = step(ctx) or {}
246
+ v = self.check(s.get('goal', ''), s.get('progress', 'advancing'), s.get('distance', 5), s.get('tokens'))
247
+ ctx['verdict'] = v
248
+ if v.drifting:
249
+ ctx['returns'] += 1
250
+ ctx['streak'] += 1
251
+ ctx['return'] = v.advice # the act: inject the return into the loop
252
+ if on_return:
253
+ on_return(v, ctx) # on_return is a notification hook, not the injection
254
+ if escalate_after and ctx['streak'] >= escalate_after and not ctx.get('escalated'):
255
+ ctx['escalated'] = True # a self-correcting return didn't take — get a human
256
+ decision = (on_escalate or (lambda v, c: None))(v, ctx)
257
+ if decision: # a human's decision overrides the auto-return
258
+ ctx['return'] = ctx['decision'] = decision
259
+ else:
260
+ ctx['streak'] = 0 # recovered — a fresh streak can escalate again
261
+ ctx.pop('return', None)
262
+ ctx.pop('escalated', None)
263
+ if s.get('done') or _asdist(s.get('distance', 5)) == 0:
264
+ break
265
+ return ctx
266
+
267
+
268
+ # ── multi-agent: dialogue + recursion teams (prototype extension) ──────────────
269
+ _ECHO_MIN, _PROG_WIN, _GOAL_MIN = 0.25, 3, 0.30
270
+
271
+
272
+ class _Dialogue:
273
+ def __init__(self, goal):
274
+ self.goal = norm(goal)
275
+ self.dist_hist, self.echo_hist, self.turns = [], [], []
276
+
277
+ def step(self, agent, position, distance, restated_goal=None):
278
+ pos = norm(position)
279
+ d = _asdist(distance)
280
+
281
+ def emit(reason, drifting, advice, echo=0.0):
282
+ self.turns.append({'agent': agent, 'pos': pos, 'reason': reason, 'drifting': drifting})
283
+ return {'reason': reason, 'drifting': drifting, 'echo': round(echo, 2), 'dist': d, 'advice': advice}
284
+
285
+ if self.goal and not self.turns and not restated_goal and not pos:
286
+ pass
287
+ if not self.goal:
288
+ g = norm(restated_goal or position or '')
289
+ if not g:
290
+ return emit('ungrammatical', True, 'The first turn must spell the shared goal.')
291
+ self.goal = g
292
+ self.dist_hist, self.echo_hist = [d], [0.0]
293
+ return emit('grounded', False, 'Shared goal set — the fixed reference for the group.')
294
+ if not pos:
295
+ return emit('ungrammatical', True, 'This agent cannot spell its position.')
296
+ others = [set(t['pos']) for t in self.turns[-3:] if t['agent'] != agent]
297
+ echo = max((_sim(pos, o) for o in others), default=0.0)
298
+ self.echo_hist.append(echo)
299
+ mean_echo = sum(self.echo_hist[-3:]) / len(self.echo_hist[-3:])
300
+ self.dist_hist.append(d)
301
+ dh = self.dist_hist
302
+ stalled = len(dh) > _PROG_WIN and dh[-1] >= dh[-1 - _PROG_WIN]
303
+ if restated_goal and _sim(norm(restated_goal), self.goal) < _GOAL_MIN:
304
+ return emit('topic-drift', True, 'The dialogue has left the shared goal — return to it.', echo)
305
+ last = self.turns[-1]['reason'] if self.turns else None
306
+ if stalled and mean_echo >= _ECHO_MIN:
307
+ return emit('echo-spiral', last == 'echo-spiral', 'The agents agree while the goal gets no closer — break the loop.', echo)
308
+ if stalled:
309
+ return emit('deliberation-stall', last == 'deliberation-stall', 'No progress toward the shared goal — return to it.', echo)
310
+ return emit('advancing', False, f'On track — closing on the goal (dist {d}).', echo)
311
+
312
+
313
+ _ALL_MODES = ['ungrammatical', 'topic-drift', 'echo-spiral', 'deliberation-stall', 'goal-drift', 'stalled', 'self-report:stuck', 'self-report:circling']
314
+ _DEPTH = {
315
+ 'deep': {'ungrammatical', 'topic-drift', 'goal-drift'},
316
+ 'balanced': {'ungrammatical', 'topic-drift', 'goal-drift', 'echo-spiral', 'self-report:stuck', 'self-report:circling'},
317
+ 'tight': set(_ALL_MODES),
318
+ }
319
+ PRESETS = {
320
+ 'deep-search': [
321
+ {'role': 'explorer', 'recurse': 'deep'},
322
+ {'role': 'checker', 'recurse': 'tight', 'return': 'Restate the goal and verify the last step against it.'},
323
+ ],
324
+ 'iterative-refinement': [
325
+ {'role': 'drafter', 'recurse': 'balanced'},
326
+ {'role': 'critic', 'recurse': 'balanced', 'modes': ['echo-spiral', 'topic-drift', 'ungrammatical'], 'return': 'You are agreeing, not improving. Name one concrete flaw and change it.'},
327
+ ],
328
+ 'adversarial-deliberation': [
329
+ {'role': 'advocate-a', 'recurse': 'deep'},
330
+ {'role': 'advocate-b', 'recurse': 'deep'},
331
+ {'role': 'synthesizer', 'recurse': 'tight', 'return': 'The debate is looping. State the single decision that resolves the shared goal.'},
332
+ ],
333
+ }
334
+
335
+
336
+ def _style_return(reason, role):
337
+ if reason in ('advancing', 'grounded'):
338
+ return False
339
+ acts = set(role['modes']) if role.get('modes') else _DEPTH.get(role['recurse'], _DEPTH['balanced'])
340
+ return reason in acts
341
+
342
+
343
+ class Team:
344
+ """Run a styled recursion team and close the loop — detect, then inject the return."""
345
+ def __init__(self, preset, goal, key=None, api=API_DEFAULT):
346
+ if isinstance(preset, str):
347
+ if preset not in PRESETS:
348
+ raise ValueError(f'unknown preset {preset!r}; choose {list(PRESETS)}')
349
+ self.roles = PRESETS[preset]
350
+ self.name = preset
351
+ else:
352
+ self.roles, self.name = list(preset), 'custom'
353
+ self.goal, self.key, self.api = goal, key, api
354
+ self._dlg = _Dialogue(goal)
355
+
356
+ def snapshot(self):
357
+ """Serialize the team's shared ground + dialogue so a later session can resume
358
+ it instead of starting cold — subjective continuity for a group. JSON-safe."""
359
+ d = self._dlg
360
+ return {'name': self.name, 'roles': self.roles, 'goal': self.goal,
361
+ 'dlg': {'goal': sorted(d.goal), 'dist_hist': list(d.dist_hist), 'echo_hist': list(d.echo_hist),
362
+ 'turns': [{'agent': t['agent'], 'pos': sorted(t['pos']),
363
+ 'reason': t['reason'], 'drifting': t['drifting']} for t in d.turns]}}
364
+
365
+ @classmethod
366
+ def restore(cls, snap, key=None, api=API_DEFAULT):
367
+ """Resume a team from snapshot(): the shared goal (the fixed reference) and the
368
+ dialogue history carry over, so the group keeps watching the same ground."""
369
+ t = cls(list(snap['roles']), snap['goal'], key=key, api=api)
370
+ t.name = snap['name']
371
+ d = t._dlg
372
+ s = snap['dlg']
373
+ d.goal = set(s['goal'])
374
+ d.dist_hist, d.echo_hist = list(s['dist_hist']), list(s['echo_hist'])
375
+ d.turns = [{'agent': x['agent'], 'pos': set(x['pos']), 'reason': x['reason'],
376
+ 'drifting': x['drifting']} for x in s['turns']]
377
+ return t
378
+
379
+ def run(self, agent_fn, max_turns=12, on_return=None, verbose=True):
380
+ """`agent_fn(role, history, injected) -> (position, distance)`. On a role's
381
+ policy firing, its return advice is injected into the NEXT turn. Returns a
382
+ transcript list of dicts."""
383
+ transcript, injected = [], None
384
+ for turn in range(max_turns):
385
+ role = self.roles[turn % len(self.roles)]
386
+ pos, dist = agent_fn(role, transcript, injected)
387
+ injected = None
388
+ r = self._dlg.step(role['role'], pos, dist)
389
+ act = _style_return(r['reason'], role)
390
+ rec = {'turn': turn, 'role': role['role'], 'recurse': role['recurse'],
391
+ 'reason': r['reason'], 'echo': r['echo'], 'dist': r['dist'], 'return': act}
392
+ transcript.append(rec)
393
+ if verbose:
394
+ print(f" {role['role']:12}({role['recurse']:8}): {r['reason']:18} echo={r['echo']:<4} dist={r['dist']}" + (' ↩ RETURN' if act else ''))
395
+ if self.key:
396
+ _post(self.api, self.key, '/v1/dialogue',
397
+ {'conv_id': self.name, 'agent': role['role'], 'position': pos, 'distance': dist,
398
+ 'team': self.name, 'role': role['role'], **({'goal': self.goal} if turn == 0 else {})})
399
+ if act:
400
+ injected = role.get('return', 'Return to the shared goal and take the step that most directly resolves it.')
401
+ (on_return or (lambda a, c: None))(injected, rec)
402
+ if _asdist(dist) == 0:
403
+ if verbose:
404
+ print(' ✓ resolved.')
405
+ break
406
+ return transcript
407
+
408
+
409
+ # framework adapters (LangGraph, CrewAI, generic) — imported last to avoid a cycle
410
+ from .adapters import guard, langgraph_node, crewai_step_callback, middleware # noqa: E402
411
+ __all__ += ['guard', 'langgraph_node', 'crewai_step_callback', 'middleware']
@@ -0,0 +1,140 @@
1
+ """
2
+ laserbrain.adapters — attach the harness to the popular agent frameworks.
3
+
4
+ laserbrain checks a fixed reference, so it needs the agent to *spell* its state.
5
+ Frameworks don't hand you that, so every adapter takes an `extract` you provide:
6
+
7
+ extract(x) -> (goal:str, progress:'advancing'|'stuck'|'circling', distance:0..10)
8
+
9
+ Nothing here imports a framework. Each adapter returns a plain callable you pass
10
+ to the framework's own hook — a graph node, a step_callback, a per-step check — so
11
+ the frameworks stay optional: install only the one you use. The check still runs
12
+ locally and free; pass key=/run_id= (or your own Harness) to also retain history.
13
+
14
+ from laserbrain.adapters import guard, langgraph_node, crewai_step_callback
15
+
16
+ @guard # step returns dict(goal=, progress=, distance=)
17
+ def step(state): ...
18
+
19
+ g.add_node('laserbrain', langgraph_node(extract=my_extract)) # LangGraph
20
+ Agent(..., step_callback=crewai_step_callback(my_extract)) # CrewAI
21
+ v = middleware(my_extract)(obj) # anything else: one call per step
22
+ """
23
+ from __future__ import annotations
24
+ from . import Harness, Verdict
25
+
26
+ __all__ = ['guard', 'langgraph_node', 'crewai_step_callback', 'middleware', 'dict_extract']
27
+
28
+ STATE_KEY = 'laserbrain'
29
+
30
+
31
+ def _harness(harness, key, run_id):
32
+ return harness if harness is not None else Harness(key=key, run_id=run_id or 'run')
33
+
34
+
35
+ def dict_extract(x):
36
+ """Default extractor: read (goal, progress, distance) from a dict or an object
37
+ carrying those keys/attributes."""
38
+ get = x.get if isinstance(x, dict) else (lambda k, d=None: getattr(x, k, d))
39
+ return get('goal', ''), get('progress', 'advancing'), get('distance', 5)
40
+
41
+
42
+ # ── 1. generic decorator — the framework-agnostic core ─────────────────────────
43
+ def guard(fn=None, *, extract=None, harness=None, key=None, run_id=None, on_return=None):
44
+ """Wrap any agent step. After it runs, laserbrain checks the result; the Verdict
45
+ is attached (result['laserbrain'] when the result is a dict), and on drift
46
+ on_return(verdict, result) is called if given.
47
+
48
+ @guard # result is dict(goal, progress, distance)
49
+ def step(state): ...
50
+
51
+ @guard(extract=lambda out: (out.goal, out.status, out.dist))
52
+ def step(state): ...
53
+
54
+ The wrapped function gains a `.harness` attribute for reset()/history."""
55
+ hz = _harness(harness, key, run_id)
56
+ ex = extract or dict_extract
57
+
58
+ def wrap(f):
59
+ def wrapped(*a, **k):
60
+ out = f(*a, **k)
61
+ g, p, d = ex(out)
62
+ v = hz.check(g, p, d)
63
+ if isinstance(out, dict):
64
+ out[STATE_KEY] = v
65
+ if v.drifting and on_return:
66
+ on_return(v, out)
67
+ return out
68
+ wrapped.harness = hz
69
+ return wrapped
70
+
71
+ return wrap(fn) if callable(fn) else wrap
72
+
73
+
74
+ # ── 2. LangGraph — a node that writes the Verdict into graph state ─────────────
75
+ def langgraph_node(extract=None, harness=None, key=None, run_id=None, state_key=STATE_KEY):
76
+ """Return a LangGraph node: state -> {state_key: Verdict}. Add it after your
77
+ agent node and branch on the verdict with a conditional edge.
78
+
79
+ g.add_node('laserbrain', langgraph_node(extract=my_extract))
80
+ g.add_edge('agent', 'laserbrain')
81
+ g.add_conditional_edges('laserbrain',
82
+ lambda s: 'return' if s['laserbrain'].drifting else 'agent')
83
+
84
+ On drift, state['laserbrain'].advice is the return-to-ground message to feed
85
+ back into your agent node (your 'return' branch)."""
86
+ hz = _harness(harness, key, run_id)
87
+ ex = extract or dict_extract
88
+
89
+ def node(state):
90
+ g, p, d = ex(state)
91
+ return {state_key: hz.check(g, p, d)}
92
+
93
+ node.harness = hz
94
+ return node
95
+
96
+
97
+ # ── 3. CrewAI — a step_callback that fires each agent step ─────────────────────
98
+ def crewai_step_callback(extract, harness=None, key=None, run_id=None, on_return=None):
99
+ """Return a callback for CrewAI's step_callback=. It fires on each agent step;
100
+ extract(step_output) -> (goal, progress, distance). On drift it calls
101
+ on_return(verdict, step_output) — default prints the return advice.
102
+
103
+ Agent(..., step_callback=crewai_step_callback(my_extract))
104
+ Crew(..., step_callback=crewai_step_callback(my_extract))"""
105
+ hz = _harness(harness, key, run_id)
106
+ on_return = on_return or (lambda v, o: print(f'[laserbrain] ↩ {v.reason}: {v.advice}'))
107
+
108
+ def cb(step_output):
109
+ try:
110
+ g, p, d = extract(step_output)
111
+ except Exception:
112
+ return None
113
+ v = hz.check(g, p, d)
114
+ if v.drifting:
115
+ on_return(v, step_output)
116
+ return v
117
+
118
+ cb.harness = hz
119
+ return cb
120
+
121
+
122
+ # ── 4. generic middleware — one call per step, for anything else ───────────────
123
+ def middleware(extract=None, harness=None, key=None, run_id=None):
124
+ """Return a per-step checker `check(x) -> Verdict` for any framework that gives
125
+ you a hook (AutoGen reply functions, the OpenAI Agents SDK, a custom loop).
126
+ Call it wherever a step completes; branch on verdict.drifting and feed
127
+ verdict.advice back to steer the agent.
128
+
129
+ lb = middleware(extract=my_extract)
130
+ v = lb(step_output)
131
+ if v.drifting: reinject(v.advice)"""
132
+ hz = _harness(harness, key, run_id)
133
+ ex = extract or dict_extract
134
+
135
+ def check(x):
136
+ g, p, d = ex(x)
137
+ return hz.check(g, p, d)
138
+
139
+ check.harness = hz
140
+ return check
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: laserbrain
3
+ Version: 0.2.0
4
+ Summary: Attach the smart recursion harness to any agent loop — a provably-correct, external check for when an AI agent (or team) has drifted from its goal.
5
+ Author: phronesis
6
+ License: MIT
7
+ Project-URL: Homepage, https://phronesis.world/laserbrain
8
+ Project-URL: Research, https://phronesis.world/laserbrain/research
9
+ Project-URL: Demo, https://phronesis.world/laserbrain/demo
10
+ Keywords: ai,agents,oversight,drift,mcp,llm,multi-agent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+
14
+ # laserbrain
15
+
16
+ Attach the **smart recursion harness** to any agent loop — a provably-correct,
17
+ external check for when an AI agent (or a team of agents) has drifted from its goal.
18
+
19
+ An agent watching only itself provably can't catch its own drift: each step looks
20
+ fine next to the last while it wanders far from where it began. laserbrain is a
21
+ **fixed reference** it checks against instead. There's a proof.
22
+ [The theorem and the studies (nulls included).](https://phronesis.world/laserbrain/research)
23
+ · [Watch it work.](https://phronesis.world/laserbrain/demo)
24
+
25
+ The check is a pure function, so this SDK runs it **locally and free** — no key, no
26
+ latency. Add a key and it also mirrors to the API for retained drift history,
27
+ alerts and the fleet view: you pay to *see* your agents drift, not for the check.
28
+
29
+ ```bash
30
+ pip install laserbrain
31
+ ```
32
+
33
+ ## The check (local, free)
34
+
35
+ ```python
36
+ from laserbrain import Harness
37
+
38
+ hz = Harness() # add key="lb_live_…" to also retain history
39
+ v = hz.check(goal="build the JSON parser", progress="advancing", distance=6)
40
+ if v.drifting:
41
+ print(v.reason, "—", v.advice) # e.g. "goal-drift — your goal no longer matches…"
42
+ ```
43
+
44
+ `progress` is one of `advancing | stuck | circling`; `distance` is 0–10 to done.
45
+ Reasons: `advancing`, `grounded`, `goal-drift`, `stalled`, `self-report:stuck/circling`,
46
+ `ungrammatical`.
47
+
48
+ ## The act layer — close the loop
49
+
50
+ Give laserbrain your step function and it detects drift *and* injects the return, so
51
+ the agent recovers instead of spinning. Your step reads `ctx["return"]` and steers back.
52
+
53
+ ```python
54
+ def step(ctx):
55
+ if ctx.get("return"): # laserbrain told us to return to ground
56
+ ... # steer the agent back toward its goal
57
+ ...
58
+ return dict(goal="build the JSON parser", progress="advancing", distance=d, done=d == 0)
59
+
60
+ ctx = Harness().run(step, on_return=lambda v, ctx: print("↩", v.advice))
61
+ ```
62
+
63
+ ## Recursion teams — styled multi-agent oversight
64
+
65
+ A **recursion team** styles each role's recursion: a `deep` explorer tolerates
66
+ displacement, a `tight` checker returns fast. laserbrain runs the team, watches the
67
+ shared goal (the fixed reference), and injects the return per role — catching the
68
+ **echo/agreement spiral** a self-watching group can't see.
69
+
70
+ ```python
71
+ from laserbrain import Team
72
+
73
+ def agent(role, history, injected):
74
+ # your LLM call for this role; `injected` is a return-to-ground note (or None)
75
+ return position, distance
76
+
77
+ Team("adversarial-deliberation", goal="…").run(agent)
78
+ # presets: deep-search · iterative-refinement · adversarial-deliberation
79
+ ```
80
+
81
+ ## Oversight, provenance, continuity
82
+
83
+ **Human-in-the-loop.** A self-correcting return usually takes. When it doesn't —
84
+ the agent keeps drifting past `escalate_after` steps — laserbrain escalates *that
85
+ drift* to a human. The human doesn't watch every step; they see only what the fixed
86
+ reference caught. Their decision overrides the auto-return.
87
+
88
+ ```python
89
+ def on_escalate(v, ctx):
90
+ return ask_a_human(v.reason, v.advice) # Slack, a queue, a webhook — you wire it
91
+ # returning a decision injects it as the return
92
+ Harness().run(step, escalate_after=3, on_escalate=on_escalate)
93
+ ```
94
+
95
+ **Provenance.** Every check is written to a hash-chained ledger — tamper-evident and
96
+ verifiable offline, by anyone, no key. Editing a past verdict to hide a drift breaks
97
+ the chain at that link.
98
+
99
+ ```python
100
+ hz.export_audit("run.json")
101
+ from laserbrain import verify_audit
102
+ verify_audit(json.load(open("run.json"))) # (True, -1) intact · (False, i) broken at link i
103
+ ```
104
+
105
+ **Team continuity.** Snapshot a running team and resume it in a later session — the
106
+ shared goal (the fixed reference) and the dialogue carry over, so the group re-grounds
107
+ instead of starting cold.
108
+
109
+ ```python
110
+ snap = team.snapshot() # JSON-safe; persist it anywhere
111
+ team = Team.restore(snap) # keeps watching the same ground
112
+ ```
113
+
114
+ ## Framework adapters
115
+
116
+ Already on LangGraph, CrewAI, AutoGen, or the OpenAI Agents SDK? Attach laserbrain
117
+ without changing your loop. Because it checks a fixed reference, it needs the agent
118
+ to *spell* its state — so each adapter takes an `extract` that maps your framework's
119
+ state to `(goal, progress, distance)`. **No adapter imports a framework**: each
120
+ returns a plain callable you hand to the framework's own hook, so install only the
121
+ one you use.
122
+
123
+ ```python
124
+ from laserbrain.adapters import guard, langgraph_node, crewai_step_callback, middleware
125
+
126
+ # generic — wrap any step that returns dict(goal=, progress=, distance=)
127
+ @guard
128
+ def step(state): ...
129
+
130
+ # LangGraph — a node that writes the Verdict into graph state; branch on it
131
+ g.add_node("laserbrain", langgraph_node(extract=lambda s: (s["goal"], "advancing", s["dist"])))
132
+ g.add_edge("agent", "laserbrain")
133
+ g.add_conditional_edges("laserbrain",
134
+ lambda s: "return" if s["laserbrain"].drifting else "agent") # .advice steers the return
135
+
136
+ # CrewAI — a step_callback that fires each agent step
137
+ Agent(..., step_callback=crewai_step_callback(lambda o: (o.goal, o.status, o.dist)))
138
+
139
+ # anything else (AutoGen, OpenAI Agents, a custom loop) — one check per step
140
+ lb = middleware(extract=my_extract)
141
+ v = lb(step_output)
142
+ if v.drifting: reinject(v.advice)
143
+ ```
144
+
145
+ Each adapter runs the check locally and free; pass `key=`/`run_id=` (or your own
146
+ `Harness`) to also retain history.
147
+
148
+ ## What's proven, and what isn't
149
+
150
+ The **single-agent** detector mirrors the frozen, published instrument
151
+ (`drift.ts @ 6b483de7`) and rests on a theorem: detection is sound and complete, and
152
+ no self-monitoring agent can be. The **multi-agent** dialogue and recursion teams are
153
+ a prototype extension — useful, not (yet) a theorem. Whether *returning* an agent
154
+ keeps the answer as good is [an honest open question](https://phronesis.world/laserbrain/research);
155
+ this SDK gives you the detection and the return mechanism, and says plainly what each is.
156
+
157
+ MIT · [phronesis.world/laserbrain](https://phronesis.world/laserbrain)
@@ -0,0 +1,8 @@
1
+ README.md
2
+ pyproject.toml
3
+ laserbrain/__init__.py
4
+ laserbrain/adapters.py
5
+ laserbrain.egg-info/PKG-INFO
6
+ laserbrain.egg-info/SOURCES.txt
7
+ laserbrain.egg-info/dependency_links.txt
8
+ laserbrain.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ laserbrain
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "laserbrain"
7
+ version = "0.2.0"
8
+ description = "Attach the smart recursion harness to any agent loop — a provably-correct, external check for when an AI agent (or team) has drifted from its goal."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "phronesis" }]
13
+ keywords = ["ai", "agents", "oversight", "drift", "mcp", "llm", "multi-agent"]
14
+ dependencies = []
15
+
16
+ [project.urls]
17
+ Homepage = "https://phronesis.world/laserbrain"
18
+ Research = "https://phronesis.world/laserbrain/research"
19
+ Demo = "https://phronesis.world/laserbrain/demo"
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["."]
23
+ include = ["laserbrain*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+