Adds the host_agent Roundtable plugin: a lightweight stdlib-only Python agent that runs on each monitored Linux host, collects CPU / memory / disk / load / uptime from /proc every 30s, and POSTs signed payloads to a bearer-token-authed ingest endpoint. Admin UI handles host registration, token rotation, and widget / alert integration.
/plugins/host_agent/install.sh?token=… renders templated systemd installer, passes sh -n
/plugins/host_agent/agent.py serves the agent source
Agent running locally against http://localhost:5000 successfully posts samples every 30s
plugin_metrics populated with cpu_pct, mem_used_pct, mem_available_bytes, swap_used_bytes, load_1m/5m/15m, uptime_secs, disk_used_pct_worst, and per-mount disk_used_pct/disk_used_bytes/disk_total_bytes
registrations.last_seen_at / agent_version / kernel / distro / arch updated on each ingest
Dashboard Host Agent — Resources widget renders fleet-glance table with live values
End-to-end install against a real remote Ubuntu VM (one-liner curl|sh, systemd unit, service running)
Deviations from the plan
Documented in docs/plugins/host-agent-plan.md "Execution note — path 1":
No DB-backed tests. The Roundtable test harness mocks db_sessionmaker under testing=True and has no existing DB fixture. Rather than invent one mid-plan (scope creep), HTTP-level route tests were dropped and everything testable was factored into pure functions (config parser, collectors, ring buffer, metric expander, staleness filter, template render). Tasks 7, 9, 11, 13 became manual-verification only — all smoke-tested above.
roundtable.core.db.get_session does not exist. Plan assumed a session helper that isn't in the codebase. Real pattern is async with current_app.db_sessionmaker() as session: — used throughout routes.py and scheduler.py.
Admin decorator. Plan left the admin auth mechanism as a TODO. Resolved to from roundtable.auth.middleware import require_role with @require_role(UserRole.admin) (matches roundtable/settings/routes.py convention).
make_stale_task renamed to make_task returning a real ScheduledTask instead of a bare dict (matches plugins/docker/scheduler.py convention).
agent.py is 370 lines vs. the plan's ~350 target — 20 lines over because the appended code matches the plan verbatim. Not reworked.
Known caveats
401 response drops buffered samples. On 401 invalid_token, the agent re-buffers the current sample but silently drops whatever was already in the ring buffer from prior failed POSTs. Behavior matches the approved plan text (Task 5, Step 3) but is asymmetric with the generic-failure path. Fine for now; worth revisiting if you want stricter at-least-once semantics.
UI polish followups tracked as Fable task 266 — settings table cells collide on Host / Agent version, rotate/delete buttons need a confirm() guard, widget header wrap is awkward.
## Summary
Adds the `host_agent` Roundtable plugin: a lightweight stdlib-only Python agent that runs on each monitored Linux host, collects CPU / memory / disk / load / uptime from `/proc` every 30s, and POSTs signed payloads to a bearer-token-authed ingest endpoint. Admin UI handles host registration, token rotation, and widget / alert integration.
Spec: `docs/plugins/host-agent-design.md`
Plan: `docs/plugins/host-agent-plan.md`
## What's in this PR
**Main repo** (this branch, 10 commits ahead of main):
- `docs/plugins/host-agent-design.md` — design spec
- `docs/plugins/host-agent-plan.md` — 15-task implementation plan + path-1 note
- `docs/plugins/index.yaml.example` — catalog entry
- `roundtable/core/widgets.py` — `host_resources` + `host_resource_history` widget registrations
- `roundtable/alerts/routes.py` — `host_agent` entry in `METRIC_CATALOG` (9 metrics)
- `tests/plugins/host_agent/` — 42 pure-Python unit tests (config parser, collectors, ring buffer, payload builder, agent POST + backoff, metric expander, install.sh template render, stale-agent filter)
**Plugins repo** (`Roundtable-plugins@7468806`, already pushed to `main`):
- `host_agent/agent.py` (370 LoC single-file daemon)
- `host_agent/routes.py` — ingest / install.sh / agent.py / widgets / settings
- `host_agent/scheduler.py` — stale-agent check
- `host_agent/models.py` — `HostAgentRegistration` (token hash, last_seen, metadata)
- `host_agent/migrations/versions/host_agent_001_initial.py`
- `host_agent/templates/` — `install.sh.j2`, `settings_list.html`, `widget_table.html`, `widget_history.html`
- `host_agent/plugin.yaml`, `__init__.py`
## Test plan
- [x] `pytest tests/plugins/host_agent` — 42/42 passing locally
- [x] Full repo `pytest tests/` — no regressions beyond pre-existing RuntimeWarnings
- [x] Manual end-to-end smoke test on 2026-04-15:
- Admin settings page renders, add-host flow creates registration + returns one-time install URL
- `/plugins/host_agent/install.sh?token=…` renders templated systemd installer, passes `sh -n`
- `/plugins/host_agent/agent.py` serves the agent source
- Agent running locally against `http://localhost:5000` successfully posts samples every 30s
- `plugin_metrics` populated with `cpu_pct`, `mem_used_pct`, `mem_available_bytes`, `swap_used_bytes`, `load_1m/5m/15m`, `uptime_secs`, `disk_used_pct_worst`, and per-mount `disk_used_pct`/`disk_used_bytes`/`disk_total_bytes`
- `registrations.last_seen_at` / `agent_version` / `kernel` / `distro` / `arch` updated on each ingest
- Dashboard `Host Agent — Resources` widget renders fleet-glance table with live values
- [ ] End-to-end install against a real remote Ubuntu VM (one-liner curl|sh, systemd unit, service running)
## Deviations from the plan
Documented in `docs/plugins/host-agent-plan.md` "Execution note — path 1":
1. **No DB-backed tests.** The Roundtable test harness mocks `db_sessionmaker` under `testing=True` and has no existing DB fixture. Rather than invent one mid-plan (scope creep), HTTP-level route tests were dropped and everything testable was factored into pure functions (config parser, collectors, ring buffer, metric expander, staleness filter, template render). Tasks 7, 9, 11, 13 became manual-verification only — all smoke-tested above.
2. **`roundtable.core.db.get_session` does not exist.** Plan assumed a session helper that isn't in the codebase. Real pattern is `async with current_app.db_sessionmaker() as session:` — used throughout `routes.py` and `scheduler.py`.
3. **Admin decorator.** Plan left the admin auth mechanism as a TODO. Resolved to `from roundtable.auth.middleware import require_role` with `@require_role(UserRole.admin)` (matches `roundtable/settings/routes.py` convention).
4. **`make_stale_task` renamed to `make_task`** returning a real `ScheduledTask` instead of a bare dict (matches `plugins/docker/scheduler.py` convention).
5. **`agent.py` is 370 lines vs. the plan's ~350 target** — 20 lines over because the appended code matches the plan verbatim. Not reworked.
## Known caveats
- **401 response drops buffered samples.** On `401 invalid_token`, the agent re-buffers the current sample but silently drops whatever was already in the ring buffer from prior failed POSTs. Behavior matches the approved plan text (Task 5, Step 3) but is asymmetric with the generic-failure path. Fine for now; worth revisiting if you want stricter at-least-once semantics.
- **UI polish followups** tracked as Fable task 266 — settings table cells collide on `Host` / `Agent version`, rotate/delete buttons need a `confirm()` guard, widget header wrap is awkward.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Adds the
host_agentRoundtable plugin: a lightweight stdlib-only Python agent that runs on each monitored Linux host, collects CPU / memory / disk / load / uptime from/procevery 30s, and POSTs signed payloads to a bearer-token-authed ingest endpoint. Admin UI handles host registration, token rotation, and widget / alert integration.Spec:
docs/plugins/host-agent-design.mdPlan:
docs/plugins/host-agent-plan.mdWhat's in this PR
Main repo (this branch, 10 commits ahead of main):
docs/plugins/host-agent-design.md— design specdocs/plugins/host-agent-plan.md— 15-task implementation plan + path-1 notedocs/plugins/index.yaml.example— catalog entryroundtable/core/widgets.py—host_resources+host_resource_historywidget registrationsroundtable/alerts/routes.py—host_agententry inMETRIC_CATALOG(9 metrics)tests/plugins/host_agent/— 42 pure-Python unit tests (config parser, collectors, ring buffer, payload builder, agent POST + backoff, metric expander, install.sh template render, stale-agent filter)Plugins repo (
Roundtable-plugins@7468806, already pushed tomain):host_agent/agent.py(370 LoC single-file daemon)host_agent/routes.py— ingest / install.sh / agent.py / widgets / settingshost_agent/scheduler.py— stale-agent checkhost_agent/models.py—HostAgentRegistration(token hash, last_seen, metadata)host_agent/migrations/versions/host_agent_001_initial.pyhost_agent/templates/—install.sh.j2,settings_list.html,widget_table.html,widget_history.htmlhost_agent/plugin.yaml,__init__.pyTest plan
pytest tests/plugins/host_agent— 42/42 passing locallypytest tests/— no regressions beyond pre-existing RuntimeWarnings/plugins/host_agent/install.sh?token=…renders templated systemd installer, passessh -n/plugins/host_agent/agent.pyserves the agent sourcehttp://localhost:5000successfully posts samples every 30splugin_metricspopulated withcpu_pct,mem_used_pct,mem_available_bytes,swap_used_bytes,load_1m/5m/15m,uptime_secs,disk_used_pct_worst, and per-mountdisk_used_pct/disk_used_bytes/disk_total_bytesregistrations.last_seen_at/agent_version/kernel/distro/archupdated on each ingestHost Agent — Resourceswidget renders fleet-glance table with live valuesDeviations from the plan
Documented in
docs/plugins/host-agent-plan.md"Execution note — path 1":db_sessionmakerundertesting=Trueand has no existing DB fixture. Rather than invent one mid-plan (scope creep), HTTP-level route tests were dropped and everything testable was factored into pure functions (config parser, collectors, ring buffer, metric expander, staleness filter, template render). Tasks 7, 9, 11, 13 became manual-verification only — all smoke-tested above.roundtable.core.db.get_sessiondoes not exist. Plan assumed a session helper that isn't in the codebase. Real pattern isasync with current_app.db_sessionmaker() as session:— used throughoutroutes.pyandscheduler.py.from roundtable.auth.middleware import require_rolewith@require_role(UserRole.admin)(matchesroundtable/settings/routes.pyconvention).make_stale_taskrenamed tomake_taskreturning a realScheduledTaskinstead of a bare dict (matchesplugins/docker/scheduler.pyconvention).agent.pyis 370 lines vs. the plan's ~350 target — 20 lines over because the appended code matches the plan verbatim. Not reworked.Known caveats
401 invalid_token, the agent re-buffers the current sample but silently drops whatever was already in the ring buffer from prior failed POSTs. Behavior matches the approved plan text (Task 5, Step 3) but is asymmetric with the generic-failure path. Fine for now; worth revisiting if you want stricter at-least-once semantics.Host/Agent version, rotate/delete buttons need aconfirm()guard, widget header wrap is awkward.🤖 Generated with Claude Code