- Python 73.5%
- JavaScript 19.5%
- CSS 6%
- Shell 0.6%
- HTML 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| docs/superpowers | ||
| scripts | ||
| src/roster | ||
| systemd | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| deploy.sh | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| roster.toml.example | ||
| uv.lock | ||
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-hocnotify).
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_pathandgatemust be executables inside[scripts] diron 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
- Create a dedicated bot account for roster in the Matrix admin UI
(Settings -> Users -> Add user; e.g. localpart
computer). - 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.
- In
roster.tomlunder[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:serverform (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.