feat(mcp): add proposals section to morning brief#20
Conversation
The brief tool's morning `sections` filter matches the runSection label "todos", but three agent-facing docs advertised the key as 'tasks', so an agent following them passed an unmatched key and silently received an empty briefing. Correct the key in the catalog tool description, the BriefInput schema tag, and the skills manual, and complete the todos-section field list (add active_todos, plus recurring_todos in the manual) so the enumerated fields match what the section actually emits. Documentation only; no behaviour change.
Expose proposals_pending — the summed count of agent-proposed area, goal, and project drafts awaiting owner triage — as a new "proposals" section on brief(mode=morning). This lets the hermes push agent read the triage backlog over the MCP surface (its existing access path) to decide whether to nudge Koopa back to the admin triage queue, instead of holding admin API credentials just to read the count. proposals_pending is a scalar on the morning wire (always emitted, 0 when nothing is pending); the consumer gates its nudge on N > 0. fillProposalsPending reads the goal and project proposed-counts independently and each degrades to its own zero on error: a transient failure on one entity undercounts rather than suppressing the whole signal, which for a "don't let the queue rot" nudge is the safer failure mode than all-or-nothing. Covered by a morning-wire unit test and an integration test asserting the sum across all three proposed-entity kinds plus the section-filter wiring.
🤖 Augment PR SummarySummary: Adds a proposals backlog signal to the MCP morning brief and fixes agent-facing docs around the morning Changes:
🤖 Was this summary useful? React with 👍 or 👎 |
| } else { | ||
| s.logger.Warn("brief: proposed projects count", "error", err) | ||
| } | ||
| out.ProposalsPending = int(total) |
There was a problem hiding this comment.
internal/mcp/brief.go:428 casts the summed proposal counts from int64 to int; if the counts ever exceed math.MaxInt (or on 32-bit builds), this can silently overflow and produce an incorrect proposals_pending signal.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| As string `json:"as,omitempty" jsonschema_description:"Caller agent identity (e.g. koopa0-dev)."` | ||
| Mode string `json:"mode" jsonschema_description:"Briefing mode (required): 'morning' = daily-planning pull (todos/goals/rss/content_pipeline); 'reflection' = end-of-day plan-vs-actual retrospective (daily plan items + completion counts). brief is a pure planning-state pull and carries no agent memory."` | ||
| Sections FlexStringSlice `json:"sections,omitempty" jsonschema_description:"MORNING-ONLY strict filter on which groups to populate (default: all). Ignored in reflection mode. Omit or pass [] to get the full morning briefing. Group key → response fields: 'tasks' → overdue_todos/today_todos/recurring_todos/committed_todos/upcoming_todos; 'goals' → active_goals; 'rss' → rss_highlights; 'content_pipeline' → content_pipeline. Unknown keys silently ignored."` | ||
| Sections FlexStringSlice `json:"sections,omitempty" jsonschema_description:"MORNING-ONLY strict filter on which groups to populate (default: all). Ignored in reflection mode. Omit or pass [] to get the full morning briefing. Group key → response fields: 'todos' → overdue_todos/today_todos/active_todos/recurring_todos/committed_todos/upcoming_todos; 'goals' → active_goals; 'rss' → rss_highlights; 'content_pipeline' → content_pipeline; 'proposals' → proposals_pending (count of agent-proposed area/goal/project drafts awaiting owner triage). Unknown keys silently ignored."` |
There was a problem hiding this comment.
internal/mcp/brief.go:84 documents sections as a strict filter, but proposals_pending is always emitted as a scalar (so callers filtering out proposals will still see proposals_pending: 0). Consider clarifying this in the tool/schema description so consumers don’t misread 0 as an explicitly computed value.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| Stability: StabilityStable, | ||
| Since: since, | ||
| Description: "Read-only planning-state pull. Pick a mode (required): 'morning' = single-call daily-planning briefing (overdue/today/recurring/committed/upcoming todos, active_goals, rss_highlights, content_pipeline); 'reflection' = end-of-day plan-vs-actual retrospective (planned_items + completed/deferred/planned counts + completion_rate). brief is a pure planning-state pull and carries no agent memory. Morning mode is filterable via the sections parameter (ignored in reflection mode) — valid keys (omit or pass [] for all): 'tasks' (overdue/today/recurring/committed/upcoming todos), 'goals' (active_goals), 'rss' (rss_highlights — feeds tagged priority=high, NOT relevance-ranked; use search_knowledge for ranked retrieval), 'content_pipeline' (content_pipeline). Every caller gets all sections by default; pass an explicit sections list to narrow the briefing. Scope is the target date (default today), not since-last-session.", | ||
| Description: "Read-only planning-state pull. Pick a mode (required): 'morning' = single-call daily-planning briefing (overdue/today/recurring/committed/upcoming todos, active_goals, rss_highlights, content_pipeline); 'reflection' = end-of-day plan-vs-actual retrospective (planned_items + completed/deferred/planned counts + completion_rate). brief is a pure planning-state pull and carries no agent memory. Morning mode is filterable via the sections parameter (ignored in reflection mode) — valid keys (omit or pass [] for all): 'todos' (overdue/today/active/recurring/committed/upcoming todos), 'goals' (active_goals), 'rss' (rss_highlights — feeds tagged priority=high, NOT relevance-ranked; use search_knowledge for ranked retrieval), 'content_pipeline' (content_pipeline), 'proposals' (proposals_pending — count of agent-proposed area/goal/project drafts awaiting owner triage). Every caller gets all sections by default; pass an explicit sections list to narrow the briefing. Scope is the target date (default today), not since-last-session.", |
There was a problem hiding this comment.
internal/mcp/ops/catalog.go:35’s top-level morning description still lists the todos set without mentioning active_todos (and doesn’t mention proposals_pending at all), which can drift from the actual morning wire shape for clients that only read the opening summary.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
…narrowing The proposed area/goal/project counts come from count(*) (int64); summing them into an int field required an int64->int conversion that narrows on 32-bit builds. The counts are far too small to overflow in practice, but typing the field as int64 matches the source and removes the conversion entirely rather than guarding an impossible case. No behaviour or wire change — JSON still serialises a bare number. Addresses Augment review on PR #20.
The opening morning summaries — in the brief tool description, the mode schema tag, and the skills manual — still listed the pre-proposals section set and omitted active_todos, drifting from what the morning wire actually emits. List active_todos and proposals_pending everywhere the sections are summarised, and correct the BriefInput doc's "empty-slice default" wording to "zero-value default" now that proposals_pending is a scalar (0), noting that an unrequested field's default is not a computed result. Addresses Augment review on PR #20.
Summary
proposalssection tobrief(mode=morning)exposingproposals_pending— the summed count of agent-proposed area/goal/project drafts awaiting owner triage. This lets the hermes push agent read the triage backlog over the MCP surface (its existing access path) to decide whether to nudge Koopa back to the admin triage queue, instead of holding admin API credentials just to read the count.sectionsfilter key was documented astasksin three agent-facing places, but the code matches the runSection labeltodos, so an agent following the docs passed an unmatched key and silently received an empty briefing. Corrected in the catalog tool description, theBriefInputschema tag, and the skills manual, and completed the todos-section field list (addsactive_todos, plusrecurring_todosin the manual).Design notes
proposals_pendingis a scalar on the morning wire (always emitted,0when nothing is pending); the consumer gates its nudge onN > 0. It is never emitted in reflection mode.fillProposalsPendingreads the goal and project proposed-counts independently; each degrades to its own zero on error (matching the sibling section fillers), so a transient failure undercounts rather than suppressing the whole nudge signal.Test Plan
go build ./... && go vet ./... && golangci-lint run ./internal/mcp/...— clean (0 issues)go test ./...— all unit tests pass, including the ops drift test (TestToolInventoryDocInSync) and newTestBriefOutput_ProposalsPendingWirego test -tags=integration ./internal/mcp/— full suite passes, including newTestIntegration_BriefMorning_ProposalsPending(asserts the sum across all three proposed-entity kinds plus the section-filter wiring)Consumer-side spec (for the hermes repo) lives in
docs/hermes-proposals-push-spec.md; it is not part of this PR's code.