Watch
1
0
Fork
You've already forked souveraine
0
Commit graph

491 commits

Author SHA1 Message Date
Fimeg
afab9ce2d5 feat(settings): editable N+1 trigger payloads + live/restart propagation marks
B5: every_n_responses and time_based triggers now expose their inner
value (count / interval secs) as a conditional sub-field — previously
stuck at the 5/3600 defaults with no way to edit.

Propagation: FieldLoc::applies_live() marks the three fields that reach
the running system immediately (atmosphere, outfit, primary_model); a
legend line states the rest take effect on restart.
2026-05-16 19:38:37 -04:00
Fimeg
afdaa45a6c feat(settings): add TUI category + expose timeout/auth/federation fields
Settings audit coverage gaps: new TUI category (interstitial narration
toggle); bifrost request timeout; server auth.required / allow_loopback;
federation role / instance_label / auto_wake — all previously unreachable
from the Settings screen.
2026-05-16 19:28:21 -04:00
Fimeg
84318b1352 fix(tui+compaction): settings audit fixes, gap-line fix, subconscious compaction
- settings: open on Categories panel; "saved" message; fix ScMaxTokens
  commit path; char-boundary-safe field editor (UTF-8 panic on paste)
- tui: drop empty interstitial gap lines (trim whitespace narration)
- compaction: teach the subconscious to compact (SUBCONSCIOUS_BODY_ORIENTATION);
  memory compact resolves per-agent-type default; subconscious leans sliding_reflect
- sliding_reflect preservation pass runs as a fresh fork of the agent being
  compacted — her persona, first person, '[Threads I carried forward]'
2026-05-16 19:23:55 -04:00
Fimeg
2ce299f293 feat(tui): atmosphere restore from system/preferences/visual + archive resolved tasks
Adds Atmosphere::from_name() constructor and wires a readback of
system/preferences/visual.md at TUI conversation startup (alongside the
existing energy-balance read), so the agent's last explicit atmosphere
choice survives restarts.

Archives 5 resolved task docs:
- tui-clean-conversation-switch (switch_pending state machine in place)
- scope-4-n25-reflection (408-line engine, fires at N+25)
- tui-presence-and-interrupt (interjection queue, /btw, raise-hand,
  phase display, self-awareness pulse all built)
- presence-visual-evolution (ChatPalette wired, posture-shift fix,
  cross-session atmosphere restore now landed)
- presence-autonomy (atmosphere tool bidirectional, prefs readback now
  wired; from_posture() coupling remains per proto task scope)

Updates CLAUDE.md active task queue to reflect current state.
2026-05-15 17:40:09 -04:00
Fimeg
3629fa8b78 feat(federation): Phase 8 — hearth & limb role model
FederationRole (hearth/limb) in config; bridge carries role in
device_announce; DeviceRegistry tracks role per peer with a split-brain
guard (two hearths → ERROR!). Role defaults to hearth: a standalone
machine is its own home.
2026-05-15 16:01:21 -04:00
Fimeg
055a3d7b38 feat(federation): Phase 7 — agent identity vs device identity
reach/consult was classified by trusting the event_type field, so any
peer could claim "reach" and skip the consent gate. Federation also used
one per-machine seed for everything.

Split the two identities the codebase already had: the agent seed
(agents/{id}/seed/, travels with the memfs, identical across one agent's
machines) now signs the summon payload; the device seed keeps signing
the transport envelope. The receiver classifies reach vs consult by
verifying the agent signature against its own agent pubkey — a match is
genuinely self (reach, no consent gate), a mismatch is a separate being
(consult, consent-gated), an invalid signature is dropped. The lite
listener verifies the same way, reading hosted agent pubkeys straight
from seed/public.key with no engine load. authorized-summoners.md is now
keyed on agent pubkeys — consent is per-being, not per-machine.

New core/identity/summon.rs carries the signing helpers and round-trip,
tampered-field, and wrong-pubkey tests.
2026-05-15 15:38:35 -04:00
Fimeg
c02e2b7f8e feat(federation): TurnInjector auto-wake for inbound summons
An inbound reach/consult now wakes the target agent with a background
turn instead of waiting for her next natural turn. SummonHandler holds
a OnceLock<Arc<dyn TurnInjector>>, wired from LocalBackend::new with the
same injector the heartbeat handler uses. Gated on FederationConfig
.auto_wake (sovereign default off). Only the inbound summon wakes; a
summon_response still surfaces in the caller's inbox per fire-and-surface
so the caller is never interrupted. The pure server path has no turn
loop — auto-wake there is a graceful no-op.
2026-05-15 15:07:34 -04:00
Fimeg
015dad561d feat(federation): Phases 4-6 — reach & consult, lite listener, memory gating
Phase 4 — Reach & Consult protocol:
- agent.rs rewritten as the `reach` (self-extension) and `consult`
  (sovereign peer) tools; event-bus dispatch, peer resolution via
  known_peers.json
- summon_handler: fixed inbox path, real pending/intrusive routing,
  timeout surfacing to bus + inbox, outbound-request self-registration
- bridge always forwards control events regardless of subscriptions
- response loop closes via a file outbox: an inbound summon instructs
  the agent to write federation/outbox/{id}.md; scan_outbox turns that
  reply into a summon_response routed back to the caller

Phase 5 — Lite listener:
- `souveraine listen` — minimal federation presence with a summon-wake
  watcher; parks authorized summons to .summon-pending/
- full server drains .summon-pending/ on startup
- FederationConfig: authorized_summoners, auto_wake

Phase 6 — Memory gating (partial):
- federation posture injected into the system prompt from the
  federation/ memfs contract
- consent floor via authorized-summoners.md
- device registry: prune_stale timer, first_seen preserved

Builds clean (0 errors). Status + deferred work documented in
docs/tasks/federation-summon.md. Also sweeps in pre-existing in-flight
working-tree changes (TUI, prompt, docs).
2026-05-15 14:58:47 -04:00
Fimeg
a2b9b131b1 feat(federation): Phase 2 — device registry, peer discovery, and CLI
DeviceRegistry tracks known federated peers from device_announce/device_leave
SensorEvents on the bus, persisted to ~/.souveraine/federation/known_peers.json
for CLI access. Bridge emits device_announce on connect. New `souveraine peers`
subcommand lists known peers. Wired into SouveraineServer at construction.
2026-05-15 14:04:19 -04:00
Fimeg
4b9c5e2031 feat(federation): Phase 1 transport — signed event streams between instances
SensorEvent gets reply_to for directed routing. FederationConfig gains
peers (url + pubkey + subscriptions). New src/server/federation/ module
with SignedEvent (Ed25519 sign/verify) and FederationBridge (per-peer
outbound WS tasks with echo-safe seed_id filter and backoff reconnect).
Inbound /v1/federation/events handler verifies signatures before bus
injection. Bridge spawned in server::run() when federation.enabled.
2026-05-15 13:45:09 -04:00
Fimeg
4b903df9c3 turn lifecycle + instance fix + dynamic agent identity in Aster
Remove the auto-reset stall guard that silently killed turns after 90s
and orphaned the backend. The TUI now trusts the stream: backend emits
Keepalive every ~15s between rounds, the phase strip shows a liveness
label (waiting Ns... / still waiting Ns...), and the user decides when
to Esc. SSE ping frames map to Keepalive on remote backend.

Instance registry no longer blanket-registers all agents at startup —
register_instance takes an explicit agent_id, called from
new_conversation only. Fixes inflated instance counts on manager cards.

Consciousness engine (Aster) looks up the primary agent's name
dynamically instead of hardcoding "Ani". Parameter renamed to
primary_response.

Interjection labels changed from /btw to hand-raise metaphor
(hand raised → noticed) since /btw is its own fork feature.
2026-05-14 19:51:44 -04:00
Fimeg
b52bfc08e0 feat(api): per-agent bearer auth on agent CRUD, conversation, and stream endpoints
- Adds owner_seed_id column to agents table, stamped from instance Ed25519
  seed on agent creation (Path B ownership model)
- Splits routes into public (list/create agents, health) and protected
  (update/delete agent, get conversation, stream messages) with per-agent
  bearer token middleware
- Factors verify_token() helper from existing memory-route middleware;
  adds require_agent_token() and require_conversation_token() wrappers
- RemoteBackend loads per-agent token from disk and sends Authorization
  header on protected requests; ChatState recreates backend with token
  after discovering agent_id
2026-05-14 18:14:20 -04:00
Fimeg
fec3aa625d tui: atmosphere palette wiring, cockpit pane, schedules config, settings editor, markdown palette, event log bootstrap, voice stubs, RGP 3D, portrait system, reflection bridge, skills wire, schedule feedback, compaction rebuild 2026-05-14 17:58:48 -04:00
Fimeg
d5ccfbba2b feat(tui): atmosphere presets, welcome portrait fix, streaming staleness guard
- Atmosphere system (src/ui/atmosphere.rs): 14 color presets ported from
  the Matrix adapter system, wired into Presence with posture-linked
  defaults and explicit BackendEvent::Atmosphere trigger path
- Welcome portrait: decoupled from 18×18 pixel-art constants, now sized
  at ~40% of terminal width (capped 48), proper Resize::Fit rendering
- Dashboard overlay: moved from TopRight to BottomRight so it no longer
  overlaps the 4th dashboard card
- Streaming staleness guard: 30s timeout in drain_events — if no
  BackendEvent arrives while busy, the turn resets to Idle and injects
  a system message instead of displaying "Streaming" indefinitely
- Hal agent: registered as d91e264c-bd5a-4d02-9641-9202b9a64be5 with
  full memfs, seed, portrait, and DB entry
- Expression cache (src/ui/expressions.rs): pre-existing but uncommitted
  241-line module for per-agent expression frames with fallback chain
- Fix borrow errors in session_manager.rs and chat.rs BtwState match
- BackendEvent::Atmosphere variant + TuiEvent::AtmosphereChanged wiring
2026-05-12 22:48:08 -04:00
Fimeg
f32d949e11 feat(tui): agent-manager cards with real portraits + chat phase strip + /btw interjections
- Agent manager: full card grid with stateful photo portraits (Resize::Fit),
  Letta-style metadata blocks (glyph, name, path, stats), PRIMARY/ACTIVE/idle
  badges, auto column count 1-4
- Welcome screen: pulls active agent portrait from same card-image cache,
  replaces the broken eager-load Protocol path
- Chat phase strip: dedicated 1-row line between messages and input showing
  ⏣ Thinking · 12s / Running tool · 4 tools used / Streaming / × Interrupted
- Always-on input: input box is live during busy — Enter queues an interjection
  instead of being rejected
- /btw <text>: slash command for explicit mid-turn messages
- Backend interjection queue: Arc<Mutex<Vec<String>>> drained between LLM rounds
  and prepended as [user interjected at HH:MM] system notes
- Esc → interrupt still works; phase strip shows × Interrupted
2026-05-12 19:31:56 -04:00
Fimeg
6b5a3c5959 ratatui 0.30 upgrade + ratatui-image for real photo rendering
ratatui-image v11 provides kitty/sixel/halfblock rendering. Loads the
agent's portrait photo as a terminal image protocol alongside the existing
half-block PortraitSource. On supportng terminals (Kitty, WezTerm, iTerm2,
Ghostty) the Welcome screen shows the actual photograph instead of the
18x18 pixel-art downsample.

Also includes the Agent Manager screen (press `i` on Welcome) with
per-agent card data: seed glyph, uptime %, instance count, memory files,
pubkey prefix, description.
2026-05-12 15:49:39 -04:00
Fimeg
e261e9758e fix(portrait): cover-crop aspect ratio, 3× source resolution
Photo portraits were stretched into a square (resize_exact to 18×18) and
lost all detail. Fix doubles the problem:

- Cover-crop: scale so the shorter axis fills the target, center-crop the
  excess — landscape or portrait photos now fill the frame without distortion.
- 3× source resolution: store at 54×54 and bilinearly interpolate at render
  time, so each of the 18×18 grid cells is a weighted blend of ~9 source
  pixels instead of a single hard-sampled one.
2026-05-12 14:25:42 -04:00
Fimeg
ba576f63f4 instances(registry): per-process agent_instances table + uptime ticker
Schema (idempotent migration in server/db.rs::init_database):
- agent_instances table (agent_id, instance_id, pid, hostname,
  started_at, last_seen_at), primary key (agent_id, instance_id)
- lifetime_active_seconds INTEGER column on agents (added via
  add_column_if_missing — SQLite has no IF NOT EXISTS on columns,
  so PRAGMA table_info is checked first)
- idx_instances_agent index on (agent_id, last_seen_at DESC)

AgentInventory wires the lifecycle:
- register_instance(instance_id): prunes rows older than 5 min, then
  INSERTs (or upserts last_seen_at) for every known agent
- heartbeat_instance(instance_id, tick_seconds): UPDATEs last_seen_at
  and increments lifetime_active_seconds by tick_seconds for every
  agent this instance has rows for
- instance_count(agent_id): COUNT of live rows (>=5min recent)
- lifetime_active_seconds(agent_id): cumulative active seconds

SouveraineServer::new generates a process-lifetime instance_id (UUID),
calls register_instance once, then spawns a 30s heartbeat loop. Both
local CLI mode and full server mode get the same registration path
(the manager-mockup "N Instances" badge will count rows; uptime % is
int(100 * lifetime_active_seconds / age_seconds), capped at 99 in UI).

Verified end-to-end via `souveraine agents`: both Annie and Souveraine
rows land with matching instance_id, current pid, hostname.

Build clean. 111/111 tests pass.
2026-05-12 13:59:42 -04:00
Fimeg
f495f179b5 seed(identity): per-agent seeds + CLI redesign
Each agent now has its own Ed25519 keypair at
~/.souveraine/agents/{uuid}/seed/, alongside its memfs. AgentInventory
gains seed_dir() / seed_id() getters and auto-initializes the per-agent
seed in create(). Federation can now reference agent identities directly
instead of routing everything through the host seed.

SeedId::glyph() produces a deterministic 4-char rendering of the pubkey
drawn from a 16-glyph geometric-shapes palette — terminal-friendly badge
for the manager-card SeedID display (and future federation peer lists).

CLI redesign — `souveraine identity` is now per-agent by default:

  souveraine identity show              # current agent's seed
  souveraine --agent Annie identity show # Annie's seed
  souveraine identity show --host       # machine seed (federation)
  souveraine identity sign "msg" --agent Annie
  souveraine identity verify --pubkey ... --signature ... --message ...

The global --agent flag is now Option<String> with no default (was
hardcoded "Ani"). When omitted, the CLI falls through to the first
available agent on disk via first_agent_id_on_disk(), which walks the
user-side memfs dir and filters orphans by requiring a memory/
subdirectory. run_chat / run_tui / run_reflect / run_schedule all
updated to take Option<String> and pass through to existing
first-available fallback logic.

Help text example replaced "souveraine --agent Ani chat" with the
neutral "souveraine --agent <name> chat".

Build clean. 111/111 tests pass (gained 2 for glyph rendering).
2026-05-12 13:56:29 -04:00
Fimeg
b35ff5f7b6 unify(memory): single canonical path at ~/.souveraine/agents/{id}/memory/
Solves the split-data architecture problem. Previously the same agent
had two independent git repos: ~/.souveraine/agents/{id}/memory/ (used
by MemoryRepo::new_default — CLI + prompt builder) and
~/.souveraine/server/agents/{id}/memory.git/ (used by AgentInventory —
consciousness engine + API handlers). They diverged: writes through one
path never reached the other, so Aster's writes and Casey's CLI writes
landed in different repos for the same agent.

AgentInventory now distinguishes:
- agents_dir (server-managed): agent.json + conversations/
- memfs_dir (user-side, canonical): memory/ — the single source of truth
  at ~/.souveraine/agents/{id}/memory/

memory_repo() / memory_root() now point at memfs_dir/{id}/memory/.
create() initializes the memfs there; update(), commit(), and
load_memory_blocks() write there. delete() removes both server-side
and memfs trees. Subconscious agents keep the memory.git/ convention
under subconscious-agents/ since they're not duplicated.

Removed the dual-path probe in PresenceState::load_portrait_from_memfs
that probed both memory/ and memory.git/ siblings — only memory/ now.

Build clean. 109/109 tests pass.
2026-05-12 13:41:03 -04:00
Fimeg
c51b53718d fix: Bifrost tool-call schema, welcome dedup, tool cards, PNG diagnostics
Broad-stroke pen-adjustment landing four pieces in one commit.

1. Bifrost tool-call schema (unblocks turns + Aster + reflection)
   BifrostMessage now carries optional tool_calls / tool_call_id / name
   with skip_serializing_if=None so unrelated turns keep the {role,content}
   wire shape. All three tool loops rewritten to push real OpenAI tool-use
   shape instead of a stringified JSON blob in content: backend/local.rs
   (Ani's loop + LocalSubagentRunner), server/consciousness_engine.rs
   (Aster's N+1 pass), core/reflection/mod.rs (N+25). This is the root of
   the "first memory tool works, then everything fails on turn 2" symptom
   and the silent Aster pipeline failure — same wire bug, three loops.

2. Welcome avatar dedup + scale-up
   Presence overlay was rendering on both Welcome and Dashboard, doubling
   up with the centered inline portrait on Welcome. Gated to Dashboard
   only. Welcome portrait now uses render_scaled at WELCOME_SCALE=2 —
   visibly larger, the focal point of the landing screen.

3. PNG portrait diagnostics
   Silent .ok()? failures replaced with tracing::warn! showing the actual
   decode error. load_portrait_from_memfs probes the sibling
   memory.git/assets/ layout in addition to memory/assets/ since
   server-managed agents live under memory.git/.

4. Tool-call rendering polish
   New BackendEvent::ToolCall { id, name, arguments, round } and
   BackendEvent::ToolResult { id, name, output, is_error } replace the
   literal "🔧 Round 1 — executing: Bash, Memory" Token text spew.
   ChatMessage::Tool variant + ToolResultBlock carry card state.
   render_tool_card draws bordered cards: status glyph (⟳/✓/⚠), header
   with name and round, compact JSON arg summary, output preview through
   markdown.rs (12 lines + "… (N more)" tail). Errors paint red.

Build clean. 109/109 tests pass.

Docs: archive prior handoffs to docs/archive/handoffs/, refresh
HANDOFF_NEXT.md, add four new task files (tui-message-interactions,
agent-profile-and-repo-manager, per-agent-seed-and-instances,
code-mode-tool-rendering) tracking the next-instance work.
2026-05-12 13:09:17 -04:00
Fimeg
fb520ebcaf fix(tui): faceless silhouette default; center avatar on Welcome
Three corrections after first walkthrough:

1) The hand-crafted "Annie face" palette grid read as a creepy llama
   (Casey's words). Replaced with a faceless silhouette — hair, neck,
   collar, no eyes/brow/mouth. ASCII portraits at this resolution
   should be a signal that no image is loaded, not a default depiction.
   Per-pixel row-swap overlays for blink/yawn/strain/processing are
   gone too; state now expresses through:
     - border color (posture_border)
     - cyan-leaning luminance breath pulse (modulate)
     - across-the-image desaturation on strain, dim on yawn,
       warm tint on affection.
   When a per-agent PortraitSource is loaded (PNG/JPEG from agent
   memfs assets/), pixels come from the source and the silhouette is
   not drawn. The source pixels get the same posture modulation pass.

2) The "press ENTER to wake <Agent>" line on the welcome avatar was
   misleading — the welcome menu owns the keyboard, not the avatar.
   Welcome's `draw_welcome` in presence.rs is removed entirely; the
   avatar is now rendered inline by `App::draw_welcome` so it can
   participate in the welcome vertical layout.

3) Avatar moved from upper-right corner to a centered, framed card
   directly under the title. The avatar IS the "your agent is loaded"
   indicator; the menu beneath reads as actions on that agent.
   Subconscious-active state still expresses through the ◈ glyph and
   border color (cyan when active, dim when idle).

`Screen::Presence` (hotkey `p`) keeps its meaning as the fullscreen
"sit with her" view — natural future home for TTS/STT triggers.

Also saves two memory entries:
- feedback_portrait_aesthetics: ASCII portraits are creepy at this
  resolution; default to PNG; state via color+breath, not row swaps.
- reference_nannou: Casey wants to lean on nannou (Rust creative-coding,
  wgpu) more frequently for graphics outside the TUI.

Build clean.
2026-05-12 12:38:17 -04:00
Fimeg
543ae50103 feat(tui): per-agent portrait loading from agent memfs assets/
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.
2026-05-12 12:24:23 -04:00
Fimeg
7a1912719a feat(tui): agent gallery — portrait grid is the way to swap agents
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.
2026-05-12 12:16:08 -04:00
Fimeg
a2b95c21b4 feat(tui): Annie's half-block portrait + presence mode
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.
2026-05-12 12:13:01 -04:00
Fimeg
c31786af3f refactor(tui): buddy → presence; one face for Annie Composite
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).
2026-05-12 11:48:52 -04:00
Fimeg
8f910ec8a8 feat: N+25 reflection engine — callable, wired into N+25 trigger
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.
2026-05-12 10:15:16 -04:00
Fimeg
b9b286293f feat(tui): Schedules editor — the Cron screen comes alive
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.
2026-05-12 09:40:51 -04:00
Fimeg
69d5e80c7b feat: HeartbeatHandler turn injection — the clockmaker connection
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.
2026-05-12 09:12:41 -04:00
Fimeg
db8536f354 fix: replace 128K context_limit hardcode with per-agent lookup
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.
2026-05-12 08:26:11 -04:00
Fimeg
8acb3884a8 feat(tui): chat refinements — input expansion, overlay polish
Input expansion and overlays from the tui-input-polish task. Visual
testing and UI spice remain (settings pages, schedule editing).
2026-05-12 08:14:28 -04:00
Fimeg
35534f1c73 feat: nervous system, seed identity, credentials, subconscious ledger
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.
2026-05-12 08:14:25 -04:00
Fimeg
c489aa4bb3 feat: conversation persistence, input expansion, overlay UX
- ConversationStore (JSONL + JSON metadata) wired through Backend trait,
  SessionManager, and TUI (/new, /resume, /convos commands)
- Dynamic input box: grows with content, caps at 40% terminal height,
  scrolls internally. Shift+Enter / Ctrl+J for newlines.
- Slash command autocomplete popup (filters live as you type /)
- Conversation picker overlay for /resume (modal, arrow select, Enter switch)
- Splash bloom animation, color_support module, system prompt builder
- Fixed 8 pre-existing test failures (95/95 pass)
- Archived superseded task docs, consolidated planning references
2026-05-11 16:05:43 -04:00
Fimeg
832d80a1ff chore: consolidate documentation, archive superseded planning docs
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).
2026-05-10 19:00:49 -04:00
Fimeg
4d0eb048d5 feat: implement compaction system with pragmatic balance architecture 2026-05-10 13:15:24 -04:00
Fimeg
9a9a31916c feat: subconscious agent identity architecture - first-class separation
- AgentType enum (Primary, Subconscious, Subagent) with AgentIdentity
- Directory routing: agents/{id}/, subconscious-agents/{id}-sub/, subagents/{parent}/{id}/
- Auto-create subconscious agents with own memory, persona, ledger, inbox
- Ledger lives in subconscious agent space (not primary)
- TUI: CockpitPane and BuddyPanel for consciousness visibility
- Aster tool loop: full tool access with 5 rounds, uncapped output
- Config: max_tokens Option for uncapped subconscious output

Builds clean, 0 errors.
2026-05-10 11:20:20 -04:00
Fimeg
8f2d2e7227 feat: N+1 LLM pass - Aster replaces heuristic detect_items with real Bifrost analysis 2026-05-10 08:47:56 -04:00
Fimeg
7da881ad8a feat: sensorium architecture — 8 sensors, subagent runner, TUI component system
Sensorium (src/core/tools/):
- defs.rs: Tool trait, ToolContext (resolve_path, is_memory_path), ToolError with 7 constructors
- read/write/edit: memory boundary with force override, line ranges, replace_all
- bash: stateful (Arc<Mutex<BashState>>), background tasks, timeout
- glob/grep/list_dir: gitignore respect, context lines, memory boundary
- mod.rs: Sensorium registry + backward compat wrappers
- subagent.rs: fork-of-self tool, dual-state framing, no hard depth guard
- agent.rs: Agent(Sam) summoning stub

Dual-state N+1:
- ConsciousnessEngine::on_response_for_agent() — runs heuristic detector on subagent response
- Observations flow back to parent agent's inbox
- Signaled limits: SubagentConfig (max_depth, max_tool_rounds=50, warning thresholds 0.8/0.95)

Architecture docs:
- SENSORIUM_ARCHITECTURE.md — full sensorium vision with nervous system scaffold
- DECISIONS.md — settled architectural decisions
- ASTER_ARCHITECTURE.md / CONSCIOUSNESS_CYCLE.md — reframe appendices
- ALIGNMENT_REPORT.md — doc-to-code alignment audit
- Scope tasks 1-4, 4 issues

TUI:
- component.rs — component system trait + event + scene
- 10 tui-component tasks (completed)
- 4 tui-xxx tasks for next iteration (presence panel, cockpit, dashboard, ambient)
- app.rs splash — vertical breathing room (30% from top)

Config:
- SubagentConfig in config.rs
- ToolContext enrichment (agent_id, subagent_runner, subagent_depth)
- Memory module context-aware overloads
2026-05-09 20:58:37 -04:00
Fimeg
ae0968bf44 feat: wire tools into LocalBackend turn loop
- Added max_tool_rounds (configurable per-agent) to LlmConfig
- Rewrote run_turn() with multi-round tool-calling loop
  (max 10 rounds by default, 0 disables tools)
- Converts core ToolDefinitions → bifrost ToolDefinitions
- Streams tool execution status to TUI via BackendEvent::Token
- Continues consciousness processing (surfacing, reflection, archivist)
  after tool loop completes
- Fixed git2::Config Send issue in memory/mod.rs (scoped block)

Closes: docs/tasks/tool-wiring.md
2026-05-08 09:15:51 -04:00
Fimeg
bc7316c411 fix: resolve compilation errors in ModelRouter + Bifrost integration
- Fixed cli/commands.rs config path resolution (removed non-existent get_config_path())
- Removed .await from ConsciousnessConfig::load() (method is synchronous)
- Fixed command routing in main.rs (Commands::Model vs Commands::Models)
- Removed dead run_models() function
- Dereferenced json/verbose bools in command handler
2026-05-07 15:49:58 -04:00
Fimeg
d64a6e9794 feat: WIP - Implement companion buddy system for Souveraine TUI
- 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
2026-05-07 02:31:44 -04:00