Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/tasks/70-the-island-agent-surfacing.md

7.5 KiB

TASK 70 — The island: agent surfacing for the shell (and her face)

Status: source implemented; relocation into the AI panel is uncommitted and not installed as of 2026-08-11. 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

The first bar rendering was installed on laptop and phone. Casey's glass review rejected that placement: the phone bar has no room for agent-session chrome, and activity belongs beside the conversation it describes.

The source now removes island from both bar profiles and mounts a compact, expandable AgentSessionPanel at the top of AiChat. It consumes the existing AgentSessions singleton and IslandExpansion; no second collector or session model was introduced. This relocation is source-only until packaged. The phone may receive it with the next package; the laptop's live shell must wait for Casey's coordinated larger push.

Acceptance remains partial: real session status is implemented read-only; the permission half is deliberately 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.