Where attention appears to go across agent threads: collector, web explorer, and native iOS client
TypeScript 39%
Python 30%
JavaScript 22%
CSS 6%
HTML 3%

README.md

Agent attention explorer #

Start with the handoff for current architecture, operations and verified state. The native iOS client lives in mobile/; its goal and parity list are in docs/ios-goal.md and docs/ios-parity.md.

A private session dashboard and read-only MCP with PDS sign-in.

The MCP App shows the latest 20 sessions and has a manual refresh action. The standalone dashboard has live polling, search, provider and activity filters, archived Codex sessions, and recent events. History includes Claude Code subagents (hidden by default; include them from the filters); it does not read Claude Desktop chat history. Activity is inferred from logs, not process liveness.

Run and update #

uv sync --frozen
uv run python server.py
uv run python mcp_server.py
uv run python -m unittest discover -s tests -v
uv run python scripts/deploy_local.py

The deployed runtime is ~/.local/share/agents-mcp. LaunchAgents io.zzstoatzz.agents-collector, io.zzstoatzz.agents-mcp, io.zzstoatzz.agents-summarizer and io.zzstoatzz.agents-tunnel restart at login. The collector and web server use separate macOS sandbox profiles. The web server reads only a projected, redacted snapshot; the collector has no network access. The remote endpoint requires this Mac to be awake and connected. The optional local development dashboard uses port 8765 and is started separately with server.py; it reads raw histories and does not apply the production snapshot projection.

~/.config/agents-mcp/server.env and tunnel.json are mode-0600 derived consumers of the SOPS agents_mcp entry. scripts/configure.py refreshes them without printing values. The allowlist matches home: only Nate's configured DID. Agents uses a distinct signing key and tunnel, requires consent for each authorization, and accepts only allowlisted DIDs before identity resolution.

The same FastMCP commit as home is pinned in uv.lock. owner_auth.py depends on its experimental provider internals and must be rechecked when upgrading.

Remaining work #

Complete a user-driven PDS sign-in in the intended MCP client. The unauthenticated boundary and local MCP calls are verified, but a new authenticated remote client session has not been exercised.

Replace raw TUI keystroke control with a structured agent input channel, or prove safe delivery and modal handling before adding public mutation tools. The prototype mutation provider is available only through an explicit code-level build_server(enable_control=True) call. Its owned-terminal registry and exec launch mitigate shell fallthrough, but do not solve modal input. Existing standalone sessions have not been migrated.

Browser access #

Open https://agents.zzstoatzz.io/ on any device and choose “Sign in”. The configured single allowed DID is sent directly to its PDS; no arbitrary identifiers are resolved. The browser dashboard uses a separate identity-only AT Protocol OAuth client at /web/client-metadata.json, with PKCE/DPoP handled by atproto-oauth. Its callback binds each attempt to a separate secure HttpOnly cookie. PDS grant revocation is attempted immediately after identity verification; only opaque 12-hour dashboard sessions remain, persisted in the private SQLite auth store. Server restarts preserve valid dashboard sessions and pending sign-in state. Every data request rechecks the DID allowlist. Logout deletes the local session; there is no retained PDS grant to revoke.

The owner completed production browser sign-in earlier. Durable auth state, fresh PDS initiation/cancellation and callback binding/replay were subsequently verified; a full owner password round trip after those changes is not newly verified. The current suite has 46 Python and six JavaScript tests. Public MCP and dashboard data reject unauthenticated requests.

Dashboard UI #

The responsive session inbox groups history by date and opens a dedicated reader on phones. Search, provider tabs, project/activity filters, archived/subagent controls, and system/light/dark appearance are available. Inputs use 16px text; pinch zoom stays enabled. The interface accommodates safe areas, reduced motion, keyboard focus, browser navigation, and background-tab polling suspension. A scrolled reader offers New activity instead of inserting updates into the reading position. UI assets can be copied to the installed runtime without restarting the service or clearing existing logins. Browser regression coverage is recorded in tests/ui-regression.md.

Production security #

The public service does not connect to the Orca relay or expose terminal reads or controls. Cloudflare Tunnel forwards to loopback port 8766. The collector exports up to 2,000 sessions with bounded recent excerpts, opaque identifiers and best-effort credential/path redaction. The export remains sensitive private data.

Host/origin checks, request size/time/concurrency limits, bounded OAuth storage and registration/login rate limits apply. URL-based OAuth client metadata discovery is disabled; dynamic registration remains supported. Browser sessions persist in private SQLite across restarts; MCP grants remain in memory and reset at restart. See SECURITY.md for exact boundaries, tests and remaining risks.

Attention explorer #

Open https://agents.zzstoatzz.io/attention. The first screen is a daily recollection: a short recap and recognizable areas of work. Tap an area for its supporting words, conversations, and other days in those same conversations. Find searches areas and source excerpts across the 30-day history. Sessions, Your day, Timeline, and Find share one shell, selected day, appearance, calendar, and detail sheet. Timeline filters are collapsed until needed; breakdowns, transitions and estimation controls remain available.

These are signs of apparent attention, not measured attention. Durations, productivity, and completed work are never inferred by the recap. Topic grouping is model-generated and can be wrong; the original excerpts remain visible. Other-day links mean shared conversation, not a claimed semantic relationship. Dates use the viewer's time zone.

The collector updates a private SQLite index, metadata-only attention.json, and redacted attention-evidence.json every 30 seconds. Signals are Codex user messages and user voice segments, and Claude user messages without SDK-origin, sidechain, or meta flags. Known environment/instruction/continuation/voice-handoff wrappers and MCP app notifications are excluded. Identical same-thread signals within two seconds are deduplicated; Codex input predating thread creation is excluded. These are heuristics, not proof of human origin. Reading, device focus, other devices, and silent attention are not observed. Orca-managed Codex/Pi histories are included; known dispatched-worker injection prompts are excluded from attention signals. Read-only Orca metadata enriches workspace context.

The summarizer reads at most 1,400 redacted characters per interaction. It sends those excerpts and thread context to OpenAI gpt-6-luna, with store=False. A first structured pass assigns individual interactions to subjects; a second describes each subject using only its assigned excerpts and returns source IDs. Validation rejects missing/duplicate assignments and citations outside the assigned evidence. Long group descriptions sample at most 48 excerpts. Busy days support up to 20 subjects; the UI initially shows five. The current guard allows 600 interactions and 700 KB of grouping input per day; oversized days remain available as conversation lists with an explicit recap error.

io.zzstoatzz.agents-summarizer checks every five minutes, generates up to three changed days per pass, and caches results by input, model, and prompt version. Existing summaries stay usable during updates and failures. Pending days expose their conversations; stale/error notices distinguish missing updates from empty history. Cache entries outside the rolling window are removed. Token usage is recorded locally with each generated day.

The dedicated key is read directly from SOPS agent_explorer.OPENAI_API_KEY at each refresh. No plaintext env file is used. To rotate: replace that entry, verify one new generation, then revoke the prior key. The selected key expires after 30 days. The summarizer runs in its own macOS sandbox; the web process reads only the projected digest, not the credential or evidence index. See SECURITY.md.

/attention/timeline is a compatibility entry into the Timeline tab at /attention?view=timeline. The duplicate timeline document and stylesheet have been removed. The tab preserves detailed signals and project/repository breakdowns. Its optional gap estimate joins globally consecutive same-thread signals within a selected 1–15 minute threshold, without idle tails or bridging filtered-out interactions. That estimate is not attention time. Repository identity comes from recorded Git origin; language is not inferred from folder names.

Validation: uv run python -m unittest discover -s tests -v and bun test. Parser-version changes rebuild only the derived index; raw histories are never modified. Browser checks are in tests/ui-regression.md.

UI ownership #

  • index.html and attention.css: the shared Sessions, Your day, Timeline and Find shell and visual system. style.css contains scoped session-list/reader styles; app.js exports the Sessions component. Legacy Attention URLs serve the same shell.
  • attention.mjs: view routing, selected date, shared sheets, recaps and search.
  • attention-shared.mjs: Chicago dates and boundaries, safe repository references, conversation breadcrumbs and DOM helpers.
  • attention-timeline.mjs: lazily mounted timeline component with scoped controls and its own fetched history; delegates navigation and sheets to the shell.
  • attention-model.mjs: pure gap estimation and aggregation, unchanged by the UI consolidation.

Timeline data is fetched only when needed; tab switching reuses in-memory data for up to a minute. Gap estimates are cached per threshold and cleared on new history; search is debounced. Repo breadcrumbs use recorded remote metadata and never infer a remote from a workspace name. Existing metadata can miss a repo discussed from a parent workspace. Both views use the viewer's time zone boundaries, including DST, and a historical range warns when it extends beyond retained data.

Additional regression checks: bun test (shared domain, palette contrast, deep links).

Repository context is resolved from the session remote, local Git origin (including worktrees), and Orca's read-only profile database. Explicit tool working directories produce separate repository references; these are not inferred from prose or used as evidence of human attention. Codex history includes Orca's separate runtime logs with session-ID deduplication. Pi user messages are included, with fork history bounded by the new session timestamp. Orca workspace names, branches and linked GitHub PR/issues appear in conversation details. Current checkout metadata can change after a conversation.

Browser sessions and pending OAuth state persist in ~/.local/state/agents-mcp-auth/auth.sqlite, outside exported data. App session tokens are hashed, app sessions retain their 12-hour expiry, and pending state expires after 10 minutes. Deployments no longer erase either. The PDS controls its own authorization-page expiration; a stale PDS request must be restarted from the app.

Provider marks are local assets from @lobehub/icons-static-svg 1.95.1 (MIT, see icons/LICENSE); Pi uses a local pi glyph. No external image requests are made.