Skip to content

Comments

feat: add read-only mode for GitOps-managed deployments#1346

Open
frank-bee wants to merge 1 commit intokagent-dev:mainfrom
frank-bee:feat/read-only-mode
Open

feat: add read-only mode for GitOps-managed deployments#1346
frank-bee wants to merge 1 commit intokagent-dev:mainfrom
frank-bee:feat/read-only-mode

Conversation

@frank-bee
Copy link

Summary

  • Add ReadOnlyAuthorizer to the backend that only allows GET operations, activated via KAGENT_READ_ONLY=true env var
  • Add ReadOnlyProvider React context for the frontend, reading NEXT_PUBLIC_READ_ONLY env var
  • Conditionally hide all create/edit/delete UI controls when read-only mode is active
  • Add route guards on /agents/new and /models/new pages
  • Skip the onboarding wizard in read-only mode
  • Show "managed via GitOps" messaging in empty states

This enables GitOps-managed deployments (FluxCD, ArgoCD) where agents, models, and MCP servers are defined declaratively as Kubernetes CRDs and the UI serves as a read-only dashboard.

Environment Variables

Variable Component Default Description
KAGENT_READ_ONLY Controller false Enables read-only mode on the API
NEXT_PUBLIC_READ_ONLY UI false Enables read-only mode in the frontend

Files Changed

Backend (Go):

  • go/internal/httpserver/auth/authz.go — new ReadOnlyAuthorizer
  • go/cmd/controller/main.go — env var wiring

Frontend (Next.js):

  • ui/src/components/ReadOnlyProvider.tsx — new React context
  • ui/src/app/layout.tsx — wrap app in ReadOnlyProvider, add force-dynamic
  • 10 component/page files — conditional rendering of write controls

Test plan

  • Set KAGENT_READ_ONLY=true + NEXT_PUBLIC_READ_ONLY=true, verify all write controls are hidden
  • Verify API rejects POST/PUT/DELETE with clear error message
  • Verify chat functionality still works (read-only hides management, not chat)
  • Verify defaults: without env vars, everything works as before
  • Run make -C go test and make -C ui build

Closes #1344
Related: #1270, #593

Copilot AI review requested due to automatic review settings February 20, 2026 19:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-in read-only mode intended for GitOps-managed deployments, enforcing (or signaling) read-only behavior in the API layer and hiding/guarding write-related UI flows in the Next.js frontend.

Changes:

  • Backend: introduce ReadOnlyAuthorizer and wire it via KAGENT_READ_ONLY=true.
  • Frontend: add ReadOnlyProvider context driven by NEXT_PUBLIC_READ_ONLY, and hide/disable create/edit/delete controls.
  • Frontend: add route guards for “new” pages and skip onboarding while in read-only mode; update empty states with GitOps messaging.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
go/internal/httpserver/auth/authz.go Adds ReadOnlyAuthorizer that rejects non-GET verbs.
go/cmd/controller/main.go Selects ReadOnlyAuthorizer vs NoopAuthorizer via KAGENT_READ_ONLY.
ui/src/components/ReadOnlyProvider.tsx New React context + hook to expose read-only state.
ui/src/app/layout.tsx Wraps app with ReadOnlyProvider; forces dynamic rendering; reads env flag.
ui/src/components/Header.tsx Hides “Create” dropdowns when read-only.
ui/src/components/AppInitializer.tsx Skips onboarding wizard when read-only.
ui/src/components/AgentList.tsx Updates empty state messaging; hides create button when read-only.
ui/src/components/AgentCard.tsx Hides edit/delete controls when read-only.
ui/src/components/sidebars/AgentDetailsSidebar.tsx Hides agent edit button when read-only.
ui/src/components/sidebars/ChatItem.tsx Hides chat deletion option when read-only.
ui/src/app/models/page.tsx Hides “New Model” and edit/delete actions when read-only.
ui/src/app/models/new/page.tsx Redirects away (route guard) when read-only.
ui/src/app/agents/new/page.tsx Redirects away (route guard) when read-only.
ui/src/app/servers/page.tsx Hides server add/delete controls; updates empty state messaging when read-only.

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

