PAF becomes saf/device (history kept), STATE.md dissolves into saf/state.md with the dated era archived, the substrate SAF moves up from souveraine, and every agreement points at saf/INDEX.md and nowhere else. one map, nothing to remember
224 lines
10 KiB
Markdown
224 lines
10 KiB
Markdown
# 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.
|