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

570 commits

Author SHA1 Message Date
Fimeg
95dada67a8 feat: add skills directory (context-doctor, feature-dev, souveraine-architecture)
All three skills are methodology-only — no LAN IPs, credentials, or
internal infrastructure details. Safe for public consumption.
2026-05-20 13:44:46 -04:00
Fimeg
845a3f58a9 public: strip docs, memory, saf, ledger, config — source-only branch for alpha testers 2026-05-20 13:35:58 -04:00
Fimeg
74887d32a3 alpha-readiness: Agent Health screen, $MEMORY_DIR fix, tool posture badge
- Settings Agent category now shows agent ID, subconscious pairing,
  memory/subconscious paths, and disk-existence checks (read-only)
- $MEMORY_DIR env var now always set correctly — strips stale host-shell
  leftovers before injecting agent-specific computed values
- Header bar shows [tools shown] / [tools folded] posture badge
- Fix "applies live" footer — only shown for categories with live fields
- Bifrost "primary model" relabeled to "new-agent default" for clarity
- Update alpha-tester-readiness.md task doc and INDEX.md task tracker
2026-05-20 11:42:22 -04:00
Fimeg
4194f00635 feat: itinerary system, subconscious checkpoint, and user nickname
- Itinerary tool (set/advance/describe/clear) with YAML persistence
  and todo-linked enrichment, rendered as TUI header strip
- Mid-turn subconscious checkpoint: every N tool rounds, Aster assesses
  progress; HALT verdict breaks the loop with full correction pass
- Nickname tool: agent learns human's name via set/get/clear, stored
  in system/human.md frontmatter, TUI uses it in chat bubble labels
- Removed hardcoded human.md write from memory::init (wizard owns it)
- Prompt nudge for itinerary when live todos exist without a route
2026-05-20 09:21:11 -04:00
Fimeg
637e854901 alpha-readiness: strip LAN IPs, provider-agnostic wizard labels, fix config path and clipboard
- Default API/STT/TTS URLs changed from Casey's LAN to 127.0.0.1
- Wizard labels renamed: "Bifrost URL" → "API Endpoint URL", provider-agnostic
- Config discovery and wizard persistence now use ~/.souveraine/ over CWD
- Clipboard copy: added OSC 52 fallback, visible error feedback on failure
- Cleaned souveraine.example.toml: removed leaked key and personal paths
2026-05-20 09:16:11 -04:00
Fimeg
0792ece0ce Add T-035 itinerary-persistent-ui task file, update INDEX.md stats to 35 active tasks, add B-010/B-011 cross-references 2026-05-19 19:03:44 -04:00
Fimeg
61c2007226 feat: context pressure denominator, ambient sense formatting, resume timestamp, user nickname system, subagent background fork, settings LLM config, turn lifecycle, ledger lifecycle, subconscious evolution
- Context pressure footer now shows 'ctx 19% of 262k' (B-008)
- Ambient sense formatting as separate timestamp marker (B-010, T-034)
- Resume timestamp indicator with duration (T-032)
- User nickname system sourced from identity frontmatter (T-033)
- Subagent background fork task (B-009, T-031)
- Settings LLM config task (T-030)
- Turn lifecycle and sensorium completion task
- Ledger entry lifecycle task
- Subconscious evolution documentation
- Multimodal input metadata task
- Image support module (src/core/image.rs)
- Various refactors: config, seeds, session, consciousness engine, server modules, bridge, API handlers, TUI cockpit/commands/render, health panel, presence, setup wizard
- website/docs submodule update
2026-05-19 17:02:09 -04:00
Fimeg
f93f9ef45b feat: input cursor navigation, paste handling, word-boundary movement
- Left/Right arrows move cursor by char boundary, Backspace deletes at
  cursor, Char inserts at cursor (not end-of-string)
- Home/End jump to start/end, Ctrl+Left/Right skip by word
- Cursor renders at the correct visual position (wrapping-aware)
- Bracketed paste detection (crossterm 0.27 feature) — pastes >3 lines
  tagged with a system message so the agent sees pasted material
- Paste inserts at cursor position with no size cap
- docs/bugs.md updated: B-006 fixed
- CLAUDE.md, docs/tasks/INDEX.md updated for refactor status
2026-05-19 12:52:11 -04:00
Fimeg
61048b61e5 refactor: split settings.rs into focused modules
settings.rs (1657 lines) → 5 files in src/ui/settings/:
  mod.rs — re-exports, module declarations
  types.rs — PanelFocus, Category, FieldLoc, EditableValue, SettingsMode
  view.rs — ActiveAgentSettings, SettingsView struct + data methods
  key_handling.rs — key event dispatch + SettingsAction enum
  draw.rs — draw functions (draw, draw_header, draw_category_list,
             draw_field_panel, draw_footer, draw_saved, draw_error)
2026-05-19 11:24:24 -04:00
Fimeg
ca2762a302 refactor: split chat.rs into focused modules
chat.rs (3265 lines) → 8 files in src/ui/chat/:
  mod.rs — re-exports, helpers, TopButtonBar
  events.rs — event/subcommand dispatch, input handling
  commands.rs — /slash command processor
  render.rs — main draw function, chat/layout rendering
  cockpit.rs — subconscious pane (the "cockpit" panel)
  overlays.rs — overlay/modal draw functions
  footer.rs — status bar rendering
  wrap.rs — word-wrap utility
2026-05-19 10:26:34 -04:00
Fimeg
d3d485f9e8 refactor: split app.rs and local.rs into focused modules
app.rs (3540 lines) → 10 modules: mod.rs (event loop core), welcome,
dashboard, presence_screen, manager_screen, voice, settings_handler,
splash, images, agents.

local.rs (1827 lines) → 5 modules: mod.rs (Backend impl), turn
(run_turn + tool loop), consciousness (TurnInjector + surfacings),
subagent (LocalSubagentRunner), energy (balance writer + event).

No behavioral changes — same code, different files. 169 tests pass.
2026-05-19 10:26:34 -04:00
Fimeg
ab1167e99d tui: cockpit pane scroll, word-aware input wrap, footer cleanup
Cockpit thinking/subconscious panes now scroll independently under mouse
wheel. Word-aware input wrapping preserves cursor position. Footer
stripped of posture label, scroll hint, S-Ret indicator — only
conv ID and pressure remain.
2026-05-18 21:31:02 -04:00
Fimeg
5586a7937c sensorium adapter reckoning — ChannelAdapter trait, Matrix inbound path, corrupt-box healing 2026-05-18 20:59:57 -04:00
Fimeg
12ccc4e6de fix: neutral description for public consumption 2026-05-18 16:10:09 -04:00
Fimeg
39fc49fbb8 fix: set MSRV 1.94 so users get a clear error instead of cryptic dep failure 2026-05-18 16:09:24 -04:00
Fimeg
93d4baa98d WIP: context doctor session + audit-fixes accum 2026-05-18 15:58:46 -04:00
Fimeg
aed114f998 chore: archive completed tasks, stale handoffs, and stray files
- heartbeat-n1-after-autonomous, n100-archivist-compression moved to
  tasks/archive/ — the first landed this session, the second was
  already built before it.
- Dated session handoffs (May 14c/15/16) moved to archive/handoffs/.
- Rescued a stray bug log to archive/bugs.md and removed the stray
  nested souveraine/ directory.
- Filed stray working files (an old chat.rs copy, a one-off script,
  terminal-paste garbage, a prev log) under docs/archive/strays/,
  which is gitignored — kept on disk, out of the repo.
- INDEX.md task list refreshed to drop archived references.
2026-05-18 11:13:37 -04:00
Fimeg
1a95139261 feat: sensorium ambient-context injection, wheel-scroll, click-to-copy fix
Sensorium: a new sensorium::ambient_line() builds a one-line ambient
sense — current date/time and the connecting user — prepended to the
user message every turn, for both the primary (send_with_signals) and
the subconscious (subconscious_tool_loop). The subconscious no longer
guesses the date; ledger entries get stamped with the real time.

TUI scroll-wheel: the chat viewport now responds to mouse ScrollUp/
ScrollDown, routed through the same scroll field the arrow keys move
(3 lines per notch).

TUI click-to-copy fix: copy_message_at was hit-testing against stale
line indices — span_spans recorded message positions against the
pre-wrap buffer, but wrap_lines reflows it and shifts every index past
a wrap point. draw_messages now builds a prefix-sum remap from pre- to
post-wrap indices and translates the spans before storing them.
arboard itself was fine; the bug was the bookkeeping.
2026-05-18 11:03:03 -04:00
Fimeg
ebb0bf4e65 feat: subconscious parser fix, heartbeat surfacing pickup, first-run cleanup
Subconscious N+1: parse_observations now reads the natural
`- **label**: content` markdown the model actually produces. The rigid
source/content/urgency triple matched nothing the model emitted, so
every observation was dropped and the pass surfaced "no anomalies
detected" every time. Legacy triple kept as a fallback. Tests cover
the live output captured from a real pass.

Heartbeat N+1: autonomous-cycle surfacings stash to
pending-surfacings.jsonl (src/core/nervous/pending.rs) and replay in
the TUI/CLI on next connect, instead of draining silently into a
stream nobody reads.

First-run: souveraine init demoted to a config-template writer; the
TUI setup wizard is the onboarding path. The wizard now persists the
Bifrost URL/key/model it collects (to disk and the OS keyring) before
agent creation, so the next launch starts configured. Default agent
name changed from Ani to Souveraine.

Docs: alpha-tester-readiness, strip-emojis, and truncation-signal-polish
task notes.

Also sweeps in pre-existing uncommitted work on this branch
(bifrost.rs, compact/plan.rs, and parts of local.rs / main.rs).
2026-05-18 10:54:11 -04:00
Fimeg
332dd4bf65 fix(tui): interjection delivery, turn cancel on agent switch, resume-or-new
Three turn-lifecycle bugs Casey hit in daily use.

④ Interjections: the backend drains the queue between tool rounds and
   after the final LLM call, but one arriving during the chunked
   stream-out — or after the turn ended — just sat in the queue, never
   delivered ("she isn't getting that message"). On turn completion
   (Done / channel close) any still-queued interjections now open a
   fresh turn. submit() is split into spawn_turn() so this delivery
   reuses the Interjection bubbles instead of duplicating a User bubble.

⑤ Agent switch mid-stream: select_agent() dropped the chat surface
   without cancelling the running turn, so run_turn kept executing
   orphaned and resurfaced as "doing the same message again" on a later
   /resume. select_agent() now fires the turn's cancel token first
   (cancel_active_turn) — the backend commits partial output and stops.

⑥ Resume-or-new: entering chat silently auto-created a conversation.
   ChatState::connect now offers it — offer_resume_or_new() lists the
   agent's prior conversations (excluding the just-created empty one)
   and raises the ConversationPicker with an [n] new option; silent
   when the agent has no prior conversation.
2026-05-17 19:07:08 -04:00
Fimeg
a2d89036c2 feat(settings): per-agent model field — Settings follows the active agent
Casey reported model changes in Settings not taking effect on the agent
he meant. Root cause: Settings only ever edited the global
[bifrost] primary_model, and a save silently pushed that global change
onto whatever agent the open chat happened to be — never the agent the
user thought he was configuring, and nothing at all with no chat open.

- New AgModel field in the Agent settings category, backed by
  ActiveAgentSettings (id, name, model) loaded from the active agent's
  agent.json on Settings entry — so the field shows and edits THIS
  agent's llm model, and follows agent switches.
- On save, an AgModel change is pushed to that agent via the backend's
  update_agent_model (also refreshes cache + SQLite mirror).
- bifrost.primary_model is now purely the new-agent default; the
  confusing primary_model -> active-agent cross-push is removed.
2026-05-17 18:31:23 -04:00
Fimeg
f0290f7eed feat: default-profile sufficiency — substrate prompt, seeding, n1 gate
Audit found the default profile's subconscious running on a hollow stub
and the n1_enabled toggle wired to nothing. Fixes:

- New src/core/seeds.rs: SUBSTRATE_PROMPT (how the substrate works),
  DEFAULT_PERSONA (grown-from template), DEFAULT_COVENANT, DEFAULT_STATE,
  SUBCONSCIOUS_MANDATE, subconscious_persona().
- prompt.rs: build_system_prompt_full injects SUBSTRATE_PROMPT whenever
  [agent] system_prompt is unset — every agent wakes knowing its world.
- agent_inventory.rs: new agents seeded with the grown-from persona +
  covenant + state; subconscious seeded with real persona + four-fold
  mandate + the six ledger files, no more placeholder stub.
- local.rs: n1_enabled now actually gates the N+1 pass (global AND
  per-agent must be on). It was read nowhere before.
- Drop the unused kimi-k2.5-turbo model everywhere; default is now
  openai/kimi-k2.6 (config.rs, main.rs template, both toml files, setup).
- chat.rs: complete the truncated wrap_text for the btw word-wrap fix.
2026-05-17 18:16:20 -04:00
Fimeg
3096b9d7b5 feat: non-blocking N+1 + iMessage-style chat surface
Decouple the subconscious pass from the user-facing turn so she is
never held hostage to N+1:

- PrimaryComplete event releases the input the moment her response
  commits; the N+1 pass runs behind it on the still-open stream
- on_response takes an owned session snapshot — no live DashMap ref
  held across the (long) pass, so a concurrent next turn can't
  deadlock on the shard lock
- TurnPhase::Subconscious — the status line reads "Subconscious",
  not "Streaming" (bug B-002)
- the subconscious now persists her own Session across N+1 passes,
  with her own compaction engine wired in

Rework the chat surface toward bubbles back and forth:

- tool calls fold into a footer on her bubble (Ctrl+T expands the
  full cards); leading/trailing runs attach to the right bubble
- streaming tail fades toward the background with a live caret
- in-flight tools breathe cyan↔purple; settle to ✓/✗ outcome glyphs
- click any bubble to copy it — ⧉ title cue, arboard clipboard
- breathing room around tool runs; entry shimmer on new messages
2026-05-17 14:08:51 -04:00
Fimeg
d55bb99ba9 chore: track Cargo.lock, gitignore voice recordings
souveraine is a binary — committing Cargo.lock gives reproducible
builds when installing on another machine and a stable dependency set
for the audit. Voice-input test recordings (voice-recording-*.mp3) are
transient and now ignored.
2026-05-16 20:07:35 -04:00
Fimeg
749a72b8a0 feat(tui): semantic stream buffer for smooth token flow
Streamed tokens no longer append straight to the visible message.
They land in a stream_buffer; advance_tick releases a proportional
slice each frame (~1/3 of what is waiting, min 8 bytes), so a burst
of tokens reveals as steady flow rather than a sudden block. The cut
prefers a nearby whitespace break so no partial word flashes.

finalize_streaming flushes the buffer so a turn never ends with
unrevealed text. The 30 FPS redraw decoupling was already in place.
2026-05-16 20:00:25 -04:00
Fimeg
b98daa7f25 feat(tui): cenno / her-voice interstitial register model
Mid-turn text the model emits alongside tool calls is no longer a flat
italic line. A new Register splits it in two: a *cenno* — a short
ambient aside attached to tool work, kept quiet and italic — and
*her-voice* — a substantive passage rendered with a left gutter bar and
no italic, so it reads as her actual voice rather than a whisper.

The backend classifies by word count at emit time against the new
tui.cenno_word_threshold (default 30), exposed as an editable field in
the Settings TUI category.
2026-05-16 19:56:05 -04:00
Fimeg
73ab402b19 feat(tui): HealthPane — live substrate vitals panel
A second sidebar component beneath the cockpit. Shows context pressure
(tiered bar), backend connectivity, N+1/N+25/N+100 cadence, last
compaction warning, inference strain (504/429/error tallies — the 504s
Casey kept seeing), energy and mood, plus session uptime in the title.

Pure accumulation from existing TuiEvents — no new backend wiring.
2026-05-16 19:50:50 -04:00
Fimeg
27d86fe4ca refactor: rename Aster -> subconscious across src/
Aster is Ani's personal subconscious-agent name, not a framework
concept. The codebase now refers to the N+1 pass and its components as
subconscious everywhere — const names, fn names, variables, comments,
the schedule source tag, the todo source enum, and UI strings.

Mechanical rename, no behaviour change. grep -i aster src/ now returns
only Faster-Whisper. reference/ and docs/ historical context untouched.
2026-05-16 19:44:51 -04:00
Fimeg
5c2632762a feat(archivist): land N+100 memory synthesis pass
ArchivistEngine scans journal entries written since the last synthesis,
sends them to a compression model, and writes a dense fragment to
system/synthesized/{end-date}.md with a covers-marker for idempotent
resume. ConsciousnessEngine::on_response calls maybe_synthesize — fires
on interval (maintenance) or pressure threshold (emergency), no-ops when
no journal entries are new. Replaces the placeholder pressure check.

This was uncommitted work-in-tree; preserved here as its own commit so
it is not lost or conflated with unrelated changes.
2026-05-16 19:40:30 -04:00
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