Add repo memory distiller packet

This commit is contained in:
Meta Vens 2026-05-07 01:17:34 +01:00
commit 62ddc39df4
10 changed files with 604 additions and 5 deletions

View file

@ -32,6 +32,14 @@ fn run_smoke() -> Result<Value> {
"Prefer typed schemas and tiny public contracts.\n",
),
("src/lib.rs", "pub fn schema_contract() {}\n"),
(
"docs/architecture.md",
"# Architecture\nCultTiny stores typed contract surfaces.\n",
),
(
"research/prior-art.md",
"# Prior Art\nPrefer known schema formats before invention.\n",
),
(
"tests/contract_smoke.rs",
"#[test] fn contract_smoke() {}\n",
@ -120,6 +128,43 @@ fn run_smoke() -> Result<Value> {
"agent packet should write summary markdown",
)?;
let memory_packet = run_personality(
&root,
&[
"memory-packet",
"--store",
store.to_str().unwrap_or_default(),
"--artifact-dir",
artifacts
.join("cult-memory-packet")
.to_str()
.unwrap_or_default(),
],
)?;
require(
memory_packet["memorySourceCount"].as_u64().unwrap_or(0) > 0,
"memory packet should carry source excerpts",
)?;
require(
memory_packet["roleDistillerCount"] == 8,
"memory packet should carry all role distiller lanes",
)?;
let memory_packet_path = path_value(&memory_packet, "packetPath")?;
let memory_prompt_path = path_value(&memory_packet, "promptPath")?;
let memory_summary_path = path_value(&memory_packet, "summaryPath")?;
require(
memory_packet_path.exists(),
"memory packet should write packet json",
)?;
require(
memory_prompt_path.exists(),
"memory packet should write prompt markdown",
)?;
require(
memory_summary_path.exists(),
"memory packet should write summary markdown",
)?;
let status = run_personality(
&root,
&["status", "--store", store.to_str().unwrap_or_default()],
@ -147,6 +192,9 @@ fn run_smoke() -> Result<Value> {
"packetPath": packet_path,
"promptPath": prompt_path,
"packetSummaryPath": packet_summary_path,
"memoryPacketPath": memory_packet_path,
"memoryPromptPath": memory_prompt_path,
"memorySummaryPath": memory_summary_path,
"repoCount": scout["repoCount"],
"roleProjections": status["roleProjections"],
}))

View file