Comment on lines +20 to +23
func (a *ReadOnlyAuthorizer) Check(ctx context.Context, principal auth.Principal, verb auth.Verb, resource auth.Resource) error {
if verb != auth.VerbGet {
return fmt.Errorf("read-only mode: %s operations are not permitted", verb)
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The current implementation blanket-denies all non-GET verbs for every resource type, but the PR description/test plan indicates chat should remain functional in read-only mode. Consider either (a) using the resource argument to allow specific non-GET operations needed for chat (e.g., sessions/events) while still blocking GitOps-managed resources, or (b) updating the PR description to clarify that backend read-only enforcement only applies to certain resource handlers.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

Not an issue. The authorization check is not applied as global middleware — it's called selectively inside individual handlers via a Check() helper in handlers/helpers.go.

Only these handlers call Check(): Agents, ModelConfig, Memory, ToolServers. Session/chat endpoints (/api/sessions/*, /api/sessions/{id}/events POST) never call the authorizer, so chat works fine in read-only mode.

The current blanket deny on non-GET is correct for the resources that use it (GitOps-managed: agents, models, servers).

Comment on lines 13 to 26
@@ -19,22 +22,26 @@ export const metadata: Metadata = {
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
const readOnly = process.env.NEXT_PUBLIC_READ_ONLY === "true";

Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

export const dynamic = "force-dynamic" in the root layout forces the entire app to be dynamically rendered and disables most static optimization/caching. If the only goal is to derive a single boolean flag from env, consider alternatives that avoid globally forcing dynamic rendering (e.g., accept build-time config for the UI, or scope dynamic behavior to a smaller boundary) to reduce performance and hosting costs.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

Intentional. The env var NEXT_PUBLIC_READ_ONLY needs to be read at container runtime (not build time) since this is deployed as a Docker image with configurable env vars. force-dynamic is required for that.

Scoping it per-page would add boilerplate for negligible gain — this is a K8s dashboard app, not a public-facing website where static optimization matters.

Comment on lines +18 to +25
type ReadOnlyAuthorizer struct{}

func (a *ReadOnlyAuthorizer) Check(ctx context.Context, principal auth.Principal, verb auth.Verb, resource auth.Resource) error {
if verb != auth.VerbGet {
return fmt.Errorf("read-only mode: %s operations are not permitted", verb)
}
return nil
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Add unit tests for ReadOnlyAuthorizer to verify it allows GET and rejects POST/PUT/DELETE with the expected error. There are already middleware tests in this package (e.g., authn_test.go), so covering this new authorizer here would help prevent regressions when expanding authz usage across more handlers.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

Already done — see authz_test.go added in this PR with table-driven tests covering both ReadOnlyAuthorizer (allows GET, rejects POST/PUT/DELETE) and NoopAuthorizer (allows all verbs).

Add a read-only mode that disables all write operations (create, update,
delete) in both the backend API and the UI. This is useful for GitOps-managed
deployments where resources are managed exclusively via Kubernetes CRDs.

Backend:
- Add ReadOnlyAuthorizer to auth package that only allows GET operations
- Wire KAGENT_READ_ONLY env var in controller main.go

Frontend:
- Add ReadOnlyProvider React context reading NEXT_PUBLIC_READ_ONLY env var
- Conditionally hide create/edit/delete controls across all UI components
- Add route guards on /agents/new and /models/new pages
- Skip onboarding wizard in read-only mode
- Show "managed via GitOps" messaging in empty states

Closes kagent-dev#1344

Signed-off-by: Frank Bernhardt <Frank.Bernhardt@quantum-machines.co>
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.

[FEATURE] Read-only mode for GitOps-managed deployments

1 participant