Lightweight Kubernetes operations agent with outbound-only connectivity and an MCP bridge for cluster management.
This repository owns the Kubernetes agent code, chart, production image, protocol contract, and agent-level docs. Central platform deployment wiring belongs in acornops-deployment.
This repository supports human and agent-assisted development. Start coding
agents from this repository root for AgentK-only work, and from the AcornOps
workspace cloned from the acornops
repository for changes that touch multiple AcornOps repositories.
Cross-repo contract documentation lives in docs/contracts/README.md. This repo's only direct platform dependency should be the control-plane protocol documented there.
Machine-readable contract data lives in docs/contracts/manifest.json.
Run npm run contracts:check to mechanically verify the documented agent/control-plane contract against the implementation.
Coverage is generated in CI with Vitest V8 coverage, uploaded as a workflow artifact, and published to Codecov when CODECOV_TOKEN is configured for the repository.
Primary docs:
AGENTS.mdARCHITECTURE.mddocs/index.mddocs/DEVELOPMENT.mddocs/OPERATIONS.md- Whole-system architecture:
../docs/system-architecture.md
- Rich Snapshots: Modular telemetry collection (Pods, workloads, Services, Ingresses, PVCs, Nodes, Metrics, Events).
- Outbound-only: Initiates secure WebSocket connection to the platform.
- MCP Bridge: Executes "Atomic Tools" and "Orchestrated Remediations" via JSON-RPC.
- Stateless: Zero local persistence; automatic re-handshake on reconnection.
- Safe: Built-in write guards and mandatory audit annotations.
- Runtime: Node.js 20+
- Language: TypeScript (Strict Mode)
- Core Libraries:
@kubernetes/client-node,ws,zod, andpino.
The agent is configured via environment variables:
| Variable | Description | Default |
|---|---|---|
ACORNOPS_AGENT_PLATFORM_URL |
WSS endpoint of the platform | (Required) |
ACORNOPS_CLUSTER_ID |
Control-plane cluster id this agent key is bound to | (Required) |
ACORNOPS_AGENT_KEY |
Unique agent authentication token | (Required) |
ACORNOPS_AGENT_HANDSHAKE_PROBE_TIMEOUT_MS |
Timeout for non-blocking metrics API probe during handshake | 3000 |
ACORNOPS_AGENT_KUBECONFIG_REWRITE_LOOPBACK |
Rewrite kubeconfig API server host (0.0.0.0/localhost) to ACORNOPS_AGENT_KUBECONFIG_HOST_ALIAS for containerized local dev |
false |
ACORNOPS_AGENT_KUBECONFIG_HOST_ALIAS |
Hostname used when loopback rewrite is enabled | host.docker.internal |
ACORNOPS_AGENT_KUBECONFIG_SKIP_TLS_VERIFY |
Skip TLS verification after loopback-host rewrite (local k3d convenience only) | false |
ACORNOPS_AGENT_K8S_CONCURRENCY |
Process-wide maximum concurrent Kubernetes API list requests during snapshot collection | 8 |
ACORNOPS_AGENT_K8S_LIST_PAGE_LIMIT |
Kubernetes API list page size used when collecting large snapshots | 500 |
ACORNOPS_AGENT_TOOL_READ_CONCURRENCY |
Maximum concurrent read tool calls | 4 |
ACORNOPS_AGENT_TOOL_WRITE_CONCURRENCY |
Maximum concurrent write tool calls | 1 |
ACORNOPS_AGENT_TOOL_QUEUE_LIMIT |
Maximum queued calls shared across read and write gates | 16 |
ACORNOPS_AGENT_TOOL_MAX_INPUT_BYTES |
Maximum inbound WebSocket/tool request size | 1048576 |
ACORNOPS_AGENT_TOOL_MAX_OUTPUT_BYTES |
Maximum serialized tool result size | 2097152 |
ACORNOPS_AGENT_SCALE_MAX_REPLICAS |
Maximum accepted scale target (hard ceiling: 100) | 100 |
ACORNOPS_AGENT_ALLOW_SCALE_TO_ZERO |
Operator opt-in required before caller-confirmed scale-to-zero | false |
ACORNOPS_AGENT_WORKLOAD_PATCH_KINDS |
Comma-separated local maximum for patch_workload kinds |
Deployment,StatefulSet,DaemonSet |
ACORNOPS_AGENT_RESOURCE_PATCH_KINDS |
Comma-separated opt-in kinds for Service/Ingress patch_resource |
Empty |
ACORNOPS_AGENT_ALLOW_ENV_PATCH |
Operator opt-in for non-secret workload environment changes | false |
ACORNOPS_AGENT_ALLOW_CONFIGMAP_PATCH |
Operator opt-in for non-secret ConfigMap data changes | false |
ACORNOPS_AGENT_ALLOW_SERVICE_SELECTOR_PATCH |
Operator opt-in required before caller-confirmed Service selector changes | false |
ACORNOPS_AGENT_RBAC_SCOPE |
Local RBAC boundary (cluster or namespace) |
cluster |
ACORNOPS_AGENT_RBAC_ADDITIONS_JSON |
Immutable, control-plane-generated custom-resource additions selected during onboarding | [] |
ACORNOPS_AGENT_WATCH_CACHE_ENABLED |
Build snapshots from a Kubernetes watch-backed local cache when ready | true |
ACORNOPS_AGENT_WATCH_SNAPSHOT_DEBOUNCE_MS |
Debounce window for snapshots triggered by watched resource or Warning event changes | 5000 |
ACORNOPS_AGENT_WATCH_CACHE_SYNC_TIMEOUT_MS |
Time to wait for watch cache warmup before using list fallback | 15000 |
ACORNOPS_AGENT_WATCH_TIMEOUT_SECONDS |
Kubernetes watch stream timeout before clean reconnect | 300 |
ACORNOPS_AGENT_WATCH_NAMESPACES |
Comma-separated list of namespaces to watch | All |
ACORNOPS_AGENT_EXCLUDE_NAMESPACES |
Comma-separated local namespace deny-list | Empty |
ACORNOPS_AGENT_WRITE_ENABLED |
Set to true to enable mutation tools |
false |
ACORNOPS_AGENT_LOCAL_FALLBACK_ENABLED |
Emit local synthetic node/usage snapshot when Kubernetes API is unreachable (dev only) | false |
ACORNOPS_AGENT_LOG_LEVEL |
Logging level (info, debug, error, warn, trace) |
info |
ACORNOPS_AGENT_LEADER_ELECTION_ENABLED |
Enable active-passive HA using a Kubernetes Lease |
false |
ACORNOPS_AGENT_LEASE_NAME |
Leader election Lease name | acornops-agent-leader |
ACORNOPS_AGENT_LEASE_NAMESPACE |
Leader election Lease namespace; empty uses pod namespace | Pod namespace |
ACORNOPS_AGENT_LEADER_IDENTITY |
Lease holder identity; empty uses pod UID, then pod name | Pod UID/name |
ACORNOPS_AGENT_LEASE_DURATION_MS |
Lease expiry window before another replica can take over | 15000 |
ACORNOPS_AGENT_RENEW_DEADLINE_MS |
Local renew failure deadline before the leader fences itself | 10000 |
ACORNOPS_AGENT_RETRY_PERIOD_MS |
Election retry/renew period | 2000 |
Snapshots are assembled from a Kubernetes watch-backed local cache by default. The agent still emits the existing compact notify/snapshot payload, but it keeps Pods, workloads, Services, Ingresses, PVCs, Nodes, Namespaces, and recent Warning Events fresh through list-then-watch streams instead of listing every resource on each interval.
The list collectors remain the fallback path when the watch cache is disabled, warming, or unhealthy. Initial sync and 410-compaction recovery still use bounded, paginated list calls, so ACORNOPS_AGENT_K8S_CONCURRENCY and ACORNOPS_AGENT_K8S_LIST_PAGE_LIMIT continue to bound API pressure during warmup, scope changes, and watch recovery.
Use snapshot-manager logs to track durationMs, skippedSnapshots, droppedSnapshots, originalBytes, and compressedBytes. If snapshots regularly skip interval ticks, increase the snapshot interval before raising ACORNOPS_AGENT_K8S_CONCURRENCY; higher concurrency can worsen API server pressure when list latency is already the bottleneck.
The supported release install path is the acornops-agentk Helm chart. This is the right install method whether the central AcornOps platform runs on Docker-on-VM or Kubernetes.
Install with the public platform base URL:
helm upgrade --install acornops-agent oci://ghcr.io/acornops/charts/acornops-agentk \
--namespace acornops \
--create-namespace \
--set-string config.platformUrl=https://api.acornops.dev \
--set-string config.clusterId=YOUR_CLUSTER_ID \
--set-string config.agentKey=YOUR_AGENT_KEYconfig.platformUrl is the public control-plane base URL. The chart derives the agent WebSocket URL as wss://<host>/api/v1/agent/connect. For Docker-on-VM central deployments, this is the API host, for example https://api.acornops.dev; the management console is served separately from https://console.acornops.dev/.
If your WebSocket route is custom, pass it directly:
helm upgrade --install acornops-agent oci://ghcr.io/acornops/charts/acornops-agentk \
--namespace acornops \
--create-namespace \
--set-string config.websocketUrl=wss://api.acornops.dev/api/v1/agent/connect \
--set-string config.clusterId=YOUR_CLUSTER_ID \
--set-string config.agentKey=YOUR_AGENT_KEYUse an existing Kubernetes Secret instead of passing the key in Helm values:
kubectl -n acornops create secret generic acornops-agent-key \
--from-literal=agent-key=YOUR_AGENT_KEY
helm upgrade --install acornops-agent oci://ghcr.io/acornops/charts/acornops-agentk \
--namespace acornops \
--create-namespace \
--set-string config.platformUrl=https://api.acornops.dev \
--set-string config.clusterId=YOUR_CLUSTER_ID \
--set-string existingSecret.name=acornops-agent-key \
--set-string existingSecret.key=agent-keyTo connect to a platform certificate signed by an organization-private CA, either supply a public PEM CA bundle directly to Helm:
--set-file config.tls.additionalCaBundle.inlinePem=/path/to/organization-ca.pemor reference an existing CA bundle in the AgentK release namespace:
config:
platformUrl: https://api.acornops.example
tls:
additionalCaBundle:
configMapKeyRef:
name: organization-platform-trust
key: ca.crtUse secretKeyRef with the same name and key fields when the bundle is
distributed as a Secret. The three sources are mutually exclusive and fail
closed when the selected resource or key is missing. AgentK mounts the bundle
read-only and uses NODE_EXTRA_CA_CERTS, which extends Node.js public CA trust
without disabling hostname or certificate verification. The resource must be
distributed into the release namespace of every affected workload cluster.
Changes supplied through --set-file roll AgentK automatically; changes to an
existing ConfigMap or Secret require a restart. Rotate private roots with an
old/new overlap, restart AgentK, rotate the platform certificate, then remove
the old root and restart again. See the
chart guide for
Secret configuration, trust-manager compatibility, commands, and failure-mode
troubleshooting.
The chart defaults to cluster-wide read-only RBAC. Write tools require an explicit opt-in:
helm upgrade --install acornops-agent oci://ghcr.io/acornops/charts/acornops-agentk \
--namespace acornops \
--create-namespace \
--set-string config.platformUrl=https://api.acornops.dev \
--set-string config.clusterId=YOUR_CLUSTER_ID \
--set-string config.agentKey=YOUR_AGENT_KEY \
--set rbac.write.enabled=truepatch_workload accepts semantic image, non-secret environment, and metadata
operations. CronJob support is an explicit patchPolicy.workloadKinds opt-in.
Service and Ingress support uses patchPolicy.resourceKinds.
patch_configmap requires patchPolicy.allowConfigMapChanges=true; workload
environment changes require patchPolicy.allowEnvChanges=true. Literal
environment and ConfigMap values are limited to single-line, non-secret
configuration of at most 4 KiB, and the caller must set
confirm_non_secret_data=true for each request that supplies one. Service
selector changes also require patchPolicy.allowServiceSelectorChanges=true
and caller confirmation. ConfigMap get/list/watch RBAC is granted only when
patchPolicy.allowConfigMapChanges=true; ConfigMap patch RBAC additionally
requires rbac.write.enabled=true.
For namespace-scoped installs, create Roles only in the watched namespaces:
helm upgrade --install acornops-agent oci://ghcr.io/acornops/charts/acornops-agentk \
--namespace acornops \
--create-namespace \
--set-string config.platformUrl=https://api.acornops.dev \
--set-string config.clusterId=YOUR_CLUSTER_ID \
--set-string config.agentKey=YOUR_AGENT_KEY \
--set-string rbac.scope=namespace \
--set-json 'namespaceScope.include=["team-a","team-b"]'When rbac.scope=namespace, the chart creates Roles in rbac.namespaces when set, otherwise in namespaceScope.include, otherwise in the release namespace.
Active-passive HA is opt-in. Multiple replicas are rejected unless leader election is enabled, because the agent is not active-active safe. In HA mode, only the elected Lease holder connects to the control plane, sends snapshots/heartbeats, and serves tool calls:
helm upgrade --install acornops-agent oci://ghcr.io/acornops/charts/acornops-agentk \
--namespace acornops \
--create-namespace \
--set-string config.platformUrl=https://api.acornops.dev \
--set-string config.clusterId=YOUR_CLUSTER_ID \
--set-string config.agentKey=YOUR_AGENT_KEY \
--set replicaCount=2 \
--set leaderElection.enabled=trueThe default remains replicaCount=1 with leader election disabled. That keeps the common install simple and avoids granting Lease write permissions unless HA is requested. For HA, replicaCount=2 is usually enough to survive one agent pod or node loss. Use replicaCount=3 only when you want two warm standbys or broader scheduling spread; Kubernetes Lease election does not need an odd number of agent replicas.
Shorter Lease timings reduce failover time but increase Kubernetes API churn and sensitivity to transient API delays. Longer timings reduce churn but extend the passive takeover gap.
Check status and logs:
kubectl -n acornops rollout status deployment/acornops-agent
kubectl -n acornops logs -f deployment/acornops-agentUninstall:
helm uninstall acornops-agent --namespace acornopsThe raw manifests under deploy/ are retained for manual development and troubleshooting. Prefer Helm for release installs.
If you have a Kubernetes cluster already running and configured in your KUBECONFIG:
-
Install dependencies:
npm install
-
Run in development mode:
export ACORNOPS_AGENT_PLATFORM_URL=wss://api.acornops.dev/api/v1/agent/connect export ACORNOPS_CLUSTER_ID=your-cluster-id export ACORNOPS_AGENT_KEY=your-test-key npm run dev
Compose files are split as:
docker-compose.yml: base/default runtime foracornops-agentk.docker-compose.override.yml: local development additions (mock-platform, host mounts, host ports, local image build).
- Component-only local development (recommended in this repo):
docker compose up -d --buildThis local mode runs the agent with tsx watch and the mock platform with node --watch, so code changes are reflected immediately.
- Component-only production-style container (agent only):
docker compose -f docker-compose.yml up -d- Full AcornOps stack (all components together):
cd ../acornops-deployment
task local-upThis full-stack flow uses the deployment repo Taskfile.yml and requires the task CLI to be installed.
Use full-stack mode when validating the agent against the real control-plane service instead of the local mock platform.
Do not run this repository's local compose stack and acornops-deployment local stack at the same time on the same host ports.
If dependencies change (package.json or lockfile), rebuild once:
docker compose up -d --buildFor efficient component-level development, run the agent with the local Mock Platform and command-testing endpoint.
The local mock platform provides:
- WebSocket Server: Listens on
ws://localhost:3000/agent - Snapshot Storage: Decompressed snapshots are saved to
./local-snapshots/in Docker Compose local mode. When the mock platform is run directly, it writes toSNAPSHOT_DIRor./snapshots/. - Command Trigger:
POST http://localhost:3000/send-commandto send MCP requests to the agent. - Health Endpoint:
GET http://localhost:3000/health - Connection Inspect Endpoint:
GET http://localhost:3000/connections - Swagger UI (Mock Platform):
http://localhost:3000/docs - OpenAPI JSON (Mock Platform):
http://localhost:3000/openapi.json
The acornops-agentk process itself is not an HTTP API server; it is a websocket JSON-RPC client. For local developer docs/testing, use the mock platform HTTP API above.
This guide provides step-by-step instructions to set up a local Kubernetes cluster using k3d, build the agent, and deploy it for manual testing.
Ensure you have the following installed:
Create a simple one-node cluster:
k3d cluster create acornops-demo-cluster --no-lb --waitBuild the Docker image locally:
docker build -t acornops/agent:local .Import the local image into the k3d cluster so it's available without a registry:
k3d image import acornops/agent:local -c acornops-demo-clusterCreate the acornops namespace:
kubectl create namespace acornopsCreate a dummy secret for testing:
kubectl create secret generic acornops-agentk-secret \
--from-literal=agent-key=test-agent-key-123 \
-n acornopsApply the RBAC and Deployment manifests:
# Apply RBAC
kubectl apply -f deploy/rbac.yaml
# Use the local development manifest, which adds least-privilege workload patch
# RBAC because this legacy development deployment explicitly enables write tools
kubectl apply -f deploy/local-development.yamlCheck the mock platform logs to see the agent connecting and sending snapshots:
docker compose logs -f mock-platformCheck the local-snapshots/ directory for saved data in Docker Compose local mode.
You can test tools by sending commands via the mock platform:
curl -X POST http://localhost:3000/send-command \
-H "Content-Type: application/json" \
-d '{
"method": "get_pod_logs",
"params": {
"podName": "acornops-agentk-xxxxxxxxxx-xxxxx",
"namespace": "acornops"
}
}'Check if the pod is running:
kubectl get pods -n acornops -l app=acornops-agentkCheck the agent logs:
kubectl logs -f -n acornops -l app=acornops-agentkWhen finished, delete the cluster:
k3d cluster delete acornops-demo-clusterRun unit tests:
npm testRun E2E tests (requires a running cluster):
npm run test:e2eRun the checks that match the change:
npm run lintnpm run contracts:checknpm run harness:checknpm run validatenpm testnpm run test:e2ewhen lifecycle, websocket, or RBAC behavior changes
list_resources: List bounded built-in or administrator-approved custom-resource summaries.get_resource: Fetch a redacted built-in or administrator-approved custom resource by name.get_resource_logs: Read bounded Pod logs (max 1 MiB).restart_workload: Guarded rolling restart for Deployments, StatefulSets, and DaemonSets.scale_workload: Guarded scaling for Deployments and StatefulSets.patch_workload: Apply guarded workload image, single-line non-secret environment, label, or annotation changes.patch_resource: Apply guarded Service or Ingress changes, or/specchanges to administrator-approved custom resources.patch_configmap: Set or remove bounded single-line non-secret ConfigMap data keys without restarting consumers.