Watch
1
0
Fork
You've already forked SouveraineOS
0

tasks: 69 stage 1 and 70 island built

This commit is contained in:
Fimeg 2026-08-11 14:23:56 -04:00
commit 74e3bc5233
2 changed files with 253 additions and 0 deletions

View file

@ -0,0 +1,108 @@
# TASK 69 — Agent usage, one spine: the session daemon
**Status:** recon 2026-08-11, nothing built. **Size:** staged — see below.
**Sources reviewed:** `knyazin47/codex-usage-quickshell` (MIT, upstream clone
for reference), the existing `ClaudeUsage` in `souveraine/surfaces/quickshell/ii-base/`,
and `souveraine/src/core/session/`.
## Goal
One collector tracks every agent's activity — **Souveraine, Claude Code, and
Codex** — and feeds the shell. The bar stops owning a fetch per provider; the
shell shows projections of one live stream.
## What already exists (verified 2026-08-11)
| Source | Truth lives at | Currently read by |
|---|---|---|
| Codex usage + limits | `~/.codex/sessions/**/*.jsonl` (`event_msg` / `payload.type == "token_count"`: total/input/cached/output/reasoning tokens, `rate_limits` embed, `session_meta` cwd) | upstream python collector `codex_usage.py` |
| Codex live limits | `codex app-server --stdio` JSON-RPC (`initialize``account/rateLimits/read`, multi-limit map `rateLimitsByLimitId`) | same collector, cached at `~/.cache/codex-usage-quickshell/rate_limits.json` |
| Claude usage | OAuth token `~/.claude/.credentials.json``https://api.anthropic.com/api/oauth/usage` (5h/7d utilization, per-model, extra credits, resets) | `ClaudeUsage.qml` + `ClaudeUsageBar.qml` in ii-base |
| Claude local sessions | `~/.claude/projects/<slug>/*.jsonl` — per-assistant `usage` with input/output/cache_creation/cache_read + model + timestamp | **nothing** — gap |
| Souveraine sessions | `core/session/mod.rs` `Session`/`ConversationMessage` with `TokenUsage`; `assistant_with_usage` has **zero callers** (TASK-67) | **nothing** — gap |
The two gaps are the same shape: the substrate already records the truth, and
nothing consumes it. The daemon is the consumer that ends TASK-67's blindness
as a side effect.
## Staged build
1. **Collector daemon** (Rust, in `souveraine`): one unix socket
(`$XDG_RUNTIME_DIR/souveraine-sessions.sock`), newline-delimited JSON, the
wire shape already proven by the island's `AgentService.qml` (TASK-70):
per-provider session table, urgency-ranked list, pending permission queue,
IPC verbs. Providers: Codex (port the collector's JSONL parse + app-server
probe), Claude (local JSONL + OAuth usage poll), Souveraine (substrate
session files).
2. **Envelope** — one JSON shape for all three: window label, used percent,
resets, tokens today/week/last turn, activity series. The upstream
collector's output is already close; don't invent a second vocabulary.
3. **Shell projections**`ClaudeUsage.qml` and the Codex panel become
thin reads of the daemon socket. Delete the per-provider timers/processes
from QML; one daemon owns cadence.
4. **Limits merge** — Codex live + cache + local fallback ladder already
exists upstream; Claude's OAuth poll has a retry ladder. Keep both, from
one owner.
## Acceptance
A bar showing both providers from one socket. A killed daemon degrades to
"unavailable", never blanks the bar. The Souveraine substrate's own
`assistant_with_usage` counters appear on the same panel — TASK-67 gets its
surface.
## Connects to
TASK-70 (the island — the daemon is its data source), TASK-67 (usage counters
finally read), `ClaudeUsageBar.qml` + `codex_usage.py` (the code being
absorbed), `core/session/mod.rs` (canonical session record).
---
## Stage 1 landed 2026-08-11 — the envelope exists and is real
Commits `47ce2d1`, `b211842` in `souveraine@primary`; activating edits queued as
`surfaces/quickshell/patches/0005`.
**`scripts/agent/agent-sessions.sh`** emits the envelope described above, from
all three sources, in 0.47 s. Both gaps in the table are closed: Claude local
JSONL and Souveraine conversations are read for the first time.
**The envelope is the contract; the transport is not.** The collector is a
polled script, not the Rust daemon. That is deliberate — it produces the exact
shape the daemon will serve over
`$XDG_RUNTIME_DIR/souveraine-sessions.sock`, so the surface can be built and
proven against real data now, and the daemon swap changes one line in
`AgentSessions.qml` and nothing downstream. Do not let surface code reach past
the service's properties into the JSON.
**Contract held deliberately:** the collector always exits 0 and always emits
one line. A provider that fails reports `available:false` with a reason rather
than taking the envelope down. A blank bar is a worse failure than a stale one,
so `AgentSessions` retains last-good data and raises `stale` instead of
emptying.
### Two bugs found by running it, not reading it
- jq's `fromdateiso8601` accepts **only** `%Y-%m-%dT%H:%M:%SZ`. All three
providers emit fractional seconds (Claude `.179Z`, Codex `.238Z`, Souveraine
`.126950960Z`), so every timestamp parse failed — and a bare `try/catch`
turned that into epoch 0, which rendered as "idle" for sessions that were
live that second. The failure looked exactly like a correct answer. Fixed by
stripping the fraction, and a parse miss now yields -1 so a future breakage is
visible rather than plausible.
- The Codex session id was the rollout **date prefix**, not the uuid.
### TASK-67's blindness is now visible instead of theoretical
Souveraine sessions report `tokensIn/Out: -1`, not 0, because the substrate
records `TokenUsage` but `assistant_with_usage` still has zero callers. -1 is an
admission; 0 would be a measurement claim we cannot back. Surfaces render it as
"—". The day TASK-67 lands, real numbers appear and nothing else changes.
### Still stage 1
- ClaudeUsage keeps its own OAuth poll. Stage 3 folds it in; until then
`AgentSessions.codexLimits` is honest about covering only what the collector
sees.
- Nothing renders yet. See TASK-70.

View file

@ -0,0 +1,145 @@
# TASK 70 — The island: agent surfacing for the shell (and her face)
**Status:** recon 2026-08-11, nothing built. **Adjacent to:** TASK-69 (the
session daemon is this surface's data source). **Source reviewed:**
`patheonsceo/Dynamic-island-for-arch` ("Open Agent Island", upstream clone for
reference). **Twines with:** TASK-59 (the avatar).
## What it is
A macOS-style dynamic-island desktop for Hyprland, forked from the same
**end-4 / illogical-impulse** base Souveraine's ii-base derives from (the file
trees match nearly 1:1 — services/, scripts/, modules/ii/, translations/).
Three floating islands; the center **notch** morphs through states: clock →
volume/brightness OSD → media → notifications → **live agent status**
permission cards. Headline feature: **Claude Code permission Allow/Deny from
the notch**, with live multi-session tracking, jump-to-terminal, and mode
chips (auto-edit / bypass / plan).
## What's worth taking (verified in the clone)
- `services/AgentService.qml` — a `SocketServer` singleton holding the session
table, urgency-ranked list, pending-permission queue, per-session
bypass/allow-all rules, staleness pruning, and an IPC surface
(`qs … ipc call agent allowOldest`). This is a working proto-session-daemon.
- `bridge/oai_hook.py` + `test_safety.py` — Claude Code hook client with a
hard safety contract: never blocks or breaks Claude, any failure exits 0
with no output so Claude falls back to its **normal prompt**, never
auto-approves, 13/13 safety checks.
- The notch mechanics (`IslandNotch.qml`, 692 lines) — morph states, gooey
springs, per-monitor anchoring.
- `AgentSurface.qml` — the permission card: Deny · Allow Once · Allow All ·
Bypass, with a live preview of the pending tool call.
## What to consider, not copy
- The upstream is a fork of end-4's base **plus** a whole second waffle shell
(15 MB with assets). Souveraine's ii-base has diverged enough that the
shape to take is the **mechanisms** — AgentService, the bridge, the notch
morph host — ported onto ii-base, not the shell wholesale.
- Claude-only today. The bridge speaks Claude hooks; TASK-69's daemon
generalizes the same wire to Souveraine and Codex, and the island becomes
one of its surfaces.
## The twine with TASK-59
The notch is a morph host with an idle state — and Annie needs a home that
isn't a summoned full-screen overlay. Her rig can be **one of the morph
states**: idle face in the notch, expanding for agent activity, leaning in
when a permission request lands. The agent stream that drives the island is
exactly the stream that can drive her expression (working → thinking,
waiting → looking, done → settling back). TASK-59's "summoned, not always-on"
survives: the compact notch is the face at rest; she expands only when
something happens. The reference rig and the Live2D runtime stay private to
the phone task — nothing public.
## Open questions
1. Adopt the notch as the shell's surface idiom, or keep the ii bar and lift
only the agent card? Casey's call.
2. The bridge installs hooks into `~/.claude/settings.json` — reversible, but
it edits live config. Gate on the same trust boundary as the rest of the
agent control (TASK-30, TASK-41).
3. Permission-from-the-notch is a security surface: the decision path must be
attestable like the rest of the verbs, or it stays read-only status.
## Acceptance
A ported `AgentService` + bridge running against ii-base showing one real
Claude Code session's status and one permission card, with the safety suite
green. Everything else — the morphing, the avatar twine — is staged after.
## Connects to
TASK-69 (daemon), TASK-59 (avatar), TASK-30/TASK-41 (verb tables, attested
producers), TASK-56 (the *other* island — the notification banner region;
name collision, distinct work).
---
## Built 2026-08-11 — souveraine-native, not a port
Commits `47ce2d1`, `b211842`; mount queued as
`souveraine/surfaces/quickshell/patches/0005`.
**Correction to this task's premise:** the upstream clone
(`patheonsceo/Dynamic-island-for-arch`) is **not on disk anywhere on the
laptop**. Checked `~/Projects`, `~/Projects/references`, and a depth-4 sweep.
So nothing was lifted — `modules/souveraine/island/` is written against
TASK-69's envelope directly. That is arguably the better outcome: no 15 MB
fork, no second waffle shell, no divergence against the ii pin.
### Open question 1 is answered — and it was a false binary
> *Adopt the notch as the shell's surface idiom, or keep the ii bar and lift
> only the agent card?*
Neither, as posed. `Island.qml` is a **morph host** mounted in the existing bar:
`hidden` (no sessions — occupies nothing, not a placeholder) → `dot` (a pulse,
and only while something is genuinely active) → `pill` (provider glyph, label,
state) → `expanded` (every session, flat and chronological — grouping by
provider would bury a live Codex run under three idle Souveraine threads).
So the notch mechanics arrive without adopting the notch as the shell's idiom,
and the ii bar survives. If the notch is wanted later it is a new resting form
on the same host, not a rewrite.
### Device types are a real difference, not a setting
The resting form is derived from
`Appearance.sizes.barShortenScreenWidthThreshold` — the *same* test
`BarContent` uses — so the island narrows exactly when its neighbours do. One
authority for "is this bar cramped"; the island is a rendering of it, never a
second opinion. `dot` on the Pixel 3 bar, `pill` on the desktop.
Mounted in `modules/ii/bar/UtilButtons.qml`, which is already a
Souveraine-owned override, so it reaches **both** device bars without forking
the 13.8K `BarContent` twice (ii-base *and* ii-phone) and carrying that
divergence against the pin.
Host-agnostic in the `SubconsciousTicker` sense: it owns no overlay state and
reaches into no manager. Expansion is local; `requestOpenPanel()` is a signal
for whoever mounted it. That is what will let the same file serve a viewtop
node later without a fork.
### Deliberately NOT built: the permission path
No Allow/Deny, no `~/.claude/settings.json` hook install. Open questions 2 and 3
are unanswered, and 3 is the binding one — *permission-from-the-notch is a
security surface; the decision path must be attestable like the rest of the
verbs, or it stays read-only status*. So it stays read-only status. There is
also deliberately **no "waiting" state** in the collector: knowing an agent
awaits a decision requires the hook bridge, and deriving it from timestamps
would be a guess wearing the costume of a measurement.
### Status
Untested against a running shell — nothing has rendered. `qmllint` is clean on
all four files, but it cannot resolve `qs.*` imports from the repo, so
property-level mistakes will only appear at load. Acceptance ("one real session
showing, safety suite green") is **not** met: the sessions are real and shown,
the permission half is not built.
**Method note:** `/usr/bin/qmllint` is a **Qt5** binary that prints nothing and
exits 0 on broken input — it is not a gate. Use `/usr/lib/qt6/bin/qmllint`, and
prove any gate by feeding it something broken first.