From ef26c0753ada6aecf469dfb7788f99abb6aa7ee2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 17:31:08 +0100 Subject: [PATCH] Separate character context from scheduler physiology --- .../src/bin/epiphany-character-loop.rs | 59 ++++-------------- epiphany-core/src/bin/epiphany-mvp-status.rs | 2 +- epiphany-core/src/bin/epiphany-work.rs | 2 - epiphany-core/src/persona_turn.rs | 4 ++ .../src/prompts/epiphany_specialists.toml | 1 + ...onceptual-substitution-audit-2026-07-12.md | 25 ++++++++ notes/fresh-workspace-handoff.md | 13 ++++ state/ledgers.msgpack | Bin 482804 -> 483313 bytes state/map.yaml | 2 +- tools/epiphany_local_run.ps1 | 1 - 10 files changed, 58 insertions(+), 51 deletions(-) diff --git a/epiphany-core/src/bin/epiphany-character-loop.rs b/epiphany-core/src/bin/epiphany-character-loop.rs index 287b89aa..7e9f8d22 100644 --- a/epiphany-core/src/bin/epiphany-character-loop.rs +++ b/epiphany-core/src/bin/epiphany-character-loop.rs @@ -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 = 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 { 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 { +fn stimulus_token_set(stimulus: &str, source: &str, mode: &str, mood: &str) -> BTreeSet { 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 { "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 { &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 { && 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 { "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"], diff --git a/epiphany-core/src/bin/epiphany-mvp-status.rs b/epiphany-core/src/bin/epiphany-mvp-status.rs index c8e81df7..700e2795 100644 --- a/epiphany-core/src/bin/epiphany-mvp-status.rs +++ b/epiphany-core/src/bin/epiphany-mvp-status.rs @@ -548,7 +548,7 @@ fn native_auxiliary_status(root: &Path) -> Result { .unwrap_or_default(), ); let persona = json!({ - "status": "ready", + "status": "loaded", "artifactDir": persona_dir, "latestArtifacts": latest_artifacts, "availableActions": ["personaBubble", "characterTurn", "discordPersonaPost", "redditPersonaPost", "otherWorldPersonaRequest"], diff --git a/epiphany-core/src/bin/epiphany-work.rs b/epiphany-core/src/bin/epiphany-work.rs index 759718da..732940b8 100644 --- a/epiphany-core/src/bin/epiphany-work.rs +++ b/epiphany-core/src/bin/epiphany-work.rs @@ -1507,8 +1507,6 @@ fn run_persona_intake(args: PersonaIntakeArgs) -> Result { 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(), ], diff --git a/epiphany-core/src/persona_turn.rs b/epiphany-core/src/persona_turn.rs index cd20abf0..6c8bef4f 100644 --- a/epiphany-core/src/persona_turn.rs +++ b/epiphany-core/src/persona_turn.rs @@ -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] diff --git a/epiphany-core/src/prompts/epiphany_specialists.toml b/epiphany-core/src/prompts/epiphany_specialists.toml index 70baa481..367caa99 100644 --- a/epiphany-core/src/prompts/epiphany_specialists.toml +++ b/epiphany-core/src/prompts/epiphany_specialists.toml @@ -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 diff --git a/notes/conceptual-substitution-audit-2026-07-12.md b/notes/conceptual-substitution-audit-2026-07-12.md index 319f079d..c5b74c41 100644 --- a/notes/conceptual-substitution-audit-2026-07-12.md +++ b/notes/conceptual-substitution-audit-2026-07-12.md @@ -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. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 289cda55..9670f1de 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -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. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index c2d7d6feef1706a472cbd4ef7e07373274139447..0ee560101fd0bbcc06f143f9aee940702e4fb872 100644 GIT binary patch delta 526 zcmX|8O=}cE5Op>md+?waL6AT}uCp6SjG!SW@gM|=3JDlddV9*5VtTqd-95AJX%9Jg z@h0p3jX(|_a`5IQ=wHZh5WH)(8*_M6L)EMIUiI(uwO`-XPVZ(nx9&gQe)#(PjsDx8 zXY1K%TjiU%u(zUQWc%FTUKH{@9}q;pqg!#=d9g zi8Z=NY&TwUYruJKTufzz0W{kVeTfCI1+9!h4Bi!kA*kZG@mJEZSrgJ1Wp>+=2J>L0mYcYn(178g(Gtc0f35(%hP&UAE delta 61 zcmeykU-rvx*$onm)8EIiv$91UV_=WDxLKL8kdd*wc`8%;R3=6s2GPtw%mT!$K+Lv% JDieErGyuFc6mkFn diff --git a/state/map.yaml b/state/map.yaml index f1c55228..09f0b08e 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -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. diff --git a/tools/epiphany_local_run.ps1 b/tools/epiphany_local_run.ps1 index ca727520..aa15a0d4 100644 --- a/tools/epiphany_local_run.ps1 +++ b/tools/epiphany_local_run.ps1 @@ -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 `