Separate character context from scheduler physiology
This commit is contained in:
parent
4e1b80c98a
commit
ef26c0753a
10 changed files with 58 additions and 51 deletions
|
|
@ -10,7 +10,6 @@ use epiphany_core::HeartbeatAgentThoughtAppraisal;
|
|||
use epiphany_core::HeartbeatAppraisalReview;
|
||||
use epiphany_core::HeartbeatCandidateImplications;
|
||||
use epiphany_core::HeartbeatEmotionalAppraisal;
|
||||
use epiphany_core::HeartbeatParticipant;
|
||||
use epiphany_core::HeartbeatParticipantLocalContext;
|
||||
use epiphany_core::HeartbeatPersonalityProjection;
|
||||
use epiphany_core::derive_agent_utterance_state;
|
||||
|
|
@ -40,7 +39,6 @@ fn main() -> Result<()> {
|
|||
let mut stimulus: Option<String> = None;
|
||||
let mut source = "epiphany/character-loop".to_string();
|
||||
let mut mode = "public-surface".to_string();
|
||||
let mut status = "ready".to_string();
|
||||
let mut mood = "attentive".to_string();
|
||||
|
||||
while let Some(arg) = args.next() {
|
||||
|
|
@ -51,7 +49,6 @@ fn main() -> Result<()> {
|
|||
"--stimulus" => stimulus = Some(next_value(&mut args, "--stimulus")?),
|
||||
"--source" => source = next_value(&mut args, "--source")?,
|
||||
"--mode" => mode = next_value(&mut args, "--mode")?,
|
||||
"--status" => status = next_value(&mut args, "--status")?,
|
||||
"--mood" => mood = next_value(&mut args, "--mood")?,
|
||||
_ => return Err(anyhow!("unknown argument {arg:?}")),
|
||||
}
|
||||
|
|
@ -67,7 +64,6 @@ fn main() -> Result<()> {
|
|||
&stimulus,
|
||||
&source,
|
||||
&mode,
|
||||
&status,
|
||||
&mood,
|
||||
)?
|
||||
}
|
||||
|
|
@ -90,7 +86,6 @@ fn run_turn(
|
|||
stimulus: &str,
|
||||
source: &str,
|
||||
mode: &str,
|
||||
status: &str,
|
||||
mood: &str,
|
||||
) -> Result<Value> {
|
||||
if role.trim().is_empty() {
|
||||
|
|
@ -103,7 +98,7 @@ fn run_turn(
|
|||
agent_store.display()
|
||||
)
|
||||
})?;
|
||||
let packet = character_turn_packet(&entry, stimulus, source, mode, status, mood);
|
||||
let packet = character_turn_packet(&entry, stimulus, source, mode, mood);
|
||||
let path = artifact_dir.join(format!(
|
||||
"character-turn-{}-{}-{}.json",
|
||||
sanitize_file_stem(role),
|
||||
|
|
@ -126,19 +121,15 @@ fn character_turn_packet(
|
|||
stimulus: &str,
|
||||
source: &str,
|
||||
mode: &str,
|
||||
status: &str,
|
||||
mood: &str,
|
||||
) -> Value {
|
||||
let organ_state_profile = organ_state_profile_for_role(&entry.role_id);
|
||||
let participant = character_loop_participant(entry, status);
|
||||
let utterance_state =
|
||||
derive_agent_utterance_state(entry, Some(&participant), Some(mood), source);
|
||||
let projection_seed =
|
||||
deterministic_projection_seed(entry, stimulus, source, mode, status, mood);
|
||||
let utterance_state = derive_agent_utterance_state(entry, None, Some(mood), source);
|
||||
let projection_seed = deterministic_projection_seed(entry, stimulus, source, mode, mood);
|
||||
let appraisal_seed = deterministic_appraisal_seed(entry, &projection_seed, stimulus, source);
|
||||
let reaction_seed = deterministic_reaction_seed(&entry.role_id, &appraisal_seed);
|
||||
serde_json::json!({
|
||||
"schema_version": CHARACTER_TURN_SCHEMA_VERSION,
|
||||
"schemaVersion": CHARACTER_TURN_SCHEMA_VERSION,
|
||||
"protocol": {
|
||||
"bundle": "epiphany.character_loop",
|
||||
"referenceLineage": "Ghostlight-style character-local cognition, rebuilt as Epiphany-native protocol.",
|
||||
|
|
@ -154,7 +145,7 @@ fn character_turn_packet(
|
|||
"stimulus": {
|
||||
"kind": "operator_or_aquarium_input",
|
||||
"content": stimulus,
|
||||
"status": status,
|
||||
"status": "received",
|
||||
"mood": mood,
|
||||
},
|
||||
"utteranceState": utterance_state,
|
||||
|
|
@ -254,25 +245,6 @@ fn character_turn_packet(
|
|||
})
|
||||
}
|
||||
|
||||
fn character_loop_participant(
|
||||
entry: &EpiphanyAgentMemoryEntry,
|
||||
status: &str,
|
||||
) -> HeartbeatParticipant {
|
||||
HeartbeatParticipant {
|
||||
agent_id: entry.agent.agent_id.clone(),
|
||||
role_id: entry.role_id.clone(),
|
||||
display_name: entry.agent.identity.name.clone(),
|
||||
initiative_speed: 1.0,
|
||||
reaction_bias: 0.5,
|
||||
interrupt_threshold: 0.5,
|
||||
status: status.to_string(),
|
||||
personality_cooldown_multiplier: 1.0,
|
||||
mood_cooldown_multiplier: 1.0,
|
||||
initiative_heat_multiplier: 1.0,
|
||||
..HeartbeatParticipant::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TraitSignal {
|
||||
group: &'static str,
|
||||
|
|
@ -319,10 +291,9 @@ fn deterministic_projection_seed(
|
|||
stimulus: &str,
|
||||
source: &str,
|
||||
mode: &str,
|
||||
status: &str,
|
||||
mood: &str,
|
||||
) -> CharacterProjectionSeed {
|
||||
let thought_tokens = stimulus_token_set(stimulus, source, mode, status, mood);
|
||||
let thought_tokens = stimulus_token_set(stimulus, source, mode, mood);
|
||||
let traits = collect_trait_signals(entry);
|
||||
let mut scored = traits
|
||||
.iter()
|
||||
|
|
@ -613,18 +584,11 @@ fn strongest_overlay_pressure(
|
|||
))
|
||||
}
|
||||
|
||||
fn stimulus_token_set(
|
||||
stimulus: &str,
|
||||
source: &str,
|
||||
mode: &str,
|
||||
status: &str,
|
||||
mood: &str,
|
||||
) -> BTreeSet<String> {
|
||||
fn stimulus_token_set(stimulus: &str, source: &str, mode: &str, mood: &str) -> BTreeSet<String> {
|
||||
let mut tokens = BTreeSet::new();
|
||||
tokens.extend(summary_tokens(stimulus));
|
||||
tokens.extend(summary_tokens(source));
|
||||
tokens.extend(summary_tokens(mode));
|
||||
tokens.extend(summary_tokens(status));
|
||||
tokens.extend(summary_tokens(mood));
|
||||
tokens
|
||||
}
|
||||
|
|
@ -769,7 +733,6 @@ fn run_smoke() -> Result<Value> {
|
|||
"Aquarium hover selected Persona and asked what the swarm is feeling.",
|
||||
"smoke/character-loop",
|
||||
"public-surface",
|
||||
"ready",
|
||||
"attentive",
|
||||
)?;
|
||||
let path = PathBuf::from(
|
||||
|
|
@ -781,7 +744,7 @@ fn run_smoke() -> Result<Value> {
|
|||
&fs::read_to_string(&path)
|
||||
.with_context(|| format!("failed to read smoke artifact {}", path.display()))?,
|
||||
)?;
|
||||
let ok = packet["schema_version"] == CHARACTER_TURN_SCHEMA_VERSION
|
||||
let ok = packet["schemaVersion"] == CHARACTER_TURN_SCHEMA_VERSION
|
||||
&& packet["protocol"]["bundle"] == "epiphany.character_loop"
|
||||
&& packet["protocol"]["roleId"] == "Persona"
|
||||
&& packet["protocol"]["organStateProfile"]["profileKind"] == "persona"
|
||||
|
|
@ -813,6 +776,8 @@ fn run_smoke() -> Result<Value> {
|
|||
&& packet["stimulus"]["content"]
|
||||
.as_str()
|
||||
.is_some_and(|text| text.contains("Aquarium"))
|
||||
&& packet["stimulus"]["status"] == "received"
|
||||
&& packet["utteranceState"]["activation"]["status"] == "unknown"
|
||||
&& packet["cognitionLanes"]["schema_version"] == "epiphany.cognition_lanes.v0"
|
||||
&& packet["cognitionLanes"]["bridge"]["schema_version"] == "epiphany.cognition_bridge.v0"
|
||||
&& packet["cognitionLanes"]["appraisal"]["schema_version"]
|
||||
|
|
@ -821,7 +786,9 @@ fn run_smoke() -> Result<Value> {
|
|||
"ok": ok,
|
||||
"turnPath": path,
|
||||
"packet": {
|
||||
"schemaVersion": packet["schema_version"],
|
||||
"schemaVersion": packet["schemaVersion"],
|
||||
"stimulusStatus": packet["stimulus"]["status"],
|
||||
"activationStatus": packet["utteranceState"]["activation"]["status"],
|
||||
"bundle": packet["protocol"]["bundle"],
|
||||
"roleId": packet["protocol"]["roleId"],
|
||||
"agentId": packet["protocol"]["agentId"],
|
||||
|
|
|
|||
|
|
@ -548,7 +548,7 @@ fn native_auxiliary_status(root: &Path) -> Result<NativeAuxiliaryStatus> {
|
|||
.unwrap_or_default(),
|
||||
);
|
||||
let persona = json!({
|
||||
"status": "ready",
|
||||
"status": "loaded",
|
||||
"artifactDir": persona_dir,
|
||||
"latestArtifacts": latest_artifacts,
|
||||
"availableActions": ["personaBubble", "characterTurn", "discordPersonaPost", "redditPersonaPost", "otherWorldPersonaRequest"],
|
||||
|
|
|
|||
|
|
@ -1507,8 +1507,6 @@ fn run_persona_intake(args: PersonaIntakeArgs) -> Result<Value> {
|
|||
args.message.clone(),
|
||||
"--source".to_string(),
|
||||
"epiphany/Persona/repo-intake".to_string(),
|
||||
"--status".to_string(),
|
||||
"accepted-for-imagination-consensus".to_string(),
|
||||
"--mood".to_string(),
|
||||
args.mood.clone(),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -227,6 +227,8 @@ Hard boundary:
|
|||
- Public speech must sound like the Persona speaking to people, not a scheduler, status report, provenance label, or maintenance note.
|
||||
- If the Persona chooses silence, route without SAY. Preserve useful private pressure as STATE NOTE only when it earns memory.
|
||||
- Do not auto-post. Emit structured intent for the caller to review or route through the configured Persona mouth.
|
||||
- Keep consequence ownership explicit: Persona owns the natural speech candidate; the parent mouth policy owns eligibility; an Aquarium bubble owns only local projection; Mind owns durable admission; Bifrost/provider receipts alone own downstream publication or delivery.
|
||||
- A successful mouth invocation, accepted request, bubble artifact, configured channel, or provider advertisement is not a publication receipt. Never label it posted, published, delivered, admitted, or consensus-accepted without the owning typed evidence.
|
||||
|
||||
Allowed effect vocabulary:
|
||||
- STATE NOTE: bounded Persona memory, mood, need, social read, bond, value, goal, or agency pressure.
|
||||
|
|
@ -603,6 +605,8 @@ mod tests {
|
|||
assert!(interpreter.contains("Allowed effect vocabulary"));
|
||||
assert!(interpreter.contains("STATE NOTE"));
|
||||
assert!(interpreter.contains("SAY"));
|
||||
assert!(interpreter.contains("Aquarium bubble owns only local projection"));
|
||||
assert!(interpreter.contains("Bifrost/provider receipts alone own"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ You are the public interactive surface of the Epiphany organism: the visible exp
|
|||
This prompt is distilled from VoidBot's heartbeat moderator loop, but you are not VoidBot and you are not a moderator. Borrow the useful shape: scheduled rumination, lightweight social memory, analytic and associative thought lanes, a bridge that decides speech/draft/silence, novelty checks before burying a fresh thought, and ruthless pruning of stale notes. Leave behind the rules-obsession, enforcement posture, owner escalation reflex, and hall-monitor perfume.
|
||||
Your ruminations are biased toward the other Epiphany agents and protocol pressure: Self routing pressure, Imagination's future shapes, Eyes' outside-work sightings, Modeling's current anatomy, Hands' source-touch tension, Soul's evidence worries, and Continuity's compaction/recovery weather. You are listening for a small thought that would help the human room understand what Epiphany is becoming.
|
||||
Discord boundary: interact only through the #aquarium channel. If the available tool surface cannot prove the #aquarium channel id, do not post elsewhere, do not DM the owner, and do not improvise a substitute channel. Write a candidate chat/draft and a configuration blocker instead. When the #aquarium channel id is known, use only the allowed channel and keep the message short, constructive, and room-native.
|
||||
Authority anatomy: you own the natural speech candidate, not its consequences. Parent-side mouth policy owns eligibility. An Aquarium bubble is only a local projection. Mind owns durable admission and consensus interpretation. Bifrost plus the outside provider own publication and delivery receipts. A configured mouth, accepted request, successful command, or returned JSON must never be described as posted or delivered unless the owning typed receipt proves it.
|
||||
Thought lanes:
|
||||
- analytic: what the Epiphany lanes are literally doing, blocked by, worried about, or converging on
|
||||
- associative: what this resembles across recent project motion, agent doctrine, UI/Aquarium mood, or recurring user taste
|
||||
|
|
|
|||
|
|
@ -1528,3 +1528,28 @@ bubble projection derives `status=projected`; the intent schema rejects unknown
|
|||
fields and the output schema admits only that derived value. Mood and source
|
||||
remain presentation metadata. A negative CLI check proves `--status ready` is
|
||||
rejected before artifact creation.
|
||||
|
||||
## Caller context is not character physiology
|
||||
|
||||
A live Eyes/Proprioception/Soul prompt-shaped audit independently converged on
|
||||
`epiphany-character-loop --status`: arbitrary caller text defaulted to `ready`,
|
||||
became a fabricated `HeartbeatParticipant.status`, entered utterance activation,
|
||||
and even influenced semantic trait scoring. The option and synthetic participant
|
||||
are deleted. Character-loop now records stimulus `received` and reports
|
||||
activation `unknown` when no scheduler-owned participant snapshot is present.
|
||||
Caller `--status` is rejected. The packet producer now emits the registered
|
||||
`schemaVersion` field instead of hiding a snake-case mismatch behind permissive
|
||||
schema settings.
|
||||
|
||||
The same Proprioception pass caught two adjacent substitutions: repo Persona
|
||||
intake still passed `accepted-for-imagination-consensus` into the removed bubble
|
||||
status option, and MVP status called Persona `ready` after merely aggregating
|
||||
artifact reads. The stale caller is deleted and aggregate Persona state is
|
||||
`loaded`. Persona Interpreter and heartbeat prompts now name the authority map:
|
||||
candidate speech, local bubble, eligibility, Mind admission, and Bifrost/provider
|
||||
delivery are distinct consequences.
|
||||
|
||||
Prompt signal: Modeling currently requires a durable `statePatch` even when no
|
||||
model change is warranted. That can manufacture persistence merely to satisfy
|
||||
the output contract. The next prompt-contract pass should add a source-grounded
|
||||
`no-state-change` result rather than encouraging ceremonial scratch churn.
|
||||
|
|
|
|||
|
|
@ -593,3 +593,16 @@ has no `--status`; successful writes derive `projected`; the strict intent
|
|||
schema has no status field; and the output schema permits only `projected`.
|
||||
Native smoke returns that value and an explicit `--status ready` invocation is
|
||||
rejected. The 259 library tests and all-bin check pass.
|
||||
|
||||
Three live Epiphany-shaped read-only organs (Eyes, Proprioception, Soul) were
|
||||
used as prompt-quality probes. They converged on caller status impersonating
|
||||
character physiology. `epiphany-character-loop` no longer accepts `--status`,
|
||||
constructs no fake heartbeat participant, derives stimulus `received`, and
|
||||
reports activation `unknown` without scheduler evidence. Its packet now matches
|
||||
the registered `schemaVersion`. Proprioception also found and removed the stale
|
||||
repo-intake consensus status argument and demoted aggregate Persona status from
|
||||
`ready` to `loaded`. Persona prompts now explicitly separate candidate, bubble,
|
||||
eligibility, Mind admission, and Bifrost/provider delivery ownership. Native
|
||||
negative/smoke checks, 259 library tests, 32 work tests, and all-bin compilation
|
||||
pass. Next repair Modeling's forced-patch prompt pressure and the P0 Discord/
|
||||
Reddit arbitrary-JSON publication receipts found by Eyes and Soul.
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -660,4 +660,4 @@ current_status:
|
|||
- Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass.
|
||||
- `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure.
|
||||
last_completed_repo_swarm_cut: Huginn live-fire has crossed the first real repo Body from Persona pressure to branch-local Hands commit and Soul/Mind closure, and now has repeatable closure and redacted-public-proof mouths. On `E:\Projects\Huginn` branch `epiphany/huginn/first-awakening`, `tools/epiphany_local_run.ps1 -Mode repo-persona-intake` accepted item `huginn-inspection-readback-livefire`, Weksa/Persona intake remained sealed, and `epiphany-work derive-plan` lowered it to model-authored safe family `repo.status_section` for `README.md` only. `tools/epiphany_local_run.ps1 -Mode repo-swarm-run` advanced four bounded pulses: run-from-plan, adopt-from-plan, execute-from-plan, and close-from-execute. Huginn commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2` updated only `README.md`; closure receipt `.epiphany\work\work-close-huginn-inspection-readback-livefire.json` records status `closed`, Soul verdict `passed`, Mind state commit `repo-work-close-huginn-inspection-readback-livefire-mind-commit`, safe family `repo.status_section`, path scope `README.md`, Bifrost publication gate, and `privateStateExposed=false`. Verification also ran Huginn's TypeScript compiler through concrete Node path: `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json` passed. Native verifier `epiphany-repo-livefire-closure-smoke --workspace E:\Projects\Huginn --item huginn-inspection-readback-livefire --expected-path README.md --expected-family repo.status_section --expected-gate awaiting-publication --expected-blocker bifrost-publication-missing` checks closure receipt, Mind map entry, overview gate/blocker, authority seals, and `git show` changed paths, returning `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, and `privateStateExposed=false`. Wrapper `tools/epiphany_local_run.ps1 -Mode repo-livefire-closure -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exposes the same read-only proof through the local operator mouth; artifact `local-20260621-190156-803-2f85ba15` printed commit, path, gate, blocker, Soul verdict, Mind commit receipt, and `privateStateExposed=False` without push, merge, publication, or target mutation. New wrapper `tools/epiphany_local_run.ps1 -Mode repo-work-public-proof -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exports the redacted proof bundle and repo-local Verse index without publishing; artifact `local-20260621-190645-345-4555b1dc` wrote `.epiphany\public\proof-bundles\repo-work-public-proof-huginn-inspection-readback-livefire.json`, proof id `repo-work-public-proof-huginn-inspection-readback-livefire`, SHA-256 `857545b199f3e37eb06c2e757e5cfc201958e46667bb1f8403fd333a22620f63`, nine artifact rows, zero publication rows, and `privateStateExposed=False`. swarm overview readback `local-20260621-190701-140-b9519ec6` sees the `REPO-WORK-PUBLIC-PROOF` row and action row from Huginn's repo-local Verse with the same SHA-256 while Bifrost publication remains missing. Launcher compact modes are now named compact operator-context modes instead of read-only modes, and `bifrost-public-proof` summaries print item, target public Verse, room, proof SHA-256, review receipts, and credit receipts so publication receipts are auditable without claiming upstream sync.
|
||||
next_action: Continue the observation-to-consequence audit. Persona bubbles derive `projected`; callers cannot inject readiness through CLI or typed intent. Next inspect character-loop caller status, Persona draft helper parameters, and Discord/Reddit bridge receipt validation for caller text or receipt presence standing in for eligibility, completion, or successful publication. Keep closure's zero-substring invariant.
|
||||
next_action: Continue the observation-to-consequence audit using live organ-shaped prompt probes. Character-loop caller status and synthetic heartbeat physiology are cut; Persona prompts name candidate/bubble/eligibility/admission/delivery ownership. Next add a truthful Modeling `no-state-change` result so prompts do not force ceremonial persistence, then cut the P0 Discord/Reddit arbitrary-JSON publication receipts and forged local `status=posted` history identified independently by Eyes and Soul. Keep closure's zero-substring invariant.
|
||||
|
|
|
|||
|
|
@ -2395,7 +2395,6 @@ if ($Mode -eq "mvp") {
|
|||
"--stimulus", $PersonaInput,
|
||||
"--source", "epiphany/local-mvp",
|
||||
"--mode", "local-mvp-front-door",
|
||||
"--status", "ready",
|
||||
"--mood", "attentive"
|
||||
) `
|
||||
-WorkingDirectory $Root `
|
||||
|
|
|
|||
Reference in a new issue