Skip to content

feat(mcp): events_query answers what happened at a time - #55

Merged
frahlg merged 3 commits into
mainfrom
feat/events-query
Nov 8, 2026
Merged

frahlg merged 3 commits into
mainfrom
feat/events-query

Conversation

@frahlg

@frahlg frahlg commented Nov 8, 2026

Copy link
Copy Markdown
Member

Problem

The box now keeps a control journal and answers the agent read events.get (srcfl/ftw#1982, merged fd4426a1). Until now this server told models that events were unavailable and sent them to logs_tail, which is a 2,000-line ring in memory, so an assistant could not answer "why did the app say FTW is not in control this morning?".

Change

  • New read-only MCP tool events_query: since and until (RFC 3339, Unix ms, or a local time read in the home's zone; the last 24 hours by default), source, min_severity (warning or alarm), limit (1–50). It answers with spans (time range in the home's zone, kind, source id, status, reason, severity, duration, asked and measured watts, the verdict's numbers), the notices in the window, and how to page when more is true.
  • A box without the journal answers E_UNKNOWN_OP; the tool says "This box does not keep a control journal yet; update FTW" and points to logs_tail and health_get. A refused source and an unavailable journal get their own messages.
  • The server's base instructions and the events guidance name the new tool. README.md and docs/public-plugin.md are updated.
  • go/protocol moves to srcfl/ftw@fd4426a1, the merge of #1982. This is the second half of a wire pair.

The tool is on base /mcp only. publicTool says a new tool needs review before the public profile lists it; that is a separate decision.

internal/mcp grows by about 330 lines of Go, not counting tests.

Evidence

  • go vet ./..., go test -race ./... (21 packages), go test -tags ftwclouddev ./... and scripts/check-imports.sh pass on this branch with the real pin.
  • internal/mcp/events_test.go covers the default window and zone, a local since, Unix ms, source, severity, limit, paging, refused arguments that never reach the box, the old box, NaN and -0 numbers, and UTC output. The e2e stub answers events.get with spans and notices.

Sample (from the e2e stub):

spans: {from 05:30:16+01:00, to 05:47:53+01:00, duration 17m37s, kind battery, source s1,
        status not_following, reason readback_mismatch, severity warning,
        askedW {mean -2500, min -3000, max -2000}, measuredW {mean -121, min -400, max 0},
        last {sent_w -3000, readback_w -2000, battery_soc 41.26, command_at 05:46:56+01:00}}
notices: {at 05:40:16+01:00, type control.not_following, source s1, status sent, priority 4}

🤖 Generated with Claude Code

frahlg and others added 2 commits November 8, 2026 08:32
A new read tool asks the box's control journal (events.get, scope
ftw.events.read) about a past window: each span in which a device, a
source, dispatch or the mode held one verdict, each scored plan slot,
and the notices the box sent. It is for questions such as why the app
said FTW was not in control this morning, what happened at 06:14, or
whether the battery did what FTW asked.

- Inputs: since and until (RFC 3339, Unix ms, or a local time read in
  the home's zone; the last 24 hours by default), source, min_severity
  (warning or alarm) and limit (at most protocol.EventsMax).
- Each span gives from and to (or ongoing) in the zone the box names in
  hello_ok, UTC without one; duration; kind, source, status, reason,
  severity and mode; askedW beside measuredW as mean, min and max; and
  the latest verdict's numbers, a *_ms time written as a time.
- When the box cut the answer, next names the until that reads on: the
  oldest start or time of each list the box cut, the later of the two.
- A box from before the journal answers E_UNKNOWN_OP for events.get with
  no field; the model hears "this box does not keep a control journal
  yet; update FTW". A refused src and E_UNAVAILABLE for the journal get
  their own words.

The stub box answers events.get with spans and notices and records each
query. Tests cover the default window and zone, a local since, Unix ms,
source, severity, limit, paging, refused arguments and the old box.
events_query stays off the public profile until it is reviewed there;
events_list stays an unsupported name.

Needs srcfl/ftw feat/control-journal (go/protocol EventSpan, Events with
spans and notices). internal/mcp grows by about 330 lines of Go without
tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-08T08:12:02.417596Z 13974ef PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13974efb57

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/mcp/events.go
Comment on lines +256 to +258
if hasSpans && len(starts) >= n {
until, found = spanEnd, true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ensure the next-page boundary advances past repeated spans

When limit is 1 and multiple spans match the window, setting the next until to the returned span's start cannot advance pagination: the inclusive overlap behavior described here includes that same span again, so the next response returns the same span and cursor with more: true. The same problem occurs at larger limits when enough spans share the boundary timestamp. Older control events therefore remain inaccessible by following the supplied paging instructions. Detect non-progress and provide a usable fallback, such as increasing the limit or narrowing the source, rather than repeatedly recommending the same boundary.

Useful? React with 👍 / 👎.

A page full of spans from one moment gave a next until at that same
moment, so a caller following next never ended. It now steps past that
moment and says what that may leave out. A time at or before 1970 is
refused, since the box reads 0 as no bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit b6d8914 into main Nov 8, 2026
2 checks passed
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.

1 participant