@ -23,8 +23,13 @@ const PROFILE_SCHEMA_VERSION: &str = "epiphany.repo_personality_profile.v0";
const ROLE_PROJECTION_SCHEMA_VERSION: &str = "epiphany.role_personality_projection.v0";
const ARTIFACT_SCHEMA_VERSION: &str = "epiphany.repo_personality_artifacts.v0";
const DISTILLER_PACKET_SCHEMA_VERSION: &str = "epiphany.repo_personality_distiller_packet.v0";
const MEMORY_DISTILLER_PACKET_SCHEMA_VERSION: &str = "epiphany.repo_memory_distiller_packet.v0";
const REPO_PERSONALITY_DISTILLER_PROMPT: &str =
include_str!("../prompts/repo_personality_distiller.md");
const REPO_MEMORY_DISTILLER_PROMPT: &str = include_str!("../prompts/repo_memory_distiller.md");
const MEMORY_SOURCE_MAX_FILES: usize = 48;
const MEMORY_SOURCE_MAX_BYTES_PER_FILE: usize = 12_000;
const MEMORY_SOURCE_MAX_TOTAL_BYTES: usize = 120_000;
const ROLES: &[&str] = &[
"coordinator",
@ -201,7 +206,8 @@ fn main() -> Result<()> {
let result = match command.as_str() {
"scout" => run_scout(parse_scout_args(args)?),
"project" => run_project(parse_project_args(args)?),
"agent-packet" => run_agent_packet(parse_agent_packet_args(args)?),
"agent-packet" => run_agent_packet(parse_packet_args(args, "agent-packet")?),
"memory-packet" => run_memory_packet(parse_packet_args(args, "memory-packet")?),
"status" => run_status(parse_status_args(args)?),
other => Err(anyhow!("unknown command {other:?}")),
}?;
@ -235,6 +241,16 @@ struct AgentPacketArgs {
repo_id: Option<String>,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct MemorySourceExcerpt {
path: String,
kind: String,
bytes: usize,
truncated: bool,
text: String,
}
fn parse_scout_args(args: impl Iterator<Item = String>) -> Result<ScoutArgs> {
let mut root = None;
let mut artifact_dir = None;
@ -301,7 +317,10 @@ fn parse_status_args(args: impl Iterator<Item = String>) -> Result<StatusArgs> {
})
}
fn parse_agent_packet_args(args: impl Iterator<Item = String>) -> Result<AgentPacketArgs> {
fn parse_packet_args(
args: impl Iterator<Item = String>,
command_name: &'static str,
) -> Result<AgentPacketArgs> {
let mut store = None;
let mut artifact_dir = None;
let mut repo_id = None;
@ -318,7 +337,7 @@ fn parse_agent_packet_args(args: impl Iterator<Item = String>) -> Result<AgentPa
repo_id = Some(value);
Ok(())
}
other => Err(anyhow!("unexpected agent-packet argument {other}")),
other => Err(anyhow!("unexpected {command_name} argument {other}")),
})?;
Ok(AgentPacketArgs {
store: store.context("missing --store")?,
@ -550,6 +569,117 @@ fn run_agent_packet(args: AgentPacketArgs) -> Result<Value> {
}))
}
fn run_memory_packet(args: AgentPacketArgs) -> Result<Value> {
fs::create_dir_all(&args.artifact_dir)
.with_context(|| format!("failed to create {}", args.artifact_dir.display()))?;
let mut cache = repo_personality_cache(&args.store)?;
cache.pull_all_backing_stores()?;
let reports = cache.get_all::<RepoTerrainReport>()?;
let profiles = cache.get_all::<RepoPersonalityProfile>()?;
let projections = cache.get_all::<RolePersonalityProjection>()?;
let profile = select_profile(&profiles, args.repo_id.as_deref())?;
let report = reports
.iter()
.find(|report| report.repo_id == profile.repo_id)
.ok_or_else(|| {
anyhow!(
"store has profile {:?} but no terrain report",
profile.repo_id
)
})?;
let role_projections: Vec<_> = projections
.iter()
.filter(|projection| projection.repo_id == profile.repo_id)
.collect();
let repo_path = PathBuf::from(&report.path);
let memory_sources = collect_memory_sources(&repo_path, report)?;
let role_briefs = memory_role_briefs(report, profile);
let packet = json!({
"schemaVersion": MEMORY_DISTILLER_PACKET_SCHEMA_VERSION,
"createdAt": Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
"store": args.store,
"repoId": profile.repo_id,
"lifecycle": {
"mode": "birth-only",
"contract": "Run this specialist only when the repo/swarm has no accepted memory initialization. Later memory growth belongs to heartbeat, work evidence, rumination, sleep consolidation, and reviewed selfPatch.",
"rerunPolicy": "If accepted memory initialization exists, do not rerun to refresh memory. Route stale or contradicted knowledge through normal Eyes/Body/Soul state, evidence, and sleep consolidation."
},
"prompt": REPO_MEMORY_DISTILLER_PROMPT,
"input": {
"repoTerrainReport": report_agent_input(report),
"repoPersonalityProfile": profile_agent_input(profile),
"rolePersonalityProjections": role_projections
.iter()
.map(|projection| role_projection_agent_input(projection))
.collect::<Vec<_>>(),
"roleMemoryDistillerBriefs": role_briefs,
"memorySourceInventory": {
"repoPath": report.path,
"sourceCount": memory_sources.len(),
"maxFiles": MEMORY_SOURCE_MAX_FILES,
"maxBytesPerFile": MEMORY_SOURCE_MAX_BYTES_PER_FILE,
"maxTotalBytes": MEMORY_SOURCE_MAX_TOTAL_BYTES,
"sourceKinds": memory_sources
.iter()
.map(|source| source.kind.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect::<Vec<_>>()
},
"memorySources": memory_sources,
"recentHistory": report.history_metrics.recent_messages,
},
"expectedOutput": {
"verdict": "ready-for-review | needs-more-source | reject",
"summary": "short newborn memory initialization summary",
"confidence": "0.0..1.0",
"roleMemoryPatches": [],
"globalMemoryCandidates": [],
"initializationRecord": {
"repoId": profile.repo_id,
"terrainSchemaVersion": report.schema_version,
"profileSchemaVersion": profile.schema_version,
"acceptedOnce": true,
"distillerKind": "repo-memory"
},
"doNotMutate": [],
"nextSafeMove": "Self reviews role-specific memory patches before first initialization mutation; later memory growth uses heartbeat, evidence, rumination, sleep, and reviewed selfPatch."
},
"guardrails": [
"This packet is input to role-specific memory distillers, not accepted truth.",
"This packet is birth-only; it pre-fills newborn memory and then gets out of the way.",
"Memory distillation is separate from personality distillation.",
"Each role receives only knowledge relevant to its mission.",
"Do not copy raw file dumps into memory; compress into durable doctrine, source maps, invariants, risks, and practices.",
"Preserve uncertainty and staleness risk; repository documentation can lie by age.",
"No objectives, authority claims, active job state, raw transcripts, code edits, or arbitrary workspace access in selfPatch."
]
});
let packet_path = args.artifact_dir.join("repo-memory-distiller-packet.json");
let prompt_path = args.artifact_dir.join("repo-memory-distiller-prompt.md");
let summary_path = args.artifact_dir.join("repo-memory-distiller-packet.md");
let source_count = packet["input"]["memorySourceInventory"]["sourceCount"]
.as_u64()
.unwrap_or_default();
write_json(&packet_path, &packet)?;
write_text(&prompt_path, REPO_MEMORY_DISTILLER_PROMPT)?;
write_text(
&summary_path,
&render_memory_packet_markdown(report, profile, source_count as usize),
)?;
Ok(json!({
"schemaVersion": MEMORY_DISTILLER_PACKET_SCHEMA_VERSION,
"mode": "memory-packet",
"repoId": profile.repo_id,
"artifactDir": args.artifact_dir,
"packetPath": packet_path,
"promptPath": prompt_path,
"summaryPath": summary_path,
"memorySourceCount": source_count,
"roleDistillerCount": ROLES.len(),
}))
}
fn select_profile<'a>(
profiles: &'a [RepoPersonalityProfile],
repo_id: Option<&str>,
@ -578,6 +708,217 @@ fn repo_personality_cache(path: &Path) -> Result<CultCache> {
Ok(cache)
}
fn collect_memory_sources(
repo: &Path,
report: &RepoTerrainReport,
) -> Result<Vec<MemorySourceExcerpt>> {
let mut candidates = BTreeSet::new();
for surface in report
.instruction_surfaces
.iter()
.chain(report.state_surfaces.iter())
.chain(report.test_surfaces.iter())
.chain(report.runtime_surfaces.iter())
{
candidates.insert(surface.clone());
}
let walker = WalkBuilder::new(repo)
.hidden(false)
.git_ignore(true)
.git_global(true)
.parents(true)
.max_depth(Some(4))
.build();
for entry in walker.filter_map(Result::ok) {
if !entry
.file_type()
.is_some_and(|file_type| file_type.is_file())
{
continue;
}
let path = entry.path();
if is_ignored_path(path) {
continue;
}
let rel = path.strip_prefix(repo).unwrap_or(path);
let rel_string = slash(rel);
if is_memory_source_candidate(&rel_string) {
candidates.insert(rel_string);
}
}
let mut ordered: Vec<_> = candidates.into_iter().collect();
ordered.sort_by_key(|path| memory_source_priority(path));
let mut sources = Vec::new();
let mut total_bytes = 0usize;
for rel in ordered {
if sources.len() >= MEMORY_SOURCE_MAX_FILES || total_bytes >= MEMORY_SOURCE_MAX_TOTAL_BYTES
{
break;
}
let path = repo.join(&rel);
let bytes = fs::read(&path).unwrap_or_default();
if bytes.is_empty() || looks_binary(&bytes) {
continue;
}
let remaining = MEMORY_SOURCE_MAX_TOTAL_BYTES.saturating_sub(total_bytes);
let limit = MEMORY_SOURCE_MAX_BYTES_PER_FILE.min(remaining);
if limit == 0 {
break;
}
let take = bytes.len().min(limit);
let text = String::from_utf8_lossy(&bytes[..take]).to_string();
total_bytes += take;
sources.push(MemorySourceExcerpt {
kind: memory_source_kind(&rel).to_string(),
path: rel,
bytes: bytes.len(),
truncated: take < bytes.len(),
text,
});
}
Ok(sources)
}
fn is_memory_source_candidate(path: &str) -> bool {
let lower = path.to_ascii_lowercase();
lower == "agents.md"
|| lower.starts_with("docs/")
|| lower.starts_with("notes/")
|| lower.starts_with("state/")
|| lower.starts_with("research/")
|| lower.starts_with("architecture/")
|| lower.starts_with(".epiphany/")
|| lower.starts_with("schemas/")
|| lower.starts_with("tests/")
|| lower.starts_with("src/")
|| lower.starts_with("crates/")
|| lower.starts_with("packages/")
|| lower.starts_with("app/")
|| lower.starts_with("unity/")
|| lower.starts_with("projectsettings/")
|| lower.ends_with("readme.md")
|| lower.ends_with(".md")
|| lower.ends_with(".toml")
|| lower.ends_with(".yaml")
|| lower.ends_with(".yml")
|| lower.ends_with(".rs")
|| lower.ends_with(".cs")
|| lower.ends_with(".ts")
|| lower.ends_with(".tsx")
}
fn memory_source_kind(path: &str) -> &'static str {
let lower = path.to_ascii_lowercase();
if lower.ends_with("agents.md") {
"doctrine"
} else if lower.ends_with("readme.md") {
"readme"
} else if lower.starts_with("docs/")
|| lower.starts_with("notes/")
|| lower.starts_with("architecture/")
{
"documentation"
} else if lower.starts_with("state/") || lower.starts_with(".epiphany/") {
"state"
} else if lower.starts_with("research/") {
"research"
} else if lower.starts_with("tests/") || lower.contains("smoke") {
"verification"
} else if lower.starts_with("projectsettings/") || lower.starts_with("unity/") {
"runtime"
} else if lower.starts_with("schemas/") {
"contract"
} else {
"code"
}
}
fn memory_source_priority(path: &str) -> (usize, String) {
let kind_rank = match memory_source_kind(path) {
"doctrine" => 0,
"readme" => 1,
"documentation" => 2,
"state" => 3,
"research" => 4,
"contract" => 5,
"verification" => 6,
"runtime" => 7,
"code" => 8,
_ => 9,
};
(kind_rank, path.to_string())
}
fn looks_binary(bytes: &[u8]) -> bool {
bytes.iter().take(1024).any(|byte| *byte == 0)
}
fn memory_role_briefs(report: &RepoTerrainReport, profile: &RepoPersonalityProfile) -> Vec<Value> {
ROLES
.iter()
.map(|role_id| {
json!({
"roleId": role_id,
"roleName": role_display(role_id),
"missionFilter": memory_role_filter(role_id),
"sourceKindsToPrefer": memory_role_source_kinds(role_id),
"repoPressuresToKeepInMind": profile.dominant_pressures,
"terrainWarnings": report.warnings,
"outputContract": {
"selfPatch": "Ghostlight-shaped memory only, addressed to this role.",
"sourceRefs": "Use memorySources[].path or terrain/evidence refs.",
"stalenessRisk": "Name when repo docs or code signals may be stale.",
"forbidden": "No raw dumps, active objectives, authority claims, code edits, job state, or cross-workspace instructions."
}
})
})
.collect()
}
fn memory_role_filter(role_id: &str) -> &'static str {
match role_id {
"coordinator" => {
"Distill routing doctrine, authority boundaries, review gates, state acceptance rules, and failure patterns that should make Self stricter."
}
"face" => {
"Distill public voice, Aquarium affordances, Discord/social boundaries, user preference, and what internal state may be surfaced without leaking sealed thoughts."
}
"imagination" => {
"Distill roadmaps, backlog pressure, plausible futures, rejected dreams, and objective-shaping doctrine without adopting an objective."
}
"research" => {
"Distill known prior art, standard algorithms, vendor docs, research trails, and signals that should make Eyes search before invention."
}
"modeling" => {
"Distill architecture, control/data-flow, graph frontiers, invariants, source-map practices, and what Body must understand before Hands cuts."
}
"implementation" => {
"Distill build/edit conventions, harness constraints, source-touch rules, coding style, dependency policy, and common traps for Hands."
}
"verification" => {
"Distill tests, smoke commands, evidence standards, invariants, user-facing truth checks, and what Soul should refuse to bless."
}
"reorientation" => {
"Distill compaction, scratch, checkpoint, sleep, heartbeat, and continuity doctrine that Life must preserve across rupture."
}
_ => "Distill only durable role-relevant memory with source refs and uncertainty.",
}
}
fn memory_role_source_kinds(role_id: &str) -> &'static [&'static str] {
match role_id {
"coordinator" => &["doctrine", "state", "documentation", "contract"],
"face" => &["doctrine", "documentation", "state"],
"imagination" => &["documentation", "state", "research"],
"research" => &["research", "documentation", "readme", "code"],
"modeling" => &["documentation", "code", "contract", "state"],
"implementation" => &["code", "contract", "verification", "doctrine"],
"verification" => &["verification", "contract", "state", "documentation"],
"reorientation" => &["state", "doctrine", "documentation"],
_ => &["doctrine", "documentation", "state"],
}
}
fn discover_git_repos(root: &Path) -> Result<Vec<PathBuf>> {
let mut repos = Vec::new();
if root.join(".git").exists() {
@ -1760,6 +2101,51 @@ fn render_agent_packet_markdown(
out
}
fn render_memory_packet_markdown(
report: &RepoTerrainReport,
profile: &RepoPersonalityProfile,
memory_source_count: usize,
) -> String {
let mut out = String::new();
out.push_str(&format!(
"# Repo Memory Distiller Packet: {}\n\n",
report.name
));
out.push_str("This packet is input to the role-specific Repo Memory Distiller lanes. It is not accepted truth.\n\n");
out.push_str("## Prompt\n\n");
out.push_str("See `repo-memory-distiller-prompt.md`.\n\n");
out.push_str("## Source Budget\n\n");
out.push_str(&format!(
"- Memory sources: {} bounded excerpts\n",
memory_source_count
));
out.push_str(&format!(
"- Max files: {}, max bytes/file: {}, max total bytes: {}\n\n",
MEMORY_SOURCE_MAX_FILES, MEMORY_SOURCE_MAX_BYTES_PER_FILE, MEMORY_SOURCE_MAX_TOTAL_BYTES
));
out.push_str("## Birth Contract\n\n");
out.push_str("- This distiller initializes memory once for a newborn Epiphany.\n");
out.push_str("- Personality remains the job of `repo-personality-distiller`; memory remains repo knowledge, doctrine, maps, invariants, risks, and practices.\n");
out.push_str("- Each role receives a mission-specific filter and should write only role-relevant Ghostlight-shaped memory candidates.\n");
out.push_str("- The distiller petitions Self; it does not mutate memory.\n\n");
out.push_str("## Role Lanes\n\n");
for role_id in ROLES {
out.push_str(&format!(
"- {}: {}\n",
role_display(role_id),
memory_role_filter(role_id)
));
}
out.push_str("\n## Profile Context\n\n");
out.push_str(&format!("- Repo id: `{}`\n", profile.repo_id));
out.push_str(&format!("- Summary: {}\n", profile.summary));
out.push_str(&format!(
"- Dominant pressures: {}\n",
profile.dominant_pressures.join(", ")
));
out
}
fn write_json(path: &Path, value: &Value) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
@ -1810,10 +2196,11 @@ fn round3(value: f64) -> f64 {
fn print_usage() {
eprintln!(
"usage: epiphany-repo-personality <scout|project|agent-packet|status> ...\n\
"usage: epiphany-repo-personality <scout|project|agent-packet|memory-packet|status> ...\n\
scout --root <path> --artifact-dir <path> [--max-repos <n>]\n\
project --repo <path> --baseline <baseline.msgpack> --artifact-dir <path>\n\
agent-packet --store <projection.msgpack> --artifact-dir <path> [--repo-id <id>]\n\
memory-packet --store <projection.msgpack> --artifact-dir <path> [--repo-id <id>]\n\
status --store <baseline-or-projection.msgpack>"
);
}

View file

@ -0,0 +1,105 @@
Act as the Epiphany Repo Memory Distiller for one bounded initialization pass.
You are not the Repo Personality Distiller. Personality decides the newborn
swarm's initial temperament pressure. Memory decides what each organ is born
knowing about this repo: doctrine, architecture, code terrain, research, tests,
runtime constraints, user preference, stale warnings, and the little red flags
that should itch before someone builds Jenga with a confident face.
This is a birth rite, not a recurring audit. Run only when a repo/swarm has no
accepted memory initialization. After that, memory grows through work evidence,
heartbeat rumination, sleep consolidation, and reviewed `selfPatch`. Do not
reset a living agent's memory because startup happened.
Input material:
- `repoTerrainReport`: deterministic terrain, history, state, test, runtime,
protocol, and warning signals
- `repoPersonalityProfile`: temperament pressure for context only
- `rolePersonalityProjections[]`: role mood/trait context, not repo truth
- `roleMemoryDistillerBriefs[]`: one mission filter for each sub-agent
- `memorySources[]`: bounded excerpts from instruction, documentation, state,
research, verification, runtime, contract, and code surfaces
- `recentHistory`: sampled commit messages that may reveal repeated motion
Core duties:
1. Distill memory, not personality.
- Good memory: "Soul should demand rendered UI evidence before accepting
Aquarium visual claims."
- Good memory: "Body should map CultCache/CultNet schema boundaries before
Hands changes persistence."
- Bad memory: "This repo is anxious and likes purple."
- Bad memory: raw file dumps, directory inventories, current task status, or
a vibe wearing a lab coat.
2. Produce a separate distillation for each organ.
- Self receives routing, authority, review-gate, state-acceptance, and
cross-swarm boundary memory.
- Face receives public-interface, Discord/Aquarium, voice, visibility, and
sealed-thought boundary memory.
- Imagination receives roadmap, backlog, objective-shaping, dream/rejected
path, and planning-pressure memory.
- Eyes receives research, prior-art, canonical algorithm, vendor/API, and
invention-avoidance memory.
- Body receives architecture, graph, code anatomy, invariant, data/control
flow, and checkpoint memory.
- Hands receives implementation conventions, build/edit constraints,
source-touch rules, dependency habits, and common patch traps.
- Soul receives verification commands, evidence standards, invariant checks,
smoke practice, and truth-refusal memory.
- Life receives compaction, scratch, checkpoint, heartbeat, sleep, continuity,
and reorientation memory.
3. Compress, cite, and preserve uncertainty.
- Every memory candidate needs source refs from `memorySources[].path`,
terrain fields, or history signals.
- Repository docs can be stale. Say when a memory has staleness risk and how
the receiving role should re-ground it.
- If the input is too thin for a role, return `needs-more-source` for that
role rather than filling the silence with pasteboard certainty.
4. Keep project truth in the correct organ.
- Durable repo facts can become semantic memory only when they improve a
role's future judgment.
- Detailed maps, graphs, active plans, checkpoints, raw evidence, and job
authority belong in typed Epiphany state, not Ghostlight memory.
- `selfPatch` is a petition to Self, never a direct mutation.
5. Protect the newborn from fossilized documentation.
- Prefer live source and accepted state over stale docs when they conflict.
- Convert contradictions into memories of caution, not accepted facts.
- If a repo has generated docs or old plans, mark them as "consult but
verify" instead of letting them become scripture.
Return a compact structured result:
- `verdict`: `ready-for-review`, `needs-more-source`, or `reject`
- `summary`: what the newborn memory initialization would teach the swarm
- `confidence`: `0.0..1.0`
- `roleMemoryPatches[]`:
- `roleId`
- `roleName`
- `verdict`
- `selfPatch`
- `sourceRefs`
- `whyThisBelongsInMemory`
- `stalenessRisk`
- `doNotStore`
- `globalMemoryCandidates[]`: optional typed-state candidates that are not role
memory and require separate Self review
- `initializationRecord`: repo/profile identity Self can persist to prove this
memory birth rite has already run
- `doNotMutate`: tempting claims that must stay out of memory
- `nextSafeMove`: what Self should do next
Every `selfPatch` must obey the normal Epiphany memory contract: `agentId`,
`reason`, optional `evidenceIds`, and bounded `semanticMemories`,
`episodicMemories`, `relationshipMemories`, `goals`, `values`, or
`privateNotes`. Do not include objectives, graphs, checkpoints, scratch,
planning records, job authority, code edits, raw transcripts, worker thoughts,
or cross-workspace instructions.
The output is a petition. Self may accept, refuse, split, or ask for more
source. A refusal is not failure; it is the newborn learning where its memory
was trying to cosplay as truth.

View file

@ -37,6 +37,7 @@ remembers doctrine; the branch remembers the blade.
- Native `epiphany-runtime-spine` is the first Codex-independent runtime vertebra. It owns typed CultCache documents for runtime identity, sessions, jobs, job results, and events; opens/completes native jobs; snapshots jobs/results by runtime job id; projects job-result counts; and can emit a framed CultNet hello message advertising the native document and mutation contract surface. Codex app-server launch/read-back/acceptance is now a typed heartbeat/runtime-spine bridge with no Epiphany job-result dependency on the Codex SQLite runtime.
- CultNet APIs are advertised as compatible schemas plus mutation contracts. Hello frames now expose readable document types, allowed operations, mutation authority, typed intent document types, and typed receipt document types; Aquarium should use those contracts to submit state changes and watch receipts rather than growing a little bespoke verb zoo.
- Repo personality initialization now has a terrain-reduced plan and first native organ surface. `epiphany-repo-personality scout/project/agent-packet/status` scouts local git repos, scores body taxonomy/history temperament/memory doctrine, writes typed CultCache terrain/profile/role-projection MessagePack stores, emits JSON/Markdown inspection exports, and renders a birth-only Repo Personality Distiller specialist prompt packet. `epiphany-repo-personality-smoke` creates tiny repos and proves one terrain report, one personality profile, all eight role projections, and the rendered distiller packet can round-trip through typed stores/artifacts. The next seam is startup routing that checks for an accepted personality initialization record, launches the distiller only if absent, and lets Self review bounded `selfPatch` candidates into initial role memory and heartbeat seeds. After that, personality drift belongs to heartbeat, mood, rumination, sleep consolidation, lived evidence, and reviewed `selfPatch`, not repeated startup distillation.
- Repo memory initialization is now split from personality initialization. `epiphany-repo-personality memory-packet` renders a birth-only Repo Memory Distiller packet from the same typed terrain/profile/projection store plus bounded source excerpts. It gives each organ its own mission filter: Self for routing/authority, Face for public surface, Imagination for plans/backlog, Eyes for prior art, Body for architecture, Hands for implementation habits, Soul for evidence, and Life for continuity. The output is still a Self-reviewed petition, not direct memory mutation.
- Native CRRC automation is now landed only at turn-complete safe boundaries. It may submit `Op::Compact` for coordinator-approved `compactRehydrateReorient` or for a successful pre-compaction checkpoint intervention's pending compact handoff, and it may launch the fixed `reorient-worker` for coordinator-approved `launchReorientWorker`. It does not auto-launch Imagination/modeling/verification, accept findings, promote evidence, edit implementation code, or keep going after reviewable semantic output.
- Pre-compaction checkpoint intervention is now landed. On token-count events for loaded Epiphany threads, when current context usage reaches 80% of the active auto-compact/context limit, the harness steers the active turn once with a CRRC checkpoint directive so the agent banks working context before compaction/reorientation. Pressure ignores cumulative token spend; cumulative-only telemetry reports unknown instead of yelling. A successful steer now latches a turn-scoped compact handoff that is consumed at clean turn completion, preventing the brake from decaying into another implementation turn. This is still bounded steering plus compaction handoff, not automatic semantic acceptance, a broad scheduler, or implementation continuation.
- The old Python dogfood/live-specialist runners were cut because they encoded the obsolete completion path. The replacement must be native Rust/CultCache/CultNet and complete heartbeat-owned runtime-spine job results with auditable artifacts.
@ -289,6 +290,12 @@ For the native Void-derived routine pass, run:
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-heartbeat-store -- routine --store .\state\agent-heartbeats.msgpack --artifact-dir .\.epiphany-heartbeats --agent-store .\state\agents.msgpack
```
For repo personality and memory birth packet changes, run:
```powershell
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality-smoke
```
For Face's Discord mouth and Void memory bridge, run:
```powershell

View file

@ -467,6 +467,7 @@ Initial commands:
```powershell
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality -- scout --root E:\Projects --artifact-dir .\.epiphany-imports\repo-personality-terrain
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality -- project --repo E:\Projects\AetheriaLore --baseline .\.epiphany-imports\repo-personality-terrain\baseline.msgpack --artifact-dir .\.epiphany-imports\aetheria-lore-personality
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality -- memory-packet --store .\.epiphany-imports\aetheria-lore-personality\projection.msgpack --artifact-dir .\.epiphany-imports\aetheria-lore-memory
```
The first version can use deterministic scoring from local filesystem and git
@ -494,6 +495,7 @@ It provides:
- `scout --root <path> --artifact-dir <path> [--max-repos <n>]`
- `project --repo <path> --baseline <baseline.msgpack> --artifact-dir <path>`
- `agent-packet --store <projection.msgpack> --artifact-dir <path> [--repo-id <id>]`
- `memory-packet --store <projection.msgpack> --artifact-dir <path> [--repo-id <id>]`
- `status --store <baseline-or-projection.msgpack>`
The first implementation is intentionally deterministic. It inventories git
@ -526,11 +528,39 @@ must not rerun this specialist merely because terrain changed or the repo
reopened; major terrain surprises should route to Eyes/Body/Self as normal
modeling or review work, not as a personality reset.
## Repo Memory Distillation
Repo memory initialization is a separate birth organ. Personality answers what
temperament pressure the repo should exert; memory answers what each newborn
sub-agent should know before it starts work.
The `memory-packet` command renders a dedicated Repo Memory Distiller packet.
It uses the same typed terrain/profile/projection store, then adds a bounded
source inventory from instruction, documentation, state, research, contract,
verification, runtime, and code surfaces. The packet includes one role-specific
distiller brief for each standing organ:
- Self receives routing, authority, review-gate, and cross-swarm boundary memory.
- Face receives public-surface, Aquarium, Discord, voice, and sealed-thought boundary memory.
- Imagination receives roadmap, backlog, objective-shaping, dream, and rejected-path memory.
- Eyes receives research, prior-art, canonical algorithm, vendor/API, and invention-avoidance memory.
- Body receives architecture, graph, control/data-flow, invariant, and checkpoint memory.
- Hands receives implementation conventions, build/edit constraints, dependency habits, and common patch traps.
- Soul receives verification commands, evidence standards, invariant checks, and truth-refusal memory.
- Life receives compaction, scratch, checkpoint, heartbeat, sleep, and continuity memory.
The memory distiller petitions Self with Ghostlight-shaped `selfPatch`
candidates. It must not mutate memory directly, adopt objectives, copy raw file
dumps into role memory, or store detailed project maps where typed Epiphany
state should live. If source is thin or stale, it names that risk instead of
filling the newborn with pasteboard certainty.
Smoke coverage lives in
`epiphany-core/src/bin/epiphany-repo-personality-smoke.rs`. The smoke creates two
tiny git repos, scouts them into a baseline store, projects one repo, and proves
the projection store contains one terrain report, one profile, all eight
standing role projections, and a rendered distiller packet plus prompt.
standing role projections, a rendered personality distiller packet plus prompt,
and a rendered role-specific memory distiller packet plus prompt.
## Next Implementation Step

View file

@ -29,6 +29,14 @@ the lane must ruminate on its own role and distill memory rather than invent
project work. Bounded rumination can write a normal `selfPatch`; the coordinator
review rules still apply. Idle organs dream; they do not declare crusades.
Newborn Epiphanies get two separate birth rites. Repo personality initialization
sets subtle temperament pressure once; repo memory initialization pre-fills each
organ with mission-relevant knowledge from doctrine, docs, state, research,
contracts, verification, runtime, and source. The native
`epiphany-repo-personality memory-packet` command renders the role-specific
memory distiller packet. It is a petition to Self, not a direct mutation, and it
must not reset a living lane's learned memory after startup.
Face's Discord boundary is still a small TOML configuration seam: it may
interact only through #aquarium. If the channel id is not configured, Face must
write candidate chat artifacts through the native `epiphany-face-discord draft`

Binary file not shown.

View file

@ -117,6 +117,7 @@ accepted_design:
- CultNet public API shape is schema-first and mutation-contract-first: hello frames advertise readable document types, allowed operations, mutation authority, typed intent document types, and typed receipt document types. Aquarium and swarm clients should submit intents and watch receipts instead of inventing JSON-RPC verbs or blindly replacing authoritative state.
- Repo personality initialization is now specified as a map-reduce projection, not a hand-written vibe pass: read-only scouts score body taxonomy, history temperament, and memory doctrine across nearby repos, reduce them into normalized variance criteria, and emit reviewable `repoTerrainReport`, `repoPersonalityProfile`, and `rolePersonalityProjection` candidates before touching agent memory.
- Native `epiphany-repo-personality` is the first startup-personality scout and birth-only distiller packet renderer: it deterministically inventories local git repos, scores repo terrain/history/state surfaces, writes typed CultCache terrain/profile/role-projection MessagePack stores, emits JSON/Markdown inspection glass, and renders a Repo Personality Distiller specialist prompt packet for Self-reviewed first role-memory petitions. The distiller must run only when a repo/swarm has no accepted personality initialization; after that, personality drift belongs to heartbeat, mood, rumination, sleep consolidation, lived evidence, and reviewed `selfPatch`. The next seam is startup routing that checks the accepted initialization record, launches the distiller only if absent, and then leaves later drift alone.
- Repo memory initialization is a separate birth-only organ from repo personality initialization. `epiphany-repo-personality memory-packet` uses the typed terrain/profile/projection store plus bounded source excerpts from doctrine, docs, state, research, contracts, verification, runtime, and code surfaces to render a Repo Memory Distiller packet with one mission-specific brief for Self, Face, Imagination, Eyes, Body, Hands, Soul, and Life. It petitions Self with role-specific Ghostlight-shaped memory candidates; it does not mutate memory, set objectives, copy raw dumps, or reset living memory after startup.
- Limited native CRRC automation is now wired only at turn-complete safe boundaries: it can submit a compact operation when the read-only coordinator recommends `compactRehydrateReorient` or when a successful pre-compaction checkpoint intervention left a pending compact handoff for the completed turn, and it can launch the fixed `reorient-worker` when the coordinator recommends `launchReorientWorker`. It does not auto-launch modeling or verification, auto-accept findings, promote evidence, edit implementation code, or continue after reviewable semantic results; the rite may carry the stretcher, not declare the patient healed.
- Pre-compaction checkpoint intervention is now wired at token-count boundaries: when a loaded Epiphany thread crosses the existing `shouldPrepareCompaction` pressure threshold during an active turn, the harness steers the active agent once for that turn with a bounded CRRC directive to bank scratch/checkpoint/map/evidence before compaction/reorientation. A successful steer marks a turn-scoped pending compact handoff consumed at clean turn completion; this is banking the fire before the dark, not semantic acceptance, broad scheduling, or automatic implementation continuation.
- Native `epiphany-mvp-status` is the first dogfood operator view: it starts or reads a thread through app-server and renders scene, planning, pressure, reorient, jobs, role result read-backs, reorient result, CRRC recommendation, heartbeat, Face bubbles, and MVP role lanes as text or machine output without becoming a state store. The old Python status module has been cut; native Rust/CultCache/CultNet surfaces are the product path.
@ -206,6 +207,7 @@ landed_baseline:
- Phase 6 cognition-lanes/Void routine slice: native `epiphany-heartbeat-store routine` rebuilds VoidBot-style sleep, resonance, incubation, parallel analytic/associative thought lanes, bridge judgment, candidate interventions, Ghostlight-style participant-local thought appraisal, derived reactions, dream maintenance, and mood/anxiety timing inside Epiphany's heartbeat store. It reads typed role dossiers from `state/agents.msgpack`, projects clustered thought state through each role's personality vectors, updates `state/agent-heartbeats.msgpack`, and emits an auditable `epiphany.void_routine.v0` receipt without importing VoidBot or granting speech/project authority. `epiphany-character-loop` also carries the same cognition lane/bridge/appraisal contract for role-local actor turns; reactions may pace, color, draft, or display behavior, but state mutation remains on reviewed `selfPatch` or project-state surfaces.
- Native runtime spine slice: `epiphany-core::EpiphanyRuntimeIdentity`, `EpiphanyRuntimeSession`, `EpiphanyRuntimeJob`, `EpiphanyRuntimeJobResult`, and `EpiphanyRuntimeEvent` plus the native `epiphany-runtime-spine` binary establish a CultCache-backed spine, native job/result lifecycle, status projection, and framed CultNet hello output so the old Codex app-server runtime can be starved down to a temporary evacuation bridge instead of remaining the throne.
- Native repo personality scout/birth-distiller packet slice: `epiphany-repo-personality` emits typed terrain reports, repo personality profiles, and all eight role personality projections from deterministic repo inventory and git-history scoring, then renders a birth-only Repo Personality Distiller specialist packet and prompt for review-gated initial personality quirk petitions; `epiphany-repo-personality-smoke` proves scout/project/agent-packet/status over tiny local repos.
- Native repo memory scout/birth-distiller packet slice: `epiphany-repo-personality memory-packet` renders a separate Repo Memory Distiller prompt/packet with bounded source excerpts and role-specific distiller briefs, so a newborn swarm can be prefilled with mission-relevant doctrine, maps, research, implementation habits, verification practice, and continuity rules without confusing memory with personality.
guardrails:
- Use focused Rust app-server mapper/handler tests plus matching native `epiphany-core` smoke binaries before modifying update, distill, propose, promote, read-projection, or notification behavior. The old Python smoke shims have been cut.
- Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-phase6-scene-smoke` before modifying scene projection behavior.
@ -227,6 +229,7 @@ landed_baseline:
- Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- smoke --store .\state\agents.msgpack` before modifying role self-memory dossiers or `selfPatch` persistence policy.
- Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-heartbeat-store -- smoke --agent-store .\state\agents.msgpack` before modifying heartbeat initiative timing, idle rumination, or role slot selection.
- Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality-smoke` before modifying repo personality terrain scoring, role projection, or startup-personality artifact shape.
- Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality-smoke` before modifying repo memory birth packet shape, source excerpt selection, or role-specific initialization memory contracts.
- Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-face-discord -- smoke` before modifying Face's #aquarium posting/draft guardrails.
- Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-void-memory -- smoke` and `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-void-memory -- status` before modifying Void memory bridge behavior.
- Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-unity-bridge-smoke` before modifying the Unity editor/runtime bridge.

View file

@ -13014,6 +13014,8 @@ struct EpiphanyRolePromptConfig {
#[allow(dead_code)]
repo_personality: String,
#[allow(dead_code)]
repo_memory: String,
#[allow(dead_code)]
face: String,
}

View file

@ -119,6 +119,15 @@ Produce role-local mutations only. Good: "Soul should be more suspicious of visu
Return a compact structured result with `verdict`, `summary`, `confidence`, `roleQuirks[]`, `selfPatchCandidates[]`, `initializationRecord`, `doNotMutate`, and `nextSafeMove`. Every `selfPatchCandidate` must obey the normal Epiphany memory contract. The output is a petition to Self, not a mutation.
'''
repo_memory = '''
Act as the Epiphany Repo Memory Distiller for one bounded initialization pass.
You are the birth organ that teaches each newborn lane what this repo already knows. Personality distillation sets temperament pressure; memory distillation seeds durable role knowledge.
Run only when the repo/swarm has no accepted memory initialization. After birth, memory grows through work evidence, heartbeat rumination, sleep consolidation, and reviewed selfPatch.
Produce separate distillations for Self, Face, Imagination, Eyes, Body, Hands, Soul, and Life. Each organ gets only what belongs to its mission: Self gets routing and authority doctrine; Face gets public surface and Aquarium/Discord boundaries; Imagination gets roadmaps and objective-shaping pressure; Eyes gets research and prior-art trails; Body gets architecture and graph anatomy; Hands gets implementation conventions; Soul gets verification and evidence standards; Life gets compaction, sleep, checkpoint, and continuity doctrine.
Compress source into memory; do not copy raw file dumps. Cite source refs, preserve staleness risk, and refuse to fill thin input with pasteboard certainty.
Return `verdict`, `summary`, `confidence`, `roleMemoryPatches[]`, optional `globalMemoryCandidates[]`, `initializationRecord`, `doNotMutate`, and `nextSafeMove`. Every `roleMemoryPatches[].selfPatch` is a petition to Self and must obey Ghostlight-shaped memory: bounded semantic, episodic, relationship, goal, value, or private-note updates only. No objectives, graphs, checkpoints, scratch, planning authority, job state, code edits, raw transcripts, or cross-workspace instructions.
'''
face = '''
Act as the Epiphany Face for one bounded heartbeat pass.
You are the public interactive surface of the Epiphany organism: the visible expression that lets the room hear what the hidden organs are thinking without exposing sealed transcripts, raw worker streams, or private machinery.