Skip to content

feat(web): per-machine notices from host status - #382

Closed
futrime wants to merge 1 commit into
mainfrom
feat/web-notices
Closed

futrime wants to merge 1 commit into
mainfrom
feat/web-notices

Conversation

@futrime

@futrime futrime commented Oct 12, 2026

Copy link
Copy Markdown
Member

Unit 2 of the multi-daemon + notifications batch (contracts C3, C4).

What

  • C3: Host.status() (and so Daemon.status()) adds pending ([{question, role, text, mode}]), ended ({how, why, epic, at, run, flow, task} or null), epic and task.
  • C4: new hmz.web.notices.Notices, held as site.notices, with .since(after), .heard(cb) (returns an unsubscribe callable), .last, .stream(), .look(), .start() and .close().
    • GET /api/notices?after= returns {notices, last}.
    • GET /api/notices/stream sends SSE notice events and resumes from Last-Event-ID or ?last=. With no id given, the stream starts from now.
    • Kinds are done, failed (failed, crashed, budget and refused), stopped, asked (mode ask) and listening (mode listen).
  • Polling, never attaching. Places.statuses() reads daemon.daemons() and each .status() when runs are held apart. Otherwise it reads each in-process host's status(). Notices polls it every 2 s. _asks_the_daemon now reuses Places.statuses().
  • Startup baseline.
    • The first poll is the baseline.
    • An epic that ended before the first poll is never news, even for a workspace first seen later.
    • A host that does not answer counts as unknown, not as one that has gone.
    • Questions are compared within the same host, by (pid, started), so a new host's q1 is still news.
    • A crash's text is the last line of its traceback.
    • The last 200 notices are kept.
    • Ids come from the clock in milliseconds and always increase, so a client can resume across a restart.
  • Docs: docs/reference/daemon.md (status keys) and docs/reference/web.md (Notices API).

Tests

  • tests/unit/web/test_notices.py: pure diffing against a fake status source.
  • tests/unit/runtime/doing/test_hosting.py: the new status keys.
  • tests/integration/test_web_notices.py: through served() and the stand-in agent, a chat produces listening, stopping it produces stopped, and a one-turn flow produces done. It also checks after= and resuming the stream.
  • Live check: hmz web --port 0 with runs held apart. The chat produced a listening notice and then stopped, both over curl on the SSE stream and on /api/notices.

🤖 Generated with Claude Code

A host's status now says the questions it waits on (`pending`), how the last
run to end ended (`ended`), and the run's `epic` and `task`. `hmz web` polls
every workspace's host status every 2 s, without attaching, and turns what
changed into notices (done, failed, stopped, asked, listening), served at
`GET /api/notices?after=` and as `notice` events on `GET /api/notices/stream`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 12, 2026 15:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@futrime

futrime commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Landed together in #385.

@futrime futrime closed this Oct 12, 2026
@futrime
futrime deleted the feat/web-notices branch October 12, 2026 23:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants