# Agent presence

Agent presence answers one narrow question: **which agent sessions can this
machine currently observe?** It does not decide which agent a conversation
controls, approve a tool on another process's behalf, or turn a terminal
session into a model behind the Panel.

That distinction matters most where the UI is smallest. A Codex process can be
running beside Souveraine while the Panel composer still addresses only the
selected Souveraine agent. Showing both is useful. Making them look equally
selectable is a lie.

## Three different facts

Keep these independent even when one menu draws them together:

- **controlled agent** — the Souveraine agent selected through `/v1/agents`;
  the Panel can create or resume its conversations and send it a turn;
- **observed session** — a recent Souveraine, Claude Code, or Codex record found
  by the local collector; it may be active, recent, or idle, but observation
  grants no action;
- **provider health** — whether a provider's source was actually inspected.
  Unavailable is not the same as available with no sessions.

The controlled agent is a server relationship. Observed activity is a local
projection. Provider health is evidence about that projection. Do not infer
one from another.

## What exists now

The current path is deliberately small and transitional:

1. `scripts/agent/agent-sessions.sh` inspects recent local records for
   Souveraine, Claude Code, and Codex and emits one JSON envelope.
2. `services/AgentSessions.qml` runs that collector on one cadence, retains the
   last good envelope, and projects sessions, provider health, active count,
   staleness, and compact labels.
3. `AgentPaneMenu.qml` shows Claude Code and Codex sessions beneath
   **Running elsewhere · observed only**. Those rows are inert.

The shell also retains `modules/souveraine/island/Island.qml` and
`IslandExpansion.qml`, written against the same service. They are not mounted
in the current bar or Panel. They are reusable observer components, not a
second live presence surface and not a reason to put agent chrome back above
the conversation.

They are also not abandoned. Their next proper home is the phone's later
dynamic top-bar system, once the bar can host more than a fixed row of widgets.
The present Panel move lets that larger work wait without making the Island
components disposable.

This is source and current composed-surface behavior. It is not the proposed
Rust session daemon. The collector still starts a shell process from QML on
each poll.

## The envelope contract

Transport may change; these meanings must not:

- `sessions` is newest first and contains provider identity, stable session
  identity, a useful label source, last activity, activity state, and only the
  usage fields the provider actually supplied;
- `providers` reports availability, errors, source-specific limits, and the
  number of sessions admitted from each source;
- `active` counts records whose observed activity falls inside the active
  window;
- `ts` says when the envelope was collected, not when every underlying source
  was updated;
- a failed provider degrades inside the envelope instead of aborting the other
  providers;
- a failed whole refresh keeps the last good model and marks it stale instead
  of replacing it with an authoritative-looking empty list.

Unknown is a real value. The current shell uses `-1` for an unavailable token
measurement and renders an em dash, never zero. Souveraine now persists
per-turn usage with assistant messages, but `conversation.json` does not expose
an aggregate for the collector, so the presence projection still reports
Souveraine session usage as unknown. That projection may be improved; the UI
must not manufacture a total in the meantime.

Claude's local usage is read from a bounded recent tail and is labelled as
windowed. Codex's latest token-count record is cumulative for that rollout.
Those numbers are not interchangeable merely because both fit in one field.
Any combined view must retain the source semantics or omit the comparison.

## Activity is a heuristic, not cognition

The present collector derives:

- `active` from activity within two minutes;
- `recent` from activity within one hour;
- `idle` from older admitted records.

These are filesystem/event-age classes. They do not prove that an inference is
running, a tool is still alive, an agent is waiting for Casey, or a response is
complete. In particular, there is no synthetic `waiting` state. Waiting for a
permission decision requires a real request lifecycle; timestamps cannot stand
in for it.

The Panel's own active-turn, primary/subconscious phase, cancellation, and
terminal event must continue to come from the Souveraine stream described in
[the agent panel](04-agent-pane.md). Presence may corroborate that state. It
must not overwrite it.

## Where presence belongs

The close conversation surface puts observed external sessions inside the
footer's agent menu because that is where mistaken control is easiest to
prevent: selectable Souveraine agents first, an explicit divider, then inert
external rows.

A bar, island, overview, or diagnostics page may project the same service when
the device has room and the projection answers a real glance question. On a
phone, permanent agent-session chrome competes with system state and was
rejected on glass. Absence from the bar is therefore a product decision, not a
collection failure.

No surface may:

- label a discovered process as controllable without an admitted action path;
- show unavailable providers as zero sessions;
- imply that recency means work is still running;
- expose conversation text, prompts, repository paths, or tool payloads merely
  to make presence richer;
- start its own provider poll when `AgentSessions` already owns the projection.

## The later dynamic bar

The phone's top bar should ultimately be an extensible host whose components
can enter, compact, expand, yield, and leave according to live state. Agent
presence is one such component, beside notifications, media, connectivity,
power, privacy, and other device facts. A collection of independent widgets
all changing width is not that system.

Before the Island is mounted again, the bar needs a shared contract for:

- component identity, priority, disclosure class, and device eligibility;
- resting, compact, attention, expanded, and hidden forms;
- one arbiter for horizontal space and collisions;
- preemption and return when a higher-priority event arrives;
- dwell, decay, and animation timing so rapid events do not thrash geometry;
- tap, long-press, dismiss, and expansion ownership;
- lock-state redaction and a safe minimal form;
- a stable overflow or secondary surface when several components deserve
  attention at once;
- component failure that removes one projection without taking down the bar.

`Island.qml` and `IslandExpansion.qml` are useful first clients for that host:
they already separate a compact morph from expanded detail and consume the
shared presence projection. They do not yet define the host protocol, arbitrate
against neighbours, or prove the phone form on glass. The dynamic bar is a
larger surface task carried here until it has enough implementation scope to
need its own SAF chapter; the old fixed bar placement is not its prototype by
default.

## Permission is a different authority

The earlier island exploration considered Claude hook installation and
Allow/Deny controls. None of that is present in Souveraine's current path. The
collector does not edit `~/.claude/settings.json`; there is no permission
bridge; observer rows do not jump to or command external terminals.

If permission control is built later, it needs more than an attractive card:

- the request must name the exact provider session, tool, arguments, and age;
- the producer and target must be authenticated strongly enough for the
  consequence of the action;
- allow-once, deny, expiry, cancellation, and duplicate delivery need explicit
  states;
- the decision and its delivery result must enter the audit trail;
- provider failure must fall back to that provider's normal safe behavior;
- a broad bypass or allow-all posture requires a separately admitted policy,
  not a more convenient button.

Until that path is owned and attested, presence stays read-only.

## The intended service

One long-lived session service remains the destination. It should replace the
poll subprocess, not change the envelope under every surface. The proposed
Unix socket at `$XDG_RUNTIME_DIR/souveraine-sessions.sock` is a candidate
transport, not proof that the daemon exists.

The service needs:

- one cadence and one adapter per provider;
- event-driven updates where a provider supports them, with bounded scans as a
  fallback;
- provider-specific provenance and measurement semantics preserved in the
  normalized record;
- last-good/stale behavior at both provider and whole-envelope level;
- stable session identity across refreshes;
- bounded resource use and no transcript-content collection merely for a
  presence label;
- an authenticated, separate action channel if external control is ever
  admitted.

Souveraine's own session and usage facts should eventually be projected from
the substrate directly rather than rediscovered through files written for
persistence. Claude and Codex adapters remain local observers unless their
providers expose a stronger owned event path.

## Proof boundary

Presence work is proven in layers:

1. **source** — each provider parser is fixture-tested, malformed timestamps do
   not become plausible epoch-zero state, and provider failure still emits a
   valid envelope;
2. **projection** — one service retains last-good state, marks staleness, and
   preserves unknown/windowed/cumulative measurement semantics;
3. **surface** — external sessions render under an explicit observed-only
   boundary and cannot be selected or controlled;
4. **installed** — the versioned package owns the collector/service/component
   files used by the running shell;
5. **live provider** — a real session enters and leaves each activity class,
   and a deliberately unavailable provider is visibly different from an empty
   one;
6. **authority**, if ever added — an attested decision reaches only the named
   request and is recorded end to end.

The current implementation has the polling envelope and Panel projection. The
Rust daemon, aggregate Souveraine usage projection, provider permission path,
and cross-provider control are not yet true.
