roxabi-sense
Official# roxabi-sense
**Workstation attention journal** — light sensors, local store, agent surfaces.
> Status: **phases 1–3 live** (collectors + store + CLI + daemon + MCP stdio) · NATS still stub
> Not screenpipe. Not a web SaaS. Not inside `roxabi-factory`.
> Agent SSOT: [`AGENTS.md`](./AGENTS.md) · Claude shim: [`CLAUDE.md`](./CLAUDE.md)
---
## Why
Screen capture + OCR is the wrong tool for “what was I doing?”.
You already have timestamped work in `~/.claude` and `~/.grok`. Meetings live in Claap. What is missing is a **cheap focus spine**: which app/window was active, which agent sessions were open, whether Slack/Discord was running — without pixels, keyloggers, or a 174 MB trial for 40 seconds of frames.
`roxabi-sense` is that spine. It publishes **facts**, not policy.
---
## What it is / is not
| Is | Is not |
|---|---|
| Local user-session daemon (systemd `--user`) | Factory hub module |
| CLI + optional MCP + optional NATS publisher | Screen OCR / continuous screenshots |
| Reads existing Claude/Grok session artifacts | Re-logs AI conversations |
| Focus / idle / process presence | Meeting recorder (→ Claap) |
| Edge sensor for Sentinelle later | Sentinelle decision brain (→ factory hub) |
---
## Architecture (target)
```
collectors (facts only)
┌─────────────┐ ┌──────────────┐ ┌────────────────┐
│ focus/idle │ │ agent sessions│ │ process presence│
│ (Wayland / │ │ ~/.claude │ │ slack/discord │
│ AT-SPI) │ │ ~/.grok │ │ (running?) │
└──────┬──────┘ └──────┬───────┘ └───────┬────────┘
│ │ │
└────────────────┼───────────────────┘
▼
local store (SQLite)
~/.local/share/roxabi-sense/
│
┌───────────────┼────────────────┐
▼ ▼ ▼
CLI MCP NATS (opt-in)
day-slice agent query factory.event.host.*
status what_was_i… activity | stale only
```
**Boundary (ADR-091-aligned):** sensors publish facts; `roxabi-factory` Sentinelle (hub module, not shipped yet) may *consume* host events and decide. This repo never opens Discord, never dispatches jobs, never applies ops policy.
| Surface | Granularity | Depends on factory? |
|---|---|---|
| Local store + CLI | Fine timeline | No |
| MCP (stdio) | Query on demand | No |
| NATS plane ① | Coarse heartbeats (`activity` / `stale`) | Yes (bus up) |
Details: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) · purpose: [`docs/PURPOSE.md`](docs/PURPOSE.md)
---
## Stack decision
**Local daemon + CLI + MCP — not a Silex web boilerplate.**
| Candidate | Verdict |
|---|---|
| `silex-boilerplate` / Next / multi-page web app | **No** — that shape is for client demos & product UI, not a user-session sensor |
| Full stack inside `roxabi-factory` | **No** — factory owns Sentinelle *consumer*; capture stays on the workstation |
| **Python 3.13 + `uv` + systemd `--user`** | **Yes** — matches Roxabi satellites (`voiceCLI`, `xcli`, cortex packages) |
| SQLite under `~/.local/share/roxabi-sense/` | **Yes** — single-machine, Syncthing-friendly if paused |
| MCP stdio | **Yes** — Claude / Grok / Hermes |
| NATS publisher (optional extra) | **Yes later** — facts only, feature-flagged |
| Tiny `127.0.0.1` status page | **Optional V2** — human glance only, not the product surface |
### Install matrix
| Layer | What it does | How |
|-------|----------------|-----|
| **Data plane** | Always-on collectors → SQLite | `sense install-service` + `systemctl --user enable --now roxabi-sense.service` |
| **Query plane** | Read API for humans/agents | CLI: `sense status` / `recap` · MCP: `sense mcp` (stdio) |
| **Agent DX** | Host wires MCP | Grok/Claude host snippets **or** thin plugin `plugins/roxabi-sense/` (`.mcp.json` → PATH `sense mcp`) |
Data plane and query plane are **separate**: MCP does not start collectors. Empty/offline tools ⇒ fix the daemon, not the agent config.
### Setup (PATH-stable)
Prefer a **stable `sense` on PATH** so agent configs never hardcode a worktree path.
```bash
# 1. clone (or pull) + install CLI + MCP deps into uv tool env
git clone git@github.com:Roxabi/roxabi-sense.git
cd roxabi-sense
uv tool install -e '.[mcp]'
# re-run after pull when the package changes:
# uv tool install -e '.[mcp]' --force
# After PyPI release: uv tool install 'roxabi-sense[mcp]'
# 2. data plane — user systemd unit (not the same process as MCP)
sense install-service
systemctl --user enable --now roxabi-sense.service
# 3. smoke / DoD
sense doctor # FAIL if offline / no DB / MCP missing
sense status # last_tick should refresh while daemon is up
sense recap # day summary; Meetings fidelity=full|active_only
which sense # typically ~/.local/bin/sense
# 5. optional NATS (when factory Sentinelle is ready)
# sense config set nats.enabled true
```
### MCP host registration (Grok + Claude)
**Prereq:** steps 1–3 above — `which sense` resolves, `sense doctor` is green (daemon + DB + MCP SDK). MCP does **not** start collectors; empty tools ⇒ fix data plane first.
**Happy path:** host spawns `sense mcp` from **PATH** — never a worktree absolute path.
#### Grok
User-global TOML (`~/.grok/config.toml`):
```toml
[mcp_servers.roxabi-sense]
command = "sense"
args = ["mcp"]
enabled = true
```
Or CLI (same result; user scope is default):
```bash
grok mcp add roxabi-sense -- sense mcp
grok mcp doctor roxabi-sense # config + spawn smoke
```
Restart the Grok session (or open a new one) so the server is re-spawned.
#### Claude Code
**CLI** (preferred one-liner):
```bash
# workstation-wide (recommended for a host sensor)
claude mcp add -s user roxabi-sense -- sense mcp
# or project-local (writes/approves .mcp.json in the repo)
claude mcp add -s project roxabi-sense -- sense mcp
```
**Project** `.mcp.json` (equivalent shape):
```json
{
"mcpServers": {
"roxabi-sense": {
"command": "sense",
"args": ["mcp"]
}
}
}
```
Project-scoped servers may show as pending approval until accepted in Claude. User scope avoids per-repo approval for a machine-local sensor.
**Asymmetry (intentional):** Grok stores servers in TOML (`~/.grok/config.toml` or project `.grok/config.toml`); Claude uses CLI scopes / `.mcp.json` JSON. Both spawn the same stdio command: `sense` + `mcp`.
#### After registration
```bash
sense doctor # still green (host config is not a substitute for data plane)
sense status # last_tick moving while daemon is up
```
Then ask the agent for tools (`sense_status`, `active_now`, `what_was_i_doing`, …). If tools are missing: restart the host session; if tools return offline/empty: fix daemon/`sense doctor`, not MCP JSON.
#### Hardening (optional)
Pin absolute binary if PATH is unreliable in the agent environment:
```toml
# Grok example — replace with real path from `which sense`
[mcp_servers.roxabi-sense]
command = "/home/YOU/.local/bin/sense"
args = ["mcp"]
enabled = true
```
#### Dev-only fallback (not for agent configs)
```bash
# contributor smoke — do not paste worktree paths into host MCP configs
uv run --extra mcp --directory /path/to/durable/clone sense mcp
```
Editable `uv tool install -e` must point at a **durable clone** (e.g. `~/projects/roxabi-sense`), not a feature worktree. If you delete that worktree, `sense` breaks with `ModuleNotFoundError` — reinstall:
```bash
cd ~/projects/roxabi-sense # durable path
uv tool install -e '.[mcp]' --force
sense doctor
```
#### Privacy / trust
- Default MCP redaction is **coarse** (no window titles / media tracks / full paths). Full detail only via operator config `[mcp] detail = "full"` — not tool-arg escalation (ADR-002).
- Agent spawn trusts the `sense` binary on PATH. Prefer operator-owned `uv tool` install over a world-writable clone.
- Only wire agents you trust with activity metadata.
### Thin agent plugin (optional)
Optional DX package under [`plugins/roxabi-sense/`](plugins/roxabi-sense/) — **wiring only**:
| Ships | Does not ship |
|-------|----------------|
| `.mcp.json` → `command: sense`, `args: [mcp]` | Python runtime, collectors, AT-SPI |
| Skill: when/how to use `sense_*` tools | Second query layer / private SQL |
| Plugin README + missing-PATH fallback | systemd unit / daemon start |
```bash
# Same happy path as host snippets — plugin just packages it:
# command = "sense" args = ["mcp"]
# Manual still wins until marketplace publish:
grok mcp add roxabi-sense -- sense mcp
claude mcp add -s user roxabi-sense -- sense mcp
```
Requires `roxabi-sense[mcp]` on PATH (package ≥ 0.0.1 with MCP extra) and `sense doctor` green. Full plugin notes: [`plugins/roxabi-sense/README.md`](plugins/roxabi-sense/README.md).
Contributor / in-tree workflow (not for host MCP config):
```bash
cd roxabi-sense && uv sync --extra mcp
uv run sense status
```
No Podman required on the laptop for V1. M₂ may use the same user unit. M₁ host-sensor path (services snapshot only) is a later collector, not a Quadlet of this whole app.
---
## Roadmap (coarse)
| Phase | Deliverable | State |
|---|---|---|
| **0 — scaffold** | Public repo, purpose, architecture | **done** |
| **1 — local spine** | Agent-session collector + store + CLI `status` / `day` / `recap` | **done** |
| **2 — focus + idle** | Multi-backend focus probes + idle chain + process/mpris/tmux | **done** (P0+P1) |
| **3 — MCP** | stdio tools over `SenseQuery` (`active_now`, timeline, sessions, …) | **done** |
| **4 — NATS opt-in** | `factory.event.host.{machine}.activity\|stale` for Sentinelle | open |
| **5 — optional** | Filtered browser history, local status HTTP | open |
### Focus probes (multi-Linux)
Focus is one collector (`kind=focus`) with swappable **FocusProbe** backends. Fact field `source` is the **backend id** (`atspi` | `x11` | `wlr` | `kde` | `noop`).
| Session | Candidate order (first healthy wins) |
|---------|--------------------------------------|
| Wayland GNOME / Cosmic / unknown | `atspi` → `x11` → `noop` |
| Wayland wlroots (Hyprland / Sway) | `wlr` → `atspi` → `x11` → `noop` |
| Wayland KDE | `atspi` → `kde` (stub) → `x11` → `noop` |
| Pure X11 | `x11` → `atspi` → `noop` |
- **AT-SPI** — long-lived agent (system Python + `gi`); best on GNOME/Cosmic when a11y works.
- **X11** — `xprop` / active window (XWayland fallback). Package: **`x11-utils`**.
- **wlr** — `hyprctl activewindow -j` or `swaymsg -t get_tree` when compositor env is set.
- **kde** — stub for now (`probe()` false until KWin D-Bus path lands).
- **Runtime demote** — if AT-SPI dies, daemon switches to next healthy probe without blocking other collectors.
- Meta focus: `focus_backend`, `focus_status`, `session_type`, `desktop_family`, `last_focus_path`.
- Meta idle: `idle_backend`, `idle_status`, `idle_chain_reason` (wayland → logind → noop).
### Agent sessions (Grok / Claude / optional Cursor)
| Source | Default | Paths (read-only) |
|--------|---------|-------------------|
| Grok | on (`agent_sessions`) | `~/.grok/active_sessions.json` |
| Claude | on (`agent_sessions`) | `~/.claude/sessions/*.json` |
| Cursor | **off** (`cursor_sessions = true`) | `~/.config/Cursor/User/workspaceStorage/*/workspace.json` |
Cursor opt-in emits `agent_sessions_snapshot` with `agent=cursor` (workspace id + folder path + mtime only). **Never** opens chat DBs (`state.vscdb`), composer history, or rewrites agent dirs.
```toml
# ~/.config/roxabi-sense/config.toml
[collectors]
cursor_sessions = true
# cursor_root = "/home/you/.config/Cursor" # optional override
# cursor_max_workspaces = 20
# cursor_max_age_days = 30
```
Agent status detail: [`AGENTS.md`](./AGENTS.md) § Status.
Out of scope forever (for this repo): OCR, continuous screenshots, keylogging, clipboard dumps, scraping Slack/Discord desktop clients, meeting transcription, cloud upload of agent chat bodies.
---
## Relation to the rest of Roxabi
| Project | Relation |
|---|---|
| [`roxabi-factory`](https://github.com/Roxabi/roxabi-factory) | Future **consumer** (Sentinelle hub module). Not the home of collectors. ADR-091 `factory-host-sensor` role lives *here* as an edge process. |
| [`roxabi-cortex`](https://github.com/Roxabi/roxabi-cortex) | Downstream memory/insight may *ingest* sense observations later. Sense stays capture + query, not the entity graph. |
| Claap | Meetings — do not duplicate |
| `~/.claude` / `~/.grok` | Read-only sources for agent presence |
---
## License
AGPL-3.0-or-later (same family as `roxabi-cortex`).
---
## Status
Phases 1–3 live (collectors, store, CLI, daemon, MCP stdio). Host registration: README § MCP host registration. NATS still open.
TDQS
Scored across 5 tools
Most tools have distinct purposes: daemon health, current presence, daily timeline, agent sessions, and day recap. However, what_was_i_doing and day_recap both cover a day's activities, and agent_sessions is a subset of the timeline, creating minor overlap that descriptions help resolve.
Names are readable and mostly snake_case, but patterns vary: sense_status and agent_sessions are noun_noun, active_now is adjective_noun, what_was_i_doing is a sentence fragment, and day_recap is noun_noun. No consistent verb_noun or clear pattern, though not chaotic.
Five tools is well-scoped for a personal sense daemon covering health, presence, timeline, sessions, and recap. Each tool earns its place without redundancy or bloat.
The surface covers core lifecycle needs: status, current state, historical timeline, specific session data, and a compiled recap. Missing minor capabilities like searching or filtering events, but agents can work around these gaps for typical tasks.