Add dynamic Persona memory recall

This commit is contained in:
Meta Vens 2026-06-21 18:54:52 +01:00
commit 003d84e09f
11 changed files with 295 additions and 18 deletions

View file

@ -15,6 +15,7 @@ use epiphany_core::build_persona_interpreter_prompt;
use epiphany_core::build_persona_memory_chunks;
use epiphany_core::build_persona_projector_prompt;
use epiphany_core::build_persona_turn_prompt;
use epiphany_core::render_dynamic_persona_memory_recall_for_output;
use epiphany_core::render_persona_memory_recall_with_cache;
use epiphany_core::render_persona_semantic_memory_recall;
use epiphany_core::semantic_memory_recall_from_heartbeat_action;
@ -147,20 +148,64 @@ fn main() {
assert_contains(&persona_prompt, "Semantic memory recall");
assert_contains(&persona_prompt, "Fallback typed memory graph context");
let persona_output = "I can speak, but the effect needs a receipt.";
let dynamic = render_dynamic_persona_memory_recall_for_output(
&persona_memory_entry(),
"state/agents.msgpack#Persona",
&persona_prompt,
persona_output,
&recall,
4,
Some(&EpiphanyMemoryContextPacket {
id: "memctx-smoke-dynamic-fallback".to_string(),
query_id: "persona-smoke-dynamic-output".to_string(),
summaries: vec![EpiphanyMemorySummary {
id: "summary-smoke-dynamic-fallback".to_string(),
target: "role:Persona".to_string(),
claim: "Dynamic self-memory recall should inspect the Persona output before Mind interprets side effects."
.to_string(),
action_implication:
"Interpreter should see output-triggered recall, not only the pre-turn prompt recall."
.to_string(),
freshness: EpiphanyMemoryFreshnessStatus::Ready,
confidence: 82,
..Default::default()
}],
..Default::default()
}),
&PersonaMemoryCacheConfig {
qdrant_url: "http://127.0.0.1:1".to_string(),
qdrant_api_key: None,
qdrant_timeout_ms: 1,
ollama_base_url: "http://127.0.0.1:1".to_string(),
ollama_model: "qwen3-embedding:0.6b".to_string(),
ollama_timeout_ms: 1,
collection_name: "epiphany_persona_memory_dynamic_smoke".to_string(),
query_instruction: "dynamic-smoke".to_string(),
},
);
assert_eq!(dynamic.status, "fallback");
assert_contains(
&dynamic.rendered_recall,
"Dynamic self-memory recall should inspect the Persona output",
);
let interpreter_prompt = build_persona_interpreter_prompt(&PersonaInterpreterInput {
identity,
persona_prompt,
persona_output: "I can speak, but the effect needs a receipt.".to_string(),
persona_output: persona_output.to_string(),
semantic_memory_recall: recall,
dynamic_semantic_memory_recall: dynamic.rendered_recall,
pending_mentions: Vec::new(),
allowed_channel_ids: vec!["aquarium".to_string()],
});
assert_contains(&interpreter_prompt, "Dynamic semantic memory recall");
assert_contains(&interpreter_prompt, "Dynamic self-memory recall");
assert_contains(&interpreter_prompt, "output-triggered recall");
assert_contains(&interpreter_prompt, "STATE NOTE");
assert_contains(&interpreter_prompt, "SAY");
println!(
"status=ok recallSource=typed-memory-graph qdrantCache=persona-memory-bridge-wired heartbeatRecall=prompt-wired liveQdrant=not-required personaLayers=projector,persona,interpreter privateStateExposed=false"
"status=ok recallSource=typed-memory-graph qdrantCache=persona-memory-bridge-wired heartbeatRecall=prompt-wired dynamicRecall=interpreter-output-wired liveQdrant=not-required personaLayers=projector,persona,interpreter privateStateExposed=false"
);
}

View file

@ -24,6 +24,7 @@ use epiphany_core::build_persona_turn_prompt;
use epiphany_core::build_weksa_interlingua_packet;
use epiphany_core::build_weksa_target_lowering_request;
use epiphany_core::record_weksa_target_lowering_receipt;
use epiphany_core::render_dynamic_persona_memory_recall_for_output;
use epiphany_core::render_persona_memory_recall_with_cache;
use epiphany_core::render_persona_semantic_memory_recall;
use epiphany_core::semantic_memory_recall_from_heartbeat_action;
@ -172,7 +173,7 @@ fn run_smoke(args: Args) -> Result<Value> {
green(
"persona-memory-recall",
"Persona/Mind",
"Qdrant-backed Persona memory cache and typed graph fallback feed Projector, Persona, and Interpreter prompts without exposing private state",
"Qdrant-backed Persona memory cache and typed graph fallback feed Projector, Persona, and output-triggered Interpreter prompts without exposing private state",
),
green(
"imagination-plan",
@ -313,6 +314,10 @@ fn verify_fresh_repo(value: &Value) -> Result<()> {
require_eq(value, &["familyAssertionsStatus"], "passed")?;
require_eq(value, &["publishStatus"], "publication-receipts-recorded")?;
require_eq(value, &["syncStatus"], "upstream-main-synced")?;
require_non_empty(value, &["personaMemoryRecallStatus"])?;
require_non_empty(value, &["personaMemoryRecallCacheStatus"])?;
require_non_empty(value, &["personaInterpreterDynamicRecallStatus"])?;
require_non_empty(value, &["personaInterpreterDynamicRecallCacheStatus"])?;
require_bool(value, &["upstreamMainSynced"], true)?;
require_eq(value, &["publicProofStatus"], "public-proof-exported")?;
require_bool(value, &["privateStateExposed"], false)
@ -646,17 +651,70 @@ fn verify_persona_memory_recall() -> Result<Value> {
"Persona prompt",
)?;
let persona_output = "I can speak, but the effect needs a receipt.";
let dynamic = render_dynamic_persona_memory_recall_for_output(
&memory_entry,
"state/agents.msgpack#Persona",
&persona_prompt,
persona_output,
&recall,
4,
Some(&EpiphanyMemoryContextPacket {
id: "memctx-repo-swarm-mvp-gate-dynamic-fallback".to_string(),
query_id: "repo-swarm-mvp-gate-persona-dynamic-output".to_string(),
summaries: vec![EpiphanyMemorySummary {
id: "summary-repo-swarm-mvp-gate-dynamic-fallback".to_string(),
target: "role:Persona".to_string(),
claim: "Dynamic self-memory recall should inspect the Persona output before Mind interprets side effects."
.to_string(),
action_implication:
"Interpreter should see output-triggered recall, not only the pre-turn prompt recall."
.to_string(),
freshness: EpiphanyMemoryFreshnessStatus::Ready,
confidence: 82,
..Default::default()
}],
..Default::default()
}),
&PersonaMemoryCacheConfig {
qdrant_url: "http://127.0.0.1:1".to_string(),
qdrant_api_key: None,
qdrant_timeout_ms: 1,
ollama_base_url: "http://127.0.0.1:1".to_string(),
ollama_model: "qwen3-embedding:0.6b".to_string(),
ollama_timeout_ms: 1,
collection_name: "epiphany_persona_memory_mvp_gate_dynamic".to_string(),
query_instruction: "repo-swarm-mvp-gate-dynamic".to_string(),
},
);
require_contains(
&dynamic.rendered_recall,
"Dynamic self-memory recall should inspect the Persona output",
"dynamic Persona memory recall",
)?;
if dynamic.private_state_exposed || dynamic.rendered_recall.contains("sealed private note") {
return Err(anyhow!(
"dynamic Persona memory recall exposed private state"
));
}
let interpreter_prompt = build_persona_interpreter_prompt(&PersonaInterpreterInput {
identity,
persona_prompt,
persona_output: "I can speak, but the effect needs a receipt.".to_string(),
persona_output: persona_output.to_string(),
semantic_memory_recall: recall,
dynamic_semantic_memory_recall: dynamic.rendered_recall,
pending_mentions: Vec::new(),
allowed_channel_ids: vec!["aquarium".to_string()],
});
require_contains(
&interpreter_prompt,
"Dynamic semantic memory recall",
"Dynamic self-memory recall",
"Persona Interpreter prompt",
)?;
require_contains(
&interpreter_prompt,
"output-triggered recall",
"Persona Interpreter prompt",
)?;
require_contains(
@ -670,10 +728,13 @@ fn verify_persona_memory_recall() -> Result<Value> {
"schemaVersion": "epiphany.repo_swarm_mvp_persona_memory_recall_gate.v0",
"status": bridge.status,
"cacheStatus": bridge.cache_status,
"dynamicCacheStatus": dynamic.cache_status,
"chunkCount": bridge.chunk_count,
"hitCount": bridge.hit_count,
"dynamicHitCount": dynamic.hit_count,
"fallbackContainsTypedGraph": true,
"heartbeatRecallWired": true,
"dynamicRecallWired": true,
"personaLayers": ["projector", "persona", "interpreter"],
"liveQdrantRequiredForSmoke": false,
"privateStateExposed": false,
@ -825,6 +886,19 @@ fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> {
}
}
fn require_non_empty(value: &Value, path: &[&str]) -> Result<()> {
let actual = path
.iter()
.try_fold(value, |current, key| current.get(*key))
.and_then(Value::as_str)
.unwrap_or("");
if actual.trim().is_empty() {
Err(anyhow!("expected {} to be non-empty", path.join(".")))
} else {
Ok(())
}
}
fn require_contains(haystack: &str, needle: &str, label: &str) -> Result<()> {
if haystack.contains(needle) {
Ok(())

View file

@ -311,6 +311,8 @@ fn run_smoke(args: Args) -> Result<Value> {
require_eq(&export, &["status"], "public-proof-exported")?;
require_non_empty(&intake, &["memoryRecallStatus"])?;
require_non_empty(&intake, &["memoryRecallCacheStatus"])?;
require_non_empty(&intake, &["interpreterDynamicRecallStatus"])?;
require_non_empty(&intake, &["interpreterDynamicRecallCacheStatus"])?;
require_non_empty(&intake, &["weksaLoweringReceiptId"])?;
require_bool(&intake, &["privateStateExposed"], false)?;
require_bool(&overview, &["privateStateExposed"], false)?;
@ -342,6 +344,9 @@ fn run_smoke(args: Args) -> Result<Value> {
"personaMemoryRecallStatus": intake["memoryRecallStatus"],
"personaMemoryRecallCacheStatus": intake["memoryRecallCacheStatus"],
"personaMemoryRecallHitCount": intake["memoryRecallHitCount"],
"personaInterpreterDynamicRecallStatus": intake["interpreterDynamicRecallStatus"],
"personaInterpreterDynamicRecallCacheStatus": intake["interpreterDynamicRecallCacheStatus"],
"personaInterpreterDynamicRecallHitCount": intake["interpreterDynamicRecallHitCount"],
"weksaLoweringReceiptId": intake["weksaLoweringReceiptId"],
"planStatus": plan["status"],
"preRunOverviewGate": pre_run_overview["gate"],

View file

@ -1931,6 +1931,9 @@ fn run_persona_intake(args: PersonaIntakeArgs) -> Result<Value> {
"memoryRecallStatus": receipt["persona"]["memoryRecall"]["status"],
"memoryRecallCacheStatus": receipt["persona"]["memoryRecall"]["cacheStatus"],
"memoryRecallHitCount": receipt["persona"]["memoryRecall"]["hitCount"],
"interpreterDynamicRecallStatus": receipt["persona"]["memoryRecall"]["interpreterDynamicRecall"]["status"],
"interpreterDynamicRecallCacheStatus": receipt["persona"]["memoryRecall"]["interpreterDynamicRecall"]["cacheStatus"],
"interpreterDynamicRecallHitCount": receipt["persona"]["memoryRecall"]["interpreterDynamicRecall"]["hitCount"],
"weksaLoweringReceiptId": receipt["persona"]["weksa"]["receiptId"],
"bubblePath": receipt["persona"]["bubblePath"],
"acceptReceiptPath": accept["receiptPath"],
@ -2006,6 +2009,20 @@ fn record_repo_persona_intake_memory_recall(
Some(&fallback),
&config,
);
let synthetic_persona_prompt = format!(
"Repo Persona intake message:\n{}\n\nInitial semantic memory recall:\n{}",
message, recall.rendered_recall
);
let dynamic_recall = epiphany_core::render_dynamic_persona_memory_recall_for_output(
&entry,
format!("{}#Persona", agent_store.display()),
&synthetic_persona_prompt,
message,
&recall.rendered_recall,
8,
Some(&fallback),
&config,
);
json!({
"schemaVersion": recall.schema_version,
@ -2016,6 +2033,26 @@ fn record_repo_persona_intake_memory_recall(
"chunkCount": recall.chunk_count,
"hitCount": recall.hit_count,
"renderedRecall": recall.rendered_recall,
"interpreterDynamicRecall": {
"schemaVersion": dynamic_recall.schema_version,
"status": dynamic_recall.status,
"cacheStatus": dynamic_recall.cache_status,
"identityId": dynamic_recall.identity_id,
"roleId": dynamic_recall.role_id,
"chunkCount": dynamic_recall.chunk_count,
"hitCount": dynamic_recall.hit_count,
"renderedRecall": dynamic_recall.rendered_recall,
"warnings": dynamic_recall.warnings,
"queryBasis": "candidate-say-meaning",
"authority": {
"scaffoldedFromOperatorMessage": true,
"fullAutonomousPersonaTurn": false,
"durableStateAdmitted": false,
"publicationAuthorized": false,
"privateStateExposed": false
},
"privateStateExposed": dynamic_recall.private_state_exposed
},
"warnings": recall.warnings,
"privateStateExposed": recall.private_state_exposed,
})

View file

@ -3879,10 +3879,23 @@ mod tests {
assert_eq!(recall["roleId"], "Persona");
assert_eq!(recall["privateStateExposed"], false);
assert!(
recall["renderedRecall"]
!recall["status"].as_str().unwrap_or_default().is_empty(),
"Persona recall should report whether it came from Qdrant or fallback"
);
assert!(
!recall["cacheStatus"]
.as_str()
.unwrap_or_default()
.contains("typed memory graph")
.is_empty(),
"Persona recall should report cache status"
);
assert!(
!recall["renderedRecall"]
.as_str()
.unwrap_or_default()
.trim()
.is_empty(),
"Persona recall should carry a rendered prompt surface"
);
assert!(recall["chunkCount"].as_u64().unwrap_or_default() > 0);
assert!(

View file

@ -621,6 +621,7 @@ pub use persona_memory_cache::PersonaMemoryRecallRender;
pub use persona_memory_cache::PersonaMemorySearchHit;
pub use persona_memory_cache::build_persona_memory_chunks;
pub use persona_memory_cache::index_persona_memory_chunks;
pub use persona_memory_cache::render_dynamic_persona_memory_recall_for_output;
pub use persona_memory_cache::render_persona_memory_recall_with_cache;
pub use persona_memory_cache::search_persona_memory_cache;
pub use persona_turn::PERSONA_INTERPRETER_PROMPT_SCHEMA_VERSION;

View file

@ -363,6 +363,53 @@ pub fn render_persona_memory_recall_with_cache(
}
}
pub fn render_dynamic_persona_memory_recall_for_output(
entry: &EpiphanyAgentMemoryEntry,
source_state_ref: impl Into<String>,
persona_prompt: &str,
persona_output: &str,
initial_recall_seed: &str,
limit: usize,
fallback_context: Option<&EpiphanyMemoryContextPacket>,
config: &PersonaMemoryCacheConfig,
) -> PersonaMemoryRecallRender {
let query = build_dynamic_persona_memory_recall_query(
&entry.agent.agent_id,
persona_prompt,
persona_output,
initial_recall_seed,
);
render_persona_memory_recall_with_cache(
entry,
source_state_ref,
&query,
limit,
fallback_context,
config,
)
}
pub fn build_dynamic_persona_memory_recall_query(
identity_id: &str,
persona_prompt: &str,
persona_output: &str,
initial_recall_seed: &str,
) -> String {
[
format!("Current train of thought from {identity_id}:"),
collapse_whitespace(persona_output, 6_000),
"Original Persona turn pressure:".to_string(),
extract_prompt_seed(persona_prompt),
"Initial semantic memory recall seed:".to_string(),
collapse_whitespace(initial_recall_seed, 2_000),
]
.into_iter()
.map(|part| part.trim().to_string())
.filter(|part| !part.is_empty())
.collect::<Vec<_>>()
.join("\n")
}
fn render_persona_memory_cache_hits(hits: &[PersonaMemorySearchHit]) -> String {
let mut lines = vec![
"These are derived Qdrant Persona-memory cache hits from typed Persona state. They are hints, not durable authority; typed memory remains the owner.".to_string(),
@ -380,6 +427,40 @@ fn render_persona_memory_cache_hits(hits: &[PersonaMemorySearchHit]) -> String {
lines.join("\n")
}
fn extract_prompt_seed(prompt: &str) -> String {
let markers = [
"Semantic memory recall:",
"Projected inner state from Imagination:",
"Recent home-repo activity",
"Pending addressed pressure:",
"Raw room transcript",
];
let mut sections = Vec::new();
for marker in markers {
let Some(start) = prompt.find(marker) else {
continue;
};
let end = markers
.iter()
.filter(|candidate| **candidate != marker)
.filter_map(|candidate| {
prompt[start + marker.len()..]
.find(candidate)
.map(|offset| start + marker.len() + offset)
})
.min()
.unwrap_or_else(|| prompt.len().min(start + 2_400));
sections.push(prompt[start..end.min(start + 2_400)].trim().to_string());
}
if sections.is_empty() {
collapse_whitespace(prompt, 2_400)
} else {
sections.join("\n\n")
}
}
fn fallback_persona_memory_recall(
identity_id: String,
role_id: String,

View file

@ -101,6 +101,8 @@ pub struct PersonaInterpreterInput {
#[serde(default)]
pub semantic_memory_recall: String,
#[serde(default)]
pub dynamic_semantic_memory_recall: String,
#[serde(default)]
pub pending_mentions: Vec<HeartbeatPendingMention>,
#[serde(default)]
pub allowed_channel_ids: Vec<String>,
@ -246,9 +248,12 @@ Allowed channel ids:
Pending addressed pressure:
{mentions}
Dynamic semantic memory recall:
Initial semantic memory recall from the Persona turn:
{semantic_recall}
Dynamic self-memory recall from the Persona output:
{dynamic_semantic_recall}
Original Persona prompt:
```
{persona_prompt}
@ -266,6 +271,14 @@ Return concise structured effect blocks. No prose outside the blocks.
channels = render_allowed_channels(&input.allowed_channel_ids),
mentions = render_pending_mentions(&input.pending_mentions),
semantic_recall = render_semantic_memory_recall_text(&input.semantic_memory_recall),
dynamic_semantic_recall = render_semantic_memory_recall_text(
input
.dynamic_semantic_memory_recall
.trim()
.is_empty()
.then_some(input.semantic_memory_recall.as_str())
.unwrap_or(input.dynamic_semantic_memory_recall.as_str())
),
persona_prompt = input.persona_prompt.trim(),
persona_output = input.persona_output.trim(),
)
@ -583,6 +596,7 @@ mod tests {
persona_prompt: persona,
persona_output: "I want to answer, but only if I can name the cut plainly.".to_string(),
semantic_memory_recall: String::new(),
dynamic_semantic_memory_recall: String::new(),
pending_mentions: vec![pending],
allowed_channel_ids: vec!["aquarium".to_string()],
});
@ -642,10 +656,13 @@ mod tests {
persona_prompt: persona,
persona_output: "I can answer, but the answer should stay review-gated.".to_string(),
semantic_memory_recall: recall,
dynamic_semantic_memory_recall: "Output-triggered recall says review gates matter."
.to_string(),
pending_mentions: Vec::new(),
allowed_channel_ids: vec!["aquarium".to_string()],
});
assert!(interpreter.contains("Dynamic semantic memory recall"));
assert!(interpreter.contains("Dynamic self-memory recall"));
assert!(interpreter.contains("Output-triggered recall"));
assert!(interpreter.contains("STATE NOTE"));
assert!(interpreter.contains("SAY"));
}

View file

@ -194,7 +194,7 @@ remembers doctrine; the branch remembers the blade.
- `bifrost-publication` is the compact non-mutating Bifrost publication proof; use it to prove body-change routing, credit, and GitHub publication receipt state without performing GitHub mutation. Its JSON now points back to compact swarm overview and tool-directory readback.
- `bifrost-artifact-acceptance` and `bifrost-metrics` are compact Bifrost/Maintainer closure rites for the safe-family planning/accounting lanes. They write typed accepted-artifact and metrics receipts from explicit artifact/public-proof/review/model-spend/review-load/credit refs, verify latest receipt readback, and let `bifrost-ledger` close `artifact-acceptance-request` and `metrics-request` rows from Bifrost-owned receipts instead of request cargo. The request-family smokes now prove both states: `.epiphany-smoke\repo-artifact-acceptance-request-family-20260621-130520` and `.epiphany-smoke\repo-metrics-request-family-20260621-130635` first assert open request rows, then write final receipts and assert closed rows; wrapper proof `local-20260621-140740-750-2815f6f7` read the closed metrics row with `privateStateExposed=False`. `epiphany-repo-bifrost-accounting-bundle-smoke` consumes the closed family summaries and emits one operator-safe aggregate receipt; wrapper mode `tools/epiphany_local_run.ps1 -Mode bifrost-accounting-bundle` now builds/runs that verifier, accepts optional exact summary paths, writes normal operator run receipts, and proved bundle `.epiphany-smoke\repo-bifrost-accounting-bundle-20260621-132815` via wrapper artifact `local-20260621-142716-268-d6bc58b4` with final Bifrost receipt ids, `planningAuthorityOnly=true`, and `privateStateExposed=false`.
- `daemon-survival-rehearsal` is the bounded Idunn physiology proof. `epiphany-daemon-survival-rehearsal-smoke` writes a durable restart policy for `epiphany-daemon-self`, runs `epiphany-daemon-supervisor serve --max-iterations 2 --force --loop-interval-seconds 0`, verifies two scheduler pulses and the scheduler receipt row, and keeps service-manager mutation/elevation/private-state exposure false. Wrapper proof `tools/epiphany_local_run.ps1 -Mode daemon-survival-rehearsal -SkipBuild` emitted `.epiphany-smoke\daemon-survival-rehearsal-20260621-135723` via artifact `local-20260621-145723-011-1175e273`.
- `repo-swarm-mvp-gate` is the compact aggregate MVP proof rite. `epiphany-repo-swarm-mvp-gate-smoke` consumes the latest fresh-repo MVP, repo-work readiness, Bifrost accounting-bundle, Idunn deployment handoff, daemon survival, Weksa interlingua, and Persona semantic memory recall proofs, verifies the green proof rows, and leaves only Idunn elevated service/install/start/remote execution as an explicit warning gate instead of laundering it into completion. Direct proof `.epiphany-smoke\repo-swarm-mvp-gate-20260621-170445` reports `demoReady=true`, `mvpReady=false`, `green=15`, `warnings=1`, `blockers=0`, `personaMemoryRecall.cacheStatus=qdrant-unavailable`, `personaMemoryRecall.status=fallback`, `personaMemoryRecall.heartbeatRecallWired=true`, Projector/Persona/Interpreter prompt coverage, and `privateStateExposed=false`. This is live-fire ready for a controlled repo Body; unattended/elevated production service or deployment execution remains an operator-granted Idunn rite.
- `repo-swarm-mvp-gate` is the compact aggregate MVP proof rite. `epiphany-repo-swarm-mvp-gate-smoke` consumes the latest fresh-repo MVP, repo-work readiness, Bifrost accounting-bundle, Idunn deployment handoff, daemon survival, Weksa interlingua, and Persona semantic memory recall proofs, verifies the green proof rows, and leaves only Idunn elevated service/install/start/remote execution as an explicit warning gate instead of laundering it into completion. Direct proof `.epiphany-smoke\repo-swarm-mvp-gate-20260621-174742` reports `demoReady=true`, `mvpReady=false`, `green=15`, `warnings=1`, `blockers=0`, `personaMemoryRecall.cacheStatus=qdrant-unavailable`, `personaMemoryRecall.status=fallback`, `personaMemoryRecall.dynamicCacheStatus=qdrant-unavailable`, `personaMemoryRecall.dynamicRecallWired=true`, `personaMemoryRecall.heartbeatRecallWired=true`, Projector/Persona/output-triggered Interpreter prompt coverage, and `privateStateExposed=false`. This is live-fire ready for a controlled repo Body; unattended/elevated production service or deployment execution remains an operator-granted Idunn rite.
- Huginn is the first controlled live-fire repo Body past closure. On branch `epiphany/huginn/first-awakening`, item `huginn-inspection-readback-livefire` went through Persona intake, Imagination `repo.status_section` planning, Self run, Mind adoption, Hands README-only commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2`, Soul closure, and Mind map admission. Refreshed overview reports `gate=awaiting-publication`, `blocker=bifrost-publication-missing`, `soulVerdict=passed`, `changedPaths=[README.md]`, and `privateStateExposed=false`; concrete TypeScript build verification passed via `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json`. Do not push, publish, merge, or sync Huginn without Bifrost/maintainer authority.
- `epiphany-repo-livefire-closure-smoke` is the repeatable proof for a controlled repo Body that has reached closure. It checks the close receipt, Mind map entry, overview gate/blocker, authority seals, and actual `git show --name-only` changed paths. Huginn proof with `--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` returned `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, changed paths `[README.md]`, and `privateStateExposed=false`.
- `bifrost-ledger` is the compact Bifrost/Imagination readback rite; use it after collaboration, publication, accepted-artifact, or metrics writes to see the latest body-change, public-proof, accepted-artifact, metrics, GitHub, feedback, consensus, credit/review-count, and accounting closure rows without opening the full local Verse.
@ -1534,11 +1534,15 @@ target-language lowering request, lowering receipt, and local Verse
`epiphany.cultmesh.weksa_lowering_receipt.v0` mirror before any mouth transport
publication. The receipt-directory row is Weksa-owned sight only; Bifrost or the
configured mouth edge still owns publication, and Mind still owns durable state.
Repo Persona intake also carries semantic Persona memory recall now. It loads
repo-local `agents.msgpack`, tries the bounded Qdrant/Ollama Persona-memory
cache, falls back to a typed RoleSelf memory-graph context cut, and records
sealed recall status in the intake receipt before routing public pressure to
Imagination. Qdrant is a rebuildable semantic cache; typed memory remains the
Repo Persona intake also carries VoidBot-style two-pass semantic Persona memory
recall now. It loads repo-local `agents.msgpack`, tries the bounded
Qdrant/Ollama Persona-memory cache, falls back to a typed RoleSelf memory-graph
context cut, and records sealed pre-turn recall status before routing public
pressure to Imagination. It also records `interpreterDynamicRecall`, keyed from
the candidate SAY meaning, so Mind/Interpreter has output-triggered self-memory
context instead of only the pre-turn prompt recall. Current repo-intake remains
a scaffolded operator-message path rather than a full autonomous model Persona
turn; Qdrant is a rebuildable semantic cache, and typed memory remains the
owner.
`notes/organ-dependency-contracts.md` is the standing dependency map: every
sub-agent depends on all the other standing sub-agents. Dependency does not collapse

Binary file not shown.

File diff suppressed because one or more lines are too long