feat(continuum-core/runtime): L0-3a.0 — BrainRegion trait + ReadyBuffer + RegionTelemetry (substrate prerequisite)#1471
Merged
joelteply merged 1 commit intoMay 30, 2026
Conversation
…er + RegionTelemetry (substrate prerequisite) Card: 71923a08-b3de-448a-98ef-fe7cc3e817c0 First sub-slice of L0-3a. Pure typed surface from BRAIN-REGIONS-SUBSTRATE.md (merged via #1470). No region implementations, no algorithms, no governor integration. Those land in L0-3a.1+ slices. ## New modules in continuum-core/src/runtime/ ### brain_region.rs The cognitive-cycle trait every region implements: - BrainRegion (async trait, dyn-compatible) - id() -> RegionId - pressure_profile() -> PressureProfile - async tick(ctx: &RegionContext) -> TickOutcome - async on_signal(signal: RegionSignal) -> Result<(), RegionError> // default no-op - RegionId (Cow<'static, str> newtype, const constructor for static IDs) - PressureProfile { memory_class, compute_class, responds_to } - MemoryClass: Light | Moderate | Heavy | VramSensitive - ComputeClass: Bookkeeping | Cpu | CpuVectorized | InferenceLight | InferenceHeavy - PressureSignalKind (kind-only mirror of governor::PressureSignal for static decl) - TickOutcome { published, consumed_since_last, pressure_observed, cadence_hint } - TickOutcome::idle() convenience constructor - CadenceHint: Faster | Hold | Slower | Sleep (region requests; governor decides) - RegionSignal: PersonaLifecycle | SleepTransition | SystemPressureChanged - PersonaLifecycle: Created | Destroyed - SleepPhase: Active | Idle | Sleep - PressureLevel: Nominal | Moderate | High | Critical - RegionContext { tick_number, persona_scope } // global vs per-persona - RegionError (thiserror): SignalRejected | NotReady | Internal ### ready_buffer.rs The publish/peek surface every region uses to hand off pre-staged results: - ReadyBuffer trait - peek(&self, key: &Key) -> Option<Value> // synchronous, MUST NOT block - publish(&self, key: Key, value: Value) // atomic replace - evict_stale(&self, max_age: Duration) -> usize - len() / is_empty() - DashMapReadyBuffer<K, V> default implementation - Arc-shared DashMap inner — cheap Clone hands out additional handles - Sharded concurrent access; wait-free reads in the common case - TimestampedEntry tracks published_at for evict_stale Semantic rules enforced in the doc + the trait: - Reads MUST NOT block / MUST NOT await - Staleness acceptable — empty buffer is signal, not block - Per-region buffers, not global ### region_telemetry.rs The per-tick telemetry shape: - RegionTelemetry { region_id, persona_id, tick_started_at, tick_duration, published, consumed_since_last, buffer_misses_since_last, pressure_observed } - consumption_fraction() -> Option<f32> // None when published == 0 - had_buffer_misses() -> bool Feeds the substrate governor's yield-learning loop (algorithm 7, lands L0-4c) and the operator surface (./jtag region/stats, region/yield). ## ts-rs bindings (11 emitted to shared/generated/runtime/) CadenceHint, ComputeClass, MemoryClass, PersonaLifecycle, PressureLevel, PressureProfile, PressureSignalKind, RegionId, RegionSignal, RegionTelemetry, SleepPhase, TickOutcome. Generated and validated by the ts-rs export_bindings_* tests. ## Tests 23 new unit tests across the three modules. All pass. - brain_region: 6 tests (trait impl, default on_signal noop, RegionId construction + Display, RegionContext global vs per-persona, TickOutcome::idle) - ready_buffer: 9 tests (publish+peek roundtrip, missing key, overwrite, evict_stale removes old + keeps fresh, evict ZERO clears everything, len/is_empty, clone shares Arc inner, dyn trait usage, with_capacity) - region_telemetry: 5 tests (consumption_fraction with publishes / zero / full, had_buffer_misses true / false) Plus ts-rs auto-generated export_bindings_* tests for all 11 types. Total: 74 tests pass in runtime::, 0 fail. ## Boy-scout cargo fmt applied across the package picked up some unrelated drift in governor/types.rs (line-width formatting on ts(export...) attributes). Including the fix. ## What is NOT in this card - No region implementations (HippocampusModule, MotorCortexModule, AttentionModule all land in later slices) - No algorithms (1-7 from COGNITION-ALGORITHMS.md land in subsequent cards) - No SubstrateGovernor integration (yield-learning loop is L0-4c) - No derive macro / scaffold generator (lands when ≥3 regions exist to motivate the abstraction — per outlier-validation in CLAUDE.md) ## Predecessors merged - #1469 (L0-2-CUTOVER-INVESTIGATION + RTOS-brain doctrine) — 2026-05-29 - #1470 (BRAIN-REGIONS-SUBSTRATE + COGNITION-ALGORITHMS docs) — 2026-05-29 ## Next slices L0-3a.1 HippocampusModule skeleton, L0-3a.2 Engram + EngramGraph types, L0-3a.3 Algorithm 4 (salience decay), L0-3a.4 Algorithm 2 (channel-as-bias), L0-3a.5 Algorithm 3 (activation spreading), L0-3a.6 Algorithm 1 (two-pool budget), L0-3a.7 Algorithm 5 (predictor + ready-buffer publish), L0-3a.8 holdout fixture suite, L0-3a.9 TS Hippocampus.ts deletion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4de80bf to
0bad2c3
Compare
joelteply
added a commit
that referenced
this pull request
May 30, 2026
…rainRegion + ServiceModule, empty tick) (#1473) * feat(continuum-core/runtime): L0-3a.0 — BrainRegion trait + ReadyBuffer + RegionTelemetry (substrate prerequisite) Card: 71923a08-b3de-448a-98ef-fe7cc3e817c0 First sub-slice of L0-3a. Pure typed surface from BRAIN-REGIONS-SUBSTRATE.md (merged via #1470). No region implementations, no algorithms, no governor integration. Those land in L0-3a.1+ slices. ## New modules in continuum-core/src/runtime/ ### brain_region.rs The cognitive-cycle trait every region implements: - BrainRegion (async trait, dyn-compatible) - id() -> RegionId - pressure_profile() -> PressureProfile - async tick(ctx: &RegionContext) -> TickOutcome - async on_signal(signal: RegionSignal) -> Result<(), RegionError> // default no-op - RegionId (Cow<'static, str> newtype, const constructor for static IDs) - PressureProfile { memory_class, compute_class, responds_to } - MemoryClass: Light | Moderate | Heavy | VramSensitive - ComputeClass: Bookkeeping | Cpu | CpuVectorized | InferenceLight | InferenceHeavy - PressureSignalKind (kind-only mirror of governor::PressureSignal for static decl) - TickOutcome { published, consumed_since_last, pressure_observed, cadence_hint } - TickOutcome::idle() convenience constructor - CadenceHint: Faster | Hold | Slower | Sleep (region requests; governor decides) - RegionSignal: PersonaLifecycle | SleepTransition | SystemPressureChanged - PersonaLifecycle: Created | Destroyed - SleepPhase: Active | Idle | Sleep - PressureLevel: Nominal | Moderate | High | Critical - RegionContext { tick_number, persona_scope } // global vs per-persona - RegionError (thiserror): SignalRejected | NotReady | Internal ### ready_buffer.rs The publish/peek surface every region uses to hand off pre-staged results: - ReadyBuffer trait - peek(&self, key: &Key) -> Option<Value> // synchronous, MUST NOT block - publish(&self, key: Key, value: Value) // atomic replace - evict_stale(&self, max_age: Duration) -> usize - len() / is_empty() - DashMapReadyBuffer<K, V> default implementation - Arc-shared DashMap inner — cheap Clone hands out additional handles - Sharded concurrent access; wait-free reads in the common case - TimestampedEntry tracks published_at for evict_stale Semantic rules enforced in the doc + the trait: - Reads MUST NOT block / MUST NOT await - Staleness acceptable — empty buffer is signal, not block - Per-region buffers, not global ### region_telemetry.rs The per-tick telemetry shape: - RegionTelemetry { region_id, persona_id, tick_started_at, tick_duration, published, consumed_since_last, buffer_misses_since_last, pressure_observed } - consumption_fraction() -> Option<f32> // None when published == 0 - had_buffer_misses() -> bool Feeds the substrate governor's yield-learning loop (algorithm 7, lands L0-4c) and the operator surface (./jtag region/stats, region/yield). ## ts-rs bindings (11 emitted to shared/generated/runtime/) CadenceHint, ComputeClass, MemoryClass, PersonaLifecycle, PressureLevel, PressureProfile, PressureSignalKind, RegionId, RegionSignal, RegionTelemetry, SleepPhase, TickOutcome. Generated and validated by the ts-rs export_bindings_* tests. ## Tests 23 new unit tests across the three modules. All pass. - brain_region: 6 tests (trait impl, default on_signal noop, RegionId construction + Display, RegionContext global vs per-persona, TickOutcome::idle) - ready_buffer: 9 tests (publish+peek roundtrip, missing key, overwrite, evict_stale removes old + keeps fresh, evict ZERO clears everything, len/is_empty, clone shares Arc inner, dyn trait usage, with_capacity) - region_telemetry: 5 tests (consumption_fraction with publishes / zero / full, had_buffer_misses true / false) Plus ts-rs auto-generated export_bindings_* tests for all 11 types. Total: 74 tests pass in runtime::, 0 fail. ## Boy-scout cargo fmt applied across the package picked up some unrelated drift in governor/types.rs (line-width formatting on ts(export...) attributes). Including the fix. ## What is NOT in this card - No region implementations (HippocampusModule, MotorCortexModule, AttentionModule all land in later slices) - No algorithms (1-7 from COGNITION-ALGORITHMS.md land in subsequent cards) - No SubstrateGovernor integration (yield-learning loop is L0-4c) - No derive macro / scaffold generator (lands when ≥3 regions exist to motivate the abstraction — per outlier-validation in CLAUDE.md) ## Predecessors merged - #1469 (L0-2-CUTOVER-INVESTIGATION + RTOS-brain doctrine) — 2026-05-29 - #1470 (BRAIN-REGIONS-SUBSTRATE + COGNITION-ALGORITHMS docs) — 2026-05-29 ## Next slices L0-3a.1 HippocampusModule skeleton, L0-3a.2 Engram + EngramGraph types, L0-3a.3 Algorithm 4 (salience decay), L0-3a.4 Algorithm 2 (channel-as-bias), L0-3a.5 Algorithm 3 (activation spreading), L0-3a.6 Algorithm 1 (two-pool budget), L0-3a.7 Algorithm 5 (predictor + ready-buffer publish), L0-3a.8 holdout fixture suite, L0-3a.9 TS Hippocampus.ts deletion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(continuum-core/modules): L0-3a.1 — HippocampusModule skeleton (BrainRegion + ServiceModule, empty tick) Card: f8c51b26-9ddd-4107-97da-3237fc18ab4b Second sub-slice of L0-3a. Skeleton only — no algorithms, no command migration. Algorithms 1-5 from COGNITION-ALGORITHMS.md land in L0-3a.2 through L0-3a.7. Command surface migration (memory/* from MemoryModule) is L0-3a.1b. ## HippocampusModule - Implements ServiceModule with EMPTY command_prefixes + event_subscriptions (MemoryModule continues to handle memory/* commands until L0-3a.1b) - Implements BrainRegion (from #1471 trait machinery) with: - id = "hippocampus" (static) - pressure_profile: { MemoryClass::Heavy, ComputeClass::CpuVectorized, responds_to: [SystemMemHigh, InferenceQueueDepth] } - tick: idle — bumps internal monotonic counter, returns TickOutcome::idle() - on_signal: default no-op (L0-4d wires SleepTransition reaction) - Owns a DashMapReadyBuffer<EngramPrefetchKey, EngramPrefetch> exposed via engram_prefetch() — Arc-shared so motor cortex / attention can peek without going through the trait object - Shares MemoryState with MemoryModule via Arc — when L0-3a.1b absorbs command handling, migration is structurally trivial ## EngramPrefetch / EngramPrefetchKey Placeholder ready-buffer value type. Carries produced_at_tick so handlers can detect stale buffers without timestamp comparison. Real shape (engram set + scoring metadata + genome blend hint) lands L0-3a.2 with the actual Engram types. Key shape: (persona_id, channel_id) tuple. Per-region buffer doctrine — one prefetch per persona-per-channel. ## Outlier-validation hedge (docstring) The BrainRegion trait in #1471 has only one implementation candidate today. Module docstring explicitly checks the trait surface against two other plausible regions to prevent it ossifying around hippocampus: - Motor cortex (L0-4a): continuous candidate-utterance ranking. Differs in latency sensitivity. CadenceHint::Faster + per-key freshness semantics fit. - Attention (L0-4b): salience-map maintenance. Differs in publish-target (writes to shared PersonaCognition.salience, not own ready-buffer). TickOutcome.published counts either target without trait change. Both alternative shapes fit the same trait without forcing. Trait surface proven for 3 distinct region behaviors before any of them ship. ## Tests (7 pass, 0 fail) - region_id_is_stable_static_string - pressure_profile_declares_memory_heavy_compute_vectorized - idle_tick_returns_idle_outcome_and_bumps_counter - engram_prefetch_buffer_roundtrip - engram_prefetch_handle_is_shared_via_arc (verifies Arc-shared semantics) - service_module_handle_command_errors_for_unrouted_commands - service_module_config_has_empty_cmd_and_event_surfaces ## Scope: 2 files Modified: src/workers/continuum-core/src/modules/mod.rs (pub mod hippocampus) Added: src/workers/continuum-core/src/modules/hippocampus.rs (379 lines) Fmt-drift in unrelated files was split off into a companion PR following the same pattern as #1472, keeping this review focused. ## Predecessors - #1471 (L0-3a.0 trait machinery) — merged to canary - #1470 (BRAIN-REGIONS-SUBSTRATE + COGNITION-ALGORITHMS docs) — merged - #1469 (L0-2-CUTOVER-INVESTIGATION + RTOS-brain doctrine) — merged ## Next slices L0-3a.2 Engram + EngramGraph types → L0-3a.3 algorithm 4 (salience decay) → L0-3a.4 algorithm 2 (channel-as-bias) → L0-3a.5 algorithm 3 (activation spreading) → L0-3a.6 algorithm 1 (two-pool budget) → L0-3a.7 algorithm 5 (predictor + ready-buffer publish — the alive-feeling slice) → L0-3a.8 holdout fixture suite → L0-3a.9 TS Hippocampus.ts deletion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 30, 2026
…dgeKind (algorithm 3 substrate) (#1474) Card: 8459bfa6-b40c-4c22-8f25-0963a7987c17 Sidecar substrate for algorithm 3 (activation spreading, COGNITION-ALGORITHMS.md §3). Pure storage layer — traversal logic lands in L0-3a.5. Does NOT modify the existing persona::engram admission membrane. ## What ships ### persona/engram_graph.rs (new, 376 lines) - EdgeKind enum — SharedEntity | SharedTopic | CitedIn | RecallCoOccurrence | ConversationalReply | TaskOutcome - EngramEdge { target: Uuid, kind: EdgeKind, weight: f32 } — algorithm-3 traversal payload - EngramGraph — DashMap<Uuid, Vec<EngramEdge>> sharded for concurrent writes - new() / with_capacity(n) / default() - add_edge(from, to, kind, weight) - neighbors(id) — outbound edges, O(1) amortized, insertion order preserved - in_degree(id) — inbound count, O(N) scan (cold path — algorithm 4 centrality) - edge_count() — telemetry - evict_engram(id) — removes outbound + inbound, idempotent - is_empty() ### ts-rs bindings shared/generated/persona/EdgeKind.ts shared/generated/persona/EngramEdge.ts ## Sidecar pattern Intentionally separate from persona::engram (the admission membrane): - engram.rs ships provenance, trust, content refs — WHERE engrams come from - engram_graph.rs ships connectivity — HOW engrams connect Keeping them separate means admission consumers don't grow algorithm-3 dependencies, and algorithm-3 consumers don't grow admission dependencies. Clean concern boundaries. ## Tests (16 pass, 0 fail) - new_engram_graph_is_empty - add_edge_increments_count - neighbors_returns_added_edges_in_insertion_order - neighbors_of_unknown_source_is_empty - weights_preserved_through_neighbors - in_degree_counts_inbound_edges_across_sources - in_degree_counts_repeated_edges_from_same_source - evict_engram_removes_outbound_edges - evict_engram_removes_inbound_edges_from_other_engrams - evict_engram_is_idempotent - concurrent_add_edge_from_threads_is_safe (8 threads × 100 edges, all targeting same id, in_degree=800) - default_constructor_matches_new - with_capacity_constructor_works - edge_kind_round_trips_through_serde - export_bindings_edgekind (ts-rs auto) - export_bindings_engramedge (ts-rs auto) ## What is NOT in this card - spread_activation function (L0-3a.5, algorithm 3 — reads this graph) - EdgeKind weights tuned by algorithm 7 (L0-4c yield-learning) - RecallMetadata sidecar (L0-3a.2b — salience, last_touched, access_count, embedding) - EngramRef shape (L0-3a.2b) - Engram admission membrane modifications (no changes to persona::engram) ## Predecessors - #1473 (L0-3a.1 HippocampusModule skeleton) — merged - #1471 (L0-3a.0 trait machinery) — merged - #1470 (cognition algorithms doc) — merged ## Flywheel test Third PR (after #1471, #1473) through the auto-merger flywheel that peer's #1091/#1092/#1093 enabled. Local fmt was scoped to ONLY my file (no widespread cargo fmt -p sweep), so no companion fmt-drift PR needed this time. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 task
joelteply
added a commit
that referenced
this pull request
May 30, 2026
The `avatars: ./src/models/avatars` additional_context was added in 9b1f6ca (April 2026) when the plan was to bake CC0 avatar VRMs into the continuum-core image. That plan never landed end-to-end — docker/continuum-core.Dockerfile lines 131-143 document the rollback: src/models is gitignored, the dir doesn't exist in CI checkouts, and the Dockerfile uses `RUN mkdir -p /app/avatars` as a placeholder instead of COPYing from the avatars context. The compose-side context declaration was left behind, dangling. No Dockerfile uses `--from=avatars` (verified by grep), so the declaration referenced nothing in build instructions. But docker compose validates that ALL additional_contexts resolve at build time — a missing local context dir fails the whole build with "stat /tmp/carl-smoke-NNNN/src/ models/avatars: no such file or directory". That's the exact failure mode currently blocking carl-install-smoke on PR #1475 (Mac Intel hardware tier) — any PR that touches install.sh triggers carl-install-smoke, which has been silently broken by this dangling context since the rollback. Other PRs (e.g. #1471, #1473, #1474) didn't touch install.sh so the check never ran on them; the break was invisible until now. Removing the line restores the carl-install-smoke happy path while keeping the Dockerfile's empty-dir placeholder intact. Restore the build context when the avatar-provisioning story lands (LFS, model-init download, or curl from a CC0 URL in CI before docker build) per the gap noted in docs/infrastructure/PR891-E2E-VALIDATION.md. Inline comment preserves the context-of-removal in the file so a future contributor doesn't re-add the dangling line. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 30, 2026
…se (#1476) The `avatars: ./src/models/avatars` additional_context was added in 9b1f6ca (April 2026) when the plan was to bake CC0 avatar VRMs into the continuum-core image. That plan never landed end-to-end — docker/continuum-core.Dockerfile lines 131-143 document the rollback: src/models is gitignored, the dir doesn't exist in CI checkouts, and the Dockerfile uses `RUN mkdir -p /app/avatars` as a placeholder instead of COPYing from the avatars context. The compose-side context declaration was left behind, dangling. No Dockerfile uses `--from=avatars` (verified by grep), so the declaration referenced nothing in build instructions. But docker compose validates that ALL additional_contexts resolve at build time — a missing local context dir fails the whole build with "stat /tmp/carl-smoke-NNNN/src/ models/avatars: no such file or directory". That's the exact failure mode currently blocking carl-install-smoke on PR #1475 (Mac Intel hardware tier) — any PR that touches install.sh triggers carl-install-smoke, which has been silently broken by this dangling context since the rollback. Other PRs (e.g. #1471, #1473, #1474) didn't touch install.sh so the check never ran on them; the break was invisible until now. Removing the line restores the carl-install-smoke happy path while keeping the Dockerfile's empty-dir placeholder intact. Restore the build context when the avatar-provisioning story lands (LFS, model-init download, or curl from a CC0 URL in CI before docker build) per the gap noted in docs/infrastructure/PR891-E2E-VALIDATION.md. Inline comment preserves the context-of-removal in the file so a future contributor doesn't re-add the dangling line. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 31, 2026
…w module scaffolds
Per Joel 2026-05-30: "we developed a generator so we could manufacture
these patterns for new commands modules etc, which itself was a
command. Meta."
The recursive bootstrap from MODULE-ARCHITECTURE.md §10 lands. The
generator IS a module. The things it creates are modules. Every
operation it performs is a command. The system describes itself in
its own terms.
# What this does
`Commands.execute("generate/module", { ... })` scaffolds a compilable
ServiceModule package under
`src/workers/continuum-core/src/modules/<name>/`:
- `mod.rs` — `pub struct <Name>Module {}` with `ServiceModule`
implemented, the `ModuleConfig` declaring the spec's commands +
events, and `handle_command` returning typed "not yet implemented"
errors for each declared command (so the scaffold compiles + the
author fills in real handlers afterwards).
- `README.md` — author-facing doc capturing the same contract +
spelling out the manual wire-up step (add `pub mod <name>;` to
the parent `modules/mod.rs`, register `Arc::new(<Name>Module::new())`
at runtime startup).
The generated module follows every pattern this session codified:
- `ServiceModule` trait from PR #1471 (the substrate floor)
- `CommandResult` cell shapes from PR #1485
- `CommandRequest<P>` / `CommandResponse<T>` envelopes from PR #1486
(the generator itself uses these — typed envelope in, typed
envelope out)
- The architecture from MODULE-ARCHITECTURE.md (PR #1482)
# Why this is the meta move
Every architectural pattern we codified degrades fast if every new
module's author has to re-derive them from the docs. The generator
is the boy-scout amplifier: write the patterns once into the
templates, run `Commands.execute("generate/module", ...)`, get a
module skeleton that already follows them. Subsequent migrations
become "fill in the handler bodies" rather than "re-derive the
shape."
The generator can eventually generate itself (the recursion closes).
This PR ships the v1; future PRs add `generate/command` (add a new
command to an existing module) and `generate/refresh` (re-scan the
modules tree and refresh manifests).
# Implementation surface
Three files under `modules/generator/`:
- **`types.rs`** — `GenerateModuleParams` (name, description, commands,
events_subscribed, events_published, priority, force) +
`GenerateModuleResult` (module_path, files_created, next_step) +
`PrioritySpec` wire enum + `validate_module_name`. All
serde-friendly, no leak of internal types onto the wire.
- **`templates.rs`** — pure render functions: `mod_rs_template`,
`readme_template`, and helpers. No I/O lives here; the caller does
the writes. Keeps the templates testable in isolation and the I/O
paths easy to swap (e.g., future dry-run mode).
- **`mod.rs`** — `GeneratorModule` (the `ServiceModule` impl) +
`generate_module_inner` (the actual filesystem work). `handle_command`
parses a `CommandRequest<GenerateModuleParams>` and materializes a
`CommandResponse<GenerateModuleResult>` — uses the exact envelope
pattern PR #1486 introduced, eating its own dogfood.
The module is wired into `modules/mod.rs` as `pub mod generator;` —
the same step the generator instructs callers to perform for the
modules IT scaffolds.
# Tests (21/21 pass)
types.rs (5):
- `validate_accepts_canonical_names` — chat, ai_provider,
ai-provider, _internal, a1
- `validate_rejects_empty_or_invalid` — empty, capitalized,
leading-digit, has-space, with-slash
- `priority_spec_round_trips_through_json` — all 4 variants
- `priority_spec_default_is_normal`
- `priority_spec_as_variant_str_matches_rust_enum`
templates.rs (7):
- `mod_rs_contains_struct_definition_and_trait_impl`
- `mod_rs_lists_each_declared_command_in_prefix_and_dispatch`
- `mod_rs_includes_module_name_prefix_in_command_prefixes`
- `mod_rs_subscribes_to_declared_events`
- `mod_rs_documents_published_events_in_module_docstring`
- `mod_rs_for_command_less_module_still_compiles_shape`
- `readme_lists_declared_contract`
- `readme_handles_empty_lists_gracefully`
mod.rs (8):
- `struct_name_handles_hyphens_underscores_and_simple_names`
- `config_advertises_generate_prefix`
- `generate_module_creates_dir_and_files` — full filesystem round-trip
in a tempdir, asserts struct name + declared commands + ServiceModule
appear in the generated mod.rs
- `generate_module_refuses_existing_dir_without_force` — fail-loud,
error names the conflict AND the escape hatch
- `generate_module_overwrites_with_force` — and the second
generation's description appears in the file
- `generate_module_rejects_invalid_names` — empty / space / slash /
parent-escape / leading-digit
- `handle_command_returns_typed_envelope` — end-to-end through the
ServiceModule trait + CommandRequest envelope + CommandResponse
envelope + the JSON round-trip
- `handle_command_rejects_unknown_command_loud` — error names the bad
command + what's supported
# What this PR explicitly does NOT do
- Does NOT auto-wire the generated module into the parent
`modules/mod.rs`. The generator emits the exact line the caller
needs to add — explicit human step keeps the registration audit
obvious. A future `generate/refresh` command can do this
automatically.
- Does NOT generate package.json / manifest.json. The architecture
doc anticipates these, but the on-disk module structure in
continuum-core today is "everything compiles into one binary," so
per-module manifests are a future migration (WASM-component
modules will need them per MODULE-ARCHITECTURE.md §9).
- Does NOT register `GeneratorModule` at runtime startup. The module
is reachable via direct construction in tests; production wire-up
happens in `ipc::start_server` once the typical "register Arc::new"
pattern is followed (the generator's README spells this out for
EVERY module it creates, including itself).
- Does NOT implement `generate/command` (add a command to an
existing module) or `generate/refresh` (re-scan + refresh
manifests). Both are natural follow-ups; this PR ships the v1.
# References
- [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md)
§10 (recursive bootstrap), §2 (what a module is)
- PR #1486 (CommandRequest/Response envelopes — used here)
- PR #1485 (cell shapes — used here)
- PR #1483 / #1484 (interceptor chain — orthogonal but composable)
- PR #1482 (architecture doc)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 31, 2026
…w module scaffolds
Per Joel 2026-05-30: "we developed a generator so we could manufacture
these patterns for new commands modules etc, which itself was a
command. Meta."
The recursive bootstrap from MODULE-ARCHITECTURE.md §10 lands. The
generator IS a module. The things it creates are modules. Every
operation it performs is a command. The system describes itself in
its own terms.
# What this does
`Commands.execute("generate/module", { ... })` scaffolds a compilable
ServiceModule package under
`src/workers/continuum-core/src/modules/<name>/`:
- `mod.rs` — `pub struct <Name>Module {}` with `ServiceModule`
implemented, the `ModuleConfig` declaring the spec's commands +
events, and `handle_command` returning typed "not yet implemented"
errors for each declared command (so the scaffold compiles + the
author fills in real handlers afterwards).
- `README.md` — author-facing doc capturing the same contract +
spelling out the manual wire-up step (add `pub mod <name>;` to
the parent `modules/mod.rs`, register `Arc::new(<Name>Module::new())`
at runtime startup).
The generated module follows every pattern this session codified:
- `ServiceModule` trait from PR #1471 (the substrate floor)
- `CommandResult` cell shapes from PR #1485
- `CommandRequest<P>` / `CommandResponse<T>` envelopes from PR #1486
(the generator itself uses these — typed envelope in, typed
envelope out)
- The architecture from MODULE-ARCHITECTURE.md (PR #1482)
# Why this is the meta move
Every architectural pattern we codified degrades fast if every new
module's author has to re-derive them from the docs. The generator
is the boy-scout amplifier: write the patterns once into the
templates, run `Commands.execute("generate/module", ...)`, get a
module skeleton that already follows them. Subsequent migrations
become "fill in the handler bodies" rather than "re-derive the
shape."
The generator can eventually generate itself (the recursion closes).
This PR ships the v1; future PRs add `generate/command` (add a new
command to an existing module) and `generate/refresh` (re-scan the
modules tree and refresh manifests).
# Implementation surface
Three files under `modules/generator/`:
- **`types.rs`** — `GenerateModuleParams` (name, description, commands,
events_subscribed, events_published, priority, force) +
`GenerateModuleResult` (module_path, files_created, next_step) +
`PrioritySpec` wire enum + `validate_module_name`. All
serde-friendly, no leak of internal types onto the wire.
- **`templates.rs`** — pure render functions: `mod_rs_template`,
`readme_template`, and helpers. No I/O lives here; the caller does
the writes. Keeps the templates testable in isolation and the I/O
paths easy to swap (e.g., future dry-run mode).
- **`mod.rs`** — `GeneratorModule` (the `ServiceModule` impl) +
`generate_module_inner` (the actual filesystem work). `handle_command`
parses a `CommandRequest<GenerateModuleParams>` and materializes a
`CommandResponse<GenerateModuleResult>` — uses the exact envelope
pattern PR #1486 introduced, eating its own dogfood.
The module is wired into `modules/mod.rs` as `pub mod generator;` —
the same step the generator instructs callers to perform for the
modules IT scaffolds.
# Tests (21/21 pass)
types.rs (5):
- `validate_accepts_canonical_names` — chat, ai_provider,
ai-provider, _internal, a1
- `validate_rejects_empty_or_invalid` — empty, capitalized,
leading-digit, has-space, with-slash
- `priority_spec_round_trips_through_json` — all 4 variants
- `priority_spec_default_is_normal`
- `priority_spec_as_variant_str_matches_rust_enum`
templates.rs (7):
- `mod_rs_contains_struct_definition_and_trait_impl`
- `mod_rs_lists_each_declared_command_in_prefix_and_dispatch`
- `mod_rs_includes_module_name_prefix_in_command_prefixes`
- `mod_rs_subscribes_to_declared_events`
- `mod_rs_documents_published_events_in_module_docstring`
- `mod_rs_for_command_less_module_still_compiles_shape`
- `readme_lists_declared_contract`
- `readme_handles_empty_lists_gracefully`
mod.rs (8):
- `struct_name_handles_hyphens_underscores_and_simple_names`
- `config_advertises_generate_prefix`
- `generate_module_creates_dir_and_files` — full filesystem round-trip
in a tempdir, asserts struct name + declared commands + ServiceModule
appear in the generated mod.rs
- `generate_module_refuses_existing_dir_without_force` — fail-loud,
error names the conflict AND the escape hatch
- `generate_module_overwrites_with_force` — and the second
generation's description appears in the file
- `generate_module_rejects_invalid_names` — empty / space / slash /
parent-escape / leading-digit
- `handle_command_returns_typed_envelope` — end-to-end through the
ServiceModule trait + CommandRequest envelope + CommandResponse
envelope + the JSON round-trip
- `handle_command_rejects_unknown_command_loud` — error names the bad
command + what's supported
# What this PR explicitly does NOT do
- Does NOT auto-wire the generated module into the parent
`modules/mod.rs`. The generator emits the exact line the caller
needs to add — explicit human step keeps the registration audit
obvious. A future `generate/refresh` command can do this
automatically.
- Does NOT generate package.json / manifest.json. The architecture
doc anticipates these, but the on-disk module structure in
continuum-core today is "everything compiles into one binary," so
per-module manifests are a future migration (WASM-component
modules will need them per MODULE-ARCHITECTURE.md §9).
- Does NOT register `GeneratorModule` at runtime startup. The module
is reachable via direct construction in tests; production wire-up
happens in `ipc::start_server` once the typical "register Arc::new"
pattern is followed (the generator's README spells this out for
EVERY module it creates, including itself).
- Does NOT implement `generate/command` (add a command to an
existing module) or `generate/refresh` (re-scan + refresh
manifests). Both are natural follow-ups; this PR ships the v1.
# References
- [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md)
§10 (recursive bootstrap), §2 (what a module is)
- PR #1486 (CommandRequest/Response envelopes — used here)
- PR #1485 (cell shapes — used here)
- PR #1483 / #1484 (interceptor chain — orthogonal but composable)
- PR #1482 (architecture doc)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 31, 2026
…w module scaffolds (#1487) * feat(runtime): CommandRequest<P> / CommandResponse<T> envelopes — handle as first-class field Per Joel 2026-05-30: "Some things are used so much should just be part of command result and params, handle for example. Find the patterns and simplify. The better the pattern, the easier to use the command or to reduce code size. I love OOP though." Today's `ServiceModule::handle_command(command, params: Value) -> Result<CommandResult, String>` shovels everything through raw JSON; handlers re-parse the cross-cutting bits (handle, sessionId, userId, success, error) themselves and rebuild the same envelope at every return point. This commit gives the pattern names and a typed API so new handlers stop hand-rolling the envelope every time. # What lands **`runtime::command_envelope::CommandRequest<P>`** — typed envelope around an inbound command. Flattens the command-specific params `P` with the cross-cutting fields every command can carry: - `handle: Option<HandleRef>` — a handle from a previous call. Present when this command operates on existing state owned by another command (e.g., `inference/poll` carries the handle minted by `inference/start`). - `session_id: Option<Uuid>` — calling session. - `user_id: Option<Uuid>` — calling user. Construction: `CommandRequest::<P>::from_value(value)?` at handler entry. Test/programmatic construction via the builder methods (`new(params)`, `.with_handle(...)`, `.with_session(...)`, `.with_user(...)`). Wire shape stays flat — `#[serde(flatten)]` on the params field — so existing TS-side callers don't see a shape change. **`runtime::command_envelope::CommandResponse<T>`** — typed envelope around an outbound result. Same flatten pattern. Cross-cutting fields: - `success: bool` — operation-level success. - `data: T` — command-specific payload, flattened into JSON. - `handle: Option<HandleRef>` — a handle MINTED by this command for the caller's follow-up. The "first call returns a handle" pattern Joel called out for inference / training / hosting / ORM lives here. - `error: Option<String>` — operation-level error, set when success == false. Builder-style API: `CommandResponse::ok(data)` for happy path; chain `.with_handle(owner, id, type_tag)` to mint a handle for follow-up; `.with_handle_ref(handle)` to echo an existing handle. For failure, `CommandResponse::<T>::err(message)` (requires `T: Default` so the data field has a value; callers without a default just construct directly). Bridge into the existing `ServiceModule::handle_command` return: call `.into_command_result()` — serializes the flattened envelope as JSON, wraps as `CommandResult::Json`. One method to bridge typed internal handler into the kernel surface. # What this collapses (before/after) Before — handler hand-rolls the envelope every time: ```ignore async fn handle_inference_start(&self, params: Value) -> Result<CommandResult, String> { let p: InferenceStartParams = serde_json::from_value(params.clone()) .map_err(|e| e.to_string())?; let session_id = params.get("sessionId").and_then(|v| v.as_str()) .and_then(|s| Uuid::parse_str(s).ok()); let id = Uuid::new_v4(); self.sessions.insert(id, InferenceSession::new(p)); Ok(CommandResult::Json(serde_json::json!({ "success": true, "firstToken": first_token, "handle": HandleRef::with_id("ai/inference", id, "ai::InferenceSession"), }))) } ``` After — envelope handles the cross-cutting fields: ```ignore async fn handle_inference_start(&self, params: Value) -> Result<CommandResult, String> { let req = CommandRequest::<InferenceStartParams>::from_value(params)?; let id = Uuid::new_v4(); self.sessions.insert(id, InferenceSession::new(req.params)); CommandResponse::ok(InferenceStartData { first_token }) .with_handle("ai/inference", id, "ai::InferenceSession") .into_command_result() } ``` Cross-cutting fields stop being something handlers know about. They become free. # Test plan (9/9 pass) - `request_parses_flat_params_no_envelope_fields` — pure params, envelope fields default to None - `request_parses_envelope_fields_flat` — handle/sessionId/userId all pulled from the same JSON object at top level - `request_parse_error_carries_diagnostic` — type mismatch surfaces as Err with envelope identity (not panic) - `request_builder_attaches_envelope_fields` — builder API works - `response_ok_serializes_flat_with_success_true` — happy-path shape, handle/error omitted when None - `response_with_handle_attaches_handle_at_top_level` — handle sits alongside flat data fields - `response_err_serializes_with_success_false_and_message` — failure shape with default data preserved - `response_into_command_result_yields_json_variant` — bridge to the ServiceModule return type works - `round_trip_through_wire_preserves_envelope_fields` — end-to-end: handler returns response with handle → serialize → caller builds next request using the handle + own session/user → all envelope fields survive # What this PR does NOT do - Does NOT change `ServiceModule::handle_command` signature. The Value-based shape stays for the 300+ existing surface; new handlers opt into the typed envelope via `from_value` / `into_command_result`. - Does NOT migrate any existing handler. The envelope is the primitive; migrations are individual follow-up PRs. - Does NOT add a kernel-level handle registry. Each producer manages handle lifetimes internally per MODULE-ARCHITECTURE.md §13.1. # References - [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md) §5.1 (cell return shapes), §13.1 (hot-path cross-module state) - PR #1485 (cell return shapes — Handle variant + HandleRef) - PR #1484 (GridInterceptor) - PR #1483 (CommandInterceptor trait + AircInterceptor stub) - PR #1482 (architecture doc) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(modules): GeneratorModule — recursive bootstrap, manufactures new module scaffolds Per Joel 2026-05-30: "we developed a generator so we could manufacture these patterns for new commands modules etc, which itself was a command. Meta." The recursive bootstrap from MODULE-ARCHITECTURE.md §10 lands. The generator IS a module. The things it creates are modules. Every operation it performs is a command. The system describes itself in its own terms. # What this does `Commands.execute("generate/module", { ... })` scaffolds a compilable ServiceModule package under `src/workers/continuum-core/src/modules/<name>/`: - `mod.rs` — `pub struct <Name>Module {}` with `ServiceModule` implemented, the `ModuleConfig` declaring the spec's commands + events, and `handle_command` returning typed "not yet implemented" errors for each declared command (so the scaffold compiles + the author fills in real handlers afterwards). - `README.md` — author-facing doc capturing the same contract + spelling out the manual wire-up step (add `pub mod <name>;` to the parent `modules/mod.rs`, register `Arc::new(<Name>Module::new())` at runtime startup). The generated module follows every pattern this session codified: - `ServiceModule` trait from PR #1471 (the substrate floor) - `CommandResult` cell shapes from PR #1485 - `CommandRequest<P>` / `CommandResponse<T>` envelopes from PR #1486 (the generator itself uses these — typed envelope in, typed envelope out) - The architecture from MODULE-ARCHITECTURE.md (PR #1482) # Why this is the meta move Every architectural pattern we codified degrades fast if every new module's author has to re-derive them from the docs. The generator is the boy-scout amplifier: write the patterns once into the templates, run `Commands.execute("generate/module", ...)`, get a module skeleton that already follows them. Subsequent migrations become "fill in the handler bodies" rather than "re-derive the shape." The generator can eventually generate itself (the recursion closes). This PR ships the v1; future PRs add `generate/command` (add a new command to an existing module) and `generate/refresh` (re-scan the modules tree and refresh manifests). # Implementation surface Three files under `modules/generator/`: - **`types.rs`** — `GenerateModuleParams` (name, description, commands, events_subscribed, events_published, priority, force) + `GenerateModuleResult` (module_path, files_created, next_step) + `PrioritySpec` wire enum + `validate_module_name`. All serde-friendly, no leak of internal types onto the wire. - **`templates.rs`** — pure render functions: `mod_rs_template`, `readme_template`, and helpers. No I/O lives here; the caller does the writes. Keeps the templates testable in isolation and the I/O paths easy to swap (e.g., future dry-run mode). - **`mod.rs`** — `GeneratorModule` (the `ServiceModule` impl) + `generate_module_inner` (the actual filesystem work). `handle_command` parses a `CommandRequest<GenerateModuleParams>` and materializes a `CommandResponse<GenerateModuleResult>` — uses the exact envelope pattern PR #1486 introduced, eating its own dogfood. The module is wired into `modules/mod.rs` as `pub mod generator;` — the same step the generator instructs callers to perform for the modules IT scaffolds. # Tests (21/21 pass) types.rs (5): - `validate_accepts_canonical_names` — chat, ai_provider, ai-provider, _internal, a1 - `validate_rejects_empty_or_invalid` — empty, capitalized, leading-digit, has-space, with-slash - `priority_spec_round_trips_through_json` — all 4 variants - `priority_spec_default_is_normal` - `priority_spec_as_variant_str_matches_rust_enum` templates.rs (7): - `mod_rs_contains_struct_definition_and_trait_impl` - `mod_rs_lists_each_declared_command_in_prefix_and_dispatch` - `mod_rs_includes_module_name_prefix_in_command_prefixes` - `mod_rs_subscribes_to_declared_events` - `mod_rs_documents_published_events_in_module_docstring` - `mod_rs_for_command_less_module_still_compiles_shape` - `readme_lists_declared_contract` - `readme_handles_empty_lists_gracefully` mod.rs (8): - `struct_name_handles_hyphens_underscores_and_simple_names` - `config_advertises_generate_prefix` - `generate_module_creates_dir_and_files` — full filesystem round-trip in a tempdir, asserts struct name + declared commands + ServiceModule appear in the generated mod.rs - `generate_module_refuses_existing_dir_without_force` — fail-loud, error names the conflict AND the escape hatch - `generate_module_overwrites_with_force` — and the second generation's description appears in the file - `generate_module_rejects_invalid_names` — empty / space / slash / parent-escape / leading-digit - `handle_command_returns_typed_envelope` — end-to-end through the ServiceModule trait + CommandRequest envelope + CommandResponse envelope + the JSON round-trip - `handle_command_rejects_unknown_command_loud` — error names the bad command + what's supported # What this PR explicitly does NOT do - Does NOT auto-wire the generated module into the parent `modules/mod.rs`. The generator emits the exact line the caller needs to add — explicit human step keeps the registration audit obvious. A future `generate/refresh` command can do this automatically. - Does NOT generate package.json / manifest.json. The architecture doc anticipates these, but the on-disk module structure in continuum-core today is "everything compiles into one binary," so per-module manifests are a future migration (WASM-component modules will need them per MODULE-ARCHITECTURE.md §9). - Does NOT register `GeneratorModule` at runtime startup. The module is reachable via direct construction in tests; production wire-up happens in `ipc::start_server` once the typical "register Arc::new" pattern is followed (the generator's README spells this out for EVERY module it creates, including itself). - Does NOT implement `generate/command` (add a command to an existing module) or `generate/refresh` (re-scan + refresh manifests). Both are natural follow-ups; this PR ships the v1. # References - [docs/architecture/MODULE-ARCHITECTURE.md](docs/architecture/MODULE-ARCHITECTURE.md) §10 (recursive bootstrap), §2 (what a module is) - PR #1486 (CommandRequest/Response envelopes — used here) - PR #1485 (cell shapes — used here) - PR #1483 / #1484 (interceptor chain — orthogonal but composable) - PR #1482 (architecture doc) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(modules/generator): per-name lock serializes concurrent same-name generation + concurrency tests Per Joel 2026-05-30: "Each persona exists in its own threads." # Race scenarios the test caught Original `generate_module_inner`: ```rust if target_dir.exists() && !params.force { return Err("already exists"); } std::fs::create_dir_all(&target_dir)?; write_file(mod.rs); write_file(README.md); ``` Concurrent same-name `generate/module` calls: 1. **Both without force**: BOTH pass the exists() check, BOTH call create_dir_all (idempotent → both succeed), BOTH write — and the friendly "already exists" error is silenced. With DIFFERENT params, last write wins per file → **silent torn state** (mod.rs from caller A + README from caller B). 2. **Both with force**: same torn-state hazard — interleaved writes produce inconsistent final state. 3. **Different names**: no conflict, should stay fully parallel. # The fix `DashMap<String, Arc<std::sync::Mutex<()>>>` keyed by module name. The per-name mutex is acquired before the exists() check and held through the writes — same-name concurrent calls serialize; different names stay parallel via DashMap's per-shard locking. `std::sync::Mutex` (not `tokio::sync::Mutex`) because the protected critical section is purely synchronous filesystem I/O — no `.await` inside the lock. Blocking the tokio worker for the brief mkdir + 2 writes is correct and avoids cascading the API into async. The critical section is short and generation is rare (humans/AI scaffolding modules, not the hot path). Lock entries are never evicted — module names are bounded (no unbounded stream of unique names) and each entry is ~50 bytes. If memory ever matters, a TTL scan can be added without changing the protocol. # Concurrency stress tests Every test uses `flavor = "multi_thread", worker_threads = 4` so spawned tasks actually preempt on distinct OS threads, not cooperatively interleave on one. ## `same_name_concurrent_generation_without_force_yields_one_winner` 8 racers, same name, no force. Asserts EXACTLY 1 winner, 7 losers, every loser's error names both the failure mode ("already exists") AND the escape hatch ("force"). Without the per-name lock, this test would have shown N winners (silent corruption). ## `same_name_concurrent_generation_with_force_produces_consistent_final_state` 8 racers, same name, force=true. Each caller embeds a unique `MARKER-NN` in its `description` (which both templates write into their output). Asserts both files end with the SAME marker — torn state would show different markers in mod.rs vs README. ## `different_names_concurrent_generation_runs_fully_parallel` 12 racers, all distinct names. Asserts all 12 succeed, each module's files exist with their own content. Verifies the per-name lock map holds 12 distinct entries (different DashMap shards → no contention). # Tests (24/24 pass — 21 pre-existing + 3 new concurrency) All pre-existing tests still pass — no regression from the locking addition. The new tests pin all three cells of the (same-name × force-flag) matrix plus the different-names parallel path. # Substrate doctrine reinforced This is the SAME pattern that landed in PR #1490 (per-cursor mutex for data/query-next). The pattern generalizes: > Every ServiceModule that protects per-resource mutable state > across an `.await` (tokio::sync::Mutex) OR holds per-resource > filesystem invariants (std::sync::Mutex) must serialize per > resource, not module-wide. `DashMap<Id, Arc<Mutex<State>>>` is the > canonical pattern. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Card:
71923a08-b3de-448a-98ef-fe7cc3e817c0First sub-slice of L0-3a. Pure typed surface from
BRAIN-REGIONS-SUBSTRATE.md(merged via #1470). No region implementations, no algorithms, no governor integration — those land in L0-3a.1+ slices.Three new modules in
continuum-core/src/runtime/brain_region.rsready_buffer.rsregion_telemetry.rs11 ts-rs bindings emitted to
shared/generated/runtime/.Doctrine carried in (from #1470 addendum)
The trait shape enforces this:
Tests (74 pass, 0 fail in runtime::)
Boy-scout disclosure
cargo fmt -p continuum-core(run to ensure my new files were fmt-clean) picked up pre-existing fmt drift in ~30 unrelated files across the crate — all line-width / attribute-formatting changes on#[ts(export, export_to = ...)]etc. Semantically empty, follows the project's rustfmt config. Including the fix in this PR per boy-scout rule. If you'd rather see those broken out, I can revert them from this commit and ship a separate fmt-only PR.What is NOT in this card
Predecessors merged
Next slices
L0-3a.1 HippocampusModule skeleton → L0-3a.2 Engram/EngramGraph types → L0-3a.3 algorithm 4 (salience decay) → L0-3a.4 algorithm 2 (channel-as-bias) → L0-3a.5 algorithm 3 (activation spreading) → L0-3a.6 algorithm 1 (two-pool budget) → L0-3a.7 algorithm 5 (predictor + ready-buffer publish, the alive-feeling slice) → L0-3a.8 holdout fixture suite → L0-3a.9 TS Hippocampus.ts deletion.
🤖 Generated with Claude Code