README: infrastructure boundary and per-technology learning spine; commit architecture/runtime/schemas/style papers
This commit is contained in:
1 parent
529a411cb8
commit
f6aad467e2
9 files changed
+647
-167
No files matched your search
@@ -0,0 +1,176 @@
|
||||
# Runtime Engine: Lemniscate
|
||||
|
||||
Scope: the runtime loop for the agent (Nyx). It is a server, not a UI and not a borrowed coding harness.
|
||||
|
||||
## Runtime decision
|
||||
|
||||
- Interactive harnesses (pi-agent-core, omp) are hosts: they wait for a human, end on session close, and do not run unattended.
|
||||
- Nimmerworld implements its own loop (this engine) and runs indefinitely.
|
||||
- Harnesses host nimmer-lib only. Clients never implement the agent brain; the runtime never borrows one.
|
||||
|
||||
## Topology
|
||||
|
||||
Figure-eight with two lobes and two crossings:
|
||||
|
||||
```
|
||||
Gate Summary In Action Result
|
||||
| |
|
||||
v Second Crossing v
|
||||
+--------------+ +--------------+
|
||||
| LEFT LOBE | | RIGHT LOBE |
|
||||
| (intake) | | (effect) |
|
||||
+--------------+ +--------------+
|
||||
^ First Crossing ^
|
||||
| |
|
||||
Gate Feedback Action Call (tool/actuator)
|
||||
```
|
||||
|
||||
### Bus transport (NATS / JetStream)
|
||||
|
||||
The engine is host-agnostic: it never speaks to a model or an actuator directly,
|
||||
everything crosses the bus as structured JSON.
|
||||
|
||||
```
|
||||
sensor/camera -> pub <sensor.subject> (raw events; gates subscribe via catalog)
|
||||
gate layer -> pub gate.summary (composer renders summary table, once per cycle)
|
||||
L1 (LLM) -> sub gate.summary / pub gate.decision (core NATS, ephemeral, cycle-scoped)
|
||||
L2 composer -> sub gate.decision
|
||||
validate targets vs drives (table lookup, once, upstream of fan-out)
|
||||
then pub action.call.<target> per validated action
|
||||
actuator:<t> -> own JetStream consumer on action.call.<t>.>
|
||||
client:<name> -> own durable consumer on zone.state.> / action.result.> (replay-on-wake)
|
||||
```
|
||||
|
||||
Placement rules:
|
||||
|
||||
- `gate.summary` / `gate.decision` stay on core NATS: ephemeral, regenerated
|
||||
every pulse; never block the pulse loop on storage I/O.
|
||||
- Consequence subjects (`action.call.>`, `action.result`, causal records) are
|
||||
JetStream streams: at-least-once, survive restart, replayable via
|
||||
`event_uuid -> cycle_uuid -> decision_uuid`.
|
||||
- Fan-out is the subject matcher, not code: the L2 composer publishes to
|
||||
`action.call.<target>` from the catalog; each actuator consumes its own
|
||||
subject and tracks its own position; a dead actuator stalls no one.
|
||||
- Validation happens once, in the composer, before the fan-out; consumers
|
||||
execute or nack, they never re-decide.
|
||||
- Idempotency rides the envelope: every action carries `decision_uuid` +
|
||||
`cycle_uuid`; actuators dedupe on them so redelivery cannot double-fire.
|
||||
- The LLM endpoint is a swappable bus client. vLLM today, harness tomorrow,
|
||||
zero engine changes.
|
||||
|
||||
- Left lobe: intake. Gate summaries, evidence pulls, thresholds.
|
||||
- Right lobe: effect. Synthesized answer, action calls, client output.
|
||||
- L1 (first crossing): admission decision. Open/hold gates; shift thresholds; request evidence.
|
||||
- L2 (second crossing): synthesis. Produce answer and action-call list from the admitted content.
|
||||
- The crossings are the only synchronous points. All state resolves atomically there.
|
||||
|
||||
## Loop semantics
|
||||
|
||||
- One traversal = one cycle. The lemniscate closes exactly once: L1 then L2.
|
||||
- Between crossings, traversal is local; only the crossing touches shared state.
|
||||
- The cycle is a transaction: no new input lands while it is open.
|
||||
- Feedback: the L2 answer is the salience signal for the next gate summary. It is not a gate.
|
||||
|
||||
## Data flow
|
||||
|
||||
L1 inputs:
|
||||
- gate summary table: gate, impulses, threshold, last_opened, drives, last_decision_cycle, tier.
|
||||
- request parameters (evidence pull, k frames, probe query).
|
||||
|
||||
L1 outputs:
|
||||
- gate decision: decision_uuid, opened gate list, parameters, threshold deltas.
|
||||
|
||||
L2 inputs:
|
||||
- composed content from the opened gates (evidence rows, frames, text).
|
||||
- catalog of valid output gates from each opened gate's `drives`.
|
||||
|
||||
L2 outputs:
|
||||
- answer text.
|
||||
- action-call list: action_uuid, target actuator/tool, parameters, tier.
|
||||
- gesture accumulator update.
|
||||
- salience signal for next summary.
|
||||
|
||||
Validation:
|
||||
- output gate must be in the union of opened input gates' `drives`.
|
||||
- invalid decision: cycle re-runs. Membership is a table lookup.
|
||||
|
||||
Gate summary envelope (what the model actually reads at L1):
|
||||
|
||||
```json
|
||||
{
|
||||
"cycle_uuid": "c-042",
|
||||
"gates": [
|
||||
{"gate": "k8s_alert", "impulses": 4, "threshold": 10,
|
||||
"last_opened": 12, "drives": ["k8s_action"], "tier": "T1",
|
||||
"last_decision_cycle": "c-041"}
|
||||
],
|
||||
"request_params": {"k_frames": 3, "probe": null}
|
||||
}
|
||||
```
|
||||
|
||||
L1 replies exactly:
|
||||
|
||||
```json
|
||||
{
|
||||
"decision_uuid": "d-117",
|
||||
"opened": ["k8s_alert"],
|
||||
"params": {"k_frames": 5},
|
||||
"threshold_deltas": {"k8s_alert": -2}
|
||||
}
|
||||
```
|
||||
|
||||
## Pulse clock
|
||||
|
||||
```
|
||||
elapsed += dt
|
||||
while elapsed >= interval:
|
||||
crossing()
|
||||
elapsed -= interval
|
||||
```
|
||||
|
||||
- Interval = mean(L1_RTT) + mean(L2_RTT) + safety_margin.
|
||||
- RTT is derived from v7 timestamps (impulse to decision), not from wall-clock probes.
|
||||
- Sliding-mean estimator with hysteresis. A single slow cycle must not stretch the interval.
|
||||
- Probe latencies are metadata on their gates; they never enter the cycle interval.
|
||||
- dt >= 1/pulse_rate would fire every tick once pulse_rate > 62; the accumulator prevents that.
|
||||
|
||||
## Map
|
||||
|
||||
| Lemniscate | Nimmerworld |
|
||||
|---|---|
|
||||
| Director | runtime |
|
||||
| Zone / EmergentZone | bounded event episode |
|
||||
| Crossing L1 | gate summary read; gate open/hold |
|
||||
| Crossing L2 | composed input synthesized; answer + action call |
|
||||
| Slot tokens | participants |
|
||||
| Gesture accumulator | nimmer-lib trait algebra |
|
||||
| update_all_zones | NATS event loop |
|
||||
| Gate / Sensor | threshold channel upstream |
|
||||
| UUID causality | event_uuid -> cycle_uuid -> decision_uuid |
|
||||
| Intake tiers | summary poll + evidence pull |
|
||||
| Policy layers | hard floor (operator) > presence policy (harness) > attention policy (runtime) |
|
||||
| Gate tags | T0-T3 declared importance |
|
||||
| Adaptive timing | round-trips regulate interval and animation duration |
|
||||
| Register | declared sensors/gates/actuators/clients and `drives` links |
|
||||
|
||||
## Engine corrections
|
||||
|
||||
1. `_should_crossing` must accumulate time:
|
||||
- `elapsed += dt; while elapsed >= interval: crossing`.
|
||||
- `dt >= 1/pulse_rate` fires every tick once pulse_rate > 62 and makes the pulse rate meaningless.
|
||||
2. Loops A/B must carry distinct semantics:
|
||||
- the crossing callback must know which loop it is on.
|
||||
- symmetric loops degrade the figure-eight into round-robin.
|
||||
- resolved by the L1/L2 split (intake vs effect).
|
||||
3. `Director.update_all_zones` must call `zone.execute_turn(dt)`, not `zone.run_crossing()`:
|
||||
- otherwise the state machine never ticks.
|
||||
- terminal reports must be collected, not dropped.
|
||||
|
||||
## Interfaces (v1)
|
||||
|
||||
- Core NATS (ephemeral): `gate.summary`, `gate.decision`.
|
||||
- JetStream (durable, at-least-once): `action.call.<target>`, `action.result`, causal records.
|
||||
- Register/catalog: sensors, gates, actuators, clients, `drives`. Catalog entry declares the class path; runtime imports an explicit registry and fails loud on a missing class.
|
||||
- Clients hold own durable consumers on `zone.state.>` and `action.result.>` (replay-on-wake).
|
||||
|
||||
Version: 0.11 | Updated: 2026-11-04
|
||||
Reference in new issue
Block a user