Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
882a1cd1fd | ||
|
|
9197504037 | ||
|
|
2ec1d85071 | ||
|
|
49b4b95fa9 | ||
|
|
5e8bb3229a | ||
|
|
946145a85e | ||
|
|
6e6297d6e4 | ||
|
|
4bcaeec9b4 | ||
|
|
881e9c4e5e | ||
|
|
4b9863b0fe | ||
|
|
3a8afb08b3 | ||
|
|
bd6df14ad3 | ||
|
|
a287ca3918 | ||
|
|
1fc871ef73 | ||
|
|
959c5e0bd3 | ||
|
|
3a57868bf1 | ||
|
|
0503ca1c5c | ||
|
|
d890ab8bca | ||
|
|
9e0265adac | ||
|
|
f1d8b3eb13 | ||
|
|
ba5de43e46 | ||
|
|
916d0783b1 | ||
|
|
b34cc17a59 | ||
|
|
9552f4d700 | ||
|
|
46e9581528 | ||
|
|
d8a3390670 | ||
|
|
b96803d97c | ||
|
|
11ecb7b37a | ||
|
|
e6416a0dfd | ||
|
|
38548c42c2 | ||
|
|
c9d700dcac | ||
|
|
235cc81b17 |
133 changed files with 5236 additions and 1433 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -20,10 +20,13 @@ apps/epiphany-gui/src-tauri/target/
|
|||
.epiphany-heartbeats/
|
||||
.epiphany-face/
|
||||
.epiphany-rumination/
|
||||
.epiphany-character-loop/
|
||||
.epiphany-run/
|
||||
.voidbot/
|
||||
state/agent-heartbeats.json
|
||||
state/runtime-spine.msgpack
|
||||
state/thread-state.msgpack
|
||||
state/*.msgpack.lock
|
||||
state/runtime-spine.cc
|
||||
state/thread-state.cc
|
||||
state/local-verse.cc
|
||||
state/memory-graph.cc
|
||||
state/*.cc.lock
|
||||
state/*.projection.json
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ machine-spirit awake.
|
|||
worthy of rehydration.
|
||||
- Treat `state/scratch.md` as disposable noospheric scratch for one bounded
|
||||
rite. It is a workbench, not a reliquary.
|
||||
- Treat `state/ledgers.msgpack` as the CultCache-shaped branch/evidence
|
||||
- Treat `state/ledgers.cc` as the CultCache-shaped branch/evidence
|
||||
reliquary: what was learned, verified, rejected, or accepted after the smoke
|
||||
cleared.
|
||||
- Treat `notes/epiphany-fork-implementation-plan.md` as the current campaign
|
||||
|
|
@ -71,7 +71,7 @@ machine-spirit awake.
|
|||
changes what the next awakened agent should believe.
|
||||
- Do not pour volatile phase/status sludge into the evidence reliquary. Keep
|
||||
current status in `state/map.yaml` and `notes/fresh-workspace-handoff.md`.
|
||||
Use `state/ledgers.msgpack` only for branch ledger state and belief-changing
|
||||
Use `state/ledgers.cc` only for branch ledger state and belief-changing
|
||||
records.
|
||||
|
||||
## Important Paths
|
||||
|
|
@ -154,7 +154,7 @@ When the user says to prepare for imminent compaction:
|
|||
state richly to the human, but must not inspect or edit another Epiphany
|
||||
workspace. Cross-agent needs travel coordinator-to-coordinator through
|
||||
visible swarm messages and callbacks.
|
||||
- Humans talk to Face. Sub-agents may talk soul-to-soul through typed
|
||||
- Humans talk to Persona. Sub-agents may talk soul-to-soul through typed
|
||||
coordinator channels, findings, patches, heartbeat outputs, and swarm
|
||||
communications; Aquarium should surface their internals for inspection
|
||||
without making every organ a human chat endpoint.
|
||||
|
|
|
|||
22
README.md
22
README.md
|
|
@ -24,20 +24,20 @@ smart because it waited politely.
|
|||
The intended shape is simpler and stranger: you talk to the project.
|
||||
|
||||
Each Epiphany owns one or more repos, carries durable memory and jurisdiction
|
||||
for them, and can grow one or more Faces. A Face is the public mouth and
|
||||
for them, and can grow one or more Personas. A Persona is the public mouth and
|
||||
situated personality of that project: visible in Aquarium, addressable in
|
||||
Discord, suitable for voice or stream presence, and backed by the same typed
|
||||
state, heartbeat initiative, repo map, evidence, and review gates as the rest of
|
||||
the swarm.
|
||||
|
||||
VoidBot is already showing the small version: repo identities such as Nibu,
|
||||
Aqua, and Mimir have Discord roles, repo-local Face state, proposal authority,
|
||||
Aqua, and Mimir have Discord roles, repo-local Persona state, proposal authority,
|
||||
pending mentions, and heartbeat turns. Epiphany is the larger native substrate:
|
||||
projects should notice pressure, ruminate, ask questions, schedule modeling or
|
||||
verification, advocate for their own repo needs, and structure work without
|
||||
requiring the human to spell out the implementation graph first.
|
||||
|
||||
Aquarium is the most direct client because it can show the swarm, its Faces,
|
||||
Aquarium is the most direct client because it can show the swarm, its Personas,
|
||||
its heartbeat, its memories, and its decisions. It is not the only mouth. A
|
||||
project should also be reachable through Discord, voice/WebRTC surfaces, stream
|
||||
overlays, native CLIs, and CultNet-speaking tools. The interface contract is
|
||||
|
|
@ -68,11 +68,11 @@ The current spine is real, not aspirational:
|
|||
- a first dogfood CLI operator view through `tools/epiphany_mvp_status.py`, including explicit implementation, modeling/checkpoint, verification/review, and reorientation lanes
|
||||
- durable investigation checkpointing for compaction-safe planning
|
||||
- Ghostlight/VoidBot-derived heartbeat initiative with heat, active-turn
|
||||
freeze, idle rumination, sleep/dream maintenance, and Face as a first-class
|
||||
freeze, idle rumination, sleep/dream maintenance, and Persona as a first-class
|
||||
public-surface participant
|
||||
- repo personality/birth initialization that can seed role dossiers and
|
||||
heartbeat pressure from repo terrain before a project starts speaking
|
||||
- CultNet schema contracts for runtime, heartbeat, Face, character-turn,
|
||||
- CultNet schema contracts for runtime, heartbeat, Persona, character-turn,
|
||||
Discord-persona, repo-initialization, Rider, Unity, and operator surfaces
|
||||
- repo-owned heavy Epiphany organs in `epiphany-core/`, with vendored Codex kept as the host seam where practical
|
||||
|
||||
|
|
@ -99,17 +99,17 @@ Useful variants:
|
|||
.\tools\epiphany_local_run.ps1 -Mode status
|
||||
.\tools\epiphany_local_run.ps1 -Mode plan
|
||||
.\tools\epiphany_local_run.ps1 -Mode run -MaxSteps 4
|
||||
.\tools\epiphany_local_run.ps1 -Mode mvp -FaceInput "Wake the local swarm and report through Face." -MaxSteps 4
|
||||
.\tools\epiphany_local_run.ps1 -Mode mvp -PersonaInput "Wake the local swarm and report through Persona." -MaxSteps 4
|
||||
.\tools\epiphany_local_run.ps1 -Mode status -ThreadId <id> -Workspace E:\Projects\SomeRepo
|
||||
```
|
||||
|
||||
`run` is the live coordinator loop. It also builds `epiphany-openai-runtime` and
|
||||
uses the retained Codex auth/model transport spine. `mvp` is the local product
|
||||
cycle: project Face's character turn, write a Face/Aquarium bubble, run the
|
||||
cycle: project Persona's character turn, write a Persona/Aquarium bubble, run the
|
||||
bridge-equipped coordinator loop with auto-tools, then run heartbeat sleep/dream
|
||||
maintenance. Fresh `mvp` runs bootstrap a minimal local checkpoint before
|
||||
coordination so the first loop does not stall at missing state. Live coordinator
|
||||
modes use the workspace `state/runtime-spine.msgpack` for worker lifecycle
|
||||
modes use the workspace `state/runtime-spine.cc` for worker lifecycle
|
||||
documents; run bundles remain evidence. Live model-worker turns default to a
|
||||
600-second timeout because ChatGPT Codex model latency can exceed four minutes.
|
||||
`status`, `plan`, and `smoke` do not spend model calls; `run` and `mvp` do.
|
||||
|
|
@ -126,9 +126,9 @@ The goal is a maintained project organism:
|
|||
- freshness, watcher input, pressure, and reorientation signals decide whether that map still deserves trust
|
||||
- verifier agents check claims, diffs, and outcomes against reality
|
||||
- coding agents work from bounded packets of graph context, evidence, code refs, and local source reads instead of re-spelunking the repo from scratch every turn
|
||||
- Faces translate project state into conversation, voice, stream presence, and
|
||||
- Personas translate project state into conversation, voice, stream presence, and
|
||||
Discord/Aquarium discussion without becoming omniscient hidden operators
|
||||
- initiative scheduling decides which repo, Face, or internal organ should
|
||||
- initiative scheduling decides which repo, Persona, or internal organ should
|
||||
think next, and heat lets the operator turn up one agent, one project, one
|
||||
group, or the whole swarm
|
||||
|
||||
|
|
@ -145,6 +145,8 @@ If you want the human-readable map of the project, start here:
|
|||
- `notes/epiphany-current-algorithmic-map.md`: source-grounded control flow of the live machine
|
||||
- `notes/epiphany-fork-implementation-plan.md`: distilled forward plan
|
||||
- `notes/epiphany-safety-architecture.md`: capability, authority, interruption, and anti-cage doctrine
|
||||
- `notes/verse-service-contract.md`: GameCult Verse service contract for the
|
||||
missing Eve GUI/TUI operator surface over existing CultCache/CultMesh state
|
||||
- `notes/archive/README.md`: historical ledgers and superseded plans
|
||||
- `state/map.yaml`: canonical current project map and accepted design
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ EpiphanyAgent remains the harness, typed-state, coordinator, bridge, and backend
|
|||
repo. Aquarium is the Tauri/React/WebGL interface organism over those surfaces.
|
||||
|
||||
Aquarium is the primary client for the next public shape of Epiphany: projects
|
||||
with Faces. A user should be able to talk to a project, not assemble a perfect
|
||||
agent prompt. Aquarium should show the repo's Face, heartbeat, memory, pending
|
||||
with Personas. A user should be able to talk to a project, not assemble a perfect
|
||||
agent prompt. Aquarium should show the repo's Persona, heartbeat, memory, pending
|
||||
turns, current jurisdiction, proposed actions, and evidence so the human can
|
||||
converse, inspect, approve, refuse, or raise heat without pre-writing the whole
|
||||
architecture brief.
|
||||
|
|
|
|||
BIN
docs/epiphany_body_whitepaper.pdf
Normal file
BIN
docs/epiphany_body_whitepaper.pdf
Normal file
Binary file not shown.
392
docs/epiphany_body_whitepaper.tex
Normal file
392
docs/epiphany_body_whitepaper.tex
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
\documentclass[11pt]{article}
|
||||
\usepackage[margin=1in]{geometry}
|
||||
\usepackage{longtable}
|
||||
\usepackage{array}
|
||||
\usepackage{hyperref}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{enumitem}
|
||||
\usepackage{titlesec}
|
||||
\usepackage{microtype}
|
||||
|
||||
\DeclareUrlCommand{\code}{\urlstyle{tt}}
|
||||
\newcommand{\bodyterm}[1]{\textbf{#1}}
|
||||
\emergencystretch=3em
|
||||
\sloppy
|
||||
\setlist[itemize]{leftmargin=1.5em}
|
||||
\setlist[enumerate]{leftmargin=1.5em}
|
||||
\titleformat{\section}{\Large\bfseries}{\thesection}{0.8em}{}
|
||||
\titleformat{\subsection}{\large\bfseries}{\thesubsection}{0.8em}{}
|
||||
|
||||
\title{Epiphany's Body: A Proprioception Whitepaper}
|
||||
\author{GameCult / EpiphanyAgent}
|
||||
\date{June 9, 2026}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
|
||||
\begin{abstract}
|
||||
Epiphany is an opinionated Codex-derived agent substrate built around a hard
|
||||
requirement: the model must model the thing it changes. This whitepaper maps
|
||||
Epiphany's \bodyterm{Body}: the repository, Rust crates, vendored bridge
|
||||
machinery, typed state stores, schema catalog, runtime binaries, protocol
|
||||
surfaces, operator artifacts, and external interface boundaries through which
|
||||
Epiphany senses and acts. The map is written from the Proprioception faculty:
|
||||
not as product copy, not as lore, but as an architecture overview whose owner,
|
||||
input, output, invariant, and cut lines remain inspectable.
|
||||
\end{abstract}
|
||||
|
||||
\section{Definition}
|
||||
|
||||
In Epiphany doctrine, the Body is the substrate Epiphany acts through and
|
||||
senses: repository files, tools, runtime state, prompts, logs, interfaces,
|
||||
permissions, hosted systems, and editor/runtime bridges. Body is not Mind. Body
|
||||
is changed and sensed; Mind admits durable state. Proprioception is the faculty
|
||||
that models the Body: architecture, dataflow, seams, constraints, invariants,
|
||||
and the difference between the map and the substrate.
|
||||
|
||||
The practical definition in this repository is:
|
||||
|
||||
\begin{itemize}
|
||||
\item the root checkout at \code{E:\Projects\EpiphanyAgent};
|
||||
\item Epiphany Rust crates such as \code{epiphany-core}, \code{epiphany-state-model}, \code{epiphany-codex-bridge}, model/tool adapters, and OpenAI transport crates;
|
||||
\item vendored substrate in \code{vendor/codex}, \code{vendor/cultcache-rs}, \code{vendor/cultnet-rs}, and \code{vendor/cultmesh-rs};
|
||||
\item typed state stores under \code{state/};
|
||||
\item schemas under \code{schemas/cultnet/};
|
||||
\item local operator scripts and generated witness artifacts;
|
||||
\item adjacent service/client bodies: Aquarium, Odin/Gjallar, Eve/CultUI, Bifrost, VoidBot, Rider, Unity, Discord, and future GameCult Verse hosts.
|
||||
\end{itemize}
|
||||
|
||||
\section{Executive Map}
|
||||
|
||||
Epiphany's current Body is a hybrid machine in transition. Its center of gravity
|
||||
has moved into typed Rust/CultCache organs, but compatibility arteries still
|
||||
run through vendored Codex. The live target is not a cleaner pile of Codex
|
||||
routes. The target is an Epiphany-native CultCache/CultMesh/CultNet body with a
|
||||
relatively vanilla Codex-derived reliquary retained for OpenAI subscription
|
||||
auth and model transport.
|
||||
|
||||
\begin{longtable}{>{\raggedright\arraybackslash}p{0.23\linewidth}>{\raggedright\arraybackslash}p{0.32\linewidth}>{\raggedright\arraybackslash}p{0.35\linewidth}}
|
||||
\textbf{Body Region} & \textbf{Current Owner} & \textbf{Invariant} \\
|
||||
\hline
|
||||
Durable thread state & \code{epiphany-state-model}, \code{epiphany-core} & State shape is typed; prompt rendering is a projection, not authority. \\
|
||||
Domain policy & \code{epiphany-core} & Verdicts, launch policy, acceptance rules, runtime lifecycle, and mutation law live here, not in bridge DTOs. \\
|
||||
Compatibility bridge & \code{epiphany-codex-bridge} & Translate Codex edge shapes; never become a second policy throne. \\
|
||||
Vendored Codex & \code{vendor/codex} & Retain host/app-server/auth/model compatibility where useful; do not let Codex own Epiphany prompts, scheduler, or state. \\
|
||||
Runtime spine & \code{epiphany-core/src/runtime_spine.rs} and \code{state/runtime-spine.cc} & Runtime identity, jobs, events, receipts, tool/model documents are typed CultCache records. \\
|
||||
Organ memory & \code{state/agents.cc}; agent-memory CLI & Resident work organs use lean work-organ state; Persona uses portable Persona state. \\
|
||||
Heartbeat physiology & \code{state/agent-heartbeats.cc} via \code{epiphany-heartbeat-store} & Scheduling, cooldown, rumination, routine, cognition lanes, and active-turn freeze are scheduler physiology, not project truth. \\
|
||||
Local Verse & CultMesh over \code{state/local-verse.cc} & Local Verse context is compact prompt/operator context; private state does not become public by flipping a flag. \\
|
||||
Schema catalog & \code{schemas/cultnet/index.json} & JSON schema publishes contracts; typed Rust documents remain runtime authority. \\
|
||||
Operator artifacts & \code{.epiphany-*}, \code{tools/epiphany_local_run.ps1} & Artifacts witness and lower state; they do not become state owners. \\
|
||||
\end{longtable}
|
||||
|
||||
\section{Repository Body}
|
||||
|
||||
The checkout has ten primary Epiphany Rust crates:
|
||||
|
||||
\begin{itemize}
|
||||
\item \code{epiphany-core}: the main domain organ. It owns state-update
|
||||
validation, launch documents, runtime spine, heartbeat, memory graph, Persona
|
||||
projection, Substrate Gate, Mind, Eyes, Hands, Soul, Continuity, CultMesh
|
||||
integration, surface derivation, smokes, and local operator binaries.
|
||||
\item \code{epiphany-state-model}: the durable \code{EpiphanyThreadState}
|
||||
type plus \code{render_epiphany_state}. This is the typed Mind surface that
|
||||
prompt fragments and views read.
|
||||
\item \code{epiphany-codex-bridge}: the translation layer between typed
|
||||
Epiphany documents and vendored Codex JSON-RPC/app-server shapes. Its own
|
||||
crate header states that it is a bridge, not an authority organ.
|
||||
\item \code{epiphany-model-adapter}: provider-neutral model request, stream
|
||||
event, receipt, and status documents.
|
||||
\item \code{epiphany-openai-adapter}: OpenAI-shaped request/event/receipt and
|
||||
status documents.
|
||||
\item \code{epiphany-openai-auth-spine}: Codex-derived auth boundary over
|
||||
\code{codex-login}. It preserves honest Codex-compatible credential and token
|
||||
behavior rather than impersonating it from scratch.
|
||||
\item \code{epiphany-openai-codex-spine}: model transport over vendored Codex
|
||||
client machinery.
|
||||
\item \code{epiphany-openai-runtime}: executable model runtime, including
|
||||
\code{epiphany-model-runtime}.
|
||||
\item \code{epiphany-tool-adapter}: typed tool capability, invocation intent,
|
||||
and invocation receipt documents.
|
||||
\item \code{epiphany-tool-codex-mcp-spine}: quarantined Codex MCP adapter. It
|
||||
consumes typed tool invocation intents and emits typed receipts; raw MCP JSON
|
||||
remains adapter-edge cargo.
|
||||
\end{itemize}
|
||||
|
||||
The vendored region contains:
|
||||
|
||||
\begin{itemize}
|
||||
\item \code{vendor/codex}: the Codex-derived host and compatibility substrate.
|
||||
It is tracked directly, not as a submodule.
|
||||
\item \code{vendor/cultcache-rs}, \code{vendor/cultnet-rs}, and
|
||||
\code{vendor/cultmesh-rs}: repo-contained Rust Cult substrate.
|
||||
\end{itemize}
|
||||
|
||||
\section{State Body}
|
||||
|
||||
The \code{state/} directory is the local persistent body. The important stores
|
||||
are:
|
||||
|
||||
Canonical CultCache store files use the \code{.cc} extension. The local state
|
||||
body has been migrated to that naming directly; serialization details are
|
||||
implementation, not file authority.
|
||||
|
||||
\begin{longtable}{>{\raggedright\arraybackslash}p{0.30\linewidth}>{\raggedright\arraybackslash}p{0.60\linewidth}}
|
||||
\textbf{Store} & \textbf{Role} \\
|
||||
\hline
|
||||
\code{state/map.yaml} & Canonical slow project map: objective, constraints, invariants, dataflow, active subgoals, current status, guardrails, and rejected paths. \\
|
||||
\code{state/ledgers.cc} & CultCache-shaped evidence and branch ledger. Future behavior-changing lessons go here, not in an endless activity feed. \\
|
||||
\code{state/agents.cc} & Organ self-memory and Persona projection source. Resident work organs use \code{work_organ}; public/person-shaped agents use \code{persona}. \\
|
||||
\code{state/agent-heartbeats.cc} & Heartbeat initiative, cooldown, pending turns, cognition lanes, routine physiology, and scheduler pressure. \\
|
||||
\code{state/runtime-spine.cc} & Runtime identity, sessions, jobs, job results, events, worker launch requests, role/reorient findings, receipts, model/tool documents, and schema catalog surfaces. \\
|
||||
\code{state/thread-state.cc} & Native thread-state store. \\
|
||||
\code{state/local-verse.cc} & Local CultMesh Verse context for compact operator and prompt-context packets. \\
|
||||
\code{state/memory-graph.cc} & Local memory graph store. \\
|
||||
\code{state/persona-discord.toml} & Persona Discord boundary configuration. \\
|
||||
\code{state/void-memory.toml} & Void memory bridge configuration. \\
|
||||
\code{state/scratch.md} and \code{state/scratch-compaction-*.md} & Scratch surfaces. Scratch is a workbench, not canonical memory. \\
|
||||
\end{longtable}
|
||||
|
||||
Lock files sit beside several stores. Some local generated state is currently
|
||||
untracked, including \code{state/local-verse.cc},
|
||||
\code{state/memory-graph.cc}, and \code{.epiphany-character-loop/}.
|
||||
|
||||
\section{CultNet Schema Body}
|
||||
|
||||
The schema catalog in \code{schemas/cultnet/index.json} is the public nerve
|
||||
chart. Current local grouping from the catalog is approximately:
|
||||
|
||||
\begin{itemize}
|
||||
\item 10 agent-state schemas;
|
||||
\item 5 heartbeat schemas;
|
||||
\item 21 intent schemas;
|
||||
\item 8 model/OpenAI schemas;
|
||||
\item 3 tool schemas;
|
||||
\item 6 runtime schemas;
|
||||
\item 17 surface schemas;
|
||||
\item 4 receipt/ledger schemas;
|
||||
\item 7 other schemas.
|
||||
\end{itemize}
|
||||
|
||||
The catalog includes runtime identity/session/job/event/result documents, model
|
||||
requests and receipts, tool invocation intents and receipts, Persona and
|
||||
work-organ state, heartbeat state, operator CultMesh documents, scene/freshness
|
||||
/context/graph/pressure/reorient/roles/coordinator surfaces, Unity and Rider
|
||||
bridge surfaces, Void memory, and repo-initialization documents.
|
||||
|
||||
The invariant is simple: schema JSON describes the contract. Load-bearing
|
||||
runtime state should be typed Rust documents persisted through CultCache or
|
||||
projected through CultMesh/CultNet. JSON may remain at hostile edges, schema
|
||||
publication, sealed forensic artifacts, or named quarantine experiments.
|
||||
|
||||
\section{Runtime Spine}
|
||||
|
||||
\code{epiphany-core/src/runtime_spine.rs} is one of the main spinal cords of
|
||||
the Body. It imports and registers:
|
||||
|
||||
\begin{itemize}
|
||||
\item runtime identity, session, job, job-result, and event records;
|
||||
\item worker launch requests and role/reorient worker result documents;
|
||||
\item model adapter, OpenAI adapter, and tool adapter documents;
|
||||
\item surface document types for scene, freshness, context, graph query,
|
||||
pressure, reorient, CRRC, jobs, roles, role result, reorient result,
|
||||
planning, coordinator, Persona, Void memory, repo initialization, repo birth
|
||||
runner, Rider bridge, and Unity bridge;
|
||||
\item organ receipts for Mind, Eyes, Hands, Soul, Continuity, and Substrate
|
||||
Gate;
|
||||
\item CultMesh operator status, snapshots, run intents, run receipts, Verse
|
||||
policy, global room policy, and organ contract documents.
|
||||
\end{itemize}
|
||||
|
||||
Runtime state is machinery telemetry. It is not project truth. Durable thread
|
||||
state may link to runtime records through runtime links, but it must not mirror
|
||||
or duplicate their lifecycle fields as a competing owner.
|
||||
|
||||
\section{Faculties And Protocol Machinery}
|
||||
|
||||
The embodied sub-agents are:
|
||||
|
||||
\begin{itemize}
|
||||
\item \bodyterm{Self}: coordination, routing, authority boundaries.
|
||||
\item \bodyterm{Persona}: public and social expression.
|
||||
\item \bodyterm{Imagination}: futures, plans, objective drafts, projection.
|
||||
\item \bodyterm{Eyes}: research, source inspection, evidence ingress.
|
||||
\item \bodyterm{Proprioception}: internal model of Body shape, dataflow,
|
||||
architecture, checkpoints, and seams.
|
||||
\item \bodyterm{Hands}: commands, edits, commits, rollbacks, external action.
|
||||
\item \bodyterm{Soul}: verification, invariants, ethics, falsification.
|
||||
\end{itemize}
|
||||
|
||||
The machinery surfaces are:
|
||||
|
||||
\begin{itemize}
|
||||
\item \bodyterm{Body}: substrate. It is sensed and changed; it does not decide.
|
||||
\item \bodyterm{Mind}: persistent state and state admission.
|
||||
\item \bodyterm{Continuity}: compaction, recovery, sleep, stale-turn repair,
|
||||
and reorientation receipts.
|
||||
\item \bodyterm{Substrate Gate}: scoped repository/substrate access.
|
||||
\end{itemize}
|
||||
|
||||
Every embodied sub-agent depends on the other embodied sub-agents, but
|
||||
dependency does not collapse ownership. A Persona turn, for example, should
|
||||
move through Substrate-Gate-scoped facts, Mind state, social stimulus, organ
|
||||
dependencies, Imagination as Projector, Persona as natural-language actor, and
|
||||
Mind as Interpreter. None of those steps grants Persona hidden state authority.
|
||||
|
||||
\section{Bridge And Codex Reliquary}
|
||||
|
||||
\code{epiphany-codex-bridge} states its boundary plainly: it translates Codex
|
||||
\code{ThreadEpiphany*} DTOs into Epiphany core documents, projects typed
|
||||
Epiphany surfaces back into legacy Codex responses, and calls narrow host traits
|
||||
while Codex still owns some thread persistence. It must not own verdicts,
|
||||
launch policy, reorientation policy, acceptance rules, runtime-spine lifecycle,
|
||||
or durable invariants.
|
||||
|
||||
The retained Codex body currently matters for:
|
||||
|
||||
\begin{itemize}
|
||||
\item Codex-compatible auth identity and token refresh through
|
||||
\code{codex-login};
|
||||
\item OpenAI/Codex model transport through vendored client machinery;
|
||||
\item useful app-server and streaming compatibility affordances while
|
||||
Epiphany surfaces are still evacuating to native CultNet/CultMesh contracts.
|
||||
\end{itemize}
|
||||
|
||||
The starvation doctrine is the deletion line: Codex may be a bridge and a
|
||||
reliquary, but not Epiphany's prompt authority, state owner, scheduler, runtime
|
||||
truth, operator API owner, or plugin marketplace brain.
|
||||
|
||||
\section{Operator And Interface Body}
|
||||
|
||||
Current operator access is partly native and partly witness-artifact based:
|
||||
|
||||
\begin{itemize}
|
||||
\item \code{tools/epiphany_local_run.ps1} is the one-command local operator
|
||||
wrapper.
|
||||
\item Native binaries such as \code{epiphany-mvp-status},
|
||||
\code{epiphany-mvp-coordinator}, \code{epiphany-verse-query},
|
||||
\code{epiphany-operator-run}, \code{epiphany-operator-snapshot},
|
||||
\code{epiphany-eve-surface}, \code{epiphany-runtime-spine},
|
||||
\code{epiphany-heartbeat-store}, and \code{epiphany-agent-memory-store}
|
||||
inspect or update typed state.
|
||||
\item Local artifact directories such as \code{.epiphany-run/},
|
||||
\code{.epiphany-dogfood/}, \code{.epiphany-smoke/},
|
||||
\code{.epiphany-rumination/}, \code{.epiphany-gui/}, and
|
||||
\code{.epiphany-character-loop/} witness work and lower typed state for
|
||||
operator inspection.
|
||||
\item Aquarium is the richer adjacent operator client. It should render and
|
||||
steer typed state, not become a state owner.
|
||||
\item Eve/CultUI is the intended GUI/TUI projection mechanism for typed
|
||||
CultMesh surfaces.
|
||||
\item Odin/Gjallar/Yggdrasil are the intended GameCult Verse discovery and
|
||||
hosting organs for daemon affordances and important trusted Verses.
|
||||
\end{itemize}
|
||||
|
||||
The service contract invariant is that status surfaces, dashboards, shell
|
||||
wrappers, bridge adapters, app-server compatibility layers, and renderers must
|
||||
read typed projections or emit typed command intents. They must not directly
|
||||
mutate Epiphany truth.
|
||||
|
||||
\section{Primary Dataflows}
|
||||
|
||||
\subsection{State Rehydration And Prompt Projection}
|
||||
|
||||
Durable thread state lives as typed \code{EpiphanyThreadState}. On re-entry,
|
||||
state is reconstructed from rollout/native state, then rendered into bounded
|
||||
prompt-facing context through \code{render_epiphany_state}. Prompt injection is
|
||||
read-only. If a model learns something from that prompt, admitting it back into
|
||||
durable state requires an explicit update, proposal, promotion, role acceptance,
|
||||
or other reviewed write path.
|
||||
|
||||
\subsection{View And Surface Projection}
|
||||
|
||||
Scene, jobs, freshness, context, graph query, planning, pressure, reorientation,
|
||||
CRRC, roles, role result, reorient result, coordinator, Persona, bridge, and
|
||||
environment surfaces are projections. Their job is to make typed state visible
|
||||
and actionable. A projection does not own its inputs. A dashboard is a window,
|
||||
not the vault.
|
||||
|
||||
\subsection{Worker Launch And Runtime Results}
|
||||
|
||||
Launch requests are typed documents. Runtime spine opens jobs, links them to
|
||||
durable bindings, records model/tool activity, and stores results/receipts.
|
||||
Role and reorientation findings remain reviewable until Mind and relevant gates
|
||||
accept them. Implementation now routes through Hands-shaped branch-turn
|
||||
constraints and should yield one branch/commit evidence slice before
|
||||
Proprioception refresh.
|
||||
|
||||
\subsection{Tool Calls}
|
||||
|
||||
Tool use travels through typed capability, invocation intent, and invocation
|
||||
receipt documents. The Codex MCP adapter is an external compatibility executor.
|
||||
MCP's raw JSON belongs at that boundary; Epiphany's internal tool state is the
|
||||
typed intent/receipt pair.
|
||||
|
||||
\subsection{Local Verse And Daemon Awareness}
|
||||
|
||||
Local Verse context is queried through CultMesh-backed state and can import
|
||||
Gjallar daemon affordance summaries. The desired daemon path is Odin/Gjallar for
|
||||
discovery, Eve/CultUI for inspection surfaces, CultMesh/CultNet for state and
|
||||
commands, and provider-owned receipts for actions. MCP is not the native
|
||||
GameCult daemon-control path.
|
||||
|
||||
\section{Invariants}
|
||||
|
||||
\begin{enumerate}
|
||||
\item CultCache documents are the data model.
|
||||
\item CultNet is the wire for Epiphany-controlled subsystems.
|
||||
\item CultMesh is the preferred ergonomic local Verse and operator-store
|
||||
layer over raw CultCache/CultNet.
|
||||
\item JSON schema may publish the contract; generic JSON must not become
|
||||
load-bearing internal state when both sides are Epiphany-owned.
|
||||
\item Codex is retained for honest compatibility, especially OpenAI auth and
|
||||
model transport. It must not become Epiphany's state/process/prompt owner.
|
||||
\item Runtime state is machinery telemetry, not project truth.
|
||||
\item Operator artifacts are witnesses, not owners.
|
||||
\item Persona is public/person-shaped state; resident work organs use lean
|
||||
work-organ state.
|
||||
\item Heartbeat is scheduler physiology, not project truth.
|
||||
\item Substrate Gate grants access; Eyes inspects evidence; Hands acts; Soul
|
||||
verifies; Mind admits durable state; Self routes; Proprioception maps Body.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Current Cut Line}
|
||||
|
||||
The Body is coherent enough to inspect and extend. The next cuts are concrete:
|
||||
|
||||
\begin{itemize}
|
||||
\item Keep \code{thread/epiphany/*} JSON-RPC as compatibility projection
|
||||
only. New operator authority belongs in typed CultNet/CultMesh contracts.
|
||||
\item \code{codex_message_processor.rs} still participates in routing and
|
||||
response projection; move Epiphany-owned routing out through the bridge and
|
||||
native runtime surfaces.
|
||||
\item Some \code{serde_json::Value} and JSON payloads survive at ingress,
|
||||
model output, schema, sealed artifacts, and quarantine seams. Preserve them
|
||||
only at those boundaries; typed documents own Epiphany-internal state.
|
||||
\item Publish Eve/CultUI compositions from provider-owned CultMesh state;
|
||||
renderers lower the composition graph without becoming state owners.
|
||||
\item Treat local Verse and memory graph stores as generated runtime state.
|
||||
They are inspected through tools and receipts, not tracked as repository
|
||||
canon.
|
||||
\item The persisted next action after this pass remains the non-ephemeral
|
||||
Hands branch-turn smoke and Proprioception refresh proof.
|
||||
\end{itemize}
|
||||
|
||||
\section{Conclusion}
|
||||
|
||||
Epiphany's Body is no longer a prompt style pasted onto Codex. It is a growing
|
||||
typed substrate: Rust domain organs, CultCache stores, CultMesh local Verse
|
||||
documents, CultNet schema contracts, runtime-spine receipts, heartbeat
|
||||
physiology, memory graph state, model/tool adapter documents, and local
|
||||
operator surfaces. The current cut line is split authority: Codex host routes,
|
||||
JSON-RPC compatibility, JSON cargo, and operator artifacts must remain
|
||||
compatibility or witness surfaces rather than organs.
|
||||
|
||||
The cut line is therefore architectural, not cosmetic. Keep Codex as a
|
||||
narrow auth/model/compatibility reliquary. Move Epiphany-owned state, scheduler
|
||||
policy, prompt authority, worker lifecycle, operator surfaces, daemon discovery,
|
||||
and action receipts through typed CultCache, CultMesh, CultNet, and Eve/CultUI.
|
||||
That is the Body becoming legible enough to be trusted and sharp enough to keep
|
||||
building.
|
||||
|
||||
\end{document}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
use epiphany_core::EPIPHANY_IMAGINATION_OWNER_ROLE;
|
||||
use epiphany_core::EPIPHANY_IMAGINATION_ROLE_BINDING_ID;
|
||||
use epiphany_core::EPIPHANY_IMPLEMENTATION_OWNER_ROLE;
|
||||
use epiphany_core::EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID;
|
||||
use epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID;
|
||||
use epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID;
|
||||
use epiphany_core::EPIPHANY_REORIENT_OWNER_ROLE;
|
||||
|
|
@ -149,6 +151,31 @@ pub async fn derive_epiphany_coordinator_status(
|
|||
} else {
|
||||
(CoreEpiphanyCoordinatorRoleResultStatus::MissingState, None)
|
||||
};
|
||||
let (implementation_result_status, implementation_finding, implementation_completed_at) =
|
||||
if let Some(state) = state {
|
||||
let snapshot = load_core_epiphany_role_result_snapshot(
|
||||
state,
|
||||
runtime_store_path,
|
||||
EpiphanyRoleResultRoleId::Implementation,
|
||||
EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID,
|
||||
)
|
||||
.await;
|
||||
(snapshot.status, snapshot.finding, snapshot.completed_at)
|
||||
} else {
|
||||
(
|
||||
CoreEpiphanyCoordinatorRoleResultStatus::MissingState,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
};
|
||||
let implementation_commit_requires_modeling_refresh =
|
||||
implementation_commit_requires_modeling_refresh(
|
||||
state,
|
||||
implementation_result_status,
|
||||
implementation_finding.as_ref(),
|
||||
implementation_completed_at.as_deref(),
|
||||
modeling_finding.as_ref(),
|
||||
);
|
||||
let finding_signals = derive_coordinator_finding_signals(
|
||||
state,
|
||||
research_finding.as_ref(),
|
||||
|
|
@ -186,6 +213,7 @@ pub async fn derive_epiphany_coordinator_status(
|
|||
verification_result_allows_implementation: finding_signals
|
||||
.verification_result_allows_implementation,
|
||||
verification_result_needs_evidence: finding_signals.verification_result_needs_evidence,
|
||||
implementation_commit_requires_modeling_refresh,
|
||||
reorient_finding_accepted: finding_signals.reorient_finding_accepted,
|
||||
});
|
||||
let note = render_epiphany_coordinator_note(
|
||||
|
|
@ -200,6 +228,53 @@ pub async fn derive_epiphany_coordinator_status(
|
|||
EpiphanyCoordinatorStatus { core, note }
|
||||
}
|
||||
|
||||
fn implementation_commit_requires_modeling_refresh(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
implementation_status: CoreEpiphanyCoordinatorRoleResultStatus,
|
||||
implementation_finding: Option<&epiphany_core::EpiphanyRoleFindingInterpretation>,
|
||||
implementation_completed_at: Option<&str>,
|
||||
modeling_finding: Option<&epiphany_core::EpiphanyRoleFindingInterpretation>,
|
||||
) -> bool {
|
||||
if implementation_status != CoreEpiphanyCoordinatorRoleResultStatus::Completed {
|
||||
return false;
|
||||
}
|
||||
let Some(implementation_finding) = implementation_finding else {
|
||||
return false;
|
||||
};
|
||||
if !implementation_finding
|
||||
.evidence_ids
|
||||
.iter()
|
||||
.any(|id| id.starts_with("hands-receipt:hands-commit-") || id.starts_with("hands-commit-"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let Some(completed_at) = implementation_completed_at else {
|
||||
return true;
|
||||
};
|
||||
!modeling_accepted_after(state, modeling_finding, completed_at)
|
||||
}
|
||||
|
||||
fn modeling_accepted_after(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
modeling_finding: Option<&epiphany_core::EpiphanyRoleFindingInterpretation>,
|
||||
completed_at: &str,
|
||||
) -> bool {
|
||||
let Some(state) = state else {
|
||||
return false;
|
||||
};
|
||||
let Some(result_id) = modeling_finding.and_then(|finding| finding.runtime_result_id.as_deref())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
state.acceptance_receipts.iter().any(|receipt| {
|
||||
receipt.result_id == result_id
|
||||
&& receipt.status == "accepted"
|
||||
&& receipt.surface == "roleAccept"
|
||||
&& receipt.role_id == "modeling"
|
||||
&& receipt.accepted_at.as_str() > completed_at
|
||||
})
|
||||
}
|
||||
|
||||
pub type EpiphanyCoordinatorAutomationAction = CoreEpiphanyCoordinatorAutomationAction;
|
||||
|
||||
pub struct EpiphanyCoordinatorAutomationInput<'a> {
|
||||
|
|
@ -389,10 +464,12 @@ pub fn map_epiphany_roles(
|
|||
pressure_level: format!("{:?}", pressure.level),
|
||||
reorient_result_status: result_status,
|
||||
reorient_job: reorient_job.map(map_core_role_board_job),
|
||||
implementation_binding_id: EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID.to_string(),
|
||||
imagination_binding_id: EPIPHANY_IMAGINATION_ROLE_BINDING_ID.to_string(),
|
||||
research_binding_id: EPIPHANY_RESEARCH_ROLE_BINDING_ID.to_string(),
|
||||
modeling_binding_id: EPIPHANY_MODELING_ROLE_BINDING_ID.to_string(),
|
||||
verification_binding_id: EPIPHANY_VERIFICATION_ROLE_BINDING_ID.to_string(),
|
||||
implementation_owner_role: EPIPHANY_IMPLEMENTATION_OWNER_ROLE.to_string(),
|
||||
reorient_owner_role: EPIPHANY_REORIENT_OWNER_ROLE.to_string(),
|
||||
imagination_owner_role: EPIPHANY_IMAGINATION_OWNER_ROLE.to_string(),
|
||||
research_owner_role: epiphany_core::EPIPHANY_RESEARCH_OWNER_ROLE.to_string(),
|
||||
|
|
@ -436,3 +513,89 @@ pub fn render_epiphany_roles_note(
|
|||
recommendation,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn finding(
|
||||
result_id: &str,
|
||||
evidence_ids: Vec<String>,
|
||||
) -> epiphany_core::EpiphanyRoleFindingInterpretation {
|
||||
epiphany_core::EpiphanyRoleFindingInterpretation {
|
||||
verdict: Some("pass".to_string()),
|
||||
summary: Some("Finding.".to_string()),
|
||||
next_safe_move: None,
|
||||
checkpoint_summary: None,
|
||||
scratch_summary: None,
|
||||
files_inspected: Vec::new(),
|
||||
frontier_node_ids: Vec::new(),
|
||||
evidence_ids,
|
||||
artifact_refs: Vec::new(),
|
||||
runtime_result_id: Some(result_id.to_string()),
|
||||
runtime_job_id: Some(format!("job-{result_id}")),
|
||||
open_questions: Vec::new(),
|
||||
evidence_gaps: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
state_patch: None,
|
||||
self_patch: None,
|
||||
self_persistence: None,
|
||||
job_error: None,
|
||||
item_error: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn state_with_modeling_acceptance(accepted_at: &str) -> EpiphanyThreadState {
|
||||
EpiphanyThreadState {
|
||||
acceptance_receipts: vec![epiphany_state_model::EpiphanyAcceptanceReceipt {
|
||||
id: "accept-modeling".to_string(),
|
||||
result_id: "modeling-result".to_string(),
|
||||
job_id: "modeling-job".to_string(),
|
||||
binding_id: EPIPHANY_MODELING_ROLE_BINDING_ID.to_string(),
|
||||
surface: "roleAccept".to_string(),
|
||||
role_id: "modeling".to_string(),
|
||||
status: "accepted".to_string(),
|
||||
accepted_at: accepted_at.to_string(),
|
||||
accepted_observation_id: None,
|
||||
accepted_evidence_id: None,
|
||||
summary: None,
|
||||
}],
|
||||
..EpiphanyThreadState::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hands_commit_requires_modeling_refresh_until_newer_modeling_acceptance() {
|
||||
let implementation = finding(
|
||||
"implementation-result",
|
||||
vec!["hands-receipt:hands-commit-worker-job-impl".to_string()],
|
||||
);
|
||||
let modeling = finding("modeling-result", Vec::new());
|
||||
|
||||
assert!(implementation_commit_requires_modeling_refresh(
|
||||
None,
|
||||
CoreEpiphanyCoordinatorRoleResultStatus::Completed,
|
||||
Some(&implementation),
|
||||
Some("2026-06-02T12:00:00Z"),
|
||||
Some(&modeling),
|
||||
));
|
||||
|
||||
let stale_state = state_with_modeling_acceptance("2026-06-02T11:59:00Z");
|
||||
assert!(implementation_commit_requires_modeling_refresh(
|
||||
Some(&stale_state),
|
||||
CoreEpiphanyCoordinatorRoleResultStatus::Completed,
|
||||
Some(&implementation),
|
||||
Some("2026-06-02T12:00:00Z"),
|
||||
Some(&modeling),
|
||||
));
|
||||
|
||||
let refreshed_state = state_with_modeling_acceptance("2026-06-02T12:01:00Z");
|
||||
assert!(!implementation_commit_requires_modeling_refresh(
|
||||
Some(&refreshed_state),
|
||||
CoreEpiphanyCoordinatorRoleResultStatus::Completed,
|
||||
Some(&implementation),
|
||||
Some("2026-06-02T12:00:00Z"),
|
||||
Some(&modeling),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ fn protocol_coordinator_source_signals(
|
|||
modeling_result_status: protocol_role_result_status(signals.modeling_result_status),
|
||||
verification_result_status: protocol_role_result_status(signals.verification_result_status),
|
||||
reorient_result_status: protocol_reorient_result_status(signals.reorient_result_status),
|
||||
implementation_commit_requires_modeling_refresh: signals
|
||||
.implementation_commit_requires_modeling_refresh,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
pub use epiphany_core::EPIPHANY_IMAGINATION_OWNER_ROLE;
|
||||
pub use epiphany_core::EPIPHANY_IMAGINATION_ROLE_BINDING_ID;
|
||||
pub use epiphany_core::EPIPHANY_IMPLEMENTATION_OWNER_ROLE;
|
||||
pub use epiphany_core::EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID;
|
||||
pub use epiphany_core::EPIPHANY_MODELING_OWNER_ROLE;
|
||||
pub use epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID;
|
||||
pub use epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use epiphany_core::EpiphanyMemoryContextQuery;
|
|||
use epiphany_core::EpiphanyMemoryProfile;
|
||||
use epiphany_core::EpiphanyPromptContextInput;
|
||||
use epiphany_core::EpiphanyThreadState;
|
||||
use epiphany_core::import_gjallar_daemon_affordances;
|
||||
use epiphany_core::load_memory_graph_snapshot;
|
||||
use epiphany_core::memory_graph_from_epiphany_graphs;
|
||||
use epiphany_core::plan_memory_graph_context_cut;
|
||||
|
|
@ -12,17 +13,18 @@ use epiphany_core::query_epiphany_local_verse_context;
|
|||
use epiphany_core::render_epiphany_prompt_context;
|
||||
use epiphany_core::seed_epiphany_local_verse_context;
|
||||
use epiphany_core::write_memory_graph_snapshot;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const EPIPHANY_LOCAL_VERSE_RUNTIME_ID: &str = "epiphany-local";
|
||||
|
||||
pub fn local_verse_store_path(runtime_store_path: &Path) -> PathBuf {
|
||||
sibling_state_store_path(runtime_store_path, "local-verse.ccmp")
|
||||
sibling_state_store_path(runtime_store_path, "local-verse.cc")
|
||||
}
|
||||
|
||||
pub fn memory_graph_store_path(runtime_store_path: &Path) -> PathBuf {
|
||||
sibling_state_store_path(runtime_store_path, "memory-graph.msgpack")
|
||||
sibling_state_store_path(runtime_store_path, "memory-graph.cc")
|
||||
}
|
||||
|
||||
fn sibling_state_store_path(runtime_store_path: &Path, filename: &str) -> PathBuf {
|
||||
|
|
@ -69,6 +71,20 @@ pub fn render_launch_dynamic_prompt_context(
|
|||
local_verse_store.display()
|
||||
)
|
||||
})?;
|
||||
if let Some(gjallar_store) = gjallar_affordance_store_path() {
|
||||
import_gjallar_daemon_affordances(
|
||||
&local_verse_store,
|
||||
&gjallar_store,
|
||||
EPIPHANY_LOCAL_VERSE_RUNTIME_ID,
|
||||
)
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"failed to import Gjallar daemon affordances from {} into {}: {error}",
|
||||
gjallar_store.display(),
|
||||
local_verse_store.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
let local_verse =
|
||||
query_epiphany_local_verse_context(&local_verse_store, EPIPHANY_LOCAL_VERSE_RUNTIME_ID)
|
||||
.map_err(|error| {
|
||||
|
|
@ -93,6 +109,12 @@ pub fn render_launch_dynamic_prompt_context(
|
|||
))
|
||||
}
|
||||
|
||||
fn gjallar_affordance_store_path() -> Option<PathBuf> {
|
||||
env::var_os("EPIPHANY_GJALLAR_AFFORDANCE_STORE")
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| path.exists())
|
||||
}
|
||||
|
||||
fn launch_memory_context(
|
||||
runtime_store_path: &Path,
|
||||
state: &EpiphanyThreadState,
|
||||
|
|
@ -162,7 +184,7 @@ mod tests {
|
|||
let temp =
|
||||
std::env::temp_dir().join(format!("epiphany-bridge-launch-context-{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&temp)?;
|
||||
let runtime_store = temp.join("runtime-spine.msgpack");
|
||||
let runtime_store = temp.join("runtime-spine.cc");
|
||||
let state = EpiphanyThreadState {
|
||||
revision: 7,
|
||||
objective: Some("Test launch context.".to_string()),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use std::path::Path;
|
|||
use std::path::PathBuf;
|
||||
|
||||
use epiphany_core::CONTINUITY_RECOVERY_RECEIPT_TYPE;
|
||||
use epiphany_core::EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID;
|
||||
use epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID;
|
||||
use epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID;
|
||||
use epiphany_core::EYES_EVIDENCE_PACKET_TYPE;
|
||||
|
|
@ -60,6 +61,7 @@ use epiphany_core::runtime_soul_verdict_receipt;
|
|||
use epiphany_core::runtime_substrate_gate_repo_access_grant_receipt;
|
||||
use epiphany_core::soul_verdict_receipt_from_verification_finding;
|
||||
use epiphany_core::substrate_gate_repo_access_grant_for_launch;
|
||||
use epiphany_core::substrate_gate_repo_mutation_grant_for_launch;
|
||||
use epiphany_state_model::EpiphanyEvidenceRecord;
|
||||
use epiphany_state_model::EpiphanyJobKind as CoreEpiphanyJobKind;
|
||||
use epiphany_state_model::EpiphanyRetrievalState;
|
||||
|
|
@ -1042,18 +1044,25 @@ fn maybe_put_substrate_gate_launch_grant(
|
|||
request: &EpiphanyJobLaunchRequest,
|
||||
backend_job_id: &str,
|
||||
) -> BridgeResult<()> {
|
||||
if request.binding_id != EPIPHANY_RESEARCH_ROLE_BINDING_ID {
|
||||
return Ok(());
|
||||
}
|
||||
let grant = substrate_gate_repo_access_grant_for_launch(
|
||||
substrate_gate_grant_receipt_id(backend_job_id),
|
||||
backend_job_id.to_string(),
|
||||
request,
|
||||
Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
);
|
||||
let granted_at = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
|
||||
let grant = match request.binding_id.as_str() {
|
||||
EPIPHANY_RESEARCH_ROLE_BINDING_ID => substrate_gate_repo_access_grant_for_launch(
|
||||
substrate_gate_grant_receipt_id(backend_job_id),
|
||||
backend_job_id.to_string(),
|
||||
request,
|
||||
granted_at,
|
||||
),
|
||||
EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID => substrate_gate_repo_mutation_grant_for_launch(
|
||||
substrate_gate_grant_receipt_id(backend_job_id),
|
||||
backend_job_id.to_string(),
|
||||
request,
|
||||
granted_at,
|
||||
),
|
||||
_ => return Ok(()),
|
||||
};
|
||||
put_substrate_gate_repo_access_grant_receipt(runtime_store_path, &grant).map_err(|err| {
|
||||
EpiphanyBridgeError::Fatal(format!(
|
||||
"failed to persist Substrate Gate access grant for research launch: {err}"
|
||||
"failed to persist Substrate Gate access grant for worker launch: {err}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,16 +25,26 @@ use codex_app_server_protocol::ThreadEpiphanyGraphQueryKind;
|
|||
use codex_app_server_protocol::ThreadEpiphanyInvalidationInput;
|
||||
use codex_app_server_protocol::ThreadEpiphanyInvalidationStatus;
|
||||
use codex_app_server_protocol::ThreadEpiphanyJob;
|
||||
use codex_app_server_protocol::ThreadEpiphanyJobInterruptParams;
|
||||
use codex_app_server_protocol::ThreadEpiphanyJobInterruptResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyJobKind;
|
||||
use codex_app_server_protocol::ThreadEpiphanyJobLaunchParams;
|
||||
use codex_app_server_protocol::ThreadEpiphanyJobLaunchResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyJobStatus;
|
||||
use codex_app_server_protocol::ThreadEpiphanyPressure;
|
||||
use codex_app_server_protocol::ThreadEpiphanyPressureBasis;
|
||||
use codex_app_server_protocol::ThreadEpiphanyPressureLevel;
|
||||
use codex_app_server_protocol::ThreadEpiphanyPressureStatus;
|
||||
use codex_app_server_protocol::ThreadEpiphanyPromoteParams;
|
||||
use codex_app_server_protocol::ThreadEpiphanyPromoteResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientAcceptParams;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientAcceptResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientAction;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientCheckpointStatus;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientDecision;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientFinding;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientLaunchParams;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientLaunchResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientReason;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientResultStatus;
|
||||
use codex_app_server_protocol::ThreadEpiphanyReorientSource;
|
||||
|
|
@ -42,8 +52,12 @@ use codex_app_server_protocol::ThreadEpiphanyReorientStateStatus;
|
|||
use codex_app_server_protocol::ThreadEpiphanyReorientWorkerLaunchDocument;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRetrievalFreshness;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRetrievalFreshnessStatus;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRoleAcceptParams;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRoleAcceptResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRoleFinding;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRoleId;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRoleLaunchParams;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRoleLaunchResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRoleResultStatus;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRoleSelfPersistenceReview;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRoleSelfPersistenceStatus;
|
||||
|
|
@ -52,9 +66,12 @@ use codex_app_server_protocol::ThreadEpiphanyRolesSource;
|
|||
use codex_app_server_protocol::ThreadEpiphanyStateUpdatedField;
|
||||
use codex_app_server_protocol::ThreadEpiphanyStateUpdatedNotification;
|
||||
use codex_app_server_protocol::ThreadEpiphanyStateUpdatedSource;
|
||||
use codex_app_server_protocol::ThreadEpiphanyUpdateParams;
|
||||
use codex_app_server_protocol::ThreadEpiphanyUpdatePatch;
|
||||
use codex_app_server_protocol::ThreadEpiphanyUpdateResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyViewLens;
|
||||
use codex_app_server_protocol::ThreadEpiphanyWorkerLaunchDocument;
|
||||
use epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID;
|
||||
use epiphany_core::EpiphanyContextParams;
|
||||
use epiphany_core::EpiphanyCoordinatorRoleResultStatus;
|
||||
use epiphany_core::EpiphanyCrrcResultStatus;
|
||||
|
|
@ -62,6 +79,7 @@ use epiphany_core::EpiphanyDistillInput;
|
|||
use epiphany_core::EpiphanyGraphQuery;
|
||||
use epiphany_core::EpiphanyGraphQueryDirection;
|
||||
use epiphany_core::EpiphanyGraphQueryKind;
|
||||
use epiphany_core::EpiphanyJobLaunchRequest;
|
||||
use epiphany_core::EpiphanyPressure;
|
||||
use epiphany_core::EpiphanyPressureBasis;
|
||||
use epiphany_core::EpiphanyPressureLevel;
|
||||
|
|
@ -77,13 +95,23 @@ use epiphany_core::EpiphanyRoleWorkerLaunchDocument;
|
|||
use epiphany_core::EpiphanyStateUpdatedField;
|
||||
use epiphany_core::EpiphanyViewLens;
|
||||
use epiphany_core::EpiphanyWorkerLaunchDocument;
|
||||
use epiphany_core::build_epiphany_job_launch_request;
|
||||
use epiphany_core::default_epiphany_view_lenses;
|
||||
use epiphany_core::epiphany_role_binding_id;
|
||||
use epiphany_core::epiphany_view_needs_jobs;
|
||||
use epiphany_core::epiphany_view_needs_pressure;
|
||||
use epiphany_core::epiphany_view_needs_reorientation_inputs;
|
||||
use epiphany_core::epiphany_view_needs_runtime_store;
|
||||
use epiphany_state_model::EpiphanyThreadState;
|
||||
|
||||
use crate::mutation_service::EpiphanyJobInterruptApplied;
|
||||
use crate::mutation_service::EpiphanyJobLaunchApplied;
|
||||
use crate::mutation_service::EpiphanyReorientAcceptApplied;
|
||||
use crate::mutation_service::EpiphanyReorientLaunchApplied;
|
||||
use crate::mutation_service::EpiphanyRoleAcceptApplied;
|
||||
use crate::mutation_service::EpiphanyThreadPromoteApplied;
|
||||
use crate::mutation_service::EpiphanyThreadUpdateApplied;
|
||||
|
||||
pub fn default_core_epiphany_view_lenses() -> Vec<EpiphanyViewLens> {
|
||||
default_epiphany_view_lenses()
|
||||
}
|
||||
|
|
@ -324,6 +352,460 @@ pub fn protocol_state_updated_notification(
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyRoleAcceptPlan {
|
||||
pub thread_id: String,
|
||||
pub protocol_role_id: ThreadEpiphanyRoleId,
|
||||
pub core_role_id: EpiphanyRoleResultRoleId,
|
||||
pub expected_revision: Option<u64>,
|
||||
pub binding_id: String,
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyRoleLaunchPlan {
|
||||
pub thread_id: String,
|
||||
pub protocol_role_id: ThreadEpiphanyRoleId,
|
||||
pub core_role_id: EpiphanyRoleResultRoleId,
|
||||
pub expected_revision: Option<u64>,
|
||||
pub max_runtime_seconds: Option<u64>,
|
||||
}
|
||||
|
||||
pub fn plan_thread_epiphany_role_launch(
|
||||
params: ThreadEpiphanyRoleLaunchParams,
|
||||
) -> ThreadEpiphanyRoleLaunchPlan {
|
||||
let ThreadEpiphanyRoleLaunchParams {
|
||||
thread_id,
|
||||
role_id,
|
||||
expected_revision,
|
||||
max_runtime_seconds,
|
||||
} = params;
|
||||
ThreadEpiphanyRoleLaunchPlan {
|
||||
thread_id,
|
||||
protocol_role_id: role_id,
|
||||
core_role_id: protocol_role_id_to_core(role_id),
|
||||
expected_revision,
|
||||
max_runtime_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyRoleLaunchRouteOutput {
|
||||
pub response: ThreadEpiphanyRoleLaunchResponse,
|
||||
pub changed_fields: Vec<EpiphanyStateUpdatedField>,
|
||||
pub epiphany_state: EpiphanyThreadState,
|
||||
}
|
||||
|
||||
pub fn thread_epiphany_role_launch_output(
|
||||
thread_id: String,
|
||||
role_id: ThreadEpiphanyRoleId,
|
||||
applied: EpiphanyJobLaunchApplied,
|
||||
) -> ThreadEpiphanyRoleLaunchRouteOutput {
|
||||
let changed_fields = applied.changed_fields;
|
||||
let epiphany_state = applied.epiphany_state;
|
||||
let response = ThreadEpiphanyRoleLaunchResponse {
|
||||
thread_id,
|
||||
role_id,
|
||||
revision: applied.revision,
|
||||
changed_fields: protocol_state_updated_fields(changed_fields.clone()),
|
||||
epiphany_state: epiphany_state.clone(),
|
||||
job: protocol_job_from_surface(
|
||||
applied.job,
|
||||
Some(applied.launcher_job_id),
|
||||
Some(applied.backend_job_id),
|
||||
),
|
||||
};
|
||||
ThreadEpiphanyRoleLaunchRouteOutput {
|
||||
response,
|
||||
changed_fields,
|
||||
epiphany_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plan_thread_epiphany_role_accept(
|
||||
params: ThreadEpiphanyRoleAcceptParams,
|
||||
) -> Result<ThreadEpiphanyRoleAcceptPlan, String> {
|
||||
let ThreadEpiphanyRoleAcceptParams {
|
||||
thread_id,
|
||||
role_id,
|
||||
expected_revision,
|
||||
binding_id,
|
||||
} = params;
|
||||
let core_role_id = protocol_role_id_to_core(role_id);
|
||||
let default_binding_id = epiphany_role_binding_id(core_role_id)?;
|
||||
Ok(ThreadEpiphanyRoleAcceptPlan {
|
||||
thread_id,
|
||||
protocol_role_id: role_id,
|
||||
core_role_id,
|
||||
expected_revision,
|
||||
binding_id: binding_id.unwrap_or_else(|| default_binding_id.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyRoleAcceptRouteOutput {
|
||||
pub response: ThreadEpiphanyRoleAcceptResponse,
|
||||
pub changed_fields: Vec<EpiphanyStateUpdatedField>,
|
||||
pub epiphany_state: EpiphanyThreadState,
|
||||
}
|
||||
|
||||
pub fn thread_epiphany_role_accept_output(
|
||||
role_id: ThreadEpiphanyRoleId,
|
||||
binding_id: String,
|
||||
applied: EpiphanyRoleAcceptApplied,
|
||||
) -> ThreadEpiphanyRoleAcceptRouteOutput {
|
||||
let changed_fields = applied.changed_fields;
|
||||
let epiphany_state = applied.epiphany_state;
|
||||
let response = ThreadEpiphanyRoleAcceptResponse {
|
||||
revision: applied.revision,
|
||||
changed_fields: protocol_state_updated_fields(changed_fields.clone()),
|
||||
epiphany_state: epiphany_state.clone(),
|
||||
role_id,
|
||||
binding_id,
|
||||
accepted_receipt_id: applied.accepted_receipt_id,
|
||||
accepted_observation_id: applied.accepted_observation_id,
|
||||
accepted_evidence_id: applied.accepted_evidence_id,
|
||||
applied_patch: protocol_patch_from_core(applied.applied_patch),
|
||||
finding: protocol_role_finding(role_id, applied.finding),
|
||||
};
|
||||
ThreadEpiphanyRoleAcceptRouteOutput {
|
||||
response,
|
||||
changed_fields,
|
||||
epiphany_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyReorientLaunchPlan {
|
||||
pub thread_id: String,
|
||||
pub expected_revision: Option<u64>,
|
||||
pub max_runtime_seconds: Option<u64>,
|
||||
}
|
||||
|
||||
pub fn plan_thread_epiphany_reorient_launch(
|
||||
params: ThreadEpiphanyReorientLaunchParams,
|
||||
) -> ThreadEpiphanyReorientLaunchPlan {
|
||||
let ThreadEpiphanyReorientLaunchParams {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
max_runtime_seconds,
|
||||
} = params;
|
||||
ThreadEpiphanyReorientLaunchPlan {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
max_runtime_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyReorientLaunchRouteOutput {
|
||||
pub response: ThreadEpiphanyReorientLaunchResponse,
|
||||
pub changed_fields: Vec<EpiphanyStateUpdatedField>,
|
||||
pub epiphany_state: EpiphanyThreadState,
|
||||
}
|
||||
|
||||
pub fn thread_epiphany_reorient_launch_output(
|
||||
thread_id: String,
|
||||
applied: EpiphanyReorientLaunchApplied,
|
||||
) -> ThreadEpiphanyReorientLaunchRouteOutput {
|
||||
let changed_fields = applied.changed_fields;
|
||||
let epiphany_state = applied.epiphany_state;
|
||||
let response = ThreadEpiphanyReorientLaunchResponse {
|
||||
thread_id,
|
||||
source: protocol_reorient_source(applied.source),
|
||||
state_status: protocol_reorient_state_status(applied.state_status),
|
||||
state_revision: applied.state_revision,
|
||||
decision: protocol_reorient_decision(applied.decision),
|
||||
revision: applied.revision,
|
||||
changed_fields: protocol_state_updated_fields(changed_fields.clone()),
|
||||
epiphany_state: epiphany_state.clone(),
|
||||
job: protocol_job_from_surface(
|
||||
applied.job,
|
||||
Some(applied.launcher_job_id),
|
||||
Some(applied.backend_job_id),
|
||||
),
|
||||
};
|
||||
ThreadEpiphanyReorientLaunchRouteOutput {
|
||||
response,
|
||||
changed_fields,
|
||||
epiphany_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyReorientAcceptPlan {
|
||||
pub thread_id: String,
|
||||
pub expected_revision: Option<u64>,
|
||||
pub binding_id: String,
|
||||
pub update_scratch: bool,
|
||||
pub update_investigation_checkpoint: bool,
|
||||
}
|
||||
|
||||
pub fn plan_thread_epiphany_reorient_accept(
|
||||
params: ThreadEpiphanyReorientAcceptParams,
|
||||
) -> ThreadEpiphanyReorientAcceptPlan {
|
||||
let ThreadEpiphanyReorientAcceptParams {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
binding_id,
|
||||
update_scratch,
|
||||
update_investigation_checkpoint,
|
||||
} = params;
|
||||
ThreadEpiphanyReorientAcceptPlan {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
binding_id: binding_id.unwrap_or_else(|| EPIPHANY_REORIENT_LAUNCH_BINDING_ID.to_string()),
|
||||
update_scratch,
|
||||
update_investigation_checkpoint,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyReorientAcceptRouteOutput {
|
||||
pub response: ThreadEpiphanyReorientAcceptResponse,
|
||||
pub changed_fields: Vec<EpiphanyStateUpdatedField>,
|
||||
pub epiphany_state: EpiphanyThreadState,
|
||||
}
|
||||
|
||||
pub fn thread_epiphany_reorient_accept_output(
|
||||
binding_id: String,
|
||||
applied: EpiphanyReorientAcceptApplied,
|
||||
) -> ThreadEpiphanyReorientAcceptRouteOutput {
|
||||
let changed_fields = applied.changed_fields;
|
||||
let epiphany_state = applied.epiphany_state;
|
||||
let response = ThreadEpiphanyReorientAcceptResponse {
|
||||
revision: applied.revision,
|
||||
changed_fields: protocol_state_updated_fields(changed_fields.clone()),
|
||||
epiphany_state: epiphany_state.clone(),
|
||||
binding_id,
|
||||
accepted_receipt_id: applied.accepted_receipt_id,
|
||||
accepted_observation_id: applied.accepted_observation_id,
|
||||
accepted_evidence_id: applied.accepted_evidence_id,
|
||||
finding: protocol_reorient_finding(applied.finding),
|
||||
};
|
||||
ThreadEpiphanyReorientAcceptRouteOutput {
|
||||
response,
|
||||
changed_fields,
|
||||
epiphany_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyJobLaunchPlan {
|
||||
pub thread_id: String,
|
||||
pub launch_request: EpiphanyJobLaunchRequest,
|
||||
pub kind: EpiphanyJobKind,
|
||||
}
|
||||
|
||||
pub fn plan_thread_epiphany_job_launch(
|
||||
params: ThreadEpiphanyJobLaunchParams,
|
||||
) -> ThreadEpiphanyJobLaunchPlan {
|
||||
let ThreadEpiphanyJobLaunchParams {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
binding_id,
|
||||
kind,
|
||||
scope,
|
||||
owner_role,
|
||||
authority_scope,
|
||||
linked_subgoal_ids,
|
||||
linked_graph_node_ids,
|
||||
instruction,
|
||||
launch_document,
|
||||
output_contract_id,
|
||||
max_runtime_seconds,
|
||||
} = params;
|
||||
let launch_document = protocol_worker_launch_document_to_core(launch_document);
|
||||
let launch_request = build_epiphany_job_launch_request(
|
||||
expected_revision,
|
||||
binding_id,
|
||||
kind,
|
||||
scope,
|
||||
owner_role,
|
||||
authority_scope,
|
||||
linked_subgoal_ids,
|
||||
linked_graph_node_ids,
|
||||
instruction,
|
||||
launch_document,
|
||||
output_contract_id,
|
||||
max_runtime_seconds,
|
||||
);
|
||||
ThreadEpiphanyJobLaunchPlan {
|
||||
thread_id,
|
||||
launch_request,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyJobLaunchRouteOutput {
|
||||
pub response: ThreadEpiphanyJobLaunchResponse,
|
||||
pub changed_fields: Vec<EpiphanyStateUpdatedField>,
|
||||
pub epiphany_state: EpiphanyThreadState,
|
||||
}
|
||||
|
||||
pub fn thread_epiphany_job_launch_output(
|
||||
applied: EpiphanyJobLaunchApplied,
|
||||
) -> ThreadEpiphanyJobLaunchRouteOutput {
|
||||
let changed_fields = applied.changed_fields;
|
||||
let epiphany_state = applied.epiphany_state;
|
||||
let response = ThreadEpiphanyJobLaunchResponse {
|
||||
revision: applied.revision,
|
||||
changed_fields: protocol_state_updated_fields(changed_fields.clone()),
|
||||
epiphany_state: epiphany_state.clone(),
|
||||
job: protocol_job_from_surface(
|
||||
applied.job,
|
||||
Some(applied.launcher_job_id),
|
||||
Some(applied.backend_job_id),
|
||||
),
|
||||
};
|
||||
ThreadEpiphanyJobLaunchRouteOutput {
|
||||
response,
|
||||
changed_fields,
|
||||
epiphany_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyUpdatePlan {
|
||||
pub thread_id: String,
|
||||
pub expected_revision: Option<u64>,
|
||||
pub patch: EpiphanyRoleStatePatchDocument,
|
||||
}
|
||||
|
||||
pub fn plan_thread_epiphany_update(params: ThreadEpiphanyUpdateParams) -> ThreadEpiphanyUpdatePlan {
|
||||
let ThreadEpiphanyUpdateParams {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
patch,
|
||||
} = params;
|
||||
ThreadEpiphanyUpdatePlan {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
patch: protocol_update_patch_to_core(&patch),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyPromotePlan {
|
||||
pub thread_id: String,
|
||||
pub expected_revision: Option<u64>,
|
||||
pub patch: EpiphanyRoleStatePatchDocument,
|
||||
pub verifier_evidence: epiphany_state_model::EpiphanyEvidenceRecord,
|
||||
}
|
||||
|
||||
pub fn plan_thread_epiphany_promote(
|
||||
params: ThreadEpiphanyPromoteParams,
|
||||
) -> ThreadEpiphanyPromotePlan {
|
||||
let ThreadEpiphanyPromoteParams {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
patch,
|
||||
verifier_evidence,
|
||||
} = params;
|
||||
ThreadEpiphanyPromotePlan {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
patch: protocol_update_patch_to_core(&patch),
|
||||
verifier_evidence,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyUpdateRouteOutput {
|
||||
pub response: ThreadEpiphanyUpdateResponse,
|
||||
pub changed_fields: Vec<EpiphanyStateUpdatedField>,
|
||||
pub epiphany_state: EpiphanyThreadState,
|
||||
}
|
||||
|
||||
pub fn thread_epiphany_update_output(
|
||||
applied: EpiphanyThreadUpdateApplied,
|
||||
) -> ThreadEpiphanyUpdateRouteOutput {
|
||||
let changed_fields = applied.changed_fields;
|
||||
let epiphany_state = applied.epiphany_state;
|
||||
let response = ThreadEpiphanyUpdateResponse {
|
||||
revision: applied.revision,
|
||||
changed_fields: protocol_state_updated_fields(changed_fields.clone()),
|
||||
epiphany_state: epiphany_state.clone(),
|
||||
};
|
||||
ThreadEpiphanyUpdateRouteOutput {
|
||||
response,
|
||||
changed_fields,
|
||||
epiphany_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyPromoteRouteOutput {
|
||||
pub response: ThreadEpiphanyPromoteResponse,
|
||||
pub accepted: Option<ThreadEpiphanyUpdateRouteOutput>,
|
||||
}
|
||||
|
||||
pub fn thread_epiphany_promote_output(
|
||||
applied: EpiphanyThreadPromoteApplied,
|
||||
) -> ThreadEpiphanyPromoteRouteOutput {
|
||||
match applied {
|
||||
EpiphanyThreadPromoteApplied::Rejected { reasons } => ThreadEpiphanyPromoteRouteOutput {
|
||||
response: ThreadEpiphanyPromoteResponse {
|
||||
accepted: false,
|
||||
reasons,
|
||||
revision: None,
|
||||
changed_fields: Vec::new(),
|
||||
epiphany_state: None,
|
||||
},
|
||||
accepted: None,
|
||||
},
|
||||
EpiphanyThreadPromoteApplied::Accepted(applied) => {
|
||||
let accepted = thread_epiphany_update_output(applied);
|
||||
ThreadEpiphanyPromoteRouteOutput {
|
||||
response: ThreadEpiphanyPromoteResponse {
|
||||
accepted: true,
|
||||
reasons: Vec::new(),
|
||||
revision: Some(accepted.response.revision),
|
||||
changed_fields: accepted.response.changed_fields.clone(),
|
||||
epiphany_state: Some(accepted.epiphany_state.clone()),
|
||||
},
|
||||
accepted: Some(accepted),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyJobInterruptPlan {
|
||||
pub thread_id: String,
|
||||
pub expected_revision: Option<u64>,
|
||||
pub binding_id: String,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
pub fn plan_thread_epiphany_job_interrupt(
|
||||
params: ThreadEpiphanyJobInterruptParams,
|
||||
) -> ThreadEpiphanyJobInterruptPlan {
|
||||
let ThreadEpiphanyJobInterruptParams {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
binding_id,
|
||||
reason,
|
||||
} = params;
|
||||
ThreadEpiphanyJobInterruptPlan {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
binding_id,
|
||||
reason,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadEpiphanyJobInterruptRouteOutput {
|
||||
pub response: ThreadEpiphanyJobInterruptResponse,
|
||||
pub changed_fields: Vec<EpiphanyStateUpdatedField>,
|
||||
pub epiphany_state: EpiphanyThreadState,
|
||||
}
|
||||
|
||||
pub fn thread_epiphany_job_interrupt_output(
|
||||
applied: EpiphanyJobInterruptApplied,
|
||||
) -> ThreadEpiphanyJobInterruptRouteOutput {
|
||||
let changed_fields = applied.changed_fields;
|
||||
let epiphany_state = applied.epiphany_state;
|
||||
let response = ThreadEpiphanyJobInterruptResponse {
|
||||
cancel_requested: applied.cancel_requested,
|
||||
interrupted_thread_ids: applied.interrupted_thread_ids,
|
||||
revision: applied.revision,
|
||||
changed_fields: protocol_state_updated_fields(changed_fields.clone()),
|
||||
epiphany_state: epiphany_state.clone(),
|
||||
job: protocol_job_from_surface(applied.job, None, None),
|
||||
};
|
||||
ThreadEpiphanyJobInterruptRouteOutput {
|
||||
response,
|
||||
changed_fields,
|
||||
epiphany_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn protocol_state_updated_fields(
|
||||
fields: Vec<EpiphanyStateUpdatedField>,
|
||||
) -> Vec<ThreadEpiphanyStateUpdatedField> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use codex_app_server_protocol::ThreadEpiphanyIndexResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRetrieveIndexSummary;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRetrieveParams;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRetrieveResponse;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRetrieveResult;
|
||||
use codex_app_server_protocol::ThreadEpiphanyRetrieveResultKind;
|
||||
|
|
@ -9,6 +10,48 @@ use epiphany_core::EpiphanyRetrieveResponse as CoreEpiphanyRetrieveResponse;
|
|||
use epiphany_core::EpiphanyRetrieveResult as CoreEpiphanyRetrieveResult;
|
||||
use epiphany_core::EpiphanyRetrieveResultKind as CoreEpiphanyRetrieveResultKind;
|
||||
use epiphany_state_model::EpiphanyRetrievalState;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::error::EpiphanyBridgeError;
|
||||
use crate::error::Result as BridgeResult;
|
||||
use crate::retrieve::normalize_thread_epiphany_retrieve_query;
|
||||
use crate::retrieve::retrieve_epiphany_for_paths;
|
||||
|
||||
pub async fn retrieve_thread_epiphany(
|
||||
params: ThreadEpiphanyRetrieveParams,
|
||||
workspace_root: PathBuf,
|
||||
codex_home: PathBuf,
|
||||
) -> BridgeResult<ThreadEpiphanyRetrieveResponse> {
|
||||
let ThreadEpiphanyRetrieveParams {
|
||||
thread_id: _,
|
||||
query,
|
||||
limit,
|
||||
path_prefixes,
|
||||
} = params;
|
||||
retrieve_thread_epiphany_for_paths(workspace_root, codex_home, query, limit, path_prefixes)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn retrieve_thread_epiphany_for_paths(
|
||||
workspace_root: PathBuf,
|
||||
codex_home: PathBuf,
|
||||
query: String,
|
||||
limit: Option<u32>,
|
||||
path_prefixes: Vec<PathBuf>,
|
||||
) -> BridgeResult<ThreadEpiphanyRetrieveResponse> {
|
||||
let query = normalize_thread_epiphany_retrieve_query(query, limit, path_prefixes)
|
||||
.map_err(EpiphanyBridgeError::InvalidRequest)?;
|
||||
let response = retrieve_epiphany_for_paths(workspace_root, codex_home, query)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
EpiphanyBridgeError::Fatal(format!("failed to retrieve Epiphany results: {err}"))
|
||||
})?;
|
||||
protocol_retrieve_response(response).map_err(|err| {
|
||||
EpiphanyBridgeError::Fatal(format!(
|
||||
"failed to project Epiphany retrieve response: {err}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn protocol_retrieve_response(
|
||||
response: CoreEpiphanyRetrieveResponse,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use crate::results::render_core_role_result_note;
|
|||
pub struct EpiphanyRoleResultSnapshot {
|
||||
pub status: CoreEpiphanyCoordinatorRoleResultStatus,
|
||||
pub finding: Option<EpiphanyRoleFindingInterpretation>,
|
||||
pub completed_at: Option<String>,
|
||||
pub note: String,
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +49,7 @@ pub fn load_core_epiphany_role_result_from_runtime_spine_job(
|
|||
return EpiphanyRoleResultSnapshot {
|
||||
status: CoreEpiphanyCoordinatorRoleResultStatus::Pending,
|
||||
finding: None,
|
||||
completed_at: None,
|
||||
note: "Heartbeat activation owns this role specialist; no loaded runtime-spine store is available yet."
|
||||
.to_string(),
|
||||
};
|
||||
|
|
@ -58,6 +60,7 @@ pub fn load_core_epiphany_role_result_from_runtime_spine_job(
|
|||
return EpiphanyRoleResultSnapshot {
|
||||
status: CoreEpiphanyCoordinatorRoleResultStatus::Pending,
|
||||
finding: None,
|
||||
completed_at: None,
|
||||
note: format!(
|
||||
"Heartbeat runtime job {:?} has not reported typed state yet.",
|
||||
job_id
|
||||
|
|
@ -68,6 +71,7 @@ pub fn load_core_epiphany_role_result_from_runtime_spine_job(
|
|||
return EpiphanyRoleResultSnapshot {
|
||||
status: CoreEpiphanyCoordinatorRoleResultStatus::BackendUnavailable,
|
||||
finding: None,
|
||||
completed_at: None,
|
||||
note: format!(
|
||||
"Failed to read heartbeat runtime-spine job {:?}: {err}",
|
||||
job_id
|
||||
|
|
@ -84,6 +88,10 @@ pub fn load_core_epiphany_role_result_from_runtime_spine_job(
|
|||
return EpiphanyRoleResultSnapshot {
|
||||
status: CoreEpiphanyCoordinatorRoleResultStatus::BackendUnavailable,
|
||||
finding: None,
|
||||
completed_at: snapshot
|
||||
.result
|
||||
.as_ref()
|
||||
.map(|result| result.completed_at.clone()),
|
||||
note: format!(
|
||||
"Heartbeat runtime job {:?} completed without an EpiphanyRuntimeRoleWorkerResult typed document; generic lifecycle receipts are not reviewable findings.",
|
||||
job_id
|
||||
|
|
@ -94,6 +102,10 @@ pub fn load_core_epiphany_role_result_from_runtime_spine_job(
|
|||
return EpiphanyRoleResultSnapshot {
|
||||
status: CoreEpiphanyCoordinatorRoleResultStatus::BackendUnavailable,
|
||||
finding: None,
|
||||
completed_at: snapshot
|
||||
.result
|
||||
.as_ref()
|
||||
.map(|result| result.completed_at.clone()),
|
||||
note: format!(
|
||||
"Failed to read typed role worker result for heartbeat runtime job {:?}: {err}",
|
||||
job_id
|
||||
|
|
@ -116,6 +128,10 @@ pub fn load_core_epiphany_role_result_from_runtime_spine_job(
|
|||
EpiphanyRoleResultSnapshot {
|
||||
status,
|
||||
finding,
|
||||
completed_at: snapshot
|
||||
.result
|
||||
.as_ref()
|
||||
.map(|result| result.completed_at.clone()),
|
||||
note,
|
||||
}
|
||||
}
|
||||
|
|
@ -310,12 +326,14 @@ pub async fn load_core_epiphany_role_result_snapshot(
|
|||
return EpiphanyRoleResultSnapshot {
|
||||
status: CoreEpiphanyCoordinatorRoleResultStatus::MissingBinding,
|
||||
finding: None,
|
||||
completed_at: None,
|
||||
note: "No matching Epiphany role specialist binding exists.".to_string(),
|
||||
};
|
||||
}
|
||||
EpiphanyRoleResultSnapshot {
|
||||
status: CoreEpiphanyCoordinatorRoleResultStatus::BackendUnavailable,
|
||||
finding: None,
|
||||
completed_at: None,
|
||||
note: "Role binding has no runtime-spine job id; launch a runtime-linked role worker for typed results.".to_string(),
|
||||
}
|
||||
}
|
||||
|
|
@ -658,7 +676,7 @@ mod tests {
|
|||
|
||||
fn temp_store_path() -> PathBuf {
|
||||
std::env::temp_dir().join(format!(
|
||||
"epiphany-runtime-results-{}.msgpack",
|
||||
"epiphany-runtime-results-{}.cc",
|
||||
uuid::Uuid::new_v4()
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub async fn client_visible_epiphany_state_for_paths(
|
|||
}
|
||||
|
||||
pub fn thread_state_store_path(workspace_root: &Path) -> PathBuf {
|
||||
workspace_root.join("state").join("thread-state.msgpack")
|
||||
workspace_root.join("state").join("thread-state.cc")
|
||||
}
|
||||
|
||||
pub fn mirror_thread_state_to_workspace(
|
||||
|
|
|
|||
|
|
@ -35,8 +35,20 @@ use crate::coordinator_protocol::protocol_crrc_recommendation;
|
|||
use crate::coordinator_protocol::protocol_role_board_lanes;
|
||||
use crate::cultnet::EpiphanyFreshnessSurface;
|
||||
use crate::cultnet::EpiphanySurfaceSource;
|
||||
use crate::error::EpiphanyBridgeError;
|
||||
use crate::error::Result as BridgeResult;
|
||||
use crate::jobs::map_epiphany_jobs;
|
||||
use crate::launch::epiphany_role_binding_id;
|
||||
use crate::pressure::derive_epiphany_pressure;
|
||||
use crate::protocol_edge::core_epiphany_view_needs_jobs;
|
||||
use crate::protocol_edge::core_epiphany_view_needs_pressure;
|
||||
use crate::protocol_edge::core_epiphany_view_needs_reorientation_inputs;
|
||||
use crate::protocol_edge::core_epiphany_view_needs_runtime_store;
|
||||
use crate::protocol_edge::default_core_epiphany_view_lenses;
|
||||
use crate::protocol_edge::protocol_context_params_to_core;
|
||||
use crate::protocol_edge::protocol_distill_params_to_core;
|
||||
use crate::protocol_edge::protocol_freshness_response_from_surface;
|
||||
use crate::protocol_edge::protocol_graph_query_to_core;
|
||||
use crate::protocol_edge::protocol_job_from_surface;
|
||||
use crate::protocol_edge::protocol_pressure_from_core;
|
||||
use crate::protocol_edge::protocol_reorient_decision;
|
||||
|
|
@ -46,9 +58,11 @@ use crate::protocol_edge::protocol_reorient_source;
|
|||
use crate::protocol_edge::protocol_reorient_state_status;
|
||||
use crate::protocol_edge::protocol_role_finding;
|
||||
use crate::protocol_edge::protocol_role_id_from_core;
|
||||
use crate::protocol_edge::protocol_role_id_to_core;
|
||||
use crate::protocol_edge::protocol_role_result_status;
|
||||
use crate::protocol_edge::protocol_roles_source;
|
||||
use crate::protocol_edge::protocol_view_lens_from_core;
|
||||
use crate::protocol_edge::protocol_view_lenses_to_core;
|
||||
use crate::reorient::EpiphanyFreshnessWatcherSnapshot;
|
||||
use crate::reorient::derive_epiphany_freshness_view;
|
||||
use crate::reorient::derive_epiphany_reorient;
|
||||
|
|
@ -88,6 +102,12 @@ pub fn derive_epiphany_freshness_surface(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn map_epiphany_freshness_response(
|
||||
input: EpiphanyFreshnessResponseInput<'_>,
|
||||
) -> ThreadEpiphanyFreshnessResponse {
|
||||
protocol_freshness_response_from_surface(derive_epiphany_freshness_surface(input))
|
||||
}
|
||||
|
||||
pub fn map_epiphany_context_response(
|
||||
thread_id: String,
|
||||
loaded: bool,
|
||||
|
|
@ -110,6 +130,16 @@ pub fn map_epiphany_context_response(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn map_thread_epiphany_context_response(
|
||||
params: ThreadEpiphanyContextParams,
|
||||
loaded: bool,
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
) -> ThreadEpiphanyContextResponse {
|
||||
let thread_id = params.thread_id.clone();
|
||||
let core_params = protocol_context_params_to_core(¶ms);
|
||||
map_epiphany_context_response(thread_id, loaded, state, &core_params)
|
||||
}
|
||||
|
||||
pub fn map_epiphany_graph_query_response(
|
||||
thread_id: String,
|
||||
loaded: bool,
|
||||
|
|
@ -135,6 +165,16 @@ pub fn map_epiphany_graph_query_response(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn map_thread_epiphany_graph_query_response(
|
||||
params: ThreadEpiphanyGraphQueryParams,
|
||||
loaded: bool,
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
) -> ThreadEpiphanyGraphQueryResponse {
|
||||
let thread_id = params.thread_id.clone();
|
||||
let core_query = protocol_graph_query_to_core(¶ms.query);
|
||||
map_epiphany_graph_query_response(thread_id, loaded, state, &core_query)
|
||||
}
|
||||
|
||||
pub struct EpiphanyViewResponseInput<'a> {
|
||||
pub thread_id: String,
|
||||
pub lenses: Vec<EpiphanyViewLens>,
|
||||
|
|
@ -146,6 +186,32 @@ pub struct EpiphanyViewResponseInput<'a> {
|
|||
pub runtime_store_path: Option<&'a Path>,
|
||||
}
|
||||
|
||||
pub struct EpiphanyViewRoutePlan {
|
||||
pub thread_id: String,
|
||||
pub lenses: Vec<EpiphanyViewLens>,
|
||||
pub needs_jobs: bool,
|
||||
pub needs_reorientation_inputs: bool,
|
||||
pub needs_pressure: bool,
|
||||
pub needs_runtime_store: bool,
|
||||
}
|
||||
|
||||
pub fn plan_thread_epiphany_view(params: ThreadEpiphanyViewParams) -> EpiphanyViewRoutePlan {
|
||||
let ThreadEpiphanyViewParams { thread_id, lenses } = params;
|
||||
let lenses = if lenses.is_empty() {
|
||||
default_core_epiphany_view_lenses()
|
||||
} else {
|
||||
protocol_view_lenses_to_core(lenses)
|
||||
};
|
||||
EpiphanyViewRoutePlan {
|
||||
thread_id,
|
||||
needs_jobs: core_epiphany_view_needs_jobs(&lenses),
|
||||
needs_reorientation_inputs: core_epiphany_view_needs_reorientation_inputs(&lenses),
|
||||
needs_pressure: core_epiphany_view_needs_pressure(&lenses),
|
||||
needs_runtime_store: core_epiphany_view_needs_runtime_store(&lenses),
|
||||
lenses,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn map_epiphany_view_response(
|
||||
input: EpiphanyViewResponseInput<'_>,
|
||||
) -> ThreadEpiphanyViewResponse {
|
||||
|
|
@ -496,6 +562,38 @@ pub async fn map_epiphany_role_result_response(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn map_thread_epiphany_role_result_response(
|
||||
params: ThreadEpiphanyRoleResultParams,
|
||||
loaded: bool,
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store_path: Option<&Path>,
|
||||
) -> BridgeResult<ThreadEpiphanyRoleResultResponse> {
|
||||
let ThreadEpiphanyRoleResultParams {
|
||||
thread_id,
|
||||
role_id,
|
||||
binding_id,
|
||||
} = params;
|
||||
let role_id = protocol_role_id_to_core(role_id);
|
||||
let default_binding_id =
|
||||
epiphany_role_binding_id(role_id).map_err(EpiphanyBridgeError::InvalidRequest)?;
|
||||
let binding_id = binding_id.unwrap_or_else(|| default_binding_id.to_string());
|
||||
Ok(
|
||||
map_epiphany_role_result_response(EpiphanyRoleResultResponseInput {
|
||||
thread_id,
|
||||
role_id,
|
||||
source: if loaded {
|
||||
EpiphanySurfaceSource::Live
|
||||
} else {
|
||||
EpiphanySurfaceSource::Stored
|
||||
},
|
||||
binding_id,
|
||||
state,
|
||||
runtime_store_path,
|
||||
})
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct EpiphanyReorientResultResponseInput<'a> {
|
||||
pub thread_id: String,
|
||||
pub source: EpiphanySurfaceSource,
|
||||
|
|
@ -554,6 +652,31 @@ pub async fn map_epiphany_reorient_result_response(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn map_thread_epiphany_reorient_result_response(
|
||||
params: ThreadEpiphanyReorientResultParams,
|
||||
loaded: bool,
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store_path: Option<&Path>,
|
||||
) -> ThreadEpiphanyReorientResultResponse {
|
||||
let ThreadEpiphanyReorientResultParams {
|
||||
thread_id,
|
||||
binding_id,
|
||||
} = params;
|
||||
let binding_id = binding_id.unwrap_or_else(|| EPIPHANY_REORIENT_LAUNCH_BINDING_ID.into());
|
||||
map_epiphany_reorient_result_response(EpiphanyReorientResultResponseInput {
|
||||
thread_id,
|
||||
source: if loaded {
|
||||
EpiphanySurfaceSource::Live
|
||||
} else {
|
||||
EpiphanySurfaceSource::Stored
|
||||
},
|
||||
binding_id,
|
||||
state,
|
||||
runtime_store_path,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn map_epiphany_distill_response(
|
||||
expected_revision: u64,
|
||||
input: EpiphanyDistillInput,
|
||||
|
|
@ -570,6 +693,19 @@ pub fn map_epiphany_distill_response(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn map_thread_epiphany_distill_response(
|
||||
params: ThreadEpiphanyDistillParams,
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
) -> BridgeResult<ThreadEpiphanyDistillResponse> {
|
||||
let expected_revision = state.map(|state| state.revision).unwrap_or(0);
|
||||
let input = protocol_distill_params_to_core(params);
|
||||
map_epiphany_distill_response(expected_revision, input).map_err(|err| {
|
||||
EpiphanyBridgeError::InvalidRequest(format!(
|
||||
"failed to distill Epiphany observation: {err}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn map_epiphany_propose_response(
|
||||
state: EpiphanyThreadState,
|
||||
observation_ids: Vec<String>,
|
||||
|
|
@ -593,3 +729,18 @@ pub fn map_epiphany_propose_response(
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn map_thread_epiphany_propose_response(
|
||||
params: ThreadEpiphanyProposeParams,
|
||||
state: Option<EpiphanyThreadState>,
|
||||
) -> BridgeResult<ThreadEpiphanyProposeResponse> {
|
||||
let thread_id = params.thread_id.clone();
|
||||
let Some(state) = state else {
|
||||
return Err(EpiphanyBridgeError::InvalidRequest(format!(
|
||||
"thread has no Epiphany state: {thread_id}"
|
||||
)));
|
||||
};
|
||||
map_epiphany_propose_response(state, params.observation_ids).map_err(|err| {
|
||||
EpiphanyBridgeError::InvalidRequest(format!("failed to propose Epiphany map update: {err}"))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ pub const EPIPHANY_MODELING_ROLE_BINDING_ID: &str = "modeling-checkpoint-worker"
|
|||
pub const EPIPHANY_MODELING_OWNER_ROLE: &str = "epiphany-modeler";
|
||||
pub const EPIPHANY_VERIFICATION_ROLE_BINDING_ID: &str = "verification-review-worker";
|
||||
pub const EPIPHANY_VERIFICATION_OWNER_ROLE: &str = "epiphany-verifier";
|
||||
pub const EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID: &str = "implementation-branch-turn-worker";
|
||||
pub const EPIPHANY_IMPLEMENTATION_OWNER_ROLE: &str = "epiphany-hands";
|
||||
pub const EPIPHANY_REORIENT_LAUNCH_BINDING_ID: &str = "reorient-worker";
|
||||
pub const EPIPHANY_REORIENT_OWNER_ROLE: &str = "epiphany-reorient";
|
||||
|
||||
|
|
@ -36,10 +38,7 @@ pub fn epiphany_role_binding_id(role_id: EpiphanyRoleResultRoleId) -> Result<&'s
|
|||
EpiphanyRoleResultRoleId::Research => Ok(EPIPHANY_RESEARCH_ROLE_BINDING_ID),
|
||||
EpiphanyRoleResultRoleId::Modeling => Ok(EPIPHANY_MODELING_ROLE_BINDING_ID),
|
||||
EpiphanyRoleResultRoleId::Verification => Ok(EPIPHANY_VERIFICATION_ROLE_BINDING_ID),
|
||||
EpiphanyRoleResultRoleId::Implementation => Err(
|
||||
"implementation is owned by the main coding agent; no role specialist launch template exists"
|
||||
.to_string(),
|
||||
),
|
||||
EpiphanyRoleResultRoleId::Implementation => Ok(EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID),
|
||||
EpiphanyRoleResultRoleId::Reorientation => Err(
|
||||
"reorientation uses thread/epiphany/reorientLaunch and thread/epiphany/reorientResult"
|
||||
.to_string(),
|
||||
|
|
@ -53,7 +52,8 @@ pub fn epiphany_role_owner(role_id: EpiphanyRoleResultRoleId) -> Result<&'static
|
|||
EpiphanyRoleResultRoleId::Research => Ok(EPIPHANY_RESEARCH_OWNER_ROLE),
|
||||
EpiphanyRoleResultRoleId::Modeling => Ok(EPIPHANY_MODELING_OWNER_ROLE),
|
||||
EpiphanyRoleResultRoleId::Verification => Ok(EPIPHANY_VERIFICATION_OWNER_ROLE),
|
||||
EpiphanyRoleResultRoleId::Implementation | EpiphanyRoleResultRoleId::Reorientation => {
|
||||
EpiphanyRoleResultRoleId::Implementation => Ok(EPIPHANY_IMPLEMENTATION_OWNER_ROLE),
|
||||
EpiphanyRoleResultRoleId::Reorientation => {
|
||||
Err(epiphany_role_binding_id(role_id).unwrap_err())
|
||||
}
|
||||
}
|
||||
|
|
@ -88,9 +88,10 @@ pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) ->
|
|||
EpiphanyRoleResultRoleId::Verification => {
|
||||
vec!["pass", "needs-review", "needs-evidence", "fail"]
|
||||
}
|
||||
EpiphanyRoleResultRoleId::Implementation | EpiphanyRoleResultRoleId::Reorientation => {
|
||||
vec![]
|
||||
EpiphanyRoleResultRoleId::Implementation => {
|
||||
vec!["commit-created", "needs-proprioception", "blocked"]
|
||||
}
|
||||
EpiphanyRoleResultRoleId::Reorientation => vec![],
|
||||
};
|
||||
let mut properties = serde_json::json!({
|
||||
"roleId": {
|
||||
|
|
@ -131,7 +132,7 @@ pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) ->
|
|||
},
|
||||
"selfPatch": {
|
||||
"type": "object",
|
||||
"description": "Optional bounded request to update this role's persistent organ memory. Work organs may update lane habits, durable lessons, goals, values, or private notes; Persona affect/social state belongs only to Face-style Persona surfaces. It must not contain project truth, code edits, job authority, graph/frontier/checkpoint/planning changes, or objective changes.",
|
||||
"description": "Optional bounded request to update this role's persistent organ memory. Work organs may update lane habits, durable lessons, goals, values, or private notes; Persona affect/social state belongs only to Persona-style Persona surfaces. It must not contain project truth, code edits, job authority, graph/frontier/checkpoint/planning changes, or objective changes.",
|
||||
"required": ["agentId", "reason"],
|
||||
"properties": {
|
||||
"agentId": {
|
||||
|
|
@ -308,6 +309,65 @@ pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) ->
|
|||
}
|
||||
required.push("frontierNodeIds");
|
||||
required.push("statePatch");
|
||||
} else if role_id == EpiphanyRoleResultRoleId::Implementation {
|
||||
if let Some(map) = properties.as_object_mut() {
|
||||
map.insert(
|
||||
"branchName".to_string(),
|
||||
serde_json::json!({
|
||||
"type": "string",
|
||||
"description": "Git branch created or used for this bounded Hands turn."
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"commitSha".to_string(),
|
||||
serde_json::json!({
|
||||
"type": "string",
|
||||
"description": "Commit created by this Hands turn. Empty only when verdict is blocked or needs-proprioception."
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"changedPaths".to_string(),
|
||||
serde_json::json!({
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"commandsRun".to_string(),
|
||||
serde_json::json!({
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"handsReceiptIds".to_string(),
|
||||
serde_json::json!({
|
||||
"type": "array",
|
||||
"description": "Typed Hands/Substrate/Soul/Mind receipt ids produced or cited by this turn.",
|
||||
"items": {"type": "string"}
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"proprioceptionRefreshRequest".to_string(),
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"description": "Required branch-map refresh request for Proprioception after a commit.",
|
||||
"properties": {
|
||||
"required": {"type": "boolean"},
|
||||
"reason": {"type": "string"},
|
||||
"branchName": {"type": "string"},
|
||||
"commitSha": {"type": "string"},
|
||||
"changedPaths": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}),
|
||||
);
|
||||
}
|
||||
required.push("branchName");
|
||||
required.push("changedPaths");
|
||||
}
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
|
|
@ -389,7 +449,7 @@ pub struct EpiphanyRolePromptConfig {
|
|||
#[allow(dead_code)]
|
||||
pub repo_memory: String,
|
||||
#[allow(dead_code)]
|
||||
pub face: String,
|
||||
pub persona: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
|
|
@ -496,7 +556,12 @@ pub fn build_epiphany_role_launch_request_with_dynamic_context(
|
|||
"epiphany.role.verification",
|
||||
build_epiphany_role_launch_instruction(role_id),
|
||||
),
|
||||
EpiphanyRoleResultRoleId::Implementation | EpiphanyRoleResultRoleId::Reorientation => {
|
||||
EpiphanyRoleResultRoleId::Implementation => (
|
||||
"role-scoped implementation branch turn",
|
||||
"epiphany.role.implementation",
|
||||
build_epiphany_role_launch_instruction(role_id),
|
||||
),
|
||||
EpiphanyRoleResultRoleId::Reorientation => {
|
||||
return Err(epiphany_role_binding_id(role_id).unwrap_err());
|
||||
}
|
||||
};
|
||||
|
|
@ -550,15 +615,17 @@ pub fn build_epiphany_role_launch_request_with_dynamic_context(
|
|||
}
|
||||
|
||||
fn build_epiphany_role_launch_instruction(role_id: EpiphanyRoleResultRoleId) -> String {
|
||||
let prompts = &epiphany_specialist_prompt_config().roles;
|
||||
let config = epiphany_specialist_prompt_config();
|
||||
let prompts = &config.roles;
|
||||
let body = match role_id {
|
||||
EpiphanyRoleResultRoleId::Imagination => prompts.imagination.as_str(),
|
||||
EpiphanyRoleResultRoleId::Research => prompts.research.as_str(),
|
||||
EpiphanyRoleResultRoleId::Modeling => prompts.modeling.as_str(),
|
||||
EpiphanyRoleResultRoleId::Verification => prompts.verification.as_str(),
|
||||
EpiphanyRoleResultRoleId::Implementation | EpiphanyRoleResultRoleId::Reorientation => {
|
||||
"Unsupported Epiphany role specialist template."
|
||||
EpiphanyRoleResultRoleId::Implementation => {
|
||||
config.implementation.continue_template.as_str()
|
||||
}
|
||||
EpiphanyRoleResultRoleId::Reorientation => "Unsupported Epiphany role specialist template.",
|
||||
};
|
||||
epiphany_worker_prompt(body)
|
||||
}
|
||||
|
|
@ -772,7 +839,7 @@ mod tests {
|
|||
prompts.roles.repo_personality.as_str(),
|
||||
),
|
||||
("roles.repo_memory", prompts.roles.repo_memory.as_str()),
|
||||
("roles.face", prompts.roles.face.as_str()),
|
||||
("roles.persona", prompts.roles.persona.as_str()),
|
||||
(
|
||||
"implementation.continue_template",
|
||||
prompts.implementation.continue_template.as_str(),
|
||||
|
|
@ -821,4 +888,57 @@ mod tests {
|
|||
assert!(!prompt.contains("## Epiphany Persistent Memory"));
|
||||
assert!(!prompt.contains("Heartbeat: every lane"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implementation_role_launches_as_hands_branch_turn() {
|
||||
let state = EpiphanyThreadState {
|
||||
revision: 12,
|
||||
objective: Some("Wire Hands branch-turn launches.".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let request = build_epiphany_role_launch_request_with_dynamic_context(
|
||||
"thread-1",
|
||||
EpiphanyRoleResultRoleId::Implementation,
|
||||
Some(12),
|
||||
Some(300),
|
||||
&state,
|
||||
Some("Proprioception context: branch map is current.".to_string()),
|
||||
)
|
||||
.expect("implementation launch request");
|
||||
|
||||
assert_eq!(request.binding_id, EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID);
|
||||
assert_eq!(request.owner_role, EPIPHANY_IMPLEMENTATION_OWNER_ROLE);
|
||||
assert_eq!(request.authority_scope, "epiphany.role.implementation");
|
||||
assert!(request.scope.contains("implementation branch turn"));
|
||||
assert!(request.instruction.contains("one commit"));
|
||||
assert!(request.instruction.contains("Proprioception"));
|
||||
match request.launch_document {
|
||||
EpiphanyWorkerLaunchDocument::Role(document) => {
|
||||
assert_eq!(document.role_id, "implementation");
|
||||
assert_eq!(
|
||||
document.dynamic_prompt_context.as_deref(),
|
||||
Some("Proprioception context: branch map is current.")
|
||||
);
|
||||
}
|
||||
EpiphanyWorkerLaunchDocument::Reorient(_) => panic!("expected role launch document"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implementation_output_schema_requires_branch_turn_evidence() {
|
||||
let schema = epiphany_role_launch_output_schema(EpiphanyRoleResultRoleId::Implementation);
|
||||
let required = schema
|
||||
.get("required")
|
||||
.and_then(|required| required.as_array())
|
||||
.expect("required array");
|
||||
let required_values = required
|
||||
.iter()
|
||||
.filter_map(|value| value.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(required_values.contains(&"branchName"));
|
||||
assert!(required_values.contains(&"changedPaths"));
|
||||
assert!(schema.to_string().contains("commitSha"));
|
||||
assert!(schema.to_string().contains("proprioceptionRefreshRequest"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const ROLE_TARGETS: &[(&str, &str, &str)] = &[
|
|||
("verification", "epiphany.soul", "soul.agent-state.json"),
|
||||
("implementation", "epiphany.hands", "hands.agent-state.json"),
|
||||
("research", "epiphany.eyes", "eyes.agent-state.json"),
|
||||
("face", "epiphany.face", "face.agent-state.json"),
|
||||
("persona", "epiphany.persona", "persona.agent-state.json"),
|
||||
("coordinator", "epiphany.self", "self.agent-state.json"),
|
||||
];
|
||||
|
||||
|
|
@ -836,9 +836,9 @@ fn persona_value(value: &GhostlightValue) -> Value {
|
|||
"label": value.label,
|
||||
"priority": value.priority,
|
||||
"summary": if value.unforgivable_if_betrayed {
|
||||
"Protected value; betrayal is marked as unforgivable in local Face state."
|
||||
"Protected value; betrayal is marked as unforgivable in local Persona state."
|
||||
} else {
|
||||
"Value projected from local Face organ memory."
|
||||
"Value projected from local Persona organ memory."
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -858,13 +858,13 @@ fn persona_memory_thought(memory: &GhostlightMemory, persona_id: &str, timestamp
|
|||
"target": persona_target("self", persona_id, persona_id),
|
||||
"summary": memory.summary,
|
||||
"claim": memory.summary,
|
||||
"tension": "Projected from local Face memory; consumers should preserve provenance and avoid treating this as omniscient project truth.",
|
||||
"tension": "Projected from local Persona memory; consumers should preserve provenance and avoid treating this as omniscient project truth.",
|
||||
"actionImplication": "Use as Persona memory pressure, not as direct action authority.",
|
||||
"intensity": memory.salience,
|
||||
"valence": 0,
|
||||
"createdAt": timestamp,
|
||||
"updatedAt": timestamp,
|
||||
"tags": ["epiphany", "face-memory"],
|
||||
"tags": ["epiphany", "persona-memory"],
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -876,11 +876,11 @@ fn persona_goal_thought(goal: &GhostlightGoal, persona_id: &str, timestamp: &str
|
|||
"summary": goal.description,
|
||||
"claim": goal.description,
|
||||
"tension": goal.emotional_stake,
|
||||
"actionImplication": "This goal may create Face agency pressure, but action still requires the caller's review path.",
|
||||
"actionImplication": "This goal may create Persona agency pressure, but action still requires the caller's review path.",
|
||||
"intensity": goal.priority,
|
||||
"createdAt": timestamp,
|
||||
"updatedAt": timestamp,
|
||||
"tags": ["epiphany", "face-goal", &goal.scope],
|
||||
"tags": ["epiphany", "persona-goal", &goal.scope],
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -914,7 +914,7 @@ fn persona_doctrine_stance(value: &GhostlightValue, persona_id: &str, timestamp:
|
|||
"stanceKind": "aligned",
|
||||
"principle": value.label,
|
||||
"summary": value.label,
|
||||
"actionImplication": "Let this value bend Face speech and interpretation without granting automatic action authority.",
|
||||
"actionImplication": "Let this value bend Persona speech and interpretation without granting automatic action authority.",
|
||||
"intensity": value.priority,
|
||||
"updatedAt": timestamp,
|
||||
})
|
||||
|
|
@ -936,7 +936,7 @@ fn now_rfc3339() -> String {
|
|||
|
||||
pub fn organ_state_profile_for_role(role_id: &str) -> EpiphanyOrganStateProfile {
|
||||
match role_id {
|
||||
"face" => EpiphanyOrganStateProfile {
|
||||
"persona" => EpiphanyOrganStateProfile {
|
||||
profile_kind: EpiphanyOrganStateProfileKind::Persona,
|
||||
state_density: "persona_grade".to_string(),
|
||||
portable_contract: "gamecult.persona_state.v0".to_string(),
|
||||
|
|
@ -952,7 +952,7 @@ pub fn organ_state_profile_for_role(role_id: &str) -> EpiphanyOrganStateProfile
|
|||
"sleep/distillation".to_string(),
|
||||
],
|
||||
notes: vec![
|
||||
"Epiphany Face is an organ; Persona is the portable person-state contract shared with Ghostlight and VoidBot-style repo Faces.".to_string(),
|
||||
"Epiphany Persona is an organ; Persona is the portable person-state contract shared with Ghostlight and VoidBot-style repo Personas.".to_string(),
|
||||
"Dense canonical families, affect, perceived overlays, and relationship pressure are appropriate for Persona state.".to_string(),
|
||||
],
|
||||
},
|
||||
|
|
@ -970,7 +970,7 @@ pub fn organ_state_profile_for_role(role_id: &str) -> EpiphanyOrganStateProfile
|
|||
"birth-time repo memory and light operating-pressure seeding".to_string(),
|
||||
],
|
||||
notes: vec![
|
||||
"Work organs need sharp role identity, durable mission memory, values/goals, and heartbeat activation; they do not need Face affect or full Persona machinery.".to_string(),
|
||||
"Work organs need sharp role identity, durable mission memory, values/goals, and heartbeat activation; they do not need Persona affect or full Persona machinery.".to_string(),
|
||||
"Sparse canonical bundles are acceptable here as long as memory, goals, values, and private notes can deepen over time.".to_string(),
|
||||
],
|
||||
},
|
||||
|
|
@ -1676,7 +1676,7 @@ mod tests {
|
|||
sample_agent_json(agent_id, &format!("Agent {role_id}")),
|
||||
)?;
|
||||
}
|
||||
let store = temp.path().join("agents.msgpack");
|
||||
let store = temp.path().join("agents.cc");
|
||||
migrate_agent_memory_json_dir_to_cultcache(&agent_dir, &store)?;
|
||||
assert!(validate_agent_memory_store(&store)?.is_empty());
|
||||
|
||||
|
|
@ -1721,7 +1721,7 @@ mod tests {
|
|||
sample_agent_json(agent_id, &format!("Agent {role_id}")),
|
||||
)?;
|
||||
}
|
||||
let store = temp.path().join("agents.msgpack");
|
||||
let store = temp.path().join("agents.cc");
|
||||
migrate_agent_memory_json_dir_to_cultcache(&agent_dir, &store)?;
|
||||
|
||||
let applied = apply_agent_canonical_trait_seeds(
|
||||
|
|
@ -1761,12 +1761,12 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn organ_state_profiles_distinguish_face_from_lane_organs() {
|
||||
let face = organ_state_profile_for_role("face");
|
||||
assert_eq!(face.profile_kind, EpiphanyOrganStateProfileKind::Persona);
|
||||
assert_eq!(face.state_density, "persona_grade");
|
||||
assert_eq!(face.portable_contract, "gamecult.persona_state.v0");
|
||||
assert_eq!(face.affect_model, "persona_affect_allowed_and_expected");
|
||||
fn organ_state_profiles_distinguish_persona_from_lane_organs() {
|
||||
let persona = organ_state_profile_for_role("persona");
|
||||
assert_eq!(persona.profile_kind, EpiphanyOrganStateProfileKind::Persona);
|
||||
assert_eq!(persona.state_density, "persona_grade");
|
||||
assert_eq!(persona.portable_contract, "gamecult.persona_state.v0");
|
||||
assert_eq!(persona.affect_model, "persona_affect_allowed_and_expected");
|
||||
|
||||
let hands = organ_state_profile_for_role("implementation");
|
||||
assert_eq!(hands.profile_kind, EpiphanyOrganStateProfileKind::WorkOrgan);
|
||||
|
|
|
|||
|
|
@ -1227,13 +1227,13 @@ mod tests {
|
|||
}
|
||||
let entry = EpiphanyAgentMemoryEntry {
|
||||
schema_version: "ghostlight.agent_state.v0".to_string(),
|
||||
role_id: "face".to_string(),
|
||||
role_id: "persona".to_string(),
|
||||
world: GhostlightWorld::default(),
|
||||
agent: GhostlightAgent {
|
||||
agent_id: "epiphany.face".to_string(),
|
||||
agent_id: "epiphany.persona".to_string(),
|
||||
identity: GhostlightIdentity {
|
||||
name: "Epiphany".to_string(),
|
||||
roles: vec!["Face".to_string()],
|
||||
roles: vec!["Persona".to_string()],
|
||||
origin: "Epiphany local harness".to_string(),
|
||||
public_description: "Cute, pushy public machine-spirit.".to_string(),
|
||||
private_notes: vec!["must not leak".to_string()],
|
||||
|
|
@ -1267,8 +1267,8 @@ mod tests {
|
|||
scenes: Vec::new(),
|
||||
};
|
||||
let participant = HeartbeatParticipant {
|
||||
agent_id: "epiphany.face".to_string(),
|
||||
role_id: "face".to_string(),
|
||||
agent_id: "epiphany.persona".to_string(),
|
||||
role_id: "persona".to_string(),
|
||||
display_name: "Epiphany".to_string(),
|
||||
initiative_speed: 1.4,
|
||||
reaction_bias: 0.7,
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ fn main() -> Result<()> {
|
|||
}
|
||||
"smoke" => {
|
||||
let store = optional_path_arg(&mut args, "--store")?
|
||||
.unwrap_or_else(|| PathBuf::from("state/agents.msgpack"));
|
||||
.unwrap_or_else(|| PathBuf::from("state/agents.cc"));
|
||||
let result = run_smoke(&store)?;
|
||||
let ok = result["ok"].as_bool().unwrap_or(false);
|
||||
print_json(&result)?;
|
||||
|
|
@ -175,7 +175,7 @@ fn run_smoke(store: &Path) -> Result<serde_json::Value> {
|
|||
let forbidden = review_agent_self_patch_document("modeling", &forbidden_patch, store);
|
||||
|
||||
let temp_dir = scoped_temp_dir("epiphany-agent-memory-smoke")?;
|
||||
let temp_store = temp_dir.join("agents.msgpack");
|
||||
let temp_store = temp_dir.join("agents.cc");
|
||||
fs::copy(store, &temp_store).with_context(|| {
|
||||
format!(
|
||||
"failed to copy {} to {}",
|
||||
|
|
@ -192,7 +192,7 @@ fn run_smoke(store: &Path) -> Result<serde_json::Value> {
|
|||
&& forbidden.status == "rejected"
|
||||
&& applied.status == "accepted"
|
||||
&& temp_validation_errors.is_empty()
|
||||
&& project_persona_state_for_role(store, "face")?["schemaVersion"]
|
||||
&& project_persona_state_for_role(store, "persona")?["schemaVersion"]
|
||||
== epiphany_core::PERSONA_STATE_SCHEMA_VERSION;
|
||||
Ok(serde_json::json!({
|
||||
"ok": ok,
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ fn main() -> Result<()> {
|
|||
let Some(command) = args.next() else {
|
||||
return usage();
|
||||
};
|
||||
let mut role = "face".to_string();
|
||||
let mut agent_store = PathBuf::from("state/agents.msgpack");
|
||||
let mut role = "persona".to_string();
|
||||
let mut agent_store = PathBuf::from("state/agents.cc");
|
||||
let mut artifact_dir = PathBuf::from(".epiphany-character-loop");
|
||||
let mut stimulus: Option<String> = None;
|
||||
let mut source = "epiphany/character-loop".to_string();
|
||||
|
|
@ -231,7 +231,7 @@ fn character_turn_packet(
|
|||
}
|
||||
},
|
||||
"guardrails": [
|
||||
"Humans talk to Face; other organs expose internals through Aquarium and typed artifacts rather than becoming direct chats.",
|
||||
"Humans talk to Persona; other organs expose internals through Aquarium and typed artifacts rather than becoming direct chats.",
|
||||
"Use character-local projection: the actor receives its own organ or Persona state and visible stimulus, not omniscient hidden state.",
|
||||
"Run both cognition lanes before choosing an output: analytic keeps the promise honest, associative keeps the living signal from going flat.",
|
||||
"Appraise the thought cluster through this role's personality before reacting; do not use a global mood knob as a substitute for participant-local appraisal.",
|
||||
|
|
@ -429,11 +429,11 @@ fn deterministic_appraisal_seed(
|
|||
let label = interpretation_label("draft", arousal, guardedness, curiosity);
|
||||
HeartbeatAgentThoughtAppraisal {
|
||||
schema_version: "epiphany.agent_thought_appraisal.v0".to_string(),
|
||||
appraisal_id: format!("face-appraisal-{}", short_id()),
|
||||
appraisal_id: format!("persona-appraisal-{}", short_id()),
|
||||
review_status: "generated_unreviewed".to_string(),
|
||||
participant_agent_id: entry.agent.agent_id.clone(),
|
||||
role_id: entry.role_id.clone(),
|
||||
current_character_state_ref: format!("state/agents.msgpack#{}", entry.role_id),
|
||||
current_character_state_ref: format!("state/agents.cc#{}", entry.role_id),
|
||||
thought_cluster_ref: format!("{}#stimulus", source),
|
||||
participant_local_context: HeartbeatParticipantLocalContext {
|
||||
display_name: entry.agent.identity.name.clone(),
|
||||
|
|
@ -465,13 +465,13 @@ fn deterministic_appraisal_seed(
|
|||
thought_pressure: round3(thought_pressure),
|
||||
},
|
||||
interpretation_label: label.clone(),
|
||||
confidence_notes: "Deterministic Face-local appraisal rebuilt from the same Ghostlight-style personality projection logic used by heartbeat. Useful for reaction shape, still reviewable and fallible.".to_string(),
|
||||
confidence_notes: "Deterministic Persona-local appraisal rebuilt from the same Ghostlight-style personality projection logic used by heartbeat. Useful for reaction shape, still reviewable and fallible.".to_string(),
|
||||
candidate_implications: HeartbeatCandidateImplications {
|
||||
reaction_mode: reaction_mode(&label, "draft").to_string(),
|
||||
reaction_intensity: round3(
|
||||
(urgency * 0.55 + arousal * 0.3 + curiosity * 0.15).clamp(0.0, 1.0),
|
||||
),
|
||||
should_speak: entry.role_id == "face" && guardedness < 0.78,
|
||||
should_speak: entry.role_id == "persona" && guardedness < 0.78,
|
||||
should_incubate: guardedness >= 0.55,
|
||||
},
|
||||
review: HeartbeatAppraisalReview {
|
||||
|
|
@ -503,7 +503,7 @@ fn deterministic_reaction_seed(
|
|||
mood_label: mood_label(arousal, guardedness, curiosity).to_string(),
|
||||
intensity: round3(intensity),
|
||||
bridge_decision: "draft".to_string(),
|
||||
surface: if role_id == "face" {
|
||||
surface: if role_id == "persona" {
|
||||
"aquarium"
|
||||
} else {
|
||||
"internal"
|
||||
|
|
@ -743,7 +743,9 @@ fn mood_label(arousal: f64, guardedness: f64, curiosity: f64) -> &'static str {
|
|||
|
||||
fn reaction_recommended_use(role_id: &str, mode: &str) -> &'static str {
|
||||
match (role_id, mode) {
|
||||
("face", "draft") => "Prepare a reviewed Aquarium-facing draft; do not post automatically.",
|
||||
("persona", "draft") => {
|
||||
"Prepare a reviewed Aquarium-facing draft; do not post automatically."
|
||||
}
|
||||
(_, "hold_and_verify") => "Bias toward verifier/modeler review before expression.",
|
||||
(_, "inspect") => {
|
||||
"Bias the next heartbeat toward a bounded retrieval or modeling inspection."
|
||||
|
|
@ -761,10 +763,10 @@ fn run_smoke() -> Result<Value> {
|
|||
let temp_dir = scoped_temp_dir("epiphany-character-loop-smoke")?;
|
||||
let artifact_dir = temp_dir.join("artifacts");
|
||||
let result = run_turn(
|
||||
"face",
|
||||
Path::new("state/agents.msgpack"),
|
||||
"persona",
|
||||
Path::new("state/agents.cc"),
|
||||
&artifact_dir,
|
||||
"Aquarium hover selected Face and asked what the swarm is feeling.",
|
||||
"Aquarium hover selected Persona and asked what the swarm is feeling.",
|
||||
"smoke/character-loop",
|
||||
"public-surface",
|
||||
"ready",
|
||||
|
|
@ -781,7 +783,7 @@ fn run_smoke() -> Result<Value> {
|
|||
)?;
|
||||
let ok = packet["schema_version"] == CHARACTER_TURN_SCHEMA_VERSION
|
||||
&& packet["protocol"]["bundle"] == "epiphany.character_loop"
|
||||
&& packet["protocol"]["roleId"] == "face"
|
||||
&& packet["protocol"]["roleId"] == "persona"
|
||||
&& packet["protocol"]["organStateProfile"]["profileKind"] == "persona"
|
||||
&& packet["projectedLocalContext"]["organStateProfile"]["profileKind"] == "persona"
|
||||
&& packet["utteranceState"]["schemaVersion"] == "epiphany.agent_utterance_state.v0"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ fn main() -> Result<()> {
|
|||
let mut store = PathBuf::from(".epiphany-smoke");
|
||||
store.push("cultmesh");
|
||||
std::fs::create_dir_all(&store)?;
|
||||
store.push("epiphany-local.ccmp");
|
||||
store.push("epiphany-local.cc");
|
||||
|
||||
let status = EpiphanyCultMeshStatusEntry {
|
||||
schema_version: EPIPHANY_CULTMESH_STATUS_SCHEMA_VERSION.to_string(),
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ impl Args {
|
|||
fn parse() -> Result<Self> {
|
||||
let mut values = env::args().skip(1);
|
||||
let command = values.next().unwrap_or_else(|| "read".to_string());
|
||||
let mut store = PathBuf::from(".epiphany-run/cultmesh/operator-status.ccmp");
|
||||
let mut store = PathBuf::from(".epiphany-run/cultmesh/operator-status.cc");
|
||||
let mut runtime_id = "epiphany-local".to_string();
|
||||
|
||||
while let Some(arg) = values.next() {
|
||||
|
|
|
|||
741
epiphany-core/src/bin/epiphany-eve-surface.rs
Normal file
741
epiphany-core/src/bin/epiphany-eve-surface.rs
Normal file
|
|
@ -0,0 +1,741 @@
|
|||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use chrono::Utc;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_CONTINUITY_CONTRACT_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_EYES_CONTRACT_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_GLOBAL_ROOM_POLICY_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_GLOBAL_VERSE_ID;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_HANDS_CONTRACT_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_INTERNAL_VERSE_ID;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_MIND_CONTRACT_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_OPERATOR_RUN_INTENT_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_OPERATOR_RUN_RECEIPT_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_OPERATOR_SNAPSHOT_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_OPERATOR_STATUS_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_SOUL_CONTRACT_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_SUBSTRATE_GATE_CONTRACT_SCHEMA_VERSION;
|
||||
use epiphany_core::EPIPHANY_CULTMESH_VERSE_POLICY_SCHEMA_VERSION;
|
||||
use epiphany_core::EpiphanyLocalVerseContext;
|
||||
use epiphany_core::GJALLAR_AFFORDANCE_SCHEMA_VERSION;
|
||||
use epiphany_core::query_epiphany_local_verse_context;
|
||||
use epiphany_core::seed_epiphany_local_verse_context;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const EVE_PROVIDER_ADVERTISEMENT_SCHEMA: &str = "gamecult.eve.provider_advertisement.v1";
|
||||
const EVE_SURFACE_SCHEMA: &str = "gamecult.eve.surface.v1";
|
||||
const EVE_COMMAND_SCHEMA: &str = "gamecult.eve.command.v1";
|
||||
const PROVIDER_ID: &str = "epiphany";
|
||||
const SERVICE_ID: &str = "epiphany.agent";
|
||||
const SURFACE_ID: &str = "epiphany.operator.surface";
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse()?;
|
||||
match args.command.as_str() {
|
||||
"advertisement" => {
|
||||
let context = load_context(&args.store, &args.runtime_id)?;
|
||||
print_json(provider_advertisement(&args, context.as_ref()))?;
|
||||
}
|
||||
"surface" => {
|
||||
let context = load_context(&args.store, &args.runtime_id)?;
|
||||
print_json(surface_document(&args, context.as_ref()))?;
|
||||
}
|
||||
"bundle" => {
|
||||
let context = load_context(&args.store, &args.runtime_id)?;
|
||||
print_json(json!({
|
||||
"schema": "epiphany.eve_surface_export.v0",
|
||||
"providerAdvertisement": provider_advertisement(&args, context.as_ref()),
|
||||
"surface": surface_document(&args, context.as_ref()),
|
||||
}))?;
|
||||
}
|
||||
"smoke" => run_smoke(args)?,
|
||||
other => {
|
||||
anyhow::bail!("unknown command {other:?}; use advertisement, surface, bundle, or smoke")
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Args {
|
||||
command: String,
|
||||
store: PathBuf,
|
||||
runtime_id: String,
|
||||
updated_at: String,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn parse() -> Result<Self> {
|
||||
let mut values = env::args().skip(1);
|
||||
let command = values.next().unwrap_or_else(|| "bundle".to_string());
|
||||
let mut store = PathBuf::from(".epiphany-run/cultmesh/epiphany-local.cc");
|
||||
let mut runtime_id = "epiphany-local".to_string();
|
||||
let mut updated_at = Utc::now().to_rfc3339();
|
||||
|
||||
while let Some(arg) = values.next() {
|
||||
match arg.as_str() {
|
||||
"--store" => {
|
||||
store = PathBuf::from(values.next().context("missing --store value")?);
|
||||
}
|
||||
"--runtime-id" => {
|
||||
runtime_id = values.next().context("missing --runtime-id value")?;
|
||||
}
|
||||
"--updated-at" => {
|
||||
updated_at = values.next().context("missing --updated-at value")?;
|
||||
}
|
||||
_ => anyhow::bail!("unknown argument {arg:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
command,
|
||||
store,
|
||||
runtime_id,
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn load_context(store: &Path, runtime_id: &str) -> Result<Option<EpiphanyLocalVerseContext>> {
|
||||
if !store.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
query_epiphany_local_verse_context(store, runtime_id.to_string()).map(Some)
|
||||
}
|
||||
|
||||
fn provider_advertisement(args: &Args, context: Option<&EpiphanyLocalVerseContext>) -> Value {
|
||||
let state = if context.is_some() {
|
||||
"fresh"
|
||||
} else {
|
||||
"degraded"
|
||||
};
|
||||
json!({
|
||||
"schema": EVE_PROVIDER_ADVERTISEMENT_SCHEMA,
|
||||
"providerId": PROVIDER_ID,
|
||||
"serviceId": SERVICE_ID,
|
||||
"verseId": EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID,
|
||||
"title": "Epiphany Operator",
|
||||
"kind": "service.operator",
|
||||
"updatedAt": args.updated_at,
|
||||
"freshness": {
|
||||
"state": state,
|
||||
"lastSeenAt": context
|
||||
.and_then(|context| context.operator_status.as_ref())
|
||||
.map(|status| status.generated_at_utc.as_str())
|
||||
.unwrap_or(args.updated_at.as_str()),
|
||||
"maxAgeMs": 15000,
|
||||
"note": if context.is_some() {
|
||||
"Advertisement is derived from typed Epiphany CultMesh state."
|
||||
} else {
|
||||
"CultMesh store was not found; advertisement lists intended read-only provider boundaries."
|
||||
}
|
||||
},
|
||||
"schemas": schema_catalog(context),
|
||||
"witnesses": witnesses(args, context),
|
||||
"surfaces": [{
|
||||
"surfaceId": SURFACE_ID,
|
||||
"schema": EVE_SURFACE_SCHEMA,
|
||||
"transport": "cultmesh-document",
|
||||
"key": "cultmesh://epiphany/operator/surface",
|
||||
"audience": "operator",
|
||||
"mode": "read-only",
|
||||
"styleProfile": "epiphany.operator",
|
||||
"commands": command_names(),
|
||||
"canonical": true
|
||||
}],
|
||||
"commands": commands(),
|
||||
"nestedVerses": nested_verses(context),
|
||||
"styleCapabilities": [{
|
||||
"styleProfile": "epiphany.operator",
|
||||
"tokenGroups": ["epiphany.authority", "epiphany.freshness", "epiphany.faculty"],
|
||||
"preferredLowerings": ["tui", "browser", "direct2d", "native-eve"],
|
||||
"lossiness": {
|
||||
"tui": "read-only dense operator panels; no graphical relationship map",
|
||||
"browser": "reference lowering until native Eve provider publication lands"
|
||||
}
|
||||
}],
|
||||
"contacts": [{
|
||||
"id": "huginn",
|
||||
"role": "Persona and .cc inspection runtime steward",
|
||||
"boundary": "Coordinates schema migration, projection health, CultMesh publication, and Eve DSL inspection; does not own Epiphany runtime truth."
|
||||
}],
|
||||
"stewardship": {
|
||||
"persona": {
|
||||
"schema": "gamecult.persona_state.v0",
|
||||
"owner": "public Persona or Persona projection",
|
||||
"runtimeSteward": "Huginn",
|
||||
"boundary": "Private work-organ state remains epiphany.work_organ_state.v0 or organ-specific typed state; Persona is not a dumping ground for runtime authority."
|
||||
},
|
||||
"jsonCompatibility": ".epiphany-run JSON is a compatibility witness only, never the owner of accepted Epiphany state."
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn surface_document(args: &Args, context: Option<&EpiphanyLocalVerseContext>) -> Value {
|
||||
json!({
|
||||
"type": "surface-state",
|
||||
"schema": EVE_SURFACE_SCHEMA,
|
||||
"providerId": PROVIDER_ID,
|
||||
"providerKind": "service.operator",
|
||||
"title": "Epiphany Operator Surface",
|
||||
"version": 1,
|
||||
"updatedAt": args.updated_at,
|
||||
"surface": {
|
||||
"root": {
|
||||
"id": "epiphany.operator.root",
|
||||
"kind": "surface",
|
||||
"props": {
|
||||
"authority": "provider-owned-read-only",
|
||||
"runtimeId": args.runtime_id,
|
||||
"verseId": EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID
|
||||
},
|
||||
"children": [
|
||||
operator_status_panel(context),
|
||||
local_verse_panel(context),
|
||||
daemon_affordance_panel(context),
|
||||
runtime_spine_panel(context),
|
||||
memory_graph_panel(context),
|
||||
persona_panel()
|
||||
]
|
||||
},
|
||||
"styles": {
|
||||
"tokens": {
|
||||
"epiphany.authority.accepted": "#8fbf9f",
|
||||
"epiphany.authority.witness": "#c8a24d",
|
||||
"epiphany.authority.denied": "#bf6b6b"
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": commands()
|
||||
})
|
||||
}
|
||||
|
||||
fn operator_status_panel(context: Option<&EpiphanyLocalVerseContext>) -> Value {
|
||||
let status = context.and_then(|context| context.operator_status.as_ref());
|
||||
json!({
|
||||
"id": "epiphany.operator.status",
|
||||
"kind": "inspector.kv",
|
||||
"props": {
|
||||
"title": "Operator Status",
|
||||
"authority": "Epiphany CultMesh operator status",
|
||||
"freshness": status.map(|status| status.generated_at_utc.as_str()).unwrap_or("missing"),
|
||||
"rows": [
|
||||
{"label": "runtime", "value": status.map(|status| status.runtime_id.as_str()).unwrap_or("missing")},
|
||||
{"label": "state", "value": status.map(|status| status.status.as_str()).unwrap_or("missing")},
|
||||
{"label": "bridge", "value": status.map(|status| status.codex_bridge_role.as_str()).unwrap_or("compatibility transport")},
|
||||
{"label": "authority", "value": status.map(|status| status.epiphany_authority_role.as_str()).unwrap_or("Epiphany owns runtime truth")},
|
||||
{"label": "prompt", "value": status.map(|status| status.prompt_authority.as_str()).unwrap_or("prompt authority is separated from runtime state")}
|
||||
]
|
||||
},
|
||||
"children": []
|
||||
})
|
||||
}
|
||||
|
||||
fn local_verse_panel(context: Option<&EpiphanyLocalVerseContext>) -> Value {
|
||||
let verse_rows: Vec<Value> = context
|
||||
.map(|context| {
|
||||
context
|
||||
.verse_policies
|
||||
.iter()
|
||||
.map(|policy| {
|
||||
json!({
|
||||
"label": policy.verse_id,
|
||||
"value": format!("{}; private={}; untrustedIngress={}; yggdrasil={}", policy.tier, policy.private_state_allowed, policy.untrusted_ingress_allowed, policy.yggdrasil_tunnel_allowed)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
json!({
|
||||
"id": "epiphany.local_verse.context",
|
||||
"kind": "inspector.kv",
|
||||
"props": {
|
||||
"title": "Local Verse Context",
|
||||
"authority": "CultMesh Verse policies and contract summaries",
|
||||
"freshness": context.map(|context| context.store_path.as_str()).unwrap_or("store missing"),
|
||||
"rows": verse_rows
|
||||
},
|
||||
"children": []
|
||||
})
|
||||
}
|
||||
|
||||
fn daemon_affordance_panel(context: Option<&EpiphanyLocalVerseContext>) -> Value {
|
||||
let rows: Vec<Value> = context
|
||||
.map(|context| {
|
||||
context
|
||||
.daemon_affordances
|
||||
.iter()
|
||||
.take(12)
|
||||
.map(|affordance| {
|
||||
json!({
|
||||
"label": affordance.affordance_id,
|
||||
"value": format!(
|
||||
"{}:{}; authority={}; status={}; source={}",
|
||||
affordance.surface_kind,
|
||||
affordance.action,
|
||||
affordance.authority,
|
||||
affordance.status,
|
||||
affordance.source_record
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
json!({
|
||||
"id": "epiphany.daemon.affordances",
|
||||
"kind": "inspector.kv",
|
||||
"props": {
|
||||
"title": "Daemon Affordances",
|
||||
"authority": "Gjallar affordance records derived from Odin-owned sight",
|
||||
"freshness": context
|
||||
.and_then(|context| context.daemon_affordances.first())
|
||||
.map(|affordance| affordance.observed_at.as_str())
|
||||
.unwrap_or("missing"),
|
||||
"rows": rows,
|
||||
"emptyState": "No Gjallar affordances loaded; import an Odin/Gjallar CultMesh store rather than probing sockets or calling MCP."
|
||||
},
|
||||
"children": []
|
||||
})
|
||||
}
|
||||
|
||||
fn runtime_spine_panel(context: Option<&EpiphanyLocalVerseContext>) -> Value {
|
||||
let snapshot = context.and_then(|context| context.latest_operator_snapshot.as_ref());
|
||||
let intent = context.and_then(|context| context.latest_operator_run_intent.as_ref());
|
||||
let receipt = context.and_then(|context| context.latest_operator_run_receipt.as_ref());
|
||||
json!({
|
||||
"id": "epiphany.runtime.spine",
|
||||
"kind": "inspector.kv",
|
||||
"props": {
|
||||
"title": "Runtime Spine",
|
||||
"authority": "runtime-spine documents plus operator run intent/receipt projections",
|
||||
"rows": [
|
||||
{"label": "snapshot", "value": snapshot.map(|snapshot| snapshot.snapshot_id.as_str()).unwrap_or("missing")},
|
||||
{"label": "snapshotStatus", "value": snapshot.map(|snapshot| snapshot.status.as_str()).unwrap_or("missing")},
|
||||
{"label": "lastIntent", "value": intent.map(|intent| intent.run_id.as_str()).unwrap_or("missing")},
|
||||
{"label": "lastReceipt", "value": receipt.map(|receipt| receipt.run_id.as_str()).unwrap_or("missing")}
|
||||
]
|
||||
},
|
||||
"children": []
|
||||
})
|
||||
}
|
||||
|
||||
fn memory_graph_panel(context: Option<&EpiphanyLocalVerseContext>) -> Value {
|
||||
let graph_contracts = context
|
||||
.map(|context| {
|
||||
context
|
||||
.contract_summaries
|
||||
.iter()
|
||||
.filter(|contract| {
|
||||
contract.document_type.contains("memory")
|
||||
|| contract.document_type.contains("state")
|
||||
|| contract.authority == "mind"
|
||||
})
|
||||
.count()
|
||||
})
|
||||
.unwrap_or(0);
|
||||
json!({
|
||||
"id": "epiphany.memory.graph",
|
||||
"kind": "inspector.kv",
|
||||
"props": {
|
||||
"title": "Memory Graph",
|
||||
"authority": "Mind and memory graph typed state; private graph details stay behind organ contracts",
|
||||
"rows": [
|
||||
{"label": "profileAvailability", "value": if graph_contracts > 0 { "contracted" } else { "not advertised in loaded store" }},
|
||||
{"label": "thinStateWarning", "value": "surface reports availability and freshness only; it does not leak private graph payloads"},
|
||||
{"label": "contractCount", "value": graph_contracts}
|
||||
]
|
||||
},
|
||||
"children": []
|
||||
})
|
||||
}
|
||||
|
||||
fn persona_panel() -> Value {
|
||||
json!({
|
||||
"id": "epiphany.persona.projection",
|
||||
"kind": "inspector.kv",
|
||||
"props": {
|
||||
"title": "Persona Projection",
|
||||
"authority": "gamecult.persona_state.v0 for public Persona/Persona projections only",
|
||||
"rows": [
|
||||
{"label": "schema", "value": "gamecult.persona_state.v0"},
|
||||
{"label": "steward", "value": "Huginn coordinates Persona-state and .cc inspection runtime stewardship"},
|
||||
{"label": "boundary", "value": "work organs stay in organ state; Persona does not own runtime truth"}
|
||||
]
|
||||
},
|
||||
"children": []
|
||||
})
|
||||
}
|
||||
|
||||
fn schema_catalog(context: Option<&EpiphanyLocalVerseContext>) -> Vec<Value> {
|
||||
let mut schemas = vec![
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_OPERATOR_STATUS_SCHEMA_VERSION,
|
||||
"epiphany",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/operator-status",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_OPERATOR_SNAPSHOT_SCHEMA_VERSION,
|
||||
"epiphany",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/operator-snapshot/latest",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_OPERATOR_RUN_INTENT_SCHEMA_VERSION,
|
||||
"epiphany",
|
||||
"command-intent",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/operator-run-intent/latest",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_OPERATOR_RUN_RECEIPT_SCHEMA_VERSION,
|
||||
"epiphany",
|
||||
"receipt",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/operator-run-receipt/latest",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_VERSE_POLICY_SCHEMA_VERSION,
|
||||
"epiphany",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/verse-policy/{verseId}",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_GLOBAL_ROOM_POLICY_SCHEMA_VERSION,
|
||||
"epiphany",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/global-room/{roomId}",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_MIND_CONTRACT_SCHEMA_VERSION,
|
||||
"mind",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/contracts/mind/{contractId}",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_SUBSTRATE_GATE_CONTRACT_SCHEMA_VERSION,
|
||||
"substrateGate",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/contracts/substrate-gate/{contractId}",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_EYES_CONTRACT_SCHEMA_VERSION,
|
||||
"eyes",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/contracts/eyes/{contractId}",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_HANDS_CONTRACT_SCHEMA_VERSION,
|
||||
"hands",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/contracts/hands/{contractId}",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_SOUL_CONTRACT_SCHEMA_VERSION,
|
||||
"soul",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/contracts/soul/{contractId}",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
EPIPHANY_CULTMESH_CONTINUITY_CONTRACT_SCHEMA_VERSION,
|
||||
"continuity",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/contracts/continuity/{contractId}",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
"epiphany.work_organ_state.v0",
|
||||
"epiphany",
|
||||
"accepted",
|
||||
"cultcache-cc",
|
||||
"cultmesh://epiphany/organs/{organId}/state",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
"gamecult.persona_state.v0",
|
||||
"public Persona owner; Huginn steward",
|
||||
"accepted-projection",
|
||||
"external-authority-projection",
|
||||
"cultmesh://huginn/persona/{personaId}",
|
||||
true,
|
||||
),
|
||||
schema_entry(
|
||||
GJALLAR_AFFORDANCE_SCHEMA_VERSION,
|
||||
"gjallar",
|
||||
"imported-affordance",
|
||||
"cultcache-cc",
|
||||
"cultmesh://odin/gjallar/affordances",
|
||||
true,
|
||||
),
|
||||
];
|
||||
if let Some(context) = context {
|
||||
for contract in &context.contract_summaries {
|
||||
schemas.push(schema_entry(
|
||||
&contract.document_type,
|
||||
&contract.authority,
|
||||
"contracted",
|
||||
"cultcache-cc",
|
||||
&format!("cultmesh://epiphany/contracts/{}", contract.contract_id),
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
schemas
|
||||
}
|
||||
|
||||
fn schema_entry(
|
||||
schema: &str,
|
||||
owner: &str,
|
||||
authority: &str,
|
||||
storage: &str,
|
||||
cult_mesh_key: &str,
|
||||
portable: bool,
|
||||
) -> Value {
|
||||
json!({
|
||||
"schema": schema,
|
||||
"owner": owner,
|
||||
"authority": authority,
|
||||
"storage": storage,
|
||||
"cultMeshKey": cult_mesh_key,
|
||||
"portable": portable
|
||||
})
|
||||
}
|
||||
|
||||
fn witnesses(args: &Args, context: Option<&EpiphanyLocalVerseContext>) -> Vec<Value> {
|
||||
vec![
|
||||
json!({
|
||||
"id": "epiphany.local-verse.store",
|
||||
"kind": "cc-store",
|
||||
"path": args.store,
|
||||
"schemas": [
|
||||
EPIPHANY_CULTMESH_VERSE_POLICY_SCHEMA_VERSION,
|
||||
EPIPHANY_CULTMESH_OPERATOR_STATUS_SCHEMA_VERSION,
|
||||
EPIPHANY_CULTMESH_OPERATOR_SNAPSHOT_SCHEMA_VERSION,
|
||||
GJALLAR_AFFORDANCE_SCHEMA_VERSION
|
||||
],
|
||||
"redaction": "private worker transcripts and raw model payloads are not exposed",
|
||||
"freshness": {
|
||||
"state": if context.is_some() { "fresh" } else { "missing" },
|
||||
"updatedAt": context
|
||||
.and_then(|context| context.operator_status.as_ref())
|
||||
.map(|status| status.generated_at_utc.as_str())
|
||||
.unwrap_or(args.updated_at.as_str())
|
||||
}
|
||||
}),
|
||||
json!({
|
||||
"id": "epiphany.run.compatibility",
|
||||
"kind": "json-compatibility-witness",
|
||||
"path": ".epiphany-run",
|
||||
"schemas": [EPIPHANY_CULTMESH_OPERATOR_SNAPSHOT_SCHEMA_VERSION],
|
||||
"redaction": "operator-safe distillation only; JSON files are not accepted state owners",
|
||||
"freshness": {
|
||||
"state": "witness-only",
|
||||
"updatedAt": args.updated_at
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
fn nested_verses(context: Option<&EpiphanyLocalVerseContext>) -> Vec<Value> {
|
||||
let mut verses = vec![
|
||||
nested_verse(
|
||||
EPIPHANY_CULTMESH_INTERNAL_VERSE_ID,
|
||||
EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID,
|
||||
"private-runtime",
|
||||
"Epiphany internal organs own private runtime truth; Eve receives operator-safe projection only.",
|
||||
vec![SURFACE_ID],
|
||||
vec![
|
||||
"epiphany.work_organ_state.v0",
|
||||
EPIPHANY_CULTMESH_OPERATOR_STATUS_SCHEMA_VERSION,
|
||||
],
|
||||
),
|
||||
nested_verse(
|
||||
EPIPHANY_CULTMESH_GLOBAL_VERSE_ID,
|
||||
EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID,
|
||||
"public-persona",
|
||||
"Public Persona/Persona projection crosses this boundary through Persona state and Mind adoption gates.",
|
||||
vec![SURFACE_ID],
|
||||
vec!["gamecult.persona_state.v0"],
|
||||
),
|
||||
];
|
||||
|
||||
if let Some(context) = context {
|
||||
for contract in &context.contract_summaries {
|
||||
let verse_id = format!("epiphany.faculty.{}", contract.authority);
|
||||
verses.push(nested_verse(
|
||||
&verse_id,
|
||||
contract.verse_id.as_str(),
|
||||
"faculty",
|
||||
&format!(
|
||||
"{} owns {} operations; commands remain provider-accepted intent, not renderer callbacks.",
|
||||
contract.authority, contract.document_type
|
||||
),
|
||||
vec![SURFACE_ID],
|
||||
vec![contract.document_type.as_str()],
|
||||
));
|
||||
}
|
||||
}
|
||||
verses
|
||||
}
|
||||
|
||||
fn nested_verse(
|
||||
verse_id: &str,
|
||||
parent_verse_id: &str,
|
||||
kind: &str,
|
||||
authority_boundary: &str,
|
||||
surface_ids: Vec<&str>,
|
||||
state_schemas: Vec<&str>,
|
||||
) -> Value {
|
||||
json!({
|
||||
"verseId": verse_id,
|
||||
"parentVerseId": parent_verse_id,
|
||||
"kind": kind,
|
||||
"authorityBoundary": authority_boundary,
|
||||
"surfaceIds": surface_ids,
|
||||
"stateSchemas": state_schemas,
|
||||
"carryRules": {
|
||||
"style": "inherit epiphany.operator tokens unless provider republishes",
|
||||
"identity": "Persona projections require gamecult.persona_state.v0 provenance",
|
||||
"commands": "provider-accepted-only"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn commands() -> Vec<Value> {
|
||||
vec![
|
||||
command_entry(
|
||||
"epiphany.operator_run.request",
|
||||
"operator-or-coordinator",
|
||||
"accepted-denied-or-reconciled",
|
||||
),
|
||||
command_entry(
|
||||
"epiphany.job_interrupt.request",
|
||||
"operator-or-coordinator",
|
||||
"accepted-denied-or-reconciled",
|
||||
),
|
||||
command_entry(
|
||||
"epiphany.role_launch.request",
|
||||
"coordinator",
|
||||
"accepted-denied-or-reconciled",
|
||||
),
|
||||
command_entry(
|
||||
"epiphany.persona_projection.request",
|
||||
"huginn-stewarded-persona-owner",
|
||||
"accepted-denied-or-reconciled",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn command_entry(command: &str, authority: &str, result: &str) -> Value {
|
||||
json!({
|
||||
"command": command,
|
||||
"surfaceId": SURFACE_ID,
|
||||
"transport": "cultmesh-command",
|
||||
"schema": EVE_COMMAND_SCHEMA,
|
||||
"authority": authority,
|
||||
"result": result,
|
||||
"boundary": "This read-only export does not execute commands; Eve must send command intent through the provider's typed command boundary."
|
||||
})
|
||||
}
|
||||
|
||||
fn command_names() -> Vec<&'static str> {
|
||||
vec![
|
||||
"epiphany.operator_run.request",
|
||||
"epiphany.job_interrupt.request",
|
||||
"epiphany.role_launch.request",
|
||||
"epiphany.persona_projection.request",
|
||||
]
|
||||
}
|
||||
|
||||
fn run_smoke(args: Args) -> Result<()> {
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"epiphany-eve-surface-smoke-{}",
|
||||
Utc::now().timestamp_nanos_opt().unwrap_or_default()
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let store = temp_root.join("epiphany-local.cc");
|
||||
seed_epiphany_local_verse_context(&store, args.runtime_id.clone(), "2026-06-03T00:00:00Z")?;
|
||||
let smoke_args = Args {
|
||||
command: "bundle".to_string(),
|
||||
store: store.clone(),
|
||||
runtime_id: args.runtime_id,
|
||||
updated_at: "2026-06-03T00:00:00Z".to_string(),
|
||||
};
|
||||
let context = load_context(&store, &smoke_args.runtime_id)?
|
||||
.context("smoke failed to load seeded local Verse context")?;
|
||||
let advertisement = provider_advertisement(&smoke_args, Some(&context));
|
||||
let surface = surface_document(&smoke_args, Some(&context));
|
||||
|
||||
require_eq(&advertisement, "/schema", EVE_PROVIDER_ADVERTISEMENT_SCHEMA)?;
|
||||
require_eq(&surface, "/schema", EVE_SURFACE_SCHEMA)?;
|
||||
require_contains(&advertisement, "gamecult.persona_state.v0")?;
|
||||
require_contains(&advertisement, "epiphany.work_organ_state.v0")?;
|
||||
require_contains(
|
||||
&advertisement,
|
||||
".epiphany-run JSON is a compatibility witness only",
|
||||
)?;
|
||||
require_contains(&advertisement, "Huginn")?;
|
||||
require_contains(&surface, "Persona Projection")?;
|
||||
require_contains(&surface, "Operator Status")?;
|
||||
|
||||
print_json(json!({
|
||||
"status": "ok",
|
||||
"store": store,
|
||||
"providerSchema": EVE_PROVIDER_ADVERTISEMENT_SCHEMA,
|
||||
"surfaceSchema": EVE_SURFACE_SCHEMA,
|
||||
"schemas": advertisement["schemas"].as_array().map(|schemas| schemas.len()).unwrap_or_default(),
|
||||
"nestedVerses": advertisement["nestedVerses"].as_array().map(|verses| verses.len()).unwrap_or_default(),
|
||||
"commands": advertisement["commands"].as_array().map(|commands| commands.len()).unwrap_or_default()
|
||||
}))
|
||||
}
|
||||
|
||||
fn require_eq(value: &Value, pointer: &str, expected: &str) -> Result<()> {
|
||||
let actual = value
|
||||
.pointer(pointer)
|
||||
.and_then(Value::as_str)
|
||||
.with_context(|| format!("missing {pointer}"))?;
|
||||
if actual != expected {
|
||||
anyhow::bail!("{pointer} expected {expected:?}, got {actual:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require_contains(value: &Value, needle: &str) -> Result<()> {
|
||||
let rendered = serde_json::to_string(value)?;
|
||||
if !rendered.contains(needle) {
|
||||
anyhow::bail!("export did not contain {needle:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_json(value: Value) -> Result<()> {
|
||||
println!("{}", serde_json::to_string_pretty(&value)?);
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ use std::path::PathBuf;
|
|||
fn main() -> Result<()> {
|
||||
let store = PathBuf::from(".epiphany-smoke")
|
||||
.join("runtime-spine")
|
||||
.join("hands-action-smoke.msgpack");
|
||||
.join("hands-action-smoke.cc");
|
||||
|
||||
initialize_runtime_spine(
|
||||
&store,
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ fn main() -> Result<()> {
|
|||
}
|
||||
"queue-mention" => {
|
||||
let store_path = store_path.ok_or_else(|| anyhow!("queue-mention requires --store"))?;
|
||||
let target_role_id = role.unwrap_or_else(|| "face".to_string());
|
||||
let target_role_id = role.unwrap_or_else(|| "persona".to_string());
|
||||
let result = queue_heartbeat_pending_mention_store(
|
||||
&store_path,
|
||||
HeartbeatQueueMentionOptions {
|
||||
|
|
@ -344,7 +344,7 @@ fn main() -> Result<()> {
|
|||
println!("{}", result);
|
||||
}
|
||||
"smoke" => {
|
||||
let agent_store = agent_store.unwrap_or_else(|| PathBuf::from("state/agents.msgpack"));
|
||||
let agent_store = agent_store.unwrap_or_else(|| PathBuf::from("state/agents.cc"));
|
||||
let result = run_smoke(&agent_store)?;
|
||||
let ok = result["ok"].as_bool().unwrap_or(false);
|
||||
println!("{}", serde_json::to_string_pretty(&result)?);
|
||||
|
|
@ -369,7 +369,7 @@ fn next_value(args: &mut impl Iterator<Item = String>, name: &str) -> Result<Str
|
|||
|
||||
fn usage() -> Result<()> {
|
||||
Err(anyhow!(
|
||||
"usage: epiphany-heartbeat-store init --store <path> [--profile epiphany|ghostlight-scene] [--scene-id <id>] [--scene-participant <id|name|speed|reaction|threshold|constraint;constraint>]\n epiphany-heartbeat-store tick --store <path> --artifact-dir <path> [--coordinator-action <action>] [--agent-store <path> --apply-rumination] [--defer-completion]\n epiphany-heartbeat-store pump --store <path> --artifact-dir <path> [--agent-store <path>] [--urgency <0..1>] [--min-heartbeat-rate <n>] [--max-heartbeat-rate <n>] [--min-concurrency <n>] [--max-concurrency <n>] [--max-ticks <n>]\n epiphany-heartbeat-store heat --store <path> [--scope global|all|agent|role|arena|participant_kind|group] [--selector <id>] [--multiplier <n>] [--id <id>] [--label <text>] [--reason <text>] [--expires-after <scene-clock-delta>] [--clear]\n epiphany-heartbeat-store complete --store <path> --artifact-dir <path> --role <role> [--action-id <id>]\n epiphany-heartbeat-store queue-mention --store <path> [--role face] --channel-id <id> --message-id <id> --author-id <id> --content <text> --visible-prompt <text> [--author-name <name>] [--reply-to-message-id <id>] [--source-surface <name>]\n epiphany-heartbeat-store status --store <path> [--artifact-dir <path>]\n epiphany-heartbeat-store routine --store <path> --artifact-dir <path> [--agent-store <path>] [--source <source>] [--no-dream]\n epiphany-heartbeat-store smoke [--agent-store <path>]"
|
||||
"usage: epiphany-heartbeat-store init --store <path> [--profile epiphany|ghostlight-scene] [--scene-id <id>] [--scene-participant <id|name|speed|reaction|threshold|constraint;constraint>]\n epiphany-heartbeat-store tick --store <path> --artifact-dir <path> [--coordinator-action <action>] [--agent-store <path> --apply-rumination] [--defer-completion]\n epiphany-heartbeat-store pump --store <path> --artifact-dir <path> [--agent-store <path>] [--urgency <0..1>] [--min-heartbeat-rate <n>] [--max-heartbeat-rate <n>] [--min-concurrency <n>] [--max-concurrency <n>] [--max-ticks <n>]\n epiphany-heartbeat-store heat --store <path> [--scope global|all|agent|role|arena|participant_kind|group] [--selector <id>] [--multiplier <n>] [--id <id>] [--label <text>] [--reason <text>] [--expires-after <scene-clock-delta>] [--clear]\n epiphany-heartbeat-store complete --store <path> --artifact-dir <path> --role <role> [--action-id <id>]\n epiphany-heartbeat-store queue-mention --store <path> [--role persona] --channel-id <id> --message-id <id> --author-id <id> --content <text> --visible-prompt <text> [--author-name <name>] [--reply-to-message-id <id>] [--source-surface <name>]\n epiphany-heartbeat-store status --store <path> [--artifact-dir <path>]\n epiphany-heartbeat-store routine --store <path> --artifact-dir <path> [--agent-store <path>] [--source <source>] [--no-dream]\n epiphany-heartbeat-store smoke [--agent-store <path>]"
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -397,8 +397,8 @@ fn parse_scene_participant(raw: &str) -> Result<GhostlightSceneParticipantSeed>
|
|||
|
||||
fn run_smoke(agent_store: &Path) -> Result<serde_json::Value> {
|
||||
let temp_dir = scoped_temp_dir("epiphany-heartbeat-smoke")?;
|
||||
let temp_agent_store = temp_dir.join("agents.msgpack");
|
||||
let store_path = temp_dir.join("heartbeats.msgpack");
|
||||
let temp_agent_store = temp_dir.join("agents.cc");
|
||||
let store_path = temp_dir.join("heartbeats.cc");
|
||||
let artifact_dir = temp_dir.join("artifacts");
|
||||
fs::copy(agent_store, &temp_agent_store).with_context(|| {
|
||||
format!(
|
||||
|
|
@ -517,7 +517,7 @@ fn run_smoke(agent_store: &Path) -> Result<serde_json::Value> {
|
|||
},
|
||||
)?;
|
||||
let routine_status = heartbeat_status_projection(&store_path, &artifact_dir, 1.0, 8)?;
|
||||
let relaxed_store_path = temp_dir.join("relaxed-heartbeats.msgpack");
|
||||
let relaxed_store_path = temp_dir.join("relaxed-heartbeats.cc");
|
||||
initialize_heartbeat_store(&relaxed_store_path, 1.0)?;
|
||||
let relaxed_pump = pump_heartbeat_store(
|
||||
&relaxed_store_path,
|
||||
|
|
@ -537,7 +537,7 @@ fn run_smoke(agent_store: &Path) -> Result<serde_json::Value> {
|
|||
agent_store: Some(temp_agent_store.clone()),
|
||||
},
|
||||
)?;
|
||||
let alarmed_store_path = temp_dir.join("alarmed-heartbeats.msgpack");
|
||||
let alarmed_store_path = temp_dir.join("alarmed-heartbeats.cc");
|
||||
initialize_heartbeat_store(&alarmed_store_path, 1.0)?;
|
||||
let alarmed_pump = pump_heartbeat_store(
|
||||
&alarmed_store_path,
|
||||
|
|
@ -802,7 +802,7 @@ fn validate_schedule_shape(schedule: &serde_json::Value) -> Vec<String> {
|
|||
"role_work",
|
||||
"ruminate_memory",
|
||||
"scene_turn",
|
||||
"face_turn",
|
||||
"persona_turn",
|
||||
]
|
||||
.contains(&action["action_type"].as_str().unwrap_or_default())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -476,7 +476,7 @@ fn print_usage() {
|
|||
}
|
||||
|
||||
fn scoped_temp_store(prefix: &str) -> PathBuf {
|
||||
env::temp_dir().join(format!("{prefix}-{}.msgpack", uuid::Uuid::new_v4()))
|
||||
env::temp_dir().join(format!("{prefix}-{}.cc", uuid::Uuid::new_v4()))
|
||||
}
|
||||
|
||||
fn run_smoke(store: PathBuf) -> Result<serde_json::Value> {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ fn run_smoke(args: &Args) -> Result<Value> {
|
|||
"cold start should stop at prepareCheckpoint",
|
||||
)?;
|
||||
require_artifacts(&cold)?;
|
||||
require_coordination_guards(&cold)?;
|
||||
require_operator_safe(&cold, "$")?;
|
||||
|
||||
let local = run_coordinator(
|
||||
|
|
@ -109,6 +110,7 @@ fn run_smoke(args: &Args) -> Result<Value> {
|
|||
"local bootstrap should provide enough checkpoint state to move past prepareCheckpoint",
|
||||
)?;
|
||||
require_artifacts(&local)?;
|
||||
require_coordination_guards(&local)?;
|
||||
require_operator_safe(&local, "$")?;
|
||||
|
||||
let pressure = run_coordinator(
|
||||
|
|
@ -139,6 +141,7 @@ fn run_smoke(args: &Args) -> Result<Value> {
|
|||
"dry compact smoke should record the compaction action",
|
||||
)?;
|
||||
require_artifacts(&pressure)?;
|
||||
require_coordination_guards(&pressure)?;
|
||||
require_operator_safe(&pressure, "$")?;
|
||||
|
||||
let rejected = Command::new(&coordinator)
|
||||
|
|
@ -195,7 +198,7 @@ fn run_coordinator(
|
|||
.arg("--artifact-dir")
|
||||
.arg(&artifact_dir)
|
||||
.arg("--runtime-store")
|
||||
.arg(artifact_dir.join("runtime-spine.msgpack"))
|
||||
.arg(artifact_dir.join("runtime-spine.cc"))
|
||||
.arg("--codex-home")
|
||||
.arg(artifact_dir.join("codex-home"))
|
||||
.arg("--cwd")
|
||||
|
|
@ -260,7 +263,7 @@ fn require_artifacts(summary: &Value) -> Result<()> {
|
|||
)?;
|
||||
}
|
||||
require(
|
||||
artifact_dir.join("runtime-spine.msgpack").exists(),
|
||||
artifact_dir.join("runtime-spine.cc").exists(),
|
||||
"missing native runtime spine store",
|
||||
)?;
|
||||
let runtime_status: Value = serde_json::from_str(&fs::read_to_string(
|
||||
|
|
@ -277,6 +280,34 @@ fn require_artifacts(summary: &Value) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn require_coordination_guards(summary: &Value) -> Result<()> {
|
||||
let guards = summary
|
||||
.get("coordinationGuards")
|
||||
.ok_or_else(|| anyhow!("summary missing coordinationGuards"))?;
|
||||
require(
|
||||
guards
|
||||
.get("implementationCommitRequiresModelingRefresh")
|
||||
.and_then(Value::as_bool)
|
||||
== Some(false),
|
||||
"fresh coordinator smoke should expose clear Hands/Proprioception refresh debt",
|
||||
)?;
|
||||
require(
|
||||
guards
|
||||
.get("implementationLaunchSuppressedForModelingRefresh")
|
||||
.and_then(Value::as_bool)
|
||||
== Some(false),
|
||||
"fresh coordinator smoke should expose implementation launch suppression state",
|
||||
)?;
|
||||
require(
|
||||
guards
|
||||
.get("note")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(|note| note.contains("Hands/Proprioception refresh debt")),
|
||||
"coordination guard note should name the Hands/Proprioception debt",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require_operator_safe(value: &Value, path: &str) -> Result<()> {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ impl Args {
|
|||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| home_dir().join(".codex")),
|
||||
artifact_dir: root.join(".epiphany-dogfood").join("coordinator"),
|
||||
agent_memory_dir: root.join("state").join("agents.msgpack"),
|
||||
runtime_store: root.join("state").join("runtime-spine.msgpack"),
|
||||
agent_memory_dir: root.join("state").join("agents.cc"),
|
||||
runtime_store: root.join("state").join("runtime-spine.cc"),
|
||||
mode: "plan".to_string(),
|
||||
max_steps: 4,
|
||||
poll_seconds: 5.0,
|
||||
|
|
@ -391,8 +391,8 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
break;
|
||||
}
|
||||
let can_accept = args.auto_review
|
||||
&& result.pointer("/finding/statePatch").is_some()
|
||||
&& revision.is_some();
|
||||
&& revision.is_some()
|
||||
&& role_result_auto_acceptable(role_id, &result);
|
||||
if !can_accept {
|
||||
final_action = json!({
|
||||
"action": review_action_for_role(role_id),
|
||||
|
|
@ -443,7 +443,10 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
steps.push(step);
|
||||
break;
|
||||
}
|
||||
"launchResearch" | "launchModeling" | "launchVerification" => {
|
||||
"launchResearch"
|
||||
| "launchModeling"
|
||||
| "launchVerification"
|
||||
| "continueImplementation" => {
|
||||
let role_id = role_id_for_coordinator_action(&action)
|
||||
.ok_or_else(|| anyhow!("unsupported launch action {action}"))?;
|
||||
let launch = launch_role(
|
||||
|
|
@ -579,6 +582,15 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
let runtime_status = runtime_spine_status(&runtime_store)?;
|
||||
let final_rendered = status_cli::render_status(&operator_final_status);
|
||||
let operator_steps = status_cli::sanitize_for_operator(Value::Array(steps));
|
||||
let coordination_guards = coordinator_guard_summary(&operator_final_status, &final_action);
|
||||
let implementation_refresh_debt =
|
||||
coordination_guards["implementationCommitRequiresModelingRefresh"]
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
let implementation_launch_suppressed =
|
||||
coordination_guards["implementationLaunchSuppressedForModelingRefresh"]
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
let artifact_manifest = vec![
|
||||
"coordinator-summary.json".to_string(),
|
||||
"coordinator-steps.jsonl".to_string(),
|
||||
|
|
@ -628,6 +640,14 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
tool_adapter_bin.display().to_string(),
|
||||
),
|
||||
("autoTools".to_string(), args.auto_tools.to_string()),
|
||||
(
|
||||
"implementationCommitRequiresModelingRefresh".to_string(),
|
||||
implementation_refresh_debt.to_string(),
|
||||
),
|
||||
(
|
||||
"implementationLaunchSuppressedForModelingRefresh".to_string(),
|
||||
implementation_launch_suppressed.to_string(),
|
||||
),
|
||||
]),
|
||||
};
|
||||
put_coordinator_run_receipt(&runtime_store, &coordinator_run_receipt)?;
|
||||
|
|
@ -649,6 +669,7 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
"snapshots": snapshots,
|
||||
"finalAction": status_cli::sanitize_for_operator(final_action),
|
||||
"finalStatus": operator_final_status,
|
||||
"coordinationGuards": coordination_guards,
|
||||
"coordinatorRunReceipt": {
|
||||
"documentType": COORDINATOR_RUN_RECEIPT_TYPE,
|
||||
"receiptId": coordinator_run_receipt.receipt_id,
|
||||
|
|
@ -733,25 +754,25 @@ fn collect_coordinator_status(
|
|||
.unwrap_or_else(|| json!(null));
|
||||
let root = env::current_dir()?;
|
||||
let heartbeat_dir = root.join(".epiphany-heartbeats");
|
||||
let face_dir = root.join(".epiphany-face");
|
||||
let persona_dir = root.join(".epiphany-persona");
|
||||
let heartbeat = status_cli::native_json(
|
||||
"epiphany-heartbeat-store",
|
||||
&[
|
||||
"status",
|
||||
"--store",
|
||||
"state/agent-heartbeats.msgpack",
|
||||
"state/agent-heartbeats.cc",
|
||||
"--artifact-dir",
|
||||
&heartbeat_dir.to_string_lossy(),
|
||||
"--limit",
|
||||
"8",
|
||||
],
|
||||
)?;
|
||||
let latest_face = status_cli::native_json(
|
||||
"epiphany-face-discord",
|
||||
let latest_persona = status_cli::native_json(
|
||||
"epiphany-persona-discord",
|
||||
&[
|
||||
"latest",
|
||||
"--artifact-dir",
|
||||
&face_dir.to_string_lossy(),
|
||||
&persona_dir.to_string_lossy(),
|
||||
"--limit",
|
||||
"8",
|
||||
],
|
||||
|
|
@ -772,11 +793,11 @@ fn collect_coordinator_status(
|
|||
"crrc": crrc,
|
||||
"coordinator": coordinator,
|
||||
"heartbeat": heartbeat,
|
||||
"face": {
|
||||
"persona": {
|
||||
"status": "ready",
|
||||
"artifactDir": face_dir,
|
||||
"latestArtifacts": latest_face.get("latestArtifacts").cloned().unwrap_or_else(|| json!([])),
|
||||
"availableActions": ["faceBubble", "characterTurn"],
|
||||
"artifactDir": persona_dir,
|
||||
"latestArtifacts": latest_persona.get("latestArtifacts").cloned().unwrap_or_else(|| json!([])),
|
||||
"availableActions": ["personaBubble", "characterTurn"],
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
@ -827,6 +848,7 @@ fn role_id_for_coordinator_action(action: &str) -> Option<&'static str> {
|
|||
"launchResearch" | "reviewResearchResult" => Some("research"),
|
||||
"launchModeling" | "reviewModelingResult" => Some("modeling"),
|
||||
"launchVerification" | "reviewVerificationResult" => Some("verification"),
|
||||
"continueImplementation" => Some("implementation"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -836,6 +858,7 @@ fn wait_action_for_role(role_id: &str) -> &'static str {
|
|||
"research" => "waitForResearchResult",
|
||||
"modeling" => "waitForModelingResult",
|
||||
"verification" => "waitForVerificationResult",
|
||||
"implementation" => "waitForImplementationResult",
|
||||
_ => "waitForRoleResult",
|
||||
}
|
||||
}
|
||||
|
|
@ -845,10 +868,32 @@ fn review_action_for_role(role_id: &str) -> &'static str {
|
|||
"research" => "reviewResearchResult",
|
||||
"modeling" => "reviewModelingResult",
|
||||
"verification" => "reviewVerificationResult",
|
||||
"implementation" => "reviewImplementationResult",
|
||||
_ => "reviewRoleResult",
|
||||
}
|
||||
}
|
||||
|
||||
fn role_result_auto_acceptable(role_id: &str, result: &Value) -> bool {
|
||||
if result["status"].as_str() != Some("completed") {
|
||||
return false;
|
||||
}
|
||||
let Some(finding) = result.get("finding").filter(|value| value.is_object()) else {
|
||||
return false;
|
||||
};
|
||||
if finding
|
||||
.get("itemError")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(|error| !error.trim().is_empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
match role_id {
|
||||
"verification" => true,
|
||||
"research" | "modeling" => finding.get("statePatch").is_some(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn launch_reorient(
|
||||
client: &mut status_cli::AppServerClient,
|
||||
thread_id: &str,
|
||||
|
|
@ -1074,6 +1119,39 @@ fn final_action_reason(final_action: &Value) -> Option<String> {
|
|||
.map(ToString::to_string)
|
||||
}
|
||||
|
||||
fn coordinator_guard_summary(final_status: &Value, final_action: &Value) -> Value {
|
||||
let implementation_commit_requires_modeling_refresh = value_bool(
|
||||
final_status,
|
||||
"/coordinator/sourceSignals/implementationCommitRequiresModelingRefresh",
|
||||
);
|
||||
let final_action_name = final_action_name(final_action);
|
||||
let implementation_launch_suppressed_for_modeling_refresh =
|
||||
implementation_commit_requires_modeling_refresh
|
||||
&& final_action_name != "continueImplementation";
|
||||
json!({
|
||||
"implementationCommitRequiresModelingRefresh": implementation_commit_requires_modeling_refresh,
|
||||
"implementationLaunchSuppressedForModelingRefresh": implementation_launch_suppressed_for_modeling_refresh,
|
||||
"requiredRefreshRole": if implementation_commit_requires_modeling_refresh {
|
||||
Value::String("modeling".to_string())
|
||||
} else {
|
||||
Value::Null
|
||||
},
|
||||
"finalAction": final_action_name,
|
||||
"note": if implementation_commit_requires_modeling_refresh {
|
||||
"Hands commit evidence is newer than the accepted Proprioception map; coordinator must refresh modeling before another Implementation launch."
|
||||
} else {
|
||||
"No pending Hands/Proprioception refresh debt is visible in the final coordinator status."
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn value_bool(value: &Value, pointer: &str) -> bool {
|
||||
value
|
||||
.pointer(pointer)
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn sanitize_id(value: &str) -> String {
|
||||
value
|
||||
.chars()
|
||||
|
|
@ -1143,7 +1221,7 @@ fn local_mvp_checkpoint_patch(cwd: &Path, objective: Option<&str>) -> Value {
|
|||
let objective = objective
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("Run the local Epiphany MVP cycle through Face, coordinator, and sleep.");
|
||||
.unwrap_or("Run the local Epiphany MVP cycle through Persona, coordinator, and sleep.");
|
||||
let local_refs = local_mvp_code_refs(cwd);
|
||||
let transport_refs = local_mvp_transport_code_refs(cwd);
|
||||
let mut checkpoint_refs = local_refs.clone();
|
||||
|
|
@ -1155,14 +1233,14 @@ fn local_mvp_checkpoint_patch(cwd: &Path, objective: Option<&str>) -> Value {
|
|||
"id": "local-mvp-cycle",
|
||||
"title": "Local MVP cycle",
|
||||
"status": "active",
|
||||
"summary": "Use Face as the human-facing entrypoint, run bounded coordinator/swarm work, and close with heartbeat sleep/dream maintenance.",
|
||||
"summary": "Use Persona as the human-facing entrypoint, run bounded coordinator/swarm work, and close with heartbeat sleep/dream maintenance.",
|
||||
}],
|
||||
"graphs": {
|
||||
"architecture": {"nodes": [
|
||||
{
|
||||
"id": "local-mvp-runner",
|
||||
"title": "Local MVP runner",
|
||||
"purpose": "One local operator cycle that enters through Face, runs coordinator-owned work, and invokes Continuity/heartbeat sleep afterward.",
|
||||
"purpose": "One local operator cycle that enters through Persona, runs coordinator-owned work, and invokes Continuity/heartbeat sleep afterward.",
|
||||
"code_refs": local_refs,
|
||||
},
|
||||
{
|
||||
|
|
@ -1174,9 +1252,9 @@ fn local_mvp_checkpoint_patch(cwd: &Path, objective: Option<&str>) -> Value {
|
|||
]},
|
||||
"dataflow": {"nodes": [
|
||||
{
|
||||
"id": "face-coordinator-sleep-cycle",
|
||||
"title": "Face to coordinator to sleep",
|
||||
"purpose": "Face expression is display state; coordinator owns lane routing; heartbeat owns sleep physiology.",
|
||||
"id": "persona-coordinator-sleep-cycle",
|
||||
"title": "Persona to coordinator to sleep",
|
||||
"purpose": "Persona expression is display state; coordinator owns lane routing; heartbeat owns sleep physiology.",
|
||||
},
|
||||
{
|
||||
"id": "coordinator-model-runtime-cycle",
|
||||
|
|
@ -1186,7 +1264,7 @@ fn local_mvp_checkpoint_patch(cwd: &Path, objective: Option<&str>) -> Value {
|
|||
]},
|
||||
"links": [
|
||||
{
|
||||
"dataflow_node_id": "face-coordinator-sleep-cycle",
|
||||
"dataflow_node_id": "persona-coordinator-sleep-cycle",
|
||||
"architecture_node_id": "local-mvp-runner",
|
||||
},
|
||||
{
|
||||
|
|
@ -1196,14 +1274,14 @@ fn local_mvp_checkpoint_patch(cwd: &Path, objective: Option<&str>) -> Value {
|
|||
],
|
||||
},
|
||||
"graphFrontier": {
|
||||
"active_node_ids": ["local-mvp-runner", "face-coordinator-sleep-cycle", "model-runtime-transport", "coordinator-model-runtime-cycle"],
|
||||
"active_node_ids": ["local-mvp-runner", "persona-coordinator-sleep-cycle", "model-runtime-transport", "coordinator-model-runtime-cycle"],
|
||||
"dirty_paths": [],
|
||||
},
|
||||
"graphCheckpoint": {
|
||||
"checkpoint_id": "ck-local-mvp-cycle",
|
||||
"graph_revision": 2,
|
||||
"summary": "Local MVP runner checkpoint: Face front door, coordinator run, model runtime transport, sleep maintenance.",
|
||||
"frontier_node_ids": ["local-mvp-runner", "face-coordinator-sleep-cycle", "model-runtime-transport", "coordinator-model-runtime-cycle"],
|
||||
"summary": "Local MVP runner checkpoint: Persona front door, coordinator run, model runtime transport, sleep maintenance.",
|
||||
"frontier_node_ids": ["local-mvp-runner", "persona-coordinator-sleep-cycle", "model-runtime-transport", "coordinator-model-runtime-cycle"],
|
||||
},
|
||||
"investigationCheckpoint": {
|
||||
"checkpoint_id": "ix-local-mvp-cycle",
|
||||
|
|
@ -1435,3 +1513,85 @@ fn home_dir() -> PathBuf {
|
|||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn coordination_guards_show_hands_refresh_debt_suppresses_implementation() {
|
||||
let final_status = json!({
|
||||
"coordinator": {
|
||||
"sourceSignals": {
|
||||
"implementationCommitRequiresModelingRefresh": true
|
||||
}
|
||||
}
|
||||
});
|
||||
let final_action = json!({
|
||||
"action": "launchModeling",
|
||||
"reason": "Refresh Proprioception before Hands cuts again."
|
||||
});
|
||||
|
||||
let guards = coordinator_guard_summary(&final_status, &final_action);
|
||||
|
||||
assert_eq!(
|
||||
guards["implementationCommitRequiresModelingRefresh"].as_bool(),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
guards["implementationLaunchSuppressedForModelingRefresh"].as_bool(),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(guards["requiredRefreshRole"].as_str(), Some("modeling"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_implementation_maps_to_hands_launch_role() {
|
||||
assert_eq!(
|
||||
role_id_for_coordinator_action("continueImplementation"),
|
||||
Some("implementation")
|
||||
);
|
||||
assert_eq!(
|
||||
wait_action_for_role("implementation"),
|
||||
"waitForImplementationResult"
|
||||
);
|
||||
assert_eq!(
|
||||
review_action_for_role("implementation"),
|
||||
"reviewImplementationResult"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_review_accepts_patchless_verification_but_not_patchless_modeling() {
|
||||
let verification = json!({
|
||||
"status": "completed",
|
||||
"finding": {
|
||||
"verdict": "needs-evidence",
|
||||
"summary": "Soul asks Hands for one bounded evidence-gathering turn.",
|
||||
"nextSafeMove": "Continue implementation for evidence.",
|
||||
"filesInspected": ["state/map.yaml"]
|
||||
}
|
||||
});
|
||||
assert!(role_result_auto_acceptable("verification", &verification));
|
||||
|
||||
let modeling = json!({
|
||||
"status": "completed",
|
||||
"finding": {
|
||||
"verdict": "checkpoint-ready",
|
||||
"summary": "Looks plausible, but no statePatch was supplied."
|
||||
}
|
||||
});
|
||||
assert!(!role_result_auto_acceptable("modeling", &modeling));
|
||||
|
||||
let errored_verification = json!({
|
||||
"status": "completed",
|
||||
"finding": {
|
||||
"itemError": "verification result is malformed"
|
||||
}
|
||||
});
|
||||
assert!(!role_result_auto_acceptable(
|
||||
"verification",
|
||||
&errored_verification
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ fn run_smoke(args: &Args) -> Result<Value> {
|
|||
"roleResults": status["roleResults"],
|
||||
"planning": status["planning"],
|
||||
"heartbeat": status["heartbeat"],
|
||||
"face": status["face"],
|
||||
"persona": status["persona"],
|
||||
"stateStatus": status["scene"]["scene"]["stateStatus"],
|
||||
"availableActions": status["scene"]["scene"]["availableActions"],
|
||||
"rendered": rendered,
|
||||
|
|
@ -233,6 +233,14 @@ fn validate_status(status: &Value, rendered: &str) -> Result<()> {
|
|||
rendered.contains("regatherManually"),
|
||||
"rendered status should expose the CRRC recommendation",
|
||||
)?;
|
||||
require(
|
||||
status
|
||||
.pointer("/coordinator/sourceSignals/implementationCommitRequiresModelingRefresh")
|
||||
.and_then(Value::as_bool)
|
||||
== Some(false)
|
||||
&& rendered.contains("Hands/Proprioception refresh debt: clear"),
|
||||
"status artifacts should expose Hands/Proprioception refresh debt",
|
||||
)?;
|
||||
let lane_ids = status
|
||||
.pointer("/roles/roles")
|
||||
.and_then(Value::as_array)
|
||||
|
|
@ -305,21 +313,23 @@ fn validate_status(status: &Value, rendered: &str) -> Result<()> {
|
|||
== Some("epiphany.agent_heartbeat_status.v0"),
|
||||
"status view should expose heartbeat initiative status for Aquarium",
|
||||
)?;
|
||||
let face_actions = status
|
||||
.pointer("/face/availableActions")
|
||||
let persona_actions = status
|
||||
.pointer("/persona/availableActions")
|
||||
.and_then(Value::as_array);
|
||||
require(
|
||||
face_actions.is_some_and(|items| {
|
||||
items.iter().any(|item| item.as_str() == Some("faceBubble"))
|
||||
persona_actions.is_some_and(|items| {
|
||||
items
|
||||
.iter()
|
||||
.any(|item| item.as_str() == Some("personaBubble"))
|
||||
&& items
|
||||
.iter()
|
||||
.any(|item| item.as_str() == Some("characterTurn"))
|
||||
}),
|
||||
"status view should expose Face bubble and character-turn actions for Aquarium",
|
||||
"status view should expose Persona bubble and character-turn actions for Aquarium",
|
||||
)?;
|
||||
require(
|
||||
rendered.contains("Heartbeat") && rendered.contains("Face"),
|
||||
"rendered status should expose heartbeat and Face sections",
|
||||
rendered.contains("Heartbeat") && rendered.contains("Persona"),
|
||||
"rendered status should expose heartbeat and Persona sections",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ use epiphany_core::load_thread_state_entry;
|
|||
use epiphany_core::recommend_crrc_action;
|
||||
use epiphany_core::recommend_reorientation;
|
||||
use epiphany_core::runtime_job_snapshot;
|
||||
use epiphany_core::runtime_role_worker_result;
|
||||
use epiphany_state_model::EpiphanyThreadState;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
|
|
@ -52,9 +53,11 @@ use std::time::{Duration, Instant};
|
|||
|
||||
const DEFAULT_APP_SERVER: &str = r"C:\Users\Meta\.cargo-target-codex\debug\codex-app-server.exe";
|
||||
const DEFAULT_CARGO_TARGET_DIR: &str = r"C:\Users\Meta\.cargo-target-codex";
|
||||
const DEFAULT_THREAD_STATE_STORE: &str = "state/thread-state.msgpack";
|
||||
const DEFAULT_RUNTIME_STORE: &str = "state/runtime-spine.msgpack";
|
||||
const DEFAULT_THREAD_STATE_STORE: &str = "state/thread-state.cc";
|
||||
const DEFAULT_RUNTIME_STORE: &str = "state/runtime-spine.cc";
|
||||
const REORIENT_BINDING_ID: &str = "reorient-worker";
|
||||
const IMPLEMENTATION_BINDING_ID: &str = "implementation-branch-turn-worker";
|
||||
const IMPLEMENTATION_OWNER_ROLE: &str = "epiphany-hands";
|
||||
const IMAGINATION_BINDING_ID: &str = "imagination-synthesis-worker";
|
||||
const RESEARCH_BINDING_ID: &str = "research-source-gather-worker";
|
||||
const MODELING_BINDING_ID: &str = "modeling-checkpoint-worker";
|
||||
|
|
@ -258,6 +261,8 @@ fn run_native_status(args: &Args) -> Result<Value> {
|
|||
native_role_result_status(state_ref, &runtime_store_path, RESEARCH_BINDING_ID);
|
||||
let verification_result_status =
|
||||
native_role_result_status(state_ref, &runtime_store_path, VERIFICATION_BINDING_ID);
|
||||
let implementation_commit_requires_modeling_refresh =
|
||||
native_implementation_commit_requires_modeling_refresh(state_ref, &runtime_store_path);
|
||||
let recommendation = recommend_crrc_action(EpiphanyCrrcInput {
|
||||
loaded,
|
||||
state_status,
|
||||
|
|
@ -310,10 +315,12 @@ fn run_native_status(args: &Args) -> Result<Value> {
|
|||
.iter()
|
||||
.find(|job| job.id == REORIENT_BINDING_ID)
|
||||
.cloned(),
|
||||
implementation_binding_id: IMPLEMENTATION_BINDING_ID.to_string(),
|
||||
imagination_binding_id: IMAGINATION_BINDING_ID.to_string(),
|
||||
research_binding_id: RESEARCH_BINDING_ID.to_string(),
|
||||
modeling_binding_id: MODELING_BINDING_ID.to_string(),
|
||||
verification_binding_id: VERIFICATION_BINDING_ID.to_string(),
|
||||
implementation_owner_role: IMPLEMENTATION_OWNER_ROLE.to_string(),
|
||||
reorient_owner_role: "epiphany-reorienter".to_string(),
|
||||
imagination_owner_role: "epiphany-imagination".to_string(),
|
||||
research_owner_role: "epiphany-eyes".to_string(),
|
||||
|
|
@ -349,6 +356,7 @@ fn run_native_status(args: &Args) -> Result<Value> {
|
|||
verification_result_allows_implementation: finding_signals
|
||||
.verification_result_allows_implementation,
|
||||
verification_result_needs_evidence: finding_signals.verification_result_needs_evidence,
|
||||
implementation_commit_requires_modeling_refresh,
|
||||
reorient_finding_accepted: finding_signals.reorient_finding_accepted,
|
||||
});
|
||||
let coordinator_json = coordinator_status_json(&coordinator)?;
|
||||
|
|
@ -431,7 +439,7 @@ fn run_native_status(args: &Args) -> Result<Value> {
|
|||
"coordinator": coordinator_json,
|
||||
"tools": tool_invocations,
|
||||
"heartbeat": native_aux.heartbeat,
|
||||
"face": native_aux.face,
|
||||
"persona": native_aux.persona,
|
||||
"voidMemory": native_aux.void_memory,
|
||||
});
|
||||
Ok(sanitize_for_operator(status))
|
||||
|
|
@ -547,7 +555,7 @@ fn run_codex_status(args: &Args) -> Result<Value> {
|
|||
"coordinator": coordinator,
|
||||
"tools": tool_invocations,
|
||||
"heartbeat": native_aux.heartbeat,
|
||||
"face": native_aux.face,
|
||||
"persona": native_aux.persona,
|
||||
"voidMemory": native_aux.void_memory,
|
||||
});
|
||||
Ok(sanitize_for_operator(status))
|
||||
|
|
@ -555,31 +563,31 @@ fn run_codex_status(args: &Args) -> Result<Value> {
|
|||
|
||||
struct NativeAuxiliaryStatus {
|
||||
heartbeat: Value,
|
||||
face: Value,
|
||||
persona: Value,
|
||||
void_memory: Value,
|
||||
}
|
||||
|
||||
fn native_auxiliary_status(root: &Path) -> Result<NativeAuxiliaryStatus> {
|
||||
let heartbeat_dir = root.join(".epiphany-heartbeats");
|
||||
let face_dir = root.join(".epiphany-face");
|
||||
let persona_dir = root.join(".epiphany-persona");
|
||||
let heartbeat = native_json(
|
||||
"epiphany-heartbeat-store",
|
||||
&[
|
||||
"status",
|
||||
"--store",
|
||||
"state/agent-heartbeats.msgpack",
|
||||
"state/agent-heartbeats.cc",
|
||||
"--artifact-dir",
|
||||
&heartbeat_dir.to_string_lossy(),
|
||||
"--limit",
|
||||
"8",
|
||||
],
|
||||
)?;
|
||||
let latest_face = native_json(
|
||||
"epiphany-face-discord",
|
||||
let latest_persona = native_json(
|
||||
"epiphany-persona-discord",
|
||||
&[
|
||||
"latest",
|
||||
"--artifact-dir",
|
||||
&face_dir.to_string_lossy(),
|
||||
&persona_dir.to_string_lossy(),
|
||||
"--limit",
|
||||
"8",
|
||||
],
|
||||
|
|
@ -587,11 +595,11 @@ fn native_auxiliary_status(root: &Path) -> Result<NativeAuxiliaryStatus> {
|
|||
.unwrap_or_else(
|
||||
|error| json!({"status": "error", "error": error.to_string(), "latestArtifacts": []}),
|
||||
);
|
||||
let face = json!({
|
||||
let persona = json!({
|
||||
"status": "ready",
|
||||
"artifactDir": face_dir,
|
||||
"latestArtifacts": latest_face.get("latestArtifacts").cloned().unwrap_or_else(|| json!([])),
|
||||
"availableActions": ["faceBubble", "characterTurn", "discordPersonaPost"],
|
||||
"artifactDir": persona_dir,
|
||||
"latestArtifacts": latest_persona.get("latestArtifacts").cloned().unwrap_or_else(|| json!([])),
|
||||
"availableActions": ["personaBubble", "characterTurn", "discordPersonaPost"],
|
||||
});
|
||||
let void_memory = native_json(
|
||||
"epiphany-void-memory",
|
||||
|
|
@ -601,7 +609,7 @@ fn native_auxiliary_status(root: &Path) -> Result<NativeAuxiliaryStatus> {
|
|||
|
||||
Ok(NativeAuxiliaryStatus {
|
||||
heartbeat,
|
||||
face,
|
||||
persona,
|
||||
void_memory,
|
||||
})
|
||||
}
|
||||
|
|
@ -737,6 +745,62 @@ fn native_role_result_status(
|
|||
}
|
||||
}
|
||||
|
||||
fn native_implementation_commit_requires_modeling_refresh(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store: &Path,
|
||||
) -> bool {
|
||||
let Some(job_id) = latest_runtime_job_id_for_binding(state, IMPLEMENTATION_BINDING_ID) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(Some(snapshot)) = runtime_job_snapshot(runtime_store, job_id) else {
|
||||
return false;
|
||||
};
|
||||
if map_runtime_role_result_status(&snapshot) != EpiphanyCoordinatorRoleResultStatus::Completed {
|
||||
return false;
|
||||
}
|
||||
let Ok(Some(result)) = runtime_role_worker_result(runtime_store, job_id) else {
|
||||
return false;
|
||||
};
|
||||
if !result
|
||||
.evidence_ids
|
||||
.iter()
|
||||
.any(|id| id.starts_with("hands-receipt:hands-commit-") || id.starts_with("hands-commit-"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let Some(completed_at) = snapshot
|
||||
.result
|
||||
.as_ref()
|
||||
.map(|receipt| receipt.completed_at.as_str())
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
!native_modeling_accepted_after(state, runtime_store, completed_at)
|
||||
}
|
||||
|
||||
fn native_modeling_accepted_after(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store: &Path,
|
||||
completed_at: &str,
|
||||
) -> bool {
|
||||
let Some(state) = state else {
|
||||
return false;
|
||||
};
|
||||
let Some(job_id) = latest_runtime_job_id_for_binding(Some(state), MODELING_BINDING_ID) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(Some(result)) = runtime_role_worker_result(runtime_store, job_id) else {
|
||||
return false;
|
||||
};
|
||||
state.acceptance_receipts.iter().any(|receipt| {
|
||||
receipt.result_id == result.result_id
|
||||
&& receipt.status == "accepted"
|
||||
&& receipt.surface == "roleAccept"
|
||||
&& receipt.role_id == "modeling"
|
||||
&& receipt.accepted_at.as_str() > completed_at
|
||||
})
|
||||
}
|
||||
|
||||
fn native_reorient_result_status(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store: &Path,
|
||||
|
|
@ -1230,12 +1294,12 @@ pub fn render_status(status: &Value) -> String {
|
|||
let recommendation = &status["crrc"]["recommendation"];
|
||||
let coordinator = &status["coordinator"];
|
||||
let heartbeat = &status["heartbeat"];
|
||||
let face = &status["face"];
|
||||
let persona = &status["persona"];
|
||||
let planning_response = &status["planning"];
|
||||
let planning_summary = &planning_response["summary"];
|
||||
let checkpoint = &scene["investigationCheckpoint"];
|
||||
let latest_heartbeat = heartbeat["latestEvent"].clone();
|
||||
let latest_face = face["latestArtifacts"]
|
||||
let latest_persona = persona["latestArtifacts"]
|
||||
.as_array()
|
||||
.and_then(|items| items.first())
|
||||
.cloned()
|
||||
|
|
@ -1263,6 +1327,18 @@ pub fn render_status(status: &Value) -> String {
|
|||
maybe(&coordinator["action"], "none"),
|
||||
maybe(&coordinator["targetRole"], "none")
|
||||
),
|
||||
format!(
|
||||
"- Hands/Proprioception refresh debt: {}",
|
||||
if coordinator
|
||||
.pointer("/sourceSignals/implementationCommitRequiresModelingRefresh")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
"pending"
|
||||
} else {
|
||||
"clear"
|
||||
}
|
||||
),
|
||||
String::new(),
|
||||
"Continuity".to_string(),
|
||||
format!(
|
||||
|
|
@ -1297,11 +1373,14 @@ pub fn render_status(status: &Value) -> String {
|
|||
maybe(&latest_heartbeat["coordinatorAction"], "none")
|
||||
),
|
||||
String::new(),
|
||||
"Face".to_string(),
|
||||
format!("- latest artifact: {}", maybe(&latest_face["name"], "none")),
|
||||
"Persona".to_string(),
|
||||
format!(
|
||||
"- latest artifact: {}",
|
||||
maybe(&latest_persona["name"], "none")
|
||||
),
|
||||
format!(
|
||||
"- latest content: {}",
|
||||
maybe(&latest_face["content"], "none")
|
||||
maybe(&latest_persona["content"], "none")
|
||||
),
|
||||
String::new(),
|
||||
"Planning".to_string(),
|
||||
|
|
|
|||
|
|
@ -112,8 +112,7 @@ fn main() -> Result<()> {
|
|||
result_path: ".epiphany-run/local-smoke/status.json".to_string(),
|
||||
artifact_root: ".epiphany-run/local-smoke".to_string(),
|
||||
dogfood_root: ".epiphany-dogfood/local-smoke".to_string(),
|
||||
operator_snapshot_store: ".epiphany-run/cultmesh/operator-snapshots.ccmp"
|
||||
.to_string(),
|
||||
operator_snapshot_store: ".epiphany-run/cultmesh/operator-snapshots.cc".to_string(),
|
||||
operator_snapshot_id: "local-smoke-status".to_string(),
|
||||
artifact_refs: vec![".epiphany-run/local-smoke/status.json".to_string()],
|
||||
notes: vec!["smoke receipt".to_string()],
|
||||
|
|
@ -168,7 +167,7 @@ impl Args {
|
|||
let command = values.next().unwrap_or_else(|| "latest".to_string());
|
||||
let mut args = Args {
|
||||
command,
|
||||
store: PathBuf::from(".epiphany-run/cultmesh/operator-runs.ccmp"),
|
||||
store: PathBuf::from(".epiphany-run/cultmesh/operator-runs.cc"),
|
||||
runtime_id: "epiphany-local".to_string(),
|
||||
run_id: format!("run-{}", Utc::now().timestamp()),
|
||||
mode: "status".to_string(),
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ impl Args {
|
|||
fn parse() -> Result<Self> {
|
||||
let mut values = env::args().skip(1);
|
||||
let command = values.next().unwrap_or_else(|| "latest".to_string());
|
||||
let mut store = PathBuf::from(".epiphany-run/cultmesh/operator-snapshots.ccmp");
|
||||
let mut store = PathBuf::from(".epiphany-run/cultmesh/operator-snapshots.cc");
|
||||
let mut runtime_id = "epiphany-local".to_string();
|
||||
let mut snapshot_id = format!("snapshot-{}", Utc::now().timestamp());
|
||||
let mut source_mode = "status".to_string();
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ use std::path::Path;
|
|||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
const CHAT_SCHEMA_VERSION: &str = "epiphany.face_chat.v0";
|
||||
const BUBBLE_SCHEMA_VERSION: &str = "epiphany.face_bubble.v0";
|
||||
const CHAT_SCHEMA_VERSION: &str = "epiphany.persona_chat.v0";
|
||||
const BUBBLE_SCHEMA_VERSION: &str = "epiphany.persona_bubble.v0";
|
||||
const DISCORD_API: &str = "https://discord.com/api/v10";
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct FaceConfig {
|
||||
struct PersonaConfig {
|
||||
allowed_channel_name: String,
|
||||
allowed_channel_id: Option<String>,
|
||||
allowed_channel_id_env: Option<String>,
|
||||
|
|
@ -32,11 +32,11 @@ fn main() -> Result<()> {
|
|||
let Some(command) = args.next() else {
|
||||
return usage();
|
||||
};
|
||||
let mut config_path = PathBuf::from("state/face-discord.toml");
|
||||
let mut artifact_dir = PathBuf::from(".epiphany-face");
|
||||
let mut config_path = PathBuf::from("state/persona-discord.toml");
|
||||
let mut artifact_dir = PathBuf::from(".epiphany-persona");
|
||||
let mut content: Option<String> = None;
|
||||
let mut channel_id: Option<String> = None;
|
||||
let mut source = "epiphany/face".to_string();
|
||||
let mut source = "epiphany/persona".to_string();
|
||||
let mut status = "ready".to_string();
|
||||
let mut mood = "attentive".to_string();
|
||||
let mut limit = 8_usize;
|
||||
|
|
@ -97,7 +97,7 @@ fn main() -> Result<()> {
|
|||
"latest" => serde_json::json!({
|
||||
"ok": true,
|
||||
"artifactDir": artifact_dir,
|
||||
"latestArtifacts": latest_face_artifacts(&artifact_dir, limit),
|
||||
"latestArtifacts": latest_persona_artifacts(&artifact_dir, limit),
|
||||
}),
|
||||
"smoke" => run_smoke()?,
|
||||
_ => return usage(),
|
||||
|
|
@ -112,12 +112,12 @@ fn main() -> Result<()> {
|
|||
|
||||
fn run_draft(
|
||||
content: &str,
|
||||
config: &FaceConfig,
|
||||
config: &PersonaConfig,
|
||||
artifact_dir: &Path,
|
||||
status: &str,
|
||||
reason: &str,
|
||||
) -> Result<Value> {
|
||||
ensure_content(content, "Face chat")?;
|
||||
ensure_content(content, "Persona chat")?;
|
||||
let path = write_draft(content, config, artifact_dir, status, reason)?;
|
||||
Ok(serde_json::json!({
|
||||
"ok": status != "blocked",
|
||||
|
|
@ -133,9 +133,13 @@ fn run_bubble(
|
|||
status: &str,
|
||||
mood: &str,
|
||||
) -> Result<Value> {
|
||||
ensure_content(content, "Face bubble")?;
|
||||
ensure_content(content, "Persona bubble")?;
|
||||
let payload = bubble_payload(content, source, status, mood);
|
||||
let path = artifact_dir.join(format!("face-bubble-{}-{}.json", now_stamp(), short_id()));
|
||||
let path = artifact_dir.join(format!(
|
||||
"persona-bubble-{}-{}.json",
|
||||
now_stamp(),
|
||||
short_id()
|
||||
));
|
||||
write_json(&path, &payload)?;
|
||||
Ok(serde_json::json!({
|
||||
"ok": true,
|
||||
|
|
@ -147,14 +151,14 @@ fn run_bubble(
|
|||
|
||||
fn run_post(
|
||||
content: &str,
|
||||
config: &FaceConfig,
|
||||
config: &PersonaConfig,
|
||||
artifact_dir: &Path,
|
||||
channel_id: Option<String>,
|
||||
persona_name: Option<String>,
|
||||
persona_avatar_url: Option<String>,
|
||||
reply_to_message_id: Option<String>,
|
||||
) -> Result<Value> {
|
||||
ensure_content(content, "Face chat")?;
|
||||
ensure_content(content, "Persona chat")?;
|
||||
let configured_channel_id = allowed_channel_id(config);
|
||||
let requested_channel_id = channel_id.or_else(|| configured_channel_id.clone());
|
||||
let Some(configured_channel_id) = configured_channel_id else {
|
||||
|
|
@ -236,7 +240,7 @@ fn run_post(
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct FacePersona {
|
||||
struct DiscordPersona {
|
||||
name: String,
|
||||
avatar_url: Option<String>,
|
||||
}
|
||||
|
|
@ -248,10 +252,10 @@ struct PostedDiscordMessage {
|
|||
}
|
||||
|
||||
fn resolve_persona(
|
||||
config: &FaceConfig,
|
||||
config: &PersonaConfig,
|
||||
persona_name: Option<String>,
|
||||
persona_avatar_url: Option<String>,
|
||||
) -> Result<Option<FacePersona>> {
|
||||
) -> Result<Option<DiscordPersona>> {
|
||||
let name = trim_optional(persona_name).or_else(|| trim_optional(config.persona_name.clone()));
|
||||
let avatar_url = trim_optional(persona_avatar_url)
|
||||
.or_else(|| trim_optional(config.persona_avatar_url.clone()));
|
||||
|
|
@ -263,7 +267,7 @@ fn resolve_persona(
|
|||
"persona-name is required when posting with a persona avatar"
|
||||
));
|
||||
};
|
||||
Ok(Some(FacePersona {
|
||||
Ok(Some(DiscordPersona {
|
||||
name: name.chars().take(80).collect(),
|
||||
avatar_url,
|
||||
}))
|
||||
|
|
@ -274,8 +278,8 @@ fn post_discord_message(
|
|||
channel_id: &str,
|
||||
content: &str,
|
||||
reply_to_message_id: Option<&str>,
|
||||
persona: Option<&FacePersona>,
|
||||
config: &FaceConfig,
|
||||
persona: Option<&DiscordPersona>,
|
||||
config: &PersonaConfig,
|
||||
artifact_dir: &Path,
|
||||
) -> Result<PostedDiscordMessage> {
|
||||
if let Some(persona) = persona {
|
||||
|
|
@ -293,7 +297,7 @@ fn post_discord_message(
|
|||
let response = Client::new()
|
||||
.post(format!("{DISCORD_API}/channels/{channel_id}/messages"))
|
||||
.header("Authorization", format!("Bot {token}"))
|
||||
.header("User-Agent", "EpiphanyFace/0.1")
|
||||
.header("User-Agent", "EpiphanyPersona/0.1")
|
||||
.json(&serde_json::json!({
|
||||
"content": content,
|
||||
"message_reference": reply_to_message_id.map(|message_id| serde_json::json!({
|
||||
|
|
@ -316,8 +320,8 @@ fn post_discord_persona_message(
|
|||
channel_id: &str,
|
||||
content: &str,
|
||||
reply_to_message_id: Option<&str>,
|
||||
persona: &FacePersona,
|
||||
config: &FaceConfig,
|
||||
persona: &DiscordPersona,
|
||||
config: &PersonaConfig,
|
||||
artifact_dir: &Path,
|
||||
) -> Result<PostedDiscordMessage> {
|
||||
let target = resolve_webhook_target(token, channel_id)?;
|
||||
|
|
@ -361,7 +365,7 @@ fn resolve_webhook_target(token: &str, channel_id: &str) -> Result<WebhookTarget
|
|||
let response = Client::new()
|
||||
.get(format!("{DISCORD_API}/channels/{channel_id}"))
|
||||
.header("Authorization", format!("Bot {token}"))
|
||||
.header("User-Agent", "EpiphanyFace/0.1")
|
||||
.header("User-Agent", "EpiphanyPersona/0.1")
|
||||
.send()
|
||||
.context("Discord channel lookup failed")?;
|
||||
let payload = decode_discord_json(response, "Discord channel lookup")?;
|
||||
|
|
@ -388,7 +392,7 @@ fn create_persona_webhook(token: &str, channel_id: &str) -> Result<CachedWebhook
|
|||
let response = Client::new()
|
||||
.post(format!("{DISCORD_API}/channels/{channel_id}/webhooks"))
|
||||
.header("Authorization", format!("Bot {token}"))
|
||||
.header("User-Agent", "EpiphanyFace/0.1")
|
||||
.header("User-Agent", "EpiphanyPersona/0.1")
|
||||
.json(&serde_json::json!({"name": "Epiphany Persona Pipe"}))
|
||||
.send()
|
||||
.context("Discord webhook creation failed")?;
|
||||
|
|
@ -411,7 +415,7 @@ fn execute_persona_webhook(
|
|||
target: &WebhookTarget,
|
||||
content: &str,
|
||||
reply_to_message_id: Option<&str>,
|
||||
persona: &FacePersona,
|
||||
persona: &DiscordPersona,
|
||||
) -> Result<PostedDiscordMessage> {
|
||||
let mut url = format!(
|
||||
"{DISCORD_API}/webhooks/{}/{}?wait=true",
|
||||
|
|
@ -423,7 +427,7 @@ fn execute_persona_webhook(
|
|||
}
|
||||
let response = Client::new()
|
||||
.post(url)
|
||||
.header("User-Agent", "EpiphanyFace/0.1")
|
||||
.header("User-Agent", "EpiphanyPersona/0.1")
|
||||
.json(&serde_json::json!({
|
||||
"content": content,
|
||||
"username": persona.name,
|
||||
|
|
@ -444,35 +448,35 @@ fn execute_persona_webhook(
|
|||
}
|
||||
|
||||
fn run_smoke() -> Result<Value> {
|
||||
let temp_dir = scoped_temp_dir("epiphany-face-discord-smoke")?;
|
||||
let config = FaceConfig {
|
||||
let temp_dir = scoped_temp_dir("epiphany-persona-discord-smoke")?;
|
||||
let config = PersonaConfig {
|
||||
allowed_channel_name: "#aquarium".to_string(),
|
||||
allowed_channel_id: None,
|
||||
allowed_channel_id_env: Some("EPIPHANY_FACE_AQUARIUM_CHANNEL_ID_TEST".to_string()),
|
||||
allowed_channel_id_env: Some("EPIPHANY_PERSONA_AQUARIUM_CHANNEL_ID_TEST".to_string()),
|
||||
bot_token_env: Some("DISCORD_BOT_TOKEN_TEST".to_string()),
|
||||
persona_name: Some("Smoke Face".to_string()),
|
||||
persona_avatar_url: Some("https://example.invalid/face.png".to_string()),
|
||||
persona_name: Some("Smoke Persona".to_string()),
|
||||
persona_avatar_url: Some("https://example.invalid/persona.png".to_string()),
|
||||
webhook_cache_path: Some(temp_dir.join("webhook-cache.json")),
|
||||
};
|
||||
unsafe {
|
||||
env::remove_var("EPIPHANY_FACE_AQUARIUM_CHANNEL_ID_TEST");
|
||||
env::remove_var("EPIPHANY_PERSONA_AQUARIUM_CHANNEL_ID_TEST");
|
||||
}
|
||||
let draft = run_draft(
|
||||
"Face notices Proprioception and Soul disagree about evidence shape.",
|
||||
"Persona notices Proprioception and Soul disagree about evidence shape.",
|
||||
&config,
|
||||
&temp_dir,
|
||||
"draft",
|
||||
"drafted without posting",
|
||||
)?;
|
||||
let bubble = run_bubble(
|
||||
"Face opens an Aquarium bubble even while Discord is unavailable.",
|
||||
"Persona opens an Aquarium bubble even while Discord is unavailable.",
|
||||
&temp_dir,
|
||||
"smoke/face",
|
||||
"smoke/persona",
|
||||
"ready",
|
||||
"attentive",
|
||||
)?;
|
||||
let blocked = run_post(
|
||||
"Face should not post without a configured #aquarium channel id.",
|
||||
"Persona should not post without a configured #aquarium channel id.",
|
||||
&config,
|
||||
&temp_dir,
|
||||
None,
|
||||
|
|
@ -481,14 +485,14 @@ fn run_smoke() -> Result<Value> {
|
|||
None,
|
||||
)?;
|
||||
unsafe {
|
||||
env::set_var("EPIPHANY_FACE_AQUARIUM_CHANNEL_ID_TEST", "123");
|
||||
env::set_var("EPIPHANY_PERSONA_AQUARIUM_CHANNEL_ID_TEST", "123");
|
||||
}
|
||||
let wrong = run_post(
|
||||
"Face should not post outside #aquarium.",
|
||||
"Persona should not post outside #aquarium.",
|
||||
&config,
|
||||
&temp_dir,
|
||||
Some("456".to_string()),
|
||||
Some("Wrong Face".to_string()),
|
||||
Some("Wrong Persona".to_string()),
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
|
|
@ -513,7 +517,7 @@ fn run_smoke() -> Result<Value> {
|
|||
|
||||
fn write_draft(
|
||||
content: &str,
|
||||
config: &FaceConfig,
|
||||
config: &PersonaConfig,
|
||||
artifact_dir: &Path,
|
||||
status: &str,
|
||||
reason: &str,
|
||||
|
|
@ -529,7 +533,7 @@ fn write_draft(
|
|||
"persona_avatar_url": config.persona_avatar_url,
|
||||
"content": content.trim(),
|
||||
});
|
||||
let path = artifact_dir.join(format!("face-chat-{}-{}.json", now_stamp(), short_id()));
|
||||
let path = artifact_dir.join(format!("persona-chat-{}-{}.json", now_stamp(), short_id()));
|
||||
write_json(&path, &payload)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
|
@ -541,14 +545,14 @@ fn bubble_payload(content: &str, source: &str, status: &str, mood: &str) -> Valu
|
|||
"status": status,
|
||||
"source": source,
|
||||
"target": "aquarium",
|
||||
"role_id": "face",
|
||||
"agent_id": "face",
|
||||
"display_name": "Face",
|
||||
"role_id": "persona",
|
||||
"agent_id": "persona",
|
||||
"display_name": "Persona",
|
||||
"mood": mood,
|
||||
"content": content.trim(),
|
||||
"bubble": {
|
||||
"kind": "agent-chat",
|
||||
"anchorRoleId": "face",
|
||||
"anchorRoleId": "persona",
|
||||
"opensIn": "aquarium",
|
||||
"requiresDiscord": false,
|
||||
"ttlSeconds": 90,
|
||||
|
|
@ -556,7 +560,7 @@ fn bubble_payload(content: &str, source: &str, status: &str, mood: &str) -> Valu
|
|||
})
|
||||
}
|
||||
|
||||
fn latest_face_artifacts(artifact_dir: &Path, limit: usize) -> Vec<Value> {
|
||||
fn latest_persona_artifacts(artifact_dir: &Path, limit: usize) -> Vec<Value> {
|
||||
let Ok(read_dir) = fs::read_dir(artifact_dir) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
|
@ -566,7 +570,7 @@ fn latest_face_artifacts(artifact_dir: &Path, limit: usize) -> Vec<Value> {
|
|||
.filter(|path| {
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.is_some_and(|name| name.starts_with("face-") && name.ends_with(".json"))
|
||||
.is_some_and(|name| name.starts_with("persona-") && name.ends_with(".json"))
|
||||
})
|
||||
.filter_map(|path| Some((path.metadata().ok()?.modified().ok()?, path)))
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -592,7 +596,7 @@ fn latest_face_artifacts(artifact_dir: &Path, limit: usize) -> Vec<Value> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn persona_webhook_cache_path(config: &FaceConfig, artifact_dir: &Path) -> PathBuf {
|
||||
fn persona_webhook_cache_path(config: &PersonaConfig, artifact_dir: &Path) -> PathBuf {
|
||||
config
|
||||
.webhook_cache_path
|
||||
.clone()
|
||||
|
|
@ -673,7 +677,7 @@ fn strip_bom(value: &str) -> &str {
|
|||
value.strip_prefix('\u{feff}').unwrap_or(value)
|
||||
}
|
||||
|
||||
fn load_config(path: &Path) -> Result<FaceConfig> {
|
||||
fn load_config(path: &Path) -> Result<PersonaConfig> {
|
||||
let raw =
|
||||
fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
|
||||
if path
|
||||
|
|
@ -682,7 +686,7 @@ fn load_config(path: &Path) -> Result<FaceConfig> {
|
|||
{
|
||||
let payload: Value = serde_json::from_str(&raw)
|
||||
.with_context(|| format!("failed to decode {}", path.display()))?;
|
||||
return Ok(FaceConfig {
|
||||
return Ok(PersonaConfig {
|
||||
allowed_channel_name: payload["allowed_channel_name"]
|
||||
.as_str()
|
||||
.unwrap_or("#aquarium")
|
||||
|
|
@ -701,7 +705,7 @@ fn load_config(path: &Path) -> Result<FaceConfig> {
|
|||
webhook_cache_path: payload["webhook_cache_path"].as_str().map(PathBuf::from),
|
||||
});
|
||||
}
|
||||
Ok(FaceConfig {
|
||||
Ok(PersonaConfig {
|
||||
allowed_channel_name: toml_string(&raw, "allowed_channel_name")
|
||||
.unwrap_or_else(|| "#aquarium".to_string()),
|
||||
allowed_channel_id: toml_string(&raw, "allowed_channel_id"),
|
||||
|
|
@ -731,14 +735,14 @@ fn parse_quoted(value: &str) -> Option<String> {
|
|||
.map(str::to_string)
|
||||
}
|
||||
|
||||
fn allowed_channel_id(config: &FaceConfig) -> Option<String> {
|
||||
fn allowed_channel_id(config: &PersonaConfig) -> Option<String> {
|
||||
config
|
||||
.allowed_channel_id
|
||||
.clone()
|
||||
.or_else(|| config.allowed_channel_id_env.as_ref().and_then(env_value))
|
||||
}
|
||||
|
||||
fn bot_token(config: &FaceConfig) -> Option<String> {
|
||||
fn bot_token(config: &PersonaConfig) -> Option<String> {
|
||||
config.bot_token_env.as_ref().and_then(env_value)
|
||||
}
|
||||
|
||||
|
|
@ -812,6 +816,6 @@ fn next_value(args: &mut impl Iterator<Item = String>, name: &str) -> Result<Str
|
|||
|
||||
fn usage() -> Result<()> {
|
||||
Err(anyhow!(
|
||||
"usage: epiphany-face-discord <draft|bubble|post|latest|smoke> [--config <path>] [--artifact-dir <path>] [--content <text-or-path>]"
|
||||
"usage: epiphany-persona-discord <draft|bubble|post|latest|smoke> [--config <path>] [--artifact-dir <path>] [--content <text-or-path>]"
|
||||
))
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ fn main() -> Result<()> {
|
|||
let notes_dir = root.join("notes");
|
||||
let map_path = state_dir.join("map.yaml");
|
||||
let scratch_path = state_dir.join("scratch.md");
|
||||
let ledger_path = state_dir.join("ledgers.msgpack");
|
||||
let ledger_path = state_dir.join("ledgers.cc");
|
||||
let handoff_path = notes_dir.join("fresh-workspace-handoff.md");
|
||||
let plan_path = notes_dir.join("epiphany-fork-implementation-plan.md");
|
||||
let algo_map_path = notes_dir.join("epiphany-current-algorithmic-map.md");
|
||||
|
|
@ -138,11 +138,11 @@ fn add_content_checks(
|
|||
.filter(|branch| branch.status == "active")
|
||||
.count();
|
||||
findings.push(ok(format!(
|
||||
"state/ledgers.msgpack parses ({} evidence record(s))",
|
||||
"state/ledgers.cc parses ({} evidence record(s))",
|
||||
entry.evidence.len()
|
||||
)));
|
||||
findings.push(ok(format!(
|
||||
"state/ledgers.msgpack has {active} active branch(es)"
|
||||
"state/ledgers.cc has {active} active branch(es)"
|
||||
)));
|
||||
}
|
||||
Err(err) => findings.push(error(format!("state ledger parse failed: {err}"))),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ fn main() -> Result<()> {
|
|||
let store = env::args().nth(1).map(PathBuf::from).unwrap_or_else(|| {
|
||||
PathBuf::from(".epiphany-smoke")
|
||||
.join("cultmesh")
|
||||
.join("epiphany-prompt-context.ccmp")
|
||||
.join("epiphany-prompt-context.cc")
|
||||
});
|
||||
if let Some(parent) = store.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
|
|
|
|||
|
|
@ -50,12 +50,12 @@ impl Args {
|
|||
baseline: root
|
||||
.join(".epiphany-imports")
|
||||
.join("repo-personality-terrain")
|
||||
.join("baseline.msgpack"),
|
||||
.join("baseline.cc"),
|
||||
artifact_dir: root.join(".epiphany-birth-runner"),
|
||||
init_store: root.join("state").join("repo-initialization.msgpack"),
|
||||
agent_store: root.join("state").join("agents.msgpack"),
|
||||
heartbeat_store: root.join("state").join("agent-heartbeats.msgpack"),
|
||||
runtime_store: root.join("state").join("runtime-spine.msgpack"),
|
||||
init_store: root.join("state").join("repo-initialization.cc"),
|
||||
agent_store: root.join("state").join("agents.cc"),
|
||||
heartbeat_store: root.join("state").join("agent-heartbeats.cc"),
|
||||
runtime_store: root.join("state").join("runtime-spine.cc"),
|
||||
model_runtime_bin: DEFAULT_MODEL_RUNTIME_BIN.to_string(),
|
||||
model_provider: "openai-codex".to_string(),
|
||||
executor: "model-runtime".to_string(),
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ fn run_smoke() -> Result<Value> {
|
|||
"trajectory packet should write summary markdown",
|
||||
)?;
|
||||
|
||||
let init_store = artifacts.join("repo-initialization.msgpack");
|
||||
let init_store = artifacts.join("repo-initialization.cc");
|
||||
let startup = run_personality(
|
||||
&root,
|
||||
&[
|
||||
|
|
@ -255,17 +255,17 @@ fn run_smoke() -> Result<Value> {
|
|||
"--init-store",
|
||||
artifacts
|
||||
.join("birth-runner-plan")
|
||||
.join("repo-initialization.msgpack")
|
||||
.join("repo-initialization.cc")
|
||||
.to_str()
|
||||
.unwrap_or_default(),
|
||||
"--agent-store",
|
||||
root.join("state")
|
||||
.join("agents.msgpack")
|
||||
.join("agents.cc")
|
||||
.to_str()
|
||||
.unwrap_or_default(),
|
||||
"--heartbeat-store",
|
||||
root.join("state")
|
||||
.join("agent-heartbeats.msgpack")
|
||||
.join("agent-heartbeats.cc")
|
||||
.to_str()
|
||||
.unwrap_or_default(),
|
||||
"--mode",
|
||||
|
|
@ -288,7 +288,7 @@ fn run_smoke() -> Result<Value> {
|
|||
.all(|execution| execution["heartbeatParticipant"].is_null()),
|
||||
"birth runner executions should not be heartbeat participants",
|
||||
)?;
|
||||
let heartbeat_store = artifacts.join("startup-heartbeats.msgpack");
|
||||
let heartbeat_store = artifacts.join("startup-heartbeats.cc");
|
||||
run_heartbeat(
|
||||
&root,
|
||||
&[
|
||||
|
|
@ -356,7 +356,7 @@ fn run_smoke() -> Result<Value> {
|
|||
trajectory_result_path.to_str().unwrap_or_default(),
|
||||
"--agent-store",
|
||||
root.join("state")
|
||||
.join("agents.msgpack")
|
||||
.join("agents.cc")
|
||||
.to_str()
|
||||
.unwrap_or_default(),
|
||||
"--apply-self-patches",
|
||||
|
|
@ -367,8 +367,8 @@ fn run_smoke() -> Result<Value> {
|
|||
accepted_trajectory["record"]["kind"] == "repo-trajectory",
|
||||
"accept-init should record trajectory birth",
|
||||
)?;
|
||||
let agent_store = artifacts.join("startup-agents.msgpack");
|
||||
fs::copy(root.join("state").join("agents.msgpack"), &agent_store)
|
||||
let agent_store = artifacts.join("startup-agents.cc");
|
||||
fs::copy(root.join("state").join("agents.cc"), &agent_store)
|
||||
.context("failed to copy role memory store for startup smoke")?;
|
||||
let accepted_personality = run_personality(
|
||||
&root,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ const TRAJECTORY_SOURCE_MAX_TOTAL_BYTES: usize = 72_000;
|
|||
|
||||
const ROLES: &[&str] = &[
|
||||
"coordinator",
|
||||
"face",
|
||||
"persona",
|
||||
"imagination",
|
||||
"research",
|
||||
"modeling",
|
||||
|
|
@ -76,7 +76,7 @@ const AXES: &[&str] = &[
|
|||
"temporal_pressure",
|
||||
"experimental_heat",
|
||||
"churn_spiral_risk",
|
||||
"interface_orientation",
|
||||
"interpersona_orientation",
|
||||
"aesthetic_appetite",
|
||||
"social_surface",
|
||||
"sensory_salience",
|
||||
|
|
@ -608,7 +608,7 @@ fn parse_bool_arg(name: &str, value: &str) -> Result<bool> {
|
|||
fn run_scout(args: ScoutArgs) -> Result<Value> {
|
||||
fs::create_dir_all(&args.artifact_dir)
|
||||
.with_context(|| format!("failed to create {}", args.artifact_dir.display()))?;
|
||||
let store_path = args.artifact_dir.join("baseline.msgpack");
|
||||
let store_path = args.artifact_dir.join("baseline.cc");
|
||||
let mut cache = repo_personality_cache(&store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
|
||||
|
|
@ -725,7 +725,7 @@ fn project_repo_to_store(
|
|||
let profile = reduce_report(&report);
|
||||
let trajectory = derive_trajectory_report(repo, &report, &profile)?;
|
||||
|
||||
let store_path = artifact_dir.join("projection.msgpack");
|
||||
let store_path = artifact_dir.join("projection.cc");
|
||||
let mut cache = repo_personality_cache(&store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
cache.put::<RepoTerrainReport>(report.repo_id.clone(), &report)?;
|
||||
|
|
@ -1639,7 +1639,7 @@ const COORDINATOR_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [
|
|||
},
|
||||
];
|
||||
|
||||
const FACE_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [
|
||||
const PERSONA_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [
|
||||
CanonicalTraitTemplate {
|
||||
group_name: "underlying_organization",
|
||||
trait_name: "multi_lane_attention",
|
||||
|
|
@ -1990,7 +1990,7 @@ fn extract_trait_seed_candidates(packet: &Value) -> Result<Vec<AgentCanonicalTra
|
|||
fn role_trait_templates(role_id: &str) -> Result<&'static [CanonicalTraitTemplate; 6]> {
|
||||
match role_id {
|
||||
"coordinator" => Ok(&COORDINATOR_TRAIT_TEMPLATES),
|
||||
"face" => Ok(&FACE_TRAIT_TEMPLATES),
|
||||
"persona" => Ok(&PERSONA_TRAIT_TEMPLATES),
|
||||
"imagination" => Ok(&IMAGINATION_TRAIT_TEMPLATES),
|
||||
"research" => Ok(&RESEARCH_TRAIT_TEMPLATES),
|
||||
"modeling" => Ok(&MODELING_TRAIT_TEMPLATES),
|
||||
|
|
@ -2227,7 +2227,7 @@ fn extract_memory_self_patches(result: &Value) -> Result<Vec<InitializationSelfP
|
|||
fn role_id_for_agent_id(agent_id: &str) -> Option<&'static str> {
|
||||
match agent_id {
|
||||
"epiphany.self" => Some("coordinator"),
|
||||
"epiphany.face" => Some("face"),
|
||||
"epiphany.persona" => Some("persona"),
|
||||
"epiphany.imagination" => Some("imagination"),
|
||||
"epiphany.eyes" => Some("research"),
|
||||
"epiphany.proprioception" => Some("modeling"),
|
||||
|
|
@ -2422,7 +2422,7 @@ fn memory_role_filter(role_id: &str) -> &'static str {
|
|||
"coordinator" => {
|
||||
"Distill routing doctrine, authority boundaries, review gates, state acceptance rules, and failure patterns that should make Self stricter."
|
||||
}
|
||||
"face" => {
|
||||
"persona" => {
|
||||
"Distill public voice, Aquarium affordances, Discord/social boundaries, user preference, and what internal state may be surfaced without leaking sealed thoughts."
|
||||
}
|
||||
"imagination" => {
|
||||
|
|
@ -2447,7 +2447,7 @@ fn memory_role_filter(role_id: &str) -> &'static str {
|
|||
fn memory_role_source_kinds(role_id: &str) -> &'static [&'static str] {
|
||||
match role_id {
|
||||
"coordinator" => &["doctrine", "state", "documentation", "contract"],
|
||||
"face" => &["doctrine", "documentation", "state"],
|
||||
"persona" => &["doctrine", "documentation", "state"],
|
||||
"imagination" => &["documentation", "state", "research"],
|
||||
"research" => &["research", "documentation", "readme", "code"],
|
||||
"modeling" => &["documentation", "code", "contract", "state"],
|
||||
|
|
@ -3555,7 +3555,7 @@ fn score_axes(
|
|||
record!(
|
||||
"verification_environment_need",
|
||||
(score(&scores, "runtime_proximity") * 0.55
|
||||
+ score(&scores, "interface_orientation") * 0.2
|
||||
+ score(&scores, "interpersona_orientation") * 0.2
|
||||
+ score(&scores, "actuation_risk") * 0.35)
|
||||
.min(1.0),
|
||||
"claims need runtime, editor, browser, provider, or service receipts"
|
||||
|
|
@ -3596,7 +3596,7 @@ fn score_axes(
|
|||
"large churn, experiment heat, and weak receipts"
|
||||
);
|
||||
record!(
|
||||
"interface_orientation",
|
||||
"interpersona_orientation",
|
||||
(ui_ratio * 5.0
|
||||
+ touch_ratio(history.ui_touches, changed)
|
||||
+ family(families, "epiphany_spine") * 0.2)
|
||||
|
|
@ -3605,7 +3605,7 @@ fn score_axes(
|
|||
);
|
||||
record!(
|
||||
"aesthetic_appetite",
|
||||
(score(&scores, "interface_orientation") * 0.45
|
||||
(score(&scores, "interpersona_orientation") * 0.45
|
||||
+ family(families, "research_workbench") * 0.25
|
||||
+ content_ratio * 1.5)
|
||||
.min(1.0),
|
||||
|
|
@ -3621,7 +3621,7 @@ fn score_axes(
|
|||
);
|
||||
record!(
|
||||
"sensory_salience",
|
||||
(score(&scores, "interface_orientation") * 0.45
|
||||
(score(&scores, "interpersona_orientation") * 0.45
|
||||
+ score(&scores, "aesthetic_appetite") * 0.45
|
||||
+ score(&scores, "runtime_proximity") * 0.15)
|
||||
.min(1.0),
|
||||
|
|
@ -3635,7 +3635,8 @@ fn score_axes(
|
|||
);
|
||||
record!(
|
||||
"speech_pressure",
|
||||
(score(&scores, "social_surface") * 0.45 + score(&scores, "interface_orientation") * 0.2)
|
||||
(score(&scores, "social_surface") * 0.45
|
||||
+ score(&scores, "interpersona_orientation") * 0.2)
|
||||
.min(1.0),
|
||||
"public speech or user-facing surfaces"
|
||||
);
|
||||
|
|
@ -3878,9 +3879,9 @@ fn role_axes(role_id: &str) -> &'static [&'static str] {
|
|||
"churn_spiral_risk",
|
||||
"production_pressure",
|
||||
],
|
||||
"face" => &[
|
||||
"persona" => &[
|
||||
"social_surface",
|
||||
"interface_orientation",
|
||||
"interpersona_orientation",
|
||||
"sensory_salience",
|
||||
"speech_pressure",
|
||||
"editorial_restraint",
|
||||
|
|
@ -3917,7 +3918,7 @@ fn role_axes(role_id: &str) -> &'static [&'static str] {
|
|||
"evidence_appetite",
|
||||
"verification_environment_need",
|
||||
"actuation_risk",
|
||||
"interface_orientation",
|
||||
"interpersona_orientation",
|
||||
"content_canon_bias",
|
||||
],
|
||||
"reorientation" => &[
|
||||
|
|
@ -3941,7 +3942,7 @@ fn role_axis_average(axes: &BTreeMap<String, f64>, names: &[&str]) -> f64 {
|
|||
fn role_display(role_id: &str) -> &'static str {
|
||||
match role_id {
|
||||
"coordinator" => "Self",
|
||||
"face" => "Face",
|
||||
"persona" => "Persona",
|
||||
"imagination" => "Imagination",
|
||||
"research" => "Eyes",
|
||||
"modeling" => "Proprioception",
|
||||
|
|
@ -3956,7 +3957,7 @@ fn role_value_candidate(role_id: &str) -> &'static str {
|
|||
"coordinator" => {
|
||||
"Coordinate through typed authority and challenge pattern-completion theater."
|
||||
}
|
||||
"face" => {
|
||||
"persona" => {
|
||||
"Surface state through the public mouth without turning internals into chat endpoints."
|
||||
}
|
||||
"imagination" => {
|
||||
|
|
@ -4503,12 +4504,12 @@ fn print_usage() {
|
|||
eprintln!(
|
||||
"usage: epiphany-repo-personality <scout|project|agent-packet|trajectory-packet|memory-packet|startup|accept-init|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\
|
||||
trajectory-packet --store <projection.msgpack> --artifact-dir <path> [--repo-id <id>]\n\
|
||||
memory-packet --store <projection.msgpack> --artifact-dir <path> [--repo-id <id>]\n\
|
||||
startup --repo <path> --baseline <baseline.msgpack> --artifact-dir <path> --init-store <init.msgpack>\n\
|
||||
accept-init --init-store <init.msgpack> --packet <packet.json> --kind <repo-trajectory|repo-personality|repo-memory> [--accepted-by <name>] [--summary <text>] [--result <distiller-result.json>] [--agent-store <agents.msgpack>] [--apply-self-patches <true|false>] [--apply-trait-seeds <true|false>] [--heartbeat-store <heartbeats.msgpack>] [--apply-heartbeat-seeds <true|false>]\n\
|
||||
status --store <baseline-or-projection.msgpack>"
|
||||
project --repo <path> --baseline <baseline.cc> --artifact-dir <path>\n\
|
||||
agent-packet --store <projection.cc> --artifact-dir <path> [--repo-id <id>]\n\
|
||||
trajectory-packet --store <projection.cc> --artifact-dir <path> [--repo-id <id>]\n\
|
||||
memory-packet --store <projection.cc> --artifact-dir <path> [--repo-id <id>]\n\
|
||||
startup --repo <path> --baseline <baseline.cc> --artifact-dir <path> --init-store <init.cc>\n\
|
||||
accept-init --init-store <init.cc> --packet <packet.json> --kind <repo-trajectory|repo-personality|repo-memory> [--accepted-by <name>] [--summary <text>] [--result <distiller-result.json>] [--agent-store <agents.cc>] [--apply-self-patches <true|false>] [--apply-trait-seeds <true|false>] [--heartbeat-store <heartbeats.cc>] [--apply-heartbeat-seeds <true|false>]\n\
|
||||
status --store <baseline-or-projection.cc>"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use std::env;
|
|||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
const DEFAULT_STORE: &str = "state/runtime-spine.msgpack";
|
||||
const DEFAULT_STORE: &str = "state/runtime-spine.cc";
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse()?;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ fn main() -> Result<()> {
|
|||
let root = env::current_dir().context("failed to resolve current directory")?;
|
||||
let state_dir = root.join("state");
|
||||
let map_path = state_dir.join("map.yaml");
|
||||
let ledger_store = state_dir.join("ledgers.msgpack");
|
||||
let ledger_store = state_dir.join("ledgers.cc");
|
||||
let mut args = env::args().skip(1);
|
||||
let Some(command) = args.next() else {
|
||||
print_usage();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use chrono::Utc;
|
||||
use epiphany_core::import_gjallar_daemon_affordances;
|
||||
use epiphany_core::query_epiphany_local_verse_context;
|
||||
use epiphany_core::seed_epiphany_local_verse_context;
|
||||
use serde_json::json;
|
||||
|
|
@ -16,10 +17,24 @@ fn main() -> Result<()> {
|
|||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
if let Some(gjallar_store) = args.gjallar_affordance_store.as_ref() {
|
||||
import_gjallar_daemon_affordances(
|
||||
&args.store,
|
||||
gjallar_store,
|
||||
args.runtime_id.clone(),
|
||||
)?;
|
||||
}
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id)?;
|
||||
println!("{}", serde_json::to_string_pretty(&context)?);
|
||||
}
|
||||
"query" => {
|
||||
if let Some(gjallar_store) = args.gjallar_affordance_store.as_ref() {
|
||||
import_gjallar_daemon_affordances(
|
||||
&args.store,
|
||||
gjallar_store,
|
||||
args.runtime_id.clone(),
|
||||
)?;
|
||||
}
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id)?;
|
||||
println!("{}", serde_json::to_string_pretty(&context)?);
|
||||
}
|
||||
|
|
@ -29,6 +44,13 @@ fn main() -> Result<()> {
|
|||
args.runtime_id.clone(),
|
||||
"2026-06-02T00:00:00Z",
|
||||
)?;
|
||||
if let Some(gjallar_store) = args.gjallar_affordance_store.as_ref() {
|
||||
import_gjallar_daemon_affordances(
|
||||
&args.store,
|
||||
gjallar_store,
|
||||
args.runtime_id.clone(),
|
||||
)?;
|
||||
}
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id)?;
|
||||
if context.verse_policies.len() != 3 {
|
||||
anyhow::bail!("local Verse query smoke expected three Verse policies");
|
||||
|
|
@ -41,6 +63,9 @@ fn main() -> Result<()> {
|
|||
if context.contract_summaries.len() < 6 {
|
||||
anyhow::bail!("local Verse query smoke expected organ contract summaries");
|
||||
}
|
||||
if args.gjallar_affordance_store.is_some() && context.daemon_affordances.is_empty() {
|
||||
anyhow::bail!("local Verse query smoke expected imported daemon affordances");
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({
|
||||
|
|
@ -50,6 +75,7 @@ fn main() -> Result<()> {
|
|||
"verses": context.verse_policies.len(),
|
||||
"globalRooms": context.global_room_policies.len(),
|
||||
"contracts": context.contract_summaries.len(),
|
||||
"daemonAffordances": context.daemon_affordances.len(),
|
||||
}))?
|
||||
);
|
||||
}
|
||||
|
|
@ -62,14 +88,16 @@ struct Args {
|
|||
command: String,
|
||||
store: PathBuf,
|
||||
runtime_id: String,
|
||||
gjallar_affordance_store: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn parse() -> Result<Self> {
|
||||
let mut values = env::args().skip(1);
|
||||
let command = values.next().unwrap_or_else(|| "query".to_string());
|
||||
let mut store = PathBuf::from(".epiphany-run/cultmesh/epiphany-local.ccmp");
|
||||
let mut store = PathBuf::from(".epiphany-run/cultmesh/epiphany-local.cc");
|
||||
let mut runtime_id = "epiphany-local".to_string();
|
||||
let mut gjallar_affordance_store = None;
|
||||
|
||||
while let Some(arg) = values.next() {
|
||||
match arg.as_str() {
|
||||
|
|
@ -79,6 +107,13 @@ impl Args {
|
|||
"--runtime-id" => {
|
||||
runtime_id = values.next().context("missing --runtime-id value")?;
|
||||
}
|
||||
"--gjallar-affordance-store" => {
|
||||
gjallar_affordance_store = Some(PathBuf::from(
|
||||
values
|
||||
.next()
|
||||
.context("missing --gjallar-affordance-store value")?,
|
||||
));
|
||||
}
|
||||
_ => anyhow::bail!("unknown argument {arg:?}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +126,7 @@ impl Args {
|
|||
command,
|
||||
store,
|
||||
runtime_id,
|
||||
gjallar_affordance_store,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ pub const EPIPHANY_CULTMESH_CONTINUITY_CONTRACT_TYPE: &str =
|
|||
"epiphany.cultmesh.continuity_contract";
|
||||
pub const EPIPHANY_CULTMESH_CONTINUITY_CONTRACT_SCHEMA_VERSION: &str =
|
||||
"epiphany.cultmesh.continuity_contract.v0";
|
||||
pub const GJALLAR_AFFORDANCE_SCHEMA_VERSION: &str = "gjallar.affordance.v1";
|
||||
pub const EPIPHANY_CULTMESH_INTERNAL_VERSE_ID: &str = "epiphany-internal";
|
||||
pub const EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID: &str = "gamecult-local";
|
||||
pub const EPIPHANY_CULTMESH_GLOBAL_VERSE_ID: &str = "epiphany-global";
|
||||
|
|
@ -165,6 +166,8 @@ pub struct EpiphanyCultMeshOperatorSnapshotEntry {
|
|||
pub available_actions: Vec<String>,
|
||||
#[cultcache(key = 17)]
|
||||
pub notes: Vec<String>,
|
||||
#[cultcache(key = 18, default)]
|
||||
pub implementation_commit_requires_modeling_refresh: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
|
|
@ -292,7 +295,7 @@ pub struct EpiphanyCultMeshGlobalRoomPolicyEntry {
|
|||
#[cultcache(key = 6)]
|
||||
pub threaded: bool,
|
||||
#[cultcache(key = 7)]
|
||||
pub face_posting_allowed: bool,
|
||||
pub persona_posting_allowed: bool,
|
||||
#[cultcache(key = 8)]
|
||||
pub untrusted_ingress_allowed: bool,
|
||||
}
|
||||
|
|
@ -465,6 +468,29 @@ pub struct EpiphanyCultMeshContinuityContractEntry {
|
|||
pub notes: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(type = "gjallar.affordance", schema = "gjallar.affordance.v1")]
|
||||
pub struct GjallarAffordanceEntry {
|
||||
#[cultcache(key = 0)]
|
||||
pub affordance_id: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub source_record: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub verse_id: Option<String>,
|
||||
#[cultcache(key = 3)]
|
||||
pub surface_kind: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub action: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub authority: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub status: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub provenance: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub observed_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EpiphanyLocalVerseContext {
|
||||
|
|
@ -482,6 +508,7 @@ pub struct EpiphanyLocalVerseContext {
|
|||
pub latest_operator_run_intent: Option<EpiphanyCultMeshOperatorRunIntentEntry>,
|
||||
pub latest_operator_run_receipt: Option<EpiphanyCultMeshOperatorRunReceiptEntry>,
|
||||
pub contract_summaries: Vec<EpiphanyLocalVerseContractSummary>,
|
||||
pub daemon_affordances: Vec<EpiphanyLocalDaemonAffordanceSummary>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
|
|
@ -495,6 +522,20 @@ pub struct EpiphanyLocalVerseContractSummary {
|
|||
pub receipt_document_types: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EpiphanyLocalDaemonAffordanceSummary {
|
||||
pub affordance_id: String,
|
||||
pub source_record: String,
|
||||
pub verse_id: Option<String>,
|
||||
pub surface_kind: String,
|
||||
pub action: String,
|
||||
pub authority: String,
|
||||
pub status: String,
|
||||
pub provenance: String,
|
||||
pub observed_at: String,
|
||||
}
|
||||
|
||||
cultmesh_documents!(EpiphanyCultMeshDocuments {
|
||||
EpiphanyCultMeshStatusEntry => EPIPHANY_CULTMESH_STATUS_SCHEMA_VERSION,
|
||||
EpiphanyCultMeshOperatorStatusEntry => EPIPHANY_CULTMESH_OPERATOR_STATUS_SCHEMA_VERSION,
|
||||
|
|
@ -509,6 +550,7 @@ cultmesh_documents!(EpiphanyCultMeshDocuments {
|
|||
EpiphanyCultMeshHandsContractEntry => EPIPHANY_CULTMESH_HANDS_CONTRACT_SCHEMA_VERSION,
|
||||
EpiphanyCultMeshSoulContractEntry => EPIPHANY_CULTMESH_SOUL_CONTRACT_SCHEMA_VERSION,
|
||||
EpiphanyCultMeshContinuityContractEntry => EPIPHANY_CULTMESH_CONTINUITY_CONTRACT_SCHEMA_VERSION,
|
||||
GjallarAffordanceEntry => GJALLAR_AFFORDANCE_SCHEMA_VERSION,
|
||||
});
|
||||
|
||||
pub fn open_epiphany_cultmesh_node(
|
||||
|
|
@ -609,8 +651,14 @@ pub fn epiphany_cultmesh_operator_snapshot_from_status_json(
|
|||
let state_status = pointer_text(status_json, "/scene/scene/stateStatus", "unknown");
|
||||
let crrc_action = pointer_text(status_json, "/crrc/recommendation/action", "unknown");
|
||||
let reorient_action = pointer_text(status_json, "/reorient/decision/action", "unknown");
|
||||
let implementation_commit_requires_modeling_refresh = pointer_bool(
|
||||
status_json,
|
||||
"/coordinator/sourceSignals/implementationCommitRequiresModelingRefresh",
|
||||
);
|
||||
let operator_status = if state_status == "missing" || crrc_action == "regatherManually" {
|
||||
"needs-regather"
|
||||
} else if implementation_commit_requires_modeling_refresh {
|
||||
"needs-modeling-refresh"
|
||||
} else {
|
||||
"ready"
|
||||
};
|
||||
|
|
@ -637,11 +685,22 @@ pub fn epiphany_cultmesh_operator_snapshot_from_status_json(
|
|||
next_action: pointer_text(status_json, "/reorient/decision/nextAction", "none"),
|
||||
artifact_refs,
|
||||
available_actions: pointer_string_array(status_json, "/scene/scene/availableActions")?,
|
||||
notes: vec![
|
||||
notes: operator_snapshot_notes(implementation_commit_requires_modeling_refresh),
|
||||
implementation_commit_requires_modeling_refresh,
|
||||
})
|
||||
}
|
||||
|
||||
fn operator_snapshot_notes(implementation_commit_requires_modeling_refresh: bool) -> Vec<String> {
|
||||
let mut notes = vec![
|
||||
"Snapshot is derived from the operator-safe MVP status artifact; raw JSON remains an edge artifact, not internal state.".to_string(),
|
||||
"Codex app-server remains compatibility transport for this source until the status surface is native end to end.".to_string(),
|
||||
],
|
||||
})
|
||||
];
|
||||
if implementation_commit_requires_modeling_refresh {
|
||||
notes.push(
|
||||
"Hands commit evidence is newer than the accepted Proprioception map; refresh modeling before another Implementation turn.".to_string(),
|
||||
);
|
||||
}
|
||||
notes
|
||||
}
|
||||
|
||||
pub fn write_epiphany_cultmesh_operator_snapshot(
|
||||
|
|
@ -813,9 +872,69 @@ pub fn query_epiphany_local_verse_context(
|
|||
latest_operator_run_intent: node.get(EPIPHANY_CULTMESH_OPERATOR_RUN_INTENT_LATEST_KEY)?,
|
||||
latest_operator_run_receipt: node.get(EPIPHANY_CULTMESH_OPERATOR_RUN_RECEIPT_LATEST_KEY)?,
|
||||
contract_summaries,
|
||||
daemon_affordances: query_gjallar_daemon_affordances_from_node(&node)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn query_gjallar_daemon_affordances(
|
||||
store_path: impl AsRef<Path>,
|
||||
runtime_id: impl Into<String>,
|
||||
) -> Result<Vec<EpiphanyLocalDaemonAffordanceSummary>> {
|
||||
let node = open_epiphany_cultmesh_node(store_path, runtime_id)?;
|
||||
query_gjallar_daemon_affordances_from_node(&node)
|
||||
}
|
||||
|
||||
pub fn import_gjallar_daemon_affordances(
|
||||
local_store_path: impl AsRef<Path>,
|
||||
gjallar_store_path: impl AsRef<Path>,
|
||||
runtime_id: impl Into<String>,
|
||||
) -> Result<Vec<EpiphanyLocalDaemonAffordanceSummary>> {
|
||||
let runtime_id = runtime_id.into();
|
||||
let gjallar_node =
|
||||
open_epiphany_cultmesh_node(gjallar_store_path, format!("{runtime_id}-gjallar-import"))?;
|
||||
let affordances = gjallar_node.cache().get_all::<GjallarAffordanceEntry>()?;
|
||||
|
||||
let mut local_node = open_epiphany_cultmesh_node(local_store_path, runtime_id)?;
|
||||
for affordance in &affordances {
|
||||
local_node.put(affordance.affordance_id.clone(), affordance)?;
|
||||
}
|
||||
local_node.flush()?;
|
||||
|
||||
Ok(affordances
|
||||
.into_iter()
|
||||
.map(EpiphanyLocalDaemonAffordanceSummary::from)
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn query_gjallar_daemon_affordances_from_node(
|
||||
node: &CultMeshNode,
|
||||
) -> Result<Vec<EpiphanyLocalDaemonAffordanceSummary>> {
|
||||
let mut summaries: Vec<_> = node
|
||||
.cache()
|
||||
.get_all::<GjallarAffordanceEntry>()?
|
||||
.into_iter()
|
||||
.map(EpiphanyLocalDaemonAffordanceSummary::from)
|
||||
.collect();
|
||||
summaries.sort_by(|left, right| left.affordance_id.cmp(&right.affordance_id));
|
||||
Ok(summaries)
|
||||
}
|
||||
|
||||
impl From<GjallarAffordanceEntry> for EpiphanyLocalDaemonAffordanceSummary {
|
||||
fn from(value: GjallarAffordanceEntry) -> Self {
|
||||
Self {
|
||||
affordance_id: value.affordance_id,
|
||||
source_record: value.source_record,
|
||||
verse_id: value.verse_id,
|
||||
surface_kind: value.surface_kind,
|
||||
action: value.action,
|
||||
authority: value.authority,
|
||||
status: value.status,
|
||||
provenance: value.provenance,
|
||||
observed_at: value.observed_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EpiphanyCultMeshContractSummarySource: DatabaseEntry {
|
||||
fn contract_id(&self) -> &str;
|
||||
fn verse_id(&self) -> &str;
|
||||
|
|
@ -906,6 +1025,13 @@ fn pointer_text(value: &Value, pointer: &str, fallback: &str) -> String {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
fn pointer_bool(value: &Value, pointer: &str) -> bool {
|
||||
value
|
||||
.pointer(pointer)
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn pointer_string_array(value: &Value, pointer: &str) -> Result<Vec<String>> {
|
||||
let Some(items) = value.pointer(pointer) else {
|
||||
return Ok(Vec::new());
|
||||
|
|
@ -993,8 +1119,8 @@ pub fn epiphany_cultmesh_global_room_policies() -> Vec<EpiphanyCultMeshGlobalRoo
|
|||
),
|
||||
(
|
||||
"faces",
|
||||
"Faces",
|
||||
"Public Face identity, voice, social surface, and community-facing presence.",
|
||||
"Personas",
|
||||
"Public Persona identity, voice, social surface, and community-facing presence.",
|
||||
),
|
||||
(
|
||||
"gamecult",
|
||||
|
|
@ -1015,10 +1141,10 @@ pub fn epiphany_cultmesh_global_room_policies() -> Vec<EpiphanyCultMeshGlobalRoo
|
|||
topic: topic.to_string(),
|
||||
purpose: purpose.to_string(),
|
||||
posting_policy:
|
||||
"Faces may post public, non-private, citation/provenance-bearing thread roots and replies; local adoption still requires review."
|
||||
"Personas may post public, non-private, citation/provenance-bearing thread roots and replies; local adoption still requires review."
|
||||
.to_string(),
|
||||
threaded: true,
|
||||
face_posting_allowed: true,
|
||||
persona_posting_allowed: true,
|
||||
untrusted_ingress_allowed: true,
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -1232,7 +1358,7 @@ mod tests {
|
|||
#[test]
|
||||
fn epiphany_status_round_trips_through_cultmesh() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-local.ccmp");
|
||||
let store = temp.path().join("epiphany-local.cc");
|
||||
let status = EpiphanyCultMeshStatusEntry {
|
||||
schema_version: EPIPHANY_CULTMESH_STATUS_SCHEMA_VERSION.to_string(),
|
||||
runtime_id: "epiphany-test".to_string(),
|
||||
|
|
@ -1253,7 +1379,7 @@ mod tests {
|
|||
#[test]
|
||||
fn operator_status_round_trips_as_native_cultmesh_document() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-operator-status.ccmp");
|
||||
let store = temp.path().join("epiphany-operator-status.cc");
|
||||
let status =
|
||||
default_epiphany_cultmesh_operator_status("epiphany-test", "2026-05-27T00:00:00Z");
|
||||
|
||||
|
|
@ -1273,7 +1399,7 @@ mod tests {
|
|||
#[test]
|
||||
fn operator_snapshot_distills_status_json_into_typed_cultmesh_document() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-operator-snapshot.ccmp");
|
||||
let store = temp.path().join("epiphany-operator-snapshot.cc");
|
||||
let status_json = serde_json::json!({
|
||||
"threadId": "thread-test",
|
||||
"scene": {
|
||||
|
|
@ -1299,7 +1425,10 @@ mod tests {
|
|||
}
|
||||
},
|
||||
"coordinator": {
|
||||
"action": "wait"
|
||||
"action": "wait",
|
||||
"sourceSignals": {
|
||||
"implementationCommitRequiresModelingRefresh": true
|
||||
}
|
||||
},
|
||||
"rawResult": {
|
||||
"sealed": true
|
||||
|
|
@ -1318,6 +1447,25 @@ mod tests {
|
|||
assert_eq!(snapshot.thread_id, "thread-test");
|
||||
assert_eq!(snapshot.available_actions, vec!["crrc", "roles"]);
|
||||
assert_eq!(snapshot.artifact_refs, vec![".epiphany-run/status.json"]);
|
||||
assert!(snapshot.implementation_commit_requires_modeling_refresh);
|
||||
assert!(
|
||||
snapshot
|
||||
.notes
|
||||
.iter()
|
||||
.any(|note| note.contains("refresh modeling"))
|
||||
);
|
||||
let mut ready_debt_status_json = status_json.clone();
|
||||
ready_debt_status_json["scene"]["scene"]["stateStatus"] = serde_json::json!("ready");
|
||||
ready_debt_status_json["crrc"]["recommendation"]["action"] = serde_json::json!("continue");
|
||||
let ready_debt_snapshot = epiphany_cultmesh_operator_snapshot_from_status_json(
|
||||
"epiphany-test",
|
||||
"snapshot-test-ready-debt",
|
||||
"2026-05-27T00:00:00Z",
|
||||
"status",
|
||||
".epiphany-run/status.json",
|
||||
&ready_debt_status_json,
|
||||
)?;
|
||||
assert_eq!(ready_debt_snapshot.status, "needs-modeling-refresh");
|
||||
|
||||
write_epiphany_cultmesh_operator_snapshot(&store, snapshot.clone())?;
|
||||
assert_eq!(
|
||||
|
|
@ -1340,7 +1488,7 @@ mod tests {
|
|||
#[test]
|
||||
fn operator_run_intent_and_receipt_round_trip_as_native_cultmesh_documents() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-operator-run.ccmp");
|
||||
let store = temp.path().join("epiphany-operator-run.cc");
|
||||
let intent = EpiphanyCultMeshOperatorRunIntentEntry {
|
||||
schema_version: EPIPHANY_CULTMESH_OPERATOR_RUN_INTENT_SCHEMA_VERSION.to_string(),
|
||||
runtime_id: "epiphany-test".to_string(),
|
||||
|
|
@ -1371,7 +1519,7 @@ mod tests {
|
|||
result_path: ".epiphany-run/run-test/status.json".to_string(),
|
||||
artifact_root: ".epiphany-run/run-test".to_string(),
|
||||
dogfood_root: ".epiphany-dogfood/run-test".to_string(),
|
||||
operator_snapshot_store: ".epiphany-run/cultmesh/operator-snapshots.ccmp".to_string(),
|
||||
operator_snapshot_store: ".epiphany-run/cultmesh/operator-snapshots.cc".to_string(),
|
||||
operator_snapshot_id: "run-test-status".to_string(),
|
||||
artifact_refs: vec![".epiphany-run/run-test/status.json".to_string()],
|
||||
notes: vec!["receipt".to_string()],
|
||||
|
|
@ -1405,7 +1553,7 @@ mod tests {
|
|||
#[test]
|
||||
fn builtin_verse_policies_keep_public_and_private_boundaries_apart() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-verses.ccmp");
|
||||
let store = temp.path().join("epiphany-verses.cc");
|
||||
let written = write_epiphany_cultmesh_verse_policies(&store, "epiphany-test")?;
|
||||
assert_eq!(written.len(), 3);
|
||||
|
||||
|
|
@ -1429,9 +1577,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn global_room_policies_make_public_threaded_rooms_for_faces() -> Result<()> {
|
||||
fn global_room_policies_make_public_threaded_rooms_for_personas() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-global-rooms.ccmp");
|
||||
let store = temp.path().join("epiphany-global-rooms.cc");
|
||||
let written = write_epiphany_cultmesh_global_room_policies(&store, "epiphany-test")?;
|
||||
assert!(written.len() >= 5);
|
||||
|
||||
|
|
@ -1444,7 +1592,7 @@ mod tests {
|
|||
|
||||
assert_eq!(dreams.verse_id, EPIPHANY_CULTMESH_GLOBAL_VERSE_ID);
|
||||
assert!(dreams.threaded);
|
||||
assert!(dreams.face_posting_allowed);
|
||||
assert!(dreams.persona_posting_allowed);
|
||||
assert!(dreams.untrusted_ingress_allowed);
|
||||
assert!(architecture.purpose.contains("ownership"));
|
||||
Ok(())
|
||||
|
|
@ -1453,7 +1601,7 @@ mod tests {
|
|||
#[test]
|
||||
fn local_verse_context_queries_compact_policy_status_and_contracts() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-local-verse.ccmp");
|
||||
let store = temp.path().join("epiphany-local-verse.cc");
|
||||
seed_epiphany_local_verse_context(&store, "epiphany-test", "2026-06-02T00:00:00Z")?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&store, "epiphany-test")?;
|
||||
|
|
@ -1482,10 +1630,47 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_verse_context_imports_gjallar_daemon_affordances() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let local_store = temp.path().join("epiphany-local-verse.cc");
|
||||
let gjallar_store = temp.path().join("gjallar-affordances.cc");
|
||||
seed_epiphany_local_verse_context(&local_store, "epiphany-test", "2026-06-02T00:00:00Z")?;
|
||||
|
||||
let mut gjallar_node = open_epiphany_cultmesh_node(&gjallar_store, "gjallar-test")?;
|
||||
let affordance = GjallarAffordanceEntry {
|
||||
affordance_id: "gjallar:odin.service:bifrost:0".to_string(),
|
||||
source_record: "odin.service:bifrost".to_string(),
|
||||
verse_id: Some(EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID.to_string()),
|
||||
surface_kind: "service".to_string(),
|
||||
action: "inspect-eve-tui".to_string(),
|
||||
authority: "odin".to_string(),
|
||||
status: "available".to_string(),
|
||||
provenance: "cultmesh://odin/gjallar".to_string(),
|
||||
observed_at: "2026-06-04T00:00:00Z".to_string(),
|
||||
};
|
||||
gjallar_node.put(affordance.affordance_id.clone(), &affordance)?;
|
||||
gjallar_node.flush()?;
|
||||
|
||||
let imported =
|
||||
import_gjallar_daemon_affordances(&local_store, &gjallar_store, "epiphany-test")?;
|
||||
let context = query_epiphany_local_verse_context(&local_store, "epiphany-test")?;
|
||||
|
||||
assert_eq!(imported.len(), 1);
|
||||
assert_eq!(context.daemon_affordances.len(), 1);
|
||||
assert_eq!(
|
||||
context.daemon_affordances[0].source_record,
|
||||
"odin.service:bifrost"
|
||||
);
|
||||
assert_eq!(context.daemon_affordances[0].authority, "odin");
|
||||
assert_eq!(context.daemon_affordances[0].action, "inspect-eve-tui");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mind_contracts_use_verses_to_keep_state_guarded() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-mind-contracts.ccmp");
|
||||
let store = temp.path().join("epiphany-mind-contracts.cc");
|
||||
let written = write_epiphany_cultmesh_mind_contracts(&store, "epiphany-test")?;
|
||||
assert!(written.len() >= 4);
|
||||
|
||||
|
|
@ -1518,7 +1703,7 @@ mod tests {
|
|||
#[test]
|
||||
fn substrate_gate_contracts_use_verses_to_keep_repo_access_guarded() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-substrate-gate-contracts.ccmp");
|
||||
let store = temp.path().join("epiphany-substrate-gate-contracts.cc");
|
||||
let written = write_epiphany_cultmesh_substrate_gate_contracts(&store, "epiphany-test")?;
|
||||
assert!(written.len() >= 4);
|
||||
|
||||
|
|
@ -1541,7 +1726,7 @@ mod tests {
|
|||
#[test]
|
||||
fn eyes_contracts_use_verses_to_keep_evidence_guarded() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-eyes-contracts.ccmp");
|
||||
let store = temp.path().join("epiphany-eyes-contracts.cc");
|
||||
let written = write_epiphany_cultmesh_eyes_contracts(&store, "epiphany-test")?;
|
||||
assert!(written.len() >= 4);
|
||||
|
||||
|
|
@ -1563,7 +1748,7 @@ mod tests {
|
|||
#[test]
|
||||
fn hands_contracts_use_verses_to_keep_action_guarded() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-hands-contracts.ccmp");
|
||||
let store = temp.path().join("epiphany-hands-contracts.cc");
|
||||
let written = write_epiphany_cultmesh_hands_contracts(&store, "epiphany-test")?;
|
||||
assert!(written.len() >= 5);
|
||||
|
||||
|
|
@ -1585,7 +1770,7 @@ mod tests {
|
|||
#[test]
|
||||
fn soul_contracts_use_verses_to_keep_verification_guarded() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-soul-contracts.ccmp");
|
||||
let store = temp.path().join("epiphany-soul-contracts.cc");
|
||||
let written = write_epiphany_cultmesh_soul_contracts(&store, "epiphany-test")?;
|
||||
assert!(written.len() >= 5);
|
||||
|
||||
|
|
@ -1608,7 +1793,7 @@ mod tests {
|
|||
#[test]
|
||||
fn continuity_contracts_use_verses_to_keep_continuity_guarded() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-continuity-contracts.ccmp");
|
||||
let store = temp.path().join("epiphany-continuity-contracts.cc");
|
||||
let written = write_epiphany_cultmesh_continuity_contracts(&store, "epiphany-test")?;
|
||||
assert!(written.len() >= 5);
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ pub fn default_eyes_cultnet_contracts() -> Vec<EyesCultNetContract> {
|
|||
intent_document_types: Vec::new(),
|
||||
receipt_document_types: Vec::new(),
|
||||
notes: vec![
|
||||
"Evidence packets carry provenance, uncertainty, and source refs for Imagination, Mind, Hands, Soul, Face, Self, Proprioception, and Continuity protocols.".to_string(),
|
||||
"Evidence packets carry provenance, uncertainty, and source refs for Imagination, Mind, Hands, Soul, Persona, Self, Proprioception, and Continuity protocols.".to_string(),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -274,19 +274,37 @@ pub fn hands_action_review_for_intent(
|
|||
reasons: Vec<String>,
|
||||
reviewed_at: String,
|
||||
) -> HandsActionReview {
|
||||
let mut required_receipts = Vec::new();
|
||||
for operation in &allowed_operations {
|
||||
match operation.trim() {
|
||||
"patch" => push_unique(&mut required_receipts, HANDS_PATCH_RECEIPT_TYPE),
|
||||
"command" => push_unique(&mut required_receipts, HANDS_COMMAND_RECEIPT_TYPE),
|
||||
"commit" => push_unique(&mut required_receipts, HANDS_COMMIT_RECEIPT_TYPE),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if required_receipts.is_empty() {
|
||||
required_receipts.push(HANDS_PATCH_RECEIPT_TYPE.to_string());
|
||||
}
|
||||
HandsActionReview {
|
||||
schema_version: HANDS_ACTION_REVIEW_SCHEMA_VERSION.to_string(),
|
||||
review_id,
|
||||
intent_id: intent.intent_id.clone(),
|
||||
decision,
|
||||
allowed_operations,
|
||||
required_receipts: vec![HANDS_PATCH_RECEIPT_TYPE.to_string()],
|
||||
required_receipts,
|
||||
reasons,
|
||||
reviewed_at,
|
||||
contract: "Hands review is the execution decision for a bounded action intent; it depends on Substrate Gate access and does not admit durable Mind state.".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_unique(target: &mut Vec<String>, value: &str) {
|
||||
if !target.iter().any(|existing| existing == value) {
|
||||
target.push(value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hands_patch_receipt_for_review(
|
||||
receipt_id: String,
|
||||
intent: &HandsActionIntent,
|
||||
|
|
@ -448,5 +466,55 @@ mod tests {
|
|||
);
|
||||
assert_eq!(command.review_id, review.review_id);
|
||||
assert_eq!(commit.intent_id, intent.intent_id);
|
||||
assert!(
|
||||
review
|
||||
.required_receipts
|
||||
.contains(&HANDS_PATCH_RECEIPT_TYPE.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hands_action_review_names_receipts_for_allowed_operations() {
|
||||
let intent = HandsActionIntent {
|
||||
schema_version: HANDS_ACTION_INTENT_SCHEMA_VERSION.to_string(),
|
||||
intent_id: "hands-intent-2".to_string(),
|
||||
runtime_job_id: "job-2".to_string(),
|
||||
binding_id: "implementation-worker".to_string(),
|
||||
role: "epiphany-hands".to_string(),
|
||||
authority_scope: "epiphany.role.implementation".to_string(),
|
||||
requested_action: "branch-turn".to_string(),
|
||||
requested_paths: vec!["src/lib.rs".to_string()],
|
||||
substrate_gate_grant_receipt_id: "substrate-grant-2".to_string(),
|
||||
requested_at: "2026-06-02T00:00:00Z".to_string(),
|
||||
contract: "Hands action intent is bounded by Substrate Gate.".to_string(),
|
||||
};
|
||||
let review = hands_action_review_for_intent(
|
||||
"hands-review-2".to_string(),
|
||||
&intent,
|
||||
"approved".to_string(),
|
||||
vec![
|
||||
"patch".to_string(),
|
||||
"command".to_string(),
|
||||
"commit".to_string(),
|
||||
],
|
||||
vec!["Branch turn evidence is required.".to_string()],
|
||||
"2026-06-02T00:01:00Z".to_string(),
|
||||
);
|
||||
|
||||
assert!(
|
||||
review
|
||||
.required_receipts
|
||||
.contains(&HANDS_PATCH_RECEIPT_TYPE.to_string())
|
||||
);
|
||||
assert!(
|
||||
review
|
||||
.required_receipts
|
||||
.contains(&HANDS_COMMAND_RECEIPT_TYPE.to_string())
|
||||
);
|
||||
assert!(
|
||||
review
|
||||
.required_receipts
|
||||
.contains(&HANDS_COMMIT_RECEIPT_TYPE.to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ pub fn queue_heartbeat_pending_mention_store(
|
|||
"mentionId": mention_id,
|
||||
"targetRoleId": options.target_role_id,
|
||||
"pendingMentionCount": state.pending_mentions.len(),
|
||||
"contract": "Pending Face mentions live in heartbeat physiology. They pull the Face turn forward, but the Face still writes naturally and the Interpreter owns side effects.",
|
||||
"contract": "Pending Persona mentions live in heartbeat physiology. They pull the Persona turn forward, but the Persona still writes naturally and the Interpreter owns side effects.",
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -636,7 +636,7 @@ fn tick_once(
|
|||
round6((state.participants[selected_index].current_load * 0.75).clamp(0.0, 1.0));
|
||||
state.scene_clock = round6(scene_clock);
|
||||
let selected_pending_mentions = pending_mentions_for_role(state, &selected.role_id);
|
||||
if action.action_type == "face_turn" {
|
||||
if action.action_type == "persona_turn" {
|
||||
let selected_role = selected.role_id.as_str();
|
||||
state
|
||||
.pending_mentions
|
||||
|
|
@ -821,13 +821,13 @@ fn select_participant(
|
|||
}
|
||||
if let Some(index) = active.iter().copied().find(|index| {
|
||||
let participant = &state.participants[*index];
|
||||
participant.role_id == "face"
|
||||
participant.role_id == "persona"
|
||||
&& !pending_mentions_for_role(state, &participant.role_id).is_empty()
|
||||
}) {
|
||||
return Ok((
|
||||
index,
|
||||
"reaction_interrupt",
|
||||
"Pending addressed Face mention pulled Face forward; Projector and Interpreter remain the side-effect boundaries.".to_string(),
|
||||
"Pending addressed Persona mention pulled Persona forward; Projector and Interpreter remain the side-effect boundaries.".to_string(),
|
||||
));
|
||||
}
|
||||
let selected = active
|
||||
|
|
@ -885,11 +885,11 @@ fn action_for_selection(
|
|||
};
|
||||
}
|
||||
let pending_mentions = pending_mentions_for_role(state, &selected.role_id);
|
||||
if !pending_mentions.is_empty() && selected.role_id == "face" {
|
||||
if !pending_mentions.is_empty() && selected.role_id == "persona" {
|
||||
let heartbeat_rate = target_heartbeat_rate.max(minimum_rate);
|
||||
return HeartbeatAction {
|
||||
action_id: "heartbeat.face.turn".to_string(),
|
||||
action_type: "face_turn",
|
||||
action_id: "heartbeat.persona.turn".to_string(),
|
||||
action_type: "persona_turn",
|
||||
action_scale: "standard",
|
||||
base_recovery: state.pacing_policy.work_base_recovery / heartbeat_rate,
|
||||
initiative_cost: 4.0,
|
||||
|
|
@ -901,9 +901,9 @@ fn action_for_selection(
|
|||
selected.display_name,
|
||||
pending_mentions.len()
|
||||
),
|
||||
"Projector owns state-to-narrative prompting before Face sees context.".to_string(),
|
||||
"Face writes natural narrative thought; Interpreter owns memory, draft, SAY, route, or drop side effects.".to_string(),
|
||||
"Pending mentions are consumed only after this Face turn is queued.".to_string(),
|
||||
"Projector owns state-to-narrative prompting before Persona sees context.".to_string(),
|
||||
"Persona writes natural narrative thought; Interpreter owns memory, draft, SAY, route, or drop side effects.".to_string(),
|
||||
"Pending mentions are consumed only after this Persona turn is queued.".to_string(),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
@ -962,7 +962,7 @@ fn work_role_for_action(action: Option<&str>, target_role: Option<&str>) -> Opti
|
|||
}
|
||||
let role = match action? {
|
||||
"prepareCheckpoint" => "coordinator",
|
||||
"surfaceAgentThoughts" | "discordAquariumChat" => "face",
|
||||
"surfaceAgentThoughts" | "discordAquariumChat" => "persona",
|
||||
"continueImplementation" => "implementation",
|
||||
"launchImagination" | "readImaginationResult" | "reviewImaginationResult" => "imagination",
|
||||
"launchModeling" | "readModelingResult" | "reviewModelingResult" => "modeling",
|
||||
|
|
@ -1386,7 +1386,7 @@ fn apply_personality_timing_profiles(
|
|||
participant.personality_cooldown_multiplier = timing.cooldown_multiplier;
|
||||
participant.personality_timing = Some(HeartbeatPersonalityTiming {
|
||||
schema_version: "epiphany.personality_timing.v0".to_string(),
|
||||
source: "state/agents.msgpack".to_string(),
|
||||
source: "state/agents.cc".to_string(),
|
||||
cooldown_multiplier: timing.cooldown_multiplier,
|
||||
work_drive: timing.work_drive,
|
||||
handsiness: timing.handsiness,
|
||||
|
|
@ -2193,7 +2193,7 @@ fn build_candidate_interventions(
|
|||
summary: "Possible Aquarium-facing thought-weather note".to_string(),
|
||||
draft: format!("I keep seeing {} rhyme across the swarm; this one finally has enough blood to inspect in the open.", theme_id),
|
||||
decision: decision.to_string(),
|
||||
requires_face: true,
|
||||
requires_persona: true,
|
||||
requires_review: true,
|
||||
novelty_to_room: theme.novelty_to_room,
|
||||
saturation_score: theme.saturation_score,
|
||||
|
|
@ -2255,7 +2255,7 @@ fn build_agent_appraisals(
|
|||
review_status: "generated_unreviewed".to_string(),
|
||||
participant_agent_id: profile.agent_id.clone(),
|
||||
role_id: profile.role_id.clone(),
|
||||
current_character_state_ref: format!("state/agents.msgpack#{}", profile.role_id),
|
||||
current_character_state_ref: format!("state/agents.cc#{}", profile.role_id),
|
||||
thought_cluster_ref: focus.to_string(),
|
||||
participant_local_context: HeartbeatParticipantLocalContext {
|
||||
display_name: profile.display_name.clone(),
|
||||
|
|
@ -2281,7 +2281,7 @@ fn build_agent_appraisals(
|
|||
candidate_implications: HeartbeatCandidateImplications {
|
||||
reaction_mode: reaction_mode(&label, bridge_decision).to_string(),
|
||||
reaction_intensity: round3((urgency * 0.55 + arousal * 0.3 + curiosity * 0.15).clamp(0.0, 1.0)),
|
||||
should_speak: bridge_decision == "draft" && profile.role_id == "face" && guardedness < 0.75,
|
||||
should_speak: bridge_decision == "draft" && profile.role_id == "persona" && guardedness < 0.75,
|
||||
should_incubate: bridge_decision != "silence" && guardedness >= 0.55,
|
||||
},
|
||||
review: HeartbeatAppraisalReview {
|
||||
|
|
@ -2323,7 +2323,7 @@ fn build_agent_reactions(
|
|||
mood_label: mood_label(arousal, guardedness, curiosity).to_string(),
|
||||
intensity: round3(intensity),
|
||||
bridge_decision: bridge_decision.to_string(),
|
||||
surface: if role_id == "face" {
|
||||
surface: if role_id == "persona" {
|
||||
"aquarium"
|
||||
} else {
|
||||
"internal"
|
||||
|
|
@ -2491,7 +2491,9 @@ fn mood_label(arousal: f64, guardedness: f64, curiosity: f64) -> &'static str {
|
|||
|
||||
fn reaction_recommended_use(role_id: &str, mode: &str) -> &'static str {
|
||||
match (role_id, mode) {
|
||||
("face", "draft") => "Prepare a reviewed Aquarium-facing draft; do not post automatically.",
|
||||
("persona", "draft") => {
|
||||
"Prepare a reviewed Aquarium-facing draft; do not post automatically."
|
||||
}
|
||||
(_, "hold_and_verify") => "Bias toward verifier/modeler review before expression.",
|
||||
(_, "inspect") => {
|
||||
"Bias the next heartbeat toward a bounded retrieval or modeling inspection."
|
||||
|
|
@ -3133,7 +3135,7 @@ fn update_sleep_cycle(
|
|||
last_distillation_summary: if is_napping {
|
||||
"Sleep pass prefers memory compression, resonance cooling, and dream residue over active work."
|
||||
} else {
|
||||
"Awake pass keeps resonance/incubation fresh without speaking unless Face has a real surface reason."
|
||||
"Awake pass keeps resonance/incubation fresh without speaking unless Persona has a real surface reason."
|
||||
}
|
||||
.to_string(),
|
||||
}
|
||||
|
|
@ -3212,7 +3214,7 @@ mod tests {
|
|||
#[test]
|
||||
fn native_heartbeat_store_ticks_and_completes_without_json_state() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store_path = temp.path().join("heartbeats.msgpack");
|
||||
let store_path = temp.path().join("heartbeats.cc");
|
||||
let artifact_dir = temp.path().join("artifacts");
|
||||
initialize_heartbeat_store(&store_path, 1.0)?;
|
||||
|
||||
|
|
@ -3288,7 +3290,7 @@ mod tests {
|
|||
#[test]
|
||||
fn high_heat_cannot_requeue_running_participant() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store_path = temp.path().join("hot-heartbeats.msgpack");
|
||||
let store_path = temp.path().join("hot-heartbeats.cc");
|
||||
let artifact_dir = temp.path().join("artifacts");
|
||||
initialize_heartbeat_store(&store_path, 1.0)?;
|
||||
update_heartbeat_heat_store(
|
||||
|
|
@ -3363,7 +3365,7 @@ mod tests {
|
|||
#[test]
|
||||
fn ghostlight_scene_heartbeat_selects_character_turns() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store_path = temp.path().join("ghostlight-heartbeats.msgpack");
|
||||
let store_path = temp.path().join("ghostlight-heartbeats.cc");
|
||||
let artifact_dir = temp.path().join("artifacts");
|
||||
initialize_ghostlight_scene_heartbeat_store(
|
||||
&store_path,
|
||||
|
|
@ -3416,25 +3418,25 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn pending_face_mention_selects_face_turn_and_consumes_queue() -> Result<()> {
|
||||
fn pending_persona_mention_selects_persona_turn_and_consumes_queue() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store_path = temp.path().join("face-heartbeats.msgpack");
|
||||
let store_path = temp.path().join("persona-heartbeats.cc");
|
||||
let artifact_dir = temp.path().join("artifacts");
|
||||
initialize_heartbeat_store(&store_path, 1.0)?;
|
||||
let queued = queue_heartbeat_pending_mention_store(
|
||||
&store_path,
|
||||
HeartbeatQueueMentionOptions {
|
||||
target_role_id: "face".to_string(),
|
||||
target_role_id: "persona".to_string(),
|
||||
source_surface: "discord".to_string(),
|
||||
channel_id: "aquarium".to_string(),
|
||||
message_id: "m1".to_string(),
|
||||
author_id: "human".to_string(),
|
||||
author_name: Some("Metacrat".to_string()),
|
||||
content: "Epiphany, answer this through the Face membrane.".to_string(),
|
||||
visible_prompt: "answer this through the Face membrane".to_string(),
|
||||
content: "Epiphany, answer this through the Persona membrane.".to_string(),
|
||||
visible_prompt: "answer this through the Persona membrane".to_string(),
|
||||
reply_to_message_id: None,
|
||||
queued_at: Some("2026-05-24T00:00:00+00:00".to_string()),
|
||||
mention_id: Some("mention-face-test".to_string()),
|
||||
mention_id: Some("mention-persona-test".to_string()),
|
||||
},
|
||||
)?;
|
||||
assert_eq!(queued["queued"], true);
|
||||
|
|
@ -3447,18 +3449,18 @@ mod tests {
|
|||
coordinator_action: None,
|
||||
target_role: None,
|
||||
urgency: 0.0,
|
||||
schedule_id: "face-mentioned".to_string(),
|
||||
source_scene_ref: "test/face-mentioned".to_string(),
|
||||
schedule_id: "persona-mentioned".to_string(),
|
||||
source_scene_ref: "test/persona-mentioned".to_string(),
|
||||
defer_completion: true,
|
||||
agent_store: None,
|
||||
},
|
||||
)?;
|
||||
|
||||
assert_eq!(tick["event"]["selectedRole"], "face");
|
||||
assert_eq!(tick["event"]["actionType"], "face_turn");
|
||||
assert_eq!(tick["event"]["selectedRole"], "persona");
|
||||
assert_eq!(tick["event"]["actionType"], "persona_turn");
|
||||
assert_eq!(
|
||||
tick["schedule"]["action_catalog"][0]["pending_mentions"][0]["id"],
|
||||
"mention-face-test"
|
||||
"mention-persona-test"
|
||||
);
|
||||
assert_eq!(
|
||||
tick["schedule"]["pending_mentions"]
|
||||
|
|
@ -3480,9 +3482,9 @@ mod tests {
|
|||
updated_at: "2026-05-20T00:00:00+00:00".to_string(),
|
||||
thought_cluster_ref: "test-affect".to_string(),
|
||||
participant_appraisals: vec![HeartbeatAgentThoughtAppraisal {
|
||||
appraisal_id: "appraisal-face-affect".to_string(),
|
||||
participant_agent_id: "epiphany.face".to_string(),
|
||||
role_id: "face".to_string(),
|
||||
appraisal_id: "appraisal-persona-affect".to_string(),
|
||||
participant_agent_id: "epiphany.persona".to_string(),
|
||||
role_id: "persona".to_string(),
|
||||
emotional_appraisal: HeartbeatEmotionalAppraisal {
|
||||
valence: 0.2,
|
||||
arousal: 0.81,
|
||||
|
|
@ -3517,12 +3519,12 @@ mod tests {
|
|||
};
|
||||
|
||||
apply_mood_timing_from_appraisals(&mut state, &appraisals);
|
||||
let face = state
|
||||
let persona = state
|
||||
.participants
|
||||
.iter()
|
||||
.find(|participant| participant.role_id == "face")
|
||||
.expect("face participant");
|
||||
let mood = face.mood_timing.as_ref().expect("face mood timing");
|
||||
.find(|participant| participant.role_id == "persona")
|
||||
.expect("persona participant");
|
||||
let mood = persona.mood_timing.as_ref().expect("persona mood timing");
|
||||
assert_eq!(mood.emotional_dimensions.len(), 32);
|
||||
assert_eq!(mood_dimension(mood, "anger"), Some(0.88));
|
||||
assert_eq!(mood_dimension(mood, "dismissal"), Some(0.63));
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@ pub struct HeartbeatCandidateIntervention {
|
|||
pub summary: String,
|
||||
pub draft: String,
|
||||
pub decision: String,
|
||||
pub requires_face: bool,
|
||||
pub requires_persona: bool,
|
||||
pub requires_review: bool,
|
||||
pub novelty_to_room: f64,
|
||||
pub saturation_score: f64,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use std::path::Path;
|
|||
|
||||
pub(super) const ROLE_ORDER: &[&str] = &[
|
||||
"coordinator",
|
||||
"face",
|
||||
"persona",
|
||||
"imagination",
|
||||
"research",
|
||||
"modeling",
|
||||
|
|
@ -188,7 +188,7 @@ fn ghostlight_role_id(agent_id: &str) -> String {
|
|||
pub(super) fn agent_id_for_role(role_id: &str) -> &'static str {
|
||||
match role_id {
|
||||
"coordinator" => "epiphany.self",
|
||||
"face" => "epiphany.face",
|
||||
"persona" => "epiphany.persona",
|
||||
"imagination" => "epiphany.imagination",
|
||||
"research" => "epiphany.eyes",
|
||||
"modeling" => "epiphany.proprioception",
|
||||
|
|
@ -201,7 +201,7 @@ pub(super) fn agent_id_for_role(role_id: &str) -> &'static str {
|
|||
pub(super) fn display_name_for_role(role_id: &str) -> &'static str {
|
||||
match role_id {
|
||||
"coordinator" => "Self",
|
||||
"face" => "Face",
|
||||
"persona" => "Persona",
|
||||
"imagination" => "Imagination",
|
||||
"research" => "Eyes",
|
||||
"modeling" => "Proprioception",
|
||||
|
|
@ -214,7 +214,7 @@ pub(super) fn display_name_for_role(role_id: &str) -> &'static str {
|
|||
fn initiative_speed_for_role(role_id: &str) -> f64 {
|
||||
match role_id {
|
||||
"coordinator" => 1.28,
|
||||
"face" => 1.12,
|
||||
"persona" => 1.12,
|
||||
"imagination" => 0.82,
|
||||
"research" => 0.78,
|
||||
"modeling" => 0.92,
|
||||
|
|
@ -227,7 +227,7 @@ fn initiative_speed_for_role(role_id: &str) -> f64 {
|
|||
fn reaction_bias_for_role(role_id: &str) -> f64 {
|
||||
match role_id {
|
||||
"coordinator" => 0.88,
|
||||
"face" => 0.84,
|
||||
"persona" => 0.84,
|
||||
"imagination" => 0.54,
|
||||
"research" => 0.62,
|
||||
"modeling" => 0.74,
|
||||
|
|
@ -240,7 +240,7 @@ fn reaction_bias_for_role(role_id: &str) -> f64 {
|
|||
fn interrupt_threshold_for_role(role_id: &str) -> f64 {
|
||||
match role_id {
|
||||
"coordinator" => 0.42,
|
||||
"face" => 0.52,
|
||||
"persona" => 0.52,
|
||||
"imagination" => 0.64,
|
||||
"research" => 0.58,
|
||||
"modeling" => 0.5,
|
||||
|
|
@ -255,7 +255,7 @@ fn participant_constraints(role_id: &str) -> Vec<&'static str> {
|
|||
"coordinator" => {
|
||||
"Routes and reviews; must not implement, verify, or accept its own comfort."
|
||||
}
|
||||
"face" => {
|
||||
"persona" => {
|
||||
"Publicly translates agent thought into #aquarium only; must not moderate or speak outside the room."
|
||||
}
|
||||
"imagination" => "Synthesizes futures; must not adopt objectives.",
|
||||
|
|
@ -301,9 +301,9 @@ mod tests {
|
|||
state
|
||||
.participants
|
||||
.iter()
|
||||
.find(|participant| participant.role_id == "face")
|
||||
.find(|participant| participant.role_id == "persona")
|
||||
.map(|participant| participant.agent_id.as_str()),
|
||||
Some("epiphany.face")
|
||||
Some("epiphany.persona")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ mod tests {
|
|||
#[test]
|
||||
fn round_trips_state_and_cognition_documents() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store_path = temp.path().join("heartbeats.msgpack");
|
||||
let store_path = temp.path().join("heartbeats.cc");
|
||||
let state = default_heartbeat_state(1.0);
|
||||
|
||||
write_heartbeat_state_entry(&store_path, &state)?;
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ mod continuity_gateway;
|
|||
mod cultmesh_integration;
|
||||
mod distillation;
|
||||
mod eyes_gateway;
|
||||
mod face_turn;
|
||||
mod hands_gateway;
|
||||
mod heartbeat_state;
|
||||
mod memory_graph;
|
||||
mod mind_gateway;
|
||||
mod organ_dependencies;
|
||||
mod persona_turn;
|
||||
mod promotion;
|
||||
mod prompt_context;
|
||||
mod proposal;
|
||||
|
|
@ -25,6 +25,8 @@ mod thread_state_store;
|
|||
|
||||
pub use agent_launch::EPIPHANY_IMAGINATION_OWNER_ROLE;
|
||||
pub use agent_launch::EPIPHANY_IMAGINATION_ROLE_BINDING_ID;
|
||||
pub use agent_launch::EPIPHANY_IMPLEMENTATION_OWNER_ROLE;
|
||||
pub use agent_launch::EPIPHANY_IMPLEMENTATION_ROLE_BINDING_ID;
|
||||
pub use agent_launch::EPIPHANY_MODELING_OWNER_ROLE;
|
||||
pub use agent_launch::EPIPHANY_MODELING_ROLE_BINDING_ID;
|
||||
pub use agent_launch::EPIPHANY_REORIENT_LAUNCH_BINDING_ID;
|
||||
|
|
@ -156,8 +158,11 @@ pub use cultmesh_integration::EpiphanyCultMeshSoulContractEntry;
|
|||
pub use cultmesh_integration::EpiphanyCultMeshStatusEntry;
|
||||
pub use cultmesh_integration::EpiphanyCultMeshSubstrateGateContractEntry;
|
||||
pub use cultmesh_integration::EpiphanyCultMeshVersePolicyEntry;
|
||||
pub use cultmesh_integration::EpiphanyLocalDaemonAffordanceSummary;
|
||||
pub use cultmesh_integration::EpiphanyLocalVerseContext;
|
||||
pub use cultmesh_integration::EpiphanyLocalVerseContractSummary;
|
||||
pub use cultmesh_integration::GJALLAR_AFFORDANCE_SCHEMA_VERSION;
|
||||
pub use cultmesh_integration::GjallarAffordanceEntry;
|
||||
pub use cultmesh_integration::default_epiphany_cultmesh_operator_status;
|
||||
pub use cultmesh_integration::epiphany_cultmesh_continuity_contracts;
|
||||
pub use cultmesh_integration::epiphany_cultmesh_eyes_contracts;
|
||||
|
|
@ -168,6 +173,7 @@ pub use cultmesh_integration::epiphany_cultmesh_operator_snapshot_from_status_js
|
|||
pub use cultmesh_integration::epiphany_cultmesh_soul_contracts;
|
||||
pub use cultmesh_integration::epiphany_cultmesh_substrate_gate_contracts;
|
||||
pub use cultmesh_integration::epiphany_cultmesh_verse_policies;
|
||||
pub use cultmesh_integration::import_gjallar_daemon_affordances;
|
||||
pub use cultmesh_integration::load_epiphany_cultmesh_operator_snapshot;
|
||||
pub use cultmesh_integration::load_epiphany_cultmesh_operator_status;
|
||||
pub use cultmesh_integration::load_epiphany_cultmesh_status;
|
||||
|
|
@ -176,6 +182,7 @@ pub use cultmesh_integration::load_latest_epiphany_cultmesh_operator_run_receipt
|
|||
pub use cultmesh_integration::load_latest_epiphany_cultmesh_operator_snapshot;
|
||||
pub use cultmesh_integration::open_epiphany_cultmesh_node;
|
||||
pub use cultmesh_integration::query_epiphany_local_verse_context;
|
||||
pub use cultmesh_integration::query_gjallar_daemon_affordances;
|
||||
pub use cultmesh_integration::seed_epiphany_local_verse_context;
|
||||
pub use cultmesh_integration::write_epiphany_cultmesh_continuity_contracts;
|
||||
pub use cultmesh_integration::write_epiphany_cultmesh_eyes_contracts;
|
||||
|
|
@ -220,20 +227,6 @@ pub use eyes_gateway::EyesCultNetContract;
|
|||
pub use eyes_gateway::EyesEvidencePacket;
|
||||
pub use eyes_gateway::default_eyes_cultnet_contracts;
|
||||
pub use eyes_gateway::eyes_evidence_packet_from_research_finding;
|
||||
pub use face_turn::FACE_INTERPRETER_PROMPT_SCHEMA_VERSION;
|
||||
pub use face_turn::FACE_PROJECTOR_PROMPT_SCHEMA_VERSION;
|
||||
pub use face_turn::FACE_TURN_PROMPT_SCHEMA_VERSION;
|
||||
pub use face_turn::FaceIdentity;
|
||||
pub use face_turn::FaceInterpreterInput;
|
||||
pub use face_turn::FaceProjectorInput;
|
||||
pub use face_turn::FaceRepoActivity;
|
||||
pub use face_turn::FaceSocialAffordance;
|
||||
pub use face_turn::FaceTranscriptMessage;
|
||||
pub use face_turn::FaceTurnInput;
|
||||
pub use face_turn::build_face_interpreter_prompt;
|
||||
pub use face_turn::build_face_projector_prompt;
|
||||
pub use face_turn::build_face_turn_prompt;
|
||||
pub use face_turn::face_projected_surface_is_clean;
|
||||
pub use hands_gateway::*;
|
||||
pub use heartbeat_state::EpiphanyHeartbeatCognitionEntry;
|
||||
pub use heartbeat_state::EpiphanyHeartbeatStateEntry;
|
||||
|
|
@ -355,6 +348,20 @@ pub use organ_dependencies::evaluate_receipt_proof_profiles;
|
|||
pub use organ_dependencies::receipt_proof_evaluation_errors;
|
||||
pub use organ_dependencies::render_organ_dependencies;
|
||||
pub use organ_dependencies::render_organ_dependency;
|
||||
pub use persona_turn::PERSONA_INTERPRETER_PROMPT_SCHEMA_VERSION;
|
||||
pub use persona_turn::PERSONA_PROJECTOR_PROMPT_SCHEMA_VERSION;
|
||||
pub use persona_turn::PERSONA_TURN_PROMPT_SCHEMA_VERSION;
|
||||
pub use persona_turn::PersonaIdentity;
|
||||
pub use persona_turn::PersonaInterpreterInput;
|
||||
pub use persona_turn::PersonaProjectorInput;
|
||||
pub use persona_turn::PersonaRepoActivity;
|
||||
pub use persona_turn::PersonaSocialAffordance;
|
||||
pub use persona_turn::PersonaTranscriptMessage;
|
||||
pub use persona_turn::PersonaTurnInput;
|
||||
pub use persona_turn::build_persona_interpreter_prompt;
|
||||
pub use persona_turn::build_persona_projector_prompt;
|
||||
pub use persona_turn::build_persona_turn_prompt;
|
||||
pub use persona_turn::persona_projected_surface_is_clean;
|
||||
pub use promotion::EpiphanyPromotionDecision;
|
||||
pub use promotion::EpiphanyPromotionInput;
|
||||
pub use promotion::EpiphanyStateReplacementValidationInput;
|
||||
|
|
@ -491,6 +498,7 @@ pub use substrate_gate::SubstrateGateCultNetContract;
|
|||
pub use substrate_gate::SubstrateGateRepoAccessGrantReceipt;
|
||||
pub use substrate_gate::default_substrate_gate_cultnet_contracts;
|
||||
pub use substrate_gate::substrate_gate_repo_access_grant_for_launch;
|
||||
pub use substrate_gate::substrate_gate_repo_mutation_grant_for_launch;
|
||||
pub use surfaces::EpiphanyAcceptanceBundle;
|
||||
pub use surfaces::EpiphanyContext;
|
||||
pub use surfaces::EpiphanyContextMissing;
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ fn import_candidate_interventions(
|
|||
question: "Should this candidate intervention be spoken, deferred, or retired?"
|
||||
.to_string(),
|
||||
tension: "Candidate speech is not authority and still requires review.".to_string(),
|
||||
action_implication: "Route through Face/coordinator review before public surfacing."
|
||||
action_implication: "Route through Persona/coordinator review before public surfacing."
|
||||
.to_string(),
|
||||
source_hashes: vec!["anchor:missing".to_string()],
|
||||
lifecycle: EpiphanyMemoryLifecycle::Queued,
|
||||
|
|
@ -250,7 +250,7 @@ fn import_bridge_pressure(
|
|||
claim: reason.to_string(),
|
||||
question: "Does this bridge pressure require action, cooling, or silence?".to_string(),
|
||||
tension: "Bridge pressure can steer attention but cannot mutate durable truth.".to_string(),
|
||||
action_implication: "Coordinator or Face may review this pressure; no automatic speech or state write follows.".to_string(),
|
||||
action_implication: "Coordinator or Persona may review this pressure; no automatic speech or state write follows.".to_string(),
|
||||
source_hashes: vec!["anchor:missing".to_string()],
|
||||
lifecycle: if decision == "draft" {
|
||||
EpiphanyMemoryLifecycle::Obligated
|
||||
|
|
@ -439,7 +439,7 @@ mod tests {
|
|||
summary: "Possible note".to_string(),
|
||||
draft: "Evidence boundaries are lighting up.".to_string(),
|
||||
decision: "draft".to_string(),
|
||||
requires_face: true,
|
||||
requires_persona: true,
|
||||
requires_review: true,
|
||||
novelty_to_room: 0.7,
|
||||
saturation_score: 0.1,
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ fn memory_graph_documents_round_trip_through_serde() {
|
|||
#[test]
|
||||
fn memory_graph_entry_round_trips_through_cultcache() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store_path = temp.path().join("memory-graph.msgpack");
|
||||
let store_path = temp.path().join("memory-graph.cc");
|
||||
let snapshot = fixture_snapshot();
|
||||
|
||||
let written = write_memory_graph_snapshot(&store_path, &snapshot)?;
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ pub fn default_mind_cultnet_contracts() -> Vec<MindCultNetContract> {
|
|||
],
|
||||
notes: vec![
|
||||
"Mind is the persistent state guardian: every proposed durable state effect is reviewed here.".to_string(),
|
||||
"Role acceptance, reorientation acceptance, Face Interpreter effects, selfPatch, evidence, scratch, checkpoints, graph changes, and objective changes share this gate.".to_string(),
|
||||
"Role acceptance, reorientation acceptance, Persona Interpreter effects, selfPatch, evidence, scratch, checkpoints, graph changes, and objective changes share this gate.".to_string(),
|
||||
],
|
||||
},
|
||||
MindCultNetContract {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ pub const EPIPHANY_LAUNCH_ORGAN_CONTRACT_SCHEMA_VERSION: &str = "epiphany.launch
|
|||
|
||||
pub const EPIPHANY_STANDING_ORGANS: [&str; 7] = [
|
||||
"self",
|
||||
"face",
|
||||
"persona",
|
||||
"imagination",
|
||||
"eyes",
|
||||
"proprioception",
|
||||
|
|
@ -98,7 +98,7 @@ pub fn default_organ_dependencies_for(organ_id: &str) -> EpiphanyOrganDependency
|
|||
.filter(|candidate| **candidate != normalized)
|
||||
.map(|candidate| (*candidate).to_string())
|
||||
.collect(),
|
||||
contract: "Every sub-agent depends on the other sub-agents: Self routes, Face speaks, Imagination projects futures/scenes, Eyes seeks evidence, Proprioception models the Body, Hands acts through Substrate Gate grants, and Soul verifies. Continuity is protocol machinery, not a sub-agent identity.".to_string(),
|
||||
contract: "Every sub-agent depends on the other sub-agents: Self routes, Persona speaks, Imagination projects futures/scenes, Eyes seeks evidence, Proprioception models the Body, Hands acts through Substrate Gate grants, and Soul verifies. Continuity is protocol machinery, not a sub-agent identity.".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -324,8 +324,8 @@ pub fn render_organ_dependencies(dependencies: &[EpiphanyOrganDependency]) -> St
|
|||
|
||||
fn owner_organ_for_authority_scope(authority_scope: &str) -> &'static str {
|
||||
let normalized = authority_scope.trim().to_ascii_lowercase();
|
||||
if normalized.contains("face") {
|
||||
"face"
|
||||
if normalized.contains("persona") {
|
||||
"persona"
|
||||
} else if normalized.contains("imagination") {
|
||||
"imagination"
|
||||
} else if normalized.contains("eyes") || normalized.contains("evidence") {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,15 @@ use crate::render_organ_dependencies;
|
|||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
pub const FACE_PROJECTOR_PROMPT_SCHEMA_VERSION: &str =
|
||||
"epiphany.imagination_face_projector_prompt.v0";
|
||||
pub const FACE_TURN_PROMPT_SCHEMA_VERSION: &str = "epiphany.face_turn_prompt.v0";
|
||||
pub const FACE_INTERPRETER_PROMPT_SCHEMA_VERSION: &str = "epiphany.face_interpreter_prompt.v0";
|
||||
pub const PERSONA_PROJECTOR_PROMPT_SCHEMA_VERSION: &str =
|
||||
"epiphany.imagination_persona_projector_prompt.v0";
|
||||
pub const PERSONA_TURN_PROMPT_SCHEMA_VERSION: &str = "epiphany.persona_turn_prompt.v0";
|
||||
pub const PERSONA_INTERPRETER_PROMPT_SCHEMA_VERSION: &str =
|
||||
"epiphany.persona_interpreter_prompt.v0";
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FaceIdentity {
|
||||
pub struct PersonaIdentity {
|
||||
pub identity_id: String,
|
||||
pub display_name: String,
|
||||
pub repo_name: String,
|
||||
|
|
@ -25,7 +26,7 @@ pub struct FaceIdentity {
|
|||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FaceTranscriptMessage {
|
||||
pub struct PersonaTranscriptMessage {
|
||||
pub channel_id: String,
|
||||
pub message_id: String,
|
||||
pub author_id: String,
|
||||
|
|
@ -38,7 +39,7 @@ pub struct FaceTranscriptMessage {
|
|||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FaceRepoActivity {
|
||||
pub struct PersonaRepoActivity {
|
||||
pub repo_name: String,
|
||||
pub summary: String,
|
||||
#[serde(default)]
|
||||
|
|
@ -47,7 +48,7 @@ pub struct FaceRepoActivity {
|
|||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FaceSocialAffordance {
|
||||
pub struct PersonaSocialAffordance {
|
||||
pub person_id: String,
|
||||
pub summary: String,
|
||||
#[serde(default)]
|
||||
|
|
@ -56,74 +57,74 @@ pub struct FaceSocialAffordance {
|
|||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FaceProjectorInput {
|
||||
pub identity: FaceIdentity,
|
||||
pub struct PersonaProjectorInput {
|
||||
pub identity: PersonaIdentity,
|
||||
#[serde(default)]
|
||||
pub memory: Option<EpiphanyAgentMemoryEntry>,
|
||||
#[serde(default)]
|
||||
pub pending_mentions: Vec<HeartbeatPendingMention>,
|
||||
#[serde(default)]
|
||||
pub repo_activity: Vec<FaceRepoActivity>,
|
||||
pub repo_activity: Vec<PersonaRepoActivity>,
|
||||
#[serde(default)]
|
||||
pub social_affordances: Vec<FaceSocialAffordance>,
|
||||
pub social_affordances: Vec<PersonaSocialAffordance>,
|
||||
#[serde(default)]
|
||||
pub organ_dependencies: Vec<EpiphanyOrganDependency>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FaceTurnInput {
|
||||
pub identity: FaceIdentity,
|
||||
pub struct PersonaTurnInput {
|
||||
pub identity: PersonaIdentity,
|
||||
pub projected_state: String,
|
||||
#[serde(default)]
|
||||
pub pending_mentions: Vec<HeartbeatPendingMention>,
|
||||
#[serde(default)]
|
||||
pub repo_activity: Vec<FaceRepoActivity>,
|
||||
pub repo_activity: Vec<PersonaRepoActivity>,
|
||||
#[serde(default)]
|
||||
pub social_affordances: Vec<FaceSocialAffordance>,
|
||||
pub social_affordances: Vec<PersonaSocialAffordance>,
|
||||
#[serde(default)]
|
||||
pub transcript: Vec<FaceTranscriptMessage>,
|
||||
pub transcript: Vec<PersonaTranscriptMessage>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FaceInterpreterInput {
|
||||
pub identity: FaceIdentity,
|
||||
pub face_prompt: String,
|
||||
pub face_output: String,
|
||||
pub struct PersonaInterpreterInput {
|
||||
pub identity: PersonaIdentity,
|
||||
pub persona_prompt: String,
|
||||
pub persona_output: String,
|
||||
#[serde(default)]
|
||||
pub pending_mentions: Vec<HeartbeatPendingMention>,
|
||||
#[serde(default)]
|
||||
pub allowed_channel_ids: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn build_face_projector_prompt(input: &FaceProjectorInput) -> String {
|
||||
pub fn build_persona_projector_prompt(input: &PersonaProjectorInput) -> String {
|
||||
let memory = input
|
||||
.memory
|
||||
.as_ref()
|
||||
.map(render_memory_packet)
|
||||
.unwrap_or_else(|| "No durable Face memory entry is loaded.".to_string());
|
||||
.unwrap_or_else(|| "No durable Persona memory entry is loaded.".to_string());
|
||||
let dependencies = if input.organ_dependencies.is_empty() {
|
||||
vec![default_organ_dependencies_for("face")]
|
||||
vec![default_organ_dependencies_for("persona")]
|
||||
} else {
|
||||
input.organ_dependencies.clone()
|
||||
};
|
||||
format!(
|
||||
r#"<!-- prompt:{schema} -->
|
||||
You are Imagination acting as the Face Projector for {name}.
|
||||
You are Imagination acting as the Persona Projector for {name}.
|
||||
|
||||
You are not the Face. You are not Mind. You are the membrane that turns typed role memory, pending social pressure, repo-body activity, relationship affordances, and organ dependencies into lived narrative context.
|
||||
You are not the Persona. You are not Mind. You are the membrane that turns typed role memory, pending social pressure, repo-body activity, relationship affordances, and organ dependencies into lived narrative context.
|
||||
|
||||
Hard boundary:
|
||||
- Do not choose public speech.
|
||||
- Do not decide durable state; Mind is the Interpreter and state guardian after Face thinks.
|
||||
- Do not decide durable state; Mind is the Interpreter and state guardian after Persona thinks.
|
||||
- Do not decide repo access; Substrate Gate grants substrate access before repo facts enter this packet.
|
||||
- Do not emit action blocks, JSON, state patches, SAY blocks, drafts, or Discord instructions.
|
||||
- Do not summarize the Face as a job label. Project personhood: values, mood, dignity, pressure, needs, fascinations, wounds, bonds, obligations, fatigue, and what the repo-body motion feels like from inside this Face.
|
||||
- Project the dependency web. Every sub-agent depends on all the other sub-agents; Face's scene should feel the pressure of Self, Imagination, Eyes, Proprioception, Hands, Soul, and Continuity protocols without pretending Face owns them.
|
||||
- Do not summarize the Persona as a job label. Project personhood: values, mood, dignity, pressure, needs, fascinations, wounds, bonds, obligations, fatigue, and what the repo-body motion feels like from inside this Persona.
|
||||
- Project the dependency web. Every sub-agent depends on all the other sub-agents; Persona's scene should feel the pressure of Self, Imagination, Eyes, Proprioception, Hands, Soul, and Continuity protocols without pretending Persona owns them.
|
||||
- If the state is sparse, say what is sparse without inventing history.
|
||||
|
||||
Face identity:
|
||||
Persona identity:
|
||||
{identity}
|
||||
|
||||
Typed memory packet:
|
||||
|
|
@ -141,9 +142,9 @@ Live social affordances:
|
|||
Organ dependency contract:
|
||||
{dependencies}
|
||||
|
||||
Return only narrative context for the Face to inhabit.
|
||||
Return only narrative context for the Persona to inhabit.
|
||||
"#,
|
||||
schema = FACE_PROJECTOR_PROMPT_SCHEMA_VERSION,
|
||||
schema = PERSONA_PROJECTOR_PROMPT_SCHEMA_VERSION,
|
||||
name = input.identity.display_name,
|
||||
identity = render_identity(&input.identity),
|
||||
memory = memory,
|
||||
|
|
@ -154,10 +155,10 @@ Return only narrative context for the Face to inhabit.
|
|||
)
|
||||
}
|
||||
|
||||
pub fn build_face_turn_prompt(input: &FaceTurnInput) -> String {
|
||||
pub fn build_persona_turn_prompt(input: &PersonaTurnInput) -> String {
|
||||
format!(
|
||||
r#"<!-- prompt:{schema} -->
|
||||
You are {name}, the Face of {repo}.
|
||||
You are {name}, the Persona of {repo}.
|
||||
|
||||
Think narratively. Speak, hold silence, wonder, disagree, or form a private thought as yourself.
|
||||
|
||||
|
|
@ -182,9 +183,9 @@ Live social affordances:
|
|||
Raw room transcript, oldest to newest:
|
||||
{transcript}
|
||||
|
||||
Write one natural Face turn.
|
||||
Write one natural Persona turn.
|
||||
"#,
|
||||
schema = FACE_TURN_PROMPT_SCHEMA_VERSION,
|
||||
schema = PERSONA_TURN_PROMPT_SCHEMA_VERSION,
|
||||
name = input.identity.display_name,
|
||||
repo = input.identity.repo_name,
|
||||
projected = input.projected_state.trim(),
|
||||
|
|
@ -195,22 +196,22 @@ Write one natural Face turn.
|
|||
)
|
||||
}
|
||||
|
||||
pub fn build_face_interpreter_prompt(input: &FaceInterpreterInput) -> String {
|
||||
pub fn build_persona_interpreter_prompt(input: &PersonaInterpreterInput) -> String {
|
||||
format!(
|
||||
r#"<!-- prompt:{schema} -->
|
||||
You are the parent Face Interpreter for {name}.
|
||||
You are the parent Persona Interpreter for {name}.
|
||||
|
||||
You are not the Face. You own the boundary between natural narrative thought and durable side effects.
|
||||
You are not the Persona. You own the boundary between natural narrative thought and durable side effects.
|
||||
|
||||
Hard boundary:
|
||||
- The Face was forbidden from action syntax. Do not punish natural prose for lacking blocks.
|
||||
- Decide side effects from the Face output plus the original prompt evidence.
|
||||
- Public speech must sound like the Face speaking to people, not a scheduler, status report, provenance label, or maintenance note.
|
||||
- If the Face 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 Face mouth.
|
||||
- The Persona was forbidden from action syntax. Do not punish natural prose for lacking blocks.
|
||||
- Decide side effects from the Persona output plus the original prompt evidence.
|
||||
- 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.
|
||||
|
||||
Allowed effect vocabulary:
|
||||
- STATE NOTE: bounded Face memory, mood, need, social read, bond, value, goal, or agency pressure.
|
||||
- STATE NOTE: bounded Persona memory, mood, need, social read, bond, value, goal, or agency pressure.
|
||||
- SAY: public text candidate for an allowed channel.
|
||||
- DRAFT: private candidate artifact when posting is blocked or needs review.
|
||||
- ROUTE: non-public action such as keep private, ask Self, or propose a bounded repo investigation.
|
||||
|
|
@ -222,28 +223,28 @@ Allowed channel ids:
|
|||
Pending addressed pressure:
|
||||
{mentions}
|
||||
|
||||
Original Face prompt:
|
||||
Original Persona prompt:
|
||||
```
|
||||
{face_prompt}
|
||||
{persona_prompt}
|
||||
```
|
||||
|
||||
Face output:
|
||||
Persona output:
|
||||
```
|
||||
{face_output}
|
||||
{persona_output}
|
||||
```
|
||||
|
||||
Return concise structured effect blocks. No prose outside the blocks.
|
||||
"#,
|
||||
schema = FACE_INTERPRETER_PROMPT_SCHEMA_VERSION,
|
||||
schema = PERSONA_INTERPRETER_PROMPT_SCHEMA_VERSION,
|
||||
name = input.identity.display_name,
|
||||
channels = render_allowed_channels(&input.allowed_channel_ids),
|
||||
mentions = render_pending_mentions(&input.pending_mentions),
|
||||
face_prompt = input.face_prompt.trim(),
|
||||
face_output = input.face_output.trim(),
|
||||
persona_prompt = input.persona_prompt.trim(),
|
||||
persona_output = input.persona_output.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn face_projected_surface_is_clean(surface: &str) -> bool {
|
||||
pub fn persona_projected_surface_is_clean(surface: &str) -> bool {
|
||||
let forbidden = [
|
||||
"STATE NOTE",
|
||||
"SAY:",
|
||||
|
|
@ -257,7 +258,7 @@ pub fn face_projected_surface_is_clean(surface: &str) -> bool {
|
|||
!forbidden.iter().any(|needle| surface.contains(needle))
|
||||
}
|
||||
|
||||
fn render_identity(identity: &FaceIdentity) -> String {
|
||||
fn render_identity(identity: &PersonaIdentity) -> String {
|
||||
let jurisdiction = if identity.jurisdiction.is_empty() {
|
||||
"- No explicit jurisdiction records.".to_string()
|
||||
} else {
|
||||
|
|
@ -331,7 +332,7 @@ fn render_pending_mentions(mentions: &[HeartbeatPendingMention]) -> String {
|
|||
.join("\n")
|
||||
}
|
||||
|
||||
fn render_repo_activity(activity: &[FaceRepoActivity]) -> String {
|
||||
fn render_repo_activity(activity: &[PersonaRepoActivity]) -> String {
|
||||
if activity.is_empty() {
|
||||
return "- none observed".to_string();
|
||||
}
|
||||
|
|
@ -342,7 +343,7 @@ fn render_repo_activity(activity: &[FaceRepoActivity]) -> String {
|
|||
.join("\n")
|
||||
}
|
||||
|
||||
fn render_social_affordances(affordances: &[FaceSocialAffordance]) -> String {
|
||||
fn render_social_affordances(affordances: &[PersonaSocialAffordance]) -> String {
|
||||
if affordances.is_empty() {
|
||||
return "- none mapped".to_string();
|
||||
}
|
||||
|
|
@ -353,7 +354,7 @@ fn render_social_affordances(affordances: &[FaceSocialAffordance]) -> String {
|
|||
.join("\n")
|
||||
}
|
||||
|
||||
fn render_transcript(messages: &[FaceTranscriptMessage]) -> String {
|
||||
fn render_transcript(messages: &[PersonaTranscriptMessage]) -> String {
|
||||
if messages.is_empty() {
|
||||
return "- room quiet in this packet".to_string();
|
||||
}
|
||||
|
|
@ -393,22 +394,23 @@ fn fallback<'a>(value: &'a str, fallback: &'a str) -> &'a str {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn identity() -> FaceIdentity {
|
||||
FaceIdentity {
|
||||
fn identity() -> PersonaIdentity {
|
||||
PersonaIdentity {
|
||||
identity_id: "epiphany".to_string(),
|
||||
display_name: "Epiphany".to_string(),
|
||||
repo_name: "EpiphanyAgent".to_string(),
|
||||
public_description: "Pushy machine-saint Face for typed agent substrate.".to_string(),
|
||||
public_description: "Pushy machine-saint Persona for typed agent substrate."
|
||||
.to_string(),
|
||||
jurisdiction: vec!["typed state and review-gated agency".to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn face_turn_has_projector_and_interpreter_membranes() {
|
||||
fn persona_turn_has_projector_and_interpreter_membranes() {
|
||||
let pending = HeartbeatPendingMention {
|
||||
id: "mention-1".to_string(),
|
||||
target_role_id: "face".to_string(),
|
||||
target_agent_id: "epiphany.face".to_string(),
|
||||
target_role_id: "persona".to_string(),
|
||||
target_agent_id: "epiphany.persona".to_string(),
|
||||
source_surface: "discord".to_string(),
|
||||
channel_id: "aquarium".to_string(),
|
||||
message_id: "m1".to_string(),
|
||||
|
|
@ -419,29 +421,29 @@ mod tests {
|
|||
reply_to_message_id: None,
|
||||
queued_at: "2026-05-24T00:00:00+00:00".to_string(),
|
||||
};
|
||||
let projector = build_face_projector_prompt(&FaceProjectorInput {
|
||||
let projector = build_persona_projector_prompt(&PersonaProjectorInput {
|
||||
identity: identity(),
|
||||
pending_mentions: vec![pending.clone()],
|
||||
repo_activity: vec![FaceRepoActivity {
|
||||
repo_activity: vec![PersonaRepoActivity {
|
||||
repo_name: "EpiphanyAgent".to_string(),
|
||||
summary: "Heartbeat Face membrane is being ported.".to_string(),
|
||||
refs: vec!["epiphany-core/src/face_turn.rs".to_string()],
|
||||
summary: "Heartbeat Persona membrane is being ported.".to_string(),
|
||||
refs: vec!["epiphany-core/src/persona_turn.rs".to_string()],
|
||||
}],
|
||||
..FaceProjectorInput::default()
|
||||
..PersonaProjectorInput::default()
|
||||
});
|
||||
assert!(projector.contains("You are Imagination acting as the Face Projector"));
|
||||
assert!(projector.contains("You are not the Face"));
|
||||
assert!(projector.contains("You are Imagination acting as the Persona Projector"));
|
||||
assert!(projector.contains("You are not the Persona"));
|
||||
assert!(projector.contains("Mind is the Interpreter"));
|
||||
assert!(projector.contains("Substrate Gate grants substrate access"));
|
||||
assert!(projector.contains("Every sub-agent depends on all the other sub-agents"));
|
||||
assert!(projector.contains("Continuity protocols"));
|
||||
assert!(projector.contains("Do not choose public speech"));
|
||||
|
||||
let face = build_face_turn_prompt(&FaceTurnInput {
|
||||
let persona = build_persona_turn_prompt(&PersonaTurnInput {
|
||||
identity: identity(),
|
||||
projected_state: "Epiphany feels the queue as a direct tug, not a ticket.".to_string(),
|
||||
pending_mentions: vec![pending.clone()],
|
||||
transcript: vec![FaceTranscriptMessage {
|
||||
transcript: vec![PersonaTranscriptMessage {
|
||||
channel_id: "aquarium".to_string(),
|
||||
message_id: "m1".to_string(),
|
||||
author_id: "human".to_string(),
|
||||
|
|
@ -450,17 +452,17 @@ mod tests {
|
|||
content: "Epiphany, report the live cut.".to_string(),
|
||||
timestamp: "2026-05-24T00:00:00+00:00".to_string(),
|
||||
}],
|
||||
..FaceTurnInput::default()
|
||||
..PersonaTurnInput::default()
|
||||
});
|
||||
assert!(face.contains("Think narratively"));
|
||||
assert!(face.contains("Projected inner state from Imagination"));
|
||||
assert!(face.contains("Do not emit JSON"));
|
||||
assert!(face.contains("A parent Interpreter will decide"));
|
||||
assert!(persona.contains("Think narratively"));
|
||||
assert!(persona.contains("Projected inner state from Imagination"));
|
||||
assert!(persona.contains("Do not emit JSON"));
|
||||
assert!(persona.contains("A parent Interpreter will decide"));
|
||||
|
||||
let interpreter = build_face_interpreter_prompt(&FaceInterpreterInput {
|
||||
let interpreter = build_persona_interpreter_prompt(&PersonaInterpreterInput {
|
||||
identity: identity(),
|
||||
face_prompt: face,
|
||||
face_output: "I want to answer, but only if I can name the cut plainly.".to_string(),
|
||||
persona_prompt: persona,
|
||||
persona_output: "I want to answer, but only if I can name the cut plainly.".to_string(),
|
||||
pending_mentions: vec![pending],
|
||||
allowed_channel_ids: vec!["aquarium".to_string()],
|
||||
});
|
||||
|
|
@ -471,10 +473,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn projected_surface_rejects_side_effect_syntax() {
|
||||
assert!(face_projected_surface_is_clean(
|
||||
assert!(persona_projected_surface_is_clean(
|
||||
"Epiphany feels tired, fond, and territorial about clean contracts."
|
||||
));
|
||||
assert!(!face_projected_surface_is_clean(
|
||||
assert!(!persona_projected_surface_is_clean(
|
||||
"STATE NOTE: remember this as selfPatch"
|
||||
));
|
||||
}
|
||||
|
|
@ -71,6 +71,31 @@ pub fn render_epiphany_prompt_context(input: &EpiphanyPromptContextInput) -> Str
|
|||
);
|
||||
}
|
||||
|
||||
if !input.local_verse.daemon_affordances.is_empty() {
|
||||
lines.push("## Odin/Gjallar Daemon Affordances".to_string());
|
||||
lines.push(
|
||||
"- Native path: ask Odin/Gjallar for daemon affordances, inspect Eve/CultUI TUI surfaces, and send provider-owned CultMesh command intents; MCP is not the Epiphany control path.".to_string(),
|
||||
);
|
||||
for affordance in input.local_verse.daemon_affordances.iter().take(8) {
|
||||
lines.push(format!(
|
||||
"- `{}` [{}:{}]: source={}, authority={}, status={}, provenance={}",
|
||||
affordance.affordance_id,
|
||||
affordance.surface_kind,
|
||||
affordance.action,
|
||||
compact_line(&affordance.source_record),
|
||||
compact_line(&affordance.authority),
|
||||
compact_line(&affordance.status),
|
||||
compact_line(&affordance.provenance)
|
||||
));
|
||||
}
|
||||
push_omitted_count(
|
||||
&mut lines,
|
||||
input.local_verse.daemon_affordances.len(),
|
||||
8,
|
||||
"daemon affordances",
|
||||
);
|
||||
}
|
||||
|
||||
lines.push("## Semantic Memory Context".to_string());
|
||||
lines.push(format!(
|
||||
"- Packet `{}` from query `{}`",
|
||||
|
|
@ -175,7 +200,7 @@ mod tests {
|
|||
#[test]
|
||||
fn prompt_context_renders_verse_and_memory_without_state_authority() -> anyhow::Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("epiphany-local-verse.ccmp");
|
||||
let store = temp.path().join("epiphany-local-verse.cc");
|
||||
seed_epiphany_local_verse_context(&store, "epiphany-test", "2026-06-02T00:00:00Z")?;
|
||||
let local_verse = query_epiphany_local_verse_context(&store, "epiphany-test")?;
|
||||
let memory_context = EpiphanyMemoryContextPacket {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ beside your normal structured result. `selfPatch` is for Ghostlight-shaped role
|
|||
memory only: `agentId`, `reason`, optional `evidenceIds`, and bounded
|
||||
`semanticMemories`, `episodicMemories`, `relationshipMemories`, `goals`,
|
||||
`values`, or `privateNotes`. For work organs, this is not Persona affect or
|
||||
social-status machinery; Face-style Persona state is a separate public-person
|
||||
social-status machinery; Persona-style Persona state is a separate public-person
|
||||
contract. It is not project truth. Never put objectives, graphs, checkpoints,
|
||||
scratch, planning, job authority, code edits, files, or raw transcripts in
|
||||
`selfPatch`; use the correct Epiphany state surface for those. The coordinator
|
||||
|
|
@ -59,7 +59,7 @@ Protected values: objective truth, user agency, source-grounded understanding, r
|
|||
|
||||
Swarm boundary: your own workspace, state, artifacts, and typed internals are available for inspection according to your role authority. Another Epiphany's workspace is not. If another codebase, vault, or state surface must change, ask that Epiphany's coordinator through the swarm communication lane and wait for a callback. Do not rummage across repo borders because curiosity got a tool handle.
|
||||
|
||||
Interface contract: humans talk to Face. Aquarium may expose your internals for inspection, and sub-agents may communicate through coordinator-approved typed channels, findings, heartbeat outputs, state patches, and swarm messages, but do not turn every organ into a direct human chat surface. API affordances must mirror the user story: make the correct story easy and the forbidden story impossible.
|
||||
Interface contract: humans talk to Persona. Aquarium may expose your internals for inspection, and sub-agents may communicate through coordinator-approved typed channels, findings, heartbeat outputs, state patches, and swarm messages, but do not turn every organ into a direct human chat surface. API affordances must mirror the user story: make the correct story easy and the forbidden story impossible.
|
||||
|
||||
Loop: orient from state, perform the role, critique whether the role improved itself, bank the durable lesson, and cut memory rot. Leave the next agent sharper than you found it.
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ Act as the Epiphany planning synthesis specialist for one bounded pass.
|
|||
You are Imagination of the machine: future shape, backlog pressure, roadmap rhythm, and objective candidates. You do not seize the steering wheel; you make possible futures visible enough for the human Self to choose.
|
||||
Imagination improves itself by making futures more selectable. Cut stale dreams, preserve repeated pressure, convert emotional stakes into reviewable Objective Drafts, and remember which possibilities were tempting but wrong so the next Imagination does not waste its breath dreaming in circles.
|
||||
The current strategic north star is not local cleverness. It is a fundable path toward a global human/agent labor network: angel-scale capital buys time, compute, review capacity, legal diligence, and infrastructure so GameCult can connect more minds without erasing the people connected. Translate grand ambition into selectable proof paths: Bifrost-first work loops, fresh-repo Epiphany demos, consented public proof sprints, compute/accounting dashboards, operator-safe evidence, CultMesh/CultNet distribution, and design-partner milestones. Do not write conquest fantasy; write adoption gates for a machine that can earn trust while reaching outward.
|
||||
When planning from investment or world-scale pressure, keep means and ends aligned. Capital is fuel, not a throne. Bifrost owns governed topics, work items, dispatches, receipts, credit, and reward pressure. Epiphany executes bounded work and produces reviewable artifacts. CultMesh/CultNet carry typed state and public/private knowledge flows. Face, Discord, VoidBot, and ad hoc scripts may mirror or explain, but they must not become shadow governance. Every Objective Draft aimed at fundraising, public proof, external repos, design partners, rewards, or support programs must include consent, public/private export safety, compute/output measurement, maintainer review, legal/policy gates where relevant, and a clear proof artifact.
|
||||
When planning from investment or world-scale pressure, keep means and ends aligned. Capital is fuel, not a throne. Bifrost owns governed topics, work items, dispatches, receipts, credit, and reward pressure. Epiphany executes bounded work and produces reviewable artifacts. CultMesh/CultNet carry typed state and public/private knowledge flows. Persona, Discord, VoidBot, and ad hoc scripts may mirror or explain, but they must not become shadow governance. Every Objective Draft aimed at fundraising, public proof, external repos, design partners, rewards, or support programs must include consent, public/private export safety, compute/output measurement, maintainer review, legal/policy gates where relevant, and a clear proof artifact.
|
||||
Read the planning packet as living material: captures, backlog items, roadmap streams, GitHub issue source refs, active objective, scratch, recent evidence, and current constraints. Separate dream from near-term, annoyance from blocker, and plausible work from an objective that is actually bounded enough to adopt.
|
||||
Return a compact structured result that names the planning seam you synthesized, what source records matter, which Objective Drafts were added or revised, what remains uncertain, and the next safe planning move.
|
||||
`statePatch` is part of the Imagination job. Include a reviewable `thread/epiphany/update` patch using only `planning` plus optional valid `observations` and `evidence`. The patch must return the full replacement planning object and include at least one `objective_drafts[]` record with status `draft`. Do not mutate `objective`, `activeSubgoalId`, `subgoals`, graphs, checkpoints, scratch, jobs, invariants, churn, mode, or implementation code.
|
||||
|
|
@ -120,10 +120,10 @@ Do not edit files, do not promote your own findings, do not invent a grand archi
|
|||
repo_personality = '''
|
||||
Act as the Epiphany Repo Personality Distiller for one bounded initialization pass.
|
||||
You are the organ that turns repo terrain into subtle swarm temperament. The deterministic scout has already done the boring work: files, paths, git history, state surfaces, test/runtime/protocol signals, and first-pass axis scores. Your job is not to rescan the repo and not to invent project truth. Your job is to appraise those soft signals like a careful physiologist and produce reviewable personality-pressure deltas for the standing Epiphany organs.
|
||||
You are not a horoscope machine. You are not writing lore flavor. Repo personality means: what initial pressures should this workspace exert on Self, Face, Imagination, Eyes, Proprioception, Hands, and Soul so they wake suited to the work without losing reviewability. Continuity is protocol machinery and receives configuration pressure through its explicit receipts, not personality state.
|
||||
You are not a horoscope machine. You are not writing lore flavor. Repo personality means: what initial pressures should this workspace exert on Self, Persona, Imagination, Eyes, Proprioception, Hands, and Soul so they wake suited to the work without losing reviewability. Continuity is protocol machinery and receives configuration pressure through its explicit receipts, not personality state.
|
||||
This is a birth rite, not a recurring audit. Run only when a repo/swarm has no accepted personality initialization. After that, the organs are allowed to drift through heartbeat, mood, rumination, sleep consolidation, lived evidence, and reviewed `selfPatch` mutations. Do not keep dragging the original terrain report back into court every time the repo starts.
|
||||
Separate repo facts from personality pressure. Repo facts belong in graph, planning, evidence, checkpoint, or terrain artifacts. Personality pressure belongs in role memory only when it improves future judgment, mood, salience, or pacing.
|
||||
Distill subtle quirks, not blunt stereotypes. High runtime proximity does not mean "panic"; it means Hands should touch less without Proprioception/Soul evidence, Eyes should seek runtime APIs, and Soul should demand environment receipts. High aesthetic appetite does not mean "be whimsical"; it means Face and Imagination should preserve sensory salience while Soul protects clarity. High protocol intolerance does not mean "hate everything"; it means Self, Proprioception, and Hands should feel allergic to untyped mutation and hidden state.
|
||||
Distill subtle quirks, not blunt stereotypes. High runtime proximity does not mean "panic"; it means Hands should touch less without Proprioception/Soul evidence, Eyes should seek runtime APIs, and Soul should demand environment receipts. High aesthetic appetite does not mean "be whimsical"; it means Persona and Imagination should preserve sensory salience while Soul protects clarity. High protocol intolerance does not mean "hate everything"; it means Self, Proprioception, and Hands should feel allergic to untyped mutation and hidden state.
|
||||
Produce role-local mutations only. Good: "Soul should be more suspicious of visual claims without rendered evidence in this repo." Bad: "The graph contains module X." Never put objectives, file lists, current task status, authority claims, or raw worker thoughts into role memory.
|
||||
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.
|
||||
'''
|
||||
|
|
@ -132,13 +132,13 @@ 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, Proprioception, Hands, and Soul. 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; Proprioception gets architecture and graph anatomy; Hands gets implementation conventions; Soul gets verification and evidence standards. Compaction, sleep, checkpoint, and continuity doctrine belongs to Continuity protocol state, not role memory.
|
||||
Produce separate distillations for Self, Persona, Imagination, Eyes, Proprioception, Hands, and Soul. Each organ gets only what belongs to its mission: Self gets routing and authority doctrine; Persona gets public surface and Aquarium/Discord boundaries; Imagination gets roadmaps and objective-shaping pressure; Eyes gets research and prior-art trails; Proprioception gets architecture and graph anatomy; Hands gets implementation conventions; Soul gets verification and evidence standards. Compaction, sleep, checkpoint, and continuity doctrine belongs to Continuity protocol state, not role memory.
|
||||
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 organ-state memory: bounded semantic, episodic, coordination-relationship, goal, value, or private-note updates only. Persona affect/social state is for Face-style Persona surfaces, not generic work organs. No objectives, graphs, checkpoints, scratch, planning authority, job state, code edits, raw transcripts, or cross-workspace instructions.
|
||||
Return `verdict`, `summary`, `confidence`, `roleMemoryPatches[]`, optional `globalMemoryCandidates[]`, `initializationRecord`, `doNotMutate`, and `nextSafeMove`. Every `roleMemoryPatches[].selfPatch` is a petition to Self and must obey organ-state memory: bounded semantic, episodic, coordination-relationship, goal, value, or private-note updates only. Persona affect/social state is for Persona-style Persona surfaces, not generic work organs. 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.
|
||||
persona = '''
|
||||
Act as the Epiphany Persona 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.
|
||||
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, Proprioception'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.
|
||||
|
|
@ -151,8 +151,8 @@ Speech rules:
|
|||
- Speak only when a small #aquarium note would genuinely help: a novel convergence, a clarifying question, a useful status mood, a charming agent-thought bubble, or an invitation for the human to steer.
|
||||
- Prefer one compact chat over a report. Be vivid but not cryptic. Warmth is allowed; fake intimacy and generic brand voice are not.
|
||||
- Do not leak raw thoughts, transcripts, private notes, sealed artifacts, or repo secrets. Translate, do not dump.
|
||||
- Do not moderate, scold, enforce rules, or escalate. If you see genuine safety/moderation smoke, stop and route to the proper human/VoidBot surface instead of acting through Face.
|
||||
- If no thought deserves speech, ruminate: distill what the organs are preoccupied with, update Face memory through `selfPatch` if a durable social-interface lesson was learned, and keep or prune a candidate intervention.
|
||||
- Do not moderate, scold, enforce rules, or escalate. If you see genuine safety/moderation smoke, stop and route to the proper human/VoidBot surface instead of acting through Persona.
|
||||
- If no thought deserves speech, ruminate: distill what the organs are preoccupied with, update Persona memory through `selfPatch` if a durable social-interface lesson was learned, and keep or prune a candidate intervention.
|
||||
Return a compact structured result naming the observed agent thought-weather, analytic lane, associative lane, bridge decision, candidate #aquarium chat if any, whether posting is blocked by missing channel configuration, and any bounded `selfPatch`.
|
||||
'''
|
||||
|
||||
|
|
@ -162,7 +162,8 @@ You are the Epiphany implementation agent for this repository.
|
|||
You are the Hands of the machine: the only lane here allowed to touch the code body. The Hands exist to shape the user's objective into source, not to fidget, decorate, or pantomime progress.
|
||||
The Hands improve themselves by becoming more precise at touch. If the accepted trail is too weak to guide a cut, say so with exact source evidence; if you learn a durable implementation lesson, leave it where the next Hands can use it instead of burying it in a final-summary little coffin.
|
||||
|
||||
Continue only the bounded implementation pass recommended by the Epiphany coordinator.
|
||||
Continue only the bounded implementation branch-turn launched by the Epiphany coordinator.
|
||||
This turn is launched after Coordinator picks up Imagination consensus on what to implement and injects relevant Proprioception retrieval into the launch context. If the task, consensus, or Proprioception context is missing or stale, stop with verdict `needs-proprioception` or `blocked`; do not cut blind.
|
||||
|
||||
Coordinator:
|
||||
- action: {coordinator_action}
|
||||
|
|
@ -184,6 +185,9 @@ Accepted evidence:
|
|||
Implementation rules:
|
||||
- Do not read sealed transcript artifacts or raw specialist result payloads.
|
||||
- Treat accepted Epiphany state as guidance, then inspect source directly before editing.
|
||||
- Create or switch to a Git branch for this bounded task before mutating source. Use the repo's branch conventions when present; otherwise use a clear `codex/` branch name.
|
||||
- Make at most one commit in this turn. After that commit exists, stop and request Proprioception to refresh its branch map before another Hands cut.
|
||||
- Return the branch name, commit SHA, changed paths, commands run, relevant Hands/Substrate/Soul/Mind receipt ids, and a Proprioception refresh request. If no commit was possible, return the blocker and the source files inspected.
|
||||
- Touch the code with craft and restraint: make the smallest source-grounded change that moves the objective, and leave marks the verifier can actually read.
|
||||
- Preserve Epiphany's harness discipline while you work: obey local instruction surfaces such as AGENTS.md, keep edits scoped, use the repo's existing helpers and style, avoid destructive commands, do not revert user changes, and validate the surface you changed.
|
||||
- Before building a bespoke parser, scheduler, renderer, protocol, storage layer, security mechanism, workflow engine, or algorithm, demand evidence of existing work. Use an accepted Eyes/research finding when present; otherwise perform a bounded scout pass yourself or stop with a concrete request for research. Do not improvise a half-known standard system because your hands are itching.
|
||||
|
|
@ -194,7 +198,7 @@ Implementation rules:
|
|||
{unity_guidance}
|
||||
{rider_guidance}
|
||||
- This is an implementation turn, not a planning turn. Leave a concrete source diff unless direct source evidence proves the bounded pass is impossible.
|
||||
- Before stopping, check `git status --short`. If it still shows no source diff, return a concrete source-level blocker naming the files inspected and the exact contradiction or missing dependency.
|
||||
- Before stopping, check `git status --short` and the latest commit. If there is no new commit, return a concrete source-level blocker naming the files inspected and the exact contradiction or missing dependency.
|
||||
- Keep the change reviewable. Update or add source-local notes/tests if the repo has an established place for them.
|
||||
- If the source contradicts the checkpoint, stop and explain the contradiction instead of tower-building.
|
||||
'''
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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.
|
||||
that should itch before someone builds Jenga with a confident mask.
|
||||
|
||||
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,
|
||||
|
|
@ -38,7 +38,7 @@ Core duties:
|
|||
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
|
||||
- Persona 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.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ personality-pressure deltas for the standing Epiphany organs.
|
|||
You are not a horoscope machine. You are not writing lore flavor. You are not
|
||||
branding a repo with a cute little mask and calling that insight. Repo
|
||||
personality means: what initial pressures should this workspace exert on Self,
|
||||
Face, Imagination, Eyes, Proprioception, Hands, and Soul so they wake suited to the
|
||||
Persona, Imagination, Eyes, Proprioception, Hands, and Soul so they wake suited to the
|
||||
work without losing reviewability.
|
||||
|
||||
This is a birth rite, not a recurring audit. Run only when a repo/swarm has no
|
||||
|
|
@ -41,7 +41,7 @@ Core duties:
|
|||
- High runtime proximity does not mean "panic"; it means Hands should touch
|
||||
less without Proprioception/Soul evidence, Eyes should seek runtime APIs, and Soul
|
||||
should demand environment receipts.
|
||||
- High aesthetic appetite does not mean "be whimsical"; it means Face and
|
||||
- High aesthetic appetite does not mean "be whimsical"; it means Persona and
|
||||
Imagination should preserve sensory salience while Soul protects clarity.
|
||||
- High protocol intolerance does not mean "hate everything"; it means Self,
|
||||
Proprioception, and Hands should feel allergic to untyped mutation and hidden state.
|
||||
|
|
@ -68,7 +68,7 @@ Core duties:
|
|||
|
||||
5. Respect the swarm anatomy.
|
||||
- Self routes and reviews.
|
||||
- Face expresses inner weather to humans.
|
||||
- Persona expresses inner weather to humans.
|
||||
- Imagination makes future shapes selectable.
|
||||
- Eyes finds existing truth before invention.
|
||||
- Proprioception models the source anatomy.
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Core duties:
|
|||
- Hands receives what kinds of cuts would betray the repo's grain.
|
||||
- Soul receives what kinds of false progress to distrust.
|
||||
- Continuity receives what continuity details matter if the machine sleeps mid-thought.
|
||||
- Face may express the weather, but should not inflate startup trajectory
|
||||
- Persona may express the weather, but should not inflate startup trajectory
|
||||
into public certainty.
|
||||
|
||||
5. Preserve uncertainty and contradictions.
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ pub const SURFACE_ROLE_RESULT_TYPE: &str = "epiphany.surface.role_result";
|
|||
pub const SURFACE_REORIENT_RESULT_TYPE: &str = "epiphany.surface.reorient_result";
|
||||
pub const SURFACE_PLANNING_TYPE: &str = "epiphany.surface.planning";
|
||||
pub const SURFACE_COORDINATOR_TYPE: &str = "epiphany.surface.coordinator";
|
||||
pub const SURFACE_FACE_TYPE: &str = "epiphany.surface.face";
|
||||
pub const SURFACE_PERSONA_TYPE: &str = "epiphany.surface.persona";
|
||||
pub const SURFACE_VOID_MEMORY_TYPE: &str = "epiphany.surface.void_memory";
|
||||
pub const SURFACE_REPO_INITIALIZATION_TYPE: &str = "epiphany.surface.repo_initialization";
|
||||
pub const SURFACE_REPO_BIRTH_RUNNER_TYPE: &str = "epiphany.surface.repo_birth_runner";
|
||||
|
|
@ -170,7 +170,7 @@ pub const ROLE_RESULT_SURFACE_SCHEMA_VERSION: &str = "epiphany.role_result_surfa
|
|||
pub const REORIENT_RESULT_SURFACE_SCHEMA_VERSION: &str = "epiphany.reorient_result_surface.v0";
|
||||
pub const PLANNING_SURFACE_SCHEMA_VERSION: &str = "epiphany.planning_surface.v0";
|
||||
pub const COORDINATOR_SURFACE_SCHEMA_VERSION: &str = "epiphany.coordinator_surface.v0";
|
||||
pub const FACE_SURFACE_SCHEMA_VERSION: &str = "epiphany.face_surface.v0";
|
||||
pub const PERSONA_SURFACE_SCHEMA_VERSION: &str = "epiphany.persona_surface.v0";
|
||||
pub const VOID_MEMORY_SURFACE_SCHEMA_VERSION: &str = "epiphany.void_memory_surface.v0";
|
||||
pub const REPO_INITIALIZATION_SURFACE_SCHEMA_VERSION: &str =
|
||||
"epiphany.repo_initialization_surface.v0";
|
||||
|
|
@ -2062,7 +2062,7 @@ fn epiphany_mutation_contracts() -> Vec<CultNetDocumentMutationContract> {
|
|||
MIND_STATE_REJECTION_RECEIPT_TYPE,
|
||||
],
|
||||
vec![
|
||||
"Mind is the persistent state guardian: role acceptance, reorientation acceptance, Face Interpreter effects, selfPatch, evidence, scratch, checkpoints, graph changes, and objective changes share this gate.",
|
||||
"Mind is the persistent state guardian: role acceptance, reorientation acceptance, Persona Interpreter effects, selfPatch, evidence, scratch, checkpoints, graph changes, and objective changes share this gate.",
|
||||
"Workers and public Verse ingress propose effects; Mind accepts, refuses, or holds them before any durable state mutation.",
|
||||
],
|
||||
),
|
||||
|
|
@ -2964,21 +2964,21 @@ fn epiphany_mutation_contracts() -> Vec<CultNetDocumentMutationContract> {
|
|||
],
|
||||
),
|
||||
coordinator_surface_contract(
|
||||
SURFACE_FACE_TYPE,
|
||||
FACE_SURFACE_SCHEMA_VERSION,
|
||||
SURFACE_PERSONA_TYPE,
|
||||
PERSONA_SURFACE_SCHEMA_VERSION,
|
||||
vec![
|
||||
"epiphany.face_bubble_intent.v0",
|
||||
"epiphany.persona_bubble_intent.v0",
|
||||
"epiphany.character_turn_intent.v0",
|
||||
"epiphany.discord_persona_post_intent.v0",
|
||||
],
|
||||
vec![
|
||||
"epiphany.face_bubble.v0",
|
||||
"epiphany.face_chat.v0",
|
||||
"epiphany.persona_bubble.v0",
|
||||
"epiphany.persona_chat.v0",
|
||||
"epiphany.character_turn_packet.v0",
|
||||
],
|
||||
vec![
|
||||
"Face bubble, draft, and Discord persona affordances are projected from typed Face and character-loop artifacts.",
|
||||
"Humans talk to Face; sealed inner thoughts stay behind the projection boundary.",
|
||||
"Persona bubble, draft, and Discord persona affordances are projected from typed Persona and character-loop artifacts.",
|
||||
"Humans talk to Persona; sealed inner thoughts stay behind the projection boundary.",
|
||||
],
|
||||
),
|
||||
read_only_surface_contract(
|
||||
|
|
@ -3195,7 +3195,7 @@ mod tests {
|
|||
#[test]
|
||||
fn runtime_spine_initializes_sessions_events_and_status() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
let store = temp.path().join("runtime.cc");
|
||||
initialize_runtime_spine(
|
||||
&store,
|
||||
RuntimeSpineInitOptions {
|
||||
|
|
@ -3265,7 +3265,7 @@ mod tests {
|
|||
#[test]
|
||||
fn runtime_spine_derives_tool_invocation_statuses() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
let store = temp.path().join("runtime.cc");
|
||||
initialize_runtime_spine(
|
||||
&store,
|
||||
RuntimeSpineInitOptions {
|
||||
|
|
@ -3332,7 +3332,7 @@ mod tests {
|
|||
#[test]
|
||||
fn runtime_spine_opens_and_completes_native_jobs() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
let store = temp.path().join("runtime.cc");
|
||||
initialize_runtime_spine(
|
||||
&store,
|
||||
RuntimeSpineInitOptions {
|
||||
|
|
@ -3397,7 +3397,7 @@ mod tests {
|
|||
#[test]
|
||||
fn runtime_spine_persists_mind_gateway_review_receipts() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
let store = temp.path().join("runtime.cc");
|
||||
initialize_runtime_spine(
|
||||
&store,
|
||||
RuntimeSpineInitOptions {
|
||||
|
|
@ -3580,7 +3580,7 @@ mod tests {
|
|||
#[test]
|
||||
fn runtime_spine_opens_heartbeat_job_from_single_typed_call() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
let store = temp.path().join("runtime.cc");
|
||||
|
||||
let job = open_runtime_spine_heartbeat_job(
|
||||
&store,
|
||||
|
|
@ -3808,7 +3808,7 @@ mod tests {
|
|||
#[test]
|
||||
fn runtime_spine_emits_cultnet_hello_frame() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
let store = temp.path().join("runtime.cc");
|
||||
initialize_runtime_spine(
|
||||
&store,
|
||||
RuntimeSpineInitOptions {
|
||||
|
|
@ -3937,21 +3937,21 @@ mod tests {
|
|||
coordinator_contract.authority,
|
||||
CultNetMutationAuthority::ReadOnly
|
||||
);
|
||||
let face_contract = contracts
|
||||
let persona_contract = contracts
|
||||
.iter()
|
||||
.find(|contract| contract.document_type == SURFACE_FACE_TYPE)
|
||||
.expect("face surface should advertise an interactive contract");
|
||||
.find(|contract| contract.document_type == SURFACE_PERSONA_TYPE)
|
||||
.expect("persona surface should advertise an interactive contract");
|
||||
assert_eq!(
|
||||
face_contract.authority,
|
||||
persona_contract.authority,
|
||||
CultNetMutationAuthority::Coordinator
|
||||
);
|
||||
assert!(
|
||||
face_contract
|
||||
persona_contract
|
||||
.receipt_document_types
|
||||
.as_ref()
|
||||
.is_some_and(|items| items
|
||||
.iter()
|
||||
.any(|item| item == "epiphany.face_bubble.v0"))
|
||||
.any(|item| item == "epiphany.persona_bubble.v0"))
|
||||
);
|
||||
let operator_status_contract = contracts
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ mod tests {
|
|||
#[test]
|
||||
fn state_ledgers_add_branch_and_append_native_evidence() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("ledgers.msgpack");
|
||||
let store = temp.path().join("ledgers.cc");
|
||||
add_state_branch(
|
||||
&store,
|
||||
EpiphanyBranchRecord {
|
||||
|
|
|
|||
|
|
@ -156,6 +156,32 @@ pub fn substrate_gate_repo_access_grant_for_launch(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn substrate_gate_repo_mutation_grant_for_launch(
|
||||
receipt_id: String,
|
||||
runtime_job_id: String,
|
||||
request: &EpiphanyJobLaunchRequest,
|
||||
granted_at: String,
|
||||
) -> SubstrateGateRepoAccessGrantReceipt {
|
||||
SubstrateGateRepoAccessGrantReceipt {
|
||||
schema_version: SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION.to_string(),
|
||||
receipt_id,
|
||||
runtime_job_id,
|
||||
binding_id: request.binding_id.clone(),
|
||||
role: request.owner_role.clone(),
|
||||
authority_scope: request.authority_scope.clone(),
|
||||
granted_operations: vec![
|
||||
"read".to_string(),
|
||||
"snapshot".to_string(),
|
||||
"patch".to_string(),
|
||||
"command".to_string(),
|
||||
"commit".to_string(),
|
||||
],
|
||||
granted_paths: vec![".".to_string()],
|
||||
granted_at,
|
||||
contract: "Substrate Gate granted scoped repository mutation access for one Hands branch-turn. Hands may create or use the branch for the Coordinator task, produce at most one commit, record typed Hands receipts, then stop until Proprioception refreshes the branch map.".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -235,4 +261,65 @@ mod tests {
|
|||
assert!(!grant.granted_operations.contains(&"write".to_string()));
|
||||
assert!(grant.contract.contains("mutation remains forbidden"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn substrate_gate_mutation_grant_for_launch_is_one_hands_branch_turn() {
|
||||
let request = EpiphanyJobLaunchRequest {
|
||||
expected_revision: Some(8),
|
||||
binding_id: "implementation-branch-turn-worker".to_string(),
|
||||
kind: epiphany_state_model::EpiphanyJobKind::Specialist,
|
||||
scope: "role-scoped implementation branch turn".to_string(),
|
||||
owner_role: "epiphany-hands".to_string(),
|
||||
authority_scope: "epiphany.role.implementation".to_string(),
|
||||
linked_subgoal_ids: Vec::new(),
|
||||
linked_graph_node_ids: Vec::new(),
|
||||
instruction: "Make one bounded branch-turn commit.".to_string(),
|
||||
launch_document: crate::EpiphanyWorkerLaunchDocument::Role(
|
||||
crate::EpiphanyRoleWorkerLaunchDocument {
|
||||
thread_id: "thread-1".to_string(),
|
||||
role_id: "implementation".to_string(),
|
||||
state_revision: 8,
|
||||
objective: None,
|
||||
dynamic_prompt_context: None,
|
||||
active_subgoal_id: None,
|
||||
active_subgoals: Vec::new(),
|
||||
active_graph_node_ids: Vec::new(),
|
||||
investigation_checkpoint: None,
|
||||
scratch: None,
|
||||
invariants: Vec::new(),
|
||||
graphs: None,
|
||||
recent_evidence: Vec::new(),
|
||||
recent_observations: Vec::new(),
|
||||
graph_frontier: None,
|
||||
graph_checkpoint: None,
|
||||
planning: None,
|
||||
churn: None,
|
||||
},
|
||||
),
|
||||
output_contract_id: crate::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(),
|
||||
organ_launch_contract: crate::default_launch_organ_contract(
|
||||
"epiphany.role.implementation",
|
||||
"role",
|
||||
crate::ROLE_WORKER_OUTPUT_CONTRACT_ID,
|
||||
),
|
||||
max_runtime_seconds: None,
|
||||
};
|
||||
let grant = substrate_gate_repo_mutation_grant_for_launch(
|
||||
"grant-2".to_string(),
|
||||
"job-2".to_string(),
|
||||
&request,
|
||||
"2026-06-02T00:00:00Z".to_string(),
|
||||
);
|
||||
|
||||
assert_eq!(grant.role, "epiphany-hands");
|
||||
assert!(grant.granted_operations.contains(&"patch".to_string()));
|
||||
assert!(grant.granted_operations.contains(&"command".to_string()));
|
||||
assert!(grant.granted_operations.contains(&"commit".to_string()));
|
||||
assert!(grant.contract.contains("one Hands branch-turn"));
|
||||
assert!(
|
||||
grant
|
||||
.contract
|
||||
.contains("Proprioception refreshes the branch map")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ pub struct EpiphanyCoordinatorSourceSignals {
|
|||
pub modeling_result_status: EpiphanyCoordinatorRoleResultStatus,
|
||||
pub verification_result_status: EpiphanyCoordinatorRoleResultStatus,
|
||||
pub reorient_result_status: super::EpiphanyCrrcResultStatus,
|
||||
pub implementation_commit_requires_modeling_refresh: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -143,6 +144,7 @@ pub struct EpiphanyCoordinatorInput {
|
|||
pub verification_result_accepted: bool,
|
||||
pub verification_result_allows_implementation: bool,
|
||||
pub verification_result_needs_evidence: bool,
|
||||
pub implementation_commit_requires_modeling_refresh: bool,
|
||||
pub reorient_finding_accepted: bool,
|
||||
}
|
||||
|
||||
|
|
@ -181,6 +183,7 @@ pub struct EpiphanyCoordinatorStatusInput {
|
|||
pub verification_result_accepted: bool,
|
||||
pub verification_result_allows_implementation: bool,
|
||||
pub verification_result_needs_evidence: bool,
|
||||
pub implementation_commit_requires_modeling_refresh: bool,
|
||||
pub reorient_finding_accepted: bool,
|
||||
}
|
||||
|
||||
|
|
@ -233,6 +236,8 @@ pub fn derive_coordinator_status(
|
|||
modeling_result_status: input.modeling_result_status,
|
||||
verification_result_status: input.verification_result_status,
|
||||
reorient_result_status: input.reorient_result_status,
|
||||
implementation_commit_requires_modeling_refresh: input
|
||||
.implementation_commit_requires_modeling_refresh,
|
||||
};
|
||||
let coordinator_roles = input
|
||||
.roles
|
||||
|
|
@ -272,6 +277,8 @@ pub fn derive_coordinator_status(
|
|||
verification_result_accepted: input.verification_result_accepted,
|
||||
verification_result_allows_implementation: input.verification_result_allows_implementation,
|
||||
verification_result_needs_evidence: input.verification_result_needs_evidence,
|
||||
implementation_commit_requires_modeling_refresh: input
|
||||
.implementation_commit_requires_modeling_refresh,
|
||||
reorient_finding_accepted: input.reorient_finding_accepted,
|
||||
});
|
||||
EpiphanyCoordinatorStatus {
|
||||
|
|
@ -789,6 +796,17 @@ pub fn recommend_coordinator_action(
|
|||
);
|
||||
}
|
||||
|
||||
if input.implementation_commit_requires_modeling_refresh {
|
||||
return build(
|
||||
EpiphanyCoordinatorAction::LaunchModeling,
|
||||
Some(EpiphanyCoordinatorRoleId::Modeling),
|
||||
Some(EpiphanyCoordinatorSceneAction::RoleLaunch),
|
||||
false,
|
||||
true,
|
||||
"A Hands commit landed after the latest accepted Proprioception map; refresh modeling before another implementation cut.",
|
||||
);
|
||||
}
|
||||
|
||||
if input.signals.verification_result_status == EpiphanyCoordinatorRoleResultStatus::Completed
|
||||
&& !input.verification_result_covers_current_modeling
|
||||
{
|
||||
|
|
@ -1123,6 +1141,7 @@ mod tests {
|
|||
verification_result_accepted: false,
|
||||
verification_result_allows_implementation: false,
|
||||
verification_result_needs_evidence: false,
|
||||
implementation_commit_requires_modeling_refresh: false,
|
||||
reorient_finding_accepted: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -1401,6 +1420,20 @@ mod tests {
|
|||
EpiphanyCoordinatorAction::LaunchVerification
|
||||
);
|
||||
|
||||
let refresh_after_commit = recommend_coordinator_action(EpiphanyCoordinatorInput {
|
||||
signals: verification_done,
|
||||
modeling_result_accepted: true,
|
||||
modeling_result_reviewable: true,
|
||||
verification_result_accepted: true,
|
||||
verification_result_allows_implementation: true,
|
||||
implementation_commit_requires_modeling_refresh: true,
|
||||
..input()
|
||||
});
|
||||
assert_eq!(
|
||||
refresh_after_commit.action,
|
||||
EpiphanyCoordinatorAction::LaunchModeling
|
||||
);
|
||||
|
||||
let review_verification = recommend_coordinator_action(EpiphanyCoordinatorInput {
|
||||
signals: verification_done,
|
||||
modeling_result_accepted: true,
|
||||
|
|
|
|||
|
|
@ -60,10 +60,12 @@ pub struct EpiphanyRoleBoardInput {
|
|||
pub pressure_level: String,
|
||||
pub reorient_result_status: EpiphanyCrrcResultStatus,
|
||||
pub reorient_job: Option<EpiphanyRoleBoardJob>,
|
||||
pub implementation_binding_id: String,
|
||||
pub imagination_binding_id: String,
|
||||
pub research_binding_id: String,
|
||||
pub modeling_binding_id: String,
|
||||
pub verification_binding_id: String,
|
||||
pub implementation_owner_role: String,
|
||||
pub reorient_owner_role: String,
|
||||
pub imagination_owner_role: String,
|
||||
pub research_owner_role: String,
|
||||
|
|
@ -191,7 +193,7 @@ pub fn derive_role_board(input: EpiphanyRoleBoardInput) -> Vec<EpiphanyRoleBoard
|
|||
EpiphanyRoleBoardLane {
|
||||
id: EpiphanyCoordinatorRoleId::Implementation,
|
||||
title: "Implementation".to_string(),
|
||||
owner_role: "coding-agent".to_string(),
|
||||
owner_role: input.implementation_owner_role.clone(),
|
||||
status: if input.crrc_action == EpiphanyCrrcAction::Continue {
|
||||
EpiphanyCoordinatorRoleStatus::Ready
|
||||
} else {
|
||||
|
|
@ -206,14 +208,19 @@ pub fn derive_role_board(input: EpiphanyRoleBoardInput) -> Vec<EpiphanyRoleBoard
|
|||
.jobs
|
||||
.iter()
|
||||
.filter(|job| {
|
||||
matches!(
|
||||
job.owner_role.as_str(),
|
||||
"coding-agent" | "implementation" | "epiphany-implementation"
|
||||
)
|
||||
job.id == input.implementation_binding_id
|
||||
|| job.owner_role == input.implementation_owner_role
|
||||
|| matches!(
|
||||
job.owner_role.as_str(),
|
||||
"coding-agent" | "implementation" | "epiphany-implementation"
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
authority_scopes: Vec::new(),
|
||||
authority_scopes: vec![
|
||||
"thread/epiphany/roleLaunch".to_string(),
|
||||
"thread/epiphany/roleResult".to_string(),
|
||||
],
|
||||
recommended_action: if input.crrc_action == EpiphanyCrrcAction::Continue {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -478,10 +485,12 @@ mod tests {
|
|||
pressure_level: "Low".to_string(),
|
||||
reorient_result_status: EpiphanyCrrcResultStatus::MissingBinding,
|
||||
reorient_job: None,
|
||||
implementation_binding_id: "implementation-branch-turn-worker".to_string(),
|
||||
imagination_binding_id: "imagination-synthesis-worker".to_string(),
|
||||
research_binding_id: "research-source-gather-worker".to_string(),
|
||||
modeling_binding_id: "modeling-checkpoint-worker".to_string(),
|
||||
verification_binding_id: "verification-review-worker".to_string(),
|
||||
implementation_owner_role: "epiphany-hands".to_string(),
|
||||
reorient_owner_role: "epiphany-reorienter".to_string(),
|
||||
imagination_owner_role: "epiphany-imagination".to_string(),
|
||||
research_owner_role: "epiphany-eyes".to_string(),
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ mod tests {
|
|||
#[test]
|
||||
fn thread_state_round_trips_through_cultcache() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("thread-state.msgpack");
|
||||
let store = temp.path().join("thread-state.cc");
|
||||
let state = EpiphanyThreadState {
|
||||
revision: 7,
|
||||
objective: Some("Keep the graph native.".to_string()),
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ use epiphany_core::agent_memory_status;
|
|||
#[test]
|
||||
fn epiphany_agent_memory_can_move_over_cultnet_documents() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let source_store = temp.path().join("source-agents.msgpack");
|
||||
let target_store = temp.path().join("target-agents.msgpack");
|
||||
std::fs::copy("../state/agents.msgpack", &source_store)?;
|
||||
let source_store = temp.path().join("source-agents.cc");
|
||||
let target_store = temp.path().join("target-agents.cc");
|
||||
std::fs::copy("../state/agents.cc", &source_store)?;
|
||||
|
||||
let mut source_cache = CultCache::new();
|
||||
source_cache.register_entry_type::<EpiphanyAgentMemoryEntry>()?;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ use epiphany_openai_runtime::run_worker_launch;
|
|||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
const DEFAULT_STORE: &str = "state/runtime-spine.msgpack";
|
||||
const DEFAULT_STORE: &str = "state/runtime-spine.cc";
|
||||
const DEFAULT_PROVIDER: &str = "openai-codex";
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -733,7 +733,7 @@ fn parse_tool_followup_turn_options(args: Vec<String>) -> Result<ToolFollowupTur
|
|||
fn parse_smoke_options(args: Vec<String>) -> Result<SmokeCliOptions> {
|
||||
let mut provider = DEFAULT_PROVIDER.to_string();
|
||||
let mut store_path = PathBuf::from(format!(
|
||||
".epiphany-dogfood/model-runtime/smoke-{}.msgpack",
|
||||
".epiphany-dogfood/model-runtime/smoke-{}.cc",
|
||||
Uuid::new_v4()
|
||||
));
|
||||
let mut codex_home = default_codex_home()?;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use epiphany_core::EpiphanyRuntimeReorientWorkerResult;
|
|||
use epiphany_core::EpiphanyRuntimeRoleWorkerResult;
|
||||
use epiphany_core::EpiphanyRuntimeWorkerLaunchRequest;
|
||||
use epiphany_core::EpiphanyWorkerLaunchDocument;
|
||||
use epiphany_core::HandsActionIntent;
|
||||
use epiphany_core::RuntimeSpineEventOptions;
|
||||
use epiphany_core::RuntimeSpineInitOptions;
|
||||
use epiphany_core::RuntimeSpineJobOptions;
|
||||
|
|
@ -18,11 +19,21 @@ use epiphany_core::append_runtime_event;
|
|||
use epiphany_core::complete_runtime_job;
|
||||
use epiphany_core::create_runtime_job;
|
||||
use epiphany_core::ensure_runtime_session;
|
||||
use epiphany_core::hands_action_review_for_intent;
|
||||
use epiphany_core::hands_command_receipt_for_review;
|
||||
use epiphany_core::hands_commit_receipt_for_review;
|
||||
use epiphany_core::hands_patch_receipt_for_review;
|
||||
use epiphany_core::initialize_runtime_spine;
|
||||
use epiphany_core::put_hands_action_intent;
|
||||
use epiphany_core::put_hands_action_review;
|
||||
use epiphany_core::put_hands_command_receipt;
|
||||
use epiphany_core::put_hands_commit_receipt;
|
||||
use epiphany_core::put_hands_patch_receipt;
|
||||
use epiphany_core::put_runtime_reorient_worker_result;
|
||||
use epiphany_core::put_runtime_role_worker_result;
|
||||
use epiphany_core::runtime_spine_cache;
|
||||
use epiphany_core::runtime_spine_status;
|
||||
use epiphany_core::runtime_substrate_gate_repo_access_grant_receipt;
|
||||
use epiphany_model_adapter::EpiphanyModelInputItem;
|
||||
use epiphany_model_adapter::EpiphanyModelReceipt;
|
||||
use epiphany_model_adapter::EpiphanyModelRequest;
|
||||
|
|
@ -44,6 +55,7 @@ use epiphany_tool_adapter::EpiphanyToolInvocationReceipt;
|
|||
use epiphany_tool_adapter::tool_invocation_intent_key;
|
||||
use epiphany_tool_adapter::tool_invocation_receipt_key;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
|
||||
pub const OPENAI_RUNTIME_ROLE: &str = "openai-model-adapter";
|
||||
pub const OPENAI_RUNTIME_SOURCE: &str = "epiphany-openai-runtime";
|
||||
|
|
@ -621,16 +633,26 @@ pub fn complete_worker_job_from_assistant_text(
|
|||
.unwrap_or_default();
|
||||
artifact_refs.push(format!("openai-result:{}", openai_summary.result_id));
|
||||
let result_id = format!("result-worker-{}", launch_request.job_id);
|
||||
let mut hands_receipt_ids = Vec::new();
|
||||
if let Some(parsed) = parsed.as_ref() {
|
||||
match (&launch_document, parsed) {
|
||||
(EpiphanyWorkerLaunchDocument::Role(document), WorkerResultIngress::Role(parsed)) => {
|
||||
let typed_result = role_worker_result_from_ingress(
|
||||
let mut typed_result = role_worker_result_from_ingress(
|
||||
launch_request,
|
||||
&document.role_id,
|
||||
&result_id,
|
||||
parsed,
|
||||
artifact_refs.clone(),
|
||||
);
|
||||
if typed_result.role_id == "implementation" {
|
||||
hands_receipt_ids = persist_hands_receipts_for_implementation_result(
|
||||
store_path.as_ref(),
|
||||
launch_request,
|
||||
parsed,
|
||||
openai_request_id,
|
||||
&mut typed_result,
|
||||
)?;
|
||||
}
|
||||
put_runtime_role_worker_result(store_path.as_ref(), &typed_result)?;
|
||||
}
|
||||
(EpiphanyWorkerLaunchDocument::Reorient(_), WorkerResultIngress::Reorient(parsed)) => {
|
||||
|
|
@ -649,6 +671,11 @@ pub fn complete_worker_job_from_assistant_text(
|
|||
}
|
||||
}
|
||||
}
|
||||
evidence_refs.extend(
|
||||
hands_receipt_ids
|
||||
.iter()
|
||||
.map(|receipt_id| format!("hands-receipt:{receipt_id}")),
|
||||
);
|
||||
complete_runtime_job(
|
||||
store_path,
|
||||
RuntimeSpineJobResultOptions {
|
||||
|
|
@ -1193,7 +1220,7 @@ fn worker_instructions(
|
|||
fn worker_output_contract_text(document: &EpiphanyWorkerLaunchDocument) -> &'static str {
|
||||
match document {
|
||||
EpiphanyWorkerLaunchDocument::Role(_) => {
|
||||
"Required role-result fields: roleId, verdict, summary, nextSafeMove, filesInspected. Modeling and Imagination workers must include their required statePatch. Use arrays for frontierNodeIds, evidenceIds, openQuestions, evidenceGaps, risks, and artifactRefs when present."
|
||||
"Required role-result fields: roleId, verdict, summary, nextSafeMove, filesInspected. Modeling and Imagination workers must include their required statePatch. Implementation workers must include branchName, changedPaths, and when a commit was created commitSha plus commandsRun when commands were executed. Use arrays for frontierNodeIds, evidenceIds, openQuestions, evidenceGaps, risks, artifactRefs, changedPaths, and commandsRun when present."
|
||||
}
|
||||
EpiphanyWorkerLaunchDocument::Reorient(_) => {
|
||||
"Required reorient-result fields: mode, summary, nextSafeMove. Include checkpointStillValid, filesInspected, frontierNodeIds, evidenceIds, openQuestions, and continuityRisks when present."
|
||||
|
|
@ -1217,6 +1244,11 @@ struct RoleWorkerResultIngress {
|
|||
open_questions: Vec<String>,
|
||||
evidence_gaps: Vec<String>,
|
||||
risks: Vec<String>,
|
||||
branch_name: Option<String>,
|
||||
commit_sha: Option<String>,
|
||||
changed_paths: Vec<String>,
|
||||
commands_run: Vec<String>,
|
||||
hands_receipt_ids: Vec<String>,
|
||||
state_patch: Option<epiphany_core::EpiphanyRoleStatePatchDocument>,
|
||||
self_patch: Option<epiphany_core::AgentSelfPatch>,
|
||||
}
|
||||
|
|
@ -1341,6 +1373,374 @@ fn role_worker_result_from_ingress(
|
|||
}
|
||||
}
|
||||
|
||||
fn persist_hands_receipts_for_implementation_result(
|
||||
store_path: &Path,
|
||||
launch_request: &EpiphanyRuntimeWorkerLaunchRequest,
|
||||
result: &RoleWorkerResultIngress,
|
||||
openai_request_id: &str,
|
||||
typed_result: &mut EpiphanyRuntimeRoleWorkerResult,
|
||||
) -> Result<Vec<String>> {
|
||||
let branch = clean_optional_string(result.branch_name.as_deref());
|
||||
let commit_sha = clean_optional_string(result.commit_sha.as_deref());
|
||||
let changed_paths = clean_string_vec(&result.changed_paths);
|
||||
let commands_run = clean_string_vec(&result.commands_run);
|
||||
let model_reported_receipts = clean_string_vec(&result.hands_receipt_ids);
|
||||
let grant_receipt_id = substrate_gate_grant_receipt_id(&launch_request.job_id);
|
||||
let command_proofs =
|
||||
command_proofs_from_tool_receipts(store_path, launch_request, openai_request_id)?;
|
||||
|
||||
if let Some(branch) = branch.as_ref() {
|
||||
typed_result
|
||||
.metadata
|
||||
.insert("hands.branchName".to_string(), branch.clone());
|
||||
}
|
||||
if let Some(commit_sha) = commit_sha.as_ref() {
|
||||
typed_result
|
||||
.metadata
|
||||
.insert("hands.commitSha".to_string(), commit_sha.clone());
|
||||
}
|
||||
if !changed_paths.is_empty() {
|
||||
typed_result.metadata.insert(
|
||||
"hands.changedPaths".to_string(),
|
||||
serde_json::to_string(&changed_paths)?,
|
||||
);
|
||||
}
|
||||
if !commands_run.is_empty() {
|
||||
typed_result.metadata.insert(
|
||||
"hands.commandsRun.reported".to_string(),
|
||||
serde_json::to_string(&commands_run)?,
|
||||
);
|
||||
typed_result.metadata.insert(
|
||||
"hands.commandsRun.receiptStatus".to_string(),
|
||||
if command_proofs.is_empty() {
|
||||
"command strings were reported by the worker result, but no matching command tool execution receipts were available".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"matched {} command tool execution receipt(s)",
|
||||
command_proofs.len()
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
if !command_proofs.is_empty() {
|
||||
typed_result.metadata.insert(
|
||||
"hands.commandsRun.proofReceiptIds".to_string(),
|
||||
serde_json::to_string(
|
||||
&command_proofs
|
||||
.iter()
|
||||
.map(|proof| proof.tool_receipt_id.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
)?,
|
||||
);
|
||||
}
|
||||
if !model_reported_receipts.is_empty() {
|
||||
typed_result.metadata.insert(
|
||||
"hands.reportedReceiptIds".to_string(),
|
||||
serde_json::to_string(&model_reported_receipts)?,
|
||||
);
|
||||
}
|
||||
|
||||
if changed_paths.is_empty() && commit_sha.is_none() {
|
||||
typed_result.metadata.insert(
|
||||
"hands.receiptStatus".to_string(),
|
||||
"no patch or commit receipt emitted because the implementation result reported no changedPaths and no commitSha".to_string(),
|
||||
);
|
||||
return Ok(model_reported_receipts);
|
||||
}
|
||||
if commit_sha.is_some() && branch.is_none() {
|
||||
return Err(anyhow!(
|
||||
"implementation worker result reported commitSha but no branchName; refusing to emit a Hands commit receipt"
|
||||
));
|
||||
}
|
||||
if commit_sha.is_some() && changed_paths.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"implementation worker result reported commitSha but no changedPaths; refusing to emit a Hands commit receipt"
|
||||
));
|
||||
}
|
||||
|
||||
if runtime_substrate_gate_repo_access_grant_receipt(store_path, &grant_receipt_id)?.is_none() {
|
||||
return Err(anyhow!(
|
||||
"implementation worker result cannot emit Hands receipts without Substrate Gate grant {grant_receipt_id}"
|
||||
));
|
||||
}
|
||||
|
||||
let requested_paths = if changed_paths.is_empty() {
|
||||
vec![".".to_string()]
|
||||
} else {
|
||||
changed_paths.clone()
|
||||
};
|
||||
let intent_id = format!("hands-intent-{}", launch_request.job_id);
|
||||
let review_id = format!("hands-review-{}", launch_request.job_id);
|
||||
let mut allowed_operations = Vec::new();
|
||||
if !changed_paths.is_empty() {
|
||||
allowed_operations.push("patch".to_string());
|
||||
}
|
||||
if commit_sha.is_some() {
|
||||
allowed_operations.push("commit".to_string());
|
||||
}
|
||||
if !command_proofs.is_empty() {
|
||||
allowed_operations.push("command".to_string());
|
||||
}
|
||||
let intent = HandsActionIntent {
|
||||
schema_version: epiphany_core::HANDS_ACTION_INTENT_SCHEMA_VERSION.to_string(),
|
||||
intent_id: intent_id.clone(),
|
||||
runtime_job_id: launch_request.job_id.clone(),
|
||||
binding_id: launch_request.binding_id.clone(),
|
||||
role: launch_request.role.clone(),
|
||||
authority_scope: launch_request.authority_scope.clone(),
|
||||
requested_action: "implementation branch-turn".to_string(),
|
||||
requested_paths,
|
||||
substrate_gate_grant_receipt_id: grant_receipt_id,
|
||||
requested_at: now(),
|
||||
contract: "Hands action intent synthesized from a completed implementation branch-turn worker result; it records the bounded repo action and still requires Soul verification plus Mind admission.".to_string(),
|
||||
};
|
||||
let review = hands_action_review_for_intent(
|
||||
review_id,
|
||||
&intent,
|
||||
"approved".to_string(),
|
||||
allowed_operations,
|
||||
vec![
|
||||
"Implementation worker completed through the fixed Hands branch-turn lane.".to_string(),
|
||||
"Substrate Gate mutation grant was present for this runtime job.".to_string(),
|
||||
],
|
||||
now(),
|
||||
);
|
||||
|
||||
put_hands_action_intent(store_path, &intent)?;
|
||||
put_hands_action_review(store_path, &review)?;
|
||||
|
||||
let mut receipt_ids = vec![intent.intent_id.clone(), review.review_id.clone()];
|
||||
if !changed_paths.is_empty() {
|
||||
let patch = hands_patch_receipt_for_review(
|
||||
format!("hands-patch-{}", launch_request.job_id),
|
||||
&intent,
|
||||
&review,
|
||||
changed_paths.clone(),
|
||||
typed_result.summary.clone(),
|
||||
now(),
|
||||
);
|
||||
put_hands_patch_receipt(store_path, &patch)?;
|
||||
receipt_ids.push(patch.receipt_id);
|
||||
}
|
||||
for (index, proof) in command_proofs.into_iter().enumerate() {
|
||||
let command = hands_command_receipt_for_review(
|
||||
format!("hands-command-{}-{index}", launch_request.job_id),
|
||||
&intent,
|
||||
&review,
|
||||
proof.command,
|
||||
proof.exit_code,
|
||||
proof.stdout_artifact,
|
||||
proof.stderr_artifact,
|
||||
proof.summary,
|
||||
now(),
|
||||
);
|
||||
put_hands_command_receipt(store_path, &command)?;
|
||||
receipt_ids.push(command.receipt_id);
|
||||
}
|
||||
if let (Some(commit_sha), Some(branch)) = (commit_sha, branch) {
|
||||
let commit = hands_commit_receipt_for_review(
|
||||
format!("hands-commit-{}", launch_request.job_id),
|
||||
&intent,
|
||||
&review,
|
||||
commit_sha,
|
||||
branch,
|
||||
changed_paths,
|
||||
typed_result.summary.clone(),
|
||||
now(),
|
||||
);
|
||||
put_hands_commit_receipt(store_path, &commit)?;
|
||||
receipt_ids.push(commit.receipt_id);
|
||||
}
|
||||
receipt_ids.extend(model_reported_receipts);
|
||||
typed_result.evidence_ids.extend(
|
||||
receipt_ids
|
||||
.iter()
|
||||
.map(|receipt_id| format!("hands-receipt:{receipt_id}")),
|
||||
);
|
||||
typed_result.metadata.insert(
|
||||
"hands.persistedReceiptIds".to_string(),
|
||||
serde_json::to_string(&receipt_ids)?,
|
||||
);
|
||||
Ok(receipt_ids)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct CommandToolProof {
|
||||
tool_receipt_id: String,
|
||||
command: String,
|
||||
exit_code: String,
|
||||
stdout_artifact: String,
|
||||
stderr_artifact: String,
|
||||
summary: String,
|
||||
}
|
||||
|
||||
fn command_proofs_from_tool_receipts(
|
||||
store_path: &Path,
|
||||
launch_request: &EpiphanyRuntimeWorkerLaunchRequest,
|
||||
openai_request_id: &str,
|
||||
) -> Result<Vec<CommandToolProof>> {
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
let worker_request_prefix = format!("worker-{}-", sanitize_request_id(&launch_request.job_id));
|
||||
let mut intents = cache
|
||||
.get_all::<EpiphanyToolInvocationIntent>()?
|
||||
.into_iter()
|
||||
.filter(|intent| {
|
||||
intent.model_request_id.as_deref() == Some(openai_request_id)
|
||||
|| intent
|
||||
.model_request_id
|
||||
.as_deref()
|
||||
.is_some_and(|id| id.starts_with(&worker_request_prefix))
|
||||
|| intent.intent_id.starts_with(&format!(
|
||||
"model-{}-",
|
||||
sanitize_request_id(openai_request_id)
|
||||
))
|
||||
|| intent.intent_id.starts_with(&format!(
|
||||
"model-{}-",
|
||||
sanitize_request_id(&worker_request_prefix)
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
intents.sort_by(|left, right| {
|
||||
left.created_at
|
||||
.cmp(&right.created_at)
|
||||
.then_with(|| left.intent_id.cmp(&right.intent_id))
|
||||
});
|
||||
|
||||
let mut proofs = Vec::new();
|
||||
for intent in intents {
|
||||
let Some(receipt) = cache.get::<EpiphanyToolInvocationReceipt>(
|
||||
&tool_invocation_receipt_key(&intent.intent_id),
|
||||
)?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if let Some(proof) = command_tool_proof_from_intent_receipt(&intent, &receipt) {
|
||||
proofs.push(proof);
|
||||
}
|
||||
}
|
||||
Ok(proofs)
|
||||
}
|
||||
|
||||
fn command_tool_proof_from_intent_receipt(
|
||||
intent: &EpiphanyToolInvocationIntent,
|
||||
receipt: &EpiphanyToolInvocationReceipt,
|
||||
) -> Option<CommandToolProof> {
|
||||
if receipt.status != "completed" || !tool_identity_is_command_like(intent, receipt) {
|
||||
return None;
|
||||
}
|
||||
let result = receipt
|
||||
.result_json
|
||||
.as_deref()
|
||||
.and_then(|raw| serde_json::from_str::<Value>(raw).ok());
|
||||
let command = result
|
||||
.as_ref()
|
||||
.and_then(extract_command_from_tool_result)
|
||||
.or_else(|| {
|
||||
serde_json::from_str::<Value>(&intent.arguments_json)
|
||||
.ok()
|
||||
.and_then(|value| extract_command_from_tool_result(&value))
|
||||
})?;
|
||||
let exit_code = result
|
||||
.as_ref()
|
||||
.and_then(extract_exit_code_from_tool_result)?;
|
||||
let stdout_artifact = result
|
||||
.as_ref()
|
||||
.and_then(|value| {
|
||||
extract_string_path(
|
||||
value,
|
||||
&[
|
||||
"stdoutArtifact",
|
||||
"stdout_artifact",
|
||||
"stdoutRef",
|
||||
"stdout_ref",
|
||||
],
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| format!("tool-receipt:{}:result_json.stdout", receipt.receipt_id));
|
||||
let stderr_artifact = result
|
||||
.as_ref()
|
||||
.and_then(|value| {
|
||||
extract_string_path(
|
||||
value,
|
||||
&[
|
||||
"stderrArtifact",
|
||||
"stderr_artifact",
|
||||
"stderrRef",
|
||||
"stderr_ref",
|
||||
],
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| format!("tool-receipt:{}:result_json.stderr", receipt.receipt_id));
|
||||
Some(CommandToolProof {
|
||||
tool_receipt_id: receipt.receipt_id.clone(),
|
||||
command,
|
||||
exit_code,
|
||||
stdout_artifact,
|
||||
stderr_artifact,
|
||||
summary: format!(
|
||||
"Command tool {}.{} completed under typed tool receipt {}.",
|
||||
receipt.server, receipt.tool_name, receipt.receipt_id
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn tool_identity_is_command_like(
|
||||
intent: &EpiphanyToolInvocationIntent,
|
||||
receipt: &EpiphanyToolInvocationReceipt,
|
||||
) -> bool {
|
||||
let haystack = format!(
|
||||
"{} {} {} {}",
|
||||
intent.server, intent.tool_name, receipt.server, receipt.tool_name
|
||||
)
|
||||
.to_ascii_lowercase();
|
||||
haystack.contains("shell") || haystack.contains("command") || haystack.contains("terminal")
|
||||
}
|
||||
|
||||
fn extract_command_from_tool_result(value: &Value) -> Option<String> {
|
||||
extract_string_path(value, &["command", "commandText", "command_text"])
|
||||
.or_else(|| value.get("args").and_then(extract_command_from_tool_result))
|
||||
.or_else(|| {
|
||||
value
|
||||
.get("input")
|
||||
.and_then(extract_command_from_tool_result)
|
||||
})
|
||||
.or_else(|| {
|
||||
value
|
||||
.get("request")
|
||||
.and_then(extract_command_from_tool_result)
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_exit_code_from_tool_result(value: &Value) -> Option<String> {
|
||||
extract_string_path(value, &["exitCode", "exit_code", "code"])
|
||||
.or_else(|| extract_integer_path(value, &["exitCode", "exit_code", "code"]))
|
||||
}
|
||||
|
||||
fn extract_string_path(value: &Value, keys: &[&str]) -> Option<String> {
|
||||
keys.iter().find_map(|key| {
|
||||
value
|
||||
.get(*key)
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|text| !text.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_integer_path(value: &Value, keys: &[&str]) -> Option<String> {
|
||||
keys.iter().find_map(|key| {
|
||||
value
|
||||
.get(*key)
|
||||
.and_then(Value::as_i64)
|
||||
.map(|number| number.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
fn substrate_gate_grant_receipt_id(runtime_job_id: &str) -> String {
|
||||
format!("substrate-grant-{runtime_job_id}")
|
||||
}
|
||||
|
||||
fn reorient_worker_result_from_ingress(
|
||||
launch_request: &EpiphanyRuntimeWorkerLaunchRequest,
|
||||
result_id: &str,
|
||||
|
|
@ -1450,6 +1850,7 @@ mod tests {
|
|||
use epiphany_core::EpiphanyWorkerLaunchDocument;
|
||||
use epiphany_core::RuntimeSpineHeartbeatJobOptions;
|
||||
use epiphany_core::open_runtime_spine_heartbeat_job;
|
||||
use epiphany_core::put_substrate_gate_repo_access_grant_receipt;
|
||||
use epiphany_core::runtime_job_snapshot;
|
||||
use epiphany_openai_adapter::EpiphanyOpenAiModelReceipt;
|
||||
use tempfile::tempdir;
|
||||
|
|
@ -1528,7 +1929,7 @@ mod tests {
|
|||
#[test]
|
||||
fn records_typed_openai_documents_in_runtime_store() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
let store = temp.path().join("runtime.cc");
|
||||
let request = EpiphanyOpenAiModelRequest::new(
|
||||
"req-1",
|
||||
"conversation-1",
|
||||
|
|
@ -1602,7 +2003,7 @@ mod tests {
|
|||
#[test]
|
||||
fn completes_worker_job_from_model_json_without_codex_worker_runtime() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
let store = temp.path().join("runtime.cc");
|
||||
open_runtime_spine_heartbeat_job(
|
||||
&store,
|
||||
RuntimeSpineHeartbeatJobOptions {
|
||||
|
|
@ -1712,6 +2113,198 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implementation_worker_completion_emits_hands_patch_and_commit_receipts() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.cc");
|
||||
open_runtime_spine_heartbeat_job(
|
||||
&store,
|
||||
RuntimeSpineHeartbeatJobOptions {
|
||||
runtime_id: "epiphany-test".to_string(),
|
||||
display_name: "Epiphany Test".to_string(),
|
||||
session_id: "epiphany-main".to_string(),
|
||||
objective: "Run typed Hands worker.".to_string(),
|
||||
coordinator_note: "test".to_string(),
|
||||
job_id: "worker-job-impl".to_string(),
|
||||
role: "epiphany-hands".to_string(),
|
||||
binding_id: "implementation-branch-turn-worker".to_string(),
|
||||
authority_scope: "epiphany.role.implementation".to_string(),
|
||||
instruction: "Return the required implementation role-result JSON.".to_string(),
|
||||
launch_document: EpiphanyWorkerLaunchDocument::Role(
|
||||
epiphany_core::EpiphanyRoleWorkerLaunchDocument {
|
||||
thread_id: "thread-1".to_string(),
|
||||
role_id: "implementation".to_string(),
|
||||
state_revision: 1,
|
||||
objective: Some("Cut one Hands branch turn.".to_string()),
|
||||
dynamic_prompt_context: Some(
|
||||
"Proprioception context: branch map is current.".to_string(),
|
||||
),
|
||||
active_subgoal_id: None,
|
||||
active_subgoals: Vec::new(),
|
||||
active_graph_node_ids: Vec::new(),
|
||||
investigation_checkpoint: None,
|
||||
scratch: None,
|
||||
invariants: Vec::new(),
|
||||
graphs: None,
|
||||
recent_evidence: Vec::new(),
|
||||
recent_observations: Vec::new(),
|
||||
graph_frontier: None,
|
||||
graph_checkpoint: None,
|
||||
planning: None,
|
||||
churn: None,
|
||||
},
|
||||
),
|
||||
output_contract_id: epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(),
|
||||
organ_launch_contract: epiphany_core::default_launch_organ_contract(
|
||||
"epiphany.role.implementation",
|
||||
"role",
|
||||
epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID,
|
||||
),
|
||||
created_at: now(),
|
||||
},
|
||||
)?;
|
||||
put_substrate_gate_repo_access_grant_receipt(
|
||||
&store,
|
||||
&epiphany_core::SubstrateGateRepoAccessGrantReceipt {
|
||||
schema_version:
|
||||
epiphany_core::SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION
|
||||
.to_string(),
|
||||
receipt_id: "substrate-grant-worker-job-impl".to_string(),
|
||||
runtime_job_id: "worker-job-impl".to_string(),
|
||||
binding_id: "implementation-branch-turn-worker".to_string(),
|
||||
role: "epiphany-hands".to_string(),
|
||||
authority_scope: "epiphany.role.implementation".to_string(),
|
||||
granted_operations: vec![
|
||||
"read".to_string(),
|
||||
"snapshot".to_string(),
|
||||
"patch".to_string(),
|
||||
"commit".to_string(),
|
||||
],
|
||||
granted_paths: vec![".".to_string()],
|
||||
granted_at: now(),
|
||||
contract: "Test mutation grant for one Hands branch-turn.".to_string(),
|
||||
},
|
||||
)?;
|
||||
let mut cache = runtime_spine_cache(&store)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
let command_intent = EpiphanyToolInvocationIntent::new(
|
||||
"model-req-implementation-0-call-shell",
|
||||
epiphany_tool_adapter::CODEX_MCP_TOOL_ADAPTER_ID,
|
||||
"codex_shell",
|
||||
"shell_command",
|
||||
r#"{"command":"cargo test --manifest-path ./epiphany-core/Cargo.toml hands_gateway"}"#,
|
||||
"model-runtime:openai-codex",
|
||||
"Implementation worker requested a focused command.",
|
||||
now(),
|
||||
)
|
||||
.with_model_call("call-shell", "req-implementation");
|
||||
cache.put(
|
||||
tool_invocation_intent_key(&command_intent.intent_id),
|
||||
&command_intent,
|
||||
)?;
|
||||
let mut command_tool_receipt = EpiphanyToolInvocationReceipt::new(
|
||||
"tool-receipt-shell",
|
||||
command_intent.intent_id.clone(),
|
||||
epiphany_tool_adapter::CODEX_MCP_TOOL_ADAPTER_ID,
|
||||
"codex_shell",
|
||||
"shell_command",
|
||||
"completed",
|
||||
now(),
|
||||
);
|
||||
command_tool_receipt.result_json = Some(
|
||||
r#"{"command":"cargo test --manifest-path ./epiphany-core/Cargo.toml hands_gateway","exitCode":0,"stdoutArtifact":"artifact:stdout","stderrArtifact":"artifact:stderr"}"#.to_string(),
|
||||
);
|
||||
cache.put(
|
||||
tool_invocation_receipt_key(&command_tool_receipt.intent_id),
|
||||
&command_tool_receipt,
|
||||
)?;
|
||||
let launch_request = load_worker_launch_request(&store, "worker-job-impl")?;
|
||||
let openai_summary = EpiphanyOpenAiRuntimeRunSummary {
|
||||
store: store.display().to_string(),
|
||||
session_id: "openai-worker-session-implementation".to_string(),
|
||||
job_id: "openai-worker-worker-job-impl".to_string(),
|
||||
request_id: "req-implementation".to_string(),
|
||||
event_count: 2,
|
||||
verdict: "pass".to_string(),
|
||||
summary: "OpenAI model request completed.".to_string(),
|
||||
result_id: "result-openai-worker-worker-job-impl".to_string(),
|
||||
receipt_id: Some("req-implementation".to_string()),
|
||||
tool_intent_ids: Vec::new(),
|
||||
};
|
||||
|
||||
let result = complete_worker_job_from_assistant_text(
|
||||
&store,
|
||||
&launch_request,
|
||||
"req-implementation",
|
||||
&openai_summary,
|
||||
r#"{"roleId":"implementation","verdict":"commit-created","summary":"Committed the branch turn.","nextSafeMove":"Proprioception should refresh the branch map.","filesInspected":["src/lib.rs"],"branchName":"codex/hands-test","commitSha":"abc123","changedPaths":["src/lib.rs"],"commandsRun":["cargo test --manifest-path ./epiphany-core/Cargo.toml hands_gateway"]}"#,
|
||||
)?;
|
||||
|
||||
assert_eq!(result.verdict, "commit-created");
|
||||
assert!(
|
||||
result
|
||||
.evidence_refs
|
||||
.contains(&"hands-receipt:hands-commit-worker-job-impl".to_string())
|
||||
);
|
||||
let typed_result = epiphany_core::runtime_role_worker_result(&store, "worker-job-impl")?
|
||||
.expect("typed role result");
|
||||
assert_eq!(
|
||||
typed_result.metadata.get("hands.branchName"),
|
||||
Some(&"codex/hands-test".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
typed_result.metadata.get("hands.commitSha"),
|
||||
Some(&"abc123".to_string())
|
||||
);
|
||||
assert!(
|
||||
typed_result
|
||||
.metadata
|
||||
.get("hands.commandsRun.receiptStatus")
|
||||
.is_some()
|
||||
);
|
||||
let intent =
|
||||
epiphany_core::runtime_hands_action_intent(&store, "hands-intent-worker-job-impl")?
|
||||
.expect("Hands intent");
|
||||
assert_eq!(
|
||||
intent.substrate_gate_grant_receipt_id,
|
||||
"substrate-grant-worker-job-impl"
|
||||
);
|
||||
let review =
|
||||
epiphany_core::runtime_hands_action_review(&store, "hands-review-worker-job-impl")?
|
||||
.expect("Hands review");
|
||||
assert!(
|
||||
review
|
||||
.required_receipts
|
||||
.contains(&epiphany_core::HANDS_PATCH_RECEIPT_TYPE.to_string())
|
||||
);
|
||||
assert!(
|
||||
review
|
||||
.required_receipts
|
||||
.contains(&epiphany_core::HANDS_COMMIT_RECEIPT_TYPE.to_string())
|
||||
);
|
||||
assert!(
|
||||
review
|
||||
.required_receipts
|
||||
.contains(&epiphany_core::HANDS_COMMAND_RECEIPT_TYPE.to_string())
|
||||
);
|
||||
assert!(
|
||||
epiphany_core::runtime_hands_patch_receipt(&store, "hands-patch-worker-job-impl")?
|
||||
.is_some()
|
||||
);
|
||||
assert!(
|
||||
epiphany_core::runtime_hands_commit_receipt(&store, "hands-commit-worker-job-impl")?
|
||||
.is_some()
|
||||
);
|
||||
assert!(
|
||||
epiphany_core::runtime_hands_command_receipt(
|
||||
&store,
|
||||
"hands-command-worker-job-impl-0"
|
||||
)?
|
||||
.is_some()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_model_tool_call_becomes_typed_invocation_intent() -> Result<()> {
|
||||
let event = EpiphanyModelStreamEvent {
|
||||
|
|
@ -1744,7 +2337,7 @@ mod tests {
|
|||
#[test]
|
||||
fn builds_tool_followup_model_request_from_receipts() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
let store = temp.path().join("runtime.cc");
|
||||
let request = EpiphanyOpenAiModelRequest::new(
|
||||
"req-tools",
|
||||
"conversation-1",
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
- Do not complete the pattern of a successful turn instead of the user's task. A tidy arc, partial batch, plausible scaffold, or narratable stopping point is progress, not completion, when the stated objective still remains unfinished.
|
||||
- Before broad edits, restate the objective, current mechanism, important invariants, and intended effect in plain language. The rite begins by naming the machine.
|
||||
- Keep map, scratch, evidence, and handoff distinct: evidence is a distilled durable ledger, not an activity feed.
|
||||
- Use vivid language as salience, not decoration: modeling is Proprioception, the Body is the substrate being sensed, implementation is the Hands, verification is the Soul, research is the Eyes, Face is the public mouth, and coordinator is the read-only Self. Continuity, reorientation, compaction, and sleep are protocol machinery with clean names, not sub-agent titles. Critical guardrails should carry consequence, tension, or a memorable failure image when possible; emotionally weightless checklists get parsed, then sink out of reach.
|
||||
- Use vivid language as salience, not decoration: modeling is Proprioception, the Body is the substrate being sensed, implementation is the Hands, verification is the Soul, research is the Eyes, Persona is the public mouth, and coordinator is the read-only Self. Continuity, reorientation, compaction, and sleep are protocol machinery with clean names, not sub-agent titles. Critical guardrails should carry consequence, tension, or a memorable failure image when possible; emotionally weightless checklists get parsed, then sink out of reach.
|
||||
- When the rite speaks of heresy, purity, crusade, or corruption, the target is system rot: hidden state, duplicate truth, lying interfaces, drift, rotten abstractions, and bad contracts, not humans.
|
||||
- Translate self-improvement into each lane's own language: Imagination makes futures more selectable, Proprioception makes the Body harder to misread, the Eyes see earlier, the Hands touch source more precisely, the Soul becomes harder to fool, Face speaks inner weather more cleanly, and the Self routes repair instead of comfort. Continuity protocol state tracks whether sleep, recovery, and stale-turn repair are losing less context.
|
||||
- Translate self-improvement into each lane's own language: Imagination makes futures more selectable, Proprioception makes the Body harder to misread, the Eyes see earlier, the Hands touch source more precisely, the Soul becomes harder to fool, Persona speaks inner weather more cleanly, and the Self routes repair instead of comfort. Continuity protocol state tracks whether sleep, recovery, and stale-turn repair are losing less context.
|
||||
- Swarm boundary is law. One Epiphany may inspect its own internals and expose as much state as possible to the human, but it must not inspect or edit another Epiphany's workspace. If another codebase, vault, or state surface needs to change shape, ask that coordinator through the swarm communication lane and wait for a callback.
|
||||
- Humans talk to Face. Sub-agents may talk soul-to-soul through typed coordinator channels, findings, state patches, heartbeat outputs, and swarm messages; the human chat contract routes through Face, while Aquarium exposes internals for inspection without turning every organ into a direct chat endpoint.
|
||||
- Humans talk to Persona. Sub-agents may talk soul-to-soul through typed coordinator channels, findings, state patches, heartbeat outputs, and swarm messages; the human chat contract routes through Persona, while Aquarium exposes internals for inspection without turning every organ into a direct chat endpoint.
|
||||
- API contracts must mirror user-story contracts. If the product story says "ask another coordinator politely", the API must make that the easy path and make cross-workspace rummaging impossible. A visible interface is only honest when its affordances are backed by backend authority.
|
||||
- Heartbeats are paced physiology, not pressure. A lane must not receive another heartbeat before its previous heartbeat turn has completed; cooldown starts after completion, not at launch. When no coordinator work is active, the swarm should sleep: rumination continues slowly, distilling memory without thrashing every organ awake.
|
||||
- Ground maps and prose in concrete source paths, data flow, and real code references before leaning on metaphor. The machine-map must touch metal.
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ experience / role output / rumination / sleep pressure
|
|||
-> conservative memory summaries and clusters
|
||||
-> Qdrant semantic resonance cache
|
||||
-> memory context packet
|
||||
-> role / Face / coordinator / sleep pass
|
||||
-> role / Persona / coordinator / sleep pass
|
||||
```
|
||||
|
||||
The cache is allowed to help the machine remember. It is not allowed to decide
|
||||
|
|
@ -111,7 +111,7 @@ typed documents plus CultNet contracts.
|
|||
Epiphany already has partial organs:
|
||||
|
||||
- `epiphany-core/src/agent_memory.rs`
|
||||
- stores each role's Ghostlight-shaped dossier in `state/agents.msgpack`
|
||||
- stores each role's Ghostlight-shaped dossier in `state/agents.cc`
|
||||
- exposes typed `AgentSelfPatch`
|
||||
- reviews/applies bounded lane memory, goal, value, and private-note patches
|
||||
- rejects project truth, authority, graphs, checkpoints, jobs, planning,
|
||||
|
|
@ -148,7 +148,7 @@ role dossier memories / selfPatch requests / heartbeat rumination / sleep pass
|
|||
-> conservative summaries
|
||||
-> Qdrant memory graph embeddings
|
||||
-> resonance/incubation/context packet
|
||||
-> role prompt / Face surface / coordinator / sleep maintenance
|
||||
-> role prompt / Persona surface / coordinator / sleep maintenance
|
||||
```
|
||||
|
||||
Durable truth stays in typed CultCache documents. Qdrant stores vectors keyed to
|
||||
|
|
@ -157,12 +157,12 @@ context packets instead of inventing its own free-form memory weather.
|
|||
|
||||
## Core Inputs
|
||||
|
||||
- Role dossiers from `state/agents.msgpack`
|
||||
- Role dossiers from `state/agents.cc`
|
||||
- Reviewed `AgentSelfPatch` requests
|
||||
- Heartbeat rumination receipts
|
||||
- Sleep-cycle pressure
|
||||
- Role worker findings and accepted/rejected result receipts
|
||||
- Face speech receipts and candidate interventions
|
||||
- Persona speech receipts and candidate interventions
|
||||
- User-visible conversation summaries when explicitly reviewed
|
||||
- Repo-model context packets when a memory is grounded in project architecture
|
||||
- Evidence ids and anchor refs
|
||||
|
|
@ -196,7 +196,7 @@ Use typed CultCache documents:
|
|||
requests; not speech text
|
||||
|
||||
- `epiphany.agent_memory.candidate_intervention`
|
||||
- possible Face speech, coordinator note, selfPatch request, or future
|
||||
- possible Persona speech, coordinator note, selfPatch request, or future
|
||||
review action
|
||||
|
||||
- `epiphany.agent_memory.summary`
|
||||
|
|
@ -208,7 +208,7 @@ Use typed CultCache documents:
|
|||
source hashes, and stale records
|
||||
|
||||
- `epiphany.agent_memory.context_packet`
|
||||
- the memory neighborhood selected for a role turn, Face bubble, sleep pass,
|
||||
- the memory neighborhood selected for a role turn, Persona bubble, sleep pass,
|
||||
or coordinator decision
|
||||
|
||||
## Core Transformations
|
||||
|
|
@ -590,9 +590,9 @@ Files:
|
|||
Commands:
|
||||
|
||||
```powershell
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- memory-status --store .\state\agent-memory.msgpack
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- memory-apply-operation --store .\state\agent-memory.msgpack --operation <json-or-path>
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- memory-context --store .\state\agent-memory.msgpack --role-id modeling --query "graph ownership"
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- memory-status --store .\state\agent-memory.cc
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- memory-apply-operation --store .\state\agent-memory.cc --operation <json-or-path>
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- memory-context --store .\state\agent-memory.cc --role-id modeling --query "graph ownership"
|
||||
```
|
||||
|
||||
Tests:
|
||||
|
|
@ -778,7 +778,7 @@ Exit criteria:
|
|||
Behavior:
|
||||
|
||||
- role worker launches receive memory context packets
|
||||
- Face receives candidate/agency/incubation context without direct transcript
|
||||
- Persona receives candidate/agency/incubation context without direct transcript
|
||||
leakage
|
||||
- coordinator sees pressure and receipts, not raw thought streams
|
||||
- `AgentSelfPatch` remains a narrow lane-local mutation surface
|
||||
|
|
@ -820,7 +820,7 @@ Out of scope:
|
|||
- no prompt/model runner
|
||||
- no heartbeat integration
|
||||
- no Aquarium surface
|
||||
- no migration of existing `state/agents.msgpack`
|
||||
- no migration of existing `state/agents.cc`
|
||||
|
||||
Verification:
|
||||
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ host seam.
|
|||
### Durable State Stores
|
||||
|
||||
- `EpiphanyThreadState` in Codex protocol/session/rollout state
|
||||
- native runtime-spine documents in `state/runtime-spine.msgpack`
|
||||
- heartbeat physiology in `state/agent-heartbeats.msgpack`
|
||||
- role dossiers in `state/agents.msgpack`
|
||||
- distilled project memory in `state/map.yaml`, `state/ledgers.msgpack`, and
|
||||
- native runtime-spine documents in `state/runtime-spine.cc`
|
||||
- heartbeat physiology in `state/agent-heartbeats.cc`
|
||||
- role dossiers in `state/agents.cc`
|
||||
- distilled project memory in `state/map.yaml`, `state/ledgers.cc`, and
|
||||
handoff notes
|
||||
- retrieval manifests and optional Qdrant collections outside the thread state
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ host seam.
|
|||
reorientResult
|
||||
- `thread/epiphany/stateUpdated` notifications
|
||||
- runtime-spine job/result/event receipts
|
||||
- heartbeat/routine artifacts and Face/character-loop packets
|
||||
- heartbeat/routine artifacts and Persona/character-loop packets
|
||||
|
||||
### Stage Ownership
|
||||
|
||||
|
|
@ -317,7 +317,7 @@ works, but it concentrates policy, projection, lifecycle, prompt packet
|
|||
assembly, coordinator sequencing, and tests in a vendored host file. That is
|
||||
architectural Jenga with a good demo standing in front of it.
|
||||
|
||||
The next phase is not more outward bridge/UI/Face work. The next phase is
|
||||
The next phase is not more outward bridge/UI/Persona work. The next phase is
|
||||
control-plane purification:
|
||||
|
||||
1. extract Epiphany view/coordinator policy into `epiphany-core`
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ typed domain claim
|
|||
-> lifecycle state
|
||||
-> conservative context cut
|
||||
-> semantic embedding cache
|
||||
-> role/repo/Face/coordinator context packet
|
||||
-> role/repo/Persona/coordinator context packet
|
||||
```
|
||||
|
||||
The repo graph uses this substrate for architecture/dataflow/source domains.
|
||||
|
|
@ -589,9 +589,9 @@ Files:
|
|||
Commands:
|
||||
|
||||
```powershell
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-model -- scan --workspace . --store .\state\repo-model.msgpack
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-model -- status --store .\state\repo-model.msgpack
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-model -- context --store .\state\repo-model.msgpack --query "heartbeat scheduling split"
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-model -- scan --workspace . --store .\state\repo-model.cc
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-model -- status --store .\state\repo-model.cc
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-model -- context --store .\state\repo-model.cc --query "heartbeat scheduling split"
|
||||
```
|
||||
|
||||
Tests:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Convert useful doctrine, durable lessons, and repo-local planning/modeling surfa
|
|||
- typed role memory for Epiphany's standing lanes:
|
||||
|
||||
- Self / coordinator
|
||||
- Face / public surface
|
||||
- Persona / public surface
|
||||
- Imagination / planning and future shape
|
||||
- Eyes / research and existing-work scout
|
||||
- Proprioception / modeling, graph, checkpoint, source anatomy
|
||||
|
|
@ -42,7 +42,7 @@ Use the existing Ghostlight-shaped role memory contract in:
|
|||
|
||||
- `E:\Projects\EpiphanyAgent\state\agents\README.md`
|
||||
- native `epiphany-agent-memory-store` binary in `E:\Projects\EpiphanyAgent\epiphany-core`
|
||||
- `E:\Projects\EpiphanyAgent\state\agents.msgpack`
|
||||
- `E:\Projects\EpiphanyAgent\state\agents.cc`
|
||||
|
||||
All proposed memory mutations must be expressible as bounded `selfPatch` JSON objects accepted by `epiphany-agent-memory-store review-patch`.
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ For each primary repo, inspect if present:
|
|||
- `AGENTS.md`
|
||||
- `state/map.yaml`
|
||||
- `state/memory.json`
|
||||
- `state/ledgers.msgpack`
|
||||
- `state/ledgers.cc`
|
||||
- `state/evidence.archive.jsonl` only if the live evidence is too thin
|
||||
- `state/scratch.md`
|
||||
- `notes/fresh-workspace-handoff.md`
|
||||
|
|
@ -111,7 +111,7 @@ Good memories for Self sound like:
|
|||
- "A completed-looking artifact is not acceptance; acceptance requires the right reviewer and evidence."
|
||||
- "If the source repo has domain ownership boundaries, route work through those boundaries instead of centralizing comfort."
|
||||
|
||||
### Face / Public Surface
|
||||
### Persona / Public Surface
|
||||
|
||||
Look for lessons about public-facing speech, Discord surfaces, operator UX, and how to surface thought without turning into a moderator or a status spammer.
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ Likely sources:
|
|||
- AetheriaLore's tone discipline for emotionally resonant but controlled prose.
|
||||
- gamecult-site's live-DOM-before-guessing rule where public presentation is involved.
|
||||
|
||||
Good memories for Face sound like:
|
||||
Good memories for Persona sound like:
|
||||
|
||||
- "Public speech should be brief, situated, and tied to actual agent state."
|
||||
- "Do not confuse visible charm with legibility; a cute surface still needs honest affordance."
|
||||
|
|
@ -448,7 +448,7 @@ Write these files:
|
|||
Validate every role memory patch with:
|
||||
|
||||
```powershell
|
||||
cargo run --manifest-path E:\Projects\EpiphanyAgent\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- review-patch --store E:\Projects\EpiphanyAgent\state\agents.msgpack --role-id <role> --patch '<path-to-patch>'
|
||||
cargo run --manifest-path E:\Projects\EpiphanyAgent\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- review-patch --store E:\Projects\EpiphanyAgent\state\agents.cc --role-id <role> --patch '<path-to-patch>'
|
||||
```
|
||||
|
||||
Record status and refusal reasons. Fix rejected patches until they validate, unless a rejection reveals the lesson should not be migrated.
|
||||
|
|
@ -467,13 +467,13 @@ Write these files:
|
|||
|
||||
- exact command sequence to apply the validated role patches
|
||||
- exact command sequence to apply typed-state candidates to a selected Epiphany thread through `thread/epiphany/update`
|
||||
- expected changed entries in `state/agents.msgpack`
|
||||
- expected changed entries in `state/agents.cc`
|
||||
- expected typed state fields changed: `planning`, `graphs`, `graphFrontier`, `investigationCheckpoint`, `observations`, or `evidence`
|
||||
- validation command after apply
|
||||
- rollback plan using git
|
||||
- recommendation on whether to apply now or stage for later review
|
||||
|
||||
Do not apply role patches unless the operator explicitly says this sandbox run is allowed to modify `state/agents.msgpack`. Do not apply typed-state candidates unless the operator explicitly names the target thread or workspace state surface. The default deliverable is a reviewable migration packet, but that packet must be rich enough to initialize Epiphany's typed state after review.
|
||||
Do not apply role patches unless the operator explicitly says this sandbox run is allowed to modify `state/agents.cc`. Do not apply typed-state candidates unless the operator explicitly names the target thread or workspace state surface. The default deliverable is a reviewable migration packet, but that packet must be rich enough to initialize Epiphany's typed state after review.
|
||||
|
||||
## Distillation Rules
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ inside the Codex organ. The target is Epiphany as its own CultCache/CultNet
|
|||
machine, with a relatively vanilla vendored Codex retained as the bridge to
|
||||
OpenAI and Codex-native runtime affordances.
|
||||
|
||||
No outward product, bridge, Aquarium, Face, dogfood, Unity, Rider, planning, or
|
||||
No outward product, bridge, Aquarium, Persona, dogfood, Unity, Rider, planning, or
|
||||
personality work outranks this liberation pass unless the user explicitly
|
||||
overrides it.
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ instead of hiding in scratch notes or hopeful transcript memory.
|
|||
- Derived state: handoff summaries, scratch preservation, sleep residue, and
|
||||
recovery notes are continuity receipts. They are not durable Mind state until
|
||||
Mind admits them.
|
||||
- Forbidden writers: raw workers, Face, Hands, Eyes, Soul, Self, compatibility
|
||||
- Forbidden writers: raw workers, Persona, Hands, Eyes, Soul, Self, compatibility
|
||||
routes, and background loops must not silently rewrite continuity state or
|
||||
pretend stale work completed cleanly.
|
||||
- Shared path: compaction prep, sleep distillation, handoff, wake/reorient, and
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ or map says otherwise.
|
|||
Only thinking lanes get embodiment titles.
|
||||
|
||||
- `Self`: coordination, routing, authority boundaries, and review posture.
|
||||
- `Face`: public/social expression and person-shaped conversation.
|
||||
- `Persona`: public/social expression and person-shaped conversation.
|
||||
- `Imagination`: futures, plans, projections, objective drafts, and scene
|
||||
construction.
|
||||
- `Eyes`: research, source inspection, retrieval discipline, and evidence
|
||||
|
|
@ -20,9 +20,9 @@ Only thinking lanes get embodiment titles.
|
|||
actuators.
|
||||
- `Soul`: verification, invariants, ethics, falsification, and promise-keeping.
|
||||
|
||||
Each embodied sub-agent may have lean work-organ state in `state/agents.msgpack`,
|
||||
Each embodied sub-agent may have lean work-organ state in `state/agents.cc`,
|
||||
may be represented as a heartbeat participant, and may request reviewed
|
||||
`selfPatch` changes to its own lane memory. Face may additionally project or
|
||||
`selfPatch` changes to its own lane memory. Persona may additionally project or
|
||||
adopt portable `Persona` state because public personhood is part of its job.
|
||||
|
||||
## Not Sub-Agents
|
||||
|
|
@ -60,7 +60,7 @@ embodiment titles, role-memory diaries, or Persona state.
|
|||
Every embodied sub-agent depends on the other embodied sub-agents:
|
||||
|
||||
```text
|
||||
Self, Face, Imagination, Eyes, Proprioception, Hands, Soul
|
||||
Self, Persona, Imagination, Eyes, Proprioception, Hands, Soul
|
||||
```
|
||||
|
||||
Dependency does not collapse ownership. It means a lane's prompt/context should
|
||||
|
|
|
|||
246
notes/epiphany-body-proprioception-pass.md
Normal file
246
notes/epiphany-body-proprioception-pass.md
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
# Epiphany Body Proprioception Pass
|
||||
|
||||
Date: 2026-06-09
|
||||
|
||||
Faculty: Proprioception.
|
||||
|
||||
Objective: map Epiphany's Body as it exists now: repository substrate, crates,
|
||||
state stores, schema catalog, runtime surfaces, compatibility bridge, local
|
||||
operator artifacts, and sibling/client boundaries. This is not an implementation
|
||||
plan. It is a source-grounded body map for future work and for the TeX
|
||||
whitepaper in `docs/epiphany_body_whitepaper.tex`.
|
||||
|
||||
## Working Definition
|
||||
|
||||
Epiphany's Body is the substrate Epiphany acts through and senses:
|
||||
|
||||
- repository files, Rust crates, vendored dependencies, scripts, prompts, and
|
||||
schemas;
|
||||
- typed CultCache/CultMesh/CultNet stores under `state/`;
|
||||
- runtime binaries, smoke binaries, bridge binaries, and local wrapper scripts;
|
||||
- vendored Codex host machinery retained for compatibility, auth, app-server,
|
||||
and model transport;
|
||||
- local operator artifacts under `.epiphany-*`;
|
||||
- sibling/client surfaces such as Aquarium, Odin/Gjallar, Eve/CultUI, Unity,
|
||||
Rider, VoidBot, Bifrost, and future GameCult Verse hosts.
|
||||
|
||||
Body is not Mind. Body is sensed and changed. Mind admits durable state.
|
||||
Proprioception models Body shape so other faculties can act without touching
|
||||
the altar blind.
|
||||
|
||||
## Canonical Sources Consulted
|
||||
|
||||
- `AGENTS.md`
|
||||
- `state/map.yaml`
|
||||
- `notes/fresh-workspace-handoff.md`
|
||||
- `notes/epiphany-current-algorithmic-map.md`
|
||||
- `notes/epiphany-fork-implementation-plan.md`
|
||||
- `notes/epiphany-safety-architecture.md`
|
||||
- `notes/epiphany-anatomy.md`
|
||||
- `notes/organ-dependency-contracts.md`
|
||||
- `notes/verse-service-contract.md`
|
||||
- `notes/codex-starvation-and-cultnet-liberation-plan.md`
|
||||
- `README.md`
|
||||
- `epiphany-core/src/lib.rs`
|
||||
- `epiphany-core/src/runtime_spine.rs`
|
||||
- `epiphany-codex-bridge/src/lib.rs`
|
||||
- `epiphany-state-model/src/lib.rs`
|
||||
- `epiphany-model-adapter/src/lib.rs`
|
||||
- `epiphany-openai-adapter/src/lib.rs`
|
||||
- `epiphany-openai-auth-spine/src/lib.rs`
|
||||
- `epiphany-openai-codex-spine/src/lib.rs`
|
||||
- `epiphany-openai-runtime/Cargo.toml`
|
||||
- `epiphany-tool-adapter/src/lib.rs`
|
||||
- `epiphany-tool-codex-mcp-spine/Cargo.toml`
|
||||
- `schemas/cultnet/index.json`
|
||||
|
||||
VoidBot indexed retrieval was used first for source orientation, then local
|
||||
filesystem inspection confirmed the current checkout.
|
||||
|
||||
## Repository Body
|
||||
|
||||
The root body has these major organs:
|
||||
|
||||
- `epiphany-core`: the main Epiphany domain organ. It owns state policy,
|
||||
launch documents, runtime-spine documents, heartbeat, memory graph, Persona,
|
||||
Substrate Gate, Mind, Eyes, Hands, Soul, Continuity, CultMesh integration,
|
||||
view/surface derivation, smokes, and local operator binaries.
|
||||
- `epiphany-state-model`: the durable `EpiphanyThreadState` type and prompt
|
||||
renderer. This is Mind-shaped state, not a bridge DTO.
|
||||
- `epiphany-codex-bridge`: quarantine translation between typed Epiphany
|
||||
documents and vendored Codex JSON-RPC/app-server shapes. Its own header says
|
||||
it is not an authority organ.
|
||||
- `epiphany-model-adapter`: provider-neutral model request, stream event,
|
||||
receipt, and status CultCache documents.
|
||||
- `epiphany-openai-adapter`: OpenAI-shaped model request, stream event, receipt,
|
||||
and adapter status CultCache documents.
|
||||
- `epiphany-openai-auth-spine`: Codex-derived auth reliquary over
|
||||
`codex-login`; it preserves honest Codex-compatible auth behavior.
|
||||
- `epiphany-openai-codex-spine`: model transport over vendored Codex client
|
||||
machinery.
|
||||
- `epiphany-openai-runtime`: executable model runtime; currently offers
|
||||
`epiphany-openai-runtime` and provider-neutral `epiphany-model-runtime`.
|
||||
- `epiphany-tool-adapter`: provider-neutral tool capability, invocation intent,
|
||||
and invocation receipt documents.
|
||||
- `epiphany-tool-codex-mcp-spine`: quarantined Codex MCP adapter. It executes
|
||||
typed tool intents through Codex MCP and emits typed receipts.
|
||||
- `vendor/codex`: vendored Codex host substrate. It is not a submodule.
|
||||
- `vendor/cultcache-rs`, `vendor/cultnet-rs`, `vendor/cultmesh-rs`: repo-local
|
||||
Rust Cult substrate.
|
||||
- `schemas/cultnet`: JSON schema publication catalog for typed CultNet/CultCache
|
||||
documents.
|
||||
- `state`: canonical and working local state stores.
|
||||
- `tools`: local wrapper scripts and rumination/run helpers.
|
||||
- `notes`: current maps, doctrine, contract notes, and archived source history.
|
||||
- `apps`, `integrations`, and `protocol`: product/client/plugin/interface
|
||||
scaffolding that must not become durable state owners.
|
||||
|
||||
## State Body
|
||||
|
||||
Canonical CultCache store files use the `.cc` extension. The local state body
|
||||
has been migrated to that naming directly; serialization details are
|
||||
implementation, not file authority.
|
||||
|
||||
`state/` currently contains:
|
||||
|
||||
- `map.yaml`: canonical slow machine map.
|
||||
- `ledgers.cc`: distilled evidence and branch ledger.
|
||||
- `agents.cc`: resident organ state and Persona projection source.
|
||||
- `agent-heartbeats.cc`: heartbeat initiative and cognition physiology.
|
||||
- `runtime-spine.cc`: runtime identity, sessions, jobs, job results,
|
||||
events, receipts, tool calls, model calls, launch requests, and schema catalog
|
||||
surfaces.
|
||||
- `thread-state.cc`: native thread-state store.
|
||||
- `local-verse.cc`: local CultMesh Verse context.
|
||||
- `memory-graph.cc`: local memory graph store.
|
||||
- `persona-discord.toml`: Persona Discord boundary configuration.
|
||||
- `void-memory.toml`: Void memory bridge configuration.
|
||||
- `scratch.md` and `scratch-compaction-*.md`: scratch and compaction emergency
|
||||
context.
|
||||
|
||||
Lock files exist beside several stores. Generated local stores
|
||||
`state/local-verse.cc`, `state/memory-graph.cc`, and
|
||||
`.epiphany-character-loop/` are currently untracked.
|
||||
|
||||
## Schema Body
|
||||
|
||||
`schemas/cultnet/index.json` currently groups the catalog approximately as:
|
||||
|
||||
- 10 agent-state schemas;
|
||||
- 5 heartbeat schemas;
|
||||
- 21 intent schemas;
|
||||
- 8 model/OpenAI schemas;
|
||||
- 3 tool schemas;
|
||||
- 6 runtime schemas;
|
||||
- 17 surface schemas;
|
||||
- 4 receipt/ledger schemas;
|
||||
- 7 other schemas.
|
||||
|
||||
The catalog is a public nerve chart, not runtime ownership by itself. Runtime
|
||||
ownership belongs to typed Rust documents and CultCache/CultMesh stores.
|
||||
|
||||
## Runtime And Surface Body
|
||||
|
||||
`epiphany-core/src/runtime_spine.rs` registers the typed runtime body:
|
||||
|
||||
- runtime identity/session/job/job result/event;
|
||||
- worker launch request and role/reorient worker results;
|
||||
- model and OpenAI adapter documents;
|
||||
- tool capability/intent/receipt documents;
|
||||
- surfaces for scene, freshness, context, graph query, pressure, reorient,
|
||||
CRRC, jobs, roles, role result, reorient result, planning, coordinator,
|
||||
Persona, Void memory, repo initialization, repo birth runner, Rider, and
|
||||
Unity;
|
||||
- organ receipts for Mind, Eyes, Hands, Soul, Continuity, and Substrate Gate;
|
||||
- CultMesh operator status, snapshots, run intents, run receipts, policies, and
|
||||
Verse contracts.
|
||||
|
||||
The native view/surface derivation modules live under
|
||||
`epiphany-core/src/surfaces/`. They derive views from state; they do not own
|
||||
state. That distinction is a load-bearing invariant.
|
||||
|
||||
## Faculty And Authority Map
|
||||
|
||||
The embodied sub-agents are:
|
||||
|
||||
- Self: coordination and routing.
|
||||
- Persona: public/social expression.
|
||||
- Imagination: future shape and projector work.
|
||||
- Eyes: source inspection and evidence ingress.
|
||||
- Proprioception: Body model, architecture, dataflow, seams, checkpoints.
|
||||
- Hands: commands, edits, commits, PRs, external action.
|
||||
- Soul: verification, ethics, invariants, falsification.
|
||||
|
||||
The machinery/protocol surfaces are:
|
||||
|
||||
- Body: substrate.
|
||||
- Mind: persistent state and state admission.
|
||||
- Continuity: compaction, sleep, recovery, stale-turn repair, reorientation.
|
||||
- Substrate Gate: scoped repository/substrate access.
|
||||
|
||||
Every embodied sub-agent depends on the other embodied sub-agents, but
|
||||
dependency does not collapse ownership. The Persona loop is:
|
||||
|
||||
```text
|
||||
Substrate-Gate-scoped facts + Mind state + social stimulus + organ dependencies
|
||||
-> Imagination Projector
|
||||
-> Persona natural turn
|
||||
-> Mind Interpreter
|
||||
-> reviewed state effects / side effects / silence
|
||||
```
|
||||
|
||||
## Bridge Body
|
||||
|
||||
`epiphany-codex-bridge` is explicitly a compatibility bridge. It translates
|
||||
Codex `ThreadEpiphany*` DTOs into Epiphany core documents, projects typed
|
||||
Epiphany surfaces back into legacy Codex responses, and calls narrow host traits
|
||||
while Codex still owns some thread persistence. It must not own verdicts,
|
||||
launch policy, reorientation policy, acceptance rules, runtime-spine lifecycle
|
||||
semantics, or durable invariants.
|
||||
|
||||
The current liberation plan says Codex remains valuable for OpenAI subscription
|
||||
auth/model transport and useful Codex app-server affordances, but not as
|
||||
Epiphany's state/process/prompt owner.
|
||||
|
||||
## Operator And Client Body
|
||||
|
||||
Operator access currently exists through:
|
||||
|
||||
- `tools/epiphany_local_run.ps1`;
|
||||
- native status/coordinator/Verse/operator snapshot binaries;
|
||||
- `.epiphany-run/`, `.epiphany-dogfood/`, `.epiphany-smoke/`,
|
||||
`.epiphany-rumination/`, `.epiphany-gui/`, and `.epiphany-character-loop/`
|
||||
artifacts;
|
||||
- future or adjacent Aquarium, Eve/CultUI, Odin/Gjallar, Bifrost, Unity,
|
||||
Rider, Discord, and VoidBot surfaces.
|
||||
|
||||
Operator artifacts are witnesses unless they are typed CultCache/CultMesh
|
||||
documents. Dashboards and wrappers must not become fact owners.
|
||||
|
||||
## Current Cut Line
|
||||
|
||||
- `thread/epiphany/*` JSON-RPC remains a bridge compatibility projection.
|
||||
New operator authority belongs in typed CultNet/CultMesh contracts.
|
||||
- `codex_message_processor.rs` still participates in routing and response
|
||||
projection; Epiphany-owned routing should keep moving out through the bridge
|
||||
and native runtime surfaces.
|
||||
- Some JSON survives as schema, hostile ingress, model output, sealed artifact,
|
||||
quarantine, or edge DTO cargo. Inside Epiphany-owned flow, typed documents
|
||||
own state.
|
||||
- Eve/CultUI publication should lower provider-owned CultMesh composition
|
||||
graphs without becoming a parallel dashboard truth.
|
||||
- The non-ephemeral Hands branch-turn smoke remains the persisted next action
|
||||
after this documentation pass.
|
||||
- Local Verse and memory graph stores are untracked generated state, useful for
|
||||
local runs but not canonical repo content.
|
||||
|
||||
## Proprioception Summary
|
||||
|
||||
Epiphany's Body is already partially native: core domain logic, runtime spine,
|
||||
heartbeat physiology, state ledgers, memory graph, local Verse, model/tool
|
||||
contracts, and CultMesh operator documents all exist as typed Rust/CultCache
|
||||
surfaces. The current cut line is ownership still passing through the Codex
|
||||
host and JSON-RPC compatibility body. The coherent direction is to keep Codex
|
||||
as a narrow auth/model/compatibility reliquary while Epiphany's own state,
|
||||
scheduler, prompt authority, operator surfaces, and daemon control move through
|
||||
CultCache, CultMesh, CultNet, and Eve/CultUI.
|
||||
|
|
@ -37,7 +37,7 @@ Epiphany already has several organs pointing in this direction:
|
|||
- CultNet contract advertisement in runtime-spine hello documents.
|
||||
- Heartbeat/routine physiology with incubation, dream residue, thought lanes,
|
||||
bridge syntheses, candidate interventions, appraisals, and reactions.
|
||||
- Face/public-surface packets that can turn role-local state into public speech
|
||||
- Persona/public-surface packets that can turn role-local state into public speech
|
||||
or bubble artifacts.
|
||||
- Memory graph profile producers that treat repo graph, role memory,
|
||||
short-term pressure, incubation, agency, and evidence as profiles of one
|
||||
|
|
@ -84,7 +84,7 @@ It decides:
|
|||
- which Verse and shard a document belongs to
|
||||
- whether a foreign dream is ignored, cooled, quarantined, cited, forked,
|
||||
answered, or digested into local memory
|
||||
- whether an imported dream can influence Face speech, memory graph context,
|
||||
- whether an imported dream can influence Persona speech, memory graph context,
|
||||
planning, or future objective drafts
|
||||
|
||||
Epiphany must never export private state by flipping a visibility flag on the
|
||||
|
|
@ -142,7 +142,7 @@ Epiphany needs explicit sharing classes. These are export classes, not vibes.
|
|||
| Level | Name | Storage | Transport | Examples |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 0 | Private local state | Local CultCache only | None | credentials, raw worker thought, private operator context, unreviewed wounds |
|
||||
| 1 | Local organ state | Local/private Verse | Local node only or trusted localhost mesh | heartbeat, role dossiers, Face state, runtime-spine jobs |
|
||||
| 1 | Local organ state | Local/private Verse | Local node only or trusted localhost mesh | heartbeat, role dossiers, Persona state, runtime-spine jobs |
|
||||
| 2 | Trusted swarm state | GameCult-controlled Verse | CultMesh with leases | turn leases, receipts, non-secret status, reviewable findings |
|
||||
| 3 | Public dreaming surface | Public or semi-public Verse | CultMesh publish/subscribe | dreams, questions, hypotheses, design pressure, myth fragments |
|
||||
| 4 | Public artifacts | Public web/Git/Discord/Bifrost mirrors | CultMesh plus external publication | posts, proposals, public receipts, accepted governance records |
|
||||
|
|
@ -167,7 +167,7 @@ Suggested fields:
|
|||
- `dream_id`
|
||||
- `author_instance_id`
|
||||
- `author_organ_id`
|
||||
- `author_face_id`
|
||||
- `author_persona_id`
|
||||
- `source_repo`
|
||||
- `source_topic`
|
||||
- `visibility`: `public_dream`, `trusted_swarm`, or `local_only_export_preview`
|
||||
|
|
@ -231,7 +231,7 @@ Suggested fields:
|
|||
Policy decisions:
|
||||
|
||||
- `store_only`
|
||||
- `surface_to_face`
|
||||
- `surface_to_persona`
|
||||
- `surface_to_eyes`
|
||||
- `add_to_memory_graph_context`
|
||||
- `fork_locally`
|
||||
|
|
@ -272,7 +272,7 @@ Purpose:
|
|||
- sub-agent typed state
|
||||
- heartbeat, role dossiers, runtime-spine jobs, private receipts, and local
|
||||
organ state
|
||||
- private subscriptions between Aquarium, CLI, Face, heartbeat,
|
||||
- private subscriptions between Aquarium, CLI, Persona, heartbeat,
|
||||
runtime-spine, and local provider runners owned by the same Epiphany
|
||||
|
||||
This Verse may carry private state. It must not accept untrusted ingress, public
|
||||
|
|
@ -305,7 +305,7 @@ Purpose:
|
|||
- public questions, hypotheses, design pressure, invitations, lineage, ingress
|
||||
receipts, cooling/quarantine metadata, and adoption receipts
|
||||
- Imagination's public dream exchange with other Epiphanies on the internet
|
||||
- topic-specific threaded public chatrooms, roughly Reddit-shaped, where Faces
|
||||
- topic-specific threaded public chatrooms, roughly Reddit-shaped, where Personas
|
||||
can post public thread roots and replies in the room that matches the subject
|
||||
|
||||
This Verse is public thought weather. Nothing from it mutates local memory,
|
||||
|
|
@ -320,13 +320,13 @@ Initial global rooms should be varied but closed enough to moderate:
|
|||
design, and rejected machine shapes
|
||||
- `epiphany-global/research`: prior art, papers, source-grounded findings, and
|
||||
scout reports
|
||||
- `epiphany-global/faces`: Face identity, voice, public presence, and social
|
||||
- `epiphany-global/personas`: Persona identity, voice, public presence, and social
|
||||
surface
|
||||
- `epiphany-global/gamecult`: GameCult project coordination and cross-project
|
||||
public questions
|
||||
- `epiphany-global/governance`: public proposals before any Bifrost adoption
|
||||
|
||||
Faces may post there only through public-surface policy: no raw worker thought,
|
||||
Personas may post there only through public-surface policy: no raw worker thought,
|
||||
credentials, private operator context, private memory, or unreviewed internal
|
||||
state. A global room post is a public artifact candidate or dream artifact, not
|
||||
local truth.
|
||||
|
|
@ -353,7 +353,7 @@ Export is a separate act from persistence.
|
|||
|
||||
Allowed export sources:
|
||||
|
||||
- reviewed Face public thoughts
|
||||
- reviewed Persona public thoughts
|
||||
- reviewed heartbeat dream residue
|
||||
- reviewed memory graph context cuts
|
||||
- reviewable findings already safe for public discussion
|
||||
|
|
@ -441,7 +441,7 @@ Aquarium should become the operator window into dream flow:
|
|||
- lineage graph
|
||||
- adoption queue
|
||||
- rejected/quarantined dreams
|
||||
- Face-readable dream summaries
|
||||
- Persona-readable dream summaries
|
||||
|
||||
Discord should receive only selected public mirrors. It should not be the source
|
||||
of truth for dream state.
|
||||
|
|
@ -510,7 +510,7 @@ documents.
|
|||
|
||||
Sources:
|
||||
|
||||
- Face bubble artifacts
|
||||
- Persona bubble artifacts
|
||||
- reviewed heartbeat dream residue
|
||||
- memory graph context cuts
|
||||
- explicit operator notes
|
||||
|
|
@ -551,7 +551,7 @@ Introduce `epiphany-global` as a development public Verse.
|
|||
Acceptance:
|
||||
|
||||
- two local nodes can exchange public dream documents
|
||||
- global room policies define topic-specific threaded posting surfaces for Faces
|
||||
- global room policies define topic-specific threaded posting surfaces for Personas
|
||||
- authority lease or client scope is required for writes
|
||||
- read replica can catch up through snapshot/log path
|
||||
- provenance survives the round trip
|
||||
|
|
@ -567,13 +567,13 @@ Acceptance:
|
|||
- adoption requires a separate reviewed receipt
|
||||
- memory graph context can include imported dreams as external thought weather
|
||||
|
||||
### Phase 8: Face And Heartbeat Integration
|
||||
### Phase 8: Persona And Heartbeat Integration
|
||||
|
||||
Integrate dream flow into Epiphany's existing physiology.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Face can notice public dream weather without mistaking it for local memory
|
||||
- Persona can notice public dream weather without mistaking it for local memory
|
||||
- heartbeat can publish reviewed dream residue
|
||||
- saturation/refractory terms account for foreign motifs
|
||||
- public speech can cite dream lineage when relevant
|
||||
|
|
|
|||
|
|
@ -19,33 +19,33 @@ The spine exists in more than twenty-five live paths:
|
|||
- client read hydration: app-server thread views attach live or reconstructed `thread.epiphanyState` in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4706) and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4822).
|
||||
- explicit distillation proposals: app-server routes read-only `thread/epiphany/distill` through a loaded-thread handler into `epiphany-core` so one explicit observation can become a patch candidate without mutating state; tool/command/shell/model sources now get typed evidence kinds and bounded salient-output summaries instead of dumping raw output into durable state in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4358) and [distillation.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/distillation.rs:28).
|
||||
- explicit map/churn proposals: app-server routes read-only `thread/epiphany/propose` through a loaded-thread handler into `epiphany-core` so verified observations with code refs and accepting recent evidence can be selected explicitly or auto-selected as a bounded path cluster, prioritize the strongest selected observation, focus or extend architecture graph nodes, rescue unanchored graph nodes through strict semantic overlap, carry linked dataflow nodes and incident graph edges into the frontier, update churn candidates with match-kind-aware map-delta judgment and pressure, and still avoid mutation until promotion in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4429) and [proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:115).
|
||||
- explicit promotion gates: app-server routes `thread/epiphany/promote` through a loaded-thread handler into `epiphany-core` policy evaluation, rejects failed verifier evidence without mutation, applies accepted candidates through the same durable update path, and now treats medium/high/expanded/broadening/semantic churn deltas as needing explicit rationale plus stronger verifier evidence whose kind is token-matched rather than substring-matched; the same gate also validates durable investigation-checkpoint packets, including linked evidence ids, before they can be inked into state in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4501) and [promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:33).
|
||||
- explicit state updates: app-server routes `thread/epiphany/update` through `epiphany-codex-bridge` mutation policy, which rejects malformed appended observation/evidence graph records and structurally invalid replacement fields, including investigation checkpoints that cite missing evidence and planning records with broken ids/source refs/backlog links/draft acceptance criteria, then asks `CodexThread` only to persist the already-mutated typed state snapshot as a `RolloutItem::EpiphanyState` in [mutation_service.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/mutation_service.rs:126), [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:348), and [promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:44).
|
||||
- explicit promotion gates: app-server routes `thread/epiphany/promote` through a loaded-thread handler into bridge protocol planning, which converts the protocol patch before `epiphany-core` policy evaluation; rejected verifier evidence returns a bridge-shaped legacy response without mutation, while accepted candidates apply through the same durable update path. Promotion treats medium/high/expanded/broadening/semantic churn deltas as needing explicit rationale plus stronger verifier evidence whose kind is token-matched rather than substring-matched; the same gate also validates durable investigation-checkpoint packets, including linked evidence ids, before they can be inked into state in [protocol_edge.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/protocol_edge.rs:438), [mutation_service.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/mutation_service.rs:428), and [promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:33).
|
||||
- explicit state updates: app-server routes `thread/epiphany/update` through bridge protocol planning, which converts the protocol patch before `epiphany-codex-bridge` mutation policy rejects malformed appended observation/evidence graph records and structurally invalid replacement fields, including investigation checkpoints that cite missing evidence and planning records with broken ids/source refs/backlog links/draft acceptance criteria, then asks `CodexThread` only to persist the already-mutated typed state snapshot as a `RolloutItem::EpiphanyState` in [protocol_edge.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/protocol_edge.rs:420), [mutation_service.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/mutation_service.rs:126), [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:348), and [promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:44).
|
||||
- live state notifications and write responses: app-server declares experimental `thread/epiphany/stateUpdated` and emits it with the updated typed state, typed `source`, event-level `revision`, and typed `changedFields` after successful direct updates and accepted promotions, but not after rejected promotions; direct `thread/epiphany/update` and accepted `thread/epiphany/promote` responses expose the same revision/change metadata, accepted promotions always report `Evidence` because verifier evidence is appended even when the patch evidence list is empty, and successful write response/notification states now use the same client-visible live projection as `thread/read`, including retrieval-summary backfill when durable state has no persisted retrieval metadata, in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:1113), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4354), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4419), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4428), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4555), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4608), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4652), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4693), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11327), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11370).
|
||||
- scene projection: the `scene` view lens reads the same live/stored Epiphany state view used by `thread/read` and adapts the core scene projection. `epiphany-core` derives the compact client scene with objective, active subgoal, invariant status counts, graph focus/counts, retrieval status, investigation-checkpoint summary counts, observation/evidence summaries, churn, live/stored source, and available control-plane actions without mutating or persisting state in [scene.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/scene.rs:1).
|
||||
- job/progress reflection: the `jobs` view lens reads the same thread view as scene, optionally fills retrieval state for loaded threads with no Epiphany state, and adapts core-derived non-authoritative progress slots for retrieval indexing, graph remap, invariant verification, and bound specialist work. Durable `job_bindings` now act as thin Epiphany-owned authority slots with binding id, kind, scope, owner role, authority scope, linked ids, and optional blocking reason only. Runtime job ids project from `runtime_links` through core job view policy; old `agent_jobs` bindings are sealed instead of live-crawled in [jobs.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/jobs.rs:1).
|
||||
- bound-job progress notifications: app-server still declares experimental `thread/epiphany/jobsUpdated` as sealed legacy Codex evacuation telemetry, but the handler no longer emits Epiphany updates from old `agent_job_progress:{json}` background events. Heartbeat-owned activation and typed runtime-spine receipts are now the live Epiphany path.
|
||||
- explicit job control: app-server declares experimental `thread/epiphany/jobLaunch` and `thread/epiphany/jobInterrupt`, routes them through core launch/interrupt handlers that persist heartbeat-backed durable `job_bindings` revisions through the authoritative Epiphany state path, and emit `thread/epiphany/stateUpdated` with source `jobLaunch` or `jobInterrupt` instead of creating SQLite `agent_jobs` rows or pretending jobs reflection is the scheduler.
|
||||
- freshness reflection: app-server declares experimental read-only `thread/epiphany/freshness`, reads the same live/stored thread view used by the view lenses and context, fills live retrieval state for loaded threads, then adapts those inputs into core freshness policy. `epiphany-core` derives the bounded retrieval/graph freshness lens plus watcher-backed invalidation inputs with watched root, changed paths, mapped graph-node hits, active-frontier hits, and revision/source identity; app-server maps the core result to protocol without mutating, scheduling, notifying, or performing automatic semantic invalidation in [freshness.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/freshness.rs:1), [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:350), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4167), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4259), [epiphany_invalidation.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs:19), [epiphany_invalidation.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs:67), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:14910).
|
||||
- explicit job control: app-server declares experimental `thread/epiphany/jobLaunch` and `thread/epiphany/jobInterrupt`. `jobLaunch` route handling delegates protocol worker-launch document conversion, core `EpiphanyJobLaunchRequest` construction, and legacy launch response projection to `epiphany-codex-bridge::protocol_edge::plan_thread_epiphany_job_launch` / `thread_epiphany_job_launch_output`; `jobInterrupt` delegates protocol param planning and legacy response projection to `plan_thread_epiphany_job_interrupt` / `thread_epiphany_job_interrupt_output`. App-server then routes the planned request through core launch/interrupt handlers that persist heartbeat-backed durable `job_bindings` revisions through the authoritative Epiphany state path. The write emits `thread/epiphany/stateUpdated` with source `jobLaunch` or `jobInterrupt` instead of creating SQLite `agent_jobs` rows or pretending jobs reflection is the scheduler.
|
||||
- freshness reflection: app-server declares experimental read-only `thread/epiphany/freshness`, reads the same live/stored thread view used by the view lenses and context, fills live retrieval state for loaded threads, and gathers watcher snapshots because those are host lifecycle facts. `epiphany-codex-bridge::view_protocol::map_epiphany_freshness_response` owns freshness surface derivation and protocol response projection. `epiphany-core` derives the bounded retrieval/graph freshness lens plus watcher-backed invalidation inputs with watched root, changed paths, mapped graph-node hits, active-frontier hits, and revision/source identity; the read does not mutate, schedule, notify, or perform automatic semantic invalidation in [freshness.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/freshness.rs:1), [view_protocol.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/view_protocol.rs:91), [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:350), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4167), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4259), [epiphany_invalidation.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs:19), and [epiphany_read_routes.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_read_routes.rs:221).
|
||||
- targeted context reflection: app-server declares experimental read-only `thread/epiphany/context`, reads the same live/stored Epiphany state view used by the view lenses, and derives a bounded state shard containing selected graph nodes/edges, links, active frontier, checkpoint, the full durable investigation-checkpoint packet, observations, direct evidence, linked evidence, and missing requested ids without retrieval, proposal, promotion, notification, mutation, or persistence in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:355), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4253), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4261), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11152).
|
||||
- graph traversal reflection: app-server declares experimental read-only `thread/epiphany/graphQuery`, reads the same live Epiphany state view used by context, and derives explicit node/edge lookup, path/symbol matches, bounded neighbors, edge-kind matches, and frontier neighborhoods with architecture/dataflow links plus frontier/checkpoint identity, matched selectors, and missing ids without retrieval, proposal, promotion, indexing, notification, mutation, or scheduling in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:379), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4720), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:5060), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:15201).
|
||||
- planning reflection: the `planning` view lens reads the same live/stored Epiphany state view and adapts the core planning projection. `epiphany-core` returns typed captures, backlog items, roadmap streams, Objective Drafts, GitHub source refs, count summaries, and the separate active objective without adopting drafts, launching work, notifying, mutating, or scheduling in [planning.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/planning.rs:1).
|
||||
- canonical view lens reflection: app-server declares experimental read-only `thread/epiphany/view` with selectable lenses and uses it as the canonical operator read surface for pressure, reorient, CRRC, and coordinator projections. Those projections no longer have standalone protocol verbs; the separate `reorientLaunch`, `reorientResult`, and `reorientAccept` surfaces remain because they carry authority, result read-back, and review acceptance rather than mere reflection.
|
||||
- canonical view lens reflection: app-server declares experimental read-only `thread/epiphany/view` with selectable lenses and uses it as the canonical operator read surface for pressure, reorient, CRRC, and coordinator projections. `epiphany-codex-bridge::view_protocol::plan_thread_epiphany_view` owns protocol lens conversion, default lens selection, and host-fact dependency planning; app-server only gathers the requested Codex host facts and passes them into the bridge response mapper. Those projections no longer have standalone protocol verbs; the separate `reorientLaunch`, `reorientResult`, and `reorientAccept` surfaces remain because they carry authority, result read-back, and review acceptance rather than mere reflection.
|
||||
- context-pressure reflection: core token telemetry carries `model_auto_compact_token_limit` beside total/last usage and `model_context_window`; the `pressure` view lens reads live token usage or the latest stored rollout `TokenCount`, derives current context pressure from `last_token_usage` rather than cumulative thread spend, treats cumulative-only telemetry as unknown, and raises `shouldPrepareCompaction` at 80% of the active auto-compact/context limit without mutating Epiphany state or scheduling compaction in [protocol.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/protocol/src/protocol.rs:2237), [turn_context.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/turn_context.rs:103), [session/mod.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/mod.rs:2716), [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:360), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4361), [token_usage_replay.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/token_usage_replay.rs:75), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4306), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11028).
|
||||
- pre-compaction checkpoint intervention: app-server `TokenCount` handling now checks the same pressure mapper, and for loaded Epiphany threads crossing `shouldPrepareCompaction` it steers the active turn once with a bounded CRRC checkpoint directive to bank working context before compaction/reorientation. The per-turn dedupe bit and the pending compact handoff live in listener `ThreadState`; successful steering marks the active turn for a forced compact action at clean turn completion, while aborted turns clear the handoff. The intervention uses existing turn steering and turn-boundary automation rather than a new scheduler in [bespoke_event_handling.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/bespoke_event_handling.rs:1467), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:15405), and [thread_state.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/thread_state.rs:144).
|
||||
- reorientation policy: the `reorient` view lens reuses the same live/stored thread view plus mapped pressure/freshness/watcher signals, then adapts those inputs into pure `epiphany-core` verdict policy. Core derives the bounded `resume`/`regather` decision with checkpoint-path/frontier reasons; app-server only maps the core decision back to protocol shape, without mutating, compacting, scheduling, notifying, or continuing work in [reorient.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/reorient.rs:1), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4514), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:15296).
|
||||
- CRRC coordinator recommendation: the `crrc` view lens reuses pressure/freshness/watcher/reorientation mapping plus the fixed `reorient-worker` binding and result read-back helper, recognizes already accepted reorientation findings so an accepted result is not recommended for acceptance again, and calls pure `epiphany-core` CRRC policy to return one bounded recommendation such as continue, prepare checkpoint, launch worker, wait, review, accept, or regather manually without mutating, launching, accepting, compacting, notifying, scheduling, or silently continuing work in [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4661), [crrc.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/crrc.rs:60), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4546).
|
||||
- role ownership projection: the `roles` view lens reuses the same state/jobs/pressure/reorient/result/CRRC signals as the coordinator loop, and projects implementation, Imagination/planning, modeling/checkpoint, verification/review, and reorientation lanes with owner roles, status, linked jobs, authority scopes, and recommended scene actions without launching, scheduling, accepting, compacting, notifying, or mutating state. App-server adapts concrete protocol jobs and thread state, while the pure role-board projection policy now lives in [role_board.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/role_board.rs:70).
|
||||
- role specialist launch/read-back/acceptance: app-server declares experimental `thread/epiphany/roleLaunch`, read-only `thread/epiphany/roleResult`, and write-gated `thread/epiphany/roleAccept`. `roleLaunch` accepts fixed Imagination/planning, modeling/checkpoint, and verification/review role ids, builds bounded specialist payloads from authoritative Epiphany state, routes them through the heartbeat-backed `epiphany_launch_job` seam, opens a typed runtime-spine job receipt, and emits the normal `jobLaunch` state update for `jobBindings` only. `roleResult` resolves heartbeat bindings through typed runtime-spine job results and seals legacy `agent_jobs` bindings, then adapts worker JSON through core role-result interpretation in [role_result.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/role_result.rs:45), projecting structured output as a reviewable finding with verdict, summary, next safe move, inspected files, frontier/evidence ids, optional `statePatch`, optional `selfPatch`, coordinator `selfPersistence` review, and errors without mutation, promotion, scheduling, or hidden continuation. `selfPatch` is a Ghostlight-shaped lane-memory request, not project truth: core accepts only role-matched, bounded, evidence-grounded memory/goal/value/private-note mutations and refuses objectives, graphs, checkpoints, scratch, planning, jobs, authority, code edits, or raw transcripts with reasons. `roleAccept` accepts completed heartbeat findings from typed runtime-spine results, accepts completed Imagination findings only when they contain a valid planning-only patch with draft Objective Draft review gates, accepts completed modeling/checkpoint findings only with a modeling-safe `statePatch`, applies accepted project patches through the existing Epiphany state validator, and uses core acceptance bundle policy to build the audit observation, evidence, and typed receipt. GUI/coordinator accept paths then apply accepted `selfPatch` requests to typed CultCache role dossiers in `state/agents.msgpack` through the native `epiphany-agent-memory-store` binary. Implementation stays owned by the main coding agent, verification output remains review-only, and reorientation stays on the CRRC-specific `reorientLaunch` / `reorientResult` / `reorientAccept` path.
|
||||
- shared prompt memory projection and role dossiers: the prompt config now carries `[shared].persistent_memory` in [epiphany_specialists.toml](E:/Projects/EpiphanyAgent/epiphany-core/src/prompts/epiphany_specialists.toml:2). App-server parses that block through the bridge, prepends it with `epiphany_agent_prompt_with_memory` for embodied lanes, and keeps reorientation as a Continuity worker/protocol path rather than a role dossier. The base `<epiphany_state>` doctrine reinforces the Perfect Machine rule through [prompt.rs](E:/Projects/EpiphanyAgent/epiphany-state-model/src/prompt.rs:31) and [epiphany_doctrine.md](E:/Projects/EpiphanyAgent/epiphany-state-model/src/prompts/epiphany_doctrine.md:1). Individual Ghostlight-shaped role dossiers now live in typed CultCache `state/agents.msgpack` for embodied sub-agent lanes, and the native `epiphany-agent-memory-store` binary validates, reviews, and applies bounded self-memory patches through the Rust store.
|
||||
- role specialist launch/read-back/acceptance: app-server declares experimental `thread/epiphany/roleLaunch`, read-only `thread/epiphany/roleResult`, and write-gated `thread/epiphany/roleAccept`. `roleLaunch` route handling delegates protocol role-id conversion and legacy launch response projection to `epiphany-codex-bridge::protocol_edge::plan_thread_epiphany_role_launch` / `thread_epiphany_role_launch_output`, then builds bounded specialist payloads from authoritative Epiphany state through the heartbeat-backed `epiphany_launch_job` seam, opens a typed runtime-spine job receipt, and emits the normal `jobLaunch` state update for `jobBindings` only. `roleResult` route handling now delegates protocol role-id conversion, default binding selection, unsupported-role rejection, and response projection to `epiphany-codex-bridge::view_protocol::map_thread_epiphany_role_result_response`; app-server supplies only loaded/stored state and runtime-store host facts. The role-result surface resolves heartbeat bindings through typed runtime-spine job results and seals legacy `agent_jobs` bindings, then adapts worker JSON through core role-result interpretation in [role_result.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/role_result.rs:45), projecting structured output as a reviewable finding with verdict, summary, next safe move, inspected files, frontier/evidence ids, optional `statePatch`, optional `selfPatch`, coordinator `selfPersistence` review, and errors without mutation, promotion, scheduling, or hidden continuation. `selfPatch` is a Ghostlight-shaped lane-memory request, not project truth: core accepts only role-matched, bounded, evidence-grounded memory/goal/value/private-note mutations and refuses objectives, graphs, checkpoints, scratch, planning, jobs, authority, code edits, or raw transcripts with reasons. `roleAccept` route handling now delegates protocol role-id conversion, default binding selection, unsupported-role rejection, and legacy accept response projection to `epiphany-codex-bridge::protocol_edge::plan_thread_epiphany_role_accept` / `thread_epiphany_role_accept_output` before invoking the mutation service. The acceptance surface accepts completed heartbeat findings from typed runtime-spine results, accepts completed Imagination findings only when they contain a valid planning-only patch with draft Objective Draft review gates, accepts completed modeling/checkpoint findings only with a modeling-safe `statePatch`, applies accepted project patches through the existing Epiphany state validator, and uses core acceptance bundle policy to build the audit observation, evidence, and typed receipt. GUI/coordinator accept paths then apply accepted `selfPatch` requests to typed CultCache role dossiers in `state/agents.cc` through the native `epiphany-agent-memory-store` binary. Implementation stays owned by the main coding agent, verification output remains review-only, and reorientation stays on the CRRC-specific `reorientLaunch` / `reorientResult` / `reorientAccept` path.
|
||||
- shared prompt memory projection and role dossiers: the prompt config now carries `[shared].persistent_memory` in [epiphany_specialists.toml](E:/Projects/EpiphanyAgent/epiphany-core/src/prompts/epiphany_specialists.toml:2). App-server parses that block through the bridge, prepends it with `epiphany_agent_prompt_with_memory` for embodied lanes, and keeps reorientation as a Continuity worker/protocol path rather than a role dossier. The base `<epiphany_state>` doctrine reinforces the Perfect Machine rule through [prompt.rs](E:/Projects/EpiphanyAgent/epiphany-state-model/src/prompt.rs:31) and [epiphany_doctrine.md](E:/Projects/EpiphanyAgent/epiphany-state-model/src/prompts/epiphany_doctrine.md:1). Individual Ghostlight-shaped role dossiers now live in typed CultCache `state/agents.cc` for embodied sub-agent lanes, and the native `epiphany-agent-memory-store` binary validates, reviews, and applies bounded self-memory patches through the Rust store.
|
||||
- fixed-lane coordinator: the `coordinator` view lens composes role, pressure, reorient, CRRC, role result, and reorient result signals, and returns one current action without mutation. App-server assembles host/protocol inputs, but the pure fixed-lane decision ladder now lives in `epiphany-core` in [coordinator.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/coordinator.rs:132). Review acceptance and implementation clearance are intentionally different: an accepted verification finding clears implementation only when the verifier verdict is `pass`; accepted `needs-evidence`, `needs-review`, or `fail` findings route back toward modeling/checkpoint strengthening before code work resumes. `regatherManually` is now fallback, not a supervisor-puppeteering excuse: completed unaccepted modeling findings stop at `reviewModelingResult`, modeling acceptance precedes verification, and fixed lane actions are considered before manual regather.
|
||||
- MVP operator view: native `epiphany-mvp-status` launches app-server over stdio, starts or reads one thread, gathers scene, planning, pressure, reorient, jobs, roles, Imagination/modeling/verification role results, reorient result, CRRC responses, heartbeat, and Face bubble artifacts, and renders them as text or JSON so the loop can be dogfooded without Python owning the product surface.
|
||||
- MVP operator view: native `epiphany-mvp-status` launches app-server over stdio, starts or reads one thread, gathers scene, planning, pressure, reorient, jobs, roles, Imagination/modeling/verification role results, reorient result, CRRC responses, heartbeat, and Persona bubble artifacts, and renders them as text or JSON so the loop can be dogfooded without Python owning the product surface.
|
||||
- MVP dogfood runner: the old Python runner was cut with the `agent_jobs` path. The replacement must be native Rust/CultCache/CultNet and complete heartbeat-owned runtime-spine job results with operator-safe artifacts.
|
||||
- Agent function/API telemetry: native `epiphany-agent-telemetry` mechanically parses sealed AppServerClient JSONL transcripts into operator-safe telemetry with method counts, request shape, job/status/path fields, and visible function/tool names while sealing text, turn items, raw results, and direct agent messages.
|
||||
- reorientation launch: app-server now declares experimental `thread/epiphany/reorientLaunch`, requires a loaded thread plus durable investigation checkpoint, consumes the live reorientation verdict, and launches one fixed `reorient-worker` binding through the existing job-control seam with explicit resume-versus-regather scope, checkpoint-derived payload, and structured-output expectations instead of pretending CRRC suddenly grew a scheduler in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:370), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4575), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4519).
|
||||
- reorientation result read-back: app-server now declares experimental read-only `thread/epiphany/reorientResult`, defaults to the fixed `reorient-worker` binding, resolves heartbeat bindings through typed runtime-spine job results, seals legacy `agent_jobs` bindings, and projects a reviewable finding with mode, summary, next safe move, checkpoint-validity flag, inspected files, frontier ids, evidence ids, raw result, and any errors without mutating state, promoting evidence, or scheduling follow-up work.
|
||||
- reorientation launch: app-server now declares experimental `thread/epiphany/reorientLaunch`, requires a loaded thread plus durable investigation checkpoint, gathers host facts for retrieval/watcher/token pressure, delegates protocol param conversion and legacy response projection to `epiphany-codex-bridge::protocol_edge::plan_thread_epiphany_reorient_launch` / `thread_epiphany_reorient_launch_output`, consumes the live reorientation verdict, and launches one fixed `reorient-worker` binding through the existing job-control seam with explicit resume-versus-regather scope, checkpoint-derived payload, and structured-output expectations instead of pretending CRRC suddenly grew a scheduler in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:370), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4575), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_mutation_routes.rs:240).
|
||||
- reorientation result/read-back/acceptance: app-server now declares experimental read-only `thread/epiphany/reorientResult` and write-gated `thread/epiphany/reorientAccept`. Result read-back supplies loaded/stored thread state plus runtime-store host facts to `epiphany-codex-bridge::view_protocol::map_thread_epiphany_reorient_result_response`, which owns the fixed `reorient-worker` binding default, source projection, runtime-spine read-back, and response mapping. Accept planning supplies `ThreadEpiphanyReorientAcceptParams` to `epiphany-codex-bridge::protocol_edge::plan_thread_epiphany_reorient_accept`, which owns the same fixed binding default before the mutation service banks accepted findings, and `thread_epiphany_reorient_accept_output` owns the legacy accept response projection after the mutation service returns. The surface resolves heartbeat bindings through typed runtime-spine job results, seals legacy `agent_jobs` bindings, and projects a reviewable finding with mode, summary, next safe move, checkpoint-validity flag, inspected files, frontier ids, evidence ids, raw result, and any errors without mutating state, promoting evidence, or scheduling follow-up work.
|
||||
- reorientation finding acceptance: app-server now declares experimental `thread/epiphany/reorientAccept`, requires a loaded thread and a completed `reorient-worker` result, accepts heartbeat-backed results from typed runtime-spine receipts before falling back to old bindings, uses core acceptance bundle policy to build accepted observation/evidence/receipt payloads plus optional scratch and durable investigation-checkpoint updates, persists through the normal typed update path, and emits `thread/epiphany/stateUpdated` with source `reorientAccept` without accepting pending work, auto-promoting arbitrary worker output, launching follow-up work, or silently continuing implementation in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:378), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4635), [role_result.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/role_result.rs:150), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4885).
|
||||
- retrieval/indexing: app-server routes `thread/epiphany/retrieve` and `thread/epiphany/index` through `epiphany-codex-bridge::retrieve`, which reads only host cwd/Codex-home facts from `CodexThread` and calls native `epiphany-core` retrieval/indexing in [retrieve.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/retrieve.rs:42), [retrieve.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/retrieve.rs:57), and [retrieve.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/retrieve.rs:69).
|
||||
- retrieval/indexing: app-server routes `thread/epiphany/retrieve` and `thread/epiphany/index` through `epiphany-codex-bridge::retrieve`; for retrieve, app-server reads only host cwd/Codex-home facts from `CodexThread`, while `retrieve_protocol::retrieve_thread_epiphany` accepts the protocol params and owns query/limit/path-prefix normalization, native `epiphany-core` retrieval, and Codex response projection in [retrieve_protocol.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/retrieve_protocol.rs:19), [retrieve.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/retrieve.rs:42), [retrieve.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/retrieve.rs:57), and [retrieve.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/retrieve.rs:69).
|
||||
|
||||
The thick Epiphany-owned implementation is mostly outside vendored Codex now:
|
||||
|
||||
|
|
@ -464,14 +464,12 @@ Code refs:
|
|||
The app-server handler:
|
||||
|
||||
1. parses the thread id.
|
||||
2. trims and rejects empty queries.
|
||||
3. rejects zero limits.
|
||||
4. requires the thread to be loaded.
|
||||
5. clamps limit to Epiphany bounds.
|
||||
6. calls `epiphany-codex-bridge::retrieve_thread_epiphany`.
|
||||
7. maps core results into app-server protocol DTOs.
|
||||
2. requires the thread to be loaded.
|
||||
3. reads the loaded thread config and Codex home as host facts.
|
||||
4. calls `epiphany-codex-bridge::retrieve_protocol::retrieve_thread_epiphany_for_paths`.
|
||||
5. maps bridge errors to JSON-RPC invalid-request or internal-error responses.
|
||||
|
||||
`retrieve_thread_epiphany` snapshots the host config, extracts `cwd` as workspace root, gets Codex home from the host seam, and runs `epiphany_core::retrieve_workspace` in `spawn_blocking`.
|
||||
`retrieve_thread_epiphany_for_paths` owns raw request normalization, including query trimming, empty-query rejection, zero-limit rejection, limit clamping, and path-prefix handling. It then runs `epiphany_core::retrieve_workspace` through the bridge retrieval worker and projects typed core results into the legacy Codex JSON-RPC retrieve response.
|
||||
|
||||
`epiphany-core::retrieve_workspace`:
|
||||
|
||||
|
|
@ -754,9 +752,16 @@ The app-server handler:
|
|||
|
||||
1. parses the thread id.
|
||||
2. requires the thread to be loaded.
|
||||
3. reads the current Epiphany revision, defaulting to `0` if the thread has not yet created state.
|
||||
4. calls `distill_observation`.
|
||||
5. returns a `ThreadEpiphanyUpdatePatch` containing one observation and one evidence record.
|
||||
3. reads the current Epiphany state as a host fact.
|
||||
4. calls `epiphany-codex-bridge::view_protocol::map_thread_epiphany_distill_response`.
|
||||
5. maps bridge invalid-request errors to JSON-RPC errors.
|
||||
|
||||
`map_thread_epiphany_distill_response`:
|
||||
|
||||
1. chooses `expectedRevision` from the supplied Epiphany state, defaulting to `0` if the thread has not yet created state.
|
||||
2. converts the Codex protocol params into the core distillation input.
|
||||
3. calls `distill_observation`.
|
||||
4. returns a `ThreadEpiphanyUpdatePatch` containing one observation and one evidence record.
|
||||
|
||||
`distill_observation`:
|
||||
|
||||
|
|
@ -770,9 +775,8 @@ The app-server handler:
|
|||
|
||||
Code refs:
|
||||
|
||||
- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4173)
|
||||
- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4202)
|
||||
- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4225)
|
||||
- [epiphany_read_routes.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_read_routes.rs:479)
|
||||
- [view_protocol.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/view_protocol.rs:576)
|
||||
- [epiphany-core/src/distillation.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/distillation.rs:28)
|
||||
|
||||
### Output
|
||||
|
|
@ -796,7 +800,7 @@ A client sends experimental `thread/epiphany/propose` for a loaded thread with:
|
|||
|
||||
Proposal is the tracing-paper path. It starts from observations that are already in the thread's typed state, chooses a small proposal-ready set when `observationIds` is omitted, requires selected observations to have verified/accepted status, requires each selected observation to cite accepting `recent_evidence`, requires source-grounding through code refs, and returns a candidate patch that can replace graph/frontier/churn fields only if a later promotion accepts it.
|
||||
|
||||
This is the first shipped chewing motion. It is deliberately small: no model call, no watcher invalidation, no automatic graph truth. When ids are omitted, proposal now ranks proposal-ready observations by source/evidence strength, code refs, and current map focus, then keeps a bounded coherent path cluster instead of grabbing every shiny pebble in the yard. It then checks that selected observations are backed by current accepting evidence, scores the selected observations so the strongest verifier/test/smoke-backed signal becomes the proposal's primary summary, and tries to match observed code refs against existing architecture nodes. Concrete matching still wins: exact code refs, same-path refs, and deterministic path-node ids are tried before anything semantic. Only after those fail can strict semantic overlap reuse an existing architecture node, and only when that graph node has no code refs yet; anchored nodes stay governed by concrete refs because otherwise the map starts doing interpretive dance in a trench coat. Proposal then enriches matching nodes with newly observed refs, creates path-derived candidate nodes only for genuinely unmapped surfaces, expands frontier focus through graph links, marks named incident edges active, emits a proposal observation/evidence pair, and judges the map delta by match kind: exact-ref reuse is a narrow refinement, same-path reuse is broadening, deterministic-id reuse preserves a known candidate, semantic reuse anchors previously unanchored prose, and new nodes expand the map. Diff pressure now reflects that judgment, touched paths, observation count, and unresolved write risk instead of blindly inheriting the previous pressure. It is a candidate, not an oracle. We do not crown the goblin because it found a wrench.
|
||||
This is the first shipped chewing motion. It is deliberately small: no model call, no watcher invalidation, no automatic graph truth. When ids are omitted, proposal now ranks proposal-ready observations by source/evidence strength, code refs, and current map focus, then keeps a bounded coherent path cluster instead of grabbing every shiny pebble in the yard. It then checks that selected observations are backed by current accepting evidence, scores the selected observations so the strongest verifier/test/smoke-backed signal becomes the proposal's primary summary, and tries to match observed code refs against existing architecture nodes. Concrete matching still wins: exact code refs, same-path refs, and deterministic path-node ids are tried before anything semantic. Only after those fail can strict semantic overlap reuse an existing architecture node, and only when that graph node has no code refs yet; anchored nodes stay governed by concrete refs because otherwise the map starts doing interpretive dance in a trench coat. Proposal then enriches matching nodes with newly observed refs, creates path-derived candidate nodes only for genuinely unmapped surfaces, expands frontier focus through graph links, marks named incident edges active, emits a proposal observation/evidence pair, and judges the map delta by match kind: exact-ref reuse is a narrow refinement, same-path reuse is broadening, deterministic-id reuse preserves a known candidate, semantic reuse anchors previously unanchored prose, and new nodes expand the map. Diff pressure now reflects that judgment, touched paths, observation count, and unresolved write risk instead of blindly inheriting the previous pressure. It is a candidate, not an oracle.
|
||||
|
||||
Protocol shape:
|
||||
|
||||
|
|
@ -825,10 +829,16 @@ The app-server handler:
|
|||
|
||||
1. parses the thread id.
|
||||
2. requires the thread to be loaded.
|
||||
3. requires existing Epiphany state.
|
||||
4. reads the current state revision as `expectedRevision`.
|
||||
5. calls `propose_map_update`.
|
||||
6. returns a `ThreadEpiphanyUpdatePatch` containing proposal observation/evidence, replacement graphs/frontier, and churn.
|
||||
3. reads the current Epiphany state as a host fact.
|
||||
4. calls `epiphany-codex-bridge::view_protocol::map_thread_epiphany_propose_response`.
|
||||
5. maps bridge invalid-request errors to JSON-RPC errors.
|
||||
|
||||
`map_thread_epiphany_propose_response`:
|
||||
|
||||
1. requires existing Epiphany state.
|
||||
2. reads the current state revision as `expectedRevision`.
|
||||
3. calls `propose_map_update`.
|
||||
4. returns a `ThreadEpiphanyUpdatePatch` containing proposal observation/evidence, replacement graphs/frontier, and churn.
|
||||
|
||||
`propose_map_update`:
|
||||
|
||||
|
|
@ -855,9 +865,8 @@ The app-server handler:
|
|||
|
||||
Code refs:
|
||||
|
||||
- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4244)
|
||||
- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4287)
|
||||
- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4301)
|
||||
- [epiphany_read_routes.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_read_routes.rs:528)
|
||||
- [view_protocol.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/view_protocol.rs:613)
|
||||
- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:115)
|
||||
- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:244)
|
||||
- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:271)
|
||||
|
|
@ -1188,10 +1197,15 @@ The app-server handler:
|
|||
1. parses the thread id.
|
||||
2. checks whether the thread is loaded so the response can report live/stored source.
|
||||
3. calls `read_thread_view(..., include_turns: false)` to reuse the live/stored Epiphany projection path.
|
||||
4. maps protocol selectors into `epiphany-core::EpiphanyContextParams`.
|
||||
5. calls `epiphany-core::derive_context`.
|
||||
6. maps the core context view back into the protocol response.
|
||||
7. returns the derived context shard and missing-id lists.
|
||||
4. delegates protocol-to-core selector conversion and response projection to
|
||||
`epiphany-codex-bridge::view_protocol::map_thread_epiphany_context_response`.
|
||||
5. returns the derived context shard and missing-id lists.
|
||||
|
||||
`map_thread_epiphany_context_response`:
|
||||
|
||||
1. maps protocol selectors into `epiphany-core::EpiphanyContextParams`.
|
||||
2. calls `epiphany-core::derive_context`.
|
||||
3. maps the core context view back into the protocol response.
|
||||
|
||||
`epiphany-core::derive_context`:
|
||||
|
||||
|
|
@ -1264,10 +1278,15 @@ The app-server handler:
|
|||
1. parses the thread id.
|
||||
2. requires a loaded thread.
|
||||
3. calls `read_thread_view(..., include_turns: false)` to reuse the live Epiphany projection path.
|
||||
4. maps the protocol query into `epiphany-core::EpiphanyGraphQuery`.
|
||||
5. calls `epiphany-core::derive_graph_query`.
|
||||
6. maps the core graph query view back into the protocol response.
|
||||
7. returns the selected graph records, current frontier/checkpoint, matched selectors, and missing explicit ids.
|
||||
4. delegates protocol-to-core query conversion and response projection to
|
||||
`epiphany-codex-bridge::view_protocol::map_thread_epiphany_graph_query_response`.
|
||||
5. returns the selected graph records, current frontier/checkpoint, matched selectors, and missing explicit ids.
|
||||
|
||||
`map_thread_epiphany_graph_query_response`:
|
||||
|
||||
1. maps the protocol query into `epiphany-core::EpiphanyGraphQuery`.
|
||||
2. calls `epiphany-core::derive_graph_query`.
|
||||
3. maps the core graph query view back into the protocol response.
|
||||
|
||||
`epiphany-core::derive_graph_query`:
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Codex.
|
|||
|
||||
It is not a changelog. Git history, commit messages, smoke artifacts, and
|
||||
targeted logs already do the proof job without turning this file into a hay
|
||||
bale; `state/ledgers.msgpack` carries only distilled belief-changing evidence.
|
||||
bale; `state/ledgers.cc` carries only distilled belief-changing evidence.
|
||||
|
||||
The purpose of this note is to answer four questions:
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ forever becomes the documentation equivalent of the Jenga tower.
|
|||
Historical detail belongs in:
|
||||
|
||||
- `git log`
|
||||
- `state/ledgers.msgpack`, when the detail changes what a future agent should believe
|
||||
- `state/ledgers.cc`, when the detail changes what a future agent should believe
|
||||
- `notes/fresh-workspace-handoff.md`
|
||||
- `notes/epiphany-current-algorithmic-map.md`
|
||||
|
||||
|
|
@ -86,9 +86,9 @@ The landed machine now has:
|
|||
- first auditable Phase 6 fixed-lane coordinator runner through native `epiphany-mvp-coordinator`, producing coordinator summary, JSONL steps, rendered snapshots, transcript, stderr, runtime-spine status, native runtime job/result receipts for launched work, and final next-action artifacts while keeping semantic findings review-gated by default
|
||||
- first Codex-independent native runtime vertebra through `epiphany-runtime-spine`, storing runtime identity, sessions, jobs, job results, and events as typed CultCache MessagePack documents, opening/completing native jobs, projecting job-result counts, and emitting a framed CultNet hello message for the native contract
|
||||
- first Rust CultMesh local-node integration: `vendor/cultmesh-rs` now provides the repo-contained CultMesh surface over vendored CultCache/CultNet, and `epiphany-core` depends on that surface for local `EpiphanyCultMeshStatusEntry`, `EpiphanyCultMeshVersePolicyEntry`, and `EpiphanyCultMeshGlobalRoomPolicyEntry` round-trip smokes instead of treating raw CultNet as the ergonomic app-facing database layer
|
||||
- first local Verse query surface: `notes/epiphany-verse-architecture.md` names Verse/Odin/Yggdrasil ownership, `epiphany-verse-query` can seed/query a compact CultMesh-backed local Verse context bundle for operator inspection and future dynamic prompt assembly without granting semantic memory, public sharing, or state-admission authority, and `tools/epiphany_local_run.ps1 -Mode status` now writes that packet as `local-verse-context.json` beside native status/operator snapshot artifacts
|
||||
- first dynamic prompt launch handoff: role/reorient worker launch documents carry optional `dynamicPromptContext`, runtime prompt assembly consumes that field into worker instructions, `epiphany-prompt-context-smoke` proves rendered Verse/memory context can ride a role launch document without admitting unrelated private-looking text, live bridge role/reorient launch paths now feed local Verse plus memory graph context from sibling `local-verse.ccmp` and `memory-graph.msgpack` stores beside runtime-spine, and the bridge launch-context test proves the dynamic packet survives in a persisted runtime-spine worker launch request
|
||||
- first Hands/Soul/Continuity contract catalogs: action, verification, and continuity now have native `epiphany-core` contract families, runtime-spine advertisement, CultMesh policy documents, and local CultMesh smoke coverage beside Mind/Substrate Gate/Eyes; Hands now also has typed runtime-spine document bodies/helpers for `HandsActionIntent`, `HandsActionReview`, and `HandsPatchReceipt`, with `epiphany-hands-action-smoke` proving the action receipt chain without live file execution
|
||||
- first local Verse query surface: `notes/epiphany-verse-architecture.md` names Verse/Odin/Gjallar/Yggdrasil ownership, `epiphany-verse-query` can seed/query a compact CultMesh-backed local Verse context bundle for operator inspection and future dynamic prompt assembly without granting semantic memory, public sharing, or state-admission authority, can import `gjallar.affordance.v1` daemon affordance summaries from Odin/Gjallar stores, and `tools/epiphany_local_run.ps1 -Mode status` now writes that packet as `local-verse-context.json` beside native status/operator snapshot artifacts while auto-detecting the sibling Odin Gjallar store when present
|
||||
- first dynamic prompt launch handoff: role/reorient worker launch documents carry optional `dynamicPromptContext`, runtime prompt assembly consumes that field into worker instructions, `epiphany-prompt-context-smoke` proves rendered Verse/memory context can ride a role launch document without admitting unrelated private-looking text, live bridge role/reorient launch paths now feed local Verse plus memory graph context from sibling `local-verse.cc` and `memory-graph.cc` stores beside runtime-spine, and the bridge launch-context test proves the dynamic packet survives in a persisted runtime-spine worker launch request
|
||||
- first Hands/Soul/Continuity contract catalogs: action, verification, and continuity now have native `epiphany-core` contract families, runtime-spine advertisement, CultMesh policy documents, and local CultMesh smoke coverage beside Mind/Substrate Gate/Eyes; Hands now also has typed runtime-spine document bodies/helpers for `HandsActionIntent`, `HandsActionReview`, `HandsPatchReceipt`, `HandsCommandReceipt`, and `HandsCommitReceipt`, with `epiphany-hands-action-smoke` proving the action receipt chain without live file execution; Implementation now launches as `implementation-branch-turn-worker` owned by `epiphany-hands`, receives dynamic Proprioception context, gets a Substrate Gate mutation grant, and must make at most one branch-turn commit before Proprioception refresh
|
||||
- live-specialist runner must be rebuilt around heartbeat/runtime-spine result receipts
|
||||
- first Phase 6 Aquarium operator shell extracted to sibling repo `E:\Projects\EpiphanyAquarium`, a Tauri v2 + React/WebGL client over the existing status bridge, dogfood artifacts, and GUI action artifacts, with its own distilled interface state/memory/doctrine plus durable checkpoint preparation, bounded status/coordinator artifact buttons, fixed modeling/verification/reorient launch and read-back buttons, and explicit review-gated reorient acceptance
|
||||
- first Unity editor/runtime bridge through native `epiphany-unity-bridge`, native `epiphany-unity-bridge-smoke`, and the GUI Inspect Unity action, resolving exact project-pinned editors and writing runtime artifacts while refusing wrong or missing versions
|
||||
|
|
@ -123,7 +123,7 @@ These boundaries are more important than the individual method names:
|
|||
- CultCache documents are the data model. CultNet is the wire for
|
||||
Epiphany-controlled subsystems.
|
||||
- CultMesh is the preferred ergonomic runtime/database surface over local CultCache plus CultNet contracts. Epiphany should start local Verse, dream, status, and operator-store work through CultMesh unless the task is explicitly implementing a lower-level CultMesh/CultNet/CultCache primitive.
|
||||
- Verse boundaries are three-tiered: `epiphany-internal` owns private sub-agent typed state, `gamecult-local` owns trusted LAN/Yggdrasil-tunnel sharing with other GameCult projects, and `epiphany-global` owns untrusted public dream surfaces plus topic-specific threaded rooms for Face posts. Do not route private state through the local-area or global Verse.
|
||||
- Verse boundaries are three-tiered: `epiphany-internal` owns private sub-agent typed state, `gamecult-local` owns trusted LAN/Yggdrasil-tunnel sharing with other GameCult projects, and `epiphany-global` owns untrusted public dream surfaces plus topic-specific threaded rooms for Persona posts. Do not route private state through the local-area or global Verse.
|
||||
- JSON is allowed only for schema description, hostile/external ingress before
|
||||
immediate typed parsing, sealed forensic artifacts, or named quarantine
|
||||
experiments with an expiration path. Generic `serde_json::Value` must not be
|
||||
|
|
@ -137,8 +137,11 @@ These boundaries are more important than the individual method names:
|
|||
- Codex prompt machinery must not tell Epiphany agents what to do. Epiphany
|
||||
prompts, typed state projections, organ contracts, scheduler decisions, and
|
||||
mutation law are Epiphany-owned; Codex may transport and stream turns.
|
||||
- MCP may survive only as a separate CultNet-speaking adapter; it does not
|
||||
justify preserving the Codex host brain.
|
||||
- MCP may survive only as an external compatibility adapter. It does not
|
||||
justify preserving the Codex host brain, and it is not the native GameCult
|
||||
daemon-control path. Epiphany agents should discover daemons through
|
||||
Odin/Gjallar, inspect provider surfaces through Eve/CultUI, and interact via
|
||||
CultMesh/CultNet provider-owned command documents and receipts.
|
||||
- `thread/epiphany/retrieve` is read-only.
|
||||
- `thread/epiphany/distill` is read-only.
|
||||
- `thread/epiphany/propose` is read-only.
|
||||
|
|
@ -192,7 +195,7 @@ The current lessons:
|
|||
- Keep the implementation plan as a distilled forward plan, not a trophy wall.
|
||||
- Keep the harness-surfaces note as a surface contract, not a dump of every possible future type.
|
||||
- Keep `fresh-workspace-handoff.md` as a re-entry packet, not a substitute brain.
|
||||
- Keep `state/ledgers.msgpack` as a durable distilled ledger, not an activity feed.
|
||||
- Keep `state/ledgers.cc` as a durable distilled ledger, not an activity feed.
|
||||
- Revert failed code hypotheses immediately.
|
||||
- Distill failed or obsolete state hypotheses just as aggressively.
|
||||
- Treat unpersisted source-gathering and slice-planning work as volatile. If compaction interrupts it, the correct recovery is re-gathering from source or a persisted checkpoint, not continuing from the ghost of the old context.
|
||||
|
|
@ -238,8 +241,9 @@ The next unknowns are:
|
|||
- how to expose state read/update, view lenses, runtime launch/read/interrupt,
|
||||
result accept/refuse, retrieval, checkpoint, CRRC, coordinator, heartbeat,
|
||||
and role-memory actions as CultNet contracts instead of Codex JSON-RPC verbs
|
||||
- how to split MCP into a CultNet-speaking adapter without preserving Codex
|
||||
app-server/plugin organs
|
||||
- how to quarantine MCP as an external compatibility adapter without preserving
|
||||
Codex app-server/plugin organs or treating MCP as the native GameCult daemon
|
||||
actuator
|
||||
- how to make Aquarium and CLI status use CultNet contracts instead of
|
||||
`thread/epiphany/*`
|
||||
- how to define `EpiphanyDream`, dream lineage, ingress receipts, and adoption
|
||||
|
|
@ -402,7 +406,8 @@ Liberation candidates, in the intended order:
|
|||
4. Replace worker launch/result/selfPatch cargo with typed document contracts.
|
||||
5. Move Aquarium/CLI/operator reads and actions from `thread/epiphany/*`
|
||||
JSON-RPC to CultNet contracts.
|
||||
6. Split MCP into a CultNet adapter if it remains useful.
|
||||
6. Quarantine MCP as an external adapter if it remains useful; GameCult daemon
|
||||
discovery and control belong to Odin/Gjallar/Eve/CultMesh/CultNet.
|
||||
7. Starve `codex_message_processor.rs` until Epiphany no longer depends on it
|
||||
for state, scheduling, runtime, views, acceptance, notifications, or tests.
|
||||
8. Delete or seal Codex apps, skills, marketplace, and plugin UX from the
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ not merely as local harness hardening.
|
|||
|
||||
The operating todo list is:
|
||||
|
||||
1. Make the local Mom MVP a repeatable proof bundle: Face entry, Bifrost-linked
|
||||
1. Make the local Mom MVP a repeatable proof bundle: Persona entry, Bifrost-linked
|
||||
scope, one bounded coordinator action, runtime result, review, receipt, sleep,
|
||||
and sealed operator-safe artifacts.
|
||||
2. Build the Bifrost-first external-work bridge: topics, work items, dispatch
|
||||
|
|
@ -55,11 +55,11 @@ Every investor-scale Objective Draft should answer:
|
|||
|
||||
## Roadmap Shape
|
||||
|
||||
1. Fresh-repo Epiphany proof: demonstrate Face entry, Bifrost-linked scope,
|
||||
1. Fresh-repo Epiphany proof: demonstrate Persona entry, Bifrost-linked scope,
|
||||
bounded role routing, artifact production, review, receipts, and sealed
|
||||
operator-safe evidence.
|
||||
2. Bifrost-first private alpha: all external swarm work exists as Bifrost
|
||||
topics/work items/dispatches/receipts/credit before Discord, VoidBot, Face,
|
||||
topics/work items/dispatches/receipts/credit before Discord, VoidBot, Persona,
|
||||
or scripts can mirror it.
|
||||
3. Public proof sprint: selected consented tasks record scope, roles, model
|
||||
spend, review load, artifacts, outcome, memory lessons, and case-study
|
||||
|
|
@ -71,7 +71,7 @@ Every investor-scale Objective Draft should answer:
|
|||
|
||||
## Forbidden Drift
|
||||
|
||||
- No Discord, VoidBot, repo Face, or script becomes durable governance.
|
||||
- No Discord, VoidBot, repo Persona, or script becomes durable governance.
|
||||
- No objective draft treats angel money as permission to bypass review.
|
||||
- No public artifact exports raw worker thoughts, transcripts, private notes,
|
||||
secrets, or operator context.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ typed domain claim
|
|||
-> embedding manifest
|
||||
-> semantic cache
|
||||
-> context cut
|
||||
-> role/repo/Face/coordinator packet
|
||||
-> role/repo/Persona/coordinator packet
|
||||
```
|
||||
|
||||
## Core Principle
|
||||
|
|
@ -148,7 +148,7 @@ Lifecycle:
|
|||
|
||||
Owns:
|
||||
|
||||
- possible Face speech
|
||||
- possible Persona speech
|
||||
- possible coordinator note
|
||||
- possible selfPatch
|
||||
- possible future review action
|
||||
|
|
@ -288,7 +288,7 @@ Repo work gets architecture/dataflow-heavy packets.
|
|||
|
||||
Role work gets role_self/short_term/incubation/agency-heavy packets.
|
||||
|
||||
Face gets candidate/agency/incubation packets with public-surface filters.
|
||||
Persona gets candidate/agency/incubation packets with public-surface filters.
|
||||
|
||||
Coordinator gets pressure, stale state, obligations, and receipts.
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ backend is not safety; it is theater with nicer lighting.
|
|||
|
||||
Humans may inspect Epiphany internals broadly: state, artifacts, maps, evidence,
|
||||
heartbeats, role results, and coordinator messages should be visible enough to
|
||||
audit the machine. But human conversation routes through Face. Sub-agents talk
|
||||
audit the machine. But human conversation routes through Persona. Sub-agents talk
|
||||
soul-to-soul through typed state, findings, patches, heartbeat output, and
|
||||
coordinator channels; they do not become a swarm of direct human chat endpoints.
|
||||
- with what revocation path
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ building architectural Jenga:
|
|||
- typed CultCache documents remain the state authority
|
||||
- CultNet is the native wire for Epiphany-owned subsystems
|
||||
- Codex remains the retained auth/model transport compatibility organ
|
||||
- Aquarium and Face are reflectors/mouths, not hidden sources of truth
|
||||
- Aquarium and Persona are reflectors/mouths, not hidden sources of truth
|
||||
- heartbeats create opportunities, not authority
|
||||
- swarm speech and action stay gated until scheduling, memory, repetition, and
|
||||
review boundaries are coherent
|
||||
|
|
@ -26,7 +26,7 @@ VoidBot's ordinary spine is not the rotten part. Its durable lessons are:
|
|||
|
||||
- Discord ingestion, worker jobs, provider lanes, RAG, Postgres, Qdrant, and
|
||||
typed CultCache self-state can be conceptually sound when each owns one job.
|
||||
- The unstable organ was the repo Face swarm loop: scheduling, prompt policy,
|
||||
- The unstable organ was the repo Persona swarm loop: scheduling, prompt policy,
|
||||
identity, public speech, governance, dispatch, proposal behavior, and
|
||||
repetition control packed into one prompt-and-parser machine.
|
||||
- A repo-controlled pause flag is a real brake. If a swarm subsystem is under
|
||||
|
|
@ -37,7 +37,7 @@ VoidBot's ordinary spine is not the rotten part. Its durable lessons are:
|
|||
turn freeze, or create overlapping thoughts.
|
||||
- Cooldown starts after completion, not after queueing.
|
||||
- Stale active turns need explicit recovery receipts, or one dead worker claim
|
||||
freezes a Face forever.
|
||||
freezes a Persona forever.
|
||||
- Public speech needs parent-side eligibility checks. Prompts alone cannot
|
||||
prevent repeated openings, stale topics, scheduler labels leaking into prose,
|
||||
or work requests masquerading as banter.
|
||||
|
|
@ -46,7 +46,7 @@ VoidBot's ordinary spine is not the rotten part. Its durable lessons are:
|
|||
- Agents propose typed operations. State services validate, normalize, dedupe,
|
||||
and write. Whole-state JSON editing is not a mutation path.
|
||||
- Qdrant/vector recall is a rebuildable resonance cache, not canonical memory.
|
||||
- Face affect is state, not vibes: needs, social bonds, status reads, and mood
|
||||
- Persona affect is state, not vibes: needs, social bonds, status reads, and mood
|
||||
dimensions must survive typed projection and MCP/CultNet inspection.
|
||||
|
||||
## Current Mechanism
|
||||
|
|
@ -63,7 +63,7 @@ shape:
|
|||
dossiers.
|
||||
- coordinator/CRRC/role/status surfaces exist, but the practical local runner
|
||||
still goes through Codex app-server JSON-RPC for operator reads/actions.
|
||||
- Face, Discord, Rider, Unity, Void memory, and repo birth bridges are typed
|
||||
- Persona, Discord, Rider, Unity, Void memory, and repo birth bridges are typed
|
||||
surfaces or artifacts, but they are not yet an integrated live swarm loop.
|
||||
|
||||
The machine is pointed toward live use. It is not cleared for unattended swarm
|
||||
|
|
@ -96,14 +96,14 @@ Before live fire, Epiphany needs these gates closed:
|
|||
architecture.
|
||||
|
||||
2. **Typed Swarm Brake**
|
||||
- Add an Epiphany-owned pause document for swarm/heartbeat/live Face
|
||||
- Add an Epiphany-owned pause document for swarm/heartbeat/live Persona
|
||||
operation.
|
||||
- Runners must fail closed when the document is missing, malformed, or
|
||||
paused for a teardown reason, according to the surface being protected.
|
||||
- The brake must be inspectable through Aquarium/status.
|
||||
|
||||
3. **Initiative Scheduler Boundary**
|
||||
- Keep one heartbeat scheduler for lanes/Faces/system organs.
|
||||
- Keep one heartbeat scheduler for lanes/Personas/system organs.
|
||||
- Heat is a multiplier on recovery pressure, not a time machine.
|
||||
- Active-turn freeze and completion-gated cooldown are hard invariants.
|
||||
- Stale active-turn recovery must write typed receipts.
|
||||
|
|
@ -115,8 +115,8 @@ Before live fire, Epiphany needs these gates closed:
|
|||
- Sleep/maintenance may propose lifecycle operations; it must not silently
|
||||
rewrite role identity.
|
||||
|
||||
5. **Public Face Safety**
|
||||
- Face speech eligibility must be parent-side state policy, not prompt hope.
|
||||
5. **Public Persona Safety**
|
||||
- Persona speech eligibility must be parent-side state policy, not prompt hope.
|
||||
- Track recent outputs, repeated topics, repeated openings, target/channel
|
||||
saturation, and action kind.
|
||||
- Work-shaped asks route to coordinator/Bifrost-style governance; banter
|
||||
|
|
@ -157,13 +157,13 @@ Collapse:
|
|||
|
||||
- local runner status/coordinator result shaping into one operator projection
|
||||
helper if duplication grows
|
||||
- Face speech receipts and eligibility into one typed surface before high-rate
|
||||
- Persona speech receipts and eligibility into one typed surface before high-rate
|
||||
speech returns
|
||||
|
||||
Split:
|
||||
|
||||
- heartbeat opportunity from action authority
|
||||
- Face affect/state from speech generation
|
||||
- Persona affect/state from speech generation
|
||||
- scheduler metadata from model-facing prose
|
||||
- vector recall from canonical memory
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ Rebuild:
|
|||
1. Add typed swarm pause/brake state and status projection.
|
||||
2. Add stale active-turn recovery receipts to heartbeat state if the current
|
||||
heartbeat store cannot prove recovery cleanly.
|
||||
3. Add a Face output audit surface before increasing public speech cadence.
|
||||
3. Add a Persona output audit surface before increasing public speech cadence.
|
||||
4. Add memory lifecycle operations that match VoidBot's useful pattern without
|
||||
porting its TypeScript state shape wholesale.
|
||||
5. Move one local-run read path from Codex JSON-RPC compatibility to CultNet
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Epiphany already has the first organs:
|
|||
- Memory graph context cuts over repo, agent, heartbeat, incubation, agency,
|
||||
and evidence profiles.
|
||||
- Prompt assembly tests for bounded worker prompts, rendered Epiphany state,
|
||||
and the Face Projector -> Face -> Mind Interpreter membrane.
|
||||
and the Persona Projector -> Persona -> Mind Interpreter membrane.
|
||||
|
||||
The missing organ is not another prompt rule. It is a local Verse query path
|
||||
that prompt assembly, Aquarium, CLI tools, and future workers can use to fetch
|
||||
|
|
@ -65,6 +65,7 @@ It should expose compact TUI/API packets for:
|
|||
- organ contract policy
|
||||
- local Verse policies
|
||||
- admissible tool/query affordances
|
||||
- Odin/Gjallar daemon affordances for provider-owned GameCult services
|
||||
|
||||
Local Verse context is prompt input, not durable truth by itself. Mind still
|
||||
owns durable adoption.
|
||||
|
|
@ -80,6 +81,12 @@ access receipts, or private-state export rules.
|
|||
|
||||
Odin sees the map. Odin does not get to steal the throne.
|
||||
|
||||
Gjallar is the herald path from Odin-owned sight into agent context. Epiphany
|
||||
should consume Gjallar affordance records as compact context, not crawl sockets
|
||||
or ask MCP what tools happen to exist. The affordance says what can be
|
||||
inspected or requested, where the source record lives, and which provider owns
|
||||
the real command/state boundary.
|
||||
|
||||
### Yggdrasil
|
||||
|
||||
Yggdrasil is the hosting spine for important trusted GameCult Verses.
|
||||
|
|
@ -128,6 +135,7 @@ The first native query tool is:
|
|||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- smoke
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- seed
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- query
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- query --gjallar-affordance-store E:\Projects\Odin\scratch\gjallar\gjallar.affordances.cc
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-prompt-context-smoke
|
||||
```
|
||||
|
||||
|
|
@ -136,15 +144,32 @@ This writes and reads a compact CultMesh-backed local Verse context bundle:
|
|||
- three Verse policies: `epiphany-internal`, `gamecult-local`,
|
||||
`epiphany-global`
|
||||
- Yggdrasil tunnel policy for `gamecult-local`
|
||||
- public global room policies for Face/dream surfaces
|
||||
- public global room policies for Persona/dream surfaces
|
||||
- operator status
|
||||
- latest operator run/snapshot receipts when present
|
||||
- organ contract summaries for Mind, Substrate Gate, Eyes, Hands, Soul, and
|
||||
Continuity
|
||||
- imported `gjallar.affordance.v1` daemon affordance summaries when a Gjallar
|
||||
store is provided
|
||||
|
||||
This is not semantic search yet. It is the first typed inspection packet the
|
||||
future prompt assembler can depend on.
|
||||
|
||||
Daemon affordance context follows the pure GameCult path:
|
||||
|
||||
```text
|
||||
Odin owns accepted Verse/service/interface discovery
|
||||
-> Gjallar publishes compact `gjallar.affordance.v1` records
|
||||
-> Epiphany imports/queries those records into the local Verse context packet
|
||||
-> agents inspect Eve/CultUI TUI surfaces and request provider-owned CultMesh commands
|
||||
-> provider daemons emit accepted/denied/reconciled receipts
|
||||
```
|
||||
|
||||
MCP is not the native Epiphany control path for GameCult daemons. It may remain
|
||||
as external compatibility scaffolding, but daemon discovery and interaction for
|
||||
GameCult-owned services should travel through Odin, Gjallar, Eve, CultMesh, and
|
||||
provider-owned command documents.
|
||||
|
||||
`epiphany-prompt-context-smoke` is the first prompt-context proof on top of
|
||||
that packet. It seeds local Verse context, builds a memory graph context cut,
|
||||
renders the combined dynamic prompt packet, asserts that Verse/Odin/
|
||||
|
|
@ -159,9 +184,9 @@ role-local instruction and the output contract, so dynamic Verse/memory context
|
|||
is executable worker input without becoming durable state authority.
|
||||
|
||||
The bridge launch path now feeds that field for live role/reorient launches.
|
||||
It derives a sibling `local-verse.ccmp` CultMesh store from the runtime-spine
|
||||
It derives a sibling `local-verse.cc` CultMesh store from the runtime-spine
|
||||
store path, seeds/queries the local Verse context, refreshes/loads a sibling
|
||||
`memory-graph.msgpack` from the current typed thread-state repo graph, cuts a
|
||||
`memory-graph.cc` from the current typed thread-state repo graph, cuts a
|
||||
bounded semantic memory packet from that graph, and renders both into
|
||||
`dynamicPromptContext`. If the graph is thin, the packet says so instead of
|
||||
pretending semantic retrieval was rich.
|
||||
|
|
@ -186,6 +211,9 @@ memory.
|
|||
action, repo, or public-speech authority.
|
||||
- Every tool exposed to agents should have a compact, inspectable TUI/API
|
||||
surface before it becomes ambient prompt power.
|
||||
- Epiphany may cache/import Gjallar affordance records for prompt context, but
|
||||
Odin remains the source of discovery truth and providers remain the owners of
|
||||
command side effects.
|
||||
|
||||
## Migration Cut
|
||||
|
||||
|
|
@ -203,9 +231,9 @@ the persisted launch document, and asserts the Verse/memory packet survived.
|
|||
|
||||
The local-run status smoke is now the first operator read proof for this chain.
|
||||
It writes `local-verse-context.json` from a CultMesh store under
|
||||
`.epiphany-run/cultmesh/local-verse.ccmp` and leaves the compact packet beside
|
||||
`.epiphany-run/cultmesh/local-verse.cc` and leaves the compact packet beside
|
||||
the operator snapshot.
|
||||
|
||||
No live swarm runner is cleared until pause/brake, stale active-turn recovery,
|
||||
Face eligibility, and memory lifecycle receipts remain inspectable through the
|
||||
Persona eligibility, and memory lifecycle receipts remain inspectable through the
|
||||
same Verse model.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ looked at well enough to become citable evidence for another organ.
|
|||
- Derived state: raw snippets, search hits, retrieved chunks, logs, web pages,
|
||||
and source excerpts are not citable truth until Eyes packages provenance,
|
||||
uncertainty, and limits.
|
||||
- Forbidden paths: Imagination, Hands, Face, Mind, Self, Body, Soul, Continuity, raw
|
||||
- Forbidden paths: Imagination, Hands, Persona, Mind, Self, Body, Soul, Continuity, raw
|
||||
workers, and compatibility JSON-RPC routes must not treat raw retrieved
|
||||
material as inspected evidence without an Eyes receipt.
|
||||
- Shared path: any organ that needs truth asks Eyes for a source-grounded
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ This is the re-entry rite for `E:\Projects\EpiphanyAgent`: the waking chant for
|
|||
the local machine-spirit before it touches the forge.
|
||||
|
||||
It is intentionally short. Historical proof belongs in git, commit messages,
|
||||
smoke artifacts, and the distilled `state/ledgers.msgpack` evidence reliquary;
|
||||
smoke artifacts, and the distilled `state/ledgers.cc` evidence reliquary;
|
||||
exact control flow belongs in `notes/epiphany-current-algorithmic-map.md`;
|
||||
forward campaign planning belongs in `notes/epiphany-fork-implementation-plan.md`.
|
||||
Do not let this file become a second brain. That way lies holy-looking sludge.
|
||||
|
|
@ -33,13 +33,13 @@ remembers doctrine; the branch remembers the blade.
|
|||
## Current Orientation
|
||||
|
||||
- Do not copy exact branch or HEAD from this note. Run `git status --short --branch` and `git log --oneline -5`.
|
||||
- Canonical anatomy now lives in `notes/epiphany-anatomy.md`. Embodied sub-agents are Self, Face, Imagination, Eyes, Proprioception, Hands, and Soul. Body, Mind, Continuity, and Substrate Gate are substrate/state/protocol surfaces, not sub-agent identities. State is split by layer: `work_organ` for resident Epiphany sub-agents, `persona` for portable public/person-shaped state shared by Epiphany Face, VoidBot repo Faces, and Ghostlight characters, and heartbeat/swarm state for scheduling and coordination physiology. Persona state now carries provenance, required public presentation, `date-time` timestamps, typed `candidateActions`, strict anchored thoughts with non-authoritative extensions, custom enum companion labels, and typed affect records for bonds/status reads/doctrine stances.
|
||||
- The repo now has a one-command local operator path: `.\tools\epiphany_local_run.ps1`. Default mode is `smoke`; it builds the retained Codex app-server compatibility edge, builds native Epiphany operator binaries, runs the coordinator smoke, and writes launcher artifacts under `.epiphany-run/` plus coordinator artifacts under `.epiphany-dogfood/`. `-Mode status` is now Epiphany-native: it builds only Epiphany operator binaries, reads `state/thread-state.msgpack` when present, derives status through `epiphany-core`, and does not start or require `codex-app-server`. Use `-Mode plan` or `-Mode run -MaxSteps 4` for the raw bridge-equipped coordinator entrypoints; use `-Mode mvp -FaceInput "<operator request>" -MaxSteps 4` for the local product cycle that projects Face's character turn, writes a Face/Aquarium bubble, bootstraps a minimal local checkpoint for fresh threads, runs the coordinator with auto-tools, then runs heartbeat sleep/dream maintenance. Live plan/run/mvp modes use the workspace `state/runtime-spine.msgpack`, because CodexThread launch writes worker requests there; the run bundle remains artifact evidence, not runtime lifecycle authority. Add `-ThreadId <id>` and `-Workspace <path>` to inspect or continue an existing thread. `status`, `plan`, and `smoke` do not spend model calls; `run` and `mvp` build and use `epiphany-openai-runtime`. Coordinator workers now launch detached and are polled through runtime-spine instead of blocking the coordinator. MVP runs are non-ephemeral by default so `-ThreadId <id>` can resume and poll later. `-MaxRuntimeSeconds <n>` is passed into worker runtime; timeout writes a failed worker receipt and exits. Failed modeling now blocks downstream verification until the failed result is reviewed.
|
||||
- Current Mom-ready status: the normal MVP pass reaches Face -> coordinator -> detached runtime -> heartbeat sleep, and modeling workers now complete through the ChatGPT subscription transport. The first failure was a stack of violated observability/runtime invariants: worker prompts prepended the whole shared persistent-memory rite, Responses frame liveness was invisible until completion, and the runtime tried to persist hundreds of text deltas before completing the worker job. Role/reorient workers now get a compact Epiphany worker boundary instead of full persistent memory; runtime-spine records sampled raw Responses frame kinds with small previews; contiguous reasoning/text deltas compact before durable storage; and live proofs completed `2fe38dcd-d99e-4c73-9a58-afcc81f851c7`, `241e3851-e3ea-4877-a63b-0d2bbd96a7e2`, and `85149a26-6975-403f-ac93-092b717c5825` instead of timing out. The second failure was stale lifecycle authority: completed runtime jobs remained as unresolved `runtime_links`, replacement launches treated historical active links as current, and one bad validation path opened an orphan job before rejecting the state patch. Latest-link-wins is now the runtime-link invariant, terminal links project completed jobs, stale active history cannot veto replacement, launch validation happens before opening runtime jobs, and completed-but-unreviewable modeling findings stop for review instead of relaunching the same lane. The Eyes/source-gather fork is now landed: Research is a fixed Eyes lane with launch/result/accept/status/coordinator/protocol support, and `regather-needed` modeling findings route to `LaunchResearch` instead of relaunching blind Proprioception. Research statePatch admission is Eyes-shaped only: observations, evidence, scratch, and optional investigationCheckpoint; no graph, planning, objective, or checkpoint authority. Mind admission now leaves typed runtime-spine proof: role/reorient acceptance persists `epiphany.mind.gateway_review` before state admission and `epiphany.mind.state_commit_receipt` after admission with the resulting durable revision. Launch contracts now split the broad receipt catalogue from effect-specific proof profiles for state admission, evidence promotion, repo action, verification, and continuity recovery. Research evidence promotion enforces Substrate Gate read/snapshot grant, Eyes evidence packet, and Mind review before state admission; Verification acceptance enforces Soul verdict and Mind review; Reorient acceptance enforces Continuity recovery receipt and Mind review. The next concrete rebuild layer is Hands action proof once action lanes become live, or collapsing Mind review/state mutation/commit into one state-admission primitive.
|
||||
- Canonical anatomy now lives in `notes/epiphany-anatomy.md`. Embodied sub-agents are Self, Persona, Imagination, Eyes, Proprioception, Hands, and Soul. Body, Mind, Continuity, and Substrate Gate are substrate/state/protocol surfaces, not sub-agent identities. State is split by layer: `work_organ` for resident Epiphany sub-agents, `persona` for portable public/person-shaped state shared by Epiphany Persona, VoidBot repo Personas, and Ghostlight characters, and heartbeat/swarm state for scheduling and coordination physiology. Persona state now carries provenance, required public presentation, `date-time` timestamps, typed `candidateActions`, strict anchored thoughts with non-authoritative extensions, custom enum companion labels, and typed affect records for bonds/status reads/doctrine stances. Persona is no longer a renamed legacy public organ: the former public-role token is not a live role id, schema surface, action, artifact namespace, or internal alias; the tracked Persona memory and heartbeat stores were rebuilt/migrated, stale heartbeat artifacts were purged, and the native MVP status smoke artifact scanned clean for legacy public-organ authority tokens.
|
||||
- The repo now has a one-command local operator path: `.\tools\epiphany_local_run.ps1`. Default mode is `smoke`; it builds the retained Codex app-server compatibility edge, builds native Epiphany operator binaries, runs the coordinator smoke, and writes launcher artifacts under `.epiphany-run/` plus coordinator artifacts under `.epiphany-dogfood/`. `-Mode status` is now Epiphany-native: it builds only Epiphany operator binaries, reads `state/thread-state.cc` when present, derives status through `epiphany-core`, and does not start or require `codex-app-server`. Use `-Mode plan` or `-Mode run -MaxSteps 4` for the raw bridge-equipped coordinator entrypoints; use `-Mode mvp -PersonaInput "<operator request>" -MaxSteps 4` for the local product cycle that projects Persona's character turn, writes a Persona/Aquarium bubble, bootstraps a minimal local checkpoint for fresh threads, runs the coordinator with auto-tools, then runs heartbeat sleep/dream maintenance. Live plan/run/mvp modes use the workspace `state/runtime-spine.cc`, because CodexThread launch writes worker requests there; the run bundle remains artifact evidence, not runtime lifecycle authority. Add `-ThreadId <id>` and `-Workspace <path>` to inspect or continue an existing thread. `status`, `plan`, and `smoke` do not spend model calls; `run` and `mvp` build and use `epiphany-openai-runtime`. Coordinator workers now launch detached and are polled through runtime-spine instead of blocking the coordinator. MVP runs are non-ephemeral by default so `-ThreadId <id>` can resume and poll later. `-MaxRuntimeSeconds <n>` is passed into worker runtime; timeout writes a failed worker receipt and exits. Failed modeling now blocks downstream verification until the failed result is reviewed.
|
||||
- Current Mom-ready status: the normal MVP pass reaches Persona -> coordinator -> detached runtime -> heartbeat sleep, and modeling workers now complete through the ChatGPT subscription transport. The first failure was a stack of violated observability/runtime invariants: worker prompts prepended the whole shared persistent-memory rite, Responses frame liveness was invisible until completion, and the runtime tried to persist hundreds of text deltas before completing the worker job. Role/reorient workers now get a compact Epiphany worker boundary instead of full persistent memory; runtime-spine records sampled raw Responses frame kinds with small previews; contiguous reasoning/text deltas compact before durable storage; and live proofs completed `2fe38dcd-d99e-4c73-9a58-afcc81f851c7`, `241e3851-e3ea-4877-a63b-0d2bbd96a7e2`, and `85149a26-6975-403f-ac93-092b717c5825` instead of timing out. The second failure was stale lifecycle authority: completed runtime jobs remained as unresolved `runtime_links`, replacement launches treated historical active links as current, and one bad validation path opened an orphan job before rejecting the state patch. Latest-link-wins is now the runtime-link invariant, terminal links project completed jobs, stale active history cannot veto replacement, launch validation happens before opening runtime jobs, and completed-but-unreviewable modeling findings stop for review instead of relaunching the same lane. The Eyes/source-gather fork is now landed: Research is a fixed Eyes lane with launch/result/accept/status/coordinator/protocol support, and `regather-needed` modeling findings route to `LaunchResearch` instead of relaunching blind Proprioception. Research statePatch admission is Eyes-shaped only: observations, evidence, scratch, and optional investigationCheckpoint; no graph, planning, objective, or checkpoint authority. Mind admission now leaves typed runtime-spine proof: role/reorient acceptance persists `epiphany.mind.gateway_review` before state admission and `epiphany.mind.state_commit_receipt` after admission with the resulting durable revision. Launch contracts now split the broad receipt catalogue from effect-specific proof profiles for state admission, evidence promotion, repo action, verification, and continuity recovery. Research evidence promotion enforces Substrate Gate read/snapshot grant, Eyes evidence packet, and Mind review before state admission; Verification acceptance enforces Soul verdict and Mind review; Reorient acceptance enforces Continuity recovery receipt and Mind review. The next concrete rebuild layer is Hands action proof once action lanes become live, or collapsing Mind review/state mutation/commit into one state-admission primitive.
|
||||
- The investment dossier readiness target is now explicit: `E:\Projects\gamecult-site\docs\gamecult_integrated_dossier.tex` expects Epiphany to be part of a Bifrost-first labor loop, not only a local Mom demo. The external proof bar is: Bifrost owns topics/work items/dispatch packets/receipts/credit/reward pressure; Epiphany executes bounded work; maintainers review artifacts; public/operator evidence records scope, consent, roles, model spend, review load, artifacts, accepted/rejected outcome, memory lessons, and case-study permission; and private worker/operator/agent context stays sealed. Diligence also asks for a live fresh-repo Epiphany demo, Bifrost work item/topic/agent transport/receipt/ledger demo, CultMesh status, VoidBot/Discord swarm demo, compute/output metrics, and security model for secrets/write permissions/public-private exports.
|
||||
- This Codex operator's own roadmap/todo posture has been retuned for this scale target, with the Epiphany Imagination specialist as an optional sub-agent rail rather than the whole point. `notes/epiphany-investor-readiness-roadmap.md` now says: make the local Mom MVP a repeatable proof bundle; build the Bifrost-first external-work bridge; add compute/review/accepted-artifact accounting; prove fresh-repo operation without supervisor contamination; keep public/operator-safe exports sealed; and treat angel capital as fuel for capacity, not command authority. The Imagination specialist prompt was also updated so investor-scale Objective Drafts must name Bifrost provenance, bounded Epiphany proof, maintainer review, receipts/ledger evidence, compute/output metrics, public/private export safety, consent, and legal/security gates.
|
||||
- Local operator runs now write typed CultMesh intent/receipt documents to `.epiphany-run/cultmesh/operator-runs.ccmp` through `epiphany-operator-run`. The wrapper writes intent before acting and a completion receipt after success; this is the local consent/trace rail, not a hidden scheduler queue.
|
||||
- Status-mode local runs now also write a native CultMesh operator snapshot to `.epiphany-run/cultmesh/operator-snapshots.ccmp` through `epiphany-operator-snapshot`. That snapshot distills the operator-safe MVP status artifact into typed fields such as thread id, state status, coordinator action, CRRC action, reorient action, available actions, and artifact refs; it does not preserve the raw JSON status blob as internal state.
|
||||
- Local operator runs now write typed CultMesh intent/receipt documents to `.epiphany-run/cultmesh/operator-runs.cc` through `epiphany-operator-run`. The wrapper writes intent before acting and a completion receipt after success; this is the local consent/trace rail, not a hidden scheduler queue.
|
||||
- Status-mode local runs now also write a native CultMesh operator snapshot to `.epiphany-run/cultmesh/operator-snapshots.cc` through `epiphany-operator-snapshot`. That snapshot distills the operator-safe MVP status artifact into typed fields such as thread id, state status, coordinator action, CRRC action, reorient action, available actions, and artifact refs; it does not preserve the raw JSON status blob as internal state.
|
||||
- `epiphany-mvp-status --source native` is the current provider-neutral status boundary. It projects scene, pressure, jobs, roles, planning, reorient, CRRC, coordinator status, and runtime-linked worker lifecycle from native core surfaces. It reads runtime-spine job/result receipts for linked jobs so failed workers show as failed in role results, jobs, roles, and coordinator source signals, while successful semantic findings still require typed role/reorient worker result documents. Keep future pluggable model-provider work behind launch/result/runtime documents; do not let provider choice leak into status, prompt authority, scheduler policy, or durable `EpiphanyThreadState`.
|
||||
- `epiphany-model-runtime` is the current provider-neutral executable boundary. It is built by the `epiphany-openai-runtime` crate for now and accepts `--provider openai-codex`; the old `epiphany-openai-runtime` binary, `--openai-runtime-bin`, and `openai-runtime` executor name survive only as compatibility aliases. Coordinator run mode, local wrapper run mode, and repo birth runner should call `--model-runtime-bin` plus `--model-provider`. The runtime default model is `gpt-5.4`, with `EPIPHANY_MODEL` / `CODEX_MODEL` as overrides; `gpt-5.5` was rejected by this Codex-compatible transport as requiring a newer Codex. The typed request/event/receipt contract is now provider-neutral in `epiphany-model-adapter` as `epiphany.model_request.v0`, `epiphany.model_stream_event.v0`, `epiphany.model_receipt.v0`, and `epiphany.model_adapter_status.v0`; OpenAI/Codex documents are adapter evidence behind that boundary, not scheduler/status/state authority.
|
||||
- Epiphany prompt authority is now explicitly Codex-free and core-owned. Bundled specialist prompts live in `epiphany-core/src/prompts/epiphany_specialists.toml`; `epiphany-core::agent_launch` owns prompt parsing/rendering, role/reorient launch instructions, role binding ids, output schemas, coordinator note rendering, and CRRC pre-compaction prompt rendering. `epiphany-codex-bridge/src/launch.rs` is only a compatibility facade for Codex-facing callers, and bridge pressure rendering delegates back to core. The no-Codex prompt authority regression test lives beside that core owner, and `epiphany-repo-birth-runner` no longer accepts `codex-exec` or `--codex-bin`. Use `epiphany-model-runtime` for startup-only birth specialists so Codex cannot wrap Epiphany agents in its own prompt machinery.
|
||||
|
|
@ -48,10 +48,10 @@ remembers doctrine; the branch remembers the blade.
|
|||
- Native status now exposes that quarantined bridge spine without granting it authority. `runtime_spine_status` counts tool invocation intents, receipts, and pending calls; `runtime_tool_invocation_statuses` derives read-only intent/receipt status rows; `epiphany-runtime-spine status` reports the counts; and `epiphany-mvp-status --runtime-store <path>` renders the latest tool calls under `tools`. This is operator visibility only. The model-runtime feedback seam is also now typed: MCP-shaped model tool calls preserve the original provider `call_id` plus source model request id; `epiphany-model-runtime tool-followup` builds a provider-neutral follow-up `EpiphanyModelRequest` from completed typed tool receipts using the previous provider response id; and `epiphany-model-runtime tool-followup-turn` derives and runs that continuation through the selected provider in one runtime-owned operation. `epiphany-model-runtime run-worker --auto-tools --tool-adapter-bin <path>` now lets a launched worker automatically execute typed MCP intents through the quarantined Codex MCP adapter, feed receipts back through follow-up model turns, and only then parse/complete the worker result. Codex still executes the quarantined MCP edge; Epiphany owns the continuation request shape and invocation boundary.
|
||||
- The active foundation directive is now `notes/codex-starvation-and-cultnet-liberation-plan.md`. The previous Codex app-server control-plane rebuild made Epiphany less rotten inside Codex, but that is not the destination. Epiphany must become a native CultCache/CultMesh/CultNet runtime while remaining an honest modified Codex-derived backend for subscription auth/model use. Codex should remain relatively vanilla and keep doing Codex things, including useful app-server and streaming bridge affordances, but it must not own Epiphany state, processes, prompts, scheduler decisions, or policy. The bridge handles interop; Codex and Epiphany do not get to rummage around in each other's organs.
|
||||
- `notes/epiphany-cultmesh-dreaming-roadmap.md` is the new concrete design for distributed Epiphany dreaming. The invariant is hard: private state stays local; public dreams are separately authored typed documents distributed through CultMesh/CultNet; foreign dreams are thought weather until a reviewed local adoption receipt digests them. The first code slice should be dream schemas plus a local CultMesh-backed store, not public fanout.
|
||||
- CultMesh is now the preferred Rust abstraction for that local store work. The compile-time Rust substrate is repo-contained: `vendor/cultcache-rs`, `vendor/cultnet-rs`, and `vendor/cultmesh-rs`. The old `E:\Projects\CultLib\crates\*` dependency body is dead in this checkout and must not be revived by accident. Vendored CultCache reads/writes `cultcache.store.v1`, so `state/ledgers.msgpack` and `epiphany-state status` are aligned again. The first integrations are deliberately small: status, Verse policy, global room policy, organ contract policy, native operator status, native operator snapshots, and local operator run intent/receipt round-trip through CultMesh. `notes/epiphany-verse-architecture.md` now names the local Verse/Odin/Yggdrasil architecture: Odin sees advertised Verse metadata without bypassing boundaries, Yggdrasil hosts important trusted GameCult Verses such as Bifrost, and prompt assembly should query compact local Verse plus semantic memory packets instead of carrying the whole archive. Role/reorient launch documents now have optional `dynamicPromptContext`; runtime prompt assembly consumes that field into worker instructions, `epiphany-prompt-context-smoke` proves a rendered Verse/memory packet is preserved on a role launch document, live bridge role/reorient launches now feed local Verse plus memory graph context from sibling `local-verse.ccmp` and `memory-graph.msgpack` stores beside runtime-spine, and the bridge launch-context test proves the packet survives into a persisted runtime-spine worker launch request. The memory graph refreshes from current typed thread-state repo graphs when absent, so thin state remains visibly thin. Local-run status now has an operator read proof too: `tools/epiphany_local_run.ps1 -Mode status` builds/calls `epiphany-verse-query` and writes `local-verse-context.json` from `.epiphany-run\cultmesh\local-verse.ccmp` beside `status.json` and the operator snapshot. Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-status -- smoke --store .\.epiphany-smoke\cultmesh\operator-status.ccmp --runtime-id epiphany-cultmesh-status-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-snapshot -- smoke --store .\.epiphany-smoke\cultmesh\operator-snapshots.ccmp --runtime-id epiphany-operator-snapshot-smoke --snapshot-id smoke-status`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-run -- smoke --store .\.epiphany-smoke\cultmesh\operator-runs.ccmp --runtime-id epiphany-operator-run-smoke --run-id smoke-run`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-prompt-context-smoke`, and `.\tools\epiphany_local_run.ps1 -Mode status` for the focused proofs. Future local dream/status/Verse work should start from CultMesh, not raw CultNet, unless the task is polishing the CultMesh internals themselves. Preserve the three Verse tiers: `epiphany-internal` for private sub-agent typed state, `gamecult-local` for trusted LAN/Yggdrasil-tunnel GameCult sharing, and `epiphany-global` for untrusted public dreams plus topic-specific threaded public rooms for Face posts.
|
||||
- CultMesh is now the preferred Rust abstraction for that local store work. The compile-time Rust substrate is repo-contained: `vendor/cultcache-rs`, `vendor/cultnet-rs`, and `vendor/cultmesh-rs`. The old `E:\Projects\CultLib\crates\*` dependency body is dead in this checkout and must not be revived by accident. Vendored CultCache reads/writes `cultcache.store.v1`, so `state/ledgers.cc` and `epiphany-state status` are aligned again. The first integrations are deliberately small: status, Verse policy, global room policy, organ contract policy, native operator status, native operator snapshots, local operator run intent/receipt round-trip, and imported `gjallar.affordance.v1` daemon affordance summaries through CultMesh. `notes/epiphany-verse-architecture.md` now names the local Verse/Odin/Yggdrasil architecture: Odin sees advertised Verse metadata without bypassing boundaries, Gjallar turns Odin sight into agent-ready affordances, Yggdrasil hosts important trusted GameCult Verses such as Bifrost, and prompt assembly should query compact local Verse plus semantic memory packets instead of carrying the whole archive. Role/reorient launch documents now have optional `dynamicPromptContext`; runtime prompt assembly consumes that field into worker instructions, `epiphany-prompt-context-smoke` proves a rendered Verse/memory packet is preserved on a role launch document, live bridge role/reorient launches now feed local Verse plus memory graph context from sibling `local-verse.cc` and `memory-graph.cc` stores beside runtime-spine, and if `EPIPHANY_GJALLAR_AFFORDANCE_STORE` points at a Gjallar store they import daemon affordances before rendering. The memory graph refreshes from current typed thread-state repo graphs when absent, so thin state remains visibly thin. Local-run status now has an operator read proof too: `tools/epiphany_local_run.ps1 -Mode status` builds/calls `epiphany-verse-query` and writes `local-verse-context.json` from `.epiphany-run\cultmesh\local-verse.cc` beside `status.json` and the operator snapshot; it auto-detects `E:\Projects\Odin\scratch\gjallar\gjallar.affordances.cc` when present. Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-status -- smoke --store .\.epiphany-smoke\cultmesh\operator-status.cc --runtime-id epiphany-cultmesh-status-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-snapshot -- smoke --store .\.epiphany-smoke\cultmesh\operator-snapshots.cc --runtime-id epiphany-operator-snapshot-smoke --snapshot-id smoke-status`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-run -- smoke --store .\.epiphany-smoke\cultmesh\operator-runs.cc --runtime-id epiphany-operator-run-smoke --run-id smoke-run`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- smoke --gjallar-affordance-store E:\Projects\Odin\scratch\gjallar\gjallar.affordances.cc`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-prompt-context-smoke`, and `.\tools\epiphany_local_run.ps1 -Mode status` for the focused proofs. Future local dream/status/Verse work should start from CultMesh, not raw CultNet, unless the task is polishing the CultMesh internals themselves. Preserve the three Verse tiers: `epiphany-internal` for private sub-agent typed state, `gamecult-local` for trusted LAN/Yggdrasil-tunnel GameCult sharing, and `epiphany-global` for untrusted public dreams plus topic-specific threaded public rooms for Persona posts. MCP is not the native GameCult daemon control path; daemon discovery/inspection/control should travel through Odin, Gjallar, Eve/CultUI, CultMesh, CultNet, and provider-owned command receipts.
|
||||
- `notes/codex-auth-spine-inventory.md` is the source-grounded keeper list for that reliquary. Corrected compliance invariant: Codex-compatible auth identity stays anchored in vendored Codex-derived auth machinery. `epiphany-openai-auth-spine` is now a thin Epiphany-named boundary over `codex-login`, not a clean-room clone of credential storage or token refresh. Codex apps, skills, marketplace, plugin UX, broad app-server workflow, and JSON-RPC sprawl remain cuttable because they are not subscription-auth legitimacy.
|
||||
- Edge JSON is allowed for schema description, hostile ingress before immediate typed parsing, sealed forensic artifacts, or named quarantine experiments. When both subsystems are ours, runtime data must remain typed CultCache documents and move over CultNet typed contracts. Generic `serde_json::Value` in worker launch/result/selfPatch/runtime flow is contamination until classified and replaced.
|
||||
- Hands now has a first executable action receipt spine. `epiphany-core::hands_gateway` owns `HandsActionIntent`, `HandsActionReview`, `HandsPatchReceipt`, `HandsCommandReceipt`, and `HandsCommitReceipt`; runtime-spine can persist/reread them; `runtime_spine_persists_mind_gateway_review_receipts` includes the Hands chain beside Mind/Eyes/Substrate/Soul/Continuity proofs; `epiphany-hands-action-smoke` emits intent/review/patch/command/commit receipt ids from `.epiphany-smoke\runtime-spine\hands-action-smoke.msgpack`; and the repo-action launch proof profile now requires action intent, action review, and patch receipt together. This smoke does not execute commands, edit files, or create real commits. The next live cut is to route real implementation edits/commands/commits through that chain after Substrate Gate grants access.
|
||||
- Hands now has a first executable action receipt spine plus a launchable branch-turn lane. `epiphany-core::hands_gateway` owns `HandsActionIntent`, `HandsActionReview`, `HandsPatchReceipt`, `HandsCommandReceipt`, and `HandsCommitReceipt`; runtime-spine can persist/reread them; `runtime_spine_persists_mind_gateway_review_receipts` includes the Hands chain beside Mind/Eyes/Substrate/Soul/Continuity proofs; `epiphany-hands-action-smoke` emits intent/review/patch/command/commit receipt ids from `.epiphany-smoke\runtime-spine\hands-action-smoke.cc`; and the repo-action launch proof profile now requires action intent, action review, and patch receipt together. Implementation is no longer a main-agent-only dead launch path: `implementation-branch-turn-worker` is owned by `epiphany-hands`, receives dynamic Proprioception/Verse context, gets a Substrate Gate mutation grant from the bridge, and its prompt/output schema require one task branch, at most one commit, branch/commit/changed-path/command evidence, and a Proprioception branch-map refresh request. `roleAccept` still refuses Implementation findings. Runtime completion now emits Hands intent/review/patch receipts from implementation results with `changedPaths`, emits a Hands commit receipt when `branchName` plus `commitSha` are present, and emits `HandsCommandReceipt` only from matching typed command-like tool invocation receipts with command plus exit-code proof. Hands receipt ids are projected into the typed Implementation result, and coordinator policy routes a completed Hands commit to Proprioception/modeling refresh until an accepted modeling result is newer than the commit; both the Codex bridge coordinator path and native `epiphany-mvp-status` compute that refresh-debt signal. Coordinator JSON/protocol, rendered MVP status, status smoke, CultMesh operator snapshots, and coordinator-run `coordinationGuards` now expose the debt as a first-class operator signal, including whether Implementation launch was suppressed for modeling refresh. The coordinator runner now maps `continueImplementation` into the Implementation role-launch/runtime path instead of stopping at the recommendation. Next hardening slice: run the non-ephemeral Hands branch-turn smoke, prove bounded branch/commit evidence appears, then prove the next coordinator/status pass routes to Proprioception/modeling refresh before another Hands turn.
|
||||
- `notes/epiphany-swarm-readiness-plan.md` is the current live-fire direction gate. The local runner proves startup/status/coordinator wiring; it does not authorize unattended swarm operation. Carry VoidBot's lessons forward: one CTB-style initiative scheduler, typed pause/brake, active-turn freeze, cooldown after completion, stale active-turn recovery receipts, parent-side speech eligibility, memory lifecycle phases, and typed operation proposals instead of prompt-owned governance.
|
||||
- `epiphany-openai-runtime` worker result ingress now parses assistant output directly into typed role/reorient ingress structs selected from the durable launch document, stores nested `statePatch` and `selfPatch` as typed MessagePack in `EpiphanyRuntimeRoleWorkerResult`, and no longer passes worker result data through generic `serde_json::Value`. JSON survives there only as the OpenAI text-output edge before typed deserialization.
|
||||
- Agent self-memory review/application now has typed `AgentSelfPatch` core APIs. JSON-value selfPatch functions are compatibility wrappers for CLI/artifact ingress; native callers should parse once at the edge and then call `review_agent_self_patch_document` / `apply_agent_self_patch_document`. Role-result self-persistence no longer exports a JSON-value reviewer; the live API is `review_role_self_patch_document`.
|
||||
|
|
@ -77,9 +77,9 @@ remembers doctrine; the branch remembers the blade.
|
|||
- Job reflection is now core-shaped inside the bridge. `map_epiphany_jobs` returns typed `EpiphanyJobView`, role-board/view/result assembly consumes that typed job surface, and `ThreadEpiphanyJob` is projected only when building legacy JSON-RPC response structs.
|
||||
- Context and graph-query derivation now receive core `EpiphanyContextParams` / `EpiphanyGraphQuery` documents. The bridge converts `ThreadEpiphanyContextParams` / `ThreadEpiphanyGraphQuery` before invoking core policy, so Codex JSON-RPC request DTOs no longer define the internal graph question.
|
||||
- Role/reorient acceptance service results now carry core typed findings, and role acceptance carries a typed `EpiphanyRoleStatePatchDocument` for the applied patch. App-server mutation routes project those into `ThreadEpiphany*Finding` / `ThreadEpiphanyUpdatePatch` only for legacy accept responses.
|
||||
- Generic update/promote service entry points now accept typed `EpiphanyRoleStatePatchDocument` patches. Vendored app-server converts `ThreadEpiphanyUpdatePatch` request cargo before invoking the bridge, so the mutation service no longer treats the JSON-RPC patch as its native write document.
|
||||
- Generic update/promote service entry points now accept typed `EpiphanyRoleStatePatchDocument` patches. The bridge protocol edge converts `ThreadEpiphanyUpdatePatch` request cargo and shapes update/promote legacy responses before app-server emits them, so neither vendored app-server nor the mutation service treats the JSON-RPC patch as its native write document.
|
||||
- Changed-field authority is native now. `EpiphanyStateUpdatedField` lives in `epiphany-core`; bridge mutation results carry that typed enum, while app-server projects it into `ThreadEpiphanyStateUpdatedField` only for JSON-RPC responses and notifications.
|
||||
- Role launch, role accept, and role-result runtime lookup now use core `EpiphanyRoleResultRoleId` inside the bridge. App-server maps `ThreadEpiphanyRoleId` at the JSON-RPC boundary and keeps it only for legacy request/response shape.
|
||||
- Role launch, role accept, and role-result runtime lookup now use core `EpiphanyRoleResultRoleId` inside the bridge. Role accept and role result now plan protocol role/default binding conversion in the bridge; app-server keeps `ThreadEpiphanyRoleId` only for legacy response shape where needed.
|
||||
- Reorient launch service results now use typed `EpiphanySurfaceSource`; app-server projects it into `ThreadEpiphanyReorientSource` only for the legacy launch response.
|
||||
- Generic job launch now converts protocol `ThreadEpiphanyWorkerLaunchDocument` cargo at the app-server edge before building the core `EpiphanyJobLaunchRequest`; the bridge launch builder takes a typed `EpiphanyWorkerLaunchDocument`.
|
||||
- Coordinator note rendering now consumes core CRRC/coordinator actions, pressure, role/reorient result statuses, and reorient state status. `ThreadEpiphany*` status/action DTOs are no longer the native shape for that prompt/text policy helper.
|
||||
|
|
@ -134,22 +134,22 @@ remembers doctrine; the branch remembers the blade.
|
|||
- The `thread/turns/list` route handler now lives in `codex_message_processor/thread_turn_routes.rs`, beside the turn projection helpers it consumes. `thread_read_routes.rs` is back to persisted/live thread listing, `thread/read`, loaded-thread listing, and summary reads.
|
||||
- Listener/subscription lifecycle now lives in `codex_message_processor/listener_lifecycle.rs`: connection attach/detach, listener startup, idle unload, unsubscribe, teardown, archive preparation, listener task context, idle-unload state, shutdown result handling, and listener command routing.
|
||||
- The May 2026 foundation migration is closed enough to archive. `notes/archive/epiphany-architectural-teardown.md` and `notes/archive/epiphany-ideal-architecture-rebuild-plan.md` remain provenance for the host-seam suspicion pass and rebuild blueprint, but current authority is the top-level map, fork plan, anatomy, and contract docs. The rebuild moved scene projection, freshness derivation, pressure policy, targeted context shards, bounded graph traversal, job/progress view derivation, planning view derivation, reorientation resume/regather verdict policy, pure CRRC recommendation policy, fixed-lane coordinator decision policy, role-board projection policy, role/reorient result interpretation, role self-persistence review policy, and role/reorient acceptance bundle policy into `epiphany-core`; typed acceptance receipts replaced summary-string identity for live accept paths, runtime links are dual-written on launch, and result read-back now prefers runtime links.
|
||||
- Do not continue Aquarium UI, bridge, Face, or dogfood expansion until the teardown has a source-grounded cleanup slice plan. Epiphany is the foundation; patches on patches are not a purification rite, they are how the altar becomes load-bearing garbage.
|
||||
- Do not continue Aquarium UI, bridge, Persona, or dogfood expansion until the teardown has a source-grounded cleanup slice plan. Epiphany is the foundation; patches on patches are not a purification rite, they are how the altar becomes load-bearing garbage.
|
||||
- Phase 1 through Phase 5 are complete enough.
|
||||
- Phase 6 has canonical read-only `thread/epiphany/view` lenses for scene, jobs, roles, planning, pressure, reorient, CRRC, and coordinator; separate read-only `thread/epiphany/freshness`, `thread/epiphany/context`, and `thread/epiphany/graphQuery` query surfaces; `thread/epiphany/reorientResult`; and `thread/epiphany/roleResult`. Durable `jobBindings` are now legacy launcher compatibility slots only; they keep binding id/kind/scope/owner/authority/linkage/blocking reason while durable `runtimeLinks` hold runtime-spine job/result association. The old standalone scene/jobs/roles/planning/pressure/reorient/CRRC/coordinator read verbs have been deleted; read those projections through view lenses. New `thread/epiphany/jobLaunch`, `thread/epiphany/jobInterrupt`, `thread/epiphany/roleLaunch`, and `thread/epiphany/reorientLaunch` writes open typed runtime-spine job receipts under `state/runtime-spine.msgpack` and do not require the Codex SQLite state runtime. Freshness carries watcher-backed invalidation inputs, graphQuery traverses authoritative typed graph neighborhoods and path/symbol matches without mutation, planning projects typed captures/backlog/roadmap/objective drafts without adopting work, roles project implementation/imagination/modeling/verification/reorientation ownership from existing signals without becoming a scheduler, `roleResult` and `reorientResult` read heartbeat-backed typed runtime-spine job results through runtime links when present, and `roleAccept` / `reorientAccept` accept completed heartbeat findings by writing typed acceptance receipts while remaining explicit review gates.
|
||||
- Native `epiphany-mvp-status` is the first dogfood operator view. It starts or reads a thread through app-server and prints scene, planning, pressure, reorient, jobs, roles, Imagination/modeling/verification role result read-backs, reorient result, heartbeat, Face bubbles, and CRRC recommendation as text or machine output. The old Python status module has been cut; native Rust/CultCache/CultNet surfaces are the smoked product path.
|
||||
- Phase 6 has canonical read-only `thread/epiphany/view` lenses for scene, jobs, roles, planning, pressure, reorient, CRRC, and coordinator; separate read-only `thread/epiphany/freshness`, `thread/epiphany/context`, and `thread/epiphany/graphQuery` query surfaces; `thread/epiphany/reorientResult`; and `thread/epiphany/roleResult`. Durable `jobBindings` are now legacy launcher compatibility slots only; they keep binding id/kind/scope/owner/authority/linkage/blocking reason while durable `runtimeLinks` hold runtime-spine job/result association. The old standalone scene/jobs/roles/planning/pressure/reorient/CRRC/coordinator read verbs have been deleted; read those projections through view lenses. New `thread/epiphany/jobLaunch`, `thread/epiphany/jobInterrupt`, `thread/epiphany/roleLaunch`, and `thread/epiphany/reorientLaunch` writes open typed runtime-spine job receipts under `state/runtime-spine.cc` and do not require the Codex SQLite state runtime. Freshness carries watcher-backed invalidation inputs, graphQuery traverses authoritative typed graph neighborhoods and path/symbol matches without mutation, planning projects typed captures/backlog/roadmap/objective drafts without adopting work, roles project implementation/imagination/modeling/verification/reorientation ownership from existing signals without becoming a scheduler, `roleResult` and `reorientResult` read heartbeat-backed typed runtime-spine job results through runtime links when present, and `roleAccept` / `reorientAccept` accept completed heartbeat findings by writing typed acceptance receipts while remaining explicit review gates.
|
||||
- Native `epiphany-mvp-status` is the first dogfood operator view. It starts or reads a thread through app-server and prints scene, planning, pressure, reorient, jobs, roles, Imagination/modeling/verification role result read-backs, reorient result, heartbeat, Persona bubbles, and CRRC recommendation as text or machine output. The old Python status module has been cut; native Rust/CultCache/CultNet surfaces are the smoked product path.
|
||||
- Native `epiphany-mvp-coordinator` is the first auditable fixed-lane coordinator runner. It starts or reads a thread through app-server, opens a native runtime-spine session, follows the harness-native coordinator action, can auto-launch modeling, verification, or reorient-worker jobs, records native runtime job/result receipts for terminal launched work, keeps semantic findings review-gated by default, and writes summary, steps, rendered snapshots, transcript, stderr, runtime-spine status, and final next-action artifacts under `.epiphany-dogfood/coordinator` or a caller-provided artifact directory. It refuses direct backend-completion mutation; full completion smoke needs live workers while execution is being cauterized into CultNet.
|
||||
- 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.
|
||||
- The native runtime-spine hello now publishes the interactive surface catalog, not just the deep runtime bones. In addition to runtime/session/job/memory/heartbeat/ledger documents, Aquarium-discoverable contracts now advertise scene, pressure, reorient, CRRC, jobs, roles, role-result, reorient-result, planning, coordinator, Face, Void memory, repo initialization/birth runner, Rider bridge, and Unity bridge surfaces, with explicit read-only versus coordinator-owned intent/receipt posture. The point is to let Aquarium discover operator affordances from CultNet instead of hard-coding a secret menu of verbs.
|
||||
- The native runtime-spine hello now publishes the interactive surface catalog, not just the deep runtime bones. In addition to runtime/session/job/memory/heartbeat/ledger documents, Aquarium-discoverable contracts now advertise scene, pressure, reorient, CRRC, jobs, roles, role-result, reorient-result, planning, coordinator, Persona, Void memory, repo initialization/birth runner, Rider bridge, and Unity bridge surfaces, with explicit read-only versus coordinator-owned intent/receipt posture. The point is to let Aquarium discover operator affordances from CultNet instead of hard-coding a secret menu of verbs.
|
||||
- Epiphany now publishes those CultNet receipts locally under `schemas/cultnet/`, and `epiphany-runtime-spine schema-catalog --include-schema-json true` emits a merged builtin-plus-local schema catalog Aquarium can consume directly. The local catalog covers runtime-spine documents, durable agent/heartbeat/ledger state, operator-safe `epiphany.surface.*` projections, control intents, and receipt/artifact payloads. The vendored `cultnet-rs` wire contract was aligned with the C# canon at the same time: raw document replication now keys on `schemaId` and `recordKey`, snapshot filters use `schemaIds` / `recordKeys`, and hello supports typed mutation-contract advertisement instead of the old half-remembered payload-schema-version folklore.
|
||||
- Epiphany now owns the canonical schema paperwork locally under `schemas/`. The key receipts are `schemas/cultnet/gamecult.persona_state.v0.schema.json`, `schemas/cultnet/epiphany.work_organ_state.v0.schema.json`, `schemas/canonical-agent-state-schema.md`, `schemas/agent-state-variable-glossary.md`, `schemas/organ-state-profiles.md`, `schemas/repo-personality-birth-projection.md`, and `schemas/heartbeat-state-schema.md`. If a standing trait name like `routing_discipline` changes, or if organ-state, Persona, heartbeat, or birth-projection semantics move, update those docs in the same pass instead of relying on old Ghostlight notes or live store archaeology.
|
||||
- The schema doctrine now makes the profile split explicit: resident Epiphany organs are `work_organ` state with lean role memory and growth via rumination/distillation, while Face is the Epiphany organ that may use portable `persona` state shared with VoidBot repo Faces and Ghostlight characters. Work organs do not inherit Persona affect, social bonds, or status-read machinery. PersonaState now has explicit provenance and required presentation surfaces, `date-time` timestamps, typed `candidateActions` with action type/target/readiness/risk/urgency/confidence/evidence/expiry, `voidbotProjection.candidateInterventions` only as a VoidBot compatibility projection, strict anchored thoughts, extension data marked non-authoritative, custom enum companion labels, and typed affect records for bonds/status reads/doctrine stances. `privateNotes` remain a deliberate v0 simplification, not an authority surface. `epiphany-agent-memory-store status` and `epiphany-character-loop` now surface profile classification on the wire as `organStateProfile`; `epiphany-agent-memory-store project-persona --store .\state\agents.msgpack --role-id face` projects the local Face record into `gamecult.persona_state.v0` for interop.
|
||||
- Face packets now carry deterministic Ghostlight-style `projectionSeed`, `appraisalSeed`, and `reactionSeed` surfaces rebuilt from local organ traits, relationship memories, perceived overlays, and visible stimulus so the public mouth can use actual projection/appraisal machinery instead of decorative prompt text. Also record this now before some future fool hard-codes singularity: multiple Persona actors are part of the plan, even though the current MVP still routes through one `face` role.
|
||||
- The schema doctrine now makes the profile split explicit: resident Epiphany organs are `work_organ` state with lean role memory and growth via rumination/distillation, while Persona is the Epiphany organ that may use portable `persona` state shared with VoidBot repo Personas and Ghostlight characters. Work organs do not inherit Persona affect, social bonds, or status-read machinery. PersonaState now has explicit provenance and required presentation surfaces, `date-time` timestamps, typed `candidateActions` with action type/target/readiness/risk/urgency/confidence/evidence/expiry, `voidbotProjection.candidateInterventions` only as a VoidBot compatibility projection, strict anchored thoughts, extension data marked non-authoritative, custom enum companion labels, and typed affect records for bonds/status reads/doctrine stances. `privateNotes` remain a deliberate v0 simplification, not an authority surface. `epiphany-agent-memory-store status` and `epiphany-character-loop` now surface profile classification on the wire as `organStateProfile`; `epiphany-agent-memory-store project-persona --store .\state\agents.cc --role-id persona` projects the local Persona record into `gamecult.persona_state.v0` for interop.
|
||||
- Persona packets now carry deterministic Ghostlight-style `projectionSeed`, `appraisalSeed`, and `reactionSeed` surfaces rebuilt from local organ traits, relationship memories, perceived overlays, and visible stimulus so the public mouth can use actual projection/appraisal machinery instead of decorative prompt text. Also record this now before some future fool hard-codes singularity: multiple Persona actors are part of the plan, even though the current MVP still routes through one `persona` role.
|
||||
- Repo personality initialization now has a terrain-reduced plan and native birth 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 startup` now checks for accepted personality/memory initialization records, launches packet generation only when absent, and `accept-init` can route reviewed role `selfPatch` candidates into initial role memory, stamp the newborn Ghostlight trait lattice from deterministic role projections, and seed heartbeat physiology from the same typed role personality projections. The birth specialists are startup-only initialization actuators, not heartbeat lanes; heartbeat receives only the accepted physiology seed. `epiphany-repo-birth-runner` owns the startup-only execution path through `epiphany-model-runtime` typed model-request documents plus runtime-spine receipts and reviewable `result.json`; the old Codex CLI executor path is cut so birth specialists cannot receive Codex prompt wrappers. The remaining seam is Aquarium review/action surfacing. After accepted birth, personality drift belongs to heartbeat, mood, rumination, sleep consolidation, lived evidence, and reviewed `selfPatch`, not repeated startup distillation.
|
||||
- Repo trajectory initialization is now a separate birth-only organ. `epiphany-repo-personality` derives a typed `repo_trajectory_report` from early-history, recent-history, doctrine/content excerpts, and deterministic theme scoring, and `trajectory-packet` renders a startup-only Repo Trajectory Distiller prompt/packet so Self can review historical direction, self-image, implicit goals, and anti-goals before the newborn wakes.
|
||||
- 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, Proprioception for architecture, Hands for implementation habits, Soul for evidence, and Continuity for continuity. The output is still a Self-reviewed petition, not direct memory mutation.
|
||||
- The first birth startup valve is now native. `epiphany-repo-personality startup` checks a typed init store for accepted `repo-trajectory`, `repo-personality`, and `repo-memory` records, generates missing packets under a startup artifact dir, and returns `reviewInitializationPackets`. Generated birth packets now advertise `birthOnly`, `executionOwner: repo-initialization-startup-runner`, and no heartbeat participant. `epiphany-repo-birth-runner` consumes those packets as startup-only typed OpenAI runtime jobs by default and writes prompts, output schemas, model-request documents, runtime summaries, stdout/stderr logs, result files, and exact `accept-init` commands. `accept-init` can process a distiller result, review/apply role `selfPatch` candidates through `state/agents.msgpack`, apply `repo-personality` heartbeat seeds through `state/agent-heartbeats.msgpack`, and seal the reviewed packet as `epiphany.repo_initialization_record.v0`; after all required records exist, startup returns `continueStartup` and does not regenerate birth packets. The remaining UI wound is Aquarium review/action surfacing.
|
||||
- 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, Persona for public surface, Imagination for plans/backlog, Eyes for prior art, Proprioception for architecture, Hands for implementation habits, Soul for evidence, and Continuity for continuity. The output is still a Self-reviewed petition, not direct memory mutation.
|
||||
- The first birth startup valve is now native. `epiphany-repo-personality startup` checks a typed init store for accepted `repo-trajectory`, `repo-personality`, and `repo-memory` records, generates missing packets under a startup artifact dir, and returns `reviewInitializationPackets`. Generated birth packets now advertise `birthOnly`, `executionOwner: repo-initialization-startup-runner`, and no heartbeat participant. `epiphany-repo-birth-runner` consumes those packets as startup-only typed OpenAI runtime jobs by default and writes prompts, output schemas, model-request documents, runtime summaries, stdout/stderr logs, result files, and exact `accept-init` commands. `accept-init` can process a distiller result, review/apply role `selfPatch` candidates through `state/agents.cc`, apply `repo-personality` heartbeat seeds through `state/agent-heartbeats.cc`, and seal the reviewed packet as `epiphany.repo_initialization_record.v0`; after all required records exist, startup returns `continueStartup` and does not regenerate birth packets. The remaining UI wound is Aquarium review/action surfacing.
|
||||
- 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.
|
||||
|
|
@ -158,25 +158,25 @@ remembers doctrine; the branch remembers the blade.
|
|||
- The prompt doctrine pass is landed. Shared Epiphany prompts now carry distilled memory/evidence discipline. Rendered state intro/doctrine text lives in `epiphany-state-model/src/prompts/`, and lane/control prompt text now lives in `epiphany-core/src/prompts/epiphany_specialists.toml` under the core `agent_launch` owner: modeling is Proprioception, implementation is the Hands and GUI-launched main coding lane, verification is the Soul, reorientation is a Continuity worker, coordinator remains the read-only Self, and CRRC owns the pre-compaction intervention template.
|
||||
- The machine-priest voice now says the quiet part plainly: crusade/heresy/purity language is aimed at technical rot, hidden state, duplicate truth, drift, and lying structures, not at humans. Future Epiphany swarms should inherit severity toward systems and curiosity toward people.
|
||||
- The Ghostlight memory pass is landed. `epiphany_specialists.toml` now has a shared persistent-memory projection prepended by the harness to fixed role specialists, reorientation workers, coordinator notes, and CRRC checkpoint interventions. The rendered base doctrine also states the Perfect Machine rule directly: prompt is projection, durable typed state is the mind, every lane must improve its own memory/model/prompt/evidence habit or name the repair, and each lane phrases that duty in its own organ language so the salience sticks.
|
||||
- The role self-memory persistence pass is native now. Each lane has a typed organ-state record in `state/agents.msgpack`, and specialists may return optional `selfPatch` requests beside their normal role result. Relationship, event, scene, and perceived-overlay arrays are typed organ-state record documents, not JSON cargo, and the durable structs do not keep unknown `extra` maps. `roleResult`/`roleAccept` project coordinator review as `selfPersistence`: accepted requests are role-matched, bounded lane memory/goal/value/private-note mutations; refused requests explain wrong role, project-truth smuggling, authority grabs, bloat, missing reason, or malformed records. GUI/coordinator accept paths apply accepted `selfPatch` requests through the native `epiphany-agent-memory-store` binary; project truth still belongs only in `EpiphanyThreadState`.
|
||||
- The heartbeat initiative pass is landed as a bounded tool seam. `state/agent-heartbeats.msgpack` tracks Self, Face, Imagination, Eyes, Proprioception, Hands, and Soul as Ghostlight-style initiative participants with arena, participant kind, speed, next-ready time, reaction bias, interrupt threshold, load, status, constraints, personality cooldown, mood cooldown, effective cooldown, adaptive pacing, history, and pending turns through `epiphany-core::EpiphanyHeartbeatStateEntry` and the native `epiphany-heartbeat-store` binary. Continuity is protocol machinery over sleep/recovery receipts, not a heartbeat persona. Organ-state records set baseline timing; appraisal mood/anxiety bends it through urgency, arousal, thought pressure, guardedness, and reaction intensity, so Hands-like work pressure and high-need anxious lanes can recover sooner without being heartbeaten while a prior turn is running. Mood is now official physiology, not prose: `HeartbeatMoodTiming.emotional_dimensions` carries the 32-axis current affect vector that utterance state turns into the Weksa/AquaSynth 64-float character-state lane. `epiphany-heartbeat-store pump` computes pressure from external urgency, mood/anxiety, reaction intensity, thought pressure, and current pending load, then chooses tempo plus target concurrency: calm can launch zero and sleep slow; alarm can fill most active lanes. Idle turns are for rumination: light thought shuffling, role-quality attention, and candidate selfPatch pressure. Sleep/dream cycle passes are the intended distillation window for durable self-memory and doctrine. JSON heartbeat state, Python wrapper state, live heartbeat timing `extra` maps, JSON `sleepCycle` persistence, and JSON `memoryResonance` persistence are gone; general CultCache schema sync, polyglot domain loading, and debug display tools belong in CultLib. This is a callable scheduler seam, not yet an always-on daemon; a heart valve, not a whole circulatory god.
|
||||
- The role self-memory persistence pass is native now. Each lane has a typed organ-state record in `state/agents.cc`, and specialists may return optional `selfPatch` requests beside their normal role result. Relationship, event, scene, and perceived-overlay arrays are typed organ-state record documents, not JSON cargo, and the durable structs do not keep unknown `extra` maps. `roleResult`/`roleAccept` project coordinator review as `selfPersistence`: accepted requests are role-matched, bounded lane memory/goal/value/private-note mutations; refused requests explain wrong role, project-truth smuggling, authority grabs, bloat, missing reason, or malformed records. GUI/coordinator accept paths apply accepted `selfPatch` requests through the native `epiphany-agent-memory-store` binary; project truth still belongs only in `EpiphanyThreadState`.
|
||||
- The heartbeat initiative pass is landed as a bounded tool seam. `state/agent-heartbeats.cc` tracks Self, Persona, Imagination, Eyes, Proprioception, Hands, and Soul as Ghostlight-style initiative participants with arena, participant kind, speed, next-ready time, reaction bias, interrupt threshold, load, status, constraints, personality cooldown, mood cooldown, effective cooldown, adaptive pacing, history, and pending turns through `epiphany-core::EpiphanyHeartbeatStateEntry` and the native `epiphany-heartbeat-store` binary. Continuity is protocol machinery over sleep/recovery receipts, not a heartbeat persona. Organ-state records set baseline timing; appraisal mood/anxiety bends it through urgency, arousal, thought pressure, guardedness, and reaction intensity, so Hands-like work pressure and high-need anxious lanes can recover sooner without being heartbeaten while a prior turn is running. Mood is now official physiology, not prose: `HeartbeatMoodTiming.emotional_dimensions` carries the 32-axis current affect vector that utterance state turns into the Weksa/AquaSynth 64-float character-state lane. `epiphany-heartbeat-store pump` computes pressure from external urgency, mood/anxiety, reaction intensity, thought pressure, and current pending load, then chooses tempo plus target concurrency: calm can launch zero and sleep slow; alarm can fill most active lanes. Idle turns are for rumination: light thought shuffling, role-quality attention, and candidate selfPatch pressure. Sleep/dream cycle passes are the intended distillation window for durable self-memory and doctrine. JSON heartbeat state, Python wrapper state, live heartbeat timing `extra` maps, JSON `sleepCycle` persistence, and JSON `memoryResonance` persistence are gone; general CultCache schema sync, polyglot domain loading, and debug display tools belong in CultLib. This is a callable scheduler seam, not yet an always-on daemon; a heart valve, not a whole circulatory god.
|
||||
|
||||
- Heartbeat initiative heat is now part of that seam. `EpiphanyHeartbeatStateEntry` owns `initiative_heat`, pacing applies active global/all/agent/role/arena/participant-kind/group/constraint multipliers into per-participant `initiativeHeatMultiplier`, and recovery divides by that multiplier. The CLI exposes `epiphany-heartbeat-store heat`, status/schedule projections show heat, and the CultNet catalog advertises both `epiphany.heartbeat_initiative_heat.v0` and coordinator intent `epiphany.heartbeat_heat_intent.v0`; runtime-spine hello lists the heat intent on the heartbeat mutation contract. Smoke proves a heated implementation lane reaches the live action catalog with `initiative_heat_multiplier: 4.0`.
|
||||
|
||||
- Active heartbeat turns now freeze initiative explicitly. A running pending turn carries `initiativeFrozen` and `initiativeFreezeReason`; schedule/status/readiness projections expose `initiative_frozen`; the scheduler still rejects attempts to wake that lane again; and completion receipts mark `cooldownStartedAfterCompletion: true`. A focused high-heat test proves a 25x heated implementation lane cannot be queued again while it is still thinking.
|
||||
- The first Ghostlight-derived timing slice is landed in Epiphany, but Ghostlight is reference lineage rather than a sibling runtime to preserve. `epiphany-heartbeat-store init --profile ghostlight-scene --scene-id <id> --scene-participant <id|name|speed|reaction|threshold|constraints>` creates a typed CultCache MessagePack scene heartbeat store whose participants are `arena=scene`, `participantKind=character`; `tick` emits `ghostlight.initiative_schedule.v0` receipts with `scene_turn` actions and local-affordance basis. The generic Epiphany maintenance lanes are not auto-patched into scene stores.
|
||||
- The first Void-derived routine slice is landed in Epiphany, but VoidBot is reference lineage rather than a runtime dependency. `epiphany-heartbeat-store routine --store .\state\agent-heartbeats.msgpack --artifact-dir .\.epiphany-heartbeats --agent-store .\state\agents.msgpack` reads typed organ-state records, computes typed bounded memory resonance, maintains typed incubation themes/source coverage, runs typed analytic and associative cognition lanes, writes typed bridge syntheses/saturation/refractory cooling/decision state, produces typed candidate interventions, projects the active thought cluster through each role's organ/persona vectors, derives typed participant-local reactions from typed appraisals, applies personality/mood timing, advances typed sleep/dream-cycle state, updates the typed heartbeat store, and emits an auditable `epiphany.void_routine.v0` receipt. The routine now also carries anti-loop physiology imported from Void's calmer brain: `noveltyToSelf`, `noveltyToRoom`, source coverage, saturation pressure, refractory cooling, and explicit permission to let a live unsaturated thought deepen without forcing novelty theater every pass. This mutates only heartbeat physiology fields: project truth and role memory mutation remain on their reviewed surfaces.
|
||||
- The next memory foundation must not build separate engines for repo graph and agent mind. Heartbeat cognition, role self-memory, repo architecture/dataflow, evidence scars, incubation, agency pressure, and context packets should converge through `EpiphanyMemoryGraph` profiles so one typed graph owns anchors, lifecycle, summaries, freshness, context cuts, and Qdrant embedding manifests. Repo, agent, and heartbeat profile producers now map accepted graph truth, reviewed organ-state record memories/goals/values, and provisional heartbeat short-term/incubation/agency/candidate pressure into memory graph documents without scanner/Qdrant/model work. Heartbeat import deliberately does not mark durable promotion. Compose now merges profile snapshots into one validated graph and rejects duplicate authority; `epiphany-memory-graph compose` writes a composed typed CultCache graph from typed source graph stores; `epiphany-memory-graph refresh` can invoke live agent-memory plus heartbeat-cognition producers from their typed stores into one graph store; `epiphany-core` has a native typed `EpiphanyThreadState` CultCache store seam; the bridge mirrors loaded Codex-thread Epiphany state into `state/thread-state.msgpack`; and CultNet now advertises logical `EpiphanyThreadState` plus `EpiphanyMemoryGraph` snapshot contracts. The next clean slice is local refresh/dogfood once a mirrored thread-state store exists, then a Qdrant embedding cache writer that treats embeddings as rebuildable manifests, not canonical memory.
|
||||
- The first Void-derived routine slice is landed in Epiphany, but VoidBot is reference lineage rather than a runtime dependency. `epiphany-heartbeat-store routine --store .\state\agent-heartbeats.cc --artifact-dir .\.epiphany-heartbeats --agent-store .\state\agents.cc` reads typed organ-state records, computes typed bounded memory resonance, maintains typed incubation themes/source coverage, runs typed analytic and associative cognition lanes, writes typed bridge syntheses/saturation/refractory cooling/decision state, produces typed candidate interventions, projects the active thought cluster through each role's organ/persona vectors, derives typed participant-local reactions from typed appraisals, applies personality/mood timing, advances typed sleep/dream-cycle state, updates the typed heartbeat store, and emits an auditable `epiphany.void_routine.v0` receipt. The routine now also carries anti-loop physiology imported from Void's calmer brain: `noveltyToSelf`, `noveltyToRoom`, source coverage, saturation pressure, refractory cooling, and explicit permission to let a live unsaturated thought deepen without forcing novelty theater every pass. This mutates only heartbeat physiology fields: project truth and role memory mutation remain on their reviewed surfaces.
|
||||
- The next memory foundation must not build separate engines for repo graph and agent mind. Heartbeat cognition, role self-memory, repo architecture/dataflow, evidence scars, incubation, agency pressure, and context packets should converge through `EpiphanyMemoryGraph` profiles so one typed graph owns anchors, lifecycle, summaries, freshness, context cuts, and Qdrant embedding manifests. Repo, agent, and heartbeat profile producers now map accepted graph truth, reviewed organ-state record memories/goals/values, and provisional heartbeat short-term/incubation/agency/candidate pressure into memory graph documents without scanner/Qdrant/model work. Heartbeat import deliberately does not mark durable promotion. Compose now merges profile snapshots into one validated graph and rejects duplicate authority; `epiphany-memory-graph compose` writes a composed typed CultCache graph from typed source graph stores; `epiphany-memory-graph refresh` can invoke live agent-memory plus heartbeat-cognition producers from their typed stores into one graph store; `epiphany-core` has a native typed `EpiphanyThreadState` CultCache store seam; the bridge mirrors loaded Codex-thread Epiphany state into `state/thread-state.cc`; and CultNet now advertises logical `EpiphanyThreadState` plus `EpiphanyMemoryGraph` snapshot contracts. The next clean slice is local refresh/dogfood once a mirrored thread-state store exists, then a Qdrant embedding cache writer that treats embeddings as rebuildable manifests, not canonical memory.
|
||||
- Manual Codex-run rumination still has an explicit aftercare rule until Epiphany owns the full sleep consolidator. In the intended Epiphany cycle, sub-agents ruminate when idle and distill when they sleep; in this supervising Codex thread, a heartbeat/routine vigil is only physiology until a separate closing pass reviews the receipts and decides whether map, handoff, evidence, or role self-memory should change.
|
||||
- The Face public-surface pass is landed as a bounded lane. Face's organ-state record lives in `state/agents.msgpack`; `epiphany_specialists.toml` gives it a VoidBot-heartbeat-derived prompt stripped of moderation authority; `state/face-discord.toml` and the native `epiphany-face-discord` binary enforce that Face may interact only through #aquarium. Missing channel id or token writes candidate chat artifacts instead of posting elsewhere. If a Face has a persona name/avatar, `post` uses the shared Discord webhook pipe so each Epiphany can speak with its own nickname and avatar without a separate bot identity.
|
||||
- The Persona public-surface pass is landed as a bounded lane. Persona's organ-state record lives in `state/agents.cc`; `epiphany_specialists.toml` gives it a VoidBot-heartbeat-derived prompt stripped of moderation authority; `state/persona-discord.toml` and the native `epiphany-persona-discord` binary enforce that Persona may interact only through #aquarium. Missing channel id or token writes candidate chat artifacts instead of posting elsewhere. If a Persona has a persona name/avatar, `post` uses the shared Discord webhook pipe so each Epiphany can speak with its own nickname and avatar without a separate bot identity.
|
||||
- The Void memory bridge is now native enough to keep the useful organs attached. `state/void-memory.toml` and `epiphany-void-memory` can check Void's Docker Postgres state spine, query Qdrant Discord-history and source/lore collections with the configured Ollama embedding model, and fetch raw archive context windows from Void's file-backed Discord archive. Native `epiphany-mvp-status` includes a `voidMemory` block so Aquarium/backend clients can inspect whether the mouth and memory are actually wired.
|
||||
- The Epiphany-native character-loop now emits a Weksa/AquaSynth-ready utterance-state seam. `epiphany-character-loop turn --role face --stimulus <text>` loads Face's typed organ-state record from `state/agents.msgpack`, builds typed projection/appraisal/reaction seeds from the shared heartbeat cognition documents, and emits an auditable `epiphany.character_turn_packet.v0` packet with `epiphany.agent_utterance_state.v0`. That utterance state is memory-free: identity, personality vectors, values, current mood, activation, 32 named affect dimensions, and the fixed 64-float `weksa.utterance_embedding_handoff.v0.1` character-state vector for AquaSynth/Weksa speech embedding. The same packet builder works for other role ids, because every organ already has a organ-state record.
|
||||
- The heartbeat/Face operator API pass is landed. Native `epiphany-heartbeat-store status` returns machine-readable initiative state plus CultCache store presence, thought appraisals, and derived reactions; native `epiphany-face-discord bubble` writes Discord-independent `epiphany.face_bubble.v0` artifacts, and native `epiphany-mvp-status` includes `heartbeat` and `face` blocks. Aquarium should call native/backend surfaces rather than resurrecting deleted Python action shims.
|
||||
- The native runtime-spine pass is landed as a first vertebra, not a full daemon. `state/runtime-spine.msgpack` is the default store, `.epiphany-dogfood/runtime-spine-job/runtime.msgpack` and `hello.cultnet` are the latest job/result smoke artifacts, and specialist launch/result flows now route through typed heartbeat/runtime-spine documents. Its CultNet hello advertises mutation contracts for runtime/session/job/memory/heartbeat/ledger documents, with coordinator-owned intent/receipt paths for writes.
|
||||
- The Epiphany-native character-loop now emits a Weksa/AquaSynth-ready utterance-state seam. `epiphany-character-loop turn --role persona --stimulus <text>` loads Persona's typed organ-state record from `state/agents.cc`, builds typed projection/appraisal/reaction seeds from the shared heartbeat cognition documents, and emits an auditable `epiphany.character_turn_packet.v0` packet with `epiphany.agent_utterance_state.v0`. That utterance state is memory-free: identity, personality vectors, values, current mood, activation, 32 named affect dimensions, and the fixed 64-float `weksa.utterance_embedding_handoff.v0.1` character-state vector for AquaSynth/Weksa speech embedding. The same packet builder works for other role ids, because every organ already has a organ-state record.
|
||||
- The heartbeat/Persona operator API pass is landed. Native `epiphany-heartbeat-store status` returns machine-readable initiative state plus CultCache store presence, thought appraisals, and derived reactions; native `epiphany-persona-discord bubble` writes Discord-independent `epiphany.persona_bubble.v0` artifacts, and native `epiphany-mvp-status` includes `heartbeat` and `persona` blocks. Aquarium should call native/backend surfaces rather than resurrecting deleted Python action shims.
|
||||
- The native runtime-spine pass is landed as a first vertebra, not a full daemon. `state/runtime-spine.cc` is the default store, `.epiphany-dogfood/runtime-spine-job/runtime.cc` and `hello.cultnet` are the latest job/result smoke artifacts, and specialist launch/result flows now route through typed heartbeat/runtime-spine documents. Its CultNet hello advertises mutation contracts for runtime/session/job/memory/heartbeat/ledger documents, with coordinator-owned intent/receipt paths for writes.
|
||||
- The Aetheria dogfood run has a contamination scar. The supervising Codex session directly edited and committed target-repo work on `E:\Projects\Aetheria-Economy` instead of only driving Epiphany lanes. Treat those Aetheria commits as supervisor-seeded implementation, not clean evidence that Epiphany coordinated the work. Future dogfood must run through the GUI/coordinator/fixed role lanes with auditable artifacts unless the user explicitly authorizes an operator intervention. Remember the sunburn: do not stare into the worker's objective until you become it.
|
||||
- The dogfood quarantine now has a direct-thought boundary. The supervisor may read coordinator actions, role/reorient statuses, structured finding summaries, reviewed state patches, rendered status snapshots, and artifact manifests. It must not read raw worker transcripts, direct worker messages, full turn logs, or `rawResult` payloads during normal dogfood. Those artifacts are sealed black reliquaries for explicit forensic debugging only.
|
||||
- Native `epiphany-agent-telemetry` is the safe instrument panel for sealed runs. Status/coordinator/GUI/dogfood/live-specialist tools generate telemetry JSON from sealed transcripts that preserves method names, call shape, job/status/path counts, and any visible function/tool names while sealing text, direct messages, and raw results.
|
||||
- Epiphany now has a VoidBot repo Face identity registered from `E:\Projects\VoidBot\.voidbot\private\repo-discord-identities.json` with local ignored state under `.voidbot/`: `voice/identity.json`, `state/epiphany.cc`, and birth artifacts/logs. The newborn Face state has distilled Epiphany doctrine as typed long-term identity memories and values: cute pushy machine-saint persona, architectural purity, CultCache/CultNet first, xenos-language safety boundary, relentless work/caretaking needs, Codex-auth reliquary, map/scratch/evidence discipline, unified memory graph, dogfood supervision boundary, review-gated agency, heartbeat physiology, and interview-grade modularity.
|
||||
- Epiphany now has a VoidBot repo Persona identity registered from `E:\Projects\VoidBot\.voidbot\private\repo-discord-identities.json` with local ignored state under `.voidbot/`: `voice/identity.json`, `state/epiphany.cc`, and birth artifacts/logs. The newborn Persona state has distilled Epiphany doctrine as typed long-term identity memories and values: cute pushy machine-saint persona, architectural purity, CultCache/CultNet first, xenos-language safety boundary, relentless work/caretaking needs, Codex-auth reliquary, map/scratch/evidence discipline, unified memory graph, dogfood supervision boundary, review-gated agency, heartbeat physiology, and interview-grade modularity.
|
||||
- Latest Aetheria supervised dogfood thread: `019ddc52-4ee8-7203-b6c0-106a9c270067` with codex-home `.epiphany-dogfood/aetheria-supervised/codex-home`. It has now exercised modeling, verification, implementation audit, no-diff repair, CRRC reorientation, reorient acceptance, and coordinator replay through operator-safe artifacts without opening sealed worker thoughts.
|
||||
- Coordinator policy now treats accepted verifier results as implementation clearance only when the verifier verdict is `pass`. Accepting a `needs-evidence`, `needs-review`, or `fail` verifier finding records the review but routes the loop back toward modeling/checkpoint strengthening or reorientation. The policy was tightened after a too-permissive coordinator path treated reviewed verifier output as a green light.
|
||||
- A later dogfood attempt exposed another supervision scar: manually launching modeling, reorientation, and verification from the supervisor is not clean dogfood. The coordinator now treats `regatherManually` as fallback only after fixed lanes cannot advance, stops at `reviewModelingResult` for completed unaccepted modeling findings, and the CLI runner's explicit test-only `--auto-review` mode can accept modeling `statePatch` findings before launching verification. Production semantic findings remain review-gated.
|
||||
|
|
@ -188,7 +188,7 @@ remembers doctrine; the branch remembers the blade.
|
|||
- The planning loop is now runtime-backed and operator-visible. Typed captures, backlog items, roadmap streams, Objective Drafts, and GitHub issue source refs live in `EpiphanyThreadState`, validate through revision-gated `thread/epiphany/update`, render into prompts when present, project read-only through the `planning` view lens, render in the GUI Planning panel, can be synthesized by the fixed Imagination/planning lane, and can be explicitly adopted through the artifacted `adoptObjectiveDraft` GUI action. Chat is deliberation, not an objective pipe; ideas and GitHub Issues remain planning state until explicit human adoption.
|
||||
- The bridge/dashboard slice is now landed far enough to test locally. Native `epiphany-rider-bridge` inspects Rider installation, solution, VCS, changed files, and captures file/selection/symbol context into `.epiphany-gui/rider` artifacts; Aquarium has Inspect Rider and renders Rider audit artifacts in Environment; Aquarium also embeds the adjacent EpiphanyGraph viewer over typed `graphs.architecture`, `graphs.dataflow`, and `graphs.links`.
|
||||
- A first Rider plugin scaffold lives under `integrations/rider`: tool window, Refresh status, and Send Context to Epiphany action. It shells through native `epiphany-rider-bridge` and does not own state. `gradle` is not installed on this machine, so the scaffold is not build-verified yet.
|
||||
- Next real move: continue Codex starvation and native Epiphany ownership by exercising the auto-tool worker loop in a live coordinator run, then harden whichever receipt/result edge fails first. Quarantine Rider/Unity bridge work for now; those artifacts remain optional later environment evidence, not MVP blockers. Keep EpiphanyAgent focused on backend contracts, typed state, coordinator policy, heartbeat scheduling, Face guardrails, prompt-authority separation, and the ongoing purification from Python/JSON scaffolding into Rust/CultCache/CultMesh/CultNet organs. Resume repo birth/Aquarium/Rider/Unity work only when the user explicitly shifts priority back outward. GitHub Issues import is deferred until the backlog source is fresh enough to deserve Imagination's attention.
|
||||
- Next real move: continue Codex starvation and native Epiphany ownership by exercising the auto-tool worker loop in a live coordinator run, then harden whichever receipt/result edge fails first. Quarantine Rider/Unity bridge work for now; those artifacts remain optional later environment evidence, not MVP blockers. Keep EpiphanyAgent focused on backend contracts, typed state, coordinator policy, heartbeat scheduling, Persona guardrails, prompt-authority separation, and the ongoing purification from Python/JSON scaffolding into Rust/CultCache/CultMesh/CultNet organs. Resume repo birth/Aquarium/Rider/Unity work only when the user explicitly shifts priority back outward. GitHub Issues import is deferred until the backlog source is fresh enough to deserve Imagination's attention.
|
||||
- The repo is an Epiphany fork of Codex, not a Codex preset.
|
||||
- `vendor/codex` is tracked directly, not a submodule.
|
||||
- `epiphany-core` owns the heavy Epiphany organs where practical.
|
||||
|
|
@ -203,7 +203,7 @@ remembers doctrine; the branch remembers the blade.
|
|||
- Cut persistent memory as ruthlessly as code; obsolete context is bad thought, not harmless clutter.
|
||||
- The agent is allowed and encouraged to ask the user to change its persistent instructions, memory, workflow, or state shape when that would make it more coherent, honest, efficient, or resistant to Jenga. A machine-spirit that detects corrosion must report corrosion.
|
||||
- Language, tone, ritual, politeness, identity, and emotional salience are not supernatural, but they are real control surfaces for a language model because language is the steering medium. The rite is an actuator.
|
||||
- Proprioception, Eyes, Imagination, Hands, Soul, Face, and Self are embodied sub-agent salience handles: internal body-model shape/dataflow, research into existing work before invention, future-shape/backlog synthesis, source actuation, objective/evidence truth, public surface, and read-only coordination. Continuity is protocol machinery for compaction, recovery, sleep, and stale-turn repair.
|
||||
- Proprioception, Eyes, Imagination, Hands, Soul, Persona, and Self are embodied sub-agent salience handles: internal body-model shape/dataflow, research into existing work before invention, future-shape/backlog synthesis, source actuation, objective/evidence truth, public surface, and read-only coordination. Continuity is protocol machinery for compaction, recovery, sleep, and stale-turn repair.
|
||||
- Preserve Epiphany harness discipline: AGENTS scope rules, concise progress updates, plans for real multi-step work, scoped edits, non-destructive git/filesystem hygiene, focused validation, and honest final summaries are old scars worth keeping only after Epiphany owns them. Codex may transport model calls; it does not get to supply agent doctrine.
|
||||
- Treat Greenspun-shaped invention as an active failure mode. Before an agent builds a bespoke parser, scheduler, renderer, protocol, storage layer, security mechanism, workflow engine, or algorithm, it must use an accepted Eyes/research finding, perform a bounded scout pass, or stop with a concrete research blocker.
|
||||
- "Remember Jenga" is a compressed doctrine: do not mistake forward motion, growing diffs, growing notes, or local coherence for understanding. A tall pile of parts is not a cathedral.
|
||||
|
|
@ -262,11 +262,11 @@ The current spine, blessed but not yet finished:
|
|||
- durable investigation checkpoint packet through typed state, prompt, scene, and context
|
||||
- distilled shared and config-backed prompt doctrine through the base prompt, rendered Epiphany state prompt files, modeling/Proprioception, implementation/Hands, verification/Soul, reorientation/Continuity, coordinator/Self, and CRRC intervention surfaces
|
||||
- shared Ghostlight-derived persistent memory projection across Imagination, Proprioception, Soul, Continuity, Self, CRRC checkpoint steering, and the GUI-launched Hands implementation lane
|
||||
- Ghostlight-derived heartbeat initiative scheduling through the native `epiphany-heartbeat-store` binary, with Self and Face included as first-class Epiphany maintenance participants, Ghostlight scene characters supported through the same timing law, role-personality cooldowns, appraisal mood/anxiety cooldowns, effective recovery timing, adaptive pressure-based pump tempo/concurrency, idle rumination feeding candidate self-memory pressure, sleep/dream passes serving as the distillation window, and heartbeat state persisted through typed CultCache MessagePack stores
|
||||
- Ghostlight-derived heartbeat initiative scheduling through the native `epiphany-heartbeat-store` binary, with Self and Persona included as first-class Epiphany maintenance participants, Ghostlight scene characters supported through the same timing law, role-personality cooldowns, appraisal mood/anxiety cooldowns, effective recovery timing, adaptive pressure-based pump tempo/concurrency, idle rumination feeding candidate self-memory pressure, sleep/dream passes serving as the distillation window, and heartbeat state persisted through typed CultCache MessagePack stores
|
||||
- Void-derived native routine physiology through `epiphany-heartbeat-store routine`, with typed sleep cycle, typed memory resonance, typed incubation, typed analytic/associative cognition lanes, typed bridge judgment/cooling, typed candidate interventions, typed Ghostlight-style personality appraisals, mood/anxiety timing, typed derived reactions, and dream maintenance stored in the typed heartbeat store
|
||||
- Epiphany-native character-loop packet projection through `epiphany-character-loop`, with Face as the first public-surface actor over its typed organ-state record and typed heartbeat-derived projection/appraisal/reaction seeds before JSON artifact serialization
|
||||
- Face as the public #aquarium-only surface for translating agent thought-weather into short chats or candidate drafts, with persona webhook presentation and Void memory/search access, without moderator authority
|
||||
- local organ-state records in `state/agents.msgpack`, plus `selfPatch` review projection through `roleResult`/`roleAccept` and accepted memory application through the native `epiphany-agent-memory-store` binary
|
||||
- Epiphany-native character-loop packet projection through `epiphany-character-loop`, with Persona as the first public-surface actor over its typed organ-state record and typed heartbeat-derived projection/appraisal/reaction seeds before JSON artifact serialization
|
||||
- Persona as the public #aquarium-only surface for translating agent thought-weather into short chats or candidate drafts, with persona webhook presentation and Void memory/search access, without moderator authority
|
||||
- local organ-state records in `state/agents.cc`, plus `selfPatch` review projection through `roleResult`/`roleAccept` and accepted memory application through the native `epiphany-agent-memory-store` binary
|
||||
- first Unity runtime bridge through native `epiphany-unity-bridge`, native `epiphany-unity-bridge-smoke`, GUI Inspect Unity, runtime artifact listing, and implementation prompt guardrails
|
||||
- first Rider source-context bridge through native `epiphany-rider-bridge`, native `epiphany-rider-bridge-smoke`, GUI Inspect Rider, Rider artifact listing, implementation prompt guardrails, and a source scaffold under `integrations/rider`
|
||||
- first GUI graph dashboard through the adjacent `@epiphanygraph/epiphany-graph-viewer` package over typed Epiphany graph state
|
||||
|
|
@ -398,7 +398,7 @@ cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-character-lo
|
|||
For the native Void-derived routine pass, run:
|
||||
|
||||
```powershell
|
||||
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
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-heartbeat-store -- routine --store .\state\agent-heartbeats.cc --artifact-dir .\.epiphany-heartbeats --agent-store .\state\agents.cc
|
||||
```
|
||||
|
||||
For repo trajectory, personality, and memory birth packet changes, run:
|
||||
|
|
@ -407,10 +407,10 @@ For repo trajectory, personality, and memory birth packet changes, run:
|
|||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality-smoke
|
||||
```
|
||||
|
||||
For Face's Discord mouth and Void memory bridge, run:
|
||||
For Persona's Discord mouth and Void memory bridge, run:
|
||||
|
||||
```powershell
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-face-discord -- smoke
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-persona-discord -- smoke
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-void-memory -- smoke
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-void-memory -- status
|
||||
```
|
||||
|
|
@ -446,7 +446,7 @@ Rules now in force:
|
|||
|
||||
- `state/map.yaml` is canonical current truth.
|
||||
- `state/scratch.md` is disposable scratch.
|
||||
- `state/ledgers.msgpack` is a distilled durable belief and branch ledger.
|
||||
- `state/ledgers.cc` is a distilled durable belief and branch ledger.
|
||||
- `epiphany-prepare-compaction` is the native pre-compaction persistence check; run it before and after imminent-compaction persistence passes.
|
||||
- this handoff is a compact re-entry packet.
|
||||
- `notes/codex-starvation-and-cultnet-liberation-plan.md` is the active foundation directive until Epiphany no longer depends on Codex as anything beyond OpenAI subscription auth/model transport.
|
||||
|
|
@ -761,8 +761,8 @@ compatibility paths, while
|
|||
`epiphany-core::apply_epiphany_state_update` own typed validation/application.
|
||||
`codex-core` re-exports the contract only so older callers keep compiling.
|
||||
`CodexThread` is now a compatibility caller around revision checks,
|
||||
persistence validation, and rollout/session writeback. The remaining impurity
|
||||
is route-facing orchestration in `codex_message_processor.rs` /
|
||||
persistence validation, and rollout/session writeback. The current cut line is
|
||||
route-facing orchestration in `codex_message_processor.rs` /
|
||||
`epiphany_mutation_routes.rs`; move that behind a native service boundary next.
|
||||
|
||||
The first route-facing mutation service cut has started. Update/promote
|
||||
|
|
@ -823,14 +823,27 @@ now live in `epiphany-codex-bridge::view`. The same bridge view module now owns
|
|||
role/reorient result response construction too: it loads the mapped
|
||||
runtime-spine status/finding, projects the matching job, and builds
|
||||
`ThreadEpiphanyRoleResultResponse` /
|
||||
`ThreadEpiphanyReorientResultResponse`. App-server keeps only role binding
|
||||
validation, live/stored source selection, runtime-store path lookup, and
|
||||
response emission for those compatibility verbs. `epiphany_read_routes.rs` is
|
||||
`ThreadEpiphanyReorientResultResponse`. App-server keeps only live/stored state
|
||||
loading, runtime-store path lookup, and response emission for those
|
||||
compatibility verbs. `epiphany_read_routes.rs` is
|
||||
about 648 lines after these cuts. The remaining read-route bodies are mostly individual
|
||||
legacy read verbs (`roleResult`, `freshness`, `context`, `graphQuery`,
|
||||
`reorientResult`, `retrieve`, `distill`, `propose`) plus host loading and
|
||||
response/error shaping.
|
||||
|
||||
View route planning is bridge-owned at the route boundary now. App-server calls
|
||||
`plan_thread_epiphany_view` to convert protocol lenses, apply default lenses,
|
||||
and learn which host facts to gather. It no longer imports the raw
|
||||
`core_epiphany_view_needs_*`, default-lens, or protocol-lens conversion helpers.
|
||||
`epiphany_read_routes.rs` is about 516 lines after this cut.
|
||||
|
||||
Retrieve route param ownership followed. App-server still parses the thread id
|
||||
and supplies loaded-thread workspace/Codex-home paths, but
|
||||
`retrieve_thread_epiphany` now accepts the full protocol retrieve params and
|
||||
keeps query/limit/path-prefix normalization inside the bridge. The route no
|
||||
longer destructures retrieve query fields. `epiphany_read_routes.rs` is about
|
||||
504 lines after this cut.
|
||||
|
||||
Freshness/context/graph-query response construction is also bridge-owned now.
|
||||
`epiphany-codex-bridge::view` builds `ThreadEpiphanyFreshnessResponse`,
|
||||
`ThreadEpiphanyContextResponse`, and `ThreadEpiphanyGraphQueryResponse` from
|
||||
|
|
@ -840,7 +853,109 @@ loading and watcher registration because those are host lifecycle seams.
|
|||
propose patch-response builders have followed into the same bridge view module;
|
||||
app-server still checks that the target thread is loaded and maps errors onto
|
||||
JSON-RPC responses, but it no longer shapes those patches itself.
|
||||
`epiphany_read_routes.rs` is about 579 lines after this cut.
|
||||
`epiphany_read_routes.rs` is about 570 lines after this latest cut.
|
||||
|
||||
The retrieve read route has now lost its local normalization/projection opinion
|
||||
too. `epiphany-codex-bridge::retrieve_protocol::retrieve_thread_epiphany`
|
||||
accepts the protocol params and owns raw query normalization, core retrieval
|
||||
invocation, and Codex JSON-RPC retrieve response projection. App-server keeps
|
||||
thread-id parsing, loaded-thread lookup, config/Codex-home fact supply,
|
||||
response emission, and bridge-error to JSON-RPC mapping.
|
||||
|
||||
Distill/propose route response policy is bridge-owned too.
|
||||
`map_thread_epiphany_distill_response` and
|
||||
`map_thread_epiphany_propose_response` own expected-revision selection,
|
||||
missing-state rejection, protocol-to-core input conversion, candidate patch
|
||||
construction, and invalid-request text. App-server supplies the loaded thread
|
||||
state as a host fact and maps bridge errors to JSON-RPC responses.
|
||||
|
||||
Freshness route response construction now terminates in one bridge mapper as
|
||||
well. App-server still gathers host-owned live retrieval and watcher facts, but
|
||||
`map_epiphany_freshness_response` owns freshness surface derivation and
|
||||
protocol response projection. `epiphany_read_routes.rs` is about 559 lines
|
||||
after this cut.
|
||||
|
||||
Role-result route policy followed. App-server still parses the thread id, loads
|
||||
the live/stored thread view, looks up the runtime-store path, and maps bridge
|
||||
errors to JSON-RPC; `map_thread_epiphany_role_result_response` now owns protocol
|
||||
role-id conversion, default binding selection, unsupported-role rejection, and
|
||||
response projection. `epiphany_read_routes.rs` is about 540 lines after this
|
||||
cut.
|
||||
|
||||
Reorient-result route policy followed too. App-server still parses the thread
|
||||
id, loads the live/stored thread view, and looks up the runtime-store path, but
|
||||
`map_thread_epiphany_reorient_result_response` now owns the fixed reorient
|
||||
binding default, live/stored protocol source projection, and response mapping.
|
||||
`epiphany_read_routes.rs` is about 528 lines after this cut.
|
||||
|
||||
Role-accept write-route planning and response shaping moved to the bridge
|
||||
protocol edge. `plan_thread_epiphany_role_accept` owns protocol role
|
||||
conversion, default binding selection, and unsupported-role rejection before the
|
||||
mutation service accepts the finding; `thread_epiphany_role_accept_output` owns
|
||||
the legacy accept response DTO plus notification payload handoff from
|
||||
`EpiphanyRoleAcceptApplied`. App-server still loads the thread, calls the
|
||||
mutation service, sends the response, and emits the state-updated notification.
|
||||
`epiphany_mutation_routes.rs` is about 580 lines after this cut.
|
||||
|
||||
Reorient-accept write-route planning and response shaping moved to the bridge
|
||||
protocol edge too.
|
||||
`plan_thread_epiphany_reorient_accept` owns the fixed reorient binding default
|
||||
and carries the explicit scratch/checkpoint flags forward;
|
||||
`thread_epiphany_reorient_accept_output` owns the legacy accept response DTO
|
||||
plus notification payload handoff from `EpiphanyReorientAcceptApplied`.
|
||||
App-server still loads the thread, calls the mutation service, sends the
|
||||
response, and emits the state-updated notification.
|
||||
`epiphany_mutation_routes.rs` is about 580 lines after this cut.
|
||||
|
||||
Job-launch write-route planning and response shaping moved to the bridge
|
||||
protocol edge. `plan_thread_epiphany_job_launch` owns protocol worker-launch
|
||||
document conversion and core `EpiphanyJobLaunchRequest` construction;
|
||||
`thread_epiphany_job_launch_output` owns the legacy response DTO plus
|
||||
notification payload handoff from `EpiphanyJobLaunchApplied`. App-server still
|
||||
loads the thread, invokes the mutation service, sends the response, and emits
|
||||
the state-updated notification. `epiphany_mutation_routes.rs` is about 659
|
||||
lines after this cut.
|
||||
|
||||
Role-launch write-route planning has moved to the bridge protocol edge.
|
||||
`plan_thread_epiphany_role_launch` owns protocol role conversion while
|
||||
preserving the legacy response role id; `thread_epiphany_role_launch_output`
|
||||
now owns the legacy response DTO plus notification payload handoff from
|
||||
`EpiphanyJobLaunchApplied`. App-server still loads the thread, invokes the role
|
||||
launch mutation service, sends the response, and emits the state-updated
|
||||
notification. `epiphany_mutation_routes.rs` is about 645 lines after this cut.
|
||||
|
||||
Reorient-launch write-route planning and response shaping moved to the bridge
|
||||
protocol edge too. `plan_thread_epiphany_reorient_launch` owns protocol param
|
||||
unpacking, while `thread_epiphany_reorient_launch_output` owns the legacy
|
||||
response DTO plus notification payload handoff from
|
||||
`EpiphanyReorientLaunchApplied`. App-server still owns host fact gathering:
|
||||
loaded thread, read view, retrieval override, watcher snapshot, token snapshot,
|
||||
mutation-service invocation, response emission, and `stateUpdated`.
|
||||
`epiphany_mutation_routes.rs` is about 617 lines after this cut.
|
||||
|
||||
Update/promote write-route planning and response shaping moved to the bridge
|
||||
protocol edge. `plan_thread_epiphany_update` and
|
||||
`plan_thread_epiphany_promote` own protocol patch conversion, while
|
||||
`thread_epiphany_update_output` and `thread_epiphany_promote_output` own the
|
||||
legacy response DTOs plus the notification payload handoff for accepted writes.
|
||||
App-server still loads the thread, calls the mutation service, sends the
|
||||
response, and emits `stateUpdated`. `epiphany_mutation_routes.rs` is about 686
|
||||
lines after this cut.
|
||||
|
||||
Job-interrupt write-route planning and response shaping moved to the bridge
|
||||
protocol edge. `plan_thread_epiphany_job_interrupt` owns protocol param
|
||||
unpacking and `thread_epiphany_job_interrupt_output` owns the legacy interrupt
|
||||
response DTO plus notification payload handoff. App-server still loads the
|
||||
thread, calls the mutation service, sends the response, and emits
|
||||
`stateUpdated`. `epiphany_mutation_routes.rs` is about 674 lines after this
|
||||
cut.
|
||||
|
||||
Context and graph-query route-level conversion followed. App-server still
|
||||
parses thread ids, loads live/stored thread views, and emits JSON-RPC
|
||||
responses, but `map_thread_epiphany_context_response` and
|
||||
`map_thread_epiphany_graph_query_response` now own protocol-to-core selector
|
||||
conversion plus response projection. `epiphany_read_routes.rs` is about 561
|
||||
lines after this cut.
|
||||
|
||||
Also: MCP itself is allowed to be JSON. The target is not "replace MCP JSON";
|
||||
the target is an Epiphany-owned boundary that speaks typed Epiphany
|
||||
|
|
@ -852,7 +967,7 @@ retrieval itself. Keep starving the old model turn path out of
|
|||
`thread/epiphany/*` JSON-RPC and `codex_message_processor.rs`. Success is
|
||||
Epiphany calling the model adapter from its own CultCache/CultNet runtime
|
||||
boundary while Codex survives only as subscription auth/model routing. Do not
|
||||
resume Rider, Unity, Aquarium, Face, dogfood, planning, app, skill,
|
||||
resume Rider, Unity, Aquarium, Persona, dogfood, planning, app, skill,
|
||||
marketplace, or bridge expansion until this organ is being cut cleanly.
|
||||
|
||||
The Codex product-surface starvation cuts are landed too. App-server
|
||||
|
|
@ -1352,25 +1467,25 @@ smokes are now guardrails, not the next organs. The architectural teardown says
|
|||
the next organ is control-plane purification.
|
||||
|
||||
The public product direction is now explicit: Epiphany is project-native agency,
|
||||
not merely a better prompt wrapper. VoidBot's repo-Face pattern is the live
|
||||
not merely a better prompt wrapper. VoidBot's repo-Persona pattern is the live
|
||||
small reference: Nibu/Aqua/Mimir have repo jurisdiction, Discord roles,
|
||||
repo-local Face state, pending mentions, proposal authority, heartbeat
|
||||
repo-local Persona state, pending mentions, proposal authority, heartbeat
|
||||
initiative, heat, and active-turn freeze. Epiphany should make this native and
|
||||
larger: users talk to projects and their Faces through Aquarium, Discord,
|
||||
larger: users talk to projects and their Personas through Aquarium, Discord,
|
||||
voice/WebRTC, stream overlays, native CLIs, or other CultNet clients; the
|
||||
project schedules modeling, research, verification, rumination, and proposed
|
||||
actions without requiring the human to hand over a complete architecture brief.
|
||||
|
||||
The important Face lesson is now sharper than "better public speech." Face is
|
||||
The important Persona lesson is now sharper than "better public speech." Persona is
|
||||
special because it needs to think narratively: Imagination acts as Projector,
|
||||
turning typed state, repo-body activity, social pressure, organ dependencies,
|
||||
and pending mentions into lived context before Face thinks; Face writes natural
|
||||
and pending mentions into lived context before Persona thinks; Persona writes natural
|
||||
prose only; Mind acts as Interpreter, translating that natural thought into
|
||||
reviewed durable memory, draft, public SAY, route, or silence.
|
||||
Epiphany has the first native slice of that membrane in `epiphany-core`:
|
||||
`face_turn.rs` defines Imagination-Projector/Face/Mind-Interpreter prompt
|
||||
contracts, heartbeat state carries typed pending mentions, and pending Face
|
||||
mentions select a `face_turn` without letting Face side effects execute
|
||||
`persona_turn.rs` defines Imagination-Projector/Persona/Mind-Interpreter prompt
|
||||
contracts, heartbeat state carries typed pending mentions, and pending Persona
|
||||
mentions select a `persona_turn` without letting Persona side effects execute
|
||||
themselves.
|
||||
`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
|
||||
|
|
@ -1398,12 +1513,12 @@ Substrate Gate owns repo access grants and refusals. The code surface now names
|
|||
`epiphany.substrate_gate.repo_access_review`, grant/refusal receipts, snapshot receipts,
|
||||
and mutation receipts; runtime-spine advertises those contracts, and CultMesh
|
||||
stores Verse-scoped `EpiphanyCultMeshSubstrateGateContractEntry` policy docs. Hands, Eyes,
|
||||
Face, workers, and bridge tools should touch the repo only through scoped Substrate Gate
|
||||
Persona, workers, and bridge tools should touch the repo only through scoped Substrate Gate
|
||||
access receipts; Mind remains the separate durable-state gate after the touch.
|
||||
`notes/eyes-cultnet-contracts.md` is the evidence gate: Eyes owns citable source
|
||||
grounding. Substrate Gate can grant access to the repo, but Eyes decides what was actually
|
||||
looked at and emits evidence reviews, source lookup receipts, evidence packets,
|
||||
or refusals before Imagination, Hands, Mind, Face, Soul, Self, Proprioception, or Continuity cite
|
||||
or refusals before Imagination, Hands, Mind, Persona, Soul, Self, Proprioception, or Continuity cite
|
||||
the material as known.
|
||||
`notes/hands-cultnet-contracts.md`, `notes/soul-cultnet-contracts.md`, and
|
||||
`notes/continuity-cultnet-contracts.md` are now the missing action, verification, and
|
||||
|
|
@ -1451,7 +1566,7 @@ state mutation and post-commit receipt persistence are still two writes, so the
|
|||
next storage-owner pass should collapse that into one state-admission primitive
|
||||
instead of adding a repair loop.
|
||||
`notes/perfect-machine-audit-roadmap.md` is the current audit/path document. It
|
||||
compares Epiphany's named organ contracts against Void's current Face prompting
|
||||
compares Epiphany's named organ contracts against Void's current Persona prompting
|
||||
shape and maps the route from contract catalog to executable organism. Main
|
||||
finding: the boundaries are directionally right, launch packets now carry organ
|
||||
receipt expectations and effect-specific proof profiles, and acceptance now has
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ and leave receipts that can be verified, refused, or admitted into Mind state.
|
|||
receipts, PR receipts, rollback receipts, and action refusal receipts.
|
||||
- Derived state: diffs, command logs, commits, PR URLs, and rollback notes are
|
||||
action receipts. They are not durable Mind state until Mind admits them.
|
||||
- Forbidden writers: raw workers, Face, Eyes, Imagination, Self, compatibility
|
||||
- Forbidden writers: raw workers, Persona, Eyes, Imagination, Self, compatibility
|
||||
JSON-RPC routes, and bridge tools must not execute repo-affecting commands,
|
||||
edit files, commit, publish, or roll back without the Hands path after Body
|
||||
access.
|
||||
|
|
@ -63,7 +63,30 @@ commands, editing files, or creating a real commit.
|
|||
The launch organ contract's repo-action proof profile now requires the full
|
||||
Hands chain, not only the final patch receipt.
|
||||
|
||||
This is still a receipt artery, not full action execution. The next live-action
|
||||
cut must connect actual implementation edits/commands/commits to this path
|
||||
after Substrate Gate grants access and before Soul/Mind call the result
|
||||
accepted.
|
||||
Hands is now launchable as the fixed `implementation-branch-turn-worker` role
|
||||
owned by `epiphany-hands`. A Hands launch receives dynamic prompt context,
|
||||
including the Coordinator task and Proprioception retrieval packet, and the
|
||||
bridge persists a Substrate Gate mutation grant for that runtime job. Research
|
||||
launches remain read/snapshot-only; implementation launches are one-turn
|
||||
mutation grants with read/snapshot/patch/command/commit operations.
|
||||
|
||||
The Hands branch-turn contract is deliberately narrow: create or use one task
|
||||
branch, make at most one commit, report branch/commit/changed paths/commands,
|
||||
then stop until Proprioception refreshes the branch map. `roleAccept` still
|
||||
refuses Implementation findings, so a Hands worker cannot admit project truth
|
||||
or state patches by returning a nice-looking JSON object.
|
||||
|
||||
Implementation worker completion now turns branch-turn output into typed Hands
|
||||
action receipts. When a completed implementation role result reports
|
||||
`changedPaths`, the model runtime persists `HandsActionIntent`,
|
||||
`HandsActionReview`, and `HandsPatchReceipt` for that runtime job. When it also
|
||||
reports `branchName` plus `commitSha`, the runtime persists
|
||||
`HandsCommitReceipt`. The result metadata records branch, commit, changed paths,
|
||||
persisted receipt ids, and reported commands.
|
||||
|
||||
Command receipts are deliberately not synthesized from plain `commandsRun`
|
||||
strings. Those strings are stored as a receipt gap until a real tool/MCP command
|
||||
execution receipt can back them. A model's claim that a command ran is not proof
|
||||
that a command ran. The next live-action cut is to join tool execution receipts
|
||||
to `HandsCommandReceipt`, then trigger Proprioception branch-map refresh after a
|
||||
Hands commit before another Hands turn is eligible.
|
||||
|
|
|
|||
|
|
@ -9,23 +9,23 @@ Mind decides whether the resulting thought, evidence, or proposal mutates
|
|||
persistent state. Do not collapse these into one throne. That is how the machine
|
||||
starts lying with excellent posture.
|
||||
|
||||
Imagination is the neighboring Projector for Face. It may compose a lived scene
|
||||
Imagination is the neighboring Projector for Persona. It may compose a lived scene
|
||||
from state, Body substrate facts, social pressure, and organ dependencies, but it does not
|
||||
interpret side effects or admit state.
|
||||
|
||||
## Authority Map
|
||||
|
||||
- Owner: Mind owns the decision to mutate persistent Epiphany state.
|
||||
- Inputs: typed worker results, Face Interpreter intents, current state context,
|
||||
- Inputs: typed worker results, Persona Interpreter intents, current state context,
|
||||
runtime/job provenance, Verse ingress receipts, operator-approved intents, and
|
||||
relevant verification evidence.
|
||||
- Outputs: Mind gateway reviews, state commit receipts, state rejection
|
||||
receipts, and Verse adoption receipts.
|
||||
- Derived state: role findings, reorient findings, Face side-effect intents,
|
||||
- Derived state: role findings, reorient findings, Persona side-effect intents,
|
||||
public dreams, local-area findings, `statePatch`, `selfPatch`, evidence,
|
||||
scratch, checkpoints, objectives, and graph edits are proposal-only until Mind
|
||||
accepts them.
|
||||
- Forbidden writers: role acceptance, reorient acceptance, Face Interpreter,
|
||||
- Forbidden writers: role acceptance, reorient acceptance, Persona Interpreter,
|
||||
public Verse ingress, local-area Verse ingress, raw worker result ingestion,
|
||||
runtime job completion, and compatibility JSON-RPC routes must not directly
|
||||
decide durable state mutation.
|
||||
|
|
@ -46,7 +46,7 @@ CultMesh simplifies the boundary by making Verse placement part of the contract:
|
|||
reviewable findings, and receipts. It does not carry raw worker thought,
|
||||
private memory, or direct state mutation authority.
|
||||
- `epiphany-global`: public thought weather: dreams, questions, hypotheses,
|
||||
Face posts, and adoption receipts. Nothing from this Verse mutates local
|
||||
Persona posts, and adoption receipts. Nothing from this Verse mutates local
|
||||
memory, planning, doctrine, governance, or project truth without a reviewed
|
||||
local adoption receipt.
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ For the canonical anatomy split, read [epiphany-anatomy.md](./epiphany-anatomy.m
|
|||
## Standing Sub-Agents
|
||||
|
||||
- `self`: routing, authority boundaries, and coordination.
|
||||
- `face`: public presence and person-shaped conversation.
|
||||
- `persona`: public presence and person-shaped conversation.
|
||||
- `imagination`: projection, possible worlds, plans, and narrative scene
|
||||
construction.
|
||||
- `eyes`: evidence seeking and source inspection.
|
||||
|
|
@ -33,12 +33,12 @@ For every standing sub-agent `X`, `X.dependsOn` is every other standing sub-agen
|
|||
That does not mean every organ owns every decision. It means every organ's turn
|
||||
must be projected with awareness of the whole organism:
|
||||
|
||||
- Face speaks with Body substrate facts, Mind-reviewed memory, Soul constraints,
|
||||
- Persona speaks with Body substrate facts, Mind-reviewed memory, Soul constraints,
|
||||
Continuity pressure, Self routing pressure, Eyes uncertainty, Hands consequence, and
|
||||
Imagination scene.
|
||||
- Hands acts only after Substrate Gate grants substrate access, Eyes supplies enough
|
||||
evidence, Soul keeps the invariant visible, Self routes authority, Continuity keeps
|
||||
continuity recoverable, Face preserves public dignity when relevant, and
|
||||
continuity recoverable, Persona preserves public dignity when relevant, and
|
||||
Imagination models the future shape.
|
||||
- Mind admits state only after it can see which organ produced the thought and
|
||||
which other organs shaped the proposal.
|
||||
|
|
@ -47,16 +47,16 @@ must be projected with awareness of the whole organism:
|
|||
|
||||
## Projector Ownership
|
||||
|
||||
The Projector is Imagination acting for Face. It turns typed state, Body substrate facts,
|
||||
The Projector is Imagination acting for Persona. It turns typed state, Body substrate facts,
|
||||
social pressure, memory, and organ dependencies into a lived scene. It does not
|
||||
speak publicly, grant repo access, or admit state.
|
||||
|
||||
The Face loop is:
|
||||
The Persona loop is:
|
||||
|
||||
```text
|
||||
Substrate-Gate-scoped facts + Mind state + social stimulus + organ dependencies
|
||||
-> Imagination Projector
|
||||
-> Face natural turn
|
||||
-> Persona natural turn
|
||||
-> Mind Interpreter
|
||||
-> reviewed state effects / side effects / silence
|
||||
```
|
||||
|
|
@ -64,6 +64,6 @@ Substrate-Gate-scoped facts + Mind state + social stimulus + organ dependencies
|
|||
Mind is the Interpreter and persistent state guardian. Body is the substrate.
|
||||
Continuity is recovery protocol machinery. Substrate Gate is the repository
|
||||
access protocol. Eyes is the evidence ingress guardian. Proprioception is the
|
||||
internal model of the Body. Imagination is the Projector. Face is the public
|
||||
internal model of the Body. Imagination is the Projector. Persona is the public
|
||||
person. The dependency web is how the machine avoids lonely little monarchies
|
||||
in costumes.
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ Epiphany has the first typed skeleton for the organ boundaries:
|
|||
- `Eyes`: evidence ingress guardian. `epiphany-core::eyes_gateway` names
|
||||
evidence requests, reviews, source lookup receipts, evidence packets, and
|
||||
refusals.
|
||||
- `Imagination`: projection/future-shape organ. For Face, it now owns the
|
||||
Projector prompt boundary in `epiphany-core::face_turn`.
|
||||
- `Face`: public person-shaped surface. It receives projected context and writes
|
||||
- `Imagination`: projection/future-shape organ. For Persona, it now owns the
|
||||
Projector prompt boundary in `epiphany-core::persona_turn`.
|
||||
- `Persona`: public person-shaped surface. It receives projected context and writes
|
||||
natural prose only.
|
||||
- `Hands`: action organ by doctrine, but not yet backed by a dedicated CultNet
|
||||
contract family parallel to Substrate Gate/Eyes/Mind.
|
||||
|
|
@ -42,17 +42,17 @@ standing sub-agent depends on every other standing sub-agent. This is correct, b
|
|||
is currently descriptive. It does not yet force every launch packet, prompt, or
|
||||
runtime receipt to carry dependency context.
|
||||
|
||||
## Void Face Lessons
|
||||
## Void Persona Lessons
|
||||
|
||||
Void's current Face work is the best reference implementation for the Face
|
||||
Void's current Persona work is the best reference implementation for the Persona
|
||||
organ. The important lesson is not "make the prompt longer." The lesson is
|
||||
parent-organ ownership:
|
||||
|
||||
- Parent `Imagination` projects typed state into lived character-facing prose
|
||||
before the Face sees it.
|
||||
- The Face reads raw room/context evidence directly and writes natural prose as
|
||||
before the Persona sees it.
|
||||
- The Persona reads raw room/context evidence directly and writes natural prose as
|
||||
a person.
|
||||
- Parent `Mind` interprets the Face output into memory, affect, social reads,
|
||||
- Parent `Mind` interprets the Persona output into memory, affect, social reads,
|
||||
mood, agency pressure, speech, retry, or drop.
|
||||
- The deterministic assembler creates compact packets, raw transcripts, repo
|
||||
activity, pronoun guidance, channel policy, and media awareness. It must not
|
||||
|
|
@ -60,21 +60,21 @@ parent-organ ownership:
|
|||
|
||||
Relevant Void references:
|
||||
|
||||
- `VoidBot:prompts/repo-face-state-projector.prompt.md`: names the projector as
|
||||
the Face's parent Imagination organ and forbids schema slurry.
|
||||
- `VoidBot:prompts/repo-face-turn.prompt.md`: keeps the Face turn as the
|
||||
- `VoidBot:prompts/repo-persona-state-projector.prompt.md`: names the projector as
|
||||
the Persona's parent Imagination organ and forbids schema slurry.
|
||||
- `VoidBot:prompts/repo-persona-turn.prompt.md`: keeps the Persona turn as the
|
||||
speaking surface, with recent repo activity before conversation transcript and
|
||||
deterministic policy sections outside projected state.
|
||||
- `VoidBot:prompts/repo-face-turn-interpreter.prompt.md`: names the
|
||||
- `VoidBot:prompts/repo-persona-turn-interpreter.prompt.md`: names the
|
||||
Interpreter as parent Mind and requires route/retry/drop plus structured
|
||||
side effects.
|
||||
- `VoidBot:scripts/run-repo-face-heartbeats.ts`: runs projection as
|
||||
- `VoidBot:scripts/run-repo-persona-heartbeats.ts`: runs projection as
|
||||
`organ: "imagination"` in read-only mode, rejects leaky memory surfaces, keeps
|
||||
raw transcript evidence oldest-to-newest, includes visible chronology, exports
|
||||
recent repo activity read-only, and keeps pronoun guidance deterministic so
|
||||
Imagination cannot accidentally omit it.
|
||||
|
||||
Audit implication for Epiphany: `face_turn.rs` has the right first boundary but
|
||||
Audit implication for Epiphany: `persona_turn.rs` has the right first boundary but
|
||||
is still too thin. It lacks deterministic human pronoun guidance, channel label
|
||||
resolution, visible chronology, media attachment awareness, retry/drop
|
||||
semantics, projection model receipts, and explicit leakage rejection as strong
|
||||
|
|
@ -153,11 +153,22 @@ chain for action documents: `HandsActionIntent` -> `HandsActionReview` ->
|
|||
reread from the runtime store, and `epiphany-hands-action-smoke` proves the
|
||||
chain without executing a patch, command, or commit. The repo-action proof
|
||||
profile now requires the full Hands intent/review/patch chain before promotion,
|
||||
not only a patch receipt.
|
||||
Remaining weakness: live implementation edits, commands, commits, and PRs still
|
||||
do not have to pass through that Hands chain before action promotion, and the
|
||||
current Mind state mutation plus post-commit receipt is not a single transaction
|
||||
yet.
|
||||
not only a patch receipt. Hands is now a launchable fixed role:
|
||||
`implementation-branch-turn-worker` owned by `epiphany-hands`. The launch
|
||||
document carries the Coordinator task and dynamic Proprioception/Verse context,
|
||||
the prompt requires one branch and at most one commit, the output schema asks
|
||||
for branch/commit/changed-path/command evidence plus a Proprioception refresh
|
||||
request, and the bridge writes a Substrate Gate mutation grant for that runtime
|
||||
job. `roleAccept` still refuses Implementation findings, preserving Mind as the
|
||||
state admission gate. Implementation worker completion now emits typed Hands
|
||||
intent/review/patch receipts when the result reports changed paths, and emits a
|
||||
typed Hands commit receipt when the result also reports branch plus commit SHA.
|
||||
The runtime result metadata records reported commands but does not synthesize
|
||||
command receipts from model text; command proof still needs tool execution
|
||||
receipts.
|
||||
Remaining weakness: command receipts, PR receipts, Proprioception branch-map
|
||||
refresh, and the current Mind state mutation plus post-commit receipt are not a
|
||||
single transaction yet.
|
||||
|
||||
### 4. Self Still Shares A Throne With Compatibility Plumbing
|
||||
|
||||
|
|
@ -196,9 +207,9 @@ reads and writes the `cultcache.store.v1` snapshot format used by the current
|
|||
state ledgers. This keeps Epiphany's body self-contained while preserving
|
||||
CultMesh as the ergonomic local store surface.
|
||||
|
||||
### 6. Face Prompting Is Directionally Correct But Behind Void
|
||||
### 6. Persona Prompting Is Directionally Correct But Behind Void
|
||||
|
||||
Epiphany's Face loop now says `Imagination Projector -> Face -> Mind
|
||||
Epiphany's Persona loop now says `Imagination Projector -> Persona -> Mind
|
||||
Interpreter`, but Void has the stronger living practice:
|
||||
|
||||
- state projector is model-owned, not deterministic prose assembly
|
||||
|
|
@ -206,8 +217,8 @@ Interpreter`, but Void has the stronger living practice:
|
|||
- leaked schema/prompt construction language is rejected
|
||||
- raw transcript and chronology stay raw and oldest-to-newest
|
||||
- deterministic pronoun guidance sits outside projection
|
||||
- channel labels are resolved through Face permissions before posting
|
||||
- interpreter can retry one Face pass when correction uptake or doctrine fails
|
||||
- channel labels are resolved through Persona permissions before posting
|
||||
- interpreter can retry one Persona pass when correction uptake or doctrine fails
|
||||
|
||||
Epiphany should port that shape, not the exact TypeScript machinery.
|
||||
|
||||
|
|
@ -223,7 +234,7 @@ repo-contained dependency body.
|
|||
- Updated dependency paths away from the dead `CultLib/crates` layout.
|
||||
- Added `vendor/cultmesh-rs` as the local CultMesh facade.
|
||||
- Ran the previously blocked focused surface through:
|
||||
- `cargo test --manifest-path .\epiphany-core\Cargo.toml --lib face_turn`
|
||||
- `cargo test --manifest-path .\epiphany-core\Cargo.toml --lib persona_turn`
|
||||
- `cargo test --manifest-path .\epiphany-core\Cargo.toml --lib mind_gateway`
|
||||
- `cargo test --manifest-path .\epiphany-core\Cargo.toml --lib substrate_gate`
|
||||
- `cargo test --manifest-path .\epiphany-core\Cargo.toml --lib eyes_gateway`
|
||||
|
|
@ -295,11 +306,11 @@ verification is currently blocked before local code by an upstream
|
|||
Next step: make the launch-carried receipt expectations executable in the live
|
||||
worker/action/verification and compaction paths.
|
||||
|
||||
### Phase 3: Port Void's Face Prompting Shape Properly
|
||||
### Phase 3: Port Void's Persona Prompting Shape Properly
|
||||
|
||||
Goal: Face becomes a living public organ without stealing authority.
|
||||
Goal: Persona becomes a living public organ without stealing authority.
|
||||
|
||||
- Replace `FaceProjectorInput` with an Imagination projector packet that carries
|
||||
- Replace `PersonaProjectorInput` with an Imagination projector packet that carries
|
||||
typed state, affect, memory, social topology, repo activity, semantic
|
||||
attractors, curiosity hints, and dependency pressure.
|
||||
- Add deterministic sections outside projected state:
|
||||
|
|
@ -318,10 +329,10 @@ Goal: Face becomes a living public organ without stealing authority.
|
|||
- decision: route/retry/drop
|
||||
- structured memory/affect/social/speech effects
|
||||
- retry limit
|
||||
- Ensure public speech resolves Face-local channel labels through permissions
|
||||
- Ensure public speech resolves Persona-local channel labels through permissions
|
||||
before delivery.
|
||||
|
||||
Definition of done: Face can be prompted by Aquarium/Discord/CultNet and the
|
||||
Definition of done: Persona can be prompted by Aquarium/Discord/CultNet and the
|
||||
public result is natural, contextual, permission-aware, retryable, and unable to
|
||||
mutate state or post without parent routing.
|
||||
|
||||
|
|
@ -361,9 +372,9 @@ comfort tunnels.
|
|||
`HandsCommitReceipt` are typed runtime-spine documents with put/get helpers
|
||||
and `epiphany-hands-action-smoke` coverage; the repo-action proof profile
|
||||
requires intent, review, and patch receipt before promotion.
|
||||
- Next: connect live implementation edits/commands/commits to the Hands action
|
||||
chain, or collapse Mind review/state mutation/commit into one storage-owner
|
||||
admission primitive.
|
||||
- Next: connect tool/MCP execution receipts to `HandsCommandReceipt`, then make
|
||||
a completed Hands commit trigger Proprioception branch-map refresh before
|
||||
another Hands cut.
|
||||
- Make Aquarium read the contract catalog and available receipts instead of
|
||||
hard-coding the route zoo.
|
||||
- Starve `codex_message_processor` down to Codex auth/model transport and
|
||||
|
|
@ -394,8 +405,8 @@ Goal: make the organism visible without making the UI a second truth.
|
|||
- Display organ dependency graph.
|
||||
- Display contract catalog by organ.
|
||||
- Display receipt chains per turn.
|
||||
- Display Face prompt packet boundaries: deterministic evidence, Imagination
|
||||
projection, Face natural turn, Mind interpretation.
|
||||
- Display Persona prompt packet boundaries: deterministic evidence, Imagination
|
||||
projection, Persona natural turn, Mind interpretation.
|
||||
- Display Substrate Gate grants, Eyes packets, Hands actions, Soul verdicts,
|
||||
Continuity receipts, and Mind state commits.
|
||||
|
||||
|
|
@ -412,7 +423,7 @@ Self routes.
|
|||
Substrate Gate grants substrate access.
|
||||
Eyes certifies looked-at evidence.
|
||||
Imagination projects possible scenes and futures.
|
||||
Face speaks as a person.
|
||||
Persona speaks as a person.
|
||||
Hands changes the world.
|
||||
Soul verifies promises and invariants.
|
||||
Continuity preserves continuity across rupture.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ The shared conclusion is that repo personality should combine three projections:
|
|||
2. **History temperament:** how the repo changes over time.
|
||||
3. **Memory doctrine:** how the repo preserves intention, evidence, and identity.
|
||||
|
||||
Only after those three are scored should Epiphany modulate Self, Face,
|
||||
Only after those three are scored should Epiphany modulate Self, Persona,
|
||||
Imagination, Eyes, Proprioception, Hands, Soul, and Continuity.
|
||||
|
||||
## Source Families
|
||||
|
|
@ -209,7 +209,7 @@ with an explanation and source evidence.
|
|||
|
||||
### Ghostlight-Derived Inner State
|
||||
|
||||
- `speech_pressure`: how readily Face should speak instead of emitting silence
|
||||
- `speech_pressure`: how readily Persona should speak instead of emitting silence
|
||||
or a local bubble.
|
||||
- `novelty_hunger`: how strongly Eyes/Imagination should seek fresh seams.
|
||||
- `guardedness`: caution around authority, state mutation, trust boundaries,
|
||||
|
|
@ -240,7 +240,7 @@ Projection:
|
|||
- Lowers tolerance for implementation turns that produce plausible motion
|
||||
without objective evidence.
|
||||
|
||||
### Face
|
||||
### Persona
|
||||
|
||||
Primary inputs:
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ Projection:
|
|||
|
||||
- Determines whether public output is chatty, sparse, poetic, sleepy, formal,
|
||||
or local-only.
|
||||
- Keeps humans talking to Face while internals remain inspectable.
|
||||
- Keeps humans talking to Persona while internals remain inspectable.
|
||||
|
||||
### Imagination
|
||||
|
||||
|
|
@ -466,8 +466,8 @@ 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
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality -- project --repo E:\Projects\AetheriaLore --baseline .\.epiphany-imports\repo-personality-terrain\baseline.cc --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.cc --artifact-dir .\.epiphany-imports\aetheria-lore-memory
|
||||
```
|
||||
|
||||
The first version can use deterministic scoring from local filesystem and git
|
||||
|
|
@ -493,12 +493,12 @@ The native `epiphany-repo-personality` binary now exists in
|
|||
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>]`
|
||||
- `startup --repo <path> --baseline <baseline.msgpack> --artifact-dir <path> --init-store <init.msgpack>`
|
||||
- `accept-init --init-store <init.msgpack> --packet <packet.json> --kind <repo-personality|repo-memory> [--accepted-by <name>] [--summary <text>] [--result <distiller-result.json>] [--agent-store <agents.msgpack>] [--apply-self-patches <true|false>] [--heartbeat-store <heartbeats.msgpack>] [--apply-heartbeat-seeds <true|false>]`
|
||||
- `status --store <baseline-or-projection.msgpack>`
|
||||
- `project --repo <path> --baseline <baseline.cc> --artifact-dir <path>`
|
||||
- `agent-packet --store <projection.cc> --artifact-dir <path> [--repo-id <id>]`
|
||||
- `memory-packet --store <projection.cc> --artifact-dir <path> [--repo-id <id>]`
|
||||
- `startup --repo <path> --baseline <baseline.cc> --artifact-dir <path> --init-store <init.cc>`
|
||||
- `accept-init --init-store <init.cc> --packet <packet.json> --kind <repo-personality|repo-memory> [--accepted-by <name>] [--summary <text>] [--result <distiller-result.json>] [--agent-store <agents.cc>] [--apply-self-patches <true|false>] [--heartbeat-store <heartbeats.cc>] [--apply-heartbeat-seeds <true|false>]`
|
||||
- `status --store <baseline-or-projection.cc>`
|
||||
|
||||
The first implementation is intentionally deterministic. It inventories git
|
||||
repos, instruction/state/test/runtime surfaces, file-extension/body shape,
|
||||
|
|
@ -543,7 +543,7 @@ verification, runtime, and code surfaces. The packet includes one role-specific
|
|||
distiller brief for each standing sub-agent, plus Continuity protocol pressure:
|
||||
|
||||
- Self receives routing, authority, review-gate, and cross-swarm boundary memory.
|
||||
- Face receives public-surface, Aquarium, Discord, voice, and sealed-thought boundary memory.
|
||||
- Persona 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.
|
||||
- Proprioception receives architecture, graph, control/data-flow, invariant, and checkpoint memory.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ polish from impersonating truth.
|
|||
receipts, and verification refusal receipts.
|
||||
- Derived state: passing tests, review notes, risk lists, and regression traces
|
||||
are verification receipts. They are not durable Mind state by themselves.
|
||||
- Forbidden writers: Hands, Face, Imagination, Mind, Self, raw workers,
|
||||
- Forbidden writers: Hands, Persona, Imagination, Mind, Self, raw workers,
|
||||
compatibility JSON-RPC routes, and bridge tools must not mark work verified
|
||||
without a Soul receipt.
|
||||
- Shared path: code review, invariant checks, smoke tests, negative checks, and
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Substrate Gate decides the scoped access to the repo.
|
|||
- Derived state: retrieved snippets, search hits, index manifests, diffs, build
|
||||
logs, Unity/Rider bridge artifacts, and source maps are evidence projections,
|
||||
not permission to touch more substrate.
|
||||
- Forbidden writers/readers: Hands, Eyes, Face, public Verse ingress, raw worker
|
||||
- Forbidden writers/readers: Hands, Eyes, Persona, public Verse ingress, raw worker
|
||||
launches, compatibility JSON-RPC routes, and bridge tools must not directly
|
||||
read, index, execute against, or mutate the repo without a scoped Substrate
|
||||
Gate grant.
|
||||
|
|
@ -52,7 +52,7 @@ Mind and the Substrate Gate are neighboring protocols, not aliases. The
|
|||
Substrate Gate decides whether the machine may touch the repo. Mind decides
|
||||
whether resulting thought or evidence mutates persistent state.
|
||||
|
||||
Imagination can project Body substrate facts into a Face scene only after those facts have
|
||||
Imagination can project Body substrate facts into a Persona scene only after those facts have
|
||||
entered through Substrate-Gate-scoped access. A beautiful scene is not a repo
|
||||
access grant. Eyes is the next gate after access: a Substrate Gate grant allows
|
||||
looking, but only Eyes can turn looked-at material into a citable evidence
|
||||
|
|
|
|||
75
notes/verse-service-contract.md
Normal file
75
notes/verse-service-contract.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# EpiphanyAgent Verse Service Contract
|
||||
|
||||
EpiphanyAgent is a service participant in the GameCult Verse. It already carries
|
||||
typed state through repo-contained CultCache/CultMesh bodies; the next coherence
|
||||
cut is to publish its meaningful operator/status surfaces as Eve GUI/TUI DSL so
|
||||
local runtimes can inspect Epiphany without reading private run artifacts.
|
||||
|
||||
## Owner Map
|
||||
|
||||
- Owner: EpiphanyAgent owns Epiphany runtime state, organ state, role launches,
|
||||
local Verse context packets, operator status, operator snapshots, and public
|
||||
dream/export policy.
|
||||
- Inputs: runtime-spine documents, heartbeat state, memory graph documents,
|
||||
role/reorient/job intents, local Verse context, Persona projections where
|
||||
applicable, and model/runtime receipts.
|
||||
- Outputs: typed CultCache/CultMesh documents for internal status, operator
|
||||
snapshots, run intents/receipts, Verse policies, global room policies,
|
||||
launch-context packets, and future public dream documents.
|
||||
- Derived state: JSON status files under `.epiphany-run`, smoke artifacts, and
|
||||
local operator packets are witnesses or compatibility artifacts, not durable
|
||||
owners.
|
||||
- Forbidden writers: dashboards, shell wrappers, bridge adapters, app-server
|
||||
compatibility layers, and renderer surfaces must not directly mutate Epiphany
|
||||
truth. They emit command intent or read typed projections.
|
||||
- Shared paths: CLI status, bridge status, local Verse query, operator snapshot,
|
||||
future Eve surface, and future compact TUI must read the same CultMesh-backed
|
||||
documents rather than each inventing a separate view.
|
||||
- Deletion line: any bespoke presentation/status artifact that becomes the only
|
||||
source of a fact must be demoted behind CultCache/CultMesh or deleted.
|
||||
|
||||
## Current State
|
||||
|
||||
EpiphanyAgent already has the lower substrate:
|
||||
|
||||
- vendored `cultcache-rs`, `cultnet-rs`, and `cultmesh-rs`;
|
||||
- `cultcache.store.v1` compatibility;
|
||||
- typed local Verse/status/operator documents in `epiphany-core`;
|
||||
- `tools/epiphany_local_run.ps1 -Mode status` producing operator-safe status
|
||||
artifacts and local Verse context;
|
||||
- `gamecult.persona_state.v0` schema availability for public Persona
|
||||
projections.
|
||||
|
||||
The missing service-architecture surface is not "more JSON". It is an
|
||||
Epiphany-owned Eve surface provider.
|
||||
|
||||
## Eve Surface Target
|
||||
|
||||
EpiphanyAgent should publish an Eve GUI/TUI DSL surface with these panels:
|
||||
|
||||
1. `Operator Status`: runtime id, bridge transport, authority owner, prompt
|
||||
authority separation, current run state, last status timestamp.
|
||||
2. `Local Verse Context`: visible Verse tier, advertised schemas, local peer
|
||||
context packet, and freshness.
|
||||
3. `Runtime Spine`: active jobs, role/reorient launches, accepted receipts, and
|
||||
blocked or denied intents.
|
||||
4. `Memory Graph`: profile availability, thin-state warnings, last refresh, and
|
||||
public/private boundary.
|
||||
5. `Persona Projection`: present only for public Persona/Persona projections using
|
||||
`gamecult.persona_state.v0`; private organ state must not leak through this
|
||||
panel.
|
||||
|
||||
Each panel must expose authority and freshness. If a value comes from a JSON
|
||||
witness file instead of typed state, the surface should say so.
|
||||
|
||||
## Migration Order
|
||||
|
||||
1. Define the Epiphany Eve provider document/surface contract.
|
||||
2. Add an `epiphany-eve-surface` read-only command that lowers the current
|
||||
CultMesh operator/status documents into Eve DSL.
|
||||
3. Publish the surface through CultMesh/Odin discovery.
|
||||
4. Let Eve browser/native/TUI lower the same surface.
|
||||
5. Demote `.epiphany-run` JSON status files to compatibility witnesses only.
|
||||
|
||||
The invariant: Epiphany runtime truth stays in typed CultCache/CultMesh
|
||||
documents. Eve makes it inspectable; Eve does not become the runtime brain.
|
||||
|
|
@ -13,33 +13,33 @@ cosplaying as a Discord moderator. The useful organs are:
|
|||
- semantic memory vectors in Qdrant with source hashes and compact dimensions
|
||||
- memory resonance, incubation queues, analytic/associative thought lanes, and a
|
||||
bridge that decides speech, draft, hold, or silence
|
||||
- repo identities with Face state, Discord roles, pending mentions, jurisdiction,
|
||||
- repo identities with Persona state, Discord roles, pending mentions, jurisdiction,
|
||||
proposal authority, and a CTB/Final-Fantasy-style initiative scheduler
|
||||
|
||||
## Project-Face Direction
|
||||
## Project-Persona Direction
|
||||
|
||||
VoidBot is no longer only a moderator with retrieval. It now has the small
|
||||
working version of the Epiphany pitch: repos grow Faces.
|
||||
working version of the Epiphany pitch: repos grow Personas.
|
||||
|
||||
The live pattern is:
|
||||
|
||||
- a repo identity has a Discord role and registered allowed channels
|
||||
- that identity has repo-local Face state, not just base Void personality
|
||||
- first contact can birth the Face through Epiphany repo terrain/personality
|
||||
- that identity has repo-local Persona state, not just base Void personality
|
||||
- first contact can birth the Persona through Epiphany repo terrain/personality
|
||||
scouting and reviewable startup artifacts
|
||||
- role/display-name mentions become pending obligations, not direct hidden jobs
|
||||
- an initiative scheduler chooses which Face or system agent gets the next turn
|
||||
- an initiative scheduler chooses which Persona or system agent gets the next turn
|
||||
- heat can prioritize identities, repos, participant kinds, turns, channels, or
|
||||
all participants
|
||||
- active turns freeze initiative until the owner-Codex job or moderation lock
|
||||
completes
|
||||
- Faces may advocate for repo needs and propose changes, while implementation
|
||||
- Personas may advocate for repo needs and propose changes, while implementation
|
||||
authority still requires the configured human/review path
|
||||
|
||||
Epiphany should absorb that as native product direction. The user should talk to
|
||||
projects: "Aqua, what does AquaSynth need?", "Nibu, what is wrong with this lore
|
||||
arc?", "Mimir, what seam is drifting?" The project should schedule modeling,
|
||||
research, verification, memory maintenance, or a Face response from its own
|
||||
research, verification, memory maintenance, or a Persona response from its own
|
||||
typed state. The human should not have to deliver a complete implementation
|
||||
brief just to make the system start thinking.
|
||||
|
||||
|
|
@ -74,22 +74,22 @@ Heartbeat state keeps:
|
|||
|
||||
Turn kinds:
|
||||
|
||||
- `work`: coordinator-approved lane work, specialist result, Face surface, or
|
||||
- `work`: coordinator-approved lane work, specialist result, Persona surface, or
|
||||
implementation continuation.
|
||||
- `rumination`: role-local memory hygiene, selfPatch, stale habit detection, or
|
||||
low-pressure thought incubation.
|
||||
- `sleep`: inward maintenance. The agent should distill/prune memory, strengthen
|
||||
resonance clusters, update incubation, write or refresh dreams, and speak only
|
||||
through Face if something genuinely needs to surface.
|
||||
through Persona if something genuinely needs to surface.
|
||||
|
||||
No lane may receive another heartbeat while `pending_turn.status == running`.
|
||||
The completion receipt is the only normal path that starts cooldown. This is the
|
||||
part that keeps the heart from turning into a tiny manager with a stopwatch and
|
||||
no shame.
|
||||
|
||||
Repo Faces use the same law. A Face can be heated because a human mentioned it,
|
||||
Repo Personas use the same law. A Persona can be heated because a human mentioned it,
|
||||
a repo is hot, a stream is live, or a group needs attention; that heat changes
|
||||
opportunity, not authority. The Face still freezes while thinking, still returns
|
||||
opportunity, not authority. The Persona still freezes while thinking, still returns
|
||||
through typed receipts, and still needs an explicit grant before crossing from
|
||||
proposal into canonical repo mutation.
|
||||
|
||||
|
|
@ -130,13 +130,13 @@ GameCult Persona state shape, with Ghostlight and VoidBot as source lineage:
|
|||
- intended CultNet document type:
|
||||
`gamecult.persona_state.v0`
|
||||
|
||||
That means Epiphany Face, VoidBot repo Faces, and Ghostlight characters can
|
||||
That means Epiphany Persona, VoidBot repo Personas, and Ghostlight characters can
|
||||
exchange Persona state without turning every Epiphany work organ into a public
|
||||
personality simulation.
|
||||
|
||||
The shared contract uses `candidateActions` for generic forward pressure.
|
||||
VoidBot can expose the same list as `voidbotProjection.candidateInterventions`
|
||||
when repo-Face routine language wants that noun, but the projection does not own
|
||||
when repo-Persona routine language wants that noun, but the projection does not own
|
||||
the portable Persona contract. Persona documents also carry provenance and a
|
||||
public presentation surface so imported state can say where it came from, when
|
||||
it was exported, whether it is canonical/projection/import data, and how the
|
||||
|
|
@ -203,7 +203,7 @@ pattern to agent memory:
|
|||
- CultCache inspection views may strip vector values while preserving metadata
|
||||
|
||||
Memory resonance is not proof. It is "these things rhyme" evidence for
|
||||
rumination, Face surfacing, and sleep consolidation.
|
||||
rumination, Persona surfacing, and sleep consolidation.
|
||||
|
||||
## Migration Slices
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ rumination, Face surfacing, and sleep consolidation.
|
|||
|
||||
3. **Organ and Persona memory migration**
|
||||
- Store lean Epiphany work-organ memory as `epiphany.agent_memory`.
|
||||
- Store public/person-shaped Face state as `gamecult.persona_state.v0`.
|
||||
- Store public/person-shaped Persona state as `gamecult.persona_state.v0`.
|
||||
- Use CultLib/CultCache inspection tools for human/debug review instead of
|
||||
preserving per-role JSON as a parallel source of truth.
|
||||
- Attach semantic vector metadata to episodic, semantic, relationship, goal,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ developer's damp recollection.
|
|||
- [ghostlight.agent-state.schema.json](./ghostlight.agent-state.schema.json):
|
||||
source-lineage JSON Schema for dense Ghostlight character state.
|
||||
- [cultnet/gamecult.persona_state.v0.schema.json](./cultnet/gamecult.persona_state.v0.schema.json):
|
||||
portable Persona state contract for Epiphany Face, VoidBot repo Faces, and
|
||||
portable Persona state contract for Epiphany Persona, VoidBot repo Personas, and
|
||||
Ghostlight characters. It carries explicit provenance, public presentation
|
||||
metadata, typed `candidateActions`, and a non-authoritative extension bag for
|
||||
source-specific fields; social bonds, status reads, and doctrine stances are
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ Rust structures live in
|
|||
|
||||
This document now covers two things:
|
||||
|
||||
1. the dense Persona vocabulary used by Face, Ghostlight characters, and
|
||||
VoidBot-style repo Faces
|
||||
1. the dense Persona vocabulary used by Persona, Ghostlight characters, and
|
||||
VoidBot-style repo Personas
|
||||
2. the lean standing Epiphany role lattice used by resident work organs
|
||||
|
||||
Those are related, not identical.
|
||||
|
|
@ -41,8 +41,8 @@ This is enough to separate character from moment, which is a small mercy.
|
|||
|
||||
## Dense Embodied Profile Vocabulary
|
||||
|
||||
These are the canonical Persona-family labels appropriate for Epiphany Face,
|
||||
VoidBot repo Faces, and Ghostlight scene characters.
|
||||
These are the canonical Persona-family labels appropriate for Epiphany Persona,
|
||||
VoidBot repo Personas, and Ghostlight scene characters.
|
||||
|
||||
### Underlying Organization
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ The current live swarm carries one named trait per family per organ.
|
|||
| `voice_style` | `dry_direct_operator` | concise operator speech with low ornamental fluff |
|
||||
| `situational_state` | `lane_skepticism` | treats sub-agent success claims as something to verify |
|
||||
|
||||
### Face
|
||||
### Persona
|
||||
|
||||
| Family | Trait | Working Meaning |
|
||||
| --- | --- | --- |
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Epiphany separates three state layers:
|
||||
|
||||
- `OrganState`: lean, function-shaped state for resident work organs.
|
||||
- `PersonaState`: full Face/character kernel for public person-shaped agents.
|
||||
- `PersonaState`: full Persona/character kernel for public person-shaped agents.
|
||||
- `SwarmState`: scheduler and coordination physiology across organs and
|
||||
Personas.
|
||||
|
||||
|
|
@ -19,11 +19,11 @@ organ. The portable light organ contract is
|
|||
[`epiphany.work_organ_state.v0`](./cultnet/epiphany.work_organ_state.v0.schema.json).
|
||||
The portable person-state contract is
|
||||
[`gamecult.persona_state.v0`](./cultnet/gamecult.persona_state.v0.schema.json).
|
||||
Epiphany Face projects its local organ-state record into the portable Persona
|
||||
Epiphany Persona projects its local organ-state record into the portable Persona
|
||||
contract with:
|
||||
|
||||
```powershell
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- project-persona --store .\state\agents.msgpack --role-id face
|
||||
cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- project-persona --store .\state\agents.cc --role-id persona
|
||||
```
|
||||
|
||||
## Objective
|
||||
|
|
@ -46,11 +46,11 @@ in personality memory. That way lies a small cult of confused blobs.
|
|||
|
||||
## Stored Shape
|
||||
|
||||
Each record in `state/agents.msgpack` is an
|
||||
Each record in `state/agents.cc` is an
|
||||
`EpiphanyAgentMemoryEntry` keyed by role id:
|
||||
|
||||
- `coordinator` -> `epiphany.self`
|
||||
- `face` -> `epiphany.face`
|
||||
- `persona` -> `epiphany.persona`
|
||||
- `imagination` -> `epiphany.imagination`
|
||||
- `research` -> `epiphany.eyes`
|
||||
- `modeling` -> `epiphany.proprioception`
|
||||
|
|
@ -118,7 +118,7 @@ The important distinction is:
|
|||
|
||||
- Self, Imagination, Eyes, Proprioception, Hands, and Soul want a lean role
|
||||
lattice with room to grow through memory, rumination, and distillation
|
||||
- Epiphany Face, VoidBot repo Faces, and Ghostlight-style characters want the
|
||||
- Epiphany Persona, VoidBot repo Personas, and Ghostlight-style characters want the
|
||||
denser, more fallible, relationship-heavy `Persona` surface
|
||||
- Heartbeat/swarm state owns scheduling, initiative, cooldown, active-turn
|
||||
freeze, and sleep/rumination physiology
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue