Skip to content

feat(core): emit Kubernetes Events on Agent/ModelConfig/MCPServerTool/RemoteMCPServer reconciles#2150

Open
braghettos wants to merge 3 commits into
kagent-dev:mainfrom
braghettos:observability-reconcile-events
Open

feat(core): emit Kubernetes Events on Agent/ModelConfig/MCPServerTool/RemoteMCPServer reconciles#2150
braghettos wants to merge 3 commits into
kagent-dev:mainfrom
braghettos:observability-reconcile-events

Conversation

@braghettos

Copy link
Copy Markdown

What

Adds Kubernetes Events on reconcile to the Agent, ModelConfig, MCPServerTool, and RemoteMCPServer controllers, so users can see reconcile outcomes with kubectl describe / kubectl get events. This is the second of the three observability gaps proposed in #2148.

Today these four controllers emit no Events (unlike agentharness-substrate-controller, which already does), so a bad Agent/ModelConfig/MCPServer gives no signal in kubectl describe.

How

Each controller gains an optional Client + Recorder events.EventRecorder and a small nil-guarded recordEvent helper that re-fetches the reconciled object and emits an Event against it, mirroring the existing substrate-controller pattern.

Emitted events:

Controller Success Failure
Agent Normal Accepted Warning ReconcileFailed
ModelConfig Normal Accepted Warning ReconcileFailed
RemoteMCPServer Normal Accepted Warning ReconcileFailed
MCPServerTool Normal ToolsDiscovered Warning ValidationFailed (spec validation) / Warning ReconcileFailed (transient)

Recorders are wired from pkg/app/app.go via mgr.GetEventRecorder(...).

Additive / no behaviour change

  • recordEvent is a no-op when either Recorder or Client is nil, so existing tests and any caller that doesn't wire them are unaffected.
  • No new gate/env var — Events are a cheap, additive status surface.
  • RBAC is already covered by the leader-election role (events: create,patch), so no RBAC changes are needed.
  • No new dependencies (go.mod unchanged); k8s.io/client-go/tools/events and apierrors are already used in-tree.

Testing

  • New mcp_server_tool_controller_events_test.go — asserts the correct event type/reason is emitted for each of the three MCPServerTool outcomes (ToolsDiscovered / ValidationFailed / ReconcileFailed) using a fake client + events.NewFakeRecorder, plus a no-Recorder no-panic case.
  • go build ./core/..., go test -race -skip 'TestE2E.*' ./core/internal/controller/..., and golangci-lint run all pass.

Notes

Opening as draft / work-in-progress per CONTRIBUTING; happy to adjust event reasons/actions. The remaining gap from #2148 (controller log→OTLP bridge) will follow as a separate PR. Companion PR for gap 1 (token metrics): #2149.

Refs: #2148

Add an optional EventRecorder to the Agent, ModelConfig, MCPServerTool and
RemoteMCPServer controllers so reconcile outcomes are visible via
'kubectl describe', mirroring the pattern already used by the
agentharness-substrate controller.

Emitted events:
- Agent / ModelConfig / RemoteMCPServer: Normal Accepted on success,
  Warning ReconcileFailed on error.
- MCPServerTool: Normal ToolsDiscovered on success, Warning
  ValidationFailed for spec validation errors, Warning ReconcileFailed
  for transient errors.

Recording is nil-guarded on both the Recorder and Client, so it is a
no-op when either is unwired (e.g. in tests) and behaviour is unchanged.
RBAC is already covered by the leader-election role (events:
create,patch). Recorders are wired from pkg/app/app.go via
mgr.GetEventRecorder.

Refs: kagent-dev#2148
Signed-off-by: Diego Braga <diego.braga86@gmail.com>
@github-actions github-actions Bot added the enhancement New feature or request label Jul 4, 2026
@braghettos braghettos marked this pull request as ready for review July 4, 2026 16:11
Copilot AI review requested due to automatic review settings July 4, 2026 16:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds Kubernetes Event emission to several go/core controllers so reconcile outcomes are visible via kubectl describe / kubectl get events, wiring recorders from the controller manager.

Changes:

  • Wire Client + EventRecorder into Agent/ModelConfig/MCPServerTool/RemoteMCPServer controllers from pkg/app/app.go.
  • Emit Normal/Warning Kubernetes Events on reconcile success/failure for those controllers via a nil-guarded recordEvent helper.
  • Add a unit test covering MCPServerTool event emission paths.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
go/core/pkg/app/app.go Wires manager client + event recorders into the four controllers.
go/core/internal/controller/agent_controller.go Emits Accepted/ReconcileFailed Events for Agent reconciles via recordEvent.
go/core/internal/controller/modelconfig_controller.go Emits Accepted/ReconcileFailed Events for ModelConfig reconciles via recordEvent.
go/core/internal/controller/mcp_server_tool_controller.go Emits ToolsDiscovered/ValidationFailed/ReconcileFailed Events for MCPServer tool discovery reconciles.
go/core/internal/controller/remote_mcp_server_controller.go Emits Accepted/ReconcileFailed Events for RemoteMCPServer reconciles via recordEvent.
go/core/internal/controller/mcp_server_tool_controller_events_test.go Adds tests validating MCPServerTool emits expected event type/reason.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 75 to 79
r.recordEvent(ctx, req, "Normal", "ToolsDiscovered", "Reconcile", "MCPServer tools discovered successfully")
// Success - requeue after 60s to refresh tool server status
return ctrl.Result{
RequeueAfter: 60 * time.Second,
}, nil
Comment on lines 71 to 75
r.recordEvent(ctx, req, "Normal", "Accepted", "Reconcile", "RemoteMCPServer reconciled successfully")
// Success - requeue after 60s to refresh tool server status
return ctrl.Result{
RequeueAfter: 60 * time.Second,
}, nil
The MCPServerTool and RemoteMCPServer controllers requeue every 60s to
refresh tool status, and previously recorded a Normal success Event on
every reconcile — flooding kubectl events and adding control-plane writes
even when nothing changed.

Gate the success Event on a pending spec change (generation !=
observedGeneration, captured before reconcile); periodic no-op refreshes
no longer emit. Warning events (ReconcileFailed/ValidationFailed) still
emit every time. The Agent/ModelConfig controllers are unaffected (they
don't requeue periodically).

Refs: kagent-dev#2148
Signed-off-by: Diego Braga <diego.braga86@gmail.com>
@braghettos braghettos requested a review from a team as a code owner July 9, 2026 15:03
@braghettos

Copy link
Copy Markdown
Author

Addressed the Copilot feedback about Event flooding: the MCPServerTool and RemoteMCPServer controllers requeue every 60s, so they now emit the Normal success Event only on a meaningful transition — a pending spec change (generation != observedGeneration, captured before reconcile) — instead of on every periodic refresh. Warning Events (ReconcileFailed / ValidationFailed) still emit each time. Added a test asserting no success Event on a periodic no-op refresh. The Agent/ModelConfig controllers are unaffected since they don't requeue periodically.

@EItanya EItanya left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall the functionality makes sense. However I'm wondering if we should add this directly to the reconciler since that code already does the Get required to ensure that the object exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants