Skip to content

feat: Paseo agent tiles deck (#180) - #183

Open
jonocodes wants to merge 4 commits into
mainfrom
feat/agent-tiles-180
Open

jonocodes wants to merge 4 commits into
mainfrom
feat/agent-tiles-180

Conversation

@jonocodes

@jonocodes jonocodes commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Proof of concept for #180: deckd shows your running AI coding agents as tiles you can look at but not act on. They appear whenever Paseo is focused, the same way any other app gets its own deck. There's no extra chrome button.

What

  • decks/paseo.yaml: contains a single agents widget set to size: full. It shows one tile per live agent, most urgent first. Each tile has a state label (Needs you / Error / Working / Done / Idle), a coloured accent, the title, the project, a short detail such as "Approve Bash", and how long it has been in that state.
  • Recognising Paseo:
    • Web app in a browser: Paseo sets document.title to the active agent's title and never mentions "Paseo", so no title: glob can claim it. The new agent:paseo match token checks the window title against the live agent titles the daemon already has from its Paseo subscription.
      • It matches an exact title, or <title> — <browser name>. The separator form is only accepted for browser windows, so an agent titled deckd can't claim a deckd — fish terminal.
      • It ranks with title: tokens.
      • The daemon re-resolves focus when agents appear, disappear or are renamed. It only switches if that changes which deck the focused window gets, so a deckctl deck pin isn't overridden by unrelated agent changes.
    • Paseo's non-workspace pages: title:Paseo… tokens, for pages that keep the static page title.
    • Desktop app: the paseo identity token. The installed AppImage reports Paseo as its WM_CLASS under XWayland (StartupWMClass) and as its app_id on native Wayland (desktopName); I checked this against the bundle.
  • ADR-0010 size: full now works on the client: the widget takes the whole deck area. ADR-0010 specified this, but nothing implemented it until now.
  • Status model shared by all sources: six agent states. Each source translates into these, so the wire and the client never see orchestrator-specific vocabulary. Added to CONTEXT.md.
  • First source is Paseo: PaseoAgentSource subscribes to the Paseo daemon's WebSocket and receives a snapshot plus agent_update pushes. No polling.
    • It authenticates with the same-user local-credential, which it re-reads on reconnect.
    • It waits for server_info before subscribing, to avoid close code 4002.
    • It ignores agent_stream noise.
    • If Paseo goes away it clears the tiles and retries with backoff.
  • Flags: --agents {auto,paseo,off} (auto turns it on when ~/.paseo exists) and --paseo-url.
  • Playground: Paseo is a virtual app with a fixture fleet, and one agent cycles through the states. Open /?playground and pick Paseo under running programs.

Why the WebSocket rather than the CLI

Research is in docs/research/agent-orchestrator-status.md. It covers Paseo, T3 Code, opencode, Claude Code hooks, Vibe Kanban, Nimbalyst, Claude Squad, Conductor, Crystal and tmux.

  • paseo ls --json leaves out the permission and attention fields.
  • Each CLI call starts Node, which takes about 0.57 s.

Privacy

  • Tiles show Paseo's agent title, which is usually taken from the first prompt. That matches Paseo's own sidebar, and the view sits behind deckd's password gate.
  • Permission details name only the tool, never its input. There's a test for this.

Verification

  • The full just test-all run passes: pyright, pytest (836), focus-wire, tsc, vitest (410), Playwright e2e, smoke and eslint.
  • New tests:
    • Title matching, including the terminal false-positive guard.
    • The shipped Paseo deck claims web, home-page and desktop windows over firefox.yaml.
    • Focus re-resolves when titles arrive and when they leave.
    • The full-surface agents widget renders.
    • There is no chrome button.
    • The source runs against a fake Paseo daemon.
  • Fixed a socket leak: closing the source's iterator never closed the Paseo socket. Because of it, each source test spent 10 s in teardown.
  • Checked read-only against the live local Paseo 0.12.0.

Not yet verified on the real desktop (needs human-verification)

  • Real window titles: the GNOME focus extension wasn't on the bus while I worked, so the real Paseo window titles weren't observed. The matching is based on Paseo's source.
    • WorkspaceDocumentTitleEffect sets the title to the tab label. A terminal or file tab in Paseo won't match any agent title, so the browser deck shows instead.
    • That the static title is "Paseo" is an inference from Expo's %WEB_TITLE%.
  • Electron desktop app: the identity comes from the bundle's metadata; I haven't seen the live window yet.

Known gaps / follow-ups

  • Paseo reports idle while background subagents are still working.
  • A second source (Claude Code http hooks) would need a push endpoint.
  • Paseo's WebSocket protocol isn't a public API. The source logs server_info.version and shows no tiles if the shape drifts.

This doesn't close #180 yet; it's a POC.

Screenshots

Paseo focused — tablet (Playground fixture)

Paseo focused — tablet (Playground fixture)

Paseo focused — phone landscape

Paseo focused — phone landscape

A source-agnostic agent status model (six agent states, urgency-ordered)
pushed as an `agents` snapshot frame, rendered in a client-local agents
chrome view. The bot chrome button appears once a source is live and
carries an amber count while any agent needs you.

First source: PaseoAgentSource subscribes to the Paseo daemon WebSocket
with the same-user local-credential (no polling, no config), mapping
Paseo snapshots via its own state-bucket precedence. Closed/archived
agents get no tile; permission details name the tool, never its input.

--agents {auto,paseo,off} (auto = on when ~/.paseo exists), --paseo-url.
Playground fixture with a cycling agent for backend-free demos.
Agent tiles now show when Paseo is focused, like any other app, instead
of behind a dedicated chrome button.

- decks/paseo.yaml: one full-surface `agents` widget; matches the
  desktop app identity, Paseo's static page title, and a new
  `agent:paseo` token.
- `agent:SOURCE` match token: Paseo's web app sets document.title to
  the active agent's title and never names itself, so it's recognised
  by matching the window title against the live agent titles from the
  agent source (exact, or "<title> — <browser>" for browser windows
  only). Site-tier, like `title:`; focus re-resolves when titles change.
- ADR-0010 `size: full` is now real on the client: the widget takes the
  whole deck area.
- Removed the agents chrome button, /agents route and key 6.
- Fix: closing PaseoAgentSource.watch() now closes the Paseo socket
  (inner generator wrapped in aclosing); it previously leaked until GC.
@jonocodes jonocodes changed the title feat: view-only agent tiles fed by Paseo (#180) feat: Paseo agent tiles deck (#180) Oct 26, 2026
The Electron app reports "Paseo" as WM_CLASS under XWayland
(StartupWMClass) and as app_id on native Wayland (desktopName), both
covered by the case-insensitive `paseo` token. sh.paseo.desktop is the
electron-builder bundle id, never reported on Linux.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Explore using deckd as display/status for running agents

1 participant