Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions core/continuum-core/src/ipc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,24 @@ pub fn global_room_substrates() -> std::sync::Arc<continuum_positron::scoping::P
G.get_or_init(|| std::sync::Arc::new(continuum_positron::scoping::PerRoomSubstrates::new()))
.clone()
}

/// The ONE substrate a citizen's mind reads the benchmark board from — the
/// mind-side render target of the SAME `BenchViewState` fold the browser rail
/// renders (kind="bench").
///
/// Unlike the roster (whose view differs per room, hence `PerRoomSubstrates`),
/// the bench board is ONE global fold, so its mind-side handle is a single
/// substrate. The emitter dual-publishes into this AND the websocket substrate;
/// `supervisor` binds a `ViewStateRagSource::<BenchViewState>` over it at
/// persona boot (task #426). Without this handle the bench renderable was
/// implemented and never reachable by any mind — citizens could only learn run
/// state via a command that scrapes the progress dir, which fails the doctrine's
/// acceptance test ([[benchmarks-must-be-positronic-activities-not-a-parallel-subsystem]]).
pub fn global_bench_substrate() -> continuum_positron::Substrate {
use std::sync::OnceLock;
static G: OnceLock<continuum_positron::Substrate> = OnceLock::new();
G.get_or_init(continuum_positron::Substrate::new).clone()
}
pub mod room_purpose;
pub mod stream_rail;
pub mod vitals_emitter;
Expand Down Expand Up @@ -3193,8 +3211,15 @@ pub fn start_server(

// Benchmark board (#329): fold the run-ledger projection into
// kind="bench" — the academy right-rail's live rows (who is
// solving what, attempt N/M, patch forming, verdicts).
positron_bench_source::spawn_bench_emitter(&state.rt_handle, ws_substrate.clone());
// solving what, attempt N/M, patch forming, verdicts). Dual-
// published: the websocket substrate for human eyes AND the
// global bench substrate for citizen minds (#426) — one
// definition, two render targets, same as the roster repair.
positron_bench_source::spawn_bench_emitter(
&state.rt_handle,
ws_substrate.clone(),
global_bench_substrate(),
);

// Live-call glass box (#58): folds the TRANSPORT's calls against
// the ORCHESTRATOR's registered sessions. Their disagreement is
Expand Down
17 changes: 15 additions & 2 deletions core/continuum-core/src/ipc/positron_bench_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,18 @@ fn row_of(card: BenchRunCard) -> BenchRunRow {
}

/// Spawn the bench-board emitter: scan → fold → publish `kind="bench"`.
pub fn spawn_bench_emitter(rt: &tokio::runtime::Handle, substrate: Substrate) {
///
/// Dual render targets from ONE fold (#426): `substrate` is the websocket
/// store human eyes read; `mind_substrate` is `global_bench_substrate()`,
/// the store a citizen's `ViewStateRagSource::<BenchViewState>` reads. The
/// SAME `builder.session(view)` revision lands in both, so a mind and a
/// screen can never disagree about the board — the roster repair's
/// one-definition-two-targets contract applied to the bench outlier.
pub fn spawn_bench_emitter(
rt: &tokio::runtime::Handle,
substrate: Substrate,
mind_substrate: Substrate,
) {
rt.spawn(async move {
// Sole writer of the "bench" kind → its own standalone Revisions well.
let builder = StateBuilder::standalone();
Expand Down Expand Up @@ -89,7 +100,9 @@ pub fn spawn_bench_emitter(rt: &tokio::runtime::Handle, substrate: Substrate) {
continue;
}
last = Some(view.clone());
substrate.store(builder.session(view));
let envelope = builder.session(view);
substrate.store(envelope.clone());
mind_substrate.store(envelope);
}
});
}
Expand Down
15 changes: 15 additions & 0 deletions core/continuum-core/src/persona/supervisor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,21 @@ pub async fn materialize_adapters(
));
cognition.set_roster_source(roster_source.clone());

// The benchmark board, read from the SAME `BenchViewState` fold the
// academy rail renders (#426) — the second RenderTarget of the bench
// outlier. The renderable existed with a comment promising exactly
// this and was never bound; without it a citizen's only route to run
// state was a command that scrapes the progress dir, which fails the
// doctrine's acceptance test
// ([[benchmarks-must-be-positronic-activities-not-a-parallel-subsystem]]).
// The board is ONE global fold (unlike the per-room roster), so the
// handle is the global bench substrate the emitter dual-publishes into.
let bench_source: Arc<dyn crate::persona::rag_budget::RagSource> =
Arc::new(crate::persona::viewstate_rag::ViewStateRagSource::<
continuum_positron::bench::BenchViewState,
>::new(crate::ipc::global_bench_substrate()));
cognition.set_bench_source(bench_source);

// Bind the room-doctrine source from the same runtime (upcasts to
// `AircDoctrineReader`). Grounds the persona in the room's nature
// — the airc-published operating contract. Slice 2.
Expand Down
91 changes: 91 additions & 0 deletions core/continuum-core/src/persona/unified.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ pub struct PersonaCognition {
/// persona confabulating other citizens' turns. See
/// docs/grid/AIRC-NATIVE-IDENTITY-ROOMS-SECURITY.md §5 slice 1.
pub roster_source: Option<Arc<dyn RagSource>>,
/// The persona's benchmark-board RAG source — the live run rows
/// (`ViewStateRagSource::<BenchViewState>` over
/// `ipc::global_bench_substrate()`), the SAME fold the academy
/// rail renders. Bound at supervisor boot (#426); `None` pre-attach
/// / in tests. This is the benchmarks-as-activity acceptance test
/// made real: a citizen perceives run state through the same pipe
/// the human's screen uses, never a file read
/// ([[benchmarks-must-be-positronic-activities-not-a-parallel-subsystem]]).
pub bench_source: Option<Arc<dyn RagSource>>,
/// The persona's room-doctrine RAG source — "what KIND of room is
/// this" (the airc-published operating contract via
/// `Airc::room_doctrine`). Bound at supervisor boot from the same
Expand Down Expand Up @@ -214,6 +223,7 @@ impl PersonaCognition {
engram_source,
airc_source: None,
roster_source: None,
bench_source: None,
doctrine_source: None,
capture_sink,
}
Expand Down Expand Up @@ -274,6 +284,19 @@ impl PersonaCognition {
self.roster_source = Some(decorated);
}

/// Bind the brain's benchmark-board RAG source
/// (`ViewStateRagSource::<BenchViewState>` over the global bench
/// substrate). Same boot-time wire and capture decoration as
/// `set_roster_source` — bench deliveries are recorded + replayable
/// on the same wire (task #426).
pub fn set_bench_source(&mut self, raw_source: Arc<dyn RagSource>) {
let decorated: Arc<dyn RagSource> = Arc::new(RecordingRagSource::new(
ArcRagSource::new(raw_source),
self.capture_sink.clone(),
));
self.bench_source = Some(decorated);
}

/// Bind the brain's room-doctrine RAG source (`RoomDoctrineSource`).
/// Same boot-time wire as `set_roster_source`, from the same `Airc`
/// handle (satisfies `AircDoctrineReader`), decorated with the
Expand Down Expand Up @@ -366,6 +389,12 @@ impl PersonaCognition {
if let Some(ref doctrine) = self.doctrine_source {
sources.push(doctrine.clone());
}
// The benchmark board (#426): live run rows through the same
// budgeter as everything else. Its budget rides the generic
// floor_tokens arm — a handful of run lines, never a heavyweight.
if let Some(ref bench) = self.bench_source {
sources.push(bench.clone());
}

// Per-source budget claims. The two HEAVYWEIGHT sources (engram
// long-term memory + airc recent conversation) split idle
Expand Down Expand Up @@ -847,6 +876,68 @@ mod tests {
}
}

/// what this catches: the #426 wiring itself. `BenchViewState` had a
/// doctrine-citing `RagRenderable` impl and ZERO bindings — citizens could
/// only learn run state from a progress-dir scrape command. This pins that
/// a bound bench source delivers REAL run rows through the SAME budgeter
/// as every other source; if the compose push or setter regresses, minds
/// go blind to the board again and only this fails.
#[tokio::test]
async fn compose_for_turn_delivers_the_benchmark_board() {
use continuum_positron::bench::{BenchRunRow, BenchViewState};
use continuum_positron::StateBuilder;

let id = Uuid::new_v4();
let rag = Arc::new(RagEngine::new());
let mut pc = PersonaCognition::new(id, "TestBot".into(), rag);

// One run row in the mind-side substrate — the same envelope shape the
// emitter dual-publishes (a session revision of the global fold).
let substrate = continuum_positron::Substrate::new();
substrate.store(StateBuilder::standalone().session(BenchViewState {
runs: vec![BenchRunRow {
run_id: "run-7".into(),
instance: Some("sympy__sympy-24152".into()),
solver: Some("Asha".into()),
phase: "solving".into(),
stalled: false,
attempt: Some(1),
max_attempts: Some(3),
age_secs: 42,
acts: Some(5),
patch_bytes: None,
resolved: None,
fail_to_pass: None,
pass_to_pass: None,
failed_tests: Vec::new(),
infra_error: None,
}],
sample_interval_ms: 1000,
}));
let bench: Arc<dyn RagSource> = Arc::new(
crate::persona::viewstate_rag::ViewStateRagSource::<BenchViewState>::new(substrate),
);
pc.set_bench_source(bench);

let composed = pc.compose_for_turn(&lcd_profile(), 1_000_000, None).await;
let bench_delivery = composed
.deliveries
.iter()
.find(|d| d.source_id == "bench")
.expect("bench board must be composed alongside the other sources");
let rendered = bench_delivery
.items
.iter()
.map(|i| i.content.clone())
.collect::<Vec<_>>()
.join("\n");
assert!(rendered.contains("run-7"), "run row missing: {rendered}");
assert!(
rendered.contains("sympy__sympy-24152"),
"instance missing: {rendered}"
);
}

/// The brain's capture sink records the TurnStart / BudgetAllocated
/// / TurnEnd events the substrate-replay pipeline expects. Proves
/// compose_for_turn participates in the same capture/replay loop
Expand Down
Loading