tasks: 69 stage 1 and 70 island built
This commit is contained in:
parent
9669b0c3e3
commit
74e3bc5233
2 changed files with 253 additions and 0 deletions
108
docs/tasks/69-agent-usage-session-daemon.md
Normal file
108
docs/tasks/69-agent-usage-session-daemon.md
Normal 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.
|
||||
145
docs/tasks/70-the-island-agent-surfacing.md
Normal file
145
docs/tasks/70-the-island-agent-surfacing.md
Normal 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.
|
||||
Loading…
Reference in a new issue