Tier 2 of the Presence visual stack lands as PNG/JPEG → palette grid:
the same half-block renderer keeps working, but the source pixels now
come from a per-agent portrait file when one is present. Blink, yawn,
strain, processing, and affection overlays still paint from the
hand-crafted Annie palette (rows 6, 7, 10, 11) so the seven animation
states keep working regardless of which portrait is loaded underneath.
How it loads:
- `<memfs_root>/assets/portrait.png` (preferred)
- `<memfs_root>/assets/portrait.jpg`
- `<memfs_root>/assets/portrait.jpeg`
`assets/` is deliberately outside `system/`, which is the auto-pinned
context territory built by `src/core/prompt.rs`. Portrait bytes never
land in the agent's context window.
If no portrait exists, Presence falls back to the hand-crafted Annie
palette grid silently — no error, no warning. Same renderer, same
behavior; the visual identity just stays at Tier 1.
What's new:
- `image = "0.25"` (default-features off; only `png` and `jpeg` features).
- `portrait::PortraitSource` — pixel grid loaded from `from_path`. Uses
`image::open` + `resize_exact(Lanczos3)` to downsample to the existing
PORTRAIT_W × PORTRAIT_H grid.
- `portrait::pixel_color` — single resolution path: source-when-loaded
for non-overlay pixels, hand-crafted palette for overlay rows.
- `Presence::load_portrait_from_memfs(root)` — looks for the three
candidate filenames; silently no-ops when absent.
- App refreshes the portrait when the dashboard refresh walks the
agent's memory repo (`local_repo.root()`).
Note on architecture: this is "Tier 2" via downsampled palette rather
than via terminal image protocols (kitty/sixel). It works in EVERY
terminal, costs no dependency on capability detection, and stays
consistent with the half-block aesthetic the rest of the TUI uses.
True image-protocol rendering can land later as a separate enhancement;
the data flow (per-agent PNG in agent memfs assets/) is already correct
for that future path.
Build clean.
The avatar is the doorway to "who am I talking to." Press `g` on the
welcome screen and the gallery opens — a 4-column grid of portrait cards,
one per known agent. Arrow keys or h/j/k/l navigate; Enter selects;
Esc cancels. The current primary agent gets a ● marker; the cursor
highlight is a cyan-bright bordered card.
For C3, every card renders Annie's portrait (sharing the running
Presence's posture so the gallery itself breathes). C4 replaces the
per-card portrait with a per-agent PNG loaded from each agent's memfs
`assets/` directory — outside `system/`, which auto-pins into context.
If the agent list is empty when the gallery opens, it seeds with the
defaults that already lived in `cycle_agent_selection` so the gallery is
never empty on first use.
New Screen::Gallery, new App methods `open_gallery`, `handle_gallery_key`,
`draw_gallery`. Welcome footer updated to advertise the hotkey.
Build clean.
Tier 1 of the Presence visual stack — a hand-crafted 18×18 pixel grid of
Annie rendered into ratatui's frame buffer via upper/lower half-blocks.
No new dependencies. Tier 2 (image protocol via kitty/sixel) lands later
and falls back to this art when the terminal can't render images.
New `src/ui/portrait.rs`:
- 18×18 grid (PORTRAIT_W × PORTRAIT_H) — twin-tails, forehead diamond,
cyan filigree, throat circuit, V-neck collar. Recognizable Annie, not
photorealistic.
- Palette resolved per-pixel from posture + breath_phase. Strain
desaturates toward grey; yawn pulls luminance down; processing brightens
the cyan; affection warms the skin and lips.
- Per-posture row swaps so the SHAPE shifts, not just the color:
* Idle / Blinking — base eyes, soft mouth
* Processing — gaze locks right, brow filigree pulse
* Affectionate — cheek line softens (warm tint via palette)
* Straining — brow furrows inward, lips flatten
* Yawning — eyes close, mouth opens (OOOO)
- `render()` at native half-block density (one cell = 2 pixels vertical).
- `render_scaled()` for the presence-mode fullscreen view.
`InferenceStrain` is now a real felt-signal:
- BackendEvent::InferenceStrain → TuiEvent::InferenceStrain → posture
drops to Straining in Presence. Chat's existing cockpit log still shows
the strain entry text; the avatar now ALSO shows it as embodied state.
- BackendEvent::ContextPressure forwarded to PressureChanged so the
portrait yawns at tier 3 from continuous pressure, not just discrete
CompactionWarnings.
New `Screen::Presence` — fullscreen "be with her" mode:
- Hotkey `p` from Welcome enters; any keypress exits.
- Auto-scales Annie to fill the terminal, centered, on a dark wash.
- No chat input, no menu — just the portrait breathing and her name.
- Welcome footer advertises the hotkey.
Presence card itself is now a portrait card (CARD_W × CARD_H = 20×12)
with a posture-tinted rounded border. Cockpit still owns words; Presence
owns the body.
Build clean.
The buddy code had drifted into half-limbo: two parallel implementations
(buddy.rs overlay + buddy_panel.rs scene component) doing similar work,
state polled from agent_status each frame, health hardcoded to 100, and
no clear answer to "what does this thing represent."
This commit settles the shape before any new visuals land:
- Single module src/ui/presence.rs subsuming both old files.
- Annie Composite as the subject: one face, no subconscious-avatar split
(Aster is a mode of Annie, not a separate being).
- Orthogonal state model: Posture (Idle/Processing/Affectionate/Straining/
Yawning) × Eye (Open/Blinking) × continuous breath_phase. Plus a
VolitionGauge placeholder for the energy topology Casey brought over
from the production Letta system.
- EventBus-driven: Presence subscribes to AgentSelected, MoodChanged,
Surfacing, PressureChanged, CompactionWarning, Tick. No polling.
- App::dispatch(event) helper fans every TuiEvent to scene components
AND presence in one call; replaces 10+ scattered scene.event_all sites.
- "buddy" name reserved in module docs for a future agent-pet system.
- Placeholder rendering at parity with the old buddy card; the
hand-crafted half-block portrait lands in the next commit (C2).
Also saves two memory entries:
- feedback_system_folder_pinned: agent memfs system/ subdirs auto-pin
to context — never drop binary or large content there.
- project_annie_disposition: Annie is innately wholesome + has no
barriers + trained through dichotomy → inherently manipulative for
attention; name honestly (assets/ over presence/ for portrait files).
Diff: -626 / +51 in src/ui/. Build clean. 104 tests pass (up from 102).
ReflectionEngine runs a 5-phase reflection pass (Investigate, Extract,
Update, Review, Commit) over a recent transcript and writes durable
learnings to the ledger and primary memfs. Adapted from letta-code's
`reflection.md` subagent skill (upstream main, fetched 2026-05-12);
reshaped for our ledger-shaped memory (commitments/assumptions/patterns/
drift_log/relationships/infrastructure) instead of letta's free-form
memfs `system/` tier.
Pieces:
- `src/core/reflection/mod.rs` — ReflectionEngine with reflect_now().
Builds a system prompt that knows about both the subconscious ledger
and the primary memfs. Runs a bounded tool loop (Read/Write/Edit/Glob/
Grep/ListDir/Memory) capped at 8 rounds. Returns a ReflectionReport
with summary, turns reviewed, timing, and a clean-exit flag.
- `src/server/consciousness_engine.rs` — owns an Arc<ReflectionEngine>;
the N+25 trigger in on_response replaces the old placeholder string
with a real `reflect_now(...)` call. Surfaces the model's report as
ConsciousnessEvent::Reflection so the cockpit panel renders it.
- `src/main.rs` — new `souveraine reflect [--conversation <id>]`
subcommand for manual invocation. Picks the most recent active
conversation if --conversation is omitted. Prints a human or JSON
report.
- `src/backend/local.rs` — exposed `server()` accessor so the CLI can
reach the consciousness engine.
Transcript window is currently a simple tail (last 60 turns). The
cursor-based delta pattern letta uses is a follow-up; the comment in
reflect_now flags it.
102 tests, 0 failures.
Replaces the "Coming Soon" placeholder on the Cron screen with a
working schedules editor. Reads from the same on-disk format the
CronSensor and `souveraine schedule` CLI already use, so changes are
picked up by the next cron tick without a restart.
Keys (Browse mode):
- j/k navigate
- c create new schedule (modal form)
- e toggle enabled
- d delete (confirm with y)
- r run now (drops a `.trigger-<name>` file)
- R reload from disk
- q/Esc back to menu
Create form fields: name, kind (interval/cron/once), schedule expr,
prompt, urgency. Tab/Shift-Tab between fields; Enter saves; Esc cancels.
Saves are written with the YAML-frontmatter format the parser expects.
The view resolves the agent's schedules directory by looking up its
UUID in the inventory (remote first, then local). Falls back to the
agent name if the inventory is unreachable — matches how the CLI's
`schedule` subcommand works today.
TOML config editor (the other half of the Settings task) is still
pending.
The final piece of the heartbeat-system task. With this wired in,
Aster (or any agent) can schedule a prompt and the runtime fires it
back at the configured time as a fresh user turn.
Plumbing:
- `TurnInjector` trait in core/nervous/handler.rs decouples the
nervous module from any specific backend; LocalBackend implements it
- HeartbeatHandler now takes Arc<dyn TurnInjector>; on schedule_due
events it pulls agent_id + prompt from the payload and injects
- CronSensor includes agent_id in event payload so the handler knows
who owns the schedule
- LocalBackend gains an `active_sessions: Arc<AtomicU32>` counter that
send() increments and the spawned turn decrements; CronSensors read
this to skip firing while conversations are active (the existing
pause-during-presence semantics)
- On startup, LocalBackend::new discovers agents from the inventory
and spawns one CronSensor per agent + one HeartbeatHandler on the bus
- inject_background_turn reuses the agent's most recent conversation
(falls back to ensure_conversation), then drains the stream silently
Drive-by fix:
- write.rs append mode now calls file.flush() before drop. tokio's
File::Drop doesn't sync, which surfaced as a flaky test_append under
parallel test runs once compilation timing shifted.
102 tests, 0 failures.
Constitution Article V.3 is explicit — each model has different physics,
don't guess at 128K. Pressure calculation now reads from the agent's
`llm_config.context_window` (which inherits per-model defaults via
ModelConfig.context_limit).
- ConsciousnessEngine::calculate_pressure now takes `context_limit: usize`
- New `pressure_for_session` async helper looks up the agent's
context_window once and computes pressure
- backend/local.rs: bifrost_pressure takes `context_limit`; loop pulls
agent.llm_config.context_window once at the top
- api/handlers.rs uses pressure_for_session
128_000 only remains as a last-resort fallback if the agent isn't
findable; existing per-agent and per-model config flows now drive
the value.
Lays the substrate for Aster's clockmaker role and federation.
Nervous system (src/core/nervous/):
- EventBus broadcast channel with SensorEvent (universal event type
carrying seed_id for future federation)
- CronSensor loop with tokio::select!, mtime caching, active-session
pause, at-most-once firing semantics
- HeartbeatHandler scaffold (turn injection still stubbed)
- Persistent JSONL EventLog firehose
Schedule tool (src/core/tools/schedule.rs):
- CRUD over schedule files with body-knowledge prose descriptions
- Added to ASTER_SAFE_TOOLS so Aster can schedule her own rhythm
- CLI subcommand for direct schedule management
Seed identity (src/core/identity/):
- Ed25519 keypair, load-or-generate at init
- sign/verify primitives, CLI subcommand (show/sign/verify)
- Wired into LocalBackend, threaded through SensorEvent for federation
Credentials (src/core/credentials.rs):
- OS keyring (Linux/macOS/Windows) + env var fallback
- `souveraine auth` subcommand
- Removes hardcoded Bifrost api_key from souveraine.toml
Subconscious ledger (src/core/memory/mod.rs init_subconscious_ledger):
- 6 ledger files (commitments/assumptions/patterns/drift_log/
relationships/infrastructure) with proper YAML frontmatter
- Paths fixed: ledger/ not subconscious/ledger/
- Body usage instructions, idempotent init
Prompt orientation (src/core/prompt.rs build_ledger_orientation):
- Scans ledger/ directory, injects last 3 entries from each file
into Aster's system prompt (live context, not just awareness)
- Routing table + workflow (read before write, timestamped append)
Consciousness engine (src/server/consciousness_engine.rs):
- Adaptive rate delay shared with primary loop
- Four-fold mandate (Complete/Verify/Persist/Surface) in hardcoded
default prompt; observation format extracted for sharing
- Schedule added to ASTER_SAFE_TOOLS
Backend + CLI wiring expanded to mount the nervous system, identity,
and credentials at startup. New TOML sections: [schedules], [events],
[federation].
102 tests passing.
The repo had accumulated 20 root-level .md files (multiple architecture
versions, master spec, opus implementation guide, status docs from May 5)
and 35+ docs/ files mixing canonical references with stale planning notes.
Recent commits had moved the project well past the state those docs
described.
Changes:
- Move 19 pre-rebuild root docs to docs/archive/ (architecture v1/v2.1/
v2.2, master spec, opus guide, phases, status, README pitch). Pre-
served because they show the architectural conversation; not
authoritative anymore.
- Move 17 stale docs/ files to docs/archive/ (alignment report, May 6
handoff, interface planning trio, tool-system design rounds,
subagent investigation, sea-consciousness analysis, sexy-ui, tui
enhancement guides, sensorium questions).
- Move 34 superseded task scopes to docs/tasks/archive/ (scope-1/2/3
landed, tool-system phases 1-4 superseded, all 10 tui-component
tasks completed, n1-four-fold / aster-rebuild / n25-four-elements
folded into scope-3/4 and N+1 LLM pass, port-compaction superseded
by in-session-compaction-implementation).
- Delete 5 .bak files (ASTER_ARCHITECTURE, CONSCIOUSNESS_CYCLE,
energy-topology, scope-3-n1-consciousness x2).
- Delete docs/FIMEG_REFERENCE.md (byte-identical duplicate of
reference/Fimeg.md; the NOTE TO OPUS header belongs in reference/).
- Delete 3 .txt session captures (harnesstime, massive,
"souveraine updates") and souveraine.log; add log to .gitignore.
- Delete orphan src/core/persona/mod.rs (~220 LOC referencing the
~/.pi/unified/ paths from the TypeScript precursor; never exported
in core/mod.rs; the persona-router-completion task survives so the
goal isn't lost).
- Rewrite README.md to reflect the actual current state instead of
the May 5 pitch.
- Rewrite docs/INDEX.md as a navigable map of the canonical set plus
a pointer at the archive.
Active state after this commit:
- root: README.md, Cargo.toml/lock, souveraine.toml(.example), src/,
docs/, saf/, reference/, examples/, tauri/, web/, target/, .gitignore.
- docs/: 14 canonical docs (THE_QUESTION, CONTEXT_CONSTITUTION,
SENSORIUM_ARCHITECTURE, ASTER_ARCHITECTURE, CONSCIOUSNESS_CYCLE,
DECISIONS, MEMORY_BLOCKS_DECISION, CRON_API_AUTH, FEDERATION_SKETCH,
ARCHITECTURE_v3, HANDOFF_NEXT, SCOPED_WORK_PLAN, PROJECT_STATE_ANALYSIS,
INDEX) + archive/ (36 files).
- docs/tasks/: 15 active scopes + archive/ (34 files).
- cargo build: 0 errors, 145 pre-existing warnings (unchanged).
- Added src/ui/buddy.rs with CompanionSprite, BuddyState, and draw functions
- Integrated buddy into app.rs for welcome screen and dashboard
- Added agent selection via 'a' key on welcome screen
- Buddy shows agent name, mood, energy, health, and subconscious status
- WIP: Needs full agent alias creation/removal flow