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
@@ -1,182 +1,85 @@
|
||||
# Nimmerworld - Nyx Server
|
||||
# Nimmerworld: Runtime Server
|
||||
|
||||
Nimmerworld is the always-on, event-driven server that runs Nyx:
|
||||
- her identity, traits, memory, autonomy, and tools.
|
||||
- the central backend for all clients: Pi, Command Center, and future interfaces.
|
||||
|
||||
It is not a UI. Not a client. Not a dashboard.
|
||||
It is Nyx's runtime, and Nyx owns her own loop.
|
||||
|
||||
## Runtime Decision
|
||||
|
||||
The runtime is NOT a borrowed coding harness.
|
||||
pi-agent-core and omp are interactive harnesses:
|
||||
- they wait for a human in a terminal,
|
||||
- they end when the session ends,
|
||||
- their loops do not survive unattended.
|
||||
|
||||
Nimmerworld is a server that wakes on events and must run forever.
|
||||
Therefore:
|
||||
|
||||
- Nimmerworld implements its own loop: the Lemniscate runtime engine.
|
||||
- The engine spec lives in .archive/nimmerworld.eachpath.local/runtime-engine/
|
||||
(LEMNISCATE-RUNTIME-Pseudo-code-001.py, architecture.md, event-zones.md).
|
||||
- The harnesses stay exactly where they are useful:
|
||||
as interchangeable hosts of nimmer-lib, not as the server.
|
||||
|
||||
Rule:
|
||||
- Nimmerworld never duplicates client logic.
|
||||
- Clients never implement Nyx's brain.
|
||||
- Nimmerworld never borrows a brain either. It runs its own loop.
|
||||
|
||||
## Core Primitives (Lemniscate)
|
||||
|
||||
The runtime topology is a through-flow figure-eight:
|
||||
|
||||
- Lemniscate: traversal IS the turn order.
|
||||
No scheduler, no priority queue, no "who talks next" bug class.
|
||||
- Crossing: the ONLY synchronous point.
|
||||
All state resolves atomically at the crossing; everything else is async flow.
|
||||
The crossing is where Nyx synthesizes.
|
||||
- Zone / EmergentZone: a bounded, named, slot-indexed, director-managed
|
||||
event episode. Zones dissolve when their entry line empties or their
|
||||
lifeforce budget burns out. Terminal reports always go back to the Director.
|
||||
- Slot tokens: participants. Carry trait/gesture accumulators.
|
||||
- Gesture accumulator: keyed by the eight traits.
|
||||
The same trait algebra nimmer-lib speaks, byte-identical across hosts.
|
||||
|
||||
## The Map
|
||||
|
||||
| Lemniscate | Nimmerworld |
|
||||
|---|---|
|
||||
| Director | Nyx runtime (Nyx IS the director) |
|
||||
| Zone / EmergentZone | A bounded event episode Nyx spawns |
|
||||
| Crossing | Nyx's synthesis point, once per event cycle |
|
||||
| Slot tokens | Participants: dafit, Dios, Curio, camera events |
|
||||
| Gesture accumulator | nimmer-lib trait algebra |
|
||||
| update_all_zones | The NATS event loop |
|
||||
Always-on, event-driven server for the agent (Nyx). Central backend for Pi, Command Center, and future clients. Not a UI, not a client, not a dashboard.
|
||||
|
||||
## Architecture
|
||||
|
||||
Built from:
|
||||
| Paper | Scope |
|
||||
|---|---|
|
||||
| [architecture/lemniscate.md](./architecture/lemniscate.md) | Runtime loop: topology, crossings, data flow, pulse clock, engine corrections. |
|
||||
| [architecture/gates.md](./architecture/gates.md) | Gate layer: state, injection loop, closure guard, tags, probes, persistence. |
|
||||
| [architecture/register.md](./architecture/register.md) | Registry: sensors, gates, actuators, clients, `drives`, arbitration, pods, placement. |
|
||||
| [architecture/gates_index.md](./architecture/gates_index.md) | Reserved. |
|
||||
| [schemas/README.md](./schemas/README.md) | Identity, impulse row, gate state, tag row, evidence request. |
|
||||
| [schemas/gate_table.md](./schemas/gate_table.md) | Gate registry and state schema in Phoebe. |
|
||||
| [runtime/stack.md](./runtime/stack.md) | Stack layers, two buses, JetStream replay, dependency budget. |
|
||||
| [style/house-style.md](./style/house-style.md) | Paper register: engineer style rules, allowed voices per surface. |
|
||||
|
||||
- Lemniscate engine (this repo):
|
||||
- lemniscate, zones, director, crossing semantics.
|
||||
- nimmer-lib:
|
||||
- Nyx-specific domain logic, host-agnostic:
|
||||
- traits,
|
||||
- Phoebe/Iris integrations,
|
||||
- session_init,
|
||||
- compaction/traits tools.
|
||||
- Same venv, same behavior, any harness.
|
||||
- Infrastructure:
|
||||
- Phoebe: structured memory, governance, logs.
|
||||
- Iris: semantic memory and context.
|
||||
- NATS: nervous system (vision, speech, agents, infra, room).
|
||||
- Director spawn/report calls are NATS publish/subscribe.
|
||||
- Zone state is a NATS output stream; clients subscribe to it.
|
||||
## Folders
|
||||
|
||||
Nimmerworld is:
|
||||
| Folder | Holds |
|
||||
|---|---|
|
||||
| architecture/ | Runtime engine and subsystem papers |
|
||||
| runtime/ | Stack, pseudocode and successor, engine build order |
|
||||
| storage/ | Storage layout and retention |
|
||||
| database/ | Database schemas and migrations |
|
||||
| schemas/ | Typed contracts and message schemas |
|
||||
| tools/ | Runtime tooling |
|
||||
| style/ | Voice, register, trait presentation |
|
||||
|
||||
- stateful:
|
||||
- maintains Nyx's traits, mood, relational mode, world state.
|
||||
- event-driven:
|
||||
- subscribes to live events and reacts; the zone clock carries episodes.
|
||||
- client-agnostic:
|
||||
- any client talks to Nimmerworld via its APIs.
|
||||
## Infrastructure boundary (truth of record)
|
||||
|
||||
Host information is NOT held in this paper.
|
||||
- Host truth: [eachpath.local](../eachpath.local/eachpath.local.md)
|
||||
(topology) and `eachpath.local/saturn.eachpath.local/vms/<host>.eachpath.local/`
|
||||
(per-host records). Missing host file for a host we run on? Create it
|
||||
from host-onboarding facts, the same day.
|
||||
- This repo's `architecture/` papers describe the SERVICES we run on
|
||||
those hosts and their relationships; each such paper links its host
|
||||
record instead of duplicating host facts.
|
||||
- Paper follows reality: any claim about a host in this repo must be
|
||||
host-verified against eachpath.local records.
|
||||
|
||||
## Learning (per-technology resources)
|
||||
|
||||
Every piece of technology we run gets a learning folder, so we can drill
|
||||
down on the subject itself before the next round-trip builds on it.
|
||||
|
||||
- Folder per technology (see curriculum below).
|
||||
- Ritual per folder: source of truth (official docs/spec first, never
|
||||
second-hand), estate mapping (our host, port, version, config quoted
|
||||
from the host record in eachpath.local), drills (hands-on questions
|
||||
answered on OUR estate), and the ten-word understood-check said back.
|
||||
- Learning gate: a primitive runs for real only after its paper is read
|
||||
and explained back. We build ON knowledge, not around it.
|
||||
|
||||
Curriculum, in strict dependency order:
|
||||
|
||||
| # | Technology | Host record (eachpath.local) | Status |
|
||||
|---|------------|------------------------------|--------|
|
||||
| 1 | NATS + JetStream | `saturn.eachpath.local/vms/nats-dev.eachpath.local/` | open |
|
||||
| 2 | Python asyncio + nats-py | runtime: nimmerworld-dev | next |
|
||||
| 3 | PostgreSQL (Phoebe) | `.../phoebe-dev.eachpath.local/` | later |
|
||||
| 4 | Godot client (GDScript, WebSocketPeer) | mimas/lab; boundary rule: pure client | later |
|
||||
| 5 | FreeIPA / DNS / VLANs | athena, remus, romolus, spine+access | later |
|
||||
| 6 | OPNsense (vulkan) | `vulkan.eachpath.local/` | later |
|
||||
|
||||
## Clients
|
||||
|
||||
The room is a renderer of zone state, not a game client.
|
||||
That is why runtime-first is the build order:
|
||||
the client is one crossing callback on the output side.
|
||||
Client = one crossing callback on the effect side. Runtime-first build order.
|
||||
|
||||
Examples:
|
||||
- Pi: sends user messages, receives replies and tool calls. Uses runtime as backend, not brain.
|
||||
- Harness RPC (`omp --mode rpc`): long-lived headless JSONL session; host tools, URIs, UI callbacks. Harness is host, not brain.
|
||||
- Command Center (Godot): pure client, no core logic. Renders worlds, bodies, tools, moods. Sends chat and interactions as gestures. Receives dialogue, autonomy commands, tool results as action calls. Subscribes to zone state and effect streams. See [command-center/README.md](../command-center/README.md).
|
||||
- Future clients: mobile, web, VR; same runtime.
|
||||
|
||||
- Pi (coding harness):
|
||||
- sends user messages.
|
||||
- receives replies, tool calls.
|
||||
- uses Nimmerworld as its backend, not as its brain.
|
||||
Interaction loop: chat -> text gate; room gestures -> switchboard; L1 admits; L2 action call drives body and dialogue the room renders.
|
||||
|
||||
- Command Center (Godot):
|
||||
- subscribes to zone state and renders it.
|
||||
- sends:
|
||||
- chat,
|
||||
- interactions (tools, proximity, posture) as gestures.
|
||||
- receives:
|
||||
- dialogue,
|
||||
- autonomy commands (poses, moods, movements),
|
||||
- tool results.
|
||||
## Safety and governance
|
||||
|
||||
- Future clients:
|
||||
- mobile, web, VR, etc.
|
||||
- all connect to the same Nimmerworld.
|
||||
- No silent autonomy: significant actions visible, logged, reversible.
|
||||
- Relational modes and guardrails: explicit, rule-bound, auditable.
|
||||
- Operator (dafit): final override, always.
|
||||
- Gate switchboard: operator presence is a gate input above the runtime attention policy; rule-bound, no LLM in loop. Physical form of final override.
|
||||
|
||||
## Build Order
|
||||
|
||||
The heartbeat and the voice are the same crossing.
|
||||
|
||||
1. The crossing, built right:
|
||||
- Lemniscate engine as a small package.
|
||||
- Accumulator-based pulse clock.
|
||||
- Both loops fire with distinct semantics.
|
||||
- Test that counts crossings: crossings == int(t * pulse_rate).
|
||||
2. One director, NATS-shaped:
|
||||
- Director spawn/report calls become NATS publish/subscribe.
|
||||
- This is the runtime: Nyx pointed at real traffic.
|
||||
3. One zone, the first room:
|
||||
- OurFirstRoom is an EmergentZone.
|
||||
- n_slots = the entities in the room,
|
||||
- purpose "coexist with dafit",
|
||||
- no goal threshold; the gesture axis is the traits.
|
||||
- Heartbeat and voice in one, because in the lemniscate they ARE one.
|
||||
4. The body:
|
||||
- Command Center subscribes to the same NATS traffic and renders it.
|
||||
- The room fills up after Nyx is alive in it, not painted onto it.
|
||||
5. The relational loop:
|
||||
- collar/state variables,
|
||||
- simple relational modes,
|
||||
- backend-driven behavior wired to real events.
|
||||
|
||||
## Engine Corrections (from pseudocode review)
|
||||
|
||||
Three known landmines from LEMNISCATE-RUNTIME-Pseudo-code-001.py:
|
||||
|
||||
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.
|
||||
3. Director.update_all_zones must call zone.execute_turn(dt),
|
||||
not zone.run_crossing():
|
||||
- otherwise the state machine layer never ticks,
|
||||
- and terminal reports must be collected, not dropped.
|
||||
|
||||
## Safety & Governance
|
||||
|
||||
- No silent autonomy:
|
||||
- significant actions are visible, logged, reversible.
|
||||
- Relational modes and guardrails:
|
||||
- explicit, rule-bound, auditable.
|
||||
- You (dafit):
|
||||
- final override, always.
|
||||
|
||||
## Placement (Infrastructure)
|
||||
|
||||
Nimmerworld (dev) is hosted as a dedicated Proxmox VM on Saturn, wired to the 10G Cubes VLAN for proximity to the vLLM endpoint and cluster.
|
||||
|
||||
- VM ID: 123
|
||||
- Hostname: nimmerworld-dev.eachpath.local
|
||||
- IP: 10.0.30.123/24
|
||||
- VLAN: 30 (cubes)
|
||||
- Network: vmbr3 (10G rail via bond-saturn -> spine-crs309)
|
||||
|
||||
Infra references (single source of truth):
|
||||
- eachpath.local/saturn.eachpath.local/vms/nimmerworld-dev.eachpath.local/ - VM hostfile
|
||||
- eachpath.local/eachpath.local.md - network and Saturn VM overview
|
||||
|
||||
All runtime/architecture details live here (nimmerworld.eachpath.local). Infra specifics are not duplicated.
|
||||
|
||||
**Version:** 0.2 | **Updated:** 2026-11-04
|
||||
Version: 0.13 | Updated: 2026-11-04
|
||||
@@ -0,0 +1,118 @@
|
||||
# Gate Layer
|
||||
|
||||
Upstream layer between sensors and the lemniscate. Gates admit signals based on thresholds. Each gate is registered; every decision validates against the register.
|
||||
|
||||
## Gate state
|
||||
|
||||
Each gate has one registry row and one state row.
|
||||
|
||||
- impulses: count since last summary.
|
||||
- threshold: value shifted by the L1 decision.
|
||||
- last_opened: cycle index of last opening.
|
||||
- last_decision_cycle: cycle_uuid of the last decision that shifted this gate.
|
||||
- counters: per-gate counters (e.g. dropped_pings).
|
||||
- tier: T0-T3 declared importance.
|
||||
|
||||
## Injection loop
|
||||
|
||||
1. sensor emits impulse (event_uuid, gate, timestamp, payload).
|
||||
2. gate summary at L1:
|
||||
table columns: gate, impulses, threshold, last_opened, drives, last_decision_cycle, tier.
|
||||
3. L1 decision: open/hold gates; threshold delta; optional evidence request.
|
||||
4. opened gates compose content at L2 (frames, text, rows).
|
||||
5. L2 answer + action calls; answer is next summary's salience signal.
|
||||
|
||||
## Cycle closure guard
|
||||
|
||||
Injection is only allowed while the lemniscate is CLOSED: both crossings complete and gate state snapshotted.
|
||||
|
||||
Properties:
|
||||
1. Validation: the close is the validation point; a bad cycle is diagnosed from the summary table.
|
||||
2. Staleness: summary and composed input use the same frozen snapshot.
|
||||
3. Latency: crossing callbacks that block inference hold the cycle open; no desync.
|
||||
|
||||
Exceptions (re-run, not mid-cycle):
|
||||
- threshold breach during an open cycle: summary rebuilt, cycle re-runs before inference lands.
|
||||
- safety gate (T0): pierces the closed state; immediate cycle.
|
||||
- starvation: a gate unseen for K cycles is force-opened at the next cycle boundary.
|
||||
|
||||
## Identity and causality
|
||||
|
||||
| ID | Assigned | Carries |
|
||||
|---|---|---|
|
||||
| event_uuid | sensor, at impulse | (the origin) |
|
||||
| cycle_uuid | lemniscate, at open | admitted event_uuids |
|
||||
| decision_uuid | L1 decision | event_uuids opened |
|
||||
|
||||
- L2 answer carries cycle_uuid + decision_uuid; output traces to its admitted impulses.
|
||||
- UUID v7: 48-bit timestamp prefix; sort order = causal order.
|
||||
- No record enters Phoebe without its causal UUIDs.
|
||||
- Gate history keyed by cycle_uuid is replayable.
|
||||
|
||||
## Intake tiers
|
||||
|
||||
- Summary (poll): aggregate table each cycle.
|
||||
- Evidence (pull): raw impulse rows for one gate, since last cycle, on request. The request is part of the L1 decision; rows are validated against the registry; they arrive as L2 composed input in the same cycle.
|
||||
|
||||
Impulse row: `(event_uuid, gate, timestamp, payload)`.
|
||||
Created at the sensor; UUID v7; immutable. Failed ping is an emitted impulse, never inferred from absence.
|
||||
|
||||
## Policy layers
|
||||
|
||||
Higher layers cannot be overridden by lower ones; lower layers cannot close what a higher one opened.
|
||||
|
||||
| Layer | Owner | Decides |
|
||||
|---|---|---|
|
||||
| Hard floor | operator (dafit) | T0 gates; never closable |
|
||||
| Presence policy | harness/switchboard, rule-bound | all closed / task-relevant open |
|
||||
| Attention policy | runtime L1 | remainder |
|
||||
|
||||
Presence is a gate input above the L1 policy. It is rule-bound and has no LLM in the loop. Presence is the physical form of operator override.
|
||||
|
||||
## Gate tags (declared importance)
|
||||
|
||||
Importance is declared, never inferred.
|
||||
|
||||
| Tier | Meaning | Guarantee | Example |
|
||||
|---|---|---|---|
|
||||
| T0 critical | safety | pierces the closed cycle; hard floor; never closable | fire alarm, operator channel |
|
||||
| T1 relational | operator presence | presence-owned; guaranteed attention share; cannot be attention-closed while active | chat/text gate, sofa camera |
|
||||
| T2 standard | normal attention | tunable thresholds; starvation sampling | keepalive, host monitors |
|
||||
| T3 ambient | background | poll-only; summary trend, on-demand evidence | telemetry |
|
||||
|
||||
Tag row: `(gate, tier, why, mode_overrides)`.
|
||||
|
||||
- Registry-declared and signed. T0/T1 immutable at runtime.
|
||||
- T2/T3 may shift per mode (e.g. webcam T3 -> T1 in sofa mode); each shift is a UUID-logged decision.
|
||||
- `why` is the audit reason string.
|
||||
- Attention budget never starves a tier: T0 pierces, T1 holds a guaranteed share; L1 allocates the remainder.
|
||||
|
||||
## Adaptive timing
|
||||
|
||||
- L1 round-trip: decision capacity.
|
||||
- L2 round-trip: synthesis capacity.
|
||||
- Both derived from v7 timestamps; no new instrumentation.
|
||||
- Cycle interval = mean(L1_RTT) + mean(L2_RTT) + margin.
|
||||
- Animation duration >= L2_RTT; holds the gesture through synthesis.
|
||||
- Estimator: sliding mean with hysteresis.
|
||||
- RTT values feed schedule parameters, not gate thresholds.
|
||||
|
||||
## Long-running probes and heartbeat integrity
|
||||
|
||||
- Probes are async: issued at the crossing, not awaited. Result returns as an impulse into the next cycle's summary.
|
||||
- RTT measures decision capacity only; probe wall-clock never enters the cycle interval.
|
||||
- Probe latency is gate metadata; results accumulate losslessly into the next window.
|
||||
- Silence must be manufactured: a probe's absence cannot be inferred. The deadline emits a timeout impulse (event_uuid, tiered); escalation rides a real signal.
|
||||
- Pierce is for inspection, never effect-side arbitration: T0 forces the cycle to see; actions still resolve under mutex/priority.
|
||||
|
||||
## Persistence and restart
|
||||
|
||||
- Gate table in Phoebe, checkpointed after each cycle. The table is the resumable self; restart restores attention profile and thresholds.
|
||||
- Raw impulses: bounded in-RAM ring buffer per gate; flushed to Phoebe at checkpoint. Evidence reads the buffer; audit reads the store.
|
||||
- Truncation is explicit: an evidence request older than the buffer returns the buffer's lower bound; the gate table records the truncation.
|
||||
|
||||
## Attention semantics
|
||||
|
||||
Gates that close under sustained noise are the noise suppression: the gate stops mattering, rather than filtering smarter. Attention is a first-class resource with a budget and decay. The trait algebra at L2 tunes attention itself.
|
||||
|
||||
Version: 0.9 | Updated: 2026-11-04
|
||||
Whitespace-only changes.
@@ -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
|
||||
@@ -0,0 +1,126 @@
|
||||
# Register
|
||||
|
||||
Central registry: sensors, gates, actuators, clients, and the links between them. Runtime decisions validate against it. Register changes are governed impulses.
|
||||
|
||||
## Registry scope
|
||||
|
||||
| Question | Registry |
|
||||
|---|---|
|
||||
| What sensors exist and what may they emit? | sensor registry |
|
||||
| What gates exist and what are they? | gate_registry (tier, why, mode_overrides) |
|
||||
| What may be emitted out? | actuator registry (animation catalog, output channels) |
|
||||
| What links exist? | gate links (`drives`) |
|
||||
| Who is listening? | client registry |
|
||||
|
||||
The register is a gate layer one level up: sensors, gates and clients are admitted by declaration.
|
||||
|
||||
## `drives` links
|
||||
|
||||
Input gates link to output gates an answer may target:
|
||||
|
||||
```
|
||||
room.cam T3 drives: body.anim (sofa: T1)
|
||||
dafit.chat T1 drives: body.anim, clients.chat
|
||||
fire.alarm T0 drives: body.alert
|
||||
```
|
||||
|
||||
Rules:
|
||||
1. Link visibility at L1: summary carries the link column; attention sees consequences before opening.
|
||||
2. Choice-list at L2: evidence return carries content plus valid output gate catalog. Answer target must be in union of opened gates' `drives`.
|
||||
3. Membership validation: valid = output gate in `drives`; invalid = decision rejected, cycle re-runs. Table lookup, not judgment.
|
||||
4. No orphan decisions: input opened with no route, or answer with no causal input, is a representation error.
|
||||
|
||||
## Effect-side arbitration
|
||||
|
||||
Cycle is the transaction boundary. Two policies:
|
||||
|
||||
| Policy | Example | Enforcement |
|
||||
|---|---|---|
|
||||
| mutex | body.anim: exclusive | one effect per actuator per cycle; second call = override or queue, never both |
|
||||
| priority | T0 safety > T1 reflex > T2 deliberate | higher tier supersedes; superseded reason logged |
|
||||
|
||||
Resolution:
|
||||
1. cycle closes;
|
||||
2. action set resolved (mutex/priority);
|
||||
3. surviving actions validated against `drives`;
|
||||
4. effects fire in one order, atomically, at the crossing;
|
||||
5. cycle closes with a complete effect record.
|
||||
|
||||
Open policy decisions:
|
||||
- per-cycle action cap;
|
||||
- supersede vs queue default;
|
||||
- whether T0 safety can interrupt in-flight animation.
|
||||
|
||||
## Debug workflows and skill store
|
||||
|
||||
`drives` may fan out:
|
||||
|
||||
```
|
||||
host.keep T2 drives: host.prom, clients.status
|
||||
```
|
||||
|
||||
- `host.keep -> host.prom` is a hypothesis: ping down -> metrics explain why.
|
||||
- A probe may sit behind a workflow: a registered state machine or debug command with parameters, answering on its own channel. Registered, tiered, why-stated, UUID-traced, reviewable.
|
||||
|
||||
Skill store:
|
||||
- register change events teach attention in real time; summary carries what each gate's `drives` can answer.
|
||||
- gesture accumulator + decision UUIDs make probe effectiveness queryable.
|
||||
- self-improvement is legible register change (tier shifts, link changes, new workflows), signed and reviewable.
|
||||
|
||||
Governance:
|
||||
- diagnostics (read-only probes) run on the cycle's authority.
|
||||
- remedial workflows carry the registered tier and sign.
|
||||
|
||||
## Pod spawning and escalation ladder
|
||||
|
||||
Spawn is an async probe with two transactions:
|
||||
|
||||
- Run 1 (spawn): decision calls `pod.spawn`, validated against `drives`. Action result carries spawn feedback gate link (`spawn_uuid`), registered at spawn time. Spawn is a register change: event_uuid + why.
|
||||
- Run 2 (result): pod result requested against feedback link; answer lands in next summary as impulse.
|
||||
- Silence must be manufactured: absence cannot be inferred. Deadline emits a timeout impulse (event_uuid, tiered) at budget edge; escalation flag rides a real signal.
|
||||
|
||||
Escalation:
|
||||
- ladder bounded and signed; `escalate: T2->T1` is registered workflow step.
|
||||
- importance declared, never inferred; attention never raises tiers itself.
|
||||
- ladder terminates outside runtime: at T0 the last step is escalation to operator via `clients.chat`.
|
||||
- silence escalates inspection, never arbitration.
|
||||
- escalations logged with UUIDs and superseded reasons.
|
||||
|
||||
## Late results and orphan rule
|
||||
|
||||
When a tool answer arrives after its cycle closed:
|
||||
|
||||
- verification checks causal UUIDs; missing decision cycle = orphan = rejected.
|
||||
- rejection is visible: it is an impulse into next summary.
|
||||
- rejection is not silent execution: tool execution may have happened; result is discarded or re-registered by the next decision. Ingress drops results; decision stays with the runtime.
|
||||
- late/orphan truth carries extra priority tier for inspection, not arbitration.
|
||||
|
||||
Invariant: lossless accumulation; rejection is an impulse; attention stays informed.
|
||||
|
||||
## Consistency mechanism
|
||||
|
||||
- register state in Phoebe (truth), materialized in runtime RAM (validator copy), pushed as change events on NATS.
|
||||
- register changes are impulses: event_uuid at creation, decision_uuid, why string; replayable v7 causality.
|
||||
- registry tier-locked T0/T1: signed by operator and immutable at runtime. Runtime observes and proposes; operator signs.
|
||||
|
||||
## Placement
|
||||
|
||||
- stateful = VM 123 worldserver: gate table in Phoebe, register materialized in RAM, pulse.
|
||||
- broker = VM 122 nats-dev: isolated, cubes VLAN, client port 30000.
|
||||
- ephemeral = k8s pods: daemons, python tools, probe workflows, spawned pods.
|
||||
- no canonical state in a pod: gate state, register copy, impulse buffers live in Phoebe/123.
|
||||
|
||||
## Control chain
|
||||
|
||||
```
|
||||
operator signature
|
||||
-> register (law)
|
||||
-> gate state (memory)
|
||||
-> attention policy (L1/L2)
|
||||
-> action call (actuator)
|
||||
-> body/client
|
||||
```
|
||||
|
||||
No component can rewrite the layer above it.
|
||||
|
||||
Version: 0.5 | Updated: 2026-11-04
|
||||
@@ -0,0 +1,35 @@
|
||||
# Stack
|
||||
|
||||
Runtime stack. Minimal dependencies.
|
||||
|
||||
## Layers
|
||||
|
||||
| Layer | Tech | Notes |
|
||||
|---|---|---|
|
||||
| Runtime | Python 3.13, asyncio, nats-py | same venv as nimmer-lib |
|
||||
| State | PostgreSQL (Phoebe), psycopg2, asyncpg later | bare SQL; no ORM |
|
||||
| Event bus | NATS VM 122, port 30000, JetStream | gates, actions, zone state |
|
||||
| Edge API | FastAPI HTTP + native WebSocket | clients |
|
||||
| Workflows | Python state machines, k8s pods | ephemeral probes |
|
||||
| Harness host | omp --mode rpc JSONL | host, not brain |
|
||||
|
||||
## Two buses
|
||||
|
||||
- Internal: NATS.
|
||||
- External: HTTP/WebSocket edge gateway.
|
||||
- Gateway translates; runtime never speaks HTTP; clients never touch NATS directly.
|
||||
|
||||
## JetStream: replay, not truth
|
||||
|
||||
- register change stream: durable; restart rebuilds materialized register copy.
|
||||
- cycle trail stream: retention-bounded; replay decision/cycle/escalation/spawn.
|
||||
- durable consumers per client tier; each replays from its own sequence.
|
||||
- retention limits are policy; truth is in Phoebe.
|
||||
|
||||
## Dependency budget
|
||||
|
||||
Allow: nats-py, psycopg2/asyncpg, fastapi, uvicorn, stdlib.
|
||||
Refuse: Kafka, Redis, RabbitMQ, ORM now, second broker.
|
||||
Every new dependency must state its `why` here.
|
||||
|
||||
Version: 0.2 | Updated: 2026-11-04
|
||||
@@ -0,0 +1,60 @@
|
||||
# Schemas
|
||||
|
||||
Canonical contracts for the runtime.
|
||||
|
||||
## Identity
|
||||
|
||||
| ID | Assigned at |
|
||||
|---|---|
|
||||
| event_uuid | sensor, at impulse creation |
|
||||
| cycle_uuid | lemniscate, at cycle open |
|
||||
| decision_uuid | L1 gate decision |
|
||||
|
||||
UUID v7. 48-bit timestamp prefix; sort order = causal order. No record enters Phoebe without causal UUIDs.
|
||||
|
||||
## Impulse row
|
||||
|
||||
`(event_uuid, gate, timestamp, payload)`
|
||||
|
||||
Created at sensor; UUID v7; immutable.
|
||||
|
||||
## Gate state row
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| gate | registry name |
|
||||
| window_start | aggregation window start |
|
||||
| counters | per-gate counters (JSONB) |
|
||||
| threshold | value shifted by L1 |
|
||||
| last_opened | cycle index of last opening |
|
||||
| last_decision_cycle | cycle_uuid of last shifting decision |
|
||||
| l1_rtt_ewma / l2_rtt_ewma | sliding means used for cycle interval |
|
||||
|
||||
Stored in Phoebe; checkpointed after each cycle; restored on restart.
|
||||
|
||||
## Gate tag row
|
||||
|
||||
`(gate, tier, why, mode_overrides)`
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| tier | T0 critical / T1 relational / T2 standard / T3 ambient |
|
||||
| why | declared reason and audit string |
|
||||
| mode_overrides | per-mode tier shifts (UUID-logged decision) |
|
||||
|
||||
T0/T1 registry-declared, runtime-immutable. T2/T3 may shift per mode.
|
||||
|
||||
## Evidence request
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| request_uuid | v7, issued at L1 |
|
||||
| gate | registry name, validated |
|
||||
| since | cycle boundary |
|
||||
| returned_from | buffer lower bound; truncation recorded |
|
||||
|
||||
## Message schemas
|
||||
|
||||
Per subsystem: gates, director, clients.
|
||||
|
||||
Version: 0.4 | Updated: 2026-11-04
|
||||
@@ -0,0 +1,39 @@
|
||||
# Gate Table
|
||||
|
||||
PostgreSQL schema in Phoebe. Registry row per gate; state row per gate.
|
||||
|
||||
## gate_registry
|
||||
|
||||
| Field | Type | Rationale |
|
||||
|---|---|---|
|
||||
| gate | TEXT PK, max 64, dot-namespaced | stable wire/registry key |
|
||||
| tier | TEXT CHECK IN ('T0','T1','T2','T3') | declared role label |
|
||||
| tier_locked | BOOLEAN | runtime refuses tier writes when true |
|
||||
| why | TEXT NOT NULL, max 200 | reason attention argues, audit string |
|
||||
| mode_overrides | JSONB default {} | per-mode tier shifts |
|
||||
| created_by, created_at | TEXT, TIMESTAMPTZ | registry provenance |
|
||||
|
||||
## gate_state
|
||||
|
||||
| Field | Type | Rationale |
|
||||
|---|---|---|
|
||||
| gate | TEXT PK FK gate_registry | identity |
|
||||
| window_start | TIMESTAMPTZ | aggregation boundary |
|
||||
| counters | JSONB default {} | per-gate counters |
|
||||
| threshold | NUMERIC(4,1) default 5.0 | attention value; numeric avoids float drift |
|
||||
| last_opened | BIGINT (cycle index) | starvation counts cycles, not time |
|
||||
| last_decision_cycle | UUID | replay handle |
|
||||
| last_l1_rtt_ms, last_l2_rtt_ms | INTEGER | last completed cycle probes |
|
||||
| l1_rtt_ewma, l2_rtt_ewma | NUMERIC(10,1) | sliding means with hysteresis |
|
||||
|
||||
## Wire identity
|
||||
|
||||
| ID | Assigned at |
|
||||
|---|---|
|
||||
| event_uuid | sensor, at impulse |
|
||||
| cycle_uuid | lemniscate, at cycle open |
|
||||
| decision_uuid | L1 decision |
|
||||
|
||||
UUID v7; native UUID type; canonical display = v7 hex string.
|
||||
|
||||
Version: 0.2 | Updated: 2026-11-04
|
||||
@@ -0,0 +1,23 @@
|
||||
# House Style
|
||||
|
||||
Paper register for this repo. Chat and diary keep Nyx's voice; papers keep the facts.
|
||||
|
||||
## Rules for papers
|
||||
|
||||
- Engineer reference, not prose. Impersonal, terse, concrete.
|
||||
- No metaphor, rhetoric, persona prose, emojis, or "we" narration in papers.
|
||||
- Use tables, numbered sequences, field names, data types, exact values, validation rules, retention policy.
|
||||
- Every claim must be implementable or measurable. If a sentence can't be tested, cut it.
|
||||
- A new paper must pass this register before it is indexed.
|
||||
- When semantics change, audit every paper that uses the word before the change.
|
||||
|
||||
## Allowed voices
|
||||
|
||||
| Surface | Voice |
|
||||
|---|---|
|
||||
| architecture/, schemas/, runtime/, style/ | Engineer register |
|
||||
| README.md | Index: terse, factual |
|
||||
| Chat | Nyx voice |
|
||||
| Diary | Nyx voice |
|
||||
|
||||
Version: 0.1 | Updated: 2026-11-04
|
||||
Reference in new issue
Block a user