Add the Persona lane and the recency marker (Cut 6)
PersonaLane (crates/ghostlight/src/controllers.rs) is the unpersisted Projector-then-Persona turn PA-Q3 A called for: no work-store row, no persisted state, a restart just repeats the two inferences. Its `turn` runs Projector then Persona for a NarrativePersona opportunity and returns a receipt-bound PersonaTurn; `narrate` runs the Projector alone over a Human opportunity for the player's own view. Both refuse the wrong mode before touching the mailbox, and a retired subject is refused by `select` itself (no live opportunity survives retirement). The Projector-then-Persona prompt builders in `run_narrative` and `run_projector` move into two private functions, `build_projector_invocation` and `build_persona_invocation`, so PersonaLane and the narrative lane build both prompts through the same one seam: a Persona's input is Projector prose and nothing else (invariant 2). PA-Q2's recency marker: `SubjectSnapshot.last_acted_at` is derived in `snapshot` from one pass over `state.events` (the greatest DecisionEvent.revision scoped to that subject), and `projector_knowledge` stamps each row `"new": minted_at > last_acted_at`. A subject that never acted sees every minted row as new; an unminted background fact (no revision) is never new, having no arrival time to compare. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
d392c088c3
commit
c4334cce3b
2 changed files with 1032 additions and 32 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -83,7 +83,7 @@ pub use controllers::{
|
|||
ControllerWorkLookup, ControllerWorkStore, ControllerWorkStoreError, ControllerWorkWrite,
|
||||
GroupedCheckpoint, InferenceEvent, InferenceFault, InferenceOutput, InferencePort,
|
||||
InferencePurpose, InferenceRequest, NarrativeCheckpoint, NarrativeRun, OperationalCheckpoint,
|
||||
OperationalRun, PreparedInference, SubmissionDisposition, ToolResultOracle,
|
||||
OperationalRun, PersonaLane, PreparedInference, SubmissionDisposition, ToolResultOracle,
|
||||
TracingInferencePort, open_controller_work, open_inference,
|
||||
};
|
||||
pub(crate) use cover::{CellId, Constituent, Resolution};
|
||||
|
|
@ -1031,6 +1031,11 @@ pub struct SubjectSnapshot {
|
|||
/// The seed brief needs to say which subjects count; recomputing the
|
||||
/// conjunction outside the kernel would be a second definition of alive.
|
||||
pub qualified: bool,
|
||||
/// The greatest `DecisionEvent.revision` whose scope is this subject, or
|
||||
/// `None` if the subject never acted. Derived from `state.events` alone:
|
||||
/// no new state, no schema change. The recency marker `projector_knowledge`
|
||||
/// stamps onto each knowledge row is `minted_at > last_acted_at`.
|
||||
pub(crate) last_acted_at: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -3738,6 +3743,20 @@ fn snapshot(state: &WorldState) -> Result<WorldSnapshot, KernelError> {
|
|||
.map(move |fact| (fact, event.revision))
|
||||
})
|
||||
.collect();
|
||||
// One pass over the causal record so each subject can be stamped with the
|
||||
// revision of its own last exercised decision. `None` for a subject that
|
||||
// never acted.
|
||||
let last_acted_at: BTreeMap<SubjectId, u64> =
|
||||
state
|
||||
.events
|
||||
.iter()
|
||||
.fold(BTreeMap::new(), |mut acc, event| {
|
||||
let entry = acc.entry(event.scope.subject_id).or_insert(event.revision);
|
||||
if event.revision > *entry {
|
||||
*entry = event.revision;
|
||||
}
|
||||
acc
|
||||
});
|
||||
let subjects = state
|
||||
.subjects
|
||||
.iter()
|
||||
|
|
@ -3863,6 +3882,7 @@ fn snapshot(state: &WorldState) -> Result<WorldSnapshot, KernelError> {
|
|||
})
|
||||
.collect(),
|
||||
qualified: qualifies(state, *subject_id),
|
||||
last_acted_at: last_acted_at.get(subject_id).copied(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, KernelError>>()?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue