Jobs engine for the Starfleet system. Allows the assistant to schedule one shot or recurrent activities through MCP exposed tools.
  • Python 73.5%
  • JavaScript 19.5%
  • CSS 6%
  • Shell 0.6%
  • HTML 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Troed Sångberg 41f155b90e
All checks were successful
CI / Sanity check (ubuntu-latest) (push) Successful in 56s
Merge remote-tracking branch 'origin/main'
2026-09-20 10:07:57 +00:00
.forgejo/workflows fix: run CI on devel pushes 2026-08-25 11:55:13 +02:00
docs/superpowers docs: roster admin UI implementation plan 2026-09-20 07:39:25 +00:00
scripts chore: ops — user units, deploy.sh, CI, AGENTS.md, README, sample scripts 2026-08-25 11:52:15 +02:00
src/roster fix: validate admin token against authenticated endpoint 2026-09-20 10:04:40 +00:00
systemd chore: ops — user units, deploy.sh, CI, AGENTS.md, README, sample scripts 2026-08-25 11:52:15 +02:00
tests fix: validate admin token against authenticated endpoint 2026-09-20 10:04:40 +00:00
.gitignore feat: repo scaffold, pyproject, strict TOML config loader 2026-08-25 08:22:17 +02:00
AGENTS.md chore: scrub internal identifiers from tracked files (public repo hygiene) 2026-08-25 22:51:20 +02:00
deploy.sh chore: scrub internal identifiers from tracked files (public repo hygiene) 2026-08-25 22:51:20 +02:00
LICENSE feat: repo scaffold, pyproject, strict TOML config loader 2026-08-25 08:22:17 +02:00
pyproject.toml feat: serve roster admin static shell 2026-09-20 07:49:47 +00:00
README.md docs: document roster admin UI 2026-09-20 08:10:48 +00:00
roster.toml.example docs: fix crew ingest path in example config 2026-09-07 17:49:18 +02:00
uv.lock fix: run MCP client calls off the event loop 2026-08-25 11:21:09 +02:00

roster

Resident scheduler for the home network. script, llm and agent jobs, created by you from the CLI or by LLM agents over MCP; results land in output files, ntfy, Matrix, and crew.

Design doc: docs/superpowers/specs/2026-08-24-roster-design.md.

Architecture

Two systemd user units on the server host:

  • roster serve — FastAPI JSON API on :4020 plus the 30 s tick loop. Owns the SQLite store (WAL) and runs every job.
  • roster mcp — streamable-HTTP MCP server on :4021 whose nine tools are thin HTTP calls to the API (including an ad-hoc notify).

The CLI is a thin client too. One mutation path: everything goes through the service process.

Job types:

type runs
script an allowlisted executable under [scripts] dir (optional gate script first)
llm one chat completion against an OpenAI-compatible endpoint
agent opencode run --agent <name> on the workstation over SSH

Schedules: interval (30m, 2h), cron (5-field), at (one-shot; fires immediately when the time is now/past).

Semantics worth knowing: catch-up window 900 s (older due events are recorded as skipped(misfire)); no overlapping runs; unreachable LLM endpoint defers instead of failing (one alert per episode, completion notes "after N deferrals"); 3 consecutive failures alert once; [SILENT] in llm/agent output suppresses ntfy and Matrix but not the file.

Where jobs run

Jobs execute on the machine hosting the roster API (the deploy target), never on the machine running the CLI or an MCP client. Consequences:

  • script_path and gate must be executables inside [scripts] dir on the server host; the API rejects anything else, naming both the given path and the allowed dir.
  • An MCP client on another machine only talks HTTP to the server host — copy scripts into its scripts dir (e.g. scp) before scheduling.
  • CLI and MCP clients surface the API's detail message on errors (e.g. roster API 422: script path '...' is outside the allowlist dir '...'), so a rejection tells you exactly which path to move.

Quickstart

uv sync --group dev
cp roster.toml.example ~/.config/roster/roster.toml   # then edit ntfy_url/token
mkdir -p ~/.config/roster/scripts
export SERVER_HOST=... SERVER_USER=... SERVER_REPO_DIR=...  # deploy target
./deploy.sh                                            # installs + starts both units

roster selftest
roster add --id ping --type script --interval 30m \
    --script-path ~/.config/roster/scripts/sample_ping.sh
roster list
roster runs ping

CLI

roster add | list | show | update | pause | resume | run | rm | runs, roster notify (ad-hoc Matrix message), plus serve, mcp, selftest, doctor [--reset-locks].

MCP clients

Computer (mcp.json):

{ "type": "http", "url": "http://<server-host>:4021/mcp" }

Workstation opencode: add the same URL as a remote MCP server. Agents can then schedule work; allow_agent_scheduling = false rejects all mutating requests from non-CLI clients.

Matrix

Matrix is a delivery target like ntfy, configured server-side under [delivery] (matrix_homeserver, matrix_user, matrix_password, matrix_room). Roster maintains its own login (token cached at ~/.local/share/roster/matrix_token, transparent re-login on expiry) using a dedicated bot account — no E2EE, plain rooms only, messages sent as m.notice.

Setup

  1. Create a dedicated bot account for roster in the Matrix admin UI (Settings -> Users -> Add user; e.g. localpart computer).
  2. Create a plain room (no E2EE) and invite the bot from your account. Roster auto-joins the room on first send, so no manual join is needed.
  3. In roster.toml under [delivery]:
    • matrix_homeserver — full homeserver URL with scheme, e.g. "https://matrix.example.org" (a bare hostname fails at runtime).
    • matrix_user — the bot's localpart ("computer") or full ID ("@computer:example.org").
    • matrix_password — the bot account password.
    • matrix_room — the room's ID, !opaque:server form (in Element: room settings -> copy room ID). Must be a room the bot is invited to.

Per job: include matrix in delivery (CLI --delivery file,matrix, MCP delivery: ["matrix"]); an optional matrix_room in the job spec overrides the default room. Failures are recorded in the run file and never fail the job.

Ad-hoc notifications, independent of any job:

roster notify "deploy done" --title "roster"          # CLI
POST /notify {"text": "...", "title": "...", "room": "..."}   # API
notify(text, title?, room?)                            # MCP tool

Crew delivery

Crew is a delivery target like Matrix, configured server-side under [delivery] (crew_url, crew_token). On a completed run roster POSTs {"user_id", "context_key", "content", "fetched_at"} to crew's /internal/context endpoint with Authorization: Bearer <crew_token> — content is truncated to 8000 chars and fetched_at is ISO-8601 UTC; crew stores it as that user's latest context under the given key.

Per job: include crew in delivery (MCP/API delivery: ["crew"]). Script jobs only, and they must set crew_user_id and crew_context_key — validation rejects crew jobs without them; llm and agent jobs reject those fields. Unlike ntfy and Matrix, [SILENT] does not suppress crew delivery (the delivery is the job's purpose); empty output skips it. Failures are noted on the run and never change its status.

In roster.toml under [delivery]:

  • crew_url — full URL of crew's ingest endpoint, e.g. "http://<crew-host>:<port>/internal/context".
  • crew_token — crew's [ingest] token (an empty token sends no auth header, which crew treats as trust-the-LAN).

Crew provisions these jobs itself through the normal HTTP API — the roster side is only the URL/token above.

Script jobs also take an optional env field, a mapping of strings to strings merged into the script's environment — handy for user parameters (e.g. which city a weather script should fetch).

Operations

journalctl --user -u roster -n 100      # engine/API logs
journalctl --user -u roster-mcp -n 100  # MCP logs
roster doctor                           # integrity check
roster doctor --reset-locks             # repair stale running rows

Admin UI

roster serve also serves a small operator console at http://<serve-host>:4020/admin — no build step, no extra dependency. It uses the token from [api] token; the token is stored only in the browser (localStorage key roster_token) and sent as a bearer header. The console lists, creates, edits, pauses/resumes, runs and deletes jobs, shows per-job run history, sends an ad-hoc notification, and reports health. It polls the API for freshness; there is no push transport.