From 9b7fdcd5f2c6f1c7633b20503aa8bf1184dc9270 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sat, 2 May 2026 00:50:06 -0400 Subject: [PATCH 01/28] ci: enforce Linear issue reference on PRs to develop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `linear-ref` job to validate.yml that fails PRs to develop unless the PR body or one of its commit messages contains an `AMC-N` Linear identifier — or an explicit `No-Linear-Issue: ` trailer for genuinely untracked work. Why: the Linear ↔ GitHub integration auto-closes Linear issues only when a PR references the right ID. The dashboard work in v0.6.0 missed this — AMC-71/72/73/74 sat in Backlog through the release because the PRs referenced freshly-created GitHub issue numbers instead of the existing AMC IDs the integration knew about. Skipped for: - PRs to main (release promotion rolls up commits already checked at the develop merge — matches the existing commit-lint pattern) - dependabot bumps (no Linear tracking by convention) PR template grows a `## Linear` section with a `Fixes AMC-XXX` placeholder so the prompt is unmissable when opening a new PR. Fixes AMC-98 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 9 ++++++ .github/workflows/validate.yml | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d3b7998..c182ec2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,6 +2,15 @@ +## Linear + + + +Fixes AMC-XXX + ## Changes diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 1e3bcd5..7c0ad69 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -195,3 +195,52 @@ jobs: echo "" echo "Tag the next release with: \`./scripts/bump-version.sh $BUMP\`" } | tee -a "$GITHUB_STEP_SUMMARY" + + # ── Linear Issue Reference (PRs to develop) ─────────────────────────────── + # Enforces that PRs reference a Linear issue (AMC-N) so the Linear ↔ GitHub + # sync auto-closes the right ticket on merge. Without this, Linear tickets + # stay in Backlog after their work ships (verified 2026-05-02 with + # AMC-71/72/73/74). Skipped for PRs to main (release-promotion rolls up + # commits already checked at the develop merge) and for dependabot. + linear-ref: + name: Linear Issue Reference + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.base_ref != 'main' && github.actor != 'dependabot[bot]' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + + - name: Check PR body or commits reference an AMC-N issue + env: + PR_BODY: ${{ github.event.pull_request.body }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.sha }} + run: | + BUFFER="$PR_BODY"$'\n'"$(git log --format='%B' "${BASE_SHA}..${HEAD_SHA}")" + + if echo "$BUFFER" | grep -qE 'AMC-[0-9]+'; then + ID=$(echo "$BUFFER" | grep -oE 'AMC-[0-9]+' | head -1) + echo "✓ References Linear issue: $ID" + exit 0 + fi + + if echo "$BUFFER" | grep -qE '^No-Linear-Issue:'; then + REASON=$(echo "$BUFFER" | grep -E '^No-Linear-Issue:' | head -1 | sed 's/^No-Linear-Issue:[[:space:]]*//') + echo "✓ Explicit opt-out (No-Linear-Issue: $REASON)" + exit 0 + fi + + echo "✗ This PR does not reference a Linear issue." + echo "" + echo "Add an AMC-N reference to the PR body (e.g. 'Fixes AMC-123')" + echo "or any commit message, so the Linear ↔ GitHub sync auto-closes" + echo "the right ticket on merge." + echo "" + echo "Linear project: https://linear.app/amcheste/project/claude-teams-operator-32aab082f36b" + echo "" + echo "If this PR genuinely is not associated with a Linear issue (e.g." + echo "trivial doc fix, dependency bump, release promotion), add a" + echo "trailer to the PR body:" + echo " No-Linear-Issue: " + exit 1 From 053678ece9baec047e7c59b2ee1b287c210f8a69 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sat, 2 May 2026 00:57:59 -0400 Subject: [PATCH 02/28] feat(docs): scaffold mkdocs-material site + CI deploy workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First v0.7.0 milestone landing — stands up the documentation site infrastructure so subsequent content issues just write Markdown into a working site. Includes: - mkdocs.yml configured for mkdocs-material with dark-mode toggle, search, code highlighting tuned for YAML/Go, indigo palette, Inter/JetBrains Mono fonts, GitHub repo + edit links - docs/index.md — placeholder homepage with hero, quickstart install one-liner, and the four "why kagents" differentiators - docs/README.md — contributor dev-loop instructions (excluded from the built site to avoid the README/index collision) - docs/requirements.txt — pinned mkdocs-material 9.5.50 + git-revision-date-localized 1.2.7 - .github/workflows/docs.yml — builds with mkdocs gh-deploy on every push to main that touches docs/, mkdocs.yml, or the workflow itself - .gitignore — adds the mkdocs site/ build output Excludes pre-existing docs/helm-values.md and docs/cfp/ from the site build via exclude_docs — they have repo-relative links that break in the site context. Both will be migrated into proper site pages (helm-values into /reference/, cfp/ stays internal repo-only) in follow-up v0.7.0 issues. The first deploy after merge will create the gh-pages branch and serve the site at amcheste.github.io/claude-teams-operator until DNS for kagents.dev (AMC-82) lands, at which point the same site auto-resolves at https://kagents.dev with no further code change. Verified locally: `mkdocs build --strict` clean (after commit) and `mkdocs serve` renders cleanly in light + dark mode. Fixes AMC-56 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .github/workflows/docs.yml | 46 +++++++++++++++++++++ docs/README.md | 20 +++++++++ docs/index.md | 27 ++++++++++++ docs/requirements.txt | 4 ++ mkdocs.yml | 85 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/README.md create mode 100644 docs/index.md create mode 100644 docs/requirements.txt create mode 100644 mkdocs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..3f55a5f --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,46 @@ +name: Deploy Docs + +on: + push: + branches: [main] + paths: + - 'docs/**' + - 'mkdocs.yml' + - '.github/workflows/docs.yml' + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: docs-deploy + cancel-in-progress: false + +jobs: + deploy: + name: Build and Deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + # Required for git-revision-date-localized to read commit history. + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: pip + cache-dependency-path: docs/requirements.txt + + - name: Install mkdocs-material + run: pip install -r docs/requirements.txt + + - name: Configure git for gh-deploy + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # mkdocs gh-deploy builds the site and force-pushes to the gh-pages + # branch in a single command. The branch is created on first run. + - name: Build + deploy to gh-pages + run: mkdocs gh-deploy --force --no-history diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..84fc85f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,20 @@ +# Docs site + +This directory holds the [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) source for [kagents.dev](https://kagents.dev). + +## Local development + +```bash +pip install -r docs/requirements.txt +mkdocs serve # http://localhost:8000 +``` + +Edits to any file under `docs/` or `mkdocs.yml` hot-reload in the browser. + +## Deploying + +A push to `main` that touches `docs/`, `mkdocs.yml`, or `.github/workflows/docs.yml` triggers `Deploy Docs`, which builds the site with `mkdocs gh-deploy` and force-pushes the rendered HTML to the `gh-pages` branch. GitHub Pages serves it at https://kagents.dev (and at `amcheste.github.io/claude-teams-operator` until the custom domain DNS resolves). + +## Structure + +The site uses the [Diátaxis framework](https://diataxis.fr) — four sections: Tutorials, How-to guides, Reference, Explanation. Section pages will be filled in by the v0.7.0 content issues. For now only the homepage exists. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..9ace2a7 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,27 @@ +# kagents + +**Run Claude Code Agent Teams as a Kubernetes operator.** + +`kagents` brings Anthropic's native Claude Code Agent Teams pattern into Kubernetes. A lead agent coordinates work via a shared task list while teammate agents communicate through file-based mailboxes — the same coordination protocol as the local tmux experience, just running as pods on your cluster. + +!!! note "Site under construction" + This site is being populated as part of the [v0.7.0 milestone](https://github.com/amcheste/claude-teams-operator/milestone/8). Until then, see the [README on GitHub](https://github.com/amcheste/claude-teams-operator) for installation and usage details. + +## Quick install + +```bash +helm install kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --create-namespace +``` + +## Why kagents + +- **Native protocol fidelity** — wraps Anthropic's file-based mailbox protocol exactly as designed; no custom RPC layer to maintain +- **Team as a first-class resource** — one `AgentTeam` CRD declares roles, budget, quality gates, coordination topology +- **Kubernetes as coordination fabric** — ServiceAccounts scope agent capabilities, RWX PVCs hold the shared mailboxes, RBAC enforces per-agent boundaries +- **Dogfooded** — built with the same agent-teams system it operates + +## Repository + +[github.com/amcheste/claude-teams-operator](https://github.com/amcheste/claude-teams-operator) — Apache 2.0 diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..6db7b52 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +# Pinned for reproducible site builds. Bump via Dependabot or by hand +# after testing locally with `mkdocs build --strict`. +mkdocs-material==9.5.50 +mkdocs-git-revision-date-localized-plugin==1.2.7 diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..3f343c5 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,85 @@ +site_name: kagents +site_description: Run Claude Code Agent Teams as a Kubernetes operator +site_url: https://kagents.dev +site_author: CAM Labs LLC + +repo_name: amcheste/claude-teams-operator +repo_url: https://github.com/amcheste/claude-teams-operator +edit_uri: edit/main/docs/ + +copyright: Copyright © 2026 CAM Labs LLC + +# Files inside docs/ that aren't part of the public site. +# `helm-values.md` and `cfp/` predate the docs site and link to repo +# files outside docs/; they'll be migrated into the site (helm-values +# under /reference/, cfp/ stays internal) in follow-up v0.7.0 issues. +# `README.md` collides with index.md on the auto-generated index — kept +# in-repo as the contributor-facing dev-loop guide. +exclude_docs: | + README.md + helm-values.md + cfp/ + +theme: + name: material + features: + - navigation.instant + - navigation.tracking + - navigation.sections + - navigation.top + - navigation.footer + - search.suggest + - search.highlight + - content.code.copy + - content.action.edit + - toc.follow + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + font: + text: Inter + code: JetBrains Mono + icon: + repo: fontawesome/brands/github + +plugins: + - search + - git-revision-date-localized: + type: date + enable_creation_date: true + fallback_to_build_date: true + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - tables + - toc: + permalink: true + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/amcheste/claude-teams-operator From 94701e76f72493fdf5aadf85e43367cbbc28478b Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sat, 2 May 2026 00:59:44 -0400 Subject: [PATCH 03/28] chore(gitignore): ignore mkdocs site/ build output The mkdocs build writes to site/ in the repo root. Without this, local `mkdocs build` or `mkdocs gh-deploy` runs leave it as untracked clutter every time a contributor previews the docs site. Fixes AMC-56 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 55e9377..224c97b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,5 +30,8 @@ Thumbs.db # Build dist/ +# mkdocs build output +site/ + # Kind kubeconfig From 4a12993110fbf54af0d00181512d54495fccbe31 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sat, 2 May 2026 01:11:47 -0400 Subject: [PATCH 04/28] =?UTF-8?q?feat(docs):=20Di=C3=A1taxis=20nav,=20poli?= =?UTF-8?q?shed=20homepage,=20Getting=20Started=20tutorial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three v0.7.0 issues bundled — they're tightly coupled: - AMC-84: Diátaxis nav with four top-level sections (Tutorials, How-to guides, Reference, Explanation) + section index pages so the structure renders correctly even when sections are sparse - AMC-85: Polished homepage replacing the placeholder — Material card grid for "Why kagents" and the four section pointers, hero with primary/secondary CTAs, 5-line quickstart, footer with source + KubeCon talk pointer - AMC-86: Getting Started tutorial — end-to-end Kind walkthrough from `make kind-create` through running a Cowork-mode hello team and inspecting its output. ~15 minutes start to finish, no cloud accounts needed. Common-errors collapsibles cover the three failure modes a fresh user hits most often (PVC pending, CrashLoopBackOff from missing API key, init-Job permission). Section index pages for sparse sections (How-to, Reference, Explanation) explicitly point at the existing in-repo docs (helm-values.md, ARCHITECTURE.md) as the interim source while the v0.7.0 content issues fill them in. No broken links, no dead-end pages. Verified locally: `mkdocs build --strict` clean (post-commit, the git-revision-date-localized warnings only fire for uncommitted files), `mkdocs serve` renders cleanly in light + dark mode. Fixes AMC-84 Fixes AMC-85 Fixes AMC-86 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- docs/explanation/index.md | 11 ++ docs/how-to/index.md | 12 ++ docs/index.md | 81 ++++++++++-- docs/reference/index.md | 10 ++ docs/tutorials/getting-started.md | 205 ++++++++++++++++++++++++++++++ docs/tutorials/index.md | 15 +++ mkdocs.yml | 15 +++ 7 files changed, 340 insertions(+), 9 deletions(-) create mode 100644 docs/explanation/index.md create mode 100644 docs/how-to/index.md create mode 100644 docs/reference/index.md create mode 100644 docs/tutorials/getting-started.md create mode 100644 docs/tutorials/index.md diff --git a/docs/explanation/index.md b/docs/explanation/index.md new file mode 100644 index 0000000..bf9bca9 --- /dev/null +++ b/docs/explanation/index.md @@ -0,0 +1,11 @@ +# Explanation + +The "why" behind kagents — architecture, design tradeoffs, the choices that shaped the project. Read these when you want to understand what's actually happening, not just how to use it. + +## Coming in v0.7.0 + +- **Resource model** — how `AgentTeam`, `AgentTeamTemplate`, and `AgentTeamRun` relate; when to reach for which +- **Coordination protocol** — the file-based mailbox model, why ReadWriteMany is required, per-teammate git worktrees as a concurrency primitive +- **Operations** — budget estimation, per-agent RBAC, observability via Prometheus + Grafana + +Until these land, [ARCHITECTURE.md in the repo](https://github.com/amcheste/claude-teams-operator/blob/main/ARCHITECTURE.md) is the most complete design document. diff --git a/docs/how-to/index.md b/docs/how-to/index.md new file mode 100644 index 0000000..9f1d801 --- /dev/null +++ b/docs/how-to/index.md @@ -0,0 +1,12 @@ +# How-to guides + +Recipes for solving specific operational tasks. These assume you already have kagents installed and a working AgentTeam — if not, start with the [Getting Started tutorial](../tutorials/getting-started.md). + +## Coming in v0.7.0 + +- Install on EKS / GKE / AKS with platform-specific RWX storage +- Expose the dashboard via Ingress +- Configure shared storage backups and performance tuning +- Set up budget alerts to Slack/PagerDuty + +Until these land, the [README's Quick Start](https://github.com/amcheste/claude-teams-operator#quick-start) and the in-repo [`docs/helm-values.md`](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md) cover most operational tasks. diff --git a/docs/index.md b/docs/index.md index 9ace2a7..44dd9f0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,11 +1,17 @@ +--- +hide: + - navigation + - toc +--- + # kagents **Run Claude Code Agent Teams as a Kubernetes operator.** -`kagents` brings Anthropic's native Claude Code Agent Teams pattern into Kubernetes. A lead agent coordinates work via a shared task list while teammate agents communicate through file-based mailboxes — the same coordination protocol as the local tmux experience, just running as pods on your cluster. +[Get started in 5 minutes :material-rocket-launch:](tutorials/getting-started.md){ .md-button .md-button--primary } +[View on GitHub :material-github:](https://github.com/amcheste/claude-teams-operator){ .md-button } -!!! note "Site under construction" - This site is being populated as part of the [v0.7.0 milestone](https://github.com/amcheste/claude-teams-operator/milestone/8). Until then, see the [README on GitHub](https://github.com/amcheste/claude-teams-operator) for installation and usage details. +--- ## Quick install @@ -17,11 +23,68 @@ helm install kagents \ ## Why kagents -- **Native protocol fidelity** — wraps Anthropic's file-based mailbox protocol exactly as designed; no custom RPC layer to maintain -- **Team as a first-class resource** — one `AgentTeam` CRD declares roles, budget, quality gates, coordination topology -- **Kubernetes as coordination fabric** — ServiceAccounts scope agent capabilities, RWX PVCs hold the shared mailboxes, RBAC enforces per-agent boundaries -- **Dogfooded** — built with the same agent-teams system it operates +
+ +- :material-protocol:{ .lg .middle } **Native protocol fidelity** + + --- + + Wraps Anthropic's file-based mailbox protocol exactly as designed. No custom RPC layer to maintain, no protocol translation, no behavior drift when Claude Code ships an update. + +- :material-account-group:{ .lg .middle } **Team as a first-class resource** + + --- + + One `AgentTeam` CRD declares roles, budget, quality gates, and coordination topology. `AgentTeamTemplate` lets you reuse common team patterns — "3-agent security review," "fullstack feature team" — with one-line instantiation. + +- :material-kubernetes:{ .lg .middle } **K8s as coordination fabric** + + --- + + ServiceAccounts scope what each agent pod can touch. RWX PVCs hold the shared mailboxes. RBAC enforces per-agent capability boundaries. The cluster does the coordination work — kagents just wires it up. + +- :material-recycle-variant:{ .lg .middle } **Dogfooded** + + --- + + Built with the same Claude Code agent teams it operates. Every release is shipped by an agent team running in production. The recursion is intentional. + +
+ +## What you'll find here + +
+ +- :material-school: **[Tutorials](tutorials/index.md)** + + Step-by-step walkthroughs from zero to a running AgentTeam. + +- :material-cog: **[How-to guides](how-to/index.md)** + + Recipes for specific operational tasks — install on a cloud, expose the dashboard, tune budgets. + +- :material-book-open-variant: **[Reference](reference/index.md)** + + CRD field reference, Helm values, CLI flags. + +- :material-lightbulb: **[Explanation](explanation/index.md)** + + How and why kagents works the way it does — the architecture, the design tradeoffs. + +
+ +--- + +
+ +- :fontawesome-brands-github:{ .lg .middle } **Source code** + + Apache 2.0. Issues, PRs, and Discussions welcome. + + [github.com/amcheste/claude-teams-operator](https://github.com/amcheste/claude-teams-operator) + +- :material-presentation:{ .lg .middle } **Talk** -## Repository + *Reconciling Agent Teams: A Kubernetes Operator for Claude Code* — KubeCon NA 2026 (submitted). -[github.com/amcheste/claude-teams-operator](https://github.com/amcheste/claude-teams-operator) — Apache 2.0 +
diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 0000000..92ef358 --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,10 @@ +# Reference + +The lookup tables — CRD field-by-field detail, Helm values, CLI flags. + +## Coming in v0.7.0 + +- **API reference** — auto-generated CRD docs for `AgentTeam`, `AgentTeamTemplate`, `AgentTeamRun` from the kubebuilder markers in `api/v1alpha1/` +- **Helm chart values** — every value documented with defaults and production override recipes (migrated from the existing [`docs/helm-values.md`](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md)) + +For now, the in-repo [Helm values reference](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md) is the most complete source. diff --git a/docs/tutorials/getting-started.md b/docs/tutorials/getting-started.md new file mode 100644 index 0000000..8b98cd3 --- /dev/null +++ b/docs/tutorials/getting-started.md @@ -0,0 +1,205 @@ +# Getting started + +This tutorial walks you from a fresh laptop to a running AgentTeam in about 15 minutes. By the end you'll have: + +- A local Kubernetes cluster with kagents installed +- A small Cowork-mode AgentTeam that researches a topic and writes a summary file +- The know-how to inspect what's happening with `kubectl` and the dashboard + +You don't need any cloud accounts or external services — everything runs on your laptop. + +## Prerequisites + +| Tool | Version | Why | +|------|---------|-----| +| [Docker](https://docs.docker.com/get-docker/) | latest | Runs the Kind cluster | +| [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) | 0.25+ | Single-node Kubernetes for dev | +| [kubectl](https://kubernetes.io/docs/tasks/tools/) | 1.28+ | Interact with the cluster | +| [helm](https://helm.sh/docs/intro/install/) | 3.14+ | Install the operator chart | +| [Anthropic API key](https://console.anthropic.com/) | — | Required for agents to actually call Claude | + +You'll also need the kagents repo cloned locally so you can use the included `make kind-create` setup script (which provisions a Kind cluster with the NFS-style RWX storage the operator needs): + +```bash +git clone https://github.com/amcheste/claude-teams-operator.git +cd claude-teams-operator +``` + +## 1. Stand up a local cluster + +```bash +make kind-create +``` + +This creates a Kind cluster named `claude-teams` with a local-path storage class aliased as `nfs`. On a single-node cluster every pod runs on the same node, so a hostPath volume is visible to all pods — that's our RWX-equivalent for laptop testing. + +!!! note "Production deployments need a real RWX backend" + For real multi-node clusters you'll need NFS, EFS, Filestore, or Azure Files. The Kind setup is a single-node convenience, not the production story. See the *Concept: file-based mailbox protocol* page (coming in v0.7.0) for why. + +Verify the cluster is up: + +```bash +kubectl cluster-info --context kind-claude-teams +``` + +## 2. Install kagents + +```bash +helm install kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --create-namespace +``` + +Wait for the operator pod to be ready: + +```bash +kubectl rollout status deployment/kagents-controller-manager \ + --namespace claude-teams-system --timeout=120s +``` + +You should see `deployment "kagents-controller-manager" successfully rolled out`. + +## 3. Create your Anthropic API key Secret + +The operator reads this Secret from the namespace where your team runs (not the operator's namespace). Create a namespace for the team and put the key there: + +```bash +kubectl create namespace dev-agents +kubectl create secret generic anthropic-api-key \ + --namespace dev-agents \ + --from-literal=ANTHROPIC_API_KEY=sk-ant-... +``` + +Replace `sk-ant-...` with your actual key from [console.anthropic.com](https://console.anthropic.com/). + +!!! warning "Don't commit your API key" + The Secret stays in the cluster. Never paste a real key into a manifest you'll commit. Use `kubectl create secret` from your shell as above, or sealed-secrets / external-secrets for production. + +## 4. Apply your first AgentTeam + +This is a small Cowork-mode team — no git repo, just an output volume. The lead coordinates a single writer agent that produces a Markdown file. + +```yaml title="hello-team.yaml" +apiVersion: claude.amcheste.io/v1alpha1 +kind: AgentTeam +metadata: + name: hello-team + namespace: dev-agents +spec: + workspace: + output: + mountPath: /workspace/output + size: 1Gi + + auth: + apiKeySecret: anthropic-api-key + + lead: + model: opus + prompt: | + Coordinate a one-person team that writes a 200-word overview of + Kubernetes operators to /workspace/output/overview.md. Make sure + the file is written before declaring the work complete. + + teammates: + - name: writer + model: sonnet + prompt: | + Write a 200-word overview of Kubernetes operators to + /workspace/output/overview.md. Keep it accessible to a reader + who has never used Kubernetes before. Cover: what an operator + is, what problem it solves, and one concrete example. + + lifecycle: + timeout: 30m + budgetLimit: "1.00" +``` + +Apply it: + +```bash +kubectl apply -f hello-team.yaml +``` + +## 5. Watch the team run + +```bash +kubectl get agentteams -n dev-agents -w +``` + +You'll see the team progress through phases: + +| Phase | Meaning | +|-------|---------| +| `Pending` | Operator received the spec; PVCs being provisioned | +| `Initializing` | Init Job running (sets up worktrees / output volume) | +| `Running` | Agent pods are up and working | +| `Completed` | The lead reported the work done | +| `Failed` / `BudgetExceeded` / `TimedOut` | Terminal failure states | + +A 200-word write usually finishes in 1–3 minutes. + +When it reaches `Completed`, press Ctrl-C to stop watching. + +## 6. Inspect what happened + +The `describe` view shows everything in one place: + +```bash +kubectl describe agentteam hello-team -n dev-agents +``` + +You'll see: + +- The `Status` block with phase, ready count, estimated cost +- A `Lead` and `Teammates` section with each agent's pod status +- Recent `Events` from the operator at every phase transition + +To see the actual file the team produced, exec into the writer pod and read it: + +```bash +kubectl exec -n dev-agents hello-team-writer -- cat /workspace/output/overview.md +``` + +## 7. Clean up + +Delete the team and the namespace: + +```bash +kubectl delete agentteam hello-team -n dev-agents +kubectl delete namespace dev-agents +``` + +The operator will tear down all the team's pods, PVCs, and per-agent ServiceAccounts via owner references. To uninstall kagents itself: + +```bash +helm uninstall kagents -n claude-teams-system +kubectl delete namespace claude-teams-system +``` + +To tear down the whole Kind cluster: + +```bash +make kind-delete +``` + +## What you just did + +A real Kubernetes operator just orchestrated two Claude Code instances communicating via a shared filesystem to produce real output, with K8s primitives doing the coordination work — RWX PVC for the mailbox, ServiceAccounts for per-agent identity, owner references for cleanup. No custom protocol, no orchestrator service, no daemon outside the cluster. + +## Where to go next + +- **[How-to guides](../how-to/index.md)** — install on a real cloud, expose the dashboard, set budget alerts +- **[Reference](../reference/index.md)** — every CRD field and Helm value documented +- **[Explanation](../explanation/index.md)** — how the file-based mailbox protocol actually works under the hood + +## Common errors + +??? warning "`PVCs stuck in Pending`" + The operator requires a ReadWriteMany-capable StorageClass. On a Kind cluster, `make kind-create` sets one up under the alias `nfs`. If you're using your own cluster, check `kubectl get sc` — there must be one named `nfs` (or you need to pass `--set storage.storageClassName=` when installing the chart). + +??? warning "`Pod stuck in CrashLoopBackOff`" + Check the agent pod logs: `kubectl logs -n dev-agents hello-team-writer`. The most common cause is a missing or invalid Anthropic API key. Re-create the Secret with `kubectl create secret generic anthropic-api-key --namespace dev-agents --from-literal=ANTHROPIC_API_KEY=... --dry-run=client -o yaml | kubectl apply -f -`. + +??? warning "`AgentTeam stuck in Initializing`" + The init Job may have failed. Inspect: `kubectl get jobs -n dev-agents` and `kubectl logs -n dev-agents job/`. Most often this is a permission issue with the StorageClass. diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md new file mode 100644 index 0000000..a2feb08 --- /dev/null +++ b/docs/tutorials/index.md @@ -0,0 +1,15 @@ +# Tutorials + +Step-by-step lessons that take you from zero to a working AgentTeam. Read these top-to-bottom — they assume you're new to the project. + +## Available tutorials + +- **[Getting started](getting-started.md)** — install kagents on a Kind cluster and run your first AgentTeam end-to-end. ~15 minutes. + +More tutorials land alongside the v0.7.0 documentation milestone. If you have a use case you'd like a tutorial for, [open a Discussion](https://github.com/amcheste/claude-teams-operator/discussions). + +## Looking for something else? + +- **Solving a specific problem?** Try the [how-to guides](../how-to/index.md). +- **Looking up a CRD field or Helm value?** See the [reference](../reference/index.md). +- **Wondering why something works the way it does?** See the [explanation](../explanation/index.md) section. diff --git a/mkdocs.yml b/mkdocs.yml index 3f343c5..59a35ee 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -83,3 +83,18 @@ extra: social: - icon: fontawesome/brands/github link: https://github.com/amcheste/claude-teams-operator + +# Diátaxis nav: Tutorials (learn) / How-to (solve) / Reference (look up) / +# Explanation (understand). Section index pages exist as placeholders so +# the structure is in place even before all content lands. +nav: + - Home: index.md + - Tutorials: + - tutorials/index.md + - Getting started: tutorials/getting-started.md + - How-to guides: + - how-to/index.md + - Reference: + - reference/index.md + - Explanation: + - explanation/index.md From 9d17c965333faf492806a8444cc1634084203050 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sat, 2 May 2026 14:18:45 -0400 Subject: [PATCH 05/28] =?UTF-8?q?feat(docs):=20three=20concept=20pages=20?= =?UTF-8?q?=E2=80=94=20resources,=20coordination,=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three v0.7.0 issues bundled — they're the Explanation section's content trio and they cross-link to each other heavily. - AMC-87: Resource model — covers AgentTeam / AgentTeamTemplate / AgentTeamRun. Mermaid diagram of the Template→Run→Team relationship, per-CRD field anatomy, the "which one do I use" decision tree, a worked example that defines a 3-agent security review template once and instantiates it against three repos. Phase state machine inline. - AMC-88: Coordination protocol — the load-bearing design choice. Why file-based instead of custom RPC (no protocol versioning, inspectable with kubectl exec, no extra infrastructure), mailbox layout under ~/.claude/teams/, Mermaid diagram of pod/PVC topology showing why ReadWriteMany is non-negotiable on multi-node, the per-teammate worktree model with the actual `teammate-{name}` branch convention, push-branch consolidation, single-node fallback with a danger callout for the multi-node footgun. - AMC-89: Operations — budget, RBAC, observability. Budget section uses the actual rates from internal/budget/tracker.go (per-million Anthropic list price × 50K input + 5K output tokens-per-minute heuristic) instead of the stale per-minute numbers in ARCHITECTURE.md. RBAC section walks through the per-agent SA/Role/ RoleBinding generation, what each agent can and cannot do, and what threats this defends against (vs. what's still inherent to the file-based protocol). Observability lists all eight Prometheus metric series, the Grafana dashboard wiring, and the four webhook event types. Wires Mermaid into mkdocs.yml via pymdownx.superfences custom_fences so the diagrams render on the site. Updates the Explanation section index to point at the three new pages instead of the interim ARCHITECTURE.md pointer. Verified locally: `mkdocs build --strict` clean (post-commit). Mermaid renders correctly in `mkdocs serve`. Heads-up worth following up on: ARCHITECTURE.md's budget rate table (opus $0.0225/min, sonnet $0.0090/min) doesn't match the code in internal/budget/tracker.go. The new operations.md page documents the code reality. Worth a small follow-up PR to either reconcile ARCHITECTURE.md or note that the doc is approximate. Fixes AMC-87 Fixes AMC-88 Fixes AMC-89 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- docs/explanation/coordination.md | 172 +++++++++++++++++++++ docs/explanation/index.md | 14 +- docs/explanation/operations.md | 168 +++++++++++++++++++++ docs/explanation/resources.md | 251 +++++++++++++++++++++++++++++++ mkdocs.yml | 11 +- 5 files changed, 610 insertions(+), 6 deletions(-) create mode 100644 docs/explanation/coordination.md create mode 100644 docs/explanation/operations.md create mode 100644 docs/explanation/resources.md diff --git a/docs/explanation/coordination.md b/docs/explanation/coordination.md new file mode 100644 index 0000000..d5ce19a --- /dev/null +++ b/docs/explanation/coordination.md @@ -0,0 +1,172 @@ +# Coordination protocol + +This is the load-bearing design choice in kagents: agent-to-agent communication happens through files on a shared PVC, not through a custom RPC protocol. Understanding why explains most of the rest of the architecture. + +## Why a shared filesystem instead of a message bus? + +Anthropic's Claude Code Agent Teams runs natively on a single machine using tmux. Multiple Claude Code instances coordinate via files in `~/.claude/teams/` — JSON inboxes for peer-to-peer messages, a JSON task list for shared work tracking. The protocol is unspecified beyond "look at the files." + +We could have translated this to Redis, NATS, or a custom gRPC service. We chose not to: + +- **No protocol versioning to track.** Claude Code owns the format. When it ships a v2 mailbox schema, kagents inherits it for free — we never read or write the contents. +- **No translation layer to debug.** When something goes wrong, you can `kubectl exec` into a pod and inspect the actual files Claude Code is reading and writing. There's no opaque protocol bridge in the middle. +- **No additional infrastructure.** A bare RWX PVC is enough. No Redis to operate, no message-bus HA story. + +The cost is real — ReadWriteMany storage isn't free on every cluster, and we have to be honest about that. + +## Mailbox layout + +Each agent has an inbox at a stable path under `~/.claude/teams/`: + +``` +~/.claude/ + teams/ + {team-name}/ + inboxes/ + lead.json + teammate-a.json + teammate-b.json + ... + tasks/ + {team-name}/ + tasks.json +``` + +- **Inboxes** are peer-to-peer. The lead writes to `inboxes/teammate-a.json` to address teammate A; teammate A reads its own inbox to receive messages. +- **The task list** is broadcast: the lead writes tasks; teammates claim them via writes to the same file (with file-locking to handle concurrent claims). + +These paths come from Claude Code itself, not from kagents. The operator just makes the files visible to all pods that need them. + +## Volume topology + +Each team uses up to three PVCs, all `ReadWriteMany`: + +```mermaid +graph TB + subgraph N1[Node 1] + L[Lead Pod
opus] + T1[Teammate Pod
backend-api] + end + subgraph N2[Node 2] + T2[Teammate Pod
frontend-auth] + T3[Teammate Pod
test-coverage] + end + + SS[(team-state PVC
RWX
~/.claude/teams
~/.claude/tasks)] + R[(repo PVC
RWX, coding mode
/workspace)] + O[(output PVC
RWX, Cowork mode
/workspace/output)] + + L -.mount.-> SS + L -.mount.-> R + T1 -.mount.-> SS + T1 -.mount.-> R + T2 -.mount.-> SS + T2 -.mount.-> R + T3 -.mount.-> SS + T3 -.mount.-> R + + style SS fill:#fff3e0,stroke:#f57c00 + style R fill:#e8f5e9,stroke:#388e3c + style O fill:#f3e5f5,stroke:#7b1fa2 +``` + +The `team-state` PVC is the coordination fabric — it carries the mailboxes and the task list. The `repo` PVC (coding mode) carries the git clone and per-teammate worktrees. The `output` PVC (Cowork mode) is where agents write artifacts. + +In practice the operator mounts the team-state PVC into each pod, and the entrypoint symlinks the `teams/` and `tasks/` subdirectories into `~/.claude/`: + +```bash +ln -sfn /var/claude-state/teams ~/.claude/teams +ln -sfn /var/claude-state/tasks ~/.claude/tasks +``` + +This preserves the native paths Claude Code expects without polluting the agent's per-pod `~/.claude/` config. + +## Why ReadWriteMany? + +Two pods need to write to the same file at the same time: + +1. **Mailbox writes.** The lead writes into a teammate's inbox. The teammate reads from its own inbox. Both sides happen continuously. +2. **Task claims.** Multiple teammates race to claim items from the shared task list. + +If the backing PVC supports only `ReadWriteOnce`, the second pod fails to mount with `volume already attached to a different node` and the team deadlocks before the first message round-trip. + +### Supported backends + +The operator has no opinion about the CSI driver — it asks for an RWX PVC and a `storageClassName` you supply. Backends that satisfy the contract: + +| Platform | Driver | Notes | +|----------|--------|-------| +| Amazon EKS | [EFS CSI driver](https://github.com/kubernetes-sigs/aws-efs-csi-driver) | Native RWX over NFS protocol | +| Google GKE | [Filestore CSI driver](https://cloud.google.com/filestore/docs/csi-driver) | Filestore instances advertise RWX | +| Azure AKS | [Azure Files CSI driver](https://learn.microsoft.com/azure/aks/azure-files-csi) | SMB or NFS protocol | +| Bare-metal / on-prem | NFS subdir provisioner, Longhorn, Rook/Ceph | Anything with `accessModes: [ReadWriteMany]` | +| Kind (multi-node dev) | NFS server provisioner | Installed by `make kind-create` | + +### Single-node fallback + +For laptops, Kind, k3d, minikube — a real RWX provisioner is overkill. The operator accepts a `--pvc-access-mode=ReadWriteOnce` flag. This works **only** because every pod lands on the same node, and a hostPath-backed RWO PVC is then visible to all of them. + +!!! danger "Don't use RWO on a multi-node cluster" + A second pod scheduled on a different node will fail to mount the PVC and the team will deadlock. The single-node fallback is a development convenience, not a production option. + +## Per-teammate git worktrees (coding mode) + +When `spec.repository` is set, the init Job: + +1. Clones the repository into `/workspace/repo` +2. Creates one git worktree per teammate at `/workspace/worktrees/{teammate-name}` on a dedicated branch named `teammate-{teammate-name}` +3. Initialises the team-state directories and an empty task list + +Each teammate pod receives `WORKTREE_PATH=/workspace/worktrees/{teammate-name}` and the entrypoint `cd`s there before launching Claude Code. The lead has no worktree path and works directly from `/workspace/repo`. + +The branch naming is a deliberate choice. Each teammate's commits go to `teammate-{name}`, completely isolated from peers' work-in-progress. There's no possibility of a merge conflict between concurrent agents because they never share a branch. The lead (or an `onComplete` action) handles consolidation at the end. + +```mermaid +graph LR + M[main branch] -.cloned to.-> R[/workspace/repo] + R -.worktree.-> WA[/workspace/worktrees/backend-api
branch: teammate-backend-api] + R -.worktree.-> WB[/workspace/worktrees/frontend-auth
branch: teammate-frontend-auth] + R -.worktree.-> WC[/workspace/worktrees/test-coverage
branch: teammate-test-coverage] + + style R fill:#e8f5e9,stroke:#388e3c + style WA fill:#fff3e0,stroke:#f57c00 + style WB fill:#fff3e0,stroke:#f57c00 + style WC fill:#fff3e0,stroke:#f57c00 +``` + +## Push-branch consolidation (`onComplete`) + +When the team finishes successfully and `lifecycle.onComplete: push-branch` is set, the operator runs a terminal Job that: + +1. Iterates each teammate worktree +2. `git merge --no-ff` each `teammate-{name}` branch into a fresh consolidation branch +3. `git push` the consolidated branch to the remote + +The default consolidated branch name is `teams/{team-name}` (Go template; overridable via `lifecycle.consolidatedBranchTemplate`). The operator sets `status.consolidatedBranch` once the push succeeds. + +If `onComplete: create-pr` is also set (or used alone), the operator opens a GitHub PR with the consolidated branch as the head. PR title and body are configurable via `lifecycle.pullRequest.titleTemplate` and `bodyTemplate`. + +## Cowork mode + +When `spec.workspace` is set instead of `spec.repository`, the operator skips the init Job and the worktree machinery entirely: + +- Creates an `output` PVC for writable agent output +- Mounts `workspace.inputs` (ConfigMaps or existing PVCs) read-only into each pod +- Doesn't set `WORKTREE_PATH`; agents work in `/workspace/output` or `/workspace/data` + +The mailbox protocol is identical — Cowork agents still coordinate via `~/.claude/teams/.../inboxes/`. The only difference is what filesystem they're writing artifacts into. + +## What this means for debugging + +A surprising amount of the system is just files on disk: + +- See what's in a teammate's inbox right now: `kubectl exec -n dev-agents - -- cat ~/.claude/teams//inboxes/.json` +- See the live task list: `kubectl exec -n dev-agents -lead -- cat ~/.claude/tasks//tasks.json` +- See worktree state: `kubectl exec ... -- git -C /workspace/worktrees/ log --oneline` + +There's no opaque coordinator process to dump. Everything Claude Code knows about its teammates is on the shared filesystem. + +## Where to look next + +- [Resource model](resources.md) — the CRDs that compose into a running team +- [Operations](operations.md) — budget, RBAC, and observability for the running team diff --git a/docs/explanation/index.md b/docs/explanation/index.md index bf9bca9..d67bfcd 100644 --- a/docs/explanation/index.md +++ b/docs/explanation/index.md @@ -2,10 +2,14 @@ The "why" behind kagents — architecture, design tradeoffs, the choices that shaped the project. Read these when you want to understand what's actually happening, not just how to use it. -## Coming in v0.7.0 +## Pages -- **Resource model** — how `AgentTeam`, `AgentTeamTemplate`, and `AgentTeamRun` relate; when to reach for which -- **Coordination protocol** — the file-based mailbox model, why ReadWriteMany is required, per-teammate git worktrees as a concurrency primitive -- **Operations** — budget estimation, per-agent RBAC, observability via Prometheus + Grafana +- **[Resource model](resources.md)** — the three CRDs (`AgentTeam`, `AgentTeamTemplate`, `AgentTeamRun`), how they relate, and when to reach for which. +- **[Coordination protocol](coordination.md)** — the file-based mailbox model, why ReadWriteMany is required, per-teammate git worktrees as a concurrency primitive. +- **[Operations](operations.md)** — budget estimation, per-agent RBAC, observability via Prometheus + Grafana + webhooks. -Until these land, [ARCHITECTURE.md in the repo](https://github.com/amcheste/claude-teams-operator/blob/main/ARCHITECTURE.md) is the most complete design document. +## Going deeper + +The repo's [`ARCHITECTURE.md`](https://github.com/amcheste/claude-teams-operator/blob/main/ARCHITECTURE.md) is the design doc — denser, more focused on rationale than on usage. It overlaps with these pages but goes further into the file-by-file structure of the codebase. + +The [KubeCon NA 2026 talk](https://github.com/amcheste/claude-teams-operator/blob/main/KUBECON.md) frames the same architecture from the conference angle (interesting problems encountered, competitive landscape, design decisions worth surfacing on stage). diff --git a/docs/explanation/operations.md b/docs/explanation/operations.md new file mode 100644 index 0000000..f7d9513 --- /dev/null +++ b/docs/explanation/operations.md @@ -0,0 +1,168 @@ +# Operations + +Three concerns once a team is running: how much it costs, what each agent can touch, and how you see what it's doing. + +## Budget tracking + +Claude Code does not expose real-time token usage to the outside world. The operator estimates cost from elapsed time and the model assigned to each agent. + +### How the estimate works + +The estimator (in [`internal/budget`](https://github.com/amcheste/claude-teams-operator/tree/main/internal/budget)) treats every active agent session as if it consumes a fixed token rate per minute. The rate per million tokens uses Anthropic's published list price, applied to a **heuristic of 50,000 input + 5,000 output tokens per active minute** per agent. + +| Model | Input ($/M tokens) | Output ($/M tokens) | Approx. cost / minute / agent | +|-------|-------------------:|--------------------:|------------------------------:| +| `opus` | $5.00 | $25.00 | $0.375 | +| `sonnet` | $3.00 | $15.00 | $0.225 | + +The cost ticks up monotonically while pods are in `Running`. The reconciler aggregates per-teammate cost into `status.estimatedCostUsd`. + +### What triggers BudgetExceeded + +The reconciler compares `status.estimatedCostUsd` against `spec.lifecycle.budgetLimit` on every reconcile loop. When the estimate crosses the limit: + +1. Phase transitions to `BudgetExceeded` +2. All agent pods are deleted via owner-reference cascade +3. `status.completedAt` is stamped +4. A `webhook.budgetExceeded` event fires (if configured) + +There's no grace period — the team stops the moment the estimate crosses. Set the limit with headroom. + +### Honest tradeoffs + +This is the lightest-touch approach available without instrumenting Claude Code. The honest limitations: + +- **Estimate, not measurement.** Real token usage depends on prompt length, context window growth, and how often the agent reaches for tools. The estimate can be off by 2-3x in either direction. +- **Heuristic is per-active-minute.** An agent waiting on `dependsOn` doesn't accrue cost; one running flat out at the same rate as one mostly idle does. The heuristic averages the difference away. +- **Rate table is hardcoded.** The token-per-minute heuristic and the per-million prices live in `internal/budget/tracker.go`. Adjusting them requires a code change and rebuild — config-via-Helm-values is on the roadmap. + +For production, set `budgetLimit` ~2x what you actually want to spend, and treat the budget as a circuit breaker rather than a precise meter. Real cost tracking via instrumented Claude Code or sidecar log parsing is on the roadmap; until then, the [Anthropic console](https://console.anthropic.com/) is the source of truth for accounting. + +## Per-agent RBAC + +Every agent pod in a team gets its own `ServiceAccount`, `Role`, and `RoleBinding`. The lead and each teammate are isolated: a compromised teammate cannot read a peer's secrets or PVCs. + +### What gets created + +For an `AgentTeam` with a lead and three teammates, the operator creates: + +- 1 `ServiceAccount` for the lead +- 1 `Role` granting access to the lead's secrets and the team-state PVC +- 1 `RoleBinding` binding the SA to the Role +- 3 `ServiceAccount`s, one per teammate +- 3 `Role`s, scoped to that teammate's secrets and PVCs only +- 3 `RoleBindings` + +All eight resources are owned by the `AgentTeam`. Deleting the team garbage-collects everything. + +### What each agent can do + +The Roles use `resourceNames` to scope by name, not just by type. A teammate's Role grants: + +| Resource | Verbs | Scope | +|----------|------:|-------| +| `secrets` | `get` | Only the API key Secret + that teammate's git credentials Secret | +| `persistentvolumeclaims` | `get`, `list`, `watch` | Only the PVCs this team uses | + +Notably absent: + +- No `pods`. Agents cannot list or exec into peer pods. +- No `pods/exec`. The teammate cannot escape the pod by `kubectl exec`. +- No `configmaps`. Skill ConfigMaps are mounted by the operator; the agent cannot enumerate or read other ConfigMaps. + +### What this defends against + +The threat model is "a teammate's prompt is malicious or compromised." The blast radius from that scenario is: + +- ✅ Cannot read another teammate's secrets (different SA) +- ✅ Cannot exec into the lead pod (no `pods/exec`) +- ✅ Cannot enumerate cluster state (no list verbs on namespace-wide resources) +- ⚠️ Can write to the shared `team-state` PVC — a malicious teammate could poison the task list or write to a peer's inbox. This is inherent to the file-based protocol; mitigations would require Claude Code to authenticate writes. +- ⚠️ Can write to the shared `repo` PVC. Worktrees are isolated by branch, but the agent could `cd` to a peer's worktree. + +The RBAC model handles the K8s side cleanly; the filesystem-level threats need protocol-level signing to fully address. For most use cases — internal CI, trusted prompts — the filesystem trust model is acceptable. + +## Observability + +The operator exposes Prometheus metrics, ships a Grafana dashboard, and fires webhook events on key state transitions. + +### Prometheus metrics + +The operator binary exposes `/metrics` on port 8080 by default. Eight series, all labeled by team name and (where applicable) teammate name + model: + +| Metric | Type | Description | +|--------|------|-------------| +| `claude_team_active_total` | gauge | Count of teams in non-terminal phases | +| `claude_team_duration_seconds` | histogram | Wall-clock time from `Pending` to a terminal phase | +| `claude_teammate_tokens_total` | counter | Estimated tokens consumed per teammate / model | +| `claude_team_cost_usd` | gauge | Current `status.estimatedCostUsd` | +| `claude_team_tasks_completed_total` | counter | Tasks marked complete in the shared task list | +| `claude_teammate_restarts_total` | counter | Pod restarts per teammate | +| `claude_team_budget_remaining_usd` | gauge | `budgetLimit - estimatedCostUsd` | +| `claude_teammate_idle_seconds` | histogram | Time between task completions per teammate | + +Wire them to Prometheus by enabling the chart's ServiceMonitor: + +```bash +helm upgrade kagents ./charts/claude-teams-operator \ + --set metrics.serviceMonitor.enabled=true +``` + +### Grafana dashboard + +The chart ships a curated Grafana dashboard as a ConfigMap with the `grafana_dashboard: "1"` label. With the standard `kube-prometheus-stack`, the Grafana sidecar auto-imports it within ~30 seconds. + +```bash +helm upgrade kagents ./charts/claude-teams-operator \ + --set metrics.serviceMonitor.enabled=true \ + --set metrics.grafanaDashboard.enabled=true +``` + +The dashboard's panels cover active team count, cost rate, per-teammate task throughput, restart count, and idle-time distribution. + +### Webhook events + +The operator's webhook engine POSTs JSON payloads to a configured URL on key transitions. Events that fire: + +| Event type | When | +|------------|------| +| `team.started` | The team transitions to `Running` | +| `teammate.error` | A teammate pod enters `CrashLoopBackOff` or `Error` | +| `budget.warning` | Estimated cost crosses 80% of `budgetLimit` | +| `completed` | An approval gate is hit; reconciler is waiting on `kubectl annotate` | + +Configure via the chart's `webhook` values. Each event includes the team name, namespace, phase, and a payload-type-specific extras object. + +### Approval gates + +Approval gates pause spawning a specific teammate until a human applies an annotation. They're useful when one agent's output should be reviewed before subsequent agents see it. + +```yaml +spec: + lifecycle: + approvalGates: + - event: "spawn-email-drafter" + channel: "webhook" + webhookUrl: "https://hooks.example.com/approvals" +``` + +When the reconciler would otherwise spawn the gated teammate, it instead: + +1. Marks the teammate's `status.pendingApproval` field +2. Fires a `completed` webhook event with the gate name +3. Waits for the annotation `approved.claude.amcheste.io/spawn-email-drafter=true` + +Grant approval: + +```bash +kubectl annotate agentteam my-team \ + approved.claude.amcheste.io/spawn-email-drafter=true +``` + +Within 30 seconds (the default reconcile interval), the gated teammate spawns and joins the team. + +## Where to look next + +- [Resource model](resources.md) — what an `AgentTeam` looks like under the hood +- [Coordination protocol](coordination.md) — how the agents actually talk to each other +- [How-to guides](../how-to/index.md) — concrete operational recipes (coming in v0.7.0) diff --git a/docs/explanation/resources.md b/docs/explanation/resources.md new file mode 100644 index 0000000..9265083 --- /dev/null +++ b/docs/explanation/resources.md @@ -0,0 +1,251 @@ +# Resource model + +kagents manages three custom resources. Most users only ever touch the first one. + +| CRD | What it represents | When to use | +|-----|-------------------|-------------| +| `AgentTeam` | A specific team running a specific job | One-off work — refactor, code review, report draft | +| `AgentTeamTemplate` | A reusable team blueprint | You'll instantiate the same team shape against many inputs | +| `AgentTeamRun` | One instantiation of a template | Used together with `AgentTeamTemplate` | + +## How they relate + +```mermaid +graph LR + T[AgentTeamTemplate
'3-agent-security-review'] -->|referenced by| R[AgentTeamRun
'q4-platform-review'] + R -->|owns + reconciles| A[AgentTeam
'q4-platform-review-team'] + A -->|owns| P1[lead Pod] + A -->|owns| P2[teammate Pods] + A -->|owns| V[PVCs] + + style T fill:#e1f5ff,stroke:#0288d1 + style R fill:#fff4e1,stroke:#f57c00 + style A fill:#e8f5e9,stroke:#388e3c +``` + +The `AgentTeamRun` controller merges the run's overrides on top of the template defaults and creates a child `AgentTeam`. Status flows back into the `AgentTeamRun` via an `Owns` watch, so `kubectl get agentteamrun` shows progress without users needing to know the child team exists. + +## AgentTeam + +The primary resource. Defines a single team and its lifecycle. + +The three load-bearing fields are `spec.lead`, `spec.teammates`, and either `spec.repository` (coding mode) or `spec.workspace` (Cowork mode): + +```yaml +apiVersion: claude.amcheste.io/v1alpha1 +kind: AgentTeam +metadata: + name: auth-refactor +spec: + repository: # coding mode: git repo + worktrees + url: "git@github.com:acme/backend.git" + branch: "main" + credentialsSecret: "git-credentials" + auth: + apiKeySecret: "anthropic-api-key" + lead: + model: "opus" + prompt: "..." + teammates: + - name: "backend-api" + model: "sonnet" + prompt: "..." + dependsOn: [] + lifecycle: + timeout: "2h" + budgetLimit: "30.00" + onComplete: "create-pr" +``` + +### Status + +The reconciler routes on `status.phase`: + +``` +(new CR) + │ + ▼ +Pending ─────► Initializing ─────► Running ─────► Completed + │ │ │ │ + │ │ init Job failed │ pod failed │ pods deleted, + │ ▼ ▼ │ completedAt stamped + │ Failed Failed/ ▼ + │ TimedOut/ (terminal) + │ BudgetExceeded + ▼ + Failed +``` + +Terminal phases (`Completed`, `Failed`, `TimedOut`, `BudgetExceeded`) trigger cleanup — pods get deleted, `status.completedAt` gets stamped, the reconciler stops requeuing. + +Other status fields worth knowing: + +- `status.lead.phase` and `status.teammates[].phase` — per-pod state +- `status.estimatedCostUsd` — budget tracker output (see [Operations](operations.md)) +- `status.consolidatedBranch` — populated when `onComplete: push-branch` runs +- `status.conditions` — Kubernetes-style conditions array + +## AgentTeamTemplate + +A reusable team blueprint. Does not run on its own — it sits inert until an `AgentTeamRun` references it. + +```yaml +apiVersion: claude.amcheste.io/v1alpha1 +kind: AgentTeamTemplate +metadata: + name: 3-agent-security-review +spec: + lead: + model: "opus" + prompt: | + Run a security audit. Coordinate three reviewers: + - dependency-review for known CVEs + - secrets-scanner for committed credentials + - auth-audit for IAM/permission changes + teammates: + - name: "dependency-review" + model: "sonnet" + prompt: "..." + - name: "secrets-scanner" + model: "sonnet" + prompt: "..." + - name: "auth-audit" + model: "sonnet" + prompt: "..." + lifecycle: + timeout: "4h" + budgetLimit: "20.00" +``` + +The template controller validates the spec on create/update: + +- `dependsOn` references match real teammate names +- Model values are valid (`opus`, `sonnet`, `haiku`) +- No duplicate teammate names + +It writes a `Ready` condition on `status`. The `AgentTeamRun` controller refuses to instantiate templates where `Ready=false`. + +## AgentTeamRun + +One concrete run of a template. The controller merges run-level fields on top of the template's defaults and creates a child `AgentTeam`. + +```yaml +apiVersion: claude.amcheste.io/v1alpha1 +kind: AgentTeamRun +metadata: + name: q4-security-review +spec: + templateRef: + name: 3-agent-security-review + repository: + url: "git@github.com:acme/platform.git" + branch: "release/4.0" + credentialsSecret: "git-credentials" + auth: + apiKeySecret: "anthropic-api-key" + # Optional: override any teammate's prompt at run time + lead: + prompt: "Focus on the new OAuth flow this quarter." +``` + +The `AgentTeam` it creates is owned by the `AgentTeamRun` (set via `ctrl.SetControllerReference` in the controller). Deleting the `AgentTeamRun` cascades to the team and all its child resources. + +`status.phase` mirrors the child `AgentTeam`'s phase, so a single `kubectl get agentteamrun` shows the full picture. + +## Which one do I use? + +```mermaid +graph TD + Q[Need to run
an agent team] --> A{Will you run
this same team
shape again?} + A -->|No, one-off| B[Use AgentTeam
directly] + A -->|Yes, regularly| C{Multiple inputs?
e.g. different repos,
different branches} + C -->|Yes| D[Define AgentTeamTemplate
once, instantiate with
AgentTeamRun per input] + C -->|No, same inputs| B + + style B fill:#e8f5e9,stroke:#388e3c + style D fill:#e1f5ff,stroke:#0288d1 +``` + +The Template+Run pattern shines when you want the same team shape (same lead prompt, same teammate roles) parameterised by repo, branch, or per-run prompt overrides. For a one-off job, the indirection is overhead — just write an `AgentTeam` directly. + +## Worked example: security review across three repos + +Define the template once: + +```yaml +apiVersion: claude.amcheste.io/v1alpha1 +kind: AgentTeamTemplate +metadata: + name: 3-agent-security-review + namespace: security-team +spec: + lead: + model: opus + prompt: "Coordinate dependency-review, secrets-scanner, and auth-audit." + teammates: + - name: dependency-review + model: sonnet + prompt: "Audit go.mod for CVEs via osv-scanner." + - name: secrets-scanner + model: sonnet + prompt: "Run trufflehog over the repo, report any matches." + - name: auth-audit + model: sonnet + prompt: "Diff RBAC manifests vs main, flag privilege escalation." + lifecycle: + timeout: 2h + budgetLimit: "15.00" + onComplete: create-pr +``` + +Then trigger it on whatever repo needs it: + +```yaml +--- +apiVersion: claude.amcheste.io/v1alpha1 +kind: AgentTeamRun +metadata: { name: payments-review, namespace: security-team } +spec: + templateRef: { name: 3-agent-security-review } + repository: + url: git@github.com:acme/payments.git + branch: main + credentialsSecret: git-credentials + auth: { apiKeySecret: anthropic-api-key } +--- +apiVersion: claude.amcheste.io/v1alpha1 +kind: AgentTeamRun +metadata: { name: identity-review, namespace: security-team } +spec: + templateRef: { name: 3-agent-security-review } + repository: + url: git@github.com:acme/identity.git + branch: main + credentialsSecret: git-credentials + auth: { apiKeySecret: anthropic-api-key } +--- +apiVersion: claude.amcheste.io/v1alpha1 +kind: AgentTeamRun +metadata: { name: notifications-review, namespace: security-team } +spec: + templateRef: { name: 3-agent-security-review } + repository: + url: git@github.com:acme/notifications.git + branch: main + credentialsSecret: git-credentials + auth: { apiKeySecret: anthropic-api-key } +``` + +Three concurrent reviews. One template definition. Updating the template (e.g. tightening the lead prompt) automatically applies to future runs. + +## Owner references and cascade delete + +Every child resource — pods, PVCs, ConfigMaps, the init Job, per-agent ServiceAccounts and Roles — has an owner reference to the `AgentTeam`. Deleting the `AgentTeam` cascades to all of them via Kubernetes garbage collection. + +If the team was created by an `AgentTeamRun`, that adds another layer: deleting the `AgentTeamRun` cascades to the `AgentTeam` (which then cascades to everything else). One `kubectl delete agentteamrun` is sufficient teardown. + +## Where to look next + +- [Coordination protocol](coordination.md) — how the agents actually talk to each other +- [Operations](operations.md) — budget, RBAC, and observability +- [API reference (coming in v0.7.0)](../reference/index.md) — every field, every type, every default diff --git a/mkdocs.yml b/mkdocs.yml index 59a35ee..af265ac 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -75,7 +75,13 @@ markdown_extensions: pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + # Mermaid diagrams. Material has built-in renderer support; this + # routes ```mermaid fences through it. + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true @@ -98,3 +104,6 @@ nav: - reference/index.md - Explanation: - explanation/index.md + - Resource model: explanation/resources.md + - Coordination protocol: explanation/coordination.md + - Operations: explanation/operations.md From 6e2f6e6f50f7f9fc28ca189f2b75e2257235057c Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sat, 2 May 2026 22:52:19 -0400 Subject: [PATCH 06/28] =?UTF-8?q?feat(docs):=20how-to=20guides=20=E2=80=94?= =?UTF-8?q?=20cloud=20installs=20+=20operational=20recipes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two v0.7.0 issues bundled — they're the entire How-to section, all six pages. - AMC-90: Three cloud install guides under /how-to/install/. Each walks from a working cluster through CSI-driver setup, the cloud-native RWX backend (EFS / Filestore / Azure Files Premium NFS), the StorageClass YAML, helm install, and ends with the same `make mailbox-smoke-test` verification. Honest cost ranges per backend, plus collapsible "common gotchas" boxes for each cloud's specific failure modes (EFS security-group rules, Filestore 1 TiB minimum, Azure subnet service endpoints + the NFS-vs-SMB choice). - AMC-91: Three operational how-tos under /how-to/operate/. - expose-dashboard.md: port-forward (dev), Ingress with htpasswd basic auth (small prod), oauth2-proxy pattern (corporate SSO). Covers namespace-scoping via dashboard.namespace too. - shared-storage.md: per-volume sizing recommendations, backup recipes per cloud (AWS Backup / Filestore Backups / Azure Backup), perf-tuning recipes (EFS throughput modes, Filestore tier choice, Azure Files nconnect=4). - budget-alerts.md: per-team budgetLimit, chart-wide defaultBudgetLimit, the budget.warning webhook payload shape with a Slack-relay sketch in Python, two PrometheusRule examples (per-team threshold + aggregate cross-team cost), and a callout to reconcile against actual Anthropic Console spend weekly. how-to/index.md replaces the v0.7.0-coming-soon placeholder with real links to all six pages, organized into Install and Operate buckets that mirror the directory structure. mkdocs.yml gets nested nav entries for both buckets so the section sidebar reads cleanly. Each page links back to the relevant Explanation page for the why, so readers landing on a how-to via search can jump back to the concept material if they need it. Verified locally: `mkdocs build --strict` clean (post-commit). Internal cross-links all resolve. Fixes AMC-90 Fixes AMC-91 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- docs/how-to/index.md | 29 +++- docs/how-to/install/aks.md | 136 +++++++++++++++++ docs/how-to/install/eks.md | 152 +++++++++++++++++++ docs/how-to/install/gke.md | 121 +++++++++++++++ docs/how-to/operate/budget-alerts.md | 191 ++++++++++++++++++++++++ docs/how-to/operate/expose-dashboard.md | 136 +++++++++++++++++ docs/how-to/operate/shared-storage.md | 121 +++++++++++++++ mkdocs.yml | 8 + 8 files changed, 887 insertions(+), 7 deletions(-) create mode 100644 docs/how-to/install/aks.md create mode 100644 docs/how-to/install/eks.md create mode 100644 docs/how-to/install/gke.md create mode 100644 docs/how-to/operate/budget-alerts.md create mode 100644 docs/how-to/operate/expose-dashboard.md create mode 100644 docs/how-to/operate/shared-storage.md diff --git a/docs/how-to/index.md b/docs/how-to/index.md index 9f1d801..007e678 100644 --- a/docs/how-to/index.md +++ b/docs/how-to/index.md @@ -1,12 +1,27 @@ # How-to guides -Recipes for solving specific operational tasks. These assume you already have kagents installed and a working AgentTeam — if not, start with the [Getting Started tutorial](../tutorials/getting-started.md). +Recipes for solving specific operational tasks. These assume you already have kagents installed and at least a basic working AgentTeam — if not, start with the [Getting Started tutorial](../tutorials/getting-started.md). -## Coming in v0.7.0 +## Install -- Install on EKS / GKE / AKS with platform-specific RWX storage -- Expose the dashboard via Ingress -- Configure shared storage backups and performance tuning -- Set up budget alerts to Slack/PagerDuty +Cloud-specific install paths covering the ReadWriteMany storage configuration that's the actual deployment friction point on each cloud: -Until these land, the [README's Quick Start](https://github.com/amcheste/claude-teams-operator#quick-start) and the in-repo [`docs/helm-values.md`](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md) cover most operational tasks. +- **[Install on Amazon EKS](install/eks.md)** — EFS CSI driver + EFS file system + Access Points +- **[Install on Google GKE](install/gke.md)** — Filestore CSI driver + Filestore instance +- **[Install on Azure AKS](install/aks.md)** — Azure Files CSI driver + Premium NFS share + +Each guide ends with the same `make mailbox-smoke-test` verification step. + +## Operate + +Day-to-day operational tasks once kagents is running: + +- **[Expose the dashboard](operate/expose-dashboard.md)** — port-forward for dev, Ingress with basic auth for prod, oauth2-proxy for corporate SSO, namespace-scoping +- **[Configure shared storage](operate/shared-storage.md)** — sizing the team-state / repo / output PVCs, backup strategies per cloud backend, performance tuning recipes +- **[Set budget alerts](operate/budget-alerts.md)** — per-team `budgetLimit`, chart-wide default, webhook events to Slack/PagerDuty, Prometheus alert rules + +## Looking for something else? + +- **New to the project?** Start with the [Getting Started tutorial](../tutorials/getting-started.md). +- **Want to understand how it works?** See the [Explanation](../explanation/index.md) section. +- **Need a specific CRD field or Helm value?** See the [Reference](../reference/index.md) section. diff --git a/docs/how-to/install/aks.md b/docs/how-to/install/aks.md new file mode 100644 index 0000000..6b1ebf1 --- /dev/null +++ b/docs/how-to/install/aks.md @@ -0,0 +1,136 @@ +# Install on Azure AKS + +This guide walks you from a working AKS cluster to a running kagents operator backed by Azure Files for the ReadWriteMany storage requirement. + +## Prerequisites + +- An AKS cluster on Kubernetes 1.28+ +- `kubectl` configured against the cluster +- `helm` 3.14+ +- `az` CLI authenticated with the subscription that owns the cluster +- The cluster's resource group and node resource group — `az aks show -g -n ` shows them + +## 1. Verify the Azure Files CSI driver is enabled + +AKS includes the Azure Files CSI driver as a managed add-on, enabled by default on new clusters since 1.21. Verify: + +```bash +kubectl get csidriver file.csi.azure.com +``` + +If the resource doesn't exist, enable it: + +```bash +az aks update -g -n --enable-file-driver +``` + +## 2. Choose the file share protocol + +Azure Files supports two protocols, and only one is suitable for kagents: + +| Protocol | RWX? | Use? | +|----------|------|------| +| **NFS v4.1** | ✅ Yes | **Yes — use this.** | +| **SMB** | ⚠️ Partial | No — POSIX semantics on the agent's mailbox writes don't work reliably. | + +NFS shares require a Premium storage account (FileStorage SKU). The good news is Premium pricing is reasonable for the small share sizes kagents needs. + +## 3. Create the StorageClass + +```yaml title="storageclass-azurefile.yaml" +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + # Match the operator's default StorageClass name. + name: nfs +provisioner: file.csi.azure.com +parameters: + protocol: nfs + skuName: Premium_LRS # FileStorage SKU; required for NFS shares + storageAccount: "" # leave empty for dynamic; populate for an existing account + resourceGroup: "" # leave empty to use the AKS node RG +mountOptions: + - nconnect=4 + - actimeo=30 + - hard +volumeBindingMode: Immediate +allowVolumeExpansion: true +reclaimPolicy: Delete +``` + +Apply it: + +```bash +kubectl apply -f storageclass-azurefile.yaml +``` + +The `nconnect=4` mount option opens four parallel TCP connections per mount, which significantly improves throughput on Azure Files. `actimeo=30` reduces metadata round-trips for the mailbox-poll workload. + +## 4. Install kagents + +```bash +helm install kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --create-namespace +``` + +Wait for the operator: + +```bash +kubectl rollout status deployment/kagents-controller-manager \ + --namespace claude-teams-system --timeout=120s +``` + +## 5. Verify with the mailbox smoke test + +```bash +git clone https://github.com/amcheste/claude-teams-operator.git +cd claude-teams-operator +make mailbox-smoke-test +``` + +A passing run reports: + +``` +PASS StorageClass=nfs AccessMode=ReadWriteMany RoundTripMs=918 +``` + +The first PVC takes longer to provision because the CSI driver creates a storage account if `storageAccount` is empty. Subsequent PVCs in the same RG reuse it. + +## Cost notes + +Azure Files Premium (FileStorage SKU) is billed by **provisioned capacity** per GiB-month, not actual usage: + +- **Provisioned capacity minimum**: 100 GiB per share. +- **Price**: ~$0.16/GiB-month for Premium NFS in most regions, plus tiny per-operation fees. +- **Network**: free within the same Azure region. + +A 100 GiB Premium share is **~$16/month**. That's enough for tens of concurrent teams' worth of mailbox state. For larger teams or longer retention, scale capacity up — Azure Files Premium auto-scales IOPS proportional to provisioned size. + +The honest range for a small production install is **$15–$50/month** depending on how aggressively you scale capacity for performance. + +## Common gotchas + +??? warning "`mount.nfs4: Permission denied` or `Stale file handle`" + The most common cause is the AKS subnet missing the `Microsoft.Storage` service endpoint. Add it: `az network vnet subnet update -g --vnet-name -n --service-endpoints Microsoft.Storage`. + +??? warning "PVCs stuck in `Pending` with `failed to provision volume: ... PrincipalNotFound`" + The AKS managed identity (or service principal) lacks `Storage Account Contributor` on the resource group. Grant it: + ```bash + az role assignment create \ + --assignee \ + --role "Storage Account Contributor" \ + --scope /subscriptions//resourceGroups/ + ``` + +??? warning "Slow mailbox round-trips (>5s)" + Azure Files NFS without `nconnect=4` can be 2-3x slower than expected. Add the mount option in the StorageClass and recreate any pods using existing PVCs to pick it up. + +??? warning "Cannot use Standard or Premium_ZRS SKU" + Only `Premium_LRS` supports NFS. Standard SMB shares technically support RWX but the file-locking semantics don't work for the mailbox protocol — use Premium NFS. + +## Where to look next + +- [Resource model](../../explanation/resources.md) — the CRDs you'll be writing +- [Coordination protocol](../../explanation/coordination.md) — why RWX matters in detail +- [Operations](../../explanation/operations.md) — budget, RBAC, observability for the running operator diff --git a/docs/how-to/install/eks.md b/docs/how-to/install/eks.md new file mode 100644 index 0000000..83db26c --- /dev/null +++ b/docs/how-to/install/eks.md @@ -0,0 +1,152 @@ +# Install on Amazon EKS + +This guide walks you from a working EKS cluster to a running kagents operator backed by Amazon EFS for the ReadWriteMany storage requirement. + +## Prerequisites + +- An EKS cluster on Kubernetes 1.28+ +- `kubectl` configured against the cluster +- `helm` 3.14+ +- `aws` CLI authenticated with permissions to create EFS file systems and IAM policies +- The cluster's VPC ID and the security group used by your worker nodes — `aws eks describe-cluster --name ` shows them + +## 1. Install the EFS CSI driver + +The official AWS EFS CSI driver supports the `ReadWriteMany` access mode kagents requires. Install via the EKS add-on: + +```bash +aws eks create-addon \ + --cluster-name \ + --addon-name aws-efs-csi-driver \ + --resolve-conflicts OVERWRITE +``` + +Or via Helm if you prefer the upstream chart: + +```bash +helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/ +helm install aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \ + --namespace kube-system +``` + +Verify pods are ready: + +```bash +kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-efs-csi-driver +``` + +## 2. Create the EFS file system + +```bash +aws efs create-file-system \ + --creation-token kagents-state \ + --performance-mode generalPurpose \ + --throughput-mode elastic \ + --encrypted \ + --tags Key=Name,Value=kagents-state +``` + +Note the returned `FileSystemId` (looks like `fs-0abc123def456`). + +Add a mount target in each worker subnet so pods on any node can mount it: + +```bash +# For each subnet your nodes live in: +aws efs create-mount-target \ + --file-system-id fs-0abc123def456 \ + --subnet-id subnet-... \ + --security-groups sg-... # the worker node security group +``` + +The security group must allow inbound NFS (TCP 2049) from itself. + +## 3. Create the StorageClass + +```yaml title="storageclass-efs.yaml" +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + # The operator defaults to a class named "nfs"; using that name avoids + # needing to override storage.storageClassName in the chart values. + name: nfs +provisioner: efs.csi.aws.com +parameters: + provisioningMode: efs-ap + fileSystemId: fs-0abc123def456 + directoryPerms: "700" + uid: "65532" + gid: "65532" +reclaimPolicy: Retain +volumeBindingMode: Immediate +``` + +Apply it: + +```bash +kubectl apply -f storageclass-efs.yaml +``` + +The `efs-ap` provisioning mode creates an EFS Access Point per PVC, which gives each team its own permissioned root directory inside the shared file system. + +## 4. Install kagents + +```bash +helm install kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --create-namespace +``` + +Wait for the operator to be ready: + +```bash +kubectl rollout status deployment/kagents-controller-manager \ + --namespace claude-teams-system --timeout=120s +``` + +## 5. Verify with the mailbox smoke test + +The repo includes a smoke test that provisions an `AgentTeam`, lets the lead and a teammate exchange a single mailbox round-trip, and reports the effective StorageClass and AccessMode. + +```bash +git clone https://github.com/amcheste/claude-teams-operator.git +cd claude-teams-operator +make mailbox-smoke-test +``` + +A passing run looks like: + +``` +PASS StorageClass=nfs AccessMode=ReadWriteMany RoundTripMs=842 +``` + +If `AccessMode` reports `ReadWriteOnce` or the test fails to schedule the second pod, your StorageClass isn't actually advertising RWX — re-check step 3. + +## Cost notes + +EFS is billed by storage GB-month + provisioned throughput. For a typical kagents deployment running 5-10 teams concurrently: + +- **Storage**: 1-5 GiB per team. At ~$0.30/GiB-month (Standard storage class), expect $0.50–$2/month for storage. +- **Throughput**: in `elastic` mode you pay per byte read/written (~$0.01/GiB). Idle teams cost nothing; active teams during a busy period might generate a few GiB of traffic per day. +- **Per-mount cost**: nothing — EFS mount targets are free. + +The honest range for a small production install is **$5–$30/month**. For larger scale see the [EFS pricing page](https://aws.amazon.com/efs/pricing/). + +## Common gotchas + +??? warning "PVCs stuck in `Pending` with `failed to provision volume`" + Almost always one of: + - The EFS CSI driver pod isn't running. `kubectl get pods -n kube-system | grep efs` + - The IAM role attached to the node group lacks `elasticfilesystem:CreateAccessPoint` and `DescribeAccessPoints`. The EKS add-on form attaches the right policy automatically; the upstream Helm install requires manual IAM setup. See [AWS docs](https://docs.aws.amazon.com/eks/latest/userguide/efs-csi.html#efs-create-iam-resources). + - The StorageClass references a `fileSystemId` that doesn't exist or has no mount targets in the right subnets. + +??? warning "Pods get stuck mounting with `mount.nfs4: Connection refused`" + The worker security group doesn't allow inbound NFS from itself. Add a rule: source ``, type `NFS`, port `2049`. + +??? warning "Slow first-mount on a fresh PVC" + EFS Access Point provisioning can take 30-60s on first use. After the first mount, subsequent mounts of the same PVC are fast. This is normal. + +## Where to look next + +- [Resource model](../../explanation/resources.md) — the CRDs you'll be writing +- [Coordination protocol](../../explanation/coordination.md) — why RWX matters in detail +- [Operations](../../explanation/operations.md) — budget, RBAC, observability for the running operator diff --git a/docs/how-to/install/gke.md b/docs/how-to/install/gke.md new file mode 100644 index 0000000..ea0584b --- /dev/null +++ b/docs/how-to/install/gke.md @@ -0,0 +1,121 @@ +# Install on Google GKE + +This guide walks you from a working GKE cluster to a running kagents operator backed by Google Filestore for the ReadWriteMany storage requirement. + +## Prerequisites + +- A GKE cluster on Kubernetes 1.28+ +- `kubectl` configured against the cluster +- `helm` 3.14+ +- `gcloud` CLI authenticated with the project that owns the cluster +- The cluster's VPC network and region — `gcloud container clusters describe ` shows them + +## 1. Enable the Filestore CSI driver + +GKE provides the Filestore CSI driver as a managed add-on. Enable it on the cluster: + +```bash +gcloud container clusters update \ + --update-addons=GcpFilestoreCsiDriver=ENABLED \ + --location +``` + +For new clusters you can enable it at create time with `--addons=GcpFilestoreCsiDriver`. + +Verify the driver pods are running: + +```bash +kubectl get pods -n kube-system -l k8s-app=gcp-filestore-csi-driver +``` + +## 2. Create the StorageClass + +The driver supports dynamic provisioning, so you don't need to create a Filestore instance manually — the CSI driver creates one when the first PVC binds. + +```yaml title="storageclass-filestore.yaml" +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + # Match the operator's default StorageClass name. + name: nfs +provisioner: filestore.csi.storage.gke.io +parameters: + tier: standard # or "premium" for SSD-backed; "enterprise" for regional HA + network: default # match your cluster's VPC +volumeBindingMode: WaitForFirstConsumer +allowVolumeExpansion: true +reclaimPolicy: Delete +``` + +Apply it: + +```bash +kubectl apply -f storageclass-filestore.yaml +``` + +`WaitForFirstConsumer` defers provisioning until a pod is scheduled, which lets the Filestore instance land in the right zone for the consuming pod. + +## 3. Install kagents + +```bash +helm install kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --create-namespace +``` + +Wait for the operator: + +```bash +kubectl rollout status deployment/kagents-controller-manager \ + --namespace claude-teams-system --timeout=120s +``` + +## 4. Verify with the mailbox smoke test + +```bash +git clone https://github.com/amcheste/claude-teams-operator.git +cd claude-teams-operator +make mailbox-smoke-test +``` + +A passing run reports the effective StorageClass and AccessMode: + +``` +PASS StorageClass=nfs AccessMode=ReadWriteMany RoundTripMs=623 +``` + +The first `make mailbox-smoke-test` run on Filestore takes a few minutes — Filestore instance provisioning is the slow step (~3-5 min). Subsequent test runs reuse the instance and complete in under 30s. + +## Cost notes + +Filestore is billed by provisioned capacity per hour, not actual usage: + +- **Standard tier**: ~$0.20/GiB-month. Minimum instance size is **1 TiB**, so the floor is ~$200/month per Filestore instance. +- **Premium tier (SSD)**: ~$0.30/GiB-month. Same 1 TiB minimum. +- **Enterprise tier (HA, regional)**: ~$0.60/GiB-month. 2.5 TiB minimum. + +Note that **each PVC creates a new Filestore instance by default** with this StorageClass config. If you're running many teams, this gets expensive fast — at least one instance per PVC times the 1 TiB minimum. + +For multi-team production use, set `volumeHandle` on a manually-provisioned shared Filestore instance and use sub-directory provisioning instead. See [GKE's Filestore docs](https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/filestore-csi-driver) for the multi-PVC pattern. + +The honest range for a small production install with one shared Filestore instance is **$200–$300/month**. + +## Common gotchas + +??? warning "PVC stuck in `Pending` with `does not satisfy capacity`" + Filestore instances have a 1 TiB minimum size. The kagents chart's default `storage.teamStateSize` is `5Gi`, but Filestore will round it up to the tier minimum. The PVC binds successfully — the warning resolves once provisioning completes (3-5 min). + +??? warning "`failed to create filestore instance: insufficient quota`" + Filestore instances count against a project-wide quota. `gcloud compute regions describe ` shows current usage. Request a quota increase via the GCP console. + +??? warning "Pods can't reach the Filestore IP" + The Filestore instance must be in the same VPC as the cluster. The StorageClass `network: default` parameter must match your cluster's VPC name. If you use a custom VPC, set it explicitly. + +??? warning "`Failed to create access mode RWO from RWX SC`" + Don't use `--pvc-access-mode=ReadWriteOnce` on GKE. Filestore is RWX-native; the operator just needs the default `ReadWriteMany` to work. + +## Where to look next + +- [Resource model](../../explanation/resources.md) — the CRDs you'll be writing +- [Coordination protocol](../../explanation/coordination.md) — why RWX matters in detail +- [Operations](../../explanation/operations.md) — budget, RBAC, observability for the running operator diff --git a/docs/how-to/operate/budget-alerts.md b/docs/how-to/operate/budget-alerts.md new file mode 100644 index 0000000..7354438 --- /dev/null +++ b/docs/how-to/operate/budget-alerts.md @@ -0,0 +1,191 @@ +# Set budget alerts + +This guide covers the four ways to limit and observe spend on a kagents installation: per-team budget limits, the chart-wide default, webhook events on threshold crossings, and Prometheus alert rules. + +For how the budget estimate is computed and its honest limitations, see the [Operations explanation](../../explanation/operations.md). + +## Per-team `budgetLimit` + +The hard stop. When a team's `status.estimatedCostUsd` crosses `spec.lifecycle.budgetLimit`, the operator deletes all the team's pods and transitions the phase to `BudgetExceeded`. + +```yaml +apiVersion: claude.amcheste.io/v1alpha1 +kind: AgentTeam +metadata: + name: nightly-security-review +spec: + # ... + lifecycle: + timeout: 4h + budgetLimit: "10.00" # USD +``` + +There's no grace period — the team stops the moment the estimate crosses. The estimate is conservative-to-the-low-side (~50K input + 5K output tokens per agent per minute is a rough ballpark), so set the limit with **2x headroom** over what you actually want to spend. + +## Chart-wide default + +For teams that don't set their own `budgetLimit`, the operator falls back to a chart-level default. The default value is **$50.00**. Override at install time: + +```bash +helm upgrade kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --reuse-values \ + --set defaultBudgetLimit=15.00 +``` + +This is a safety net, not a recommendation — every team should set its own `budgetLimit` based on the work it's doing. The default exists to prevent a misconfigured team from running unbounded. + +## Webhook events on threshold crossings + +The operator fires a `budget.warning` webhook event when a team's estimated cost crosses **80% of its `budgetLimit`** — useful as an early warning before the hard stop fires. + +### Configure the webhook URL + +Set the chart-level webhook URL (applies to all teams unless overridden per-team): + +```bash +helm upgrade kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --reuse-values \ + --set webhook.defaultUrl=https://hooks.example.com/kagents +``` + +### Payload shape + +Each event POSTs a JSON body: + +```json +{ + "type": "budget.warning", + "team": { + "namespace": "dev-agents", + "name": "auth-refactor", + "phase": "Running" + }, + "budget": { + "limitUsd": 10.00, + "estimatedCostUsd": 8.42, + "percentOfLimit": 84.2 + }, + "timestamp": "2026-05-02T14:33:21Z" +} +``` + +### Wire to Slack + +For a Slack notification, point the webhook at an [Incoming Webhook URL](https://api.slack.com/messaging/webhooks) and translate the payload via a small Cloud Function or a dedicated webhook-relay service (Slack expects its own message format, not the kagents one). + +A minimal relay in Cloud Run / AWS Lambda / Azure Functions: + +```python title="kagents-to-slack.py" +import json, os, urllib.request + +def handler(event): + body = json.loads(event["body"]) + if body["type"] != "budget.warning": + return {"statusCode": 204} + + msg = { + "text": f":warning: kagents budget warning: " + f"team `{body['team']['namespace']}/{body['team']['name']}` " + f"at {body['budget']['percentOfLimit']:.1f}% of " + f"${body['budget']['limitUsd']:.2f} limit " + f"(${body['budget']['estimatedCostUsd']:.2f} estimated)" + } + req = urllib.request.Request( + os.environ["SLACK_WEBHOOK_URL"], + data=json.dumps(msg).encode(), + headers={"Content-Type": "application/json"} + ) + urllib.request.urlopen(req) + return {"statusCode": 200} +``` + +### Wire to PagerDuty + +PagerDuty's [Events API v2](https://developer.pagerduty.com/docs/events-api-v2/overview/) accepts a similar relay pattern. The dedup key should combine team namespace + name so repeated `budget.warning` events for the same team collapse to a single incident. + +## Prometheus alert rules + +For teams that already have a Prometheus + Alertmanager stack, alert directly on the metrics the chart exposes. The relevant series: + +- `claude_team_cost_usd{team_name=...}` — current estimated cost +- `claude_team_budget_remaining_usd{team_name=...}` — `limit - cost` + +### Alert: budget about to be exceeded + +```yaml title="kagents-alerts.yaml" +groups: + - name: kagents-budget + rules: + - alert: KagentsBudgetWarning + expr: | + (claude_team_cost_usd / on(team_name) group_left + (claude_team_cost_usd + claude_team_budget_remaining_usd)) + > 0.80 + for: 1m + labels: + severity: warning + annotations: + summary: "kagents team {{ $labels.team_name }} at {{ $value | humanizePercentage }} of budget" + description: | + Team {{ $labels.namespace }}/{{ $labels.team_name }} has + consumed {{ $value | humanizePercentage }} of its budget. + Hard stop fires at 100%. + + - alert: KagentsBudgetExceeded + expr: claude_team_budget_remaining_usd <= 0 + for: 30s + labels: + severity: critical + annotations: + summary: "kagents team {{ $labels.team_name }} hit budget limit and was terminated" +``` + +Apply via your Prometheus operator's `PrometheusRule` CRD if you're using kube-prometheus-stack: + +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: kagents-budget + namespace: monitoring + labels: + release: kube-prometheus-stack +spec: + groups: + # ...same as above +``` + +### Alert: aggregate cost across all running teams + +For total spend visibility: + +```yaml +- alert: KagentsAggregateCostHigh + expr: sum(claude_team_cost_usd) > 100 + for: 5m + labels: + severity: warning + annotations: + summary: "Total in-flight kagents cost exceeds $100" + description: | + Aggregate estimated cost across all running teams: ${{ $value }}. + Investigate which teams are running with: kubectl get agentteams -A +``` + +## Cross-checking against actual spend + +The operator's estimate is a heuristic, not a meter. Reconcile against ground truth at least weekly: + +- Pull actual spend from the [Anthropic Console](https://console.anthropic.com/) usage API +- Compare against the `claude_team_cost_usd` Prometheus history for the same time window +- Adjust your `budgetLimit` headroom factor based on the observed estimate-vs-actual ratio + +If the estimate is consistently 50% low, double your `budgetLimit` headroom. If it's 200% high, you can tighten limits. + +## Where to look next + +- [Operations explanation](../../explanation/operations.md) — how the budget is computed in detail +- [Expose the dashboard](expose-dashboard.md) — visual cost view per team +- [Configure shared storage](shared-storage.md) — the other recurring cost on a kagents install diff --git a/docs/how-to/operate/expose-dashboard.md b/docs/how-to/operate/expose-dashboard.md new file mode 100644 index 0000000..00a4c59 --- /dev/null +++ b/docs/how-to/operate/expose-dashboard.md @@ -0,0 +1,136 @@ +# Expose the dashboard + +The dashboard ships with kagents but is **off by default** — installing the chart alone gives you the controller and CRDs only. This guide walks through enabling it and exposing it for the three most common scenarios. + +For why the dashboard is off by default and what it can show, see the [Operations explanation](../../explanation/operations.md). + +## Enable the dashboard + +The dashboard is a chart sub-component gated on `dashboard.enabled`: + +```bash +helm upgrade kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --reuse-values \ + --set dashboard.enabled=true +``` + +This deploys: + +- A read-only `Deployment` running the dashboard binary +- A `ClusterIP` Service on port 8080 +- A dedicated `ServiceAccount` with read-only RBAC on AgentTeam CRs and Pods/log +- Templates for an optional Ingress (off by default) + +Verify the deployment: + +```bash +kubectl rollout status deployment/kagents-dashboard \ + --namespace claude-teams-system --timeout=60s +``` + +## Scenario 1: dev / first-look (port-forward) + +For local development or a quick "is it working" check, port-forward the Service: + +```bash +kubectl port-forward -n claude-teams-system svc/kagents-dashboard 8080:8080 +``` + +Open http://localhost:8080. You'll see the team list view; click any team for the detail page with live SSE updates. + +`port-forward` is fine for dev but is a single-user tunnel through your local kubeconfig — don't rely on it for shared access. + +## Scenario 2: production (Ingress with basic auth) + +For a small-team production deployment, expose the dashboard via an Ingress with basic auth in front. Most ingress controllers can do this without a separate auth proxy. + +### a. Create the basic-auth secret + +```bash +htpasswd -bc auth admin "$(openssl rand -base64 24)" +kubectl create secret generic dashboard-basic-auth \ + --namespace claude-teams-system \ + --from-file=auth +``` + +The `auth` file contains an htpasswd-formatted line; the `nginx` ingress controller (and most others) read this format directly. + +### b. Configure the Ingress via Helm values + +```yaml title="dashboard-values.yaml" +dashboard: + enabled: true + ingress: + enabled: true + className: nginx # or "traefik", "alb", whatever your cluster uses + annotations: + nginx.ingress.kubernetes.io/auth-type: basic + nginx.ingress.kubernetes.io/auth-secret: dashboard-basic-auth + nginx.ingress.kubernetes.io/auth-realm: "kagents dashboard" + cert-manager.io/cluster-issuer: letsencrypt-prod # if using cert-manager + hosts: + - host: kagents.example.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: [kagents.example.com] + secretName: kagents-dashboard-tls +``` + +Apply it: + +```bash +helm upgrade kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --reuse-values \ + -f dashboard-values.yaml +``` + +Set the DNS for `kagents.example.com` to the Ingress controller's external IP. Once cert-manager provisions the TLS cert (1-3 minutes), browse to https://kagents.example.com and authenticate with the password you generated. + +## Scenario 3: corporate (oauth2-proxy + identity provider) + +For larger teams that already have an OIDC identity provider (Okta, Auth0, Google Workspace, GitHub, etc.), put [`oauth2-proxy`](https://oauth2-proxy.github.io/oauth2-proxy/) in front of the dashboard. + +The pattern: + +1. Deploy oauth2-proxy as a separate Deployment + Service in the same namespace +2. Point your Ingress at oauth2-proxy instead of the dashboard +3. Configure oauth2-proxy's `--upstream` flag to forward authenticated requests to `http://kagents-dashboard:8080` + +This is a standard pattern with extensive documentation in the oauth2-proxy project. The dashboard itself doesn't need to change — it stays on the internal Service, and oauth2-proxy handles all authentication and group/role checks before requests reach it. + +## Scoping the dashboard to one namespace + +By default the dashboard sees AgentTeams in **every** namespace (a `ClusterRoleBinding` grants read across the cluster). To restrict it to a single namespace — e.g. when teams in different namespaces belong to different tenants: + +```bash +helm upgrade kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --reuse-values \ + --set dashboard.enabled=true \ + --set dashboard.namespace=dev-agents +``` + +This: + +- Passes `--namespace=dev-agents` to the dashboard binary, so it only lists teams from that namespace +- Generates a `RoleBinding` scoped to `dev-agents` instead of a `ClusterRoleBinding` + +## Verifying + +Once the dashboard is reachable, deploy a quick test team and open the detail view: + +```bash +kubectl apply -n dev-agents -f config/samples/auth-refactor-team.yaml +``` + +The list view should show the team. Click in — the detail page streams live status updates via SSE; killing a teammate pod with `kubectl delete pod ...` should cause the page to redraw within a second or two. + +## Where to look next + +- [Operations explanation](../../explanation/operations.md) — what the dashboard's metrics and alerts look like +- [Configure shared storage](shared-storage.md) — sizing and tuning the PVC backends +- [Set budget alerts](budget-alerts.md) — wiring webhook alerts on cost overruns diff --git a/docs/how-to/operate/shared-storage.md b/docs/how-to/operate/shared-storage.md new file mode 100644 index 0000000..72bb39d --- /dev/null +++ b/docs/how-to/operate/shared-storage.md @@ -0,0 +1,121 @@ +# Configure shared storage + +kagents needs ReadWriteMany PVCs for the team-state, repo (coding mode), and output (Cowork mode) volumes. This guide covers sizing, backup, and per-backend performance tuning once you've picked a backend. + +For the *why* of RWX, see the [Coordination protocol explanation](../../explanation/coordination.md). For initial backend setup, see the cloud-specific install guides ([EKS](../install/eks.md), [GKE](../install/gke.md), [AKS](../install/aks.md)). + +## Sizing + +The chart's default sizes are conservative; raise them if your teams handle large repos or produce big outputs. + +| Volume | Default Helm value | Default size | When to raise | +|--------|-------------------:|-------------:|---------------| +| Team state (mailboxes + tasks) | `storage.teamStateSize` | `5Gi` | Almost never — mailbox JSON is tiny. 5 GiB holds thousands of messages. | +| Repo (coding mode) | `storage.repoSize` | `20Gi` | If your monorepo + per-teammate worktrees together exceed 20 GiB. Each worktree is roughly the size of your `git checkout`. For a 5-teammate team on a 4 GiB repo, 20 GiB might tip over. | +| Output (Cowork mode) | `spec.workspace.output.size` (per-team) | n/a | Set per AgentTeam based on expected artifact volume. 1 GiB is fine for documents; raise for image/video output. | + +Override at install time: + +```bash +helm upgrade kagents \ + oci://ghcr.io/amcheste/charts/claude-teams-operator \ + --namespace claude-teams-system --reuse-values \ + --set storage.teamStateSize=10Gi \ + --set storage.repoSize=50Gi +``` + +The Cowork output size is per-team, set in the manifest: + +```yaml +spec: + workspace: + output: + mountPath: /workspace/output + size: 5Gi # adjust per team +``` + +## Backup + +For most use cases the team-state PVC can be discarded — the mailbox is intermediate state, and finished teams' artifacts live elsewhere (in the git remote or in the Cowork output PVC). For the cases where you do want backups: + +### EFS (EKS) + +Use [AWS Backup](https://aws.amazon.com/backup/) with an EFS resource type. A daily backup with 7-day retention is the standard pattern: + +```bash +aws backup create-backup-plan --backup-plan '{ + "BackupPlanName": "kagents-efs-daily", + "Rules": [{ + "RuleName": "DailyBackup", + "TargetBackupVaultName": "Default", + "ScheduleExpression": "cron(0 2 ? * * *)", + "Lifecycle": {"DeleteAfterDays": 7} + }] +}' +``` + +EFS backups are incremental after the first; cost scales with change rate, not full size. + +### Filestore (GKE) + +Use [Filestore Backups](https://cloud.google.com/filestore/docs/backups). They're snapshot-based; the first is full, subsequent are incremental: + +```bash +gcloud filestore backups create kagents-daily-$(date +%Y%m%d) \ + --source-instance \ + --source-instance-region \ + --region +``` + +Schedule via Cloud Scheduler hitting a Cloud Function that runs the above command. + +### Azure Files (AKS) + +Premium NFS shares support [Azure Backup](https://learn.microsoft.com/en-us/azure/backup/azure-file-share-backup-overview): + +```bash +az backup vault create -g -n kagents-vault --location +az backup protection enable-for-azurefileshare \ + --vault-name kagents-vault -g \ + --storage-account \ + --azure-file-share \ + --policy-name DefaultPolicy +``` + +The default policy is daily with 30-day retention. Override per the [Azure docs](https://learn.microsoft.com/en-us/azure/backup/manage-afs-backup). + +## Performance tuning + +The dominant workload is small synchronous writes (mailbox JSON updates) and small synchronous reads (mailbox polls). Raw throughput matters less than IOPS and metadata-op latency. + +### EFS + +- **Throughput mode**: `elastic` is the right default — pay per byte, scale automatically. Switch to `provisioned` only if you measure consistent saturation in CloudWatch's `BurstCreditBalance` metric. +- **Performance mode**: `generalPurpose` for <7,000 file ops/sec total across all teams (the typical case). `maxIO` only if you exceed that; it adds 1-3ms latency per op which hurts mailbox round-trips. +- **Mount options**: defaults are fine. The CSI driver applies `nfsvers=4.1, rsize=1048576, wsize=1048576` by default. + +### Filestore + +- **Tier**: `standard` is HDD-backed and fine for mailbox-polling workloads. Move to `premium` only if you measure IOPS-bound saturation under load (rare with kagents). +- **Capacity scaling**: Filestore IOPS scale linearly with provisioned capacity. If a single shared instance is saturated by many teams, double the capacity rather than splitting into multiple instances. + +### Azure Files (Premium NFS) + +- **Mount option `nconnect=4`** is the single biggest performance win. Without it, expect 2-3x slower mailbox round-trips. Set it in the StorageClass — see the [AKS install guide](../install/aks.md#3-create-the-storageclass). +- **Provisioned IOPS**: Azure Files Premium gives baseline IOPS proportional to provisioned size (1 IOPS per GiB). For a 100 GiB share, you get ~100 IOPS baseline + bursting. Raise capacity for more IOPS, not for more storage you don't need. + +## Monitoring storage health + +Use the Prometheus metrics the chart exposes (see the [Operations explanation](../../explanation/operations.md)) plus your cloud's native metrics: + +- **EFS**: `IOBytes`, `BurstCreditBalance`, `ClientConnections` in CloudWatch +- **Filestore**: `nfs/server/operation_count`, `nfs/server/free_bytes_percent` in Cloud Monitoring +- **Azure Files**: `Transactions`, `SuccessE2ELatency` in Azure Monitor + +A sudden spike in operation count without a corresponding rise in active teams usually indicates a stuck-poll loop in one team — `kubectl describe agentteam ` to investigate. + +## Where to look next + +- [Coordination protocol](../../explanation/coordination.md) — what the storage is actually carrying +- [Set budget alerts](budget-alerts.md) — wiring cost overruns into your alert pipeline +- [Expose the dashboard](expose-dashboard.md) — visual storage-load view diff --git a/mkdocs.yml b/mkdocs.yml index af265ac..38e8e53 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -100,6 +100,14 @@ nav: - Getting started: tutorials/getting-started.md - How-to guides: - how-to/index.md + - Install: + - On Amazon EKS: how-to/install/eks.md + - On Google GKE: how-to/install/gke.md + - On Azure AKS: how-to/install/aks.md + - Operate: + - Expose the dashboard: how-to/operate/expose-dashboard.md + - Configure shared storage: how-to/operate/shared-storage.md + - Set budget alerts: how-to/operate/budget-alerts.md - Reference: - reference/index.md - Explanation: From 8fa0fbe9e4f5871747f087d005a3b976fa8a0476 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sun, 3 May 2026 03:32:17 -0400 Subject: [PATCH 07/28] docs(readme): swap mascot logo for kagents banner image Replaces the 200px mascot logo at the top of the README with the new 2560x640 kagents banner (assets/banner.png) at width=800. The previous logo file is preserved in assets/ for use in social previews, favicons, etc. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- README.md | 2 +- assets/banner.png | Bin 0 -> 84518 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/banner.png diff --git a/README.md b/README.md index b50ca7f..cba7b8b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-kagents mascot +kagents banner # kagents diff --git a/assets/banner.png b/assets/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..c5324d58197f22fddacec8430f63a20d98601a0a GIT binary patch literal 84518 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 DIzO8L literal 0 HcmV?d00001 From c6a5cea87cbaf04df382cc9b3aec9026e9807689 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sun, 3 May 2026 03:36:22 -0400 Subject: [PATCH 08/28] chore: reference AMC-99 for Linear gate Fixes AMC-99 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> From 829cccc1a664b9e283e351e32b595fe0400d19b4 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sun, 3 May 2026 01:35:04 -0400 Subject: [PATCH 09/28] feat(docs): auto-generate API reference from kubebuilder markers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires crd-ref-docs into the build so docs/reference/api/index.md stays in lockstep with api/v1alpha1/*.go. Closes the v0.7.0 documentation milestone's reference section. Tooling: - New `make docs-api` target installs crd-ref-docs v0.3.0 (pinned) and regenerates the markdown into docs/reference/api/index.md - hack/crd-ref-docs-config.yaml configures the renderer — Markdown output, single-file mode, kubernetesVersion 1.31 for the ObjectMeta link target. Filters out the kubebuilder-generated `*List` types since they're client-go pagination wrappers, not user-facing API surface. CI guardrail: - New "Check API reference docs are up to date" step in validate.yml's lint job runs `make docs-api` and fails on any diff under docs/reference/api/. Mirrors the existing manifests-up-to-date check pattern. Catches a contributor adding/changing a kubebuilder marker without regenerating the docs. Site wiring: - mkdocs.yml nav grows an "API reference" entry under Reference - docs/reference/index.md replaces the v0.7.0-coming-soon placeholder with a real link to the auto-generated page Generated output: - docs/reference/api/index.md is 665 lines — every type, every field, every kubebuilder validation rule rendered as a markdown table. AgentTeam / AgentTeamTemplate / AgentTeamRun all present. Embedded types (AgentTeamSpec, LifecycleSpec, etc.) get their own sections with cross-link "Appears in" backrefs. Verified locally: - `make docs-api` is reproducible (re-running produces no diff) - `mkdocs build --strict` clean post-commit Fixes AMC-54 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .github/workflows/validate.yml | 5 + Makefile | 19 + docs/reference/api/index.md | 665 +++++++++++++++++++++++++++++++++ docs/reference/index.md | 17 +- hack/crd-ref-docs-config.yaml | 18 + mkdocs.yml | 1 + 6 files changed, 720 insertions(+), 5 deletions(-) create mode 100644 docs/reference/api/index.md create mode 100644 hack/crd-ref-docs-config.yaml diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7c0ad69..ba9fb18 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -65,6 +65,11 @@ jobs: make manifests generate git diff --exit-code || (echo "CRD manifests or generated code is out of date. Run 'make manifests generate' and commit the result." && exit 1) + - name: Check API reference docs are up to date + run: | + make docs-api + git diff --exit-code docs/reference/api/ || (echo "API reference docs are out of date. Run 'make docs-api' and commit the result." && exit 1) + - name: Helm lint run: | curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash diff --git a/Makefile b/Makefile index dbe396f..45696ff 100644 --- a/Makefile +++ b/Makefile @@ -176,3 +176,22 @@ CONTROLLER_GEN = $(shell go env GOPATH)/bin/controller-gen .PHONY: controller-gen controller-gen: ## Install controller-gen @test -f $(CONTROLLER_GEN) || go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION) + +CRD_REF_DOCS_VERSION ?= v0.3.0 +CRD_REF_DOCS = $(shell go env GOPATH)/bin/crd-ref-docs +.PHONY: crd-ref-docs +crd-ref-docs: ## Install crd-ref-docs (used by docs-api) + @test -f $(CRD_REF_DOCS) || go install github.com/elastic/crd-ref-docs@$(CRD_REF_DOCS_VERSION) + +##@ Documentation + +.PHONY: docs-api +docs-api: crd-ref-docs ## Regenerate the API reference under docs/reference/api/ from kubebuilder markers + @mkdir -p docs/reference/api + $(CRD_REF_DOCS) \ + --config=hack/crd-ref-docs-config.yaml \ + --source-path=api/v1alpha1 \ + --renderer=markdown \ + --output-path=docs/reference/api/index.md \ + --output-mode=single + @echo "API reference regenerated at docs/reference/api/index.md" diff --git a/docs/reference/api/index.md b/docs/reference/api/index.md new file mode 100644 index 0000000..f6ad1b4 --- /dev/null +++ b/docs/reference/api/index.md @@ -0,0 +1,665 @@ +# API Reference + +## Packages +- [claude.amcheste.io/v1alpha1](#claudeamchesteiov1alpha1) + + +## claude.amcheste.io/v1alpha1 + +Package v1alpha1 contains API Schema definitions for the claude v1alpha1 API group. + +Package v1alpha1 contains API Schema definitions for the claude v1alpha1 API group. + +### Resource Types +- [AgentTeam](#agentteam) +- [AgentTeamRun](#agentteamrun) +- [AgentTeamTemplate](#agentteamtemplate) + + + +#### AgentStatus + + + +AgentStatus reports a single agent's state. + + + +_Appears in:_ +- [AgentTeamStatus](#agentteamstatus) +- [TeammateStatus](#teammatestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `podName` _string_ | PodName is the name of the agent's pod. | | | +| `phase` _string_ | Phase of this agent. | | Enum: [Pending Running Idle Completed Failed Waiting]
| + + +#### AgentTeam + + + +AgentTeam is the Schema for the agentteams API. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `claude.amcheste.io/v1alpha1` | | | +| `kind` _string_ | `AgentTeam` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[AgentTeamSpec](#agentteamspec)_ | | | | +| `status` _[AgentTeamStatus](#agentteamstatus)_ | | | | + + +#### AgentTeamRun + + + +AgentTeamRun is an instance of an AgentTeamTemplate applied to a specific repository. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `claude.amcheste.io/v1alpha1` | | | +| `kind` _string_ | `AgentTeamRun` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[AgentTeamRunSpec](#agentteamrunspec)_ | | | | +| `status` _[AgentTeamStatus](#agentteamstatus)_ | | | | + + +#### AgentTeamRunSpec + + + +AgentTeamRunSpec defines an instance of a template applied to a specific repo. + + + +_Appears in:_ +- [AgentTeamRun](#agentteamrun) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `templateRef` _[TemplateReference](#templatereference)_ | TemplateRef references the AgentTeamTemplate to instantiate. | | | +| `repository` _[RepositorySpec](#repositoryspec)_ | Repository configuration for this run (coding mode). | | Optional: \{\}
| +| `workspace` _[WorkspaceSpec](#workspacespec)_ | Workspace configures inputs/outputs for this run (Cowork mode). | | Optional: \{\}
| +| `auth` _[AuthSpec](#authspec)_ | Auth configures API authentication for this run. | | | +| `lead` _[LeadSpec](#leadspec)_ | Lead configures the team lead for this run. | | | +| `lifecycle` _[LifecycleSpec](#lifecyclespec)_ | Lifecycle overrides for this run. | | Optional: \{\}
| + + +#### AgentTeamSpec + + + +AgentTeamSpec defines the desired state of an AgentTeam. + + + +_Appears in:_ +- [AgentTeam](#agentteam) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `repository` _[RepositorySpec](#repositoryspec)_ | Repository configuration for the codebase agents will work on.
Use this for coding tasks. Optional when Workspace is set. | | Optional: \{\}
| +| `workspace` _[WorkspaceSpec](#workspacespec)_ | Workspace configures non-git inputs and outputs for Cowork teams.
Use this for knowledge-work tasks (documents, reports, email, etc.). | | Optional: \{\}
| +| `auth` _[AuthSpec](#authspec)_ | Auth configures how agents authenticate with the Anthropic API. | | | +| `lead` _[LeadSpec](#leadspec)_ | Lead configures the team lead agent. | | | +| `teammates` _[TeammateSpec](#teammatespec) array_ | Teammates defines the worker agents in the team. | | MaxItems: 16
MinItems: 1
| +| `coordination` _[CoordinationSpec](#coordinationspec)_ | Coordination configures how agents communicate. | | Optional: \{\}
| +| `lifecycle` _[LifecycleSpec](#lifecyclespec)_ | Lifecycle configures team runtime behavior and budget. | | Optional: \{\}
| +| `qualityGates` _[QualityGateSpec](#qualitygatespec)_ | QualityGates configures validation before marking team complete. | | Optional: \{\}
| +| `observability` _[ObservabilitySpec](#observabilityspec)_ | Observability configures metrics and notifications. | | Optional: \{\}
| + + +#### AgentTeamStatus + + + +AgentTeamStatus defines the observed state of an AgentTeam. + + + +_Appears in:_ +- [AgentTeam](#agentteam) +- [AgentTeamRun](#agentteamrun) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `phase` _string_ | Phase is the current lifecycle phase of the team. | | Enum: [Pending Initializing Running Completed Failed TimedOut BudgetExceeded]
| +| `startedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#time-v1-meta)_ | StartedAt is when the team began execution. | | Optional: \{\}
| +| `completedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#time-v1-meta)_ | CompletedAt is when the team finished execution. | | Optional: \{\}
| +| `totalTokensUsed` _integer_ | TotalTokensUsed is the estimated total tokens consumed. | | | +| `estimatedCost` _string_ | EstimatedCost is the estimated API cost in USD (e.g. "4.50"). | | | +| `ready` _string_ | Ready reports how many teammate pods are ready vs. declared, in the form
"running+completed/total" (e.g. "3/5"). Shown in `kubectl get` output. | | Optional: \{\}
| +| `lead` _[AgentStatus](#agentstatus)_ | Lead reports the team lead's status. | | Optional: \{\}
| +| `teammates` _[TeammateStatus](#teammatestatus) array_ | Teammates reports each teammate's status. | | Optional: \{\}
| +| `tasks` _[TaskSummary](#tasksummary)_ | Tasks reports aggregate task progress. | | Optional: \{\}
| +| `pullRequest` _[PullRequestStatus](#pullrequeststatus)_ | PullRequest reports PR creation status. | | Optional: \{\}
| +| `consolidatedBranch` _string_ | ConsolidatedBranch is the branch name pushed by OnComplete=push-branch.
Populated once the push-branch Job succeeds; OnComplete=create-pr reads
this as the PR head branch when set, in place of Spec.Repository.Branch. | | Optional: \{\}
| +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions represent the latest available observations. | | Optional: \{\}
| + + +#### AgentTeamTemplate + + + +AgentTeamTemplate is a reusable team definition. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `claude.amcheste.io/v1alpha1` | | | +| `kind` _string_ | `AgentTeamTemplate` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[AgentTeamTemplateSpec](#agentteamtemplatespec)_ | | | | +| `status` _[AgentTeamTemplateStatus](#agentteamtemplatestatus)_ | | | | + + +#### AgentTeamTemplateSpec + + + +AgentTeamTemplateSpec defines a reusable team pattern. + + + +_Appears in:_ +- [AgentTeamTemplate](#agentteamtemplate) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `description` _string_ | Description explains the template's purpose. | | | +| `teammates` _[TeammateSpec](#teammatespec) array_ | Teammates defines the worker agents in the template. | | MaxItems: 16
MinItems: 1
| +| `coordination` _[CoordinationSpec](#coordinationspec)_ | Coordination configures how agents communicate. | | Optional: \{\}
| +| `lifecycle` _[LifecycleSpec](#lifecyclespec)_ | Lifecycle configures default runtime behavior. | | Optional: \{\}
| +| `qualityGates` _[QualityGateSpec](#qualitygatespec)_ | QualityGates configures default validation steps. | | Optional: \{\}
| + + +#### AgentTeamTemplateStatus + + + +AgentTeamTemplateStatus reports validation state for an AgentTeamTemplate. +The reconciler validates teammate references and writes a Ready condition; +AgentTeamRun controllers should refuse to instantiate templates where +Ready is false. + + + +_Appears in:_ +- [AgentTeamTemplate](#agentteamtemplate) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `ready` _boolean_ | Ready is true when the template has passed validation and is safe to
instantiate via an AgentTeamRun. | | Optional: \{\}
| +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions track the latest validation state with structured reasons. | | Optional: \{\}
| + + +#### ApprovalGateSpec + + + +ApprovalGateSpec pauses execution before a named event until human approval is recorded. +Approval is granted by adding the annotation approved.claude.amcheste.io/{event}=true to the AgentTeam. + + + +_Appears in:_ +- [LifecycleSpec](#lifecyclespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `event` _string_ | Event is the gate identifier. Use "spawn-\{teammate-name\}" to gate spawning a specific teammate. | | | +| `channel` _string_ | Channel is how the approval request notification is sent. | none | Enum: [webhook none]
| +| `webhookUrl` _string_ | WebhookURL to POST when this gate is triggered (used when channel is "webhook"). | | Optional: \{\}
| + + +#### AuthSpec + + + +AuthSpec defines Anthropic API authentication. + + + +_Appears in:_ +- [AgentTeamRunSpec](#agentteamrunspec) +- [AgentTeamSpec](#agentteamspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiKeySecret` _string_ | APIKeySecret references a Secret containing ANTHROPIC_API_KEY. | | Optional: \{\}
| +| `oauthSecret` _string_ | OAuthSecret references a Secret containing OAuth tokens for subscription auth. | | Optional: \{\}
| + + +#### BeadsSpec + + + +BeadsSpec configures Beads integration. + + + +_Appears in:_ +- [CoordinationSpec](#coordinationspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `enabled` _boolean_ | Enabled turns on Beads tracking. | | | +| `doltServerService` _string_ | DoltServerService is the K8s service name for the Dolt SQL server. | | Optional: \{\}
| +| `doltServerPort` _integer_ | DoltServerPort is the port for the Dolt SQL server. | 3306 | | + + +#### CoordinationSpec + + + +CoordinationSpec configures inter-agent communication. + + + +_Appears in:_ +- [AgentTeamSpec](#agentteamspec) +- [AgentTeamTemplateSpec](#agentteamtemplatespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `mailboxBackend` _string_ | MailboxBackend determines how mailbox messages are transported. | shared-volume | Enum: [shared-volume redis nats]
| +| `taskBackend` _string_ | TaskBackend determines how the shared task list is stored. | shared-volume | Enum: [shared-volume beads]
| +| `beads` _[BeadsSpec](#beadsspec)_ | Beads configures optional Beads integration for persistent tracking. | | Optional: \{\}
| + + +#### LeadSpec + + + +LeadSpec defines the team lead configuration. + + + +_Appears in:_ +- [AgentTeamRunSpec](#agentteamrunspec) +- [AgentTeamSpec](#agentteamspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `model` _string_ | Model to use for the team lead. | opus | Enum: [opus sonnet haiku]
| +| `prompt` _string_ | Prompt is the initial instruction for the team lead. | | | +| `permissionMode` _string_ | PermissionMode controls how the lead handles permission requests. | auto-accept | Enum: [auto-accept plan default]
| +| `skills` _[SkillSpec](#skillspec) array_ | Skills to mount into .claude/skills/ for the lead agent. | | Optional: \{\}
| +| `mcpServers` _[MCPServerSpec](#mcpserverspec) array_ | MCPServers configures Model Context Protocol connections for the lead agent. | | Optional: \{\}
| +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#resourcerequirements-v1-core)_ | Resources defines compute resources for the lead pod. | | Optional: \{\}
| + + +#### LifecycleSpec + + + +LifecycleSpec controls team runtime behavior. + + + +_Appears in:_ +- [AgentTeamRunSpec](#agentteamrunspec) +- [AgentTeamSpec](#agentteamspec) +- [AgentTeamTemplateSpec](#agentteamtemplatespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `timeout` _string_ | Timeout is the maximum duration the team can run (e.g. "4h", "30m"). | 4h | | +| `budgetLimit` _string_ | BudgetLimit is the maximum API spend in USD before the team is terminated (e.g. "10.00"). | | Optional: \{\}
| +| `onComplete` _string_ | OnComplete determines what happens when the team finishes. | notify | Enum: [create-pr push-branch notify none]
| +| `pullRequest` _[PullRequestSpec](#pullrequestspec)_ | PullRequest configures PR creation when onComplete is "create-pr". | | Optional: \{\}
| +| `approvalGates` _[ApprovalGateSpec](#approvalgatespec) array_ | ApprovalGates pause execution before specified events until human approval is recorded.
Grant approval by annotating the AgentTeam: kubectl annotate agentteam approved.claude.amcheste.io/=true | | Optional: \{\}
| +| `maxRestarts` _integer_ | MaxRestarts bounds how many times each teammate pod may be re-spawned
after a Failed phase before the team itself is marked Failed. The lead
pod is not subject to this limit — a lead crash always fails the team. | 3 | Minimum: 0
Optional: \{\}
| +| `githubTokenSecret` _string_ | GitHubTokenSecret names a Secret in the team's namespace carrying a
GitHub token under the key GITHUB_TOKEN. Used by OnComplete=create-pr
(and OnComplete=push-branch, once implemented) to authenticate against
the GitHub REST API. | | Optional: \{\}
| +| `prTitleTemplate` _string_ | PRTitleTemplate overrides the title template used by OnComplete=create-pr.
Available variables: .TeamName, .Namespace. When empty, falls back to
Spec.Lifecycle.PullRequest.TitleTemplate, then to the default
"claude-teams: \{\{.TeamName\}\}". | | Optional: \{\}
| +| `gitCredentialsSecret` _string_ | GitCredentialsSecret names a Secret in the team's namespace carrying git
push credentials. The Secret must contain either 'ssh-privatekey' or
'token'. Used by OnComplete=push-branch (and OnComplete=create-pr when
push-branch runs ahead of it). Falls back to Spec.Repository.CredentialsSecret
when unset, so teams that already configured clone credentials with push
scope don't need to duplicate. | | Optional: \{\}
| +| `consolidatedBranchTemplate` _string_ | ConsolidatedBranchTemplate is a Go template rendered to produce the
branch name pushed by OnComplete=push-branch. Available variables:
.TeamName, .Namespace. When empty, defaults to "teams/\{\{.TeamName\}\}". | | Optional: \{\}
| + + +#### MCPServerSpec + + + +MCPServerSpec configures a Model Context Protocol server for an agent. + + + +_Appears in:_ +- [LeadSpec](#leadspec) +- [TeammateSpec](#teammatespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | Name identifies this MCP server in the agent's config. | | | +| `url` _string_ | URL is the MCP server endpoint. | | | +| `credentialsSecret` _string_ | CredentialsSecret references a Secret containing an 'apiKey' key for bearer auth. | | Optional: \{\}
| + + +#### MetricsSpec + + + +MetricsSpec configures Prometheus metrics. + + + +_Appears in:_ +- [ObservabilitySpec](#observabilityspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `enabled` _boolean_ | Enabled turns on metrics exposition. | | | +| `port` _integer_ | Port for the metrics endpoint. | 9090 | | + + +#### ObservabilitySpec + + + +ObservabilitySpec configures monitoring and notifications. + + + +_Appears in:_ +- [AgentTeamSpec](#agentteamspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `metrics` _[MetricsSpec](#metricsspec)_ | Metrics configures Prometheus metrics exposition. | | Optional: \{\}
| +| `logLevel` _string_ | LogLevel controls operator log verbosity for this team. | info | Enum: [debug info warn error]
| +| `webhook` _[WebhookSpec](#webhookspec)_ | Webhook configures event notifications. | | Optional: \{\}
| + + +#### PullRequestSpec + + + +PullRequestSpec configures automatic PR creation. + + + +_Appears in:_ +- [LifecycleSpec](#lifecyclespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `targetBranch` _string_ | TargetBranch is the branch to open the PR against. | main | | +| `titleTemplate` _string_ | TitleTemplate is a Go template for the PR title.
Available variables: .TeamName, .Namespace | | | +| `reviewers` _string array_ | Reviewers to request on the PR. | | Optional: \{\}
| +| `labels` _string array_ | Labels to apply to the PR. | | Optional: \{\}
| + + +#### PullRequestStatus + + + +PullRequestStatus reports PR creation state. + + + +_Appears in:_ +- [AgentTeamStatus](#agentteamstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | | | | +| `state` _string_ | | | | + + +#### QualityGateSpec + + + +QualityGateSpec configures validation steps. + + + +_Appears in:_ +- [AgentTeamSpec](#agentteamspec) +- [AgentTeamTemplateSpec](#agentteamtemplatespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `requireTests` _boolean_ | RequireTests ensures tests pass before completion. | | | +| `requireLint` _boolean_ | RequireLint ensures linting passes before completion. | | | +| `validationScript` _string_ | ValidationScript is a custom script to run before marking complete. | | Optional: \{\}
| + + +#### RepositorySpec + + + +RepositorySpec defines the git repository configuration. + + + +_Appears in:_ +- [AgentTeamRunSpec](#agentteamrunspec) +- [AgentTeamSpec](#agentteamspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | URL is the git clone URL. | | | +| `branch` _string_ | Branch to clone and work from. | main | | +| `worktreeStrategy` _string_ | WorktreeStrategy determines how git worktrees are managed. | per-teammate | Enum: [per-teammate shared]
| +| `credentialsSecret` _string_ | CredentialsSecret references a Secret containing git credentials.
The secret should contain either 'ssh-privatekey' or 'token'. | | Optional: \{\}
| + + +#### ScopeSpec + + + +ScopeSpec restricts file access for a teammate. + + + +_Appears in:_ +- [TeammateSpec](#teammatespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `includePaths` _string array_ | IncludePaths lists paths the teammate should focus on. | | Optional: \{\}
| +| `excludePaths` _string array_ | ExcludePaths lists paths the teammate should not modify. | | Optional: \{\}
| + + +#### SkillSource + + + +SkillSource identifies where to load a skill from. Exactly one field should be set. + + + +_Appears in:_ +- [SkillSpec](#skillspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `configMap` _string_ | ConfigMap references a ConfigMap in the same namespace.
Each key in the ConfigMap becomes a file in the skill directory. | | Optional: \{\}
| +| `oci` _string_ | OCI is an OCI artifact reference containing the skill files (e.g. "ghcr.io/org/skills/web-research:v1"). | | Optional: \{\}
| + + +#### SkillSpec + + + +SkillSpec defines a Claude Code skill to mount into an agent pod. + + + +_Appears in:_ +- [LeadSpec](#leadspec) +- [TeammateSpec](#teammatespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | Name is the skill directory name under .claude/skills/. | | | +| `source` _[SkillSource](#skillsource)_ | Source identifies where to load the skill from. | | | + + +#### TaskSummary + + + +TaskSummary reports aggregate task progress. + + + +_Appears in:_ +- [AgentTeamStatus](#agentteamstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `total` _integer_ | | | | +| `completed` _integer_ | | | | +| `inProgress` _integer_ | | | | +| `pending` _integer_ | | | | + + +#### TeammateSpec + + + +TeammateSpec defines a single teammate agent. + + + +_Appears in:_ +- [AgentTeamSpec](#agentteamspec) +- [AgentTeamTemplateSpec](#agentteamtemplatespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | Name is the unique identifier for this teammate. | | Pattern: `^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`
| +| `model` _string_ | Model to use for this teammate. | sonnet | Enum: [opus sonnet haiku]
| +| `prompt` _string_ | Prompt is the spawn instruction for this teammate. | | | +| `scope` _[ScopeSpec](#scopespec)_ | Scope restricts which files this teammate can access. | | Optional: \{\}
| +| `dependsOn` _string array_ | DependsOn lists teammate names that must complete before this one starts. | | Optional: \{\}
| +| `skills` _[SkillSpec](#skillspec) array_ | Skills to mount into .claude/skills/ for this teammate. | | Optional: \{\}
| +| `mcpServers` _[MCPServerSpec](#mcpserverspec) array_ | MCPServers configures Model Context Protocol connections for this teammate. | | Optional: \{\}
| +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#resourcerequirements-v1-core)_ | Resources defines compute resources for this teammate's pod. | | Optional: \{\}
| + + +#### TeammateStatus + + + +TeammateStatus reports a teammate's state. + + + +_Appears in:_ +- [AgentTeamStatus](#agentteamstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `podName` _string_ | PodName is the name of the agent's pod. | | | +| `phase` _string_ | Phase of this agent. | | Enum: [Pending Running Idle Completed Failed Waiting]
| +| `name` _string_ | Name matches the teammate's spec name. | | | +| `tasksCompleted` _integer_ | TasksCompleted is the number of tasks this teammate has finished. | | | +| `tasksClaimed` _integer_ | TasksClaimed is the number of tasks currently owned by this teammate. | | | +| `pendingApproval` _string_ | PendingApproval is the approval gate event this teammate is waiting on, if any. | | Optional: \{\}
| +| `restartCount` _integer_ | RestartCount is the number of times this teammate's pod has been
re-spawned after a Failed phase. The team is marked Failed when any
teammate's RestartCount reaches Spec.Lifecycle.MaxRestarts. | | Optional: \{\}
| + + +#### TemplateReference + + + +TemplateReference points to an AgentTeamTemplate. + + + +_Appears in:_ +- [AgentTeamRunSpec](#agentteamrunspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | Name of the AgentTeamTemplate in the same namespace. | | | + + +#### WebhookSpec + + + +WebhookSpec configures event notifications. + + + +_Appears in:_ +- [ObservabilitySpec](#observabilityspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | URL to POST events to. | | | +| `events` _string array_ | Events to send notifications for. | | MinItems: 1
| + + +#### WorkspaceInputSpec + + + +WorkspaceInputSpec defines a read-only input mounted into the agent pod. + + + +_Appears in:_ +- [WorkspaceSpec](#workspacespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `configMap` _string_ | ConfigMap references a ConfigMap to mount as a directory. | | Optional: \{\}
| +| `pvc` _string_ | PVC references an existing PersistentVolumeClaim to mount read-only. | | Optional: \{\}
| +| `mountPath` _string_ | MountPath is where to mount this input inside the container. | | | + + +#### WorkspaceOutputSpec + + + +WorkspaceOutputSpec defines the writable output volume for a Cowork team. + + + +_Appears in:_ +- [WorkspaceSpec](#workspacespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `pvc` _string_ | PVC is the name of an existing PVC to use. If empty, the operator creates one named "\{team\}-output". | | Optional: \{\}
| +| `storageClass` _string_ | StorageClass for the auto-created PVC. Defaults to "nfs". | | Optional: \{\}
| +| `size` _string_ | Size of the auto-created PVC. | 5Gi | | +| `mountPath` _string_ | MountPath inside the container where the output volume is mounted. | /workspace/output | | + + +#### WorkspaceSpec + + + +WorkspaceSpec configures non-git inputs and outputs for Cowork teams. +Use this instead of (or alongside) Repository for knowledge-work tasks. + + + +_Appears in:_ +- [AgentTeamRunSpec](#agentteamrunspec) +- [AgentTeamSpec](#agentteamspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `inputs` _[WorkspaceInputSpec](#workspaceinputspec) array_ | Inputs are read-only volumes mounted into all agent pods. | | Optional: \{\}
| +| `output` _[WorkspaceOutputSpec](#workspaceoutputspec)_ | Output configures the shared writable output volume. | | Optional: \{\}
| + + diff --git a/docs/reference/index.md b/docs/reference/index.md index 92ef358..26b7e3f 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,10 +1,17 @@ # Reference -The lookup tables — CRD field-by-field detail, Helm values, CLI flags. +The lookup tables — every CRD field, every Helm value, every CLI flag, with no narrative wrapping. -## Coming in v0.7.0 +## Pages -- **API reference** — auto-generated CRD docs for `AgentTeam`, `AgentTeamTemplate`, `AgentTeamRun` from the kubebuilder markers in `api/v1alpha1/` -- **Helm chart values** — every value documented with defaults and production override recipes (migrated from the existing [`docs/helm-values.md`](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md)) +- **[API reference](api/index.md)** — auto-generated field-by-field detail for `AgentTeam`, `AgentTeamTemplate`, and `AgentTeamRun`. Regenerated from the kubebuilder markers in `api/v1alpha1/` on every site build via `make docs-api`. -For now, the in-repo [Helm values reference](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md) is the most complete source. +## Coming next + +- **Helm chart values** — every chart value documented with defaults and production override recipes (will migrate from the existing in-repo [`docs/helm-values.md`](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md)) + +## Looking for a tutorial or recipe? + +- **Step-by-step learning?** See the [Tutorials](../tutorials/index.md). +- **Solving a specific operational task?** See the [How-to guides](../how-to/index.md). +- **Want to understand the *why*?** See the [Explanation](../explanation/index.md). diff --git a/hack/crd-ref-docs-config.yaml b/hack/crd-ref-docs-config.yaml new file mode 100644 index 0000000..14fd3fa --- /dev/null +++ b/hack/crd-ref-docs-config.yaml @@ -0,0 +1,18 @@ +# Configuration for crd-ref-docs (https://github.com/elastic/crd-ref-docs). +# Used by `make docs-api` to regenerate docs/reference/api/ from the +# kubebuilder markers in api/v1alpha1/. + +processor: + # No type-level ignores — the v0.7.0 reference docs aim to be + # comprehensive. Each spec, status, and embedded helper type gets a + # section. Add an `ignoreTypes` regex here only if a type genuinely + # shouldn't be in the public API surface. + # `List` suffix types are kubebuilder-generated pagination wrappers + # used only by client-go callers — not part of the user-facing API. + ignoreTypes: + - "List$" + ignoreFields: + - "TypeMeta$" + +render: + kubernetesVersion: "1.31" diff --git a/mkdocs.yml b/mkdocs.yml index 38e8e53..2069355 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -110,6 +110,7 @@ nav: - Set budget alerts: how-to/operate/budget-alerts.md - Reference: - reference/index.md + - API reference: reference/api/index.md - Explanation: - explanation/index.md - Resource model: explanation/resources.md From 210e11b59474b74ae0e35631f04d0b71a0426cd9 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sun, 3 May 2026 02:38:41 -0400 Subject: [PATCH 10/28] chore(make): always reinstall crd-ref-docs to honor pinned version Drops the `test -f $(CRD_REF_DOCS) || ...` cache check on the crd-ref-docs install target so `make crd-ref-docs` (and therefore `make docs-api`) always invokes `go install github.com/elastic/crd-ref-docs@$(CRD_REF_DOCS_VERSION)`. The cached-binary path was a footgun: a contributor with an older crd-ref-docs already on PATH would silently keep using it after we bumped CRD_REF_DOCS_VERSION, and the API reference output could drift from CI's expectation. `go install` against the module proxy is fast (~1s) for an already-downloaded version, so unconditional install pays a tiny cost in exchange for guaranteed version correctness. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 45696ff..bbcdd36 100644 --- a/Makefile +++ b/Makefile @@ -181,7 +181,8 @@ CRD_REF_DOCS_VERSION ?= v0.3.0 CRD_REF_DOCS = $(shell go env GOPATH)/bin/crd-ref-docs .PHONY: crd-ref-docs crd-ref-docs: ## Install crd-ref-docs (used by docs-api) - @test -f $(CRD_REF_DOCS) || go install github.com/elastic/crd-ref-docs@$(CRD_REF_DOCS_VERSION) + @go install github.com/elastic/crd-ref-docs@$(CRD_REF_DOCS_VERSION) + ##@ Documentation From db9f9cd87c3df8ccdcc19957aa372da4eac36aab Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sun, 3 May 2026 10:46:15 -0400 Subject: [PATCH 11/28] docs(community): add COC, polish CONTRIBUTING, harden SECURITY, expand issue templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the v0.7.0 milestone with the community-baseline trio so the GitHub Community Standards page reads 100% and external contributors have an obvious path in. CODE_OF_CONDUCT.md (new): - Adopts Contributor Covenant v2.1 by reference (links to canonical text rather than vendoring it inline). Defines scope, reporting channels (email + GitHub Security Advisory), enforcement stages, and attribution. SECURITY.md (refreshed): - Adds explicit coordinated-disclosure expectations: 7-day acknowledgement, 30-day fix-or-status-update, 90-day max embargo, GitHub Security Advisory + CVE workflow. - Adds "what counts as a security issue" with in-scope and out-of-scope examples — privilege escalation, container escape, RBAC bypass, dashboard info-disclosure are in; pure DoS requiring cluster-admin to set up is out. - Pointer to the docs site's Operations explanation for the defense-in-depth model. CONTRIBUTING.md (polished): - Welcome paragraph + 4-step orientation (Getting Started tutorial → concept pages → this guide → good first issues). - Code of Conduct reference. - New "Issue tracking" section explains the Linear ↔ GitHub split: external contributors file on GitHub, maintainer mirrors to Linear; internal contributors open in Linear directly. Calls out the linear-ref CI check that requires `Fixes AMC-N` (or `No-Linear-Issue:` opt-out) on PRs to develop. - New "Good first issues" section with three label links. - Pre-push checklist updated to include `make docs-api` for PRs that touch api/v1alpha1/*.go (matches the CI drift check). - New "Documentation site changes" section with the mkdocs-material local-preview loop for docs/ contributors. Issue templates (new): - .github/ISSUE_TEMPLATE/docs_issue.yml — structured template for reporting wrong/missing/stale docs (location dropdown, page link, problem, optional fix suggestion). Auto-labels `documentation`. - .github/ISSUE_TEMPLATE/config.yml — disables blank issues so contributors pick a structured template, and routes "general question" → Discussions, "find the docs" → kagents.dev, "security vulnerability" → private advisory. Not touched: - PR template stays as-is (the Linear section was reverted earlier in the milestone — the linear-ref CI check enforces the convention without needing a template hint). - Bug report and feature request templates — already solid from the existing setup. - GitHub Discussions — still needs to be enabled in repo Settings → Features (one click on the maintainer's end). The config.yml contact_links assume Discussions exists; once enabled, the "Question or discussion" link will work. Verified locally: `mkdocs build --strict` clean. Fixes AMC-93 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/config.yml | 15 +++++++ .github/ISSUE_TEMPLATE/docs_issue.yml | 42 ++++++++++++++++++++ CODE_OF_CONDUCT.md | 43 ++++++++++++++++++++ CONTRIBUTING.md | 56 ++++++++++++++++++++++++++ SECURITY.md | 57 ++++++++++++++++++++++----- 5 files changed, 204 insertions(+), 9 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/docs_issue.yml create mode 100644 CODE_OF_CONDUCT.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..a66ccdf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,15 @@ +# Disables blank issues so contributors choose one of the structured +# templates above. Routes "I have a question" / "I have an idea" to +# Discussions instead of issues. +blank_issues_enabled: false + +contact_links: + - name: Question or discussion + url: https://github.com/amcheste/claude-teams-operator/discussions + about: For general questions, design discussions, or help requests, please open a Discussion instead of an issue. + - name: Documentation site + url: https://kagents.dev + about: Tutorials, how-to guides, concept pages, and CRD reference are at kagents.dev. + - name: Security vulnerability + url: https://github.com/amcheste/claude-teams-operator/security/advisories/new + about: Report security vulnerabilities privately via GitHub Security Advisories — see SECURITY.md. diff --git a/.github/ISSUE_TEMPLATE/docs_issue.yml b/.github/ISSUE_TEMPLATE/docs_issue.yml new file mode 100644 index 0000000..01db5e5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs_issue.yml @@ -0,0 +1,42 @@ +name: Documentation Issue +description: Something on https://kagents.dev (or in-repo docs) is wrong, missing, confusing, or out of date. +labels: [documentation] +body: + - type: dropdown + id: location + attributes: + label: Where did you encounter this? + options: + - kagents.dev (docs site) + - README.md + - ARCHITECTURE.md + - CONTRIBUTING.md + - In-repo docs (docs/ tree) + - kubectl explain output (CRD docstring) + - Other + validations: + required: true + + - type: input + id: page + attributes: + label: Page or section + placeholder: e.g. https://kagents.dev/tutorials/getting-started/ or "Resource model > AgentTeamRun" + validations: + required: true + + - type: textarea + id: problem + attributes: + label: What's wrong? + description: Describe the issue. Wrong instruction? Stale info? Confusing wording? Missing example? + validations: + required: true + + - type: textarea + id: suggestion + attributes: + label: Proposed fix (optional) + description: If you have a specific suggestion — wording, an example, a diagram — share it here. PRs welcome too. + validations: + required: false diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d442294 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,43 @@ +# Code of Conduct + +## Our pledge + +This project follows the [**Contributor Covenant Code of Conduct, version 2.1**](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) — an industry-standard agreement adopted by thousands of open-source projects including Kubernetes, the Cloud Native Computing Foundation, Rust, and Node.js. + +In short: contributors and maintainers commit to making participation in this project a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +The full text of the standards expected — both the positive examples (welcoming language, accepting constructive feedback, etc.) and the unacceptable behaviors (sexualized language, trolling, harassment, etc.) — is available at the link above. **By contributing to this project, you agree to abide by it.** + +## Scope + +This Code of Conduct applies within all project spaces, including: + +- The GitHub repository (issues, pull requests, Discussions, code review) +- Any chat, mailing list, or social media account associated with the project +- Public events where someone is representing the project (talks, demos, meetups) + +## Reporting + +If you experience or witness a violation, report it confidentially: + +- **Email:** amcheste@gmail.com +- **GitHub:** open a [private security advisory](https://github.com/amcheste/claude-teams-operator/security/advisories/new) and tag it as a Code of Conduct concern in the title + +You will receive an acknowledgement within **7 days** and a resolution or status update within **30 days**. All reports are handled confidentially. Reporters will not be retaliated against. + +## Enforcement + +Project maintainers will follow the Contributor Covenant's [Enforcement Guidelines](https://www.contributor-covenant.org/version/2/1/code_of_conduct/#enforcement-guidelines), which describe a four-stage response: + +1. **Correction** — private warning for a first minor incident +2. **Warning** — formal warning with conditions +3. **Temporary ban** — time-limited removal from project spaces +4. **Permanent ban** — for sustained or severe violations + +The maintainer is the final authority on enforcement decisions for this project. + +## Attribution + +This Code of Conduct adopts the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct/. Translations are available at https://www.contributor-covenant.org/translations. + +The Contributor Covenant is licensed under [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e427ad..3c3b189 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,42 @@ # Contributing +Thank you for considering a contribution to **kagents** (the [`claude-teams-operator`](https://github.com/amcheste/claude-teams-operator) repo). This guide covers everything from setting up your dev environment to opening your first PR. + +If you're new here, the fastest orientation: + +1. Read the [Getting Started tutorial](https://kagents.dev/tutorials/getting-started/) to install kagents on a Kind cluster and run a team end-to-end (~15 minutes) +2. Read the [Resource model](https://kagents.dev/explanation/resources/) and [Coordination protocol](https://kagents.dev/explanation/coordination/) explanation pages to understand the architecture +3. Skim this guide +4. Check the [good first issues](https://github.com/amcheste/claude-teams-operator/labels/good%20first%20issue) below for something to pick up + +## Code of Conduct + +This project adopts the [Contributor Covenant Code of Conduct, version 2.1](CODE_OF_CONDUCT.md). By participating you agree to abide by it. Report violations confidentially per the instructions in that file. + +## Issue tracking + +This project uses **Linear** (team `AMC`, project `claude-teams-operator`) as the source of truth for issue tracking. Linear ↔ GitHub sync mirrors issues both ways: a Linear issue gets a GitHub mirror, and a PR that references the Linear ID (`Fixes AMC-N` in the PR body or any commit message) auto-closes the Linear ticket on merge. + +For external contributors who don't have Linear access: + +- File issues directly on GitHub using the [issue templates](https://github.com/amcheste/claude-teams-operator/issues/new/choose). The maintainer will mirror them into Linear. +- Reference the GitHub issue number in your PR (`Fixes #123`) — that works fine. The Linear sync handles the cross-reference. + +For maintainers and regular contributors: + +- Open or claim issues in Linear directly via [save_issue](https://linear.app/amcheste/project/claude-teams-operator-32aab082f36b) (or the Linear UI). +- PRs to `develop` are required to reference an `AMC-N` ID or carry a `No-Linear-Issue: ` trailer — the `Linear Issue Reference` CI check enforces this. + +## Good first issues + +If you're looking for a way in, browse: + +- [`good first issue`](https://github.com/amcheste/claude-teams-operator/labels/good%20first%20issue) — small, well-scoped tasks with clear acceptance criteria +- [`help wanted`](https://github.com/amcheste/claude-teams-operator/labels/help%20wanted) — areas where the maintainer would specifically welcome a hand +- [`documentation`](https://github.com/amcheste/claude-teams-operator/labels/documentation) — content fixes, new tutorials, or how-to guides for the docs site at [kagents.dev](https://kagents.dev) + +If nothing on those lists fits, [open a Discussion](https://github.com/amcheste/claude-teams-operator/discussions) describing what you'd like to work on. Better to align before writing code than after. + ## Prerequisites - **Go 1.23+** — `brew install go` or [go.dev/dl](https://go.dev/dl) @@ -122,6 +159,25 @@ make manifests generate fmt vet test All must pass. CI will re-run them. +If your PR touches `api/v1alpha1/*.go`, also run: + +```bash +make docs-api +``` + +This regenerates the auto-generated API reference at `docs/reference/api/index.md`. CI's `Check API reference docs are up to date` step fails if you skip this. + +### Documentation site changes + +The docs site at [kagents.dev](https://kagents.dev) lives under `docs/` and is built with [mkdocs-material](https://squidfunk.github.io/mkdocs-material/). To preview your changes locally: + +```bash +pip install -r docs/requirements.txt +mkdocs serve # http://localhost:8000 +``` + +The site auto-deploys to `gh-pages` on every push to `main` that touches `docs/`, `mkdocs.yml`, or `.github/workflows/docs.yml`. See [`docs/README.md`](docs/README.md) for the dev loop. + --- ## How to add a new reconciler feature diff --git a/SECURITY.md b/SECURITY.md index adcd08b..6778b5b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,18 +1,57 @@ # Security Policy -## Supported Versions +## Supported versions -Only the latest release is actively maintained. +Only the latest released version is actively maintained. Security fixes are issued against `main` and tagged with the next patch release. -## Reporting a Vulnerability +| Version | Supported | +|---------|:---------:| +| Latest release | ✅ | +| Older releases | ❌ — please upgrade | -**Please do not open a public issue for security vulnerabilities.** +The latest release is the most recent `v*` tag on https://github.com/amcheste/claude-teams-operator/releases. -Use GitHub's [private vulnerability reporting](../../security/advisories/new) to report issues confidentially. +## Reporting a vulnerability + +**Please do not open a public issue, Discussion, or pull request for security vulnerabilities.** Use GitHub's [private vulnerability reporting](https://github.com/amcheste/claude-teams-operator/security/advisories/new) instead — that surface lets you submit confidentially, and the maintainer can collaborate with you on a fix without the report being visible to anyone else until it's resolved. + +Please include in your report: -Please include: - A clear description of the vulnerability -- Steps to reproduce -- Potential impact +- Steps to reproduce (or a proof-of-concept manifest / kubectl invocation) +- The kagents version you observed it on +- Potential impact — what an attacker could achieve, and against what cluster topology + +## Coordinated disclosure expectations + +We follow a **coordinated disclosure** process: + +1. **Acknowledgement** — within **7 days** of your report, the maintainer will confirm receipt and start triage. +2. **Triage + fix** — within **30 days**, you will receive either a fix candidate, a status update with a clear timeline, or a written explanation of why the report doesn't qualify as a vulnerability. +3. **Embargo** — fix development happens in private. We ask you to keep the issue confidential until the fix ships and is publicly announced. We will not embargo for longer than 90 days from the original report without your agreement. +4. **Public disclosure** — once the fix is released, we publish a [GitHub Security Advisory](https://github.com/amcheste/claude-teams-operator/security/advisories) with the details, affected versions, mitigation steps, and credit to you (unless you ask to remain anonymous). +5. **CVE assignment** — if the issue qualifies, we request a CVE through GitHub's CNA before public disclosure. + +## What counts as a security issue + +If you're not sure whether something is a vulnerability or a bug, err on the side of reporting it through the private channel — it's easy to move a non-security report to a public issue, but a public report of a real vulnerability is unfixable damage. + +In-scope examples: + +- Privilege escalation between agent pods within a team or across teams +- Container escape from an agent pod to the node +- Reading secrets that an agent's RBAC scope shouldn't allow +- Operator manipulating arbitrary cluster resources beyond its declared RBAC +- Information disclosure through dashboard endpoints or webhook payloads +- Supply-chain weaknesses in the operator or runner container images + +Out of scope (please file as regular GitHub issues): + +- Bugs without a security impact +- Denial-of-service requiring cluster-admin access to set up +- Issues that require physical access to a node +- Best-practice deviations that don't enable an attack + +## Hardening checklist for operators -You can expect an acknowledgement within **7 days** and a resolution or status update within **30 days**. +For users deploying kagents in production, the [Operations explanation](https://kagents.dev/explanation/operations/) covers the defense-in-depth model — per-agent ServiceAccounts, the file-based-protocol threat model, and what RBAC does and doesn't enforce. Reading that page before going live is recommended. From 594da8bf74ffb13b74d33594887527485f711e6f Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Tue, 5 May 2026 21:50:58 -0400 Subject: [PATCH 12/28] chore: align badges and description to brand Apply CAM family brand colors to README badges: - License badge: blue -> Hunter Green (#1F4D3A), the family-system accent for license badges. Apache 2.0 stays unchanged. - Version badge: append &color=0B0B0C (Ink), the structural color for version badges across the brand system. Also update the GitHub repo description to remove an em-dash, per brand voice rules (use periods, commas, parentheses, or "since/ because" instead). README body still contains many em-dashes (feature lists, status output, etc.). Those are deferred to a follow-up sweep PR so this change stays scoped to badges and description. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cba7b8b..9feeb08 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ **Run Claude Code Agent Teams as a Kubernetes operator.** [![Validate](https://github.com/amcheste/claude-teams-operator/actions/workflows/validate.yml/badge.svg)](https://github.com/amcheste/claude-teams-operator/actions/workflows/validate.yml) -[![Version](https://img.shields.io/github/v/tag/amcheste/claude-teams-operator?label=version&sort=semver)](https://github.com/amcheste/claude-teams-operator/releases) -[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) +[![Version](https://img.shields.io/github/v/tag/amcheste/claude-teams-operator?label=version&sort=semver&color=0B0B0C)](https://github.com/amcheste/claude-teams-operator/releases) +[![License](https://img.shields.io/badge/License-Apache_2.0-1F4D3A.svg)](LICENSE) [![Go](https://img.shields.io/badge/Go-1.23-00ADD8)](go.mod)
From 1d089595634507eda1b07c5c825b0b404a099b79 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Tue, 5 May 2026 21:58:34 -0400 Subject: [PATCH 13/28] chore: link to AMC-121 for Linear gate Adds the Linear issue reference so the "Linear Issue Reference" CI check passes. The PR body was updated separately, but editing the body alone does not re-trigger the workflow. Refs AMC-121 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> From 421319a983f6e5cbd85fc77e7ccf25ffa7ea67bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 00:58:59 +0000 Subject: [PATCH 14/28] chore: Bump github.com/onsi/ginkgo/v2 from 2.28.2 to 2.28.3 Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.28.2 to 2.28.3. - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v2.28.2...v2.28.3) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.28.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 20 ++++++++++---------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index fcb2e83..48e50ac 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.25.0 require ( github.com/go-logr/logr v1.4.3 - github.com/onsi/ginkgo/v2 v2.28.2 - github.com/onsi/gomega v1.39.1 + github.com/onsi/ginkgo/v2 v2.28.3 + github.com/onsi/gomega v1.40.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 k8s.io/api v0.35.4 @@ -31,7 +31,7 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect + github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -50,15 +50,15 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/tools v0.44.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.36.8 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/go.sum b/go.sum index 86edcb6..97fa4af 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -85,10 +85,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= -github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/onsi/ginkgo/v2 v2.28.3 h1:4JvMdwtFU0imd8fHx25OJXoDMRexnf8v5NHKYSTTji4= +github.com/onsi/ginkgo/v2 v2.28.3/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= +github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -137,24 +137,24 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= From 596a10e642c3a57bb24d6efb55ad95312293d2e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 05:05:27 +0000 Subject: [PATCH 15/28] chore: Bump sigs.k8s.io/controller-runtime Bumps the controller-runtime group with 1 update in the / directory: [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime). Updates `sigs.k8s.io/controller-runtime` from 0.23.3 to 0.24.0 - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.23.3...v0.24.0) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.24.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: controller-runtime ... Signed-off-by: dependabot[bot] --- go.mod | 35 +++++++++++++++---------------- go.sum | 66 ++++++++++++++++++++++++++++------------------------------ 2 files changed, 49 insertions(+), 52 deletions(-) diff --git a/go.mod b/go.mod index 48e50ac..ad9bf87 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/amcheste/claude-teams-operator -go 1.25.0 +go 1.26.0 require ( github.com/go-logr/logr v1.4.3 @@ -8,10 +8,10 @@ require ( github.com/onsi/gomega v1.40.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - k8s.io/api v0.35.4 - k8s.io/apimachinery v0.35.4 - k8s.io/client-go v0.35.4 - sigs.k8s.io/controller-runtime v0.23.3 + k8s.io/api v0.36.0 + k8s.io/apimachinery v0.36.0 + k8s.io/client-go v0.36.0 + sigs.k8s.io/controller-runtime v0.24.0 ) require ( @@ -19,7 +19,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -28,7 +28,6 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect @@ -42,34 +41,34 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.53.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/term v0.42.0 // indirect golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.9.0 // indirect + golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.44.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.35.3 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + k8s.io/apiextensions-apiserver v0.36.0 // indirect + k8s.io/klog/v2 v2.140.0 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 97fa4af..c98c0bc 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= @@ -41,8 +41,6 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -98,10 +96,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= @@ -131,8 +129,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -141,8 +139,8 @@ golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= @@ -151,14 +149,14 @@ golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -169,27 +167,27 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= -k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= -k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU= -k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8= -k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= -k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= -k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8= -k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= -sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= +k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= +k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= +k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= +k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= +k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= +k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= +k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4= +sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From 956b99884d8cc1c01b643897ff161aabc47bb39f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 01:00:06 +0000 Subject: [PATCH 16/28] chore: Bump actions/setup-python from 5 to 6 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3f55a5f..3b434e0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,7 +26,7 @@ jobs: # Required for git-revision-date-localized to read commit history. fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.12' cache: pip From 2c6f19a86b7471fd812fcf5fcf98a046049d7c78 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Wed, 6 May 2026 02:33:12 -0400 Subject: [PATCH 17/28] docs(release): draft v0.7.0-rc.1 announcement + minor tutorials index polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two small things in one PR ahead of cutting v0.7.0-rc.1: 1. Drop the "v0.7.0 milestone" phrasing in docs/tutorials/index.md. Since v0.7.0 IS shipping with this release, "tutorials land alongside the v0.7.0 milestone" reads strangely after the cut. Replaced with an evergreen "more tutorials will be added as the project matures" + a deep link into the Ideas Discussion category for use-case requests. 2. New .github/release-announcements/v0.7.0-rc.1.md — a working draft of the release announcement, transparency-first as discussed. Two parts: - Short version (Discussions / Slack / social card) — the "preview release, please poke at it" framing leading with three concrete feedback channels (docs issue / Q&A Discussion / Ideas Discussion). - Long version (GitHub release body) — what's in this preview organized by Diátaxis section, the kagents brand intro, upgrade notes (docs-only, zero functional changes), what's intentionally not yet in v0.7.0 (helm-values migration), and the next-up roadmap pointing at v1.0.0 KubeCon Demo Polish. This file is a working draft. Edit freely before posting. It's not on the docs site (sits under .github/) so changes here have no public surface until the GitHub release is cut with this body. No-Linear-Issue: pre-release announcement copy + tutorials wording polish — no specific Linear ticket; counts toward the v0.7.0 stable cut readiness. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .github/release-announcements/v0.7.0-rc.1.md | 83 ++++++++++++++++++++ docs/tutorials/index.md | 2 +- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 .github/release-announcements/v0.7.0-rc.1.md diff --git a/.github/release-announcements/v0.7.0-rc.1.md b/.github/release-announcements/v0.7.0-rc.1.md new file mode 100644 index 0000000..0bccbda --- /dev/null +++ b/.github/release-announcements/v0.7.0-rc.1.md @@ -0,0 +1,83 @@ +# v0.7.0-rc.1 — Documentation Site preview + +> Draft announcement for the v0.7.0 release candidate. Tone is transparency-first: this is a preview, we want feedback, here's what's in it. Edit this file freely before posting — it's a working draft, not a contract. + +--- + +## Short version (Discussions / Slack / social card) + +**kagents v0.7.0-rc.1 is live — first preview of the documentation site at https://kagents.dev.** + +Six weeks of work to replace the README-only experience with a proper docs site: tutorials, how-to guides, an auto-generated CRD reference, and concept pages on the architecture. Built with mkdocs-material, deployed via GitHub Pages. + +This is a **preview release** — we're shaking out polish items before the stable v0.7.0 cut. Please poke at it and tell us what's broken, missing, or confusing: + +- Found a typo or broken link? [Docs issue](https://github.com/amcheste/claude-teams-operator/issues/new?template=docs_issue.yml) +- Confused by a concept? [Discussion](https://github.com/amcheste/claude-teams-operator/discussions/categories/q-a) +- Have an idea for a missing tutorial? [Idea](https://github.com/amcheste/claude-teams-operator/discussions/categories/ideas) + +We'll hold the stable cut until the rough edges are smoothed. + +— Alan + +--- + +## Long version (GitHub release body) + +### What's in this preview + +The v0.7.0 milestone ships a [polished documentation site at kagents.dev](https://kagents.dev), structured around the [Diátaxis framework](https://diataxis.fr) — four sections, each with a clear purpose: + +**📘 [Tutorials](https://kagents.dev/tutorials/)** +- [Getting Started](https://kagents.dev/tutorials/getting-started/) — install kagents on a Kind cluster and run your first AgentTeam end-to-end. ~15 minutes, no cloud accounts needed. + +**🔧 [How-to guides](https://kagents.dev/how-to/)** +- [Install on Amazon EKS](https://kagents.dev/how-to/install/eks/) (EFS CSI driver + EFS file system + Access Points) +- [Install on Google GKE](https://kagents.dev/how-to/install/gke/) (Filestore CSI driver + Filestore instance) +- [Install on Azure AKS](https://kagents.dev/how-to/install/aks/) (Azure Files CSI driver + Premium NFS share) +- [Expose the dashboard](https://kagents.dev/how-to/operate/expose-dashboard/) (port-forward, Ingress + basic auth, oauth2-proxy) +- [Configure shared storage](https://kagents.dev/how-to/operate/shared-storage/) (sizing, backup, perf tuning per backend) +- [Set budget alerts](https://kagents.dev/how-to/operate/budget-alerts/) (per-team limits, webhook events, Prometheus rules) + +**📚 [Reference](https://kagents.dev/reference/)** +- [API reference](https://kagents.dev/reference/api/) — auto-generated from the kubebuilder markers in `api/v1alpha1/`. CI's `Check API reference docs are up to date` step keeps it in lockstep with the code. + +**💡 [Explanation](https://kagents.dev/explanation/)** +- [Resource model](https://kagents.dev/explanation/resources/) — the `AgentTeam` / `AgentTeamTemplate` / `AgentTeamRun` CRDs and how they relate, with a worked "3-agent security review across multiple repos" example. +- [Coordination protocol](https://kagents.dev/explanation/coordination/) — the load-bearing design choice. File-based mailboxes over RWX PVCs, per-teammate git worktrees as a concurrency primitive, the single-node fallback story. +- [Operations](https://kagents.dev/explanation/operations/) — honest breakdown of estimation-based budget tracking, per-agent RBAC's threat model, and the eight Prometheus metrics the operator exposes. + +### Other v0.7.0 changes + +- **`make docs-api`** — new make target regenerates the API reference from kubebuilder markers via [`crd-ref-docs`](https://github.com/elastic/crd-ref-docs). Wired into the lint job's drift check, mirrors the existing `make manifests` pattern. +- **mkdocs-material site infrastructure** — `docs/` tree, `mkdocs.yml`, `docs/requirements.txt`, and `.github/workflows/docs.yml` (deploys to `gh-pages` on every push to `main`). +- **Community baseline** — adopted [Contributor Covenant v2.1](https://github.com/amcheste/claude-teams-operator/blob/main/CODE_OF_CONDUCT.md), polished CONTRIBUTING.md with Linear ↔ GitHub guidance for contributors, hardened SECURITY.md with explicit coordinated-disclosure expectations, added a docs-issue template, enabled GitHub Discussions. +- **kagents brand introduced** — README, KUBECON.md, and CFP draft all aligned around the new public name. Repo, Helm chart, image names, and CRD group (`claude.amcheste.io/v1alpha1`) intentionally unchanged — same pattern as Argo CD, Knative, Kueue. + +### Upgrade notes + +This release is **docs-only** — zero functional changes to the operator, dashboard, or CRDs. Existing v0.6.0 installs continue to work without any migration. Helm chart version is bumped purely for tagging cohesion. + +### What's not yet in v0.7.0 (and won't block the stable cut) + +- The "Helm chart values" page under [Reference](https://kagents.dev/reference/) — the existing in-repo [`docs/helm-values.md`](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md) is still the source of truth. Migration to the docs site is tracked for a future minor release. + +### What we want feedback on + +Please tell us if you find: + +- **Broken anything** — links, code blocks that don't run, commands that fail. [Docs issue template](https://github.com/amcheste/claude-teams-operator/issues/new?template=docs_issue.yml). +- **Confusing concepts** — a passage you read three times. [Q&A discussion](https://github.com/amcheste/claude-teams-operator/discussions/categories/q-a). +- **Missing tutorials** — a use case you'd want a step-by-step guide for. [Ideas discussion](https://github.com/amcheste/claude-teams-operator/discussions/categories/ideas). +- **Wrong or stale technical details** — especially in the cloud-install guides. The author wrote them based on documented patterns; real platform engineers on each cloud will catch nuances that haven't been hit yet. + +### What's next + +We'll hold the v0.7.0 stable cut until the obvious rough edges are smoothed — likely 1–2 weeks. After that: + +- **v0.4.0/v0.5.0/v0.6.0 retrospective** — these milestones are all 100% complete in code; we'll close out any straggling Linear hygiene. +- **v1.0.0 — KubeCon Demo Polish** (target: Oct 2026). The on-stage demo script, real-API E2E gating in CI, OCI skill distribution, and a presentation-mode dashboard view. KubeCon NA 2026 is Nov 9–12 in Salt Lake City. + +Thanks for reading. Try the [Getting Started tutorial](https://kagents.dev/tutorials/getting-started/) and let us know how it goes. + +— Alan, with help from the Claude Code agent team that built it diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index a2feb08..ad15ef6 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -6,7 +6,7 @@ Step-by-step lessons that take you from zero to a working AgentTeam. Read these - **[Getting started](getting-started.md)** — install kagents on a Kind cluster and run your first AgentTeam end-to-end. ~15 minutes. -More tutorials land alongside the v0.7.0 documentation milestone. If you have a use case you'd like a tutorial for, [open a Discussion](https://github.com/amcheste/claude-teams-operator/discussions). +More tutorials will be added as the project matures. Have a use case you'd like a tutorial for — a security review team, a doc-generation team, multi-cluster fan-out? [Open a Discussion](https://github.com/amcheste/claude-teams-operator/discussions/categories/ideas) and tell us about it. ## Looking for something else? From ffae82e7781599bb021a84bb121c1e431cebd15a Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Sat, 9 May 2026 16:14:38 -0400 Subject: [PATCH 18/28] docs: mark KubeCon CFP as submitted The CFP was submitted in May 2026, ahead of the May 31 deadline. Update KUBECON.md and the CLAUDE.md release-timeline note to reflect the new status, and drop #23 from the Current Priority list. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- CLAUDE.md | 7 +++---- KUBECON.md | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e49f309..57c626c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -118,7 +118,7 @@ This project is being developed with the goal of presenting at KubeCon NA 2026 ( ### Release Timeline -All milestones and issues are tracked on GitHub. The CFP is **OPEN** with submissions due **May 31 2026 at 11:59pm MT** — see KUBECON.md. +All milestones and issues are tracked on GitHub. The KubeCon CFP has been **submitted** (May 2026) — see KUBECON.md. | Version | GitHub Milestone | Due | What it unlocks | |---------|-----------------|-----|-----------------| @@ -128,9 +128,9 @@ All milestones and issues are tracked on GitHub. The CFP is **OPEN** with submis | **v0.4.0** | Resilience & RBAC | Aug 31 2026 | Crash re-spawn ✅, per-agent ServiceAccounts ✅, `onComplete: create-pr` ✅, `onComplete: push-branch` | | **v0.5.0** | Template Engine & Helm | Sep 30 2026 | `AgentTeamTemplate`/`AgentTeamRun` controllers, production Helm chart, CONTRIBUTING.md | | **v0.6.0** | Operator Dashboard | Oct 5 2026 | Web UI for running AgentTeams: backend API, list + detail views (HTMX + Go templates), live SSE updates, Helm packaging | -| **v1.0.0** | KubeCon Demo Polish | Oct 26 2026 | Demo script, CFP submitted, OCI skill distribution, dashboard presentation mode for stage | +| **v1.0.0** | KubeCon Demo Polish | Oct 26 2026 | Demo script, OCI skill distribution, dashboard presentation mode for stage | -**KubeCon talk:** November 9–12 2026, Salt Lake City. CFP deadline: May 31 2026. +**KubeCon talk:** November 9–12 2026, Salt Lake City. CFP submitted May 2026. ### Current Priority (post-v0.3.0) @@ -138,7 +138,6 @@ The next highest-value issues: 1. **#16** — `onComplete: push-branch` — closes out v0.4.0 alongside the already-merged #13/#14/#15 2. **#17 / #18** — AgentTeamTemplate + AgentTeamRun controllers (v0.5.0) 3. **#137–#140** — the operator dashboard (v0.6.0) -4. **#23** — draft and submit the KubeCon CFP by May 31 — this is the hard deadline ### Ask of Claude Code diff --git a/KUBECON.md b/KUBECON.md index 3a36c31..eaab977 100644 --- a/KUBECON.md +++ b/KUBECON.md @@ -3,7 +3,7 @@ **Project:** **kagents** — run Claude Code Agent Teams as a Kubernetes operator. Site: [kagents.dev](https://kagents.dev) (in progress). Repo: [`claude-teams-operator`](https://github.com/amcheste/claude-teams-operator). **Conference:** KubeCon + CloudNativeCon North America 2026 **Dates:** November 9–12, 2026 — Salt Lake City, Utah -**CFP:** Open now — deadline **May 31, 2026 at 11:59pm MT**. Submit at https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america/program/cfp/ +**CFP:** Submitted (May 2026) — deadline was **May 31, 2026 at 11:59pm MT**. --- From 25fcf54b014a910b42aca14c9c62382540ed5d08 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 17:00:32 -0400 Subject: [PATCH 19/28] chore(brand): em-dash sweep across prose + remove submitted CFP draft MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brand alignment pass against the alanchester-brand voice rules. 196 em-dashes swept across 22 prose files. The mechanical sweep replaces ` — ` with `. ` (period + space) and capitalizes the following letter when it was lowercase. Code blocks and markdown table rows are protected from substitution. Files swept: README.md, CONTRIBUTING.md, AGENTS.md, ARCHITECTURE.md, SECURITY.md, docs/README.md, docs/index.md, docs/helm-values.md, docs/explanation/{coordination,index,operations,resources}.md, docs/how-to/index.md, docs/how-to/install/{aks,eks,gke}.md, docs/how-to/operate/{budget-alerts,expose-dashboard, shared-storage}.md, docs/tutorials/{getting-started,index}.md, docs/reference/index.md Post-sweep audit (`grep -nE '\. [a-z]'`) found 5 awkward continuations after mechanical replacement. 4 are abbreviation false positives (`e.g.`, `Approx.`, `vs.`) and left as-is. 1 was a real awkward continuation in docs/how-to/operate/expose-dashboard.md where the original em-dash separated a comma-clause; restored to comma form. Out of scope (intentional): - internal/dashboard/templates/layout.html status colors. The dashboard is a tool surface, not a brand surface; the semantic UI palette (gray/amber/blue/green/red phase colors) stays. - docs/cfp/cfp-draft.md is deleted in this PR. The CFP was submitted, the draft no longer needs to live in the repo. Removing it eliminates 30 em-dashes that otherwise would have been flagged in scope. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- AGENTS.md | 30 ++--- ARCHITECTURE.md | 40 +++--- CONTRIBUTING.md | 52 ++++---- README.md | 28 ++--- SECURITY.md | 18 +-- docs/README.md | 2 +- docs/cfp/cfp-draft.md | 156 ------------------------ docs/explanation/coordination.md | 18 +-- docs/explanation/index.md | 10 +- docs/explanation/operations.md | 14 +-- docs/explanation/resources.md | 22 ++-- docs/helm-values.md | 6 +- docs/how-to/index.md | 14 +-- docs/how-to/install/aks.md | 12 +- docs/how-to/install/eks.md | 12 +- docs/how-to/install/gke.md | 16 +-- docs/how-to/operate/budget-alerts.md | 16 +-- docs/how-to/operate/expose-dashboard.md | 16 +-- docs/how-to/operate/shared-storage.md | 14 +-- docs/index.md | 10 +- docs/reference/index.md | 6 +- docs/tutorials/getting-started.md | 16 +-- docs/tutorials/index.md | 6 +- 23 files changed, 189 insertions(+), 345 deletions(-) delete mode 100644 docs/cfp/cfp-draft.md diff --git a/AGENTS.md b/AGENTS.md index f21b66f..a64c775 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,24 +1,24 @@ -# AGENTS.md — Agent Team Guidelines for claude-teams-operator +# AGENTS.md. Agent Team Guidelines for claude-teams-operator ## When working as a teammate on this project -1. **Check the task list first** — before starting work, check what's assigned to you -2. **Respect module boundaries** — each internal package has a clear scope: - - `internal/controller/` — only reconciliation logic - - `internal/claude/` — only Claude Code file I/O and session management - - `internal/budget/` — only cost estimation - - `internal/webhook/` — only external notifications - - `internal/metrics/` — only Prometheus metrics -3. **Use kubebuilder markers** — all CRD types in `api/v1alpha1/` must have proper `+kubebuilder:` annotations -4. **Test with envtest** — controller tests should use controller-runtime's envtest framework -5. **Follow Kubernetes conventions** — conditions use `metav1.Condition`, status updates are separate from spec changes +1. **Check the task list first**. Before starting work, check what's assigned to you +2. **Respect module boundaries**. Each internal package has a clear scope: + - `internal/controller/`. Only reconciliation logic + - `internal/claude/`. Only Claude Code file I/O and session management + - `internal/budget/`. Only cost estimation + - `internal/webhook/`. Only external notifications + - `internal/metrics/`. Only Prometheus metrics +3. **Use kubebuilder markers**. All CRD types in `api/v1alpha1/` must have proper `+kubebuilder:` annotations +4. **Test with envtest**. Controller tests should use controller-runtime's envtest framework +5. **Follow Kubernetes conventions**. Conditions use `metav1.Condition`, status updates are separate from spec changes ## Architecture rules -- The operator NEVER makes Anthropic API calls directly — it only manages pods that run Claude Code -- All inter-agent communication goes through the shared PVC filesystem — the operator just creates and monitors the volumes -- Budget tracking is estimation-based — we can't read real-time token counts from Claude Code -- Pods use `RestartPolicy: Never` — crashed agents get re-spawned fresh, not restarted +- The operator NEVER makes Anthropic API calls directly. It only manages pods that run Claude Code +- All inter-agent communication goes through the shared PVC filesystem. The operator just creates and monitors the volumes +- Budget tracking is estimation-based. We can't read real-time token counts from Claude Code +- Pods use `RestartPolicy: Never`. Crashed agents get re-spawned fresh, not restarted ## Build verification diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index dfe9eb0..db18a59 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -86,20 +86,20 @@ This approach preserves the native Agent Teams protocol without modification whi ## Storage Requirements -All operator-managed PVCs — `team-state`, `repo`, and (in Cowork mode) `output` — default to `ReadWriteMany` access on a StorageClass named `nfs`. The requirement is not incidental: the lead and every teammate pod must open the same mailbox and task files concurrently, and on a multi-node cluster they will generally land on different nodes. `ReadWriteOnce` can only bind to one node at a time, so it is not a viable default. +All operator-managed PVCs. `team-state`, `repo`, and (in Cowork mode) `output`. Default to `ReadWriteMany` access on a StorageClass named `nfs`. The requirement is not incidental: the lead and every teammate pod must open the same mailbox and task files concurrently, and on a multi-node cluster they will generally land on different nodes. `ReadWriteOnce` can only bind to one node at a time, so it is not a viable default. ### Why ReadWriteMany Each agent pod does two concurrent things against shared state: -- **Writing into peers' inboxes** — the lead writes `teams/{team}/inboxes/{teammate}.json`; each teammate writes to the lead's inbox and occasionally to other teammates'. -- **Claiming tasks** — multiple teammates race to claim items from `tasks/{team}/tasks.json`. +- **Writing into peers' inboxes**. The lead writes `teams/{team}/inboxes/{teammate}.json`; each teammate writes to the lead's inbox and occasionally to other teammates'. +- **Claiming tasks**. Multiple teammates race to claim items from `tasks/{team}/tasks.json`. If the backing PVC cannot be mounted on more than one node, the second pod will fail to schedule (`volume already attached to a different node`) and the team deadlocks before the first mailbox round-trip. ### Supported storage backends -The operator itself has no opinion about the CSI driver — it asks for a PVC with `accessModes: [ReadWriteMany]` and a `storageClassName` that you supply. The table below lists drivers known to satisfy the RWX contract: +The operator itself has no opinion about the CSI driver. It asks for a PVC with `accessModes: [ReadWriteMany]` and a `storageClassName` that you supply. The table below lists drivers known to satisfy the RWX contract: | Platform | Driver | Notes | |----------|--------|-------| @@ -114,11 +114,11 @@ The StorageClass name the operator requests defaults to `nfs` and is overridable ### Single-node fallback -For laptops and CI — Kind, k3d, minikube — a full RWX provisioner is overkill. The operator accepts a `--pvc-access-mode=ReadWriteOnce` flag that switches every managed PVC from `ReadWriteMany` to `ReadWriteOnce`. This works **only** on single-node clusters, because every pod lands on the same node and a hostPath-backed RWO PVC is effectively visible to all of them. +For laptops and CI. Kind, k3d, minikube. A full RWX provisioner is overkill. The operator accepts a `--pvc-access-mode=ReadWriteOnce` flag that switches every managed PVC from `ReadWriteMany` to `ReadWriteOnce`. This works **only** on single-node clusters, because every pod lands on the same node and a hostPath-backed RWO PVC is effectively visible to all of them. `hack/acceptance-setup.sh` uses exactly this trick: it creates an alias StorageClass named `nfs` over `rancher.io/local-path` so the operator's PVC specs still validate, then sets `--pvc-access-mode=ReadWriteOnce` on the controller deployment. -The architectural claim — that a shared mount is sufficient to ferry mailbox JSON between pods — can be verified on any single-node cluster with: +The architectural claim. That a shared mount is sufficient to ferry mailbox JSON between pods. Can be verified on any single-node cluster with: ```bash make acceptance-up @@ -133,10 +133,10 @@ The smoke test reports the effective StorageClass and AccessMode on its PASS lin The native Agent Teams protocol is file-based: -- **Mailboxes** — each agent has a JSON inbox at `~/.claude/teams/{team}/inboxes/{agent}.json`. Agents read their own inbox for messages from teammates. -- **Task list** — a shared JSON file at `~/.claude/tasks/{team}/tasks.json`. The lead writes tasks; teammates claim and update them. +- **Mailboxes**. Each agent has a JSON inbox at `~/.claude/teams/{team}/inboxes/{agent}.json`. Agents read their own inbox for messages from teammates. +- **Task list**. A shared JSON file at `~/.claude/tasks/{team}/tasks.json`. The lead writes tasks; teammates claim and update them. -The operator does not implement or speak this protocol — it only creates the shared PVC that makes the filesystem visible to all pods. Claude Code manages the protocol itself. +The operator does not implement or speak this protocol. It only creates the shared PVC that makes the filesystem visible to all pods. Claude Code manages the protocol itself. ## Coding Mode @@ -148,7 +148,7 @@ When `spec.repository` is set, the operator runs an init Job before deploying po Each teammate pod receives `WORKTREE_PATH=worktrees/{name}`, and the entrypoint `cd`s to that path before launching Claude Code. The lead has no worktree path and works directly from `/workspace/repo`. -Per-worktree isolation prevents git conflicts between concurrent agents — each agent commits to its own branch, and the lead (or an `onComplete` action) handles merging. +Per-worktree isolation prevents git conflicts between concurrent agents. Each agent commits to its own branch, and the lead (or an `onComplete` action) handles merging. ## Cowork Mode @@ -156,7 +156,7 @@ When `spec.workspace` is set (and `spec.repository` is absent or minimal), the o - Creates an output PVC for writable agent output - Mounts workspace inputs (ConfigMaps or existing PVCs) read-only into each pod -- Does not set `WORKTREE_PATH` — agents work in `/workspace/output` or `/workspace/data` +- Does not set `WORKTREE_PATH`. Agents work in `/workspace/output` or `/workspace/data` The entrypoint detects the absence of a git repo gracefully and skips the `git log` startup output. @@ -164,7 +164,7 @@ The entrypoint detects the absence of a git repo gracefully and skips the `git l Claude Code skills live under `~/.claude/skills/{name}/`. The operator mounts ConfigMap-backed skills at `/var/claude-skills/{name}/` and the entrypoint copies them to `~/.claude/skills/{name}/` before launching Claude Code. -Skills are per-agent — the same skill ConfigMap can be mounted into multiple pods independently, so different teammates can have different skill sets. +Skills are per-agent. The same skill ConfigMap can be mounted into multiple pods independently, so different teammates can have different skill sets. ## MCP Servers @@ -193,7 +193,7 @@ The next reconcile loop (within 30 seconds) sees the annotation and spawns the t ## DependsOn Ordering -Teammates can declare `dependsOn` — a list of other teammate names that must reach `Succeeded` phase before this teammate is spawned. The check runs every reconcile loop: +Teammates can declare `dependsOn`. A list of other teammate names that must reach `Succeeded` phase before this teammate is spawned. The check runs every reconcile loop: - In `reconcileInitializing`: initial pod deployment respects dependency order - In `reconcileRunning`: newly unblocked teammates are spawned automatically as their dependencies complete @@ -217,7 +217,7 @@ When the estimate exceeds `budgetLimit`, the operator terminates all pods and se ### Why shared PVC over a message bus? -Agent Teams uses a file-based protocol. Rather than translating it to Redis or NATS, we preserve it exactly by mounting a shared filesystem. This means no changes to Claude Code itself, no protocol versioning concerns, and no additional infrastructure dependencies for simple deployments. The tradeoff is the requirement for ReadWriteMany PVC support — NFS or a cloud-native equivalent like EFS or GCP Filestore. +Agent Teams uses a file-based protocol. Rather than translating it to Redis or NATS, we preserve it exactly by mounting a shared filesystem. This means no changes to Claude Code itself, no protocol versioning concerns, and no additional infrastructure dependencies for simple deployments. The tradeoff is the requirement for ReadWriteMany PVC support. NFS or a cloud-native equivalent like EFS or GCP Filestore. ### Why RestartPolicy: Never? @@ -266,9 +266,9 @@ hack/ ## Roadmap -- **OCI skill artifacts** — pull skills from OCI registries instead of ConfigMaps -- **Real token tracking** — instrument or sidecar Claude Code to capture actual usage -- **envtest integration tests** — full reconcile loop tests against a real API server -- **Horizontal scaling** — multiple operator replicas with leader election -- **Beads/Dolt integration** — persistent task tracking across team runs -- **`AgentTeamRun` controller** — reconciler for the template-instantiation CRD +- **OCI skill artifacts**. Pull skills from OCI registries instead of ConfigMaps +- **Real token tracking**. Instrument or sidecar Claude Code to capture actual usage +- **envtest integration tests**. Full reconcile loop tests against a real API server +- **Horizontal scaling**. Multiple operator replicas with leader election +- **Beads/Dolt integration**. Persistent task tracking across team runs +- **`AgentTeamRun` controller**. Reconciler for the template-instantiation CRD diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c3b189..999ced7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,31 +20,31 @@ This project uses **Linear** (team `AMC`, project `claude-teams-operator`) as th For external contributors who don't have Linear access: - File issues directly on GitHub using the [issue templates](https://github.com/amcheste/claude-teams-operator/issues/new/choose). The maintainer will mirror them into Linear. -- Reference the GitHub issue number in your PR (`Fixes #123`) — that works fine. The Linear sync handles the cross-reference. +- Reference the GitHub issue number in your PR (`Fixes #123`). That works fine. The Linear sync handles the cross-reference. For maintainers and regular contributors: - Open or claim issues in Linear directly via [save_issue](https://linear.app/amcheste/project/claude-teams-operator-32aab082f36b) (or the Linear UI). -- PRs to `develop` are required to reference an `AMC-N` ID or carry a `No-Linear-Issue: ` trailer — the `Linear Issue Reference` CI check enforces this. +- PRs to `develop` are required to reference an `AMC-N` ID or carry a `No-Linear-Issue: ` trailer. The `Linear Issue Reference` CI check enforces this. ## Good first issues If you're looking for a way in, browse: -- [`good first issue`](https://github.com/amcheste/claude-teams-operator/labels/good%20first%20issue) — small, well-scoped tasks with clear acceptance criteria -- [`help wanted`](https://github.com/amcheste/claude-teams-operator/labels/help%20wanted) — areas where the maintainer would specifically welcome a hand -- [`documentation`](https://github.com/amcheste/claude-teams-operator/labels/documentation) — content fixes, new tutorials, or how-to guides for the docs site at [kagents.dev](https://kagents.dev) +- [`good first issue`](https://github.com/amcheste/claude-teams-operator/labels/good%20first%20issue). Small, well-scoped tasks with clear acceptance criteria +- [`help wanted`](https://github.com/amcheste/claude-teams-operator/labels/help%20wanted). Areas where the maintainer would specifically welcome a hand +- [`documentation`](https://github.com/amcheste/claude-teams-operator/labels/documentation). Content fixes, new tutorials, or how-to guides for the docs site at [kagents.dev](https://kagents.dev) If nothing on those lists fits, [open a Discussion](https://github.com/amcheste/claude-teams-operator/discussions) describing what you'd like to work on. Better to align before writing code than after. ## Prerequisites -- **Go 1.23+** — `brew install go` or [go.dev/dl](https://go.dev/dl) -- **Docker** — for building container images -- **Kind** — `brew install kind` (local cluster) -- **kubectl** — `brew install kubectl` -- **Helm** — `brew install helm` -- **golangci-lint** — `brew install golangci-lint` +- **Go 1.23+**. `brew install go` or [go.dev/dl](https://go.dev/dl) +- **Docker**. For building container images +- **Kind**. `brew install kind` (local cluster) +- **kubectl**. `brew install kubectl` +- **Helm**. `brew install helm` +- **golangci-lint**. `brew install golangci-lint` Verify your Go installation: @@ -119,7 +119,7 @@ The CRD types live in `api/v1alpha1/`. After modifying them: 3. Run `make install` to apply the updated CRDs to your cluster 4. Commit both the Go source changes **and** the generated files -Do not edit `zz_generated.deepcopy.go` or `config/crd/bases/*.yaml` by hand — they are always regenerated. +Do not edit `zz_generated.deepcopy.go` or `config/crd/bases/*.yaml` by hand. They are always regenerated. ## Testing @@ -144,8 +144,8 @@ In short: branch from `develop`, one logical change per PR, [Conventional Commit This repo extends the canonical commit types with: -- `test:` — adding or updating tests -- `ci:` — CI/CD configuration changes +- `test:`. Adding or updating tests +- `ci:`. CI/CD configuration changes Scopes are encouraged (optional but helpful): `feat(controller):`, `fix(crd):`, `docs(readme):`, `feat(crd)!: rename budgetLimit field`. @@ -182,11 +182,11 @@ The site auto-deploys to `gh-pages` on every push to `main` that touches `docs/` ## How to add a new reconciler feature -The most common contribution path is "add a new field to an `AgentTeam` and have the operator do something with it." Use this worked example as a template — it's the path #13–#16 followed for crash respawn, RBAC, create-pr, and push-branch. +The most common contribution path is "add a new field to an `AgentTeam` and have the operator do something with it." Use this worked example as a template. It's the path #13–#16 followed for crash respawn, RBAC, create-pr, and push-branch. ### 1. Decide where the field belongs -Most lifecycle-related fields live on `LifecycleSpec`; pod-level configuration lives on `LeadSpec`/`TeammateSpec`; cluster-wide defaults live on the Helm chart's `values.yaml`. When in doubt, look at how `MaxRestarts` or `GitCredentialsSecret` are wired — they're representative. +Most lifecycle-related fields live on `LifecycleSpec`; pod-level configuration lives on `LeadSpec`/`TeammateSpec`; cluster-wide defaults live on the Helm chart's `values.yaml`. When in doubt, look at how `MaxRestarts` or `GitCredentialsSecret` are wired. They're representative. ### 2. Extend the CRD type @@ -202,7 +202,7 @@ Edit `api/v1alpha1/agentteam_types.go` (or `template_types.go`). Add the field w MaxRestarts *int32 `json:"maxRestarts,omitempty"` ``` -The doc comment becomes the CRD's OpenAPI description — write it for someone reading `kubectl explain agentteam.spec.lifecycle.maxRestarts`. +The doc comment becomes the CRD's OpenAPI description. Write it for someone reading `kubectl explain agentteam.spec.lifecycle.maxRestarts`. ### 3. Regenerate manifests + deepcopy @@ -214,7 +214,7 @@ This rewrites `config/crd/bases/*.yaml`, `charts/claude-teams-operator/crds/*.ya ### 4. Implement the reconciler change -Find the right phase function — `reconcilePending`, `reconcileInitializing`, `reconcileRunning`, or `reconcileTerminal` — in `internal/controller/agentteam_controller.go`. The phases are documented in [ARCHITECTURE.md § State Machine](ARCHITECTURE.md). +Find the right phase function. `reconcilePending`, `reconcileInitializing`, `reconcileRunning`, or `reconcileTerminal`. In `internal/controller/agentteam_controller.go`. The phases are documented in [ARCHITECTURE.md § State Machine](ARCHITECTURE.md). Add a small helper rather than inlining new logic. The convention is `func (r *AgentTeamReconciler) handleX(ctx, team) (...)` for stateful behavior, and free functions for pure logic. See `handleTeammateFailures` and `newTeamTracker` for examples. @@ -236,9 +236,9 @@ If the existing webhook event types don't fit, add a new one to `internal/webhoo Each PR should add tests at the layers it changes: -- **Unit tests** — fast, fake-client based. Cover validation, branch coverage in your helper, error paths. Add to `internal/controller/agentteam__test.go`. See [TESTING.md](TESTING.md) for the suite breakdown. -- **Integration tests** — envtest-backed Ginkgo specs in `internal/controller/agentteam_integration_test.go` (or a new `agentteam__integration_test.go`). Use these when the behavior depends on the real API server's optimistic concurrency, status subresource handling, or owner references. -- **Acceptance tests** — Kind-cluster Ginkgo specs under `test/acceptance/`. Use when the behavior involves pod lifecycle, PVC mounting, or anything that fake-client can't simulate. Real-API E2E (`test/e2e/`) is reserved for end-to-end verification against Anthropic's API. +- **Unit tests**. Fast, fake-client based. Cover validation, branch coverage in your helper, error paths. Add to `internal/controller/agentteam__test.go`. See [TESTING.md](TESTING.md) for the suite breakdown. +- **Integration tests**. Envtest-backed Ginkgo specs in `internal/controller/agentteam_integration_test.go` (or a new `agentteam__integration_test.go`). Use these when the behavior depends on the real API server's optimistic concurrency, status subresource handling, or owner references. +- **Acceptance tests**. Kind-cluster Ginkgo specs under `test/acceptance/`. Use when the behavior involves pod lifecycle, PVC mounting, or anything that fake-client can't simulate. Real-API E2E (`test/e2e/`) is reserved for end-to-end verification against Anthropic's API. A good rule: if your feature has a state machine, your test count should be ≥ the number of branches in the state machine. @@ -255,9 +255,9 @@ Cluster-wide defaults belong on the operator's CLI flags (read from a ConfigMap ### Reference PRs -These are good examples to skim before opening your first reconciler PR — each one followed this exact recipe: +These are good examples to skim before opening your first reconciler PR. Each one followed this exact recipe: -- [#13 Crash respawn](https://github.com/amcheste/claude-teams-operator/pull/133) — controller state machine + metrics + webhook + tests across all three layers -- [#14 Per-agent RBAC](https://github.com/amcheste/claude-teams-operator/pull/134) — CRD-less feature: just controller logic + scoped Roles + RBAC markers -- [#15 create-pr](https://github.com/amcheste/claude-teams-operator/pull/135) — new internal package (`internal/github`) + controller wiring + httptest-backed tests -- [#16 push-branch](https://github.com/amcheste/claude-teams-operator/pull/148) — async terminal Job + status mirror + envtest integration spec +- [#13 Crash respawn](https://github.com/amcheste/claude-teams-operator/pull/133). Controller state machine + metrics + webhook + tests across all three layers +- [#14 Per-agent RBAC](https://github.com/amcheste/claude-teams-operator/pull/134). CRD-less feature: just controller logic + scoped Roles + RBAC markers +- [#15 create-pr](https://github.com/amcheste/claude-teams-operator/pull/135). New internal package (`internal/github`) + controller wiring + httptest-backed tests +- [#16 push-branch](https://github.com/amcheste/claude-teams-operator/pull/148). Async terminal Job + status mirror + envtest integration spec diff --git a/README.md b/README.md index 9feeb08..d6db3f3 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ --- -> **kagents** is the project brand. The implementation lives in the [`claude-teams-operator`](https://github.com/amcheste/claude-teams-operator) repository and ships under the `claude.amcheste.io/v1alpha1` API group. Documentation site: [kagents.dev](https://kagents.dev) (under construction — see [v0.7.0 milestone](https://github.com/amcheste/claude-teams-operator/milestone/8)). +> **kagents** is the project brand. The implementation lives in the [`claude-teams-operator`](https://github.com/amcheste/claude-teams-operator) repository and ships under the `claude.amcheste.io/v1alpha1` API group. Documentation site: [kagents.dev](https://kagents.dev) (under construction. See [v0.7.0 milestone](https://github.com/amcheste/claude-teams-operator/milestone/8)). -Claude Code [Agent Teams](https://docs.anthropic.com/en/docs/claude-code/agent-teams) let multiple Claude Code instances collaborate — a lead coordinates work via a shared task list while teammates communicate through peer-to-peer mailboxes. Natively this runs on a single machine using tmux. This operator lifts that pattern into Kubernetes so you can run large-scale agent teams on your cluster. +Claude Code [Agent Teams](https://docs.anthropic.com/en/docs/claude-code/agent-teams) let multiple Claude Code instances collaborate. A lead coordinates work via a shared task list while teammates communicate through peer-to-peer mailboxes. Natively this runs on a single machine using tmux. This operator lifts that pattern into Kubernetes so you can run large-scale agent teams on your cluster. ## Modes @@ -32,23 +32,23 @@ Both modes share the same coordination protocol (shared PVCs, mailboxes, task li ## Features -- **Native Agent Teams protocol** — preserves Anthropic's file-based mailbox and task list format over ReadWriteMany PVCs; no protocol translation -- **Per-teammate git worktrees** — each coding agent works on an isolated branch to prevent merge conflicts -- **Cowork mode** — mount ConfigMap/PVC inputs and collect outputs without requiring a git repo -- **Skills as CRD fields** — mount Claude Code skills from ConfigMaps into each agent's `.claude/skills/` -- **MCP servers per agent** — configure Model Context Protocol connections per teammate -- **Approval gates** — pause spawning specific teammates until a human applies an annotation -- **Budget enforcement** — terminate the team if estimated API cost exceeds a configured limit -- **Timeout enforcement** — terminate the team after a configurable wall-clock duration -- **`dependsOn` ordering** — spawn teammates only after their declared dependencies complete -- **Reusable templates** — define team patterns with `AgentTeamTemplate`, instantiate with `AgentTeamRun` +- **Native Agent Teams protocol**. Preserves Anthropic's file-based mailbox and task list format over ReadWriteMany PVCs; no protocol translation +- **Per-teammate git worktrees**. Each coding agent works on an isolated branch to prevent merge conflicts +- **Cowork mode**. Mount ConfigMap/PVC inputs and collect outputs without requiring a git repo +- **Skills as CRD fields**. Mount Claude Code skills from ConfigMaps into each agent's `.claude/skills/` +- **MCP servers per agent**. Configure Model Context Protocol connections per teammate +- **Approval gates**. Pause spawning specific teammates until a human applies an annotation +- **Budget enforcement**. Terminate the team if estimated API cost exceeds a configured limit +- **Timeout enforcement**. Terminate the team after a configurable wall-clock duration +- **`dependsOn` ordering**. Spawn teammates only after their declared dependencies complete +- **Reusable templates**. Define team patterns with `AgentTeamTemplate`, instantiate with `AgentTeamRun` ## Quick Start ### Prerequisites - Kubernetes 1.28+ -- ReadWriteMany PVC support (NFS, EFS, or a compatible CSI driver — see [ARCHITECTURE.md § Storage Requirements](ARCHITECTURE.md#storage-requirements) for options) +- ReadWriteMany PVC support (NFS, EFS, or a compatible CSI driver. See [ARCHITECTURE.md § Storage Requirements](ARCHITECTURE.md#storage-requirements) for options) - Claude Code CLI access (Max subscription or API key) - Opus 4.6 model access (required for Agent Teams) @@ -222,7 +222,7 @@ The primary resource. Defines the full team, its workspace, lifecycle, and obser ### AgentTeamTemplate -A reusable team pattern. Does not run on its own — instantiate with `AgentTeamRun`. +A reusable team pattern. Does not run on its own. Instantiate with `AgentTeamRun`. ### AgentTeamRun diff --git a/SECURITY.md b/SECURITY.md index 6778b5b..96690f6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,28 +13,28 @@ The latest release is the most recent `v*` tag on https://github.com/amcheste/cl ## Reporting a vulnerability -**Please do not open a public issue, Discussion, or pull request for security vulnerabilities.** Use GitHub's [private vulnerability reporting](https://github.com/amcheste/claude-teams-operator/security/advisories/new) instead — that surface lets you submit confidentially, and the maintainer can collaborate with you on a fix without the report being visible to anyone else until it's resolved. +**Please do not open a public issue, Discussion, or pull request for security vulnerabilities.** Use GitHub's [private vulnerability reporting](https://github.com/amcheste/claude-teams-operator/security/advisories/new) instead. That surface lets you submit confidentially, and the maintainer can collaborate with you on a fix without the report being visible to anyone else until it's resolved. Please include in your report: - A clear description of the vulnerability - Steps to reproduce (or a proof-of-concept manifest / kubectl invocation) - The kagents version you observed it on -- Potential impact — what an attacker could achieve, and against what cluster topology +- Potential impact. What an attacker could achieve, and against what cluster topology ## Coordinated disclosure expectations We follow a **coordinated disclosure** process: -1. **Acknowledgement** — within **7 days** of your report, the maintainer will confirm receipt and start triage. -2. **Triage + fix** — within **30 days**, you will receive either a fix candidate, a status update with a clear timeline, or a written explanation of why the report doesn't qualify as a vulnerability. -3. **Embargo** — fix development happens in private. We ask you to keep the issue confidential until the fix ships and is publicly announced. We will not embargo for longer than 90 days from the original report without your agreement. -4. **Public disclosure** — once the fix is released, we publish a [GitHub Security Advisory](https://github.com/amcheste/claude-teams-operator/security/advisories) with the details, affected versions, mitigation steps, and credit to you (unless you ask to remain anonymous). -5. **CVE assignment** — if the issue qualifies, we request a CVE through GitHub's CNA before public disclosure. +1. **Acknowledgement**. Within **7 days** of your report, the maintainer will confirm receipt and start triage. +2. **Triage + fix**. Within **30 days**, you will receive either a fix candidate, a status update with a clear timeline, or a written explanation of why the report doesn't qualify as a vulnerability. +3. **Embargo**. Fix development happens in private. We ask you to keep the issue confidential until the fix ships and is publicly announced. We will not embargo for longer than 90 days from the original report without your agreement. +4. **Public disclosure**. Once the fix is released, we publish a [GitHub Security Advisory](https://github.com/amcheste/claude-teams-operator/security/advisories) with the details, affected versions, mitigation steps, and credit to you (unless you ask to remain anonymous). +5. **CVE assignment**. If the issue qualifies, we request a CVE through GitHub's CNA before public disclosure. ## What counts as a security issue -If you're not sure whether something is a vulnerability or a bug, err on the side of reporting it through the private channel — it's easy to move a non-security report to a public issue, but a public report of a real vulnerability is unfixable damage. +If you're not sure whether something is a vulnerability or a bug, err on the side of reporting it through the private channel. It's easy to move a non-security report to a public issue, but a public report of a real vulnerability is unfixable damage. In-scope examples: @@ -54,4 +54,4 @@ Out of scope (please file as regular GitHub issues): ## Hardening checklist for operators -For users deploying kagents in production, the [Operations explanation](https://kagents.dev/explanation/operations/) covers the defense-in-depth model — per-agent ServiceAccounts, the file-based-protocol threat model, and what RBAC does and doesn't enforce. Reading that page before going live is recommended. +For users deploying kagents in production, the [Operations explanation](https://kagents.dev/explanation/operations/) covers the defense-in-depth model. Per-agent ServiceAccounts, the file-based-protocol threat model, and what RBAC does and doesn't enforce. Reading that page before going live is recommended. diff --git a/docs/README.md b/docs/README.md index 84fc85f..6980717 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,4 +17,4 @@ A push to `main` that touches `docs/`, `mkdocs.yml`, or `.github/workflows/docs. ## Structure -The site uses the [Diátaxis framework](https://diataxis.fr) — four sections: Tutorials, How-to guides, Reference, Explanation. Section pages will be filled in by the v0.7.0 content issues. For now only the homepage exists. +The site uses the [Diátaxis framework](https://diataxis.fr). Four sections: Tutorials, How-to guides, Reference, Explanation. Section pages will be filled in by the v0.7.0 content issues. For now only the homepage exists. diff --git a/docs/cfp/cfp-draft.md b/docs/cfp/cfp-draft.md deleted file mode 100644 index d502b8f..0000000 --- a/docs/cfp/cfp-draft.md +++ /dev/null @@ -1,156 +0,0 @@ -# KubeCon NA 2026 — kagents CFP Draft - -> **Project:** **kagents** ([kagents.dev](https://kagents.dev)) — implementation in [`claude-teams-operator`](https://github.com/amcheste/claude-teams-operator). -> -> Draft submission for issue [#23](https://github.com/amcheste/claude-teams-operator/issues/23). Conference: KubeCon + CloudNativeCon North America 2026, Salt Lake City, Nov 9–12. CFP deadline: **May 31, 2026 at 11:59pm MT**. Submit at https://sessionize.com/kubecon-cloudnativecon-north-america-2026/. -> -> This is a starting draft. Every field below is meant to be edited. Open questions for the maintainer are listed at the bottom. - ---- - -## Submission metadata - -| Field | Recommendation | Rationale | -|-------|----------------|-----------| -| **Submission type** | Session Presentation (30 min) | The form's options are 5 / 30 / 75 minutes. 30 fits the demo-heavy structure without padding. Tutorial (75 min) is the alternative if the maintainer wants hands-on. | -| **Track (primary)** | AI Inference + Agentic | New track for 2026. Direct fit: this is a system for running agent workloads on K8s. | -| **Track (alternate)** | Platform Engineering | Reasonable alternate angle: the operator *is* platform infra for agent teams. Pick AI Inference + Agentic if both feel viable, since the program committee may load-balance between them. | -| **Audience level** | Intermediate | Assumes operator-pattern literacy (CRDs, reconcile loops, RBAC, PVC access modes). Does not assume Claude Code or LLM background. | -| **Case study?** | No | This is a project talk, not a deployment retrospective. | - ---- - -## Abstract title (75 char max) - -**Primary:** - -``` -Reconciling Agent Teams: A Kubernetes Operator for Claude Code -``` - -(62 characters) - -**Alternates worth considering:** - -``` -Stateless Agents, Stateful Cluster: K8s for Claude Code Agent Teams -``` - -(67 characters — leans harder into the architectural narrative from KUBECON.md: the agent forgets, the cluster remembers.) - -``` -The Operator Pattern for Multi-Agent Coding Teams -``` - -(49 characters — most general, drops the Claude Code brand. Use if the program committee tends to read brand-named titles as vendor pitches.) - ---- - -## Abstract (1,300 char max) - -> Most multi-agent orchestration frameworks treat Kubernetes as deployment infrastructure: pods that happen to run an LLM. This talk shows what changes when the cluster becomes the coordination fabric. **kagents** ([kagents.dev](https://kagents.dev)) runs Anthropic's Claude Code Agent Teams as a CRD-driven workload, preserving the native file-based mailbox protocol over a ReadWriteMany PVC instead of inventing a new one. An AgentTeam resource declares a lead, teammates, budget, quality gates, and lifecycle policy in a single spec. The reconciler provisions per-teammate git worktrees, scopes each pod with its own ServiceAccount, and re-spawns crashed agents using the durable task list as recovery state. The agent does not remember the conversation, but the task list on the PVC tells the fresh pod what work remains. The talk walks through the architectural choices that made this work in K8s: why agent state lives on a PVC instead of a CRD status field, why RestartPolicy is Never, what RWX storage you actually need in production versus on a laptop, and how Prometheus metrics, webhooks, and human approval gates plug into the reconcile loop. A live demo deploys a coding team to a Kind cluster, shows mailbox traffic between pods, kills a teammate, and watches the operator respawn it from the task list. - -(~1,290 characters, against a 1,300 limit. The buffer is small. Trim if any field is added during iteration.) - ---- - -## Audience - -> Platform engineers, operator authors, and SREs who run Kubernetes and are evaluating how to host multi-agent LLM workloads without building a custom protocol. Attendees should be comfortable with the operator pattern (CRDs, controllers, reconcile loops), Kubernetes RBAC, and PVC access modes. Familiarity with Claude Code or Agent Teams is helpful but not required; the talk explains the native protocol and the K8s primitives it maps to. Attendees will leave with a clear picture of which Kubernetes building blocks translate cleanly to agent workloads (git worktrees as a concurrency primitive, ServiceAccounts as per-agent capability boundaries, owner references for cascade deletion of team state) and which assumptions break down at scale (CRD status as long-running state, single-node RWO fallbacks, real-time cost tracking). - ---- - -## Benefits to the ecosystem (1,000 char max) - -> Cloud-native multi-agent systems are a 2026 priority for both CNCF and individual platform teams, but most current solutions invent new orchestration protocols and layer them on top of Kubernetes. This talk demonstrates the alternative: model the agent team as a first-class Kubernetes resource and let existing primitives do the coordination work. The architectural patterns generalize beyond Claude Code; any multi-agent system with file-based or shared-state coordination can adopt the same approach. The talk surfaces the honest tradeoffs (ReadWriteMany storage cost, estimation-based budget tracking, the limits of CRD status for long-running state) so attendees can evaluate whether the pattern fits their workloads. The operator is open source under Apache 2.0, ships with a published Helm chart and Prometheus dashboard, and gates every release on a real-Claude end-to-end test in CI. - -(~970 characters) - ---- - -## Open source projects discussed - -- **kagents** ([kagents.dev](https://kagents.dev)) — the operator itself, Apache 2.0; implementation at [claude-teams-operator](https://github.com/amcheste/claude-teams-operator) -- [Kubernetes](https://github.com/kubernetes/kubernetes) — the platform; specifically `controller-runtime`, `kubebuilder`, RBAC, PVC subsystem -- [Prometheus](https://github.com/prometheus/prometheus) and [Grafana](https://github.com/grafana/grafana) — metrics scraping and the published dashboard ConfigMap -- [Helm](https://github.com/helm/helm) — chart packaging and release distribution -- Anthropic's Claude Code Agent Teams protocol — the native file-based coordination format the operator preserves (Claude Code itself is not open source; the protocol behavior is documented and stable enough to wrap as-is) - ---- - -## Reviewer-facing talk outline (~30 min) - -This expands on the abstract — provided in case the Sessionize form exposes a longer description field, and to anchor the demo plan. - -| Time | Beat | -|------|------| -| 0:00 | The problem framing. Most agent frameworks bolt onto Kubernetes; this talk argues for the inverse — Kubernetes primitives doing the coordination work. | -| 2:00 | Native Agent Teams in 60 seconds: file-based JSON mailboxes, shared task list, no session resumption. Why this protocol is unusually well-suited to a shared filesystem. | -| 5:00 | The `AgentTeam` CRD: one spec for a whole team (lead + teammates + lifecycle + budget). Contrast with agent-as-a-resource designs. | -| 8:00 | Phase state machine: `Pending → Initializing → Running → Completed/Failed/TimedOut/BudgetExceeded`. How state transitions map to actual K8s objects (PVCs, init Job, pods). | -| 11:00 | The ReadWriteMany requirement, in detail. Why coordination over a PVC actually works. What fails on RWO. The single-node RWO fallback used in CI and what it can and cannot prove. | -| 14:00 | Per-agent RBAC. Each pod gets its own ServiceAccount with `resourceNames`-restricted Roles on the secrets and PVCs it owns. A free security win that non-native orchestrators have to reinvent. | -| 16:00 | **Demo 1 — Crash recovery.** Deploy a coding team. Show mailbox files appearing on the PVC. Kill a teammate pod. Watch the reconciler respawn it. The fresh agent has no conversation memory, but the task list tells it what is left. | -| 21:00 | `onComplete` actions: `create-pr` opens a real GitHub PR via the REST API; `push-branch` consolidates per-teammate worktree branches into one head via a Job. The worktree-as-concurrency-primitive story. | -| 24:00 | **Demo 2 — Observability.** Prometheus metrics, the Grafana dashboard ConfigMap, an approval gate firing a webhook before a sensitive teammate spawns. | -| 27:00 | Honest tradeoffs we are still working through: estimation-based budget tracking, real multi-node test coverage, the limits of CRD status as a substitute for a workflow engine. | -| 29:00 | Wrap and pointers (repo, Helm chart, contributor docs). | -| 30:00 | Q&A. | - ---- - -## Demo plan - -Two demos, both runnable on a laptop with Kind: - -1. **Crash recovery (5 min, on stage).** Deploy a 3-agent `AgentTeam` from a sample manifest, watch pods come up, observe mailbox JSON appearing on the shared PVC, `kubectl delete pod` one teammate, watch the reconciler respawn it. The point is to show the agent's lost context window does not lose the team's progress, because the task list is durable. - -2. **Observability and gates (3 min, on stage).** Bring up the Grafana dashboard against the operator's Prometheus metrics. Trigger an approval gate so a webhook fires; grant approval via `kubectl annotate`; watch the gated teammate spawn. - -Both demos run today against the shipped v0.5.0 release. Backup recordings will be prepared in case live demo bandwidth fails on the venue Wi-Fi. - ---- - -## Speaker bio - -> _TBD — see open questions._ - ---- - -## Prior speaking history - -> _TBD — see open questions._ - ---- - -## Open questions for the maintainer - -These are the items that need maintainer input before submission: - -1. **Speaker bio** — short paragraph (≤ ~500 chars) covering current role, relevant background, and any past public talks or projects. Include a recent headshot upload-ready. -2. **Prior speaking history** — has the maintainer presented at a CNCF event in the past 12 months? The form asks for video links if so. -3. **Track preference** — primary recommendation here is **AI Inference + Agentic**; the alternate is **Platform Engineering**. Which one does the maintainer want as the primary track? (Submitting to one does not preclude the program committee from re-routing.) -4. **Title preference** — three candidates above. Maintainer's call. -5. **Co-speaker?** — solo or two-speaker? The form allows up to two on a Session Presentation. -6. **Tutorial alternate?** — if the talk lands strongly, a 75-minute Tutorial slot is also viable (deploy a team in real time, walk through the CRD field by field). Worth submitting both? The CFP allows up to three submissions per speaker. -7. **Demo cluster** — confirm the on-stage cluster is Kind on a laptop, vs. an actual cloud cluster. Bandwidth and predictability favor Kind; "real cluster" favors the multi-node RWX story. -8. **Release alignment** — the v0.6.0 (Operator Dashboard) and v1.0.0 (Demo Polish) milestones land before the conference. Should the dashboard be part of the demo, or kept as a parallel track? Including it strengthens the story but adds a moving piece to rehearse. - ---- - -## Notes on substance - -Everything in the abstract and the outline maps to shipped, tested code in v0.1.0–v0.5.0: - -- AgentTeam CRD with single-spec team declaration → [api/v1alpha1/agentteam_types.go](../../api/v1alpha1/agentteam_types.go) -- Reconciler phase state machine → [internal/controller/agentteam_controller.go](../../internal/controller/agentteam_controller.go), see also [ARCHITECTURE.md § Phase State Machine](../../ARCHITECTURE.md#phase-state-machine) -- ReadWriteMany PVC coordination + single-node fallback → [ARCHITECTURE.md § Storage Requirements](../../ARCHITECTURE.md#storage-requirements), [hack/mailbox-smoke-test.sh](../../hack/mailbox-smoke-test.sh) -- Per-agent ServiceAccounts with `resourceNames`-restricted Roles — shipped in v0.4.0 (#14) -- Crash respawn with restart counters — v0.4.0 (#13) -- `onComplete: create-pr` — v0.4.0 (#15); `onComplete: push-branch` — v0.4.0 (#16) -- Prometheus metrics + Grafana dashboard ConfigMap — v0.3.0 -- Webhook engine + approval gates — v0.3.0 -- AgentTeamTemplate + AgentTeamRun controllers — v0.5.0 (#17, #18) -- Real-Claude E2E gate before release publishes — v0.4.0 (#150) - -No claim in this draft refers to unshipped work. diff --git a/docs/explanation/coordination.md b/docs/explanation/coordination.md index d5ce19a..bb17b98 100644 --- a/docs/explanation/coordination.md +++ b/docs/explanation/coordination.md @@ -4,15 +4,15 @@ This is the load-bearing design choice in kagents: agent-to-agent communication ## Why a shared filesystem instead of a message bus? -Anthropic's Claude Code Agent Teams runs natively on a single machine using tmux. Multiple Claude Code instances coordinate via files in `~/.claude/teams/` — JSON inboxes for peer-to-peer messages, a JSON task list for shared work tracking. The protocol is unspecified beyond "look at the files." +Anthropic's Claude Code Agent Teams runs natively on a single machine using tmux. Multiple Claude Code instances coordinate via files in `~/.claude/teams/`. JSON inboxes for peer-to-peer messages, a JSON task list for shared work tracking. The protocol is unspecified beyond "look at the files." We could have translated this to Redis, NATS, or a custom gRPC service. We chose not to: -- **No protocol versioning to track.** Claude Code owns the format. When it ships a v2 mailbox schema, kagents inherits it for free — we never read or write the contents. +- **No protocol versioning to track.** Claude Code owns the format. When it ships a v2 mailbox schema, kagents inherits it for free. We never read or write the contents. - **No translation layer to debug.** When something goes wrong, you can `kubectl exec` into a pod and inspect the actual files Claude Code is reading and writing. There's no opaque protocol bridge in the middle. - **No additional infrastructure.** A bare RWX PVC is enough. No Redis to operate, no message-bus HA story. -The cost is real — ReadWriteMany storage isn't free on every cluster, and we have to be honest about that. +The cost is real. ReadWriteMany storage isn't free on every cluster, and we have to be honest about that. ## Mailbox layout @@ -70,7 +70,7 @@ graph TB style O fill:#f3e5f5,stroke:#7b1fa2 ``` -The `team-state` PVC is the coordination fabric — it carries the mailboxes and the task list. The `repo` PVC (coding mode) carries the git clone and per-teammate worktrees. The `output` PVC (Cowork mode) is where agents write artifacts. +The `team-state` PVC is the coordination fabric. It carries the mailboxes and the task list. The `repo` PVC (coding mode) carries the git clone and per-teammate worktrees. The `output` PVC (Cowork mode) is where agents write artifacts. In practice the operator mounts the team-state PVC into each pod, and the entrypoint symlinks the `teams/` and `tasks/` subdirectories into `~/.claude/`: @@ -92,7 +92,7 @@ If the backing PVC supports only `ReadWriteOnce`, the second pod fails to mount ### Supported backends -The operator has no opinion about the CSI driver — it asks for an RWX PVC and a `storageClassName` you supply. Backends that satisfy the contract: +The operator has no opinion about the CSI driver. It asks for an RWX PVC and a `storageClassName` you supply. Backends that satisfy the contract: | Platform | Driver | Notes | |----------|--------|-------| @@ -104,7 +104,7 @@ The operator has no opinion about the CSI driver — it asks for an RWX PVC and ### Single-node fallback -For laptops, Kind, k3d, minikube — a real RWX provisioner is overkill. The operator accepts a `--pvc-access-mode=ReadWriteOnce` flag. This works **only** because every pod lands on the same node, and a hostPath-backed RWO PVC is then visible to all of them. +For laptops, Kind, k3d, minikube. A real RWX provisioner is overkill. The operator accepts a `--pvc-access-mode=ReadWriteOnce` flag. This works **only** because every pod lands on the same node, and a hostPath-backed RWO PVC is then visible to all of them. !!! danger "Don't use RWO on a multi-node cluster" A second pod scheduled on a different node will fail to mount the PVC and the team will deadlock. The single-node fallback is a development convenience, not a production option. @@ -154,7 +154,7 @@ When `spec.workspace` is set instead of `spec.repository`, the operator skips th - Mounts `workspace.inputs` (ConfigMaps or existing PVCs) read-only into each pod - Doesn't set `WORKTREE_PATH`; agents work in `/workspace/output` or `/workspace/data` -The mailbox protocol is identical — Cowork agents still coordinate via `~/.claude/teams/.../inboxes/`. The only difference is what filesystem they're writing artifacts into. +The mailbox protocol is identical. Cowork agents still coordinate via `~/.claude/teams/.../inboxes/`. The only difference is what filesystem they're writing artifacts into. ## What this means for debugging @@ -168,5 +168,5 @@ There's no opaque coordinator process to dump. Everything Claude Code knows abou ## Where to look next -- [Resource model](resources.md) — the CRDs that compose into a running team -- [Operations](operations.md) — budget, RBAC, and observability for the running team +- [Resource model](resources.md). The CRDs that compose into a running team +- [Operations](operations.md). Budget, RBAC, and observability for the running team diff --git a/docs/explanation/index.md b/docs/explanation/index.md index d67bfcd..852f6f8 100644 --- a/docs/explanation/index.md +++ b/docs/explanation/index.md @@ -1,15 +1,15 @@ # Explanation -The "why" behind kagents — architecture, design tradeoffs, the choices that shaped the project. Read these when you want to understand what's actually happening, not just how to use it. +The "why" behind kagents. Architecture, design tradeoffs, the choices that shaped the project. Read these when you want to understand what's actually happening, not just how to use it. ## Pages -- **[Resource model](resources.md)** — the three CRDs (`AgentTeam`, `AgentTeamTemplate`, `AgentTeamRun`), how they relate, and when to reach for which. -- **[Coordination protocol](coordination.md)** — the file-based mailbox model, why ReadWriteMany is required, per-teammate git worktrees as a concurrency primitive. -- **[Operations](operations.md)** — budget estimation, per-agent RBAC, observability via Prometheus + Grafana + webhooks. +- **[Resource model](resources.md)**. The three CRDs (`AgentTeam`, `AgentTeamTemplate`, `AgentTeamRun`), how they relate, and when to reach for which. +- **[Coordination protocol](coordination.md)**. The file-based mailbox model, why ReadWriteMany is required, per-teammate git worktrees as a concurrency primitive. +- **[Operations](operations.md)**. Budget estimation, per-agent RBAC, observability via Prometheus + Grafana + webhooks. ## Going deeper -The repo's [`ARCHITECTURE.md`](https://github.com/amcheste/claude-teams-operator/blob/main/ARCHITECTURE.md) is the design doc — denser, more focused on rationale than on usage. It overlaps with these pages but goes further into the file-by-file structure of the codebase. +The repo's [`ARCHITECTURE.md`](https://github.com/amcheste/claude-teams-operator/blob/main/ARCHITECTURE.md) is the design doc. Denser, more focused on rationale than on usage. It overlaps with these pages but goes further into the file-by-file structure of the codebase. The [KubeCon NA 2026 talk](https://github.com/amcheste/claude-teams-operator/blob/main/KUBECON.md) frames the same architecture from the conference angle (interesting problems encountered, competitive landscape, design decisions worth surfacing on stage). diff --git a/docs/explanation/operations.md b/docs/explanation/operations.md index f7d9513..415dd90 100644 --- a/docs/explanation/operations.md +++ b/docs/explanation/operations.md @@ -26,7 +26,7 @@ The reconciler compares `status.estimatedCostUsd` against `spec.lifecycle.budget 3. `status.completedAt` is stamped 4. A `webhook.budgetExceeded` event fires (if configured) -There's no grace period — the team stops the moment the estimate crosses. Set the limit with headroom. +There's no grace period. The team stops the moment the estimate crosses. Set the limit with headroom. ### Honest tradeoffs @@ -34,7 +34,7 @@ This is the lightest-touch approach available without instrumenting Claude Code. - **Estimate, not measurement.** Real token usage depends on prompt length, context window growth, and how often the agent reaches for tools. The estimate can be off by 2-3x in either direction. - **Heuristic is per-active-minute.** An agent waiting on `dependsOn` doesn't accrue cost; one running flat out at the same rate as one mostly idle does. The heuristic averages the difference away. -- **Rate table is hardcoded.** The token-per-minute heuristic and the per-million prices live in `internal/budget/tracker.go`. Adjusting them requires a code change and rebuild — config-via-Helm-values is on the roadmap. +- **Rate table is hardcoded.** The token-per-minute heuristic and the per-million prices live in `internal/budget/tracker.go`. Adjusting them requires a code change and rebuild. Config-via-Helm-values is on the roadmap. For production, set `budgetLimit` ~2x what you actually want to spend, and treat the budget as a circuit breaker rather than a precise meter. Real cost tracking via instrumented Claude Code or sidecar log parsing is on the roadmap; until then, the [Anthropic console](https://console.anthropic.com/) is the source of truth for accounting. @@ -77,10 +77,10 @@ The threat model is "a teammate's prompt is malicious or compromised." The blast - ✅ Cannot read another teammate's secrets (different SA) - ✅ Cannot exec into the lead pod (no `pods/exec`) - ✅ Cannot enumerate cluster state (no list verbs on namespace-wide resources) -- ⚠️ Can write to the shared `team-state` PVC — a malicious teammate could poison the task list or write to a peer's inbox. This is inherent to the file-based protocol; mitigations would require Claude Code to authenticate writes. +- ⚠️ Can write to the shared `team-state` PVC. A malicious teammate could poison the task list or write to a peer's inbox. This is inherent to the file-based protocol; mitigations would require Claude Code to authenticate writes. - ⚠️ Can write to the shared `repo` PVC. Worktrees are isolated by branch, but the agent could `cd` to a peer's worktree. -The RBAC model handles the K8s side cleanly; the filesystem-level threats need protocol-level signing to fully address. For most use cases — internal CI, trusted prompts — the filesystem trust model is acceptable. +The RBAC model handles the K8s side cleanly; the filesystem-level threats need protocol-level signing to fully address. For most use cases. Internal CI, trusted prompts. The filesystem trust model is acceptable. ## Observability @@ -163,6 +163,6 @@ Within 30 seconds (the default reconcile interval), the gated teammate spawns an ## Where to look next -- [Resource model](resources.md) — what an `AgentTeam` looks like under the hood -- [Coordination protocol](coordination.md) — how the agents actually talk to each other -- [How-to guides](../how-to/index.md) — concrete operational recipes (coming in v0.7.0) +- [Resource model](resources.md). What an `AgentTeam` looks like under the hood +- [Coordination protocol](coordination.md). How the agents actually talk to each other +- [How-to guides](../how-to/index.md). Concrete operational recipes (coming in v0.7.0) diff --git a/docs/explanation/resources.md b/docs/explanation/resources.md index 9265083..97d5c40 100644 --- a/docs/explanation/resources.md +++ b/docs/explanation/resources.md @@ -76,18 +76,18 @@ Pending ─────► Initializing ─────► Running ──── Failed ``` -Terminal phases (`Completed`, `Failed`, `TimedOut`, `BudgetExceeded`) trigger cleanup — pods get deleted, `status.completedAt` gets stamped, the reconciler stops requeuing. +Terminal phases (`Completed`, `Failed`, `TimedOut`, `BudgetExceeded`) trigger cleanup. Pods get deleted, `status.completedAt` gets stamped, the reconciler stops requeuing. Other status fields worth knowing: -- `status.lead.phase` and `status.teammates[].phase` — per-pod state -- `status.estimatedCostUsd` — budget tracker output (see [Operations](operations.md)) -- `status.consolidatedBranch` — populated when `onComplete: push-branch` runs -- `status.conditions` — Kubernetes-style conditions array +- `status.lead.phase` and `status.teammates[].phase`. Per-pod state +- `status.estimatedCostUsd`. Budget tracker output (see [Operations](operations.md)) +- `status.consolidatedBranch`. Populated when `onComplete: push-branch` runs +- `status.conditions`. Kubernetes-style conditions array ## AgentTeamTemplate -A reusable team blueprint. Does not run on its own — it sits inert until an `AgentTeamRun` references it. +A reusable team blueprint. Does not run on its own. It sits inert until an `AgentTeamRun` references it. ```yaml apiVersion: claude.amcheste.io/v1alpha1 @@ -166,7 +166,7 @@ graph TD style D fill:#e1f5ff,stroke:#0288d1 ``` -The Template+Run pattern shines when you want the same team shape (same lead prompt, same teammate roles) parameterised by repo, branch, or per-run prompt overrides. For a one-off job, the indirection is overhead — just write an `AgentTeam` directly. +The Template+Run pattern shines when you want the same team shape (same lead prompt, same teammate roles) parameterised by repo, branch, or per-run prompt overrides. For a one-off job, the indirection is overhead. Just write an `AgentTeam` directly. ## Worked example: security review across three repos @@ -240,12 +240,12 @@ Three concurrent reviews. One template definition. Updating the template (e.g. t ## Owner references and cascade delete -Every child resource — pods, PVCs, ConfigMaps, the init Job, per-agent ServiceAccounts and Roles — has an owner reference to the `AgentTeam`. Deleting the `AgentTeam` cascades to all of them via Kubernetes garbage collection. +Every child resource. Pods, PVCs, ConfigMaps, the init Job, per-agent ServiceAccounts and Roles. Has an owner reference to the `AgentTeam`. Deleting the `AgentTeam` cascades to all of them via Kubernetes garbage collection. If the team was created by an `AgentTeamRun`, that adds another layer: deleting the `AgentTeamRun` cascades to the `AgentTeam` (which then cascades to everything else). One `kubectl delete agentteamrun` is sufficient teardown. ## Where to look next -- [Coordination protocol](coordination.md) — how the agents actually talk to each other -- [Operations](operations.md) — budget, RBAC, and observability -- [API reference (coming in v0.7.0)](../reference/index.md) — every field, every type, every default +- [Coordination protocol](coordination.md). How the agents actually talk to each other +- [Operations](operations.md). Budget, RBAC, and observability +- [API reference (coming in v0.7.0)](../reference/index.md). Every field, every type, every default diff --git a/docs/helm-values.md b/docs/helm-values.md index 59bb39a..793e1cf 100644 --- a/docs/helm-values.md +++ b/docs/helm-values.md @@ -69,7 +69,7 @@ The operator pod is single-replica and lightweight by default. Bump limits if yo ## Storage -Defaults applied to PVCs the operator creates per AgentTeam. **Required:** the storage class must support `ReadWriteMany` for multi-pod teams (NFS, EFS, CephFS) — see [ARCHITECTURE.md § Storage Requirements](../ARCHITECTURE.md#storage-requirements). +Defaults applied to PVCs the operator creates per AgentTeam. **Required:** the storage class must support `ReadWriteMany` for multi-pod teams (NFS, EFS, CephFS). See [ARCHITECTURE.md § Storage Requirements](../ARCHITECTURE.md#storage-requirements). | Key | Default | Description | |---|---|---| @@ -77,7 +77,7 @@ Defaults applied to PVCs the operator creates per AgentTeam. **Required:** the s | `storage.teamStateSize` | `5Gi` | Size of the team-state PVC (mailboxes + task list). | | `storage.repoSize` | `20Gi` | Size of the per-team repo PVC (clones + worktrees). | -## Metrics — Service + ServiceMonitor +## Metrics. Service + ServiceMonitor | Key | Default | Description | |---|---|---| @@ -88,7 +88,7 @@ Defaults applied to PVCs the operator creates per AgentTeam. **Required:** the s | `metrics.serviceMonitor.interval` | `30s` | Prometheus scrape interval. | | `metrics.serviceMonitor.additionalLabels` | `{}` | Extra labels on the ServiceMonitor. Match your Prometheus CR's selector — e.g. `{release: kube-prometheus-stack}`. | -## Metrics — Grafana dashboard +## Metrics. Grafana dashboard Renders a ConfigMap holding a 10-panel Grafana dashboard for Claude team observability. With kube-prometheus-stack, the Grafana sidecar auto-imports any ConfigMap carrying the configured label. diff --git a/docs/how-to/index.md b/docs/how-to/index.md index 007e678..1c2cde6 100644 --- a/docs/how-to/index.md +++ b/docs/how-to/index.md @@ -1,14 +1,14 @@ # How-to guides -Recipes for solving specific operational tasks. These assume you already have kagents installed and at least a basic working AgentTeam — if not, start with the [Getting Started tutorial](../tutorials/getting-started.md). +Recipes for solving specific operational tasks. These assume you already have kagents installed and at least a basic working AgentTeam. If not, start with the [Getting Started tutorial](../tutorials/getting-started.md). ## Install Cloud-specific install paths covering the ReadWriteMany storage configuration that's the actual deployment friction point on each cloud: -- **[Install on Amazon EKS](install/eks.md)** — EFS CSI driver + EFS file system + Access Points -- **[Install on Google GKE](install/gke.md)** — Filestore CSI driver + Filestore instance -- **[Install on Azure AKS](install/aks.md)** — Azure Files CSI driver + Premium NFS share +- **[Install on Amazon EKS](install/eks.md)**. EFS CSI driver + EFS file system + Access Points +- **[Install on Google GKE](install/gke.md)**. Filestore CSI driver + Filestore instance +- **[Install on Azure AKS](install/aks.md)**. Azure Files CSI driver + Premium NFS share Each guide ends with the same `make mailbox-smoke-test` verification step. @@ -16,9 +16,9 @@ Each guide ends with the same `make mailbox-smoke-test` verification step. Day-to-day operational tasks once kagents is running: -- **[Expose the dashboard](operate/expose-dashboard.md)** — port-forward for dev, Ingress with basic auth for prod, oauth2-proxy for corporate SSO, namespace-scoping -- **[Configure shared storage](operate/shared-storage.md)** — sizing the team-state / repo / output PVCs, backup strategies per cloud backend, performance tuning recipes -- **[Set budget alerts](operate/budget-alerts.md)** — per-team `budgetLimit`, chart-wide default, webhook events to Slack/PagerDuty, Prometheus alert rules +- **[Expose the dashboard](operate/expose-dashboard.md)**. Port-forward for dev, Ingress with basic auth for prod, oauth2-proxy for corporate SSO, namespace-scoping +- **[Configure shared storage](operate/shared-storage.md)**. Sizing the team-state / repo / output PVCs, backup strategies per cloud backend, performance tuning recipes +- **[Set budget alerts](operate/budget-alerts.md)**. Per-team `budgetLimit`, chart-wide default, webhook events to Slack/PagerDuty, Prometheus alert rules ## Looking for something else? diff --git a/docs/how-to/install/aks.md b/docs/how-to/install/aks.md index 6b1ebf1..10a0c7e 100644 --- a/docs/how-to/install/aks.md +++ b/docs/how-to/install/aks.md @@ -8,7 +8,7 @@ This guide walks you from a working AKS cluster to a running kagents operator ba - `kubectl` configured against the cluster - `helm` 3.14+ - `az` CLI authenticated with the subscription that owns the cluster -- The cluster's resource group and node resource group — `az aks show -g -n ` shows them +- The cluster's resource group and node resource group. `az aks show -g -n ` shows them ## 1. Verify the Azure Files CSI driver is enabled @@ -105,7 +105,7 @@ Azure Files Premium (FileStorage SKU) is billed by **provisioned capacity** per - **Price**: ~$0.16/GiB-month for Premium NFS in most regions, plus tiny per-operation fees. - **Network**: free within the same Azure region. -A 100 GiB Premium share is **~$16/month**. That's enough for tens of concurrent teams' worth of mailbox state. For larger teams or longer retention, scale capacity up — Azure Files Premium auto-scales IOPS proportional to provisioned size. +A 100 GiB Premium share is **~$16/month**. That's enough for tens of concurrent teams' worth of mailbox state. For larger teams or longer retention, scale capacity up. Azure Files Premium auto-scales IOPS proportional to provisioned size. The honest range for a small production install is **$15–$50/month** depending on how aggressively you scale capacity for performance. @@ -127,10 +127,10 @@ The honest range for a small production install is **$15–$50/month** depending Azure Files NFS without `nconnect=4` can be 2-3x slower than expected. Add the mount option in the StorageClass and recreate any pods using existing PVCs to pick it up. ??? warning "Cannot use Standard or Premium_ZRS SKU" - Only `Premium_LRS` supports NFS. Standard SMB shares technically support RWX but the file-locking semantics don't work for the mailbox protocol — use Premium NFS. + Only `Premium_LRS` supports NFS. Standard SMB shares technically support RWX but the file-locking semantics don't work for the mailbox protocol. Use Premium NFS. ## Where to look next -- [Resource model](../../explanation/resources.md) — the CRDs you'll be writing -- [Coordination protocol](../../explanation/coordination.md) — why RWX matters in detail -- [Operations](../../explanation/operations.md) — budget, RBAC, observability for the running operator +- [Resource model](../../explanation/resources.md). The CRDs you'll be writing +- [Coordination protocol](../../explanation/coordination.md). Why RWX matters in detail +- [Operations](../../explanation/operations.md). Budget, RBAC, observability for the running operator diff --git a/docs/how-to/install/eks.md b/docs/how-to/install/eks.md index 83db26c..71a0e62 100644 --- a/docs/how-to/install/eks.md +++ b/docs/how-to/install/eks.md @@ -8,7 +8,7 @@ This guide walks you from a working EKS cluster to a running kagents operator ba - `kubectl` configured against the cluster - `helm` 3.14+ - `aws` CLI authenticated with permissions to create EFS file systems and IAM policies -- The cluster's VPC ID and the security group used by your worker nodes — `aws eks describe-cluster --name ` shows them +- The cluster's VPC ID and the security group used by your worker nodes. `aws eks describe-cluster --name ` shows them ## 1. Install the EFS CSI driver @@ -119,7 +119,7 @@ A passing run looks like: PASS StorageClass=nfs AccessMode=ReadWriteMany RoundTripMs=842 ``` -If `AccessMode` reports `ReadWriteOnce` or the test fails to schedule the second pod, your StorageClass isn't actually advertising RWX — re-check step 3. +If `AccessMode` reports `ReadWriteOnce` or the test fails to schedule the second pod, your StorageClass isn't actually advertising RWX. Re-check step 3. ## Cost notes @@ -127,7 +127,7 @@ EFS is billed by storage GB-month + provisioned throughput. For a typical kagent - **Storage**: 1-5 GiB per team. At ~$0.30/GiB-month (Standard storage class), expect $0.50–$2/month for storage. - **Throughput**: in `elastic` mode you pay per byte read/written (~$0.01/GiB). Idle teams cost nothing; active teams during a busy period might generate a few GiB of traffic per day. -- **Per-mount cost**: nothing — EFS mount targets are free. +- **Per-mount cost**: nothing. EFS mount targets are free. The honest range for a small production install is **$5–$30/month**. For larger scale see the [EFS pricing page](https://aws.amazon.com/efs/pricing/). @@ -147,6 +147,6 @@ The honest range for a small production install is **$5–$30/month**. For large ## Where to look next -- [Resource model](../../explanation/resources.md) — the CRDs you'll be writing -- [Coordination protocol](../../explanation/coordination.md) — why RWX matters in detail -- [Operations](../../explanation/operations.md) — budget, RBAC, observability for the running operator +- [Resource model](../../explanation/resources.md). The CRDs you'll be writing +- [Coordination protocol](../../explanation/coordination.md). Why RWX matters in detail +- [Operations](../../explanation/operations.md). Budget, RBAC, observability for the running operator diff --git a/docs/how-to/install/gke.md b/docs/how-to/install/gke.md index ea0584b..25509d7 100644 --- a/docs/how-to/install/gke.md +++ b/docs/how-to/install/gke.md @@ -8,7 +8,7 @@ This guide walks you from a working GKE cluster to a running kagents operator ba - `kubectl` configured against the cluster - `helm` 3.14+ - `gcloud` CLI authenticated with the project that owns the cluster -- The cluster's VPC network and region — `gcloud container clusters describe ` shows them +- The cluster's VPC network and region. `gcloud container clusters describe ` shows them ## 1. Enable the Filestore CSI driver @@ -30,7 +30,7 @@ kubectl get pods -n kube-system -l k8s-app=gcp-filestore-csi-driver ## 2. Create the StorageClass -The driver supports dynamic provisioning, so you don't need to create a Filestore instance manually — the CSI driver creates one when the first PVC binds. +The driver supports dynamic provisioning, so you don't need to create a Filestore instance manually. The CSI driver creates one when the first PVC binds. ```yaml title="storageclass-filestore.yaml" apiVersion: storage.k8s.io/v1 @@ -84,7 +84,7 @@ A passing run reports the effective StorageClass and AccessMode: PASS StorageClass=nfs AccessMode=ReadWriteMany RoundTripMs=623 ``` -The first `make mailbox-smoke-test` run on Filestore takes a few minutes — Filestore instance provisioning is the slow step (~3-5 min). Subsequent test runs reuse the instance and complete in under 30s. +The first `make mailbox-smoke-test` run on Filestore takes a few minutes. Filestore instance provisioning is the slow step (~3-5 min). Subsequent test runs reuse the instance and complete in under 30s. ## Cost notes @@ -94,7 +94,7 @@ Filestore is billed by provisioned capacity per hour, not actual usage: - **Premium tier (SSD)**: ~$0.30/GiB-month. Same 1 TiB minimum. - **Enterprise tier (HA, regional)**: ~$0.60/GiB-month. 2.5 TiB minimum. -Note that **each PVC creates a new Filestore instance by default** with this StorageClass config. If you're running many teams, this gets expensive fast — at least one instance per PVC times the 1 TiB minimum. +Note that **each PVC creates a new Filestore instance by default** with this StorageClass config. If you're running many teams, this gets expensive fast. At least one instance per PVC times the 1 TiB minimum. For multi-team production use, set `volumeHandle` on a manually-provisioned shared Filestore instance and use sub-directory provisioning instead. See [GKE's Filestore docs](https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/filestore-csi-driver) for the multi-PVC pattern. @@ -103,7 +103,7 @@ The honest range for a small production install with one shared Filestore instan ## Common gotchas ??? warning "PVC stuck in `Pending` with `does not satisfy capacity`" - Filestore instances have a 1 TiB minimum size. The kagents chart's default `storage.teamStateSize` is `5Gi`, but Filestore will round it up to the tier minimum. The PVC binds successfully — the warning resolves once provisioning completes (3-5 min). + Filestore instances have a 1 TiB minimum size. The kagents chart's default `storage.teamStateSize` is `5Gi`, but Filestore will round it up to the tier minimum. The PVC binds successfully. The warning resolves once provisioning completes (3-5 min). ??? warning "`failed to create filestore instance: insufficient quota`" Filestore instances count against a project-wide quota. `gcloud compute regions describe ` shows current usage. Request a quota increase via the GCP console. @@ -116,6 +116,6 @@ The honest range for a small production install with one shared Filestore instan ## Where to look next -- [Resource model](../../explanation/resources.md) — the CRDs you'll be writing -- [Coordination protocol](../../explanation/coordination.md) — why RWX matters in detail -- [Operations](../../explanation/operations.md) — budget, RBAC, observability for the running operator +- [Resource model](../../explanation/resources.md). The CRDs you'll be writing +- [Coordination protocol](../../explanation/coordination.md). Why RWX matters in detail +- [Operations](../../explanation/operations.md). Budget, RBAC, observability for the running operator diff --git a/docs/how-to/operate/budget-alerts.md b/docs/how-to/operate/budget-alerts.md index 7354438..fb9c612 100644 --- a/docs/how-to/operate/budget-alerts.md +++ b/docs/how-to/operate/budget-alerts.md @@ -20,7 +20,7 @@ spec: budgetLimit: "10.00" # USD ``` -There's no grace period — the team stops the moment the estimate crosses. The estimate is conservative-to-the-low-side (~50K input + 5K output tokens per agent per minute is a rough ballpark), so set the limit with **2x headroom** over what you actually want to spend. +There's no grace period. The team stops the moment the estimate crosses. The estimate is conservative-to-the-low-side (~50K input + 5K output tokens per agent per minute is a rough ballpark), so set the limit with **2x headroom** over what you actually want to spend. ## Chart-wide default @@ -33,11 +33,11 @@ helm upgrade kagents \ --set defaultBudgetLimit=15.00 ``` -This is a safety net, not a recommendation — every team should set its own `budgetLimit` based on the work it's doing. The default exists to prevent a misconfigured team from running unbounded. +This is a safety net, not a recommendation. Every team should set its own `budgetLimit` based on the work it's doing. The default exists to prevent a misconfigured team from running unbounded. ## Webhook events on threshold crossings -The operator fires a `budget.warning` webhook event when a team's estimated cost crosses **80% of its `budgetLimit`** — useful as an early warning before the hard stop fires. +The operator fires a `budget.warning` webhook event when a team's estimated cost crosses **80% of its `budgetLimit`**. Useful as an early warning before the hard stop fires. ### Configure the webhook URL @@ -109,8 +109,8 @@ PagerDuty's [Events API v2](https://developer.pagerduty.com/docs/events-api-v2/o For teams that already have a Prometheus + Alertmanager stack, alert directly on the metrics the chart exposes. The relevant series: -- `claude_team_cost_usd{team_name=...}` — current estimated cost -- `claude_team_budget_remaining_usd{team_name=...}` — `limit - cost` +- `claude_team_cost_usd{team_name=...}`. Current estimated cost +- `claude_team_budget_remaining_usd{team_name=...}`. `limit - cost` ### Alert: budget about to be exceeded @@ -186,6 +186,6 @@ If the estimate is consistently 50% low, double your `budgetLimit` headroom. If ## Where to look next -- [Operations explanation](../../explanation/operations.md) — how the budget is computed in detail -- [Expose the dashboard](expose-dashboard.md) — visual cost view per team -- [Configure shared storage](shared-storage.md) — the other recurring cost on a kagents install +- [Operations explanation](../../explanation/operations.md). How the budget is computed in detail +- [Expose the dashboard](expose-dashboard.md). Visual cost view per team +- [Configure shared storage](shared-storage.md). The other recurring cost on a kagents install diff --git a/docs/how-to/operate/expose-dashboard.md b/docs/how-to/operate/expose-dashboard.md index 00a4c59..35a0ecb 100644 --- a/docs/how-to/operate/expose-dashboard.md +++ b/docs/how-to/operate/expose-dashboard.md @@ -1,6 +1,6 @@ # Expose the dashboard -The dashboard ships with kagents but is **off by default** — installing the chart alone gives you the controller and CRDs only. This guide walks through enabling it and exposing it for the three most common scenarios. +The dashboard ships with kagents but is **off by default**. Installing the chart alone gives you the controller and CRDs only. This guide walks through enabling it and exposing it for the three most common scenarios. For why the dashboard is off by default and what it can show, see the [Operations explanation](../../explanation/operations.md). @@ -39,7 +39,7 @@ kubectl port-forward -n claude-teams-system svc/kagents-dashboard 8080:8080 Open http://localhost:8080. You'll see the team list view; click any team for the detail page with live SSE updates. -`port-forward` is fine for dev but is a single-user tunnel through your local kubeconfig — don't rely on it for shared access. +`port-forward` is fine for dev but is a single-user tunnel through your local kubeconfig. Don't rely on it for shared access. ## Scenario 2: production (Ingress with basic auth) @@ -100,11 +100,11 @@ The pattern: 2. Point your Ingress at oauth2-proxy instead of the dashboard 3. Configure oauth2-proxy's `--upstream` flag to forward authenticated requests to `http://kagents-dashboard:8080` -This is a standard pattern with extensive documentation in the oauth2-proxy project. The dashboard itself doesn't need to change — it stays on the internal Service, and oauth2-proxy handles all authentication and group/role checks before requests reach it. +This is a standard pattern with extensive documentation in the oauth2-proxy project. The dashboard itself doesn't need to change. It stays on the internal Service, and oauth2-proxy handles all authentication and group/role checks before requests reach it. ## Scoping the dashboard to one namespace -By default the dashboard sees AgentTeams in **every** namespace (a `ClusterRoleBinding` grants read across the cluster). To restrict it to a single namespace — e.g. when teams in different namespaces belong to different tenants: +By default the dashboard sees AgentTeams in **every** namespace (a `ClusterRoleBinding` grants read across the cluster). To restrict it to a single namespace, e.g. when teams in different namespaces belong to different tenants: ```bash helm upgrade kagents \ @@ -127,10 +127,10 @@ Once the dashboard is reachable, deploy a quick test team and open the detail vi kubectl apply -n dev-agents -f config/samples/auth-refactor-team.yaml ``` -The list view should show the team. Click in — the detail page streams live status updates via SSE; killing a teammate pod with `kubectl delete pod ...` should cause the page to redraw within a second or two. +The list view should show the team. Click in. The detail page streams live status updates via SSE; killing a teammate pod with `kubectl delete pod ...` should cause the page to redraw within a second or two. ## Where to look next -- [Operations explanation](../../explanation/operations.md) — what the dashboard's metrics and alerts look like -- [Configure shared storage](shared-storage.md) — sizing and tuning the PVC backends -- [Set budget alerts](budget-alerts.md) — wiring webhook alerts on cost overruns +- [Operations explanation](../../explanation/operations.md). What the dashboard's metrics and alerts look like +- [Configure shared storage](shared-storage.md). Sizing and tuning the PVC backends +- [Set budget alerts](budget-alerts.md). Wiring webhook alerts on cost overruns diff --git a/docs/how-to/operate/shared-storage.md b/docs/how-to/operate/shared-storage.md index 72bb39d..038b403 100644 --- a/docs/how-to/operate/shared-storage.md +++ b/docs/how-to/operate/shared-storage.md @@ -36,7 +36,7 @@ spec: ## Backup -For most use cases the team-state PVC can be discarded — the mailbox is intermediate state, and finished teams' artifacts live elsewhere (in the git remote or in the Cowork output PVC). For the cases where you do want backups: +For most use cases the team-state PVC can be discarded. The mailbox is intermediate state, and finished teams' artifacts live elsewhere (in the git remote or in the Cowork output PVC). For the cases where you do want backups: ### EFS (EKS) @@ -90,7 +90,7 @@ The dominant workload is small synchronous writes (mailbox JSON updates) and sma ### EFS -- **Throughput mode**: `elastic` is the right default — pay per byte, scale automatically. Switch to `provisioned` only if you measure consistent saturation in CloudWatch's `BurstCreditBalance` metric. +- **Throughput mode**: `elastic` is the right default. Pay per byte, scale automatically. Switch to `provisioned` only if you measure consistent saturation in CloudWatch's `BurstCreditBalance` metric. - **Performance mode**: `generalPurpose` for <7,000 file ops/sec total across all teams (the typical case). `maxIO` only if you exceed that; it adds 1-3ms latency per op which hurts mailbox round-trips. - **Mount options**: defaults are fine. The CSI driver applies `nfsvers=4.1, rsize=1048576, wsize=1048576` by default. @@ -101,7 +101,7 @@ The dominant workload is small synchronous writes (mailbox JSON updates) and sma ### Azure Files (Premium NFS) -- **Mount option `nconnect=4`** is the single biggest performance win. Without it, expect 2-3x slower mailbox round-trips. Set it in the StorageClass — see the [AKS install guide](../install/aks.md#3-create-the-storageclass). +- **Mount option `nconnect=4`** is the single biggest performance win. Without it, expect 2-3x slower mailbox round-trips. Set it in the StorageClass. See the [AKS install guide](../install/aks.md#3-create-the-storageclass). - **Provisioned IOPS**: Azure Files Premium gives baseline IOPS proportional to provisioned size (1 IOPS per GiB). For a 100 GiB share, you get ~100 IOPS baseline + bursting. Raise capacity for more IOPS, not for more storage you don't need. ## Monitoring storage health @@ -112,10 +112,10 @@ Use the Prometheus metrics the chart exposes (see the [Operations explanation](. - **Filestore**: `nfs/server/operation_count`, `nfs/server/free_bytes_percent` in Cloud Monitoring - **Azure Files**: `Transactions`, `SuccessE2ELatency` in Azure Monitor -A sudden spike in operation count without a corresponding rise in active teams usually indicates a stuck-poll loop in one team — `kubectl describe agentteam ` to investigate. +A sudden spike in operation count without a corresponding rise in active teams usually indicates a stuck-poll loop in one team. `kubectl describe agentteam ` to investigate. ## Where to look next -- [Coordination protocol](../../explanation/coordination.md) — what the storage is actually carrying -- [Set budget alerts](budget-alerts.md) — wiring cost overruns into your alert pipeline -- [Expose the dashboard](expose-dashboard.md) — visual storage-load view +- [Coordination protocol](../../explanation/coordination.md). What the storage is actually carrying +- [Set budget alerts](budget-alerts.md). Wiring cost overruns into your alert pipeline +- [Expose the dashboard](expose-dashboard.md). Visual storage-load view diff --git a/docs/index.md b/docs/index.md index 44dd9f0..cc46df3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,13 +35,13 @@ helm install kagents \ --- - One `AgentTeam` CRD declares roles, budget, quality gates, and coordination topology. `AgentTeamTemplate` lets you reuse common team patterns — "3-agent security review," "fullstack feature team" — with one-line instantiation. + One `AgentTeam` CRD declares roles, budget, quality gates, and coordination topology. `AgentTeamTemplate` lets you reuse common team patterns. "3-agent security review," "fullstack feature team". With one-line instantiation. - :material-kubernetes:{ .lg .middle } **K8s as coordination fabric** --- - ServiceAccounts scope what each agent pod can touch. RWX PVCs hold the shared mailboxes. RBAC enforces per-agent capability boundaries. The cluster does the coordination work — kagents just wires it up. + ServiceAccounts scope what each agent pod can touch. RWX PVCs hold the shared mailboxes. RBAC enforces per-agent capability boundaries. The cluster does the coordination work. Kagents just wires it up. - :material-recycle-variant:{ .lg .middle } **Dogfooded** @@ -61,7 +61,7 @@ helm install kagents \ - :material-cog: **[How-to guides](how-to/index.md)** - Recipes for specific operational tasks — install on a cloud, expose the dashboard, tune budgets. + Recipes for specific operational tasks. Install on a cloud, expose the dashboard, tune budgets. - :material-book-open-variant: **[Reference](reference/index.md)** @@ -69,7 +69,7 @@ helm install kagents \ - :material-lightbulb: **[Explanation](explanation/index.md)** - How and why kagents works the way it does — the architecture, the design tradeoffs. + How and why kagents works the way it does. The architecture, the design tradeoffs. @@ -85,6 +85,6 @@ helm install kagents \ - :material-presentation:{ .lg .middle } **Talk** - *Reconciling Agent Teams: A Kubernetes Operator for Claude Code* — KubeCon NA 2026 (submitted). + *Reconciling Agent Teams: A Kubernetes Operator for Claude Code*. KubeCon NA 2026 (submitted). diff --git a/docs/reference/index.md b/docs/reference/index.md index 26b7e3f..887d193 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,14 +1,14 @@ # Reference -The lookup tables — every CRD field, every Helm value, every CLI flag, with no narrative wrapping. +The lookup tables. Every CRD field, every Helm value, every CLI flag, with no narrative wrapping. ## Pages -- **[API reference](api/index.md)** — auto-generated field-by-field detail for `AgentTeam`, `AgentTeamTemplate`, and `AgentTeamRun`. Regenerated from the kubebuilder markers in `api/v1alpha1/` on every site build via `make docs-api`. +- **[API reference](api/index.md)**. Auto-generated field-by-field detail for `AgentTeam`, `AgentTeamTemplate`, and `AgentTeamRun`. Regenerated from the kubebuilder markers in `api/v1alpha1/` on every site build via `make docs-api`. ## Coming next -- **Helm chart values** — every chart value documented with defaults and production override recipes (will migrate from the existing in-repo [`docs/helm-values.md`](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md)) +- **Helm chart values**. Every chart value documented with defaults and production override recipes (will migrate from the existing in-repo [`docs/helm-values.md`](https://github.com/amcheste/claude-teams-operator/blob/main/docs/helm-values.md)) ## Looking for a tutorial or recipe? diff --git a/docs/tutorials/getting-started.md b/docs/tutorials/getting-started.md index 8b98cd3..6d89051 100644 --- a/docs/tutorials/getting-started.md +++ b/docs/tutorials/getting-started.md @@ -6,7 +6,7 @@ This tutorial walks you from a fresh laptop to a running AgentTeam in about 15 m - A small Cowork-mode AgentTeam that researches a topic and writes a summary file - The know-how to inspect what's happening with `kubectl` and the dashboard -You don't need any cloud accounts or external services — everything runs on your laptop. +You don't need any cloud accounts or external services. Everything runs on your laptop. ## Prerequisites @@ -31,7 +31,7 @@ cd claude-teams-operator make kind-create ``` -This creates a Kind cluster named `claude-teams` with a local-path storage class aliased as `nfs`. On a single-node cluster every pod runs on the same node, so a hostPath volume is visible to all pods — that's our RWX-equivalent for laptop testing. +This creates a Kind cluster named `claude-teams` with a local-path storage class aliased as `nfs`. On a single-node cluster every pod runs on the same node, so a hostPath volume is visible to all pods. That's our RWX-equivalent for laptop testing. !!! note "Production deployments need a real RWX backend" For real multi-node clusters you'll need NFS, EFS, Filestore, or Azure Files. The Kind setup is a single-node convenience, not the production story. See the *Concept: file-based mailbox protocol* page (coming in v0.7.0) for why. @@ -77,7 +77,7 @@ Replace `sk-ant-...` with your actual key from [console.anthropic.com](https://c ## 4. Apply your first AgentTeam -This is a small Cowork-mode team — no git repo, just an output volume. The lead coordinates a single writer agent that produces a Markdown file. +This is a small Cowork-mode team. No git repo, just an output volume. The lead coordinates a single writer agent that produces a Markdown file. ```yaml title="hello-team.yaml" apiVersion: claude.amcheste.io/v1alpha1 @@ -185,18 +185,18 @@ make kind-delete ## What you just did -A real Kubernetes operator just orchestrated two Claude Code instances communicating via a shared filesystem to produce real output, with K8s primitives doing the coordination work — RWX PVC for the mailbox, ServiceAccounts for per-agent identity, owner references for cleanup. No custom protocol, no orchestrator service, no daemon outside the cluster. +A real Kubernetes operator just orchestrated two Claude Code instances communicating via a shared filesystem to produce real output, with K8s primitives doing the coordination work. RWX PVC for the mailbox, ServiceAccounts for per-agent identity, owner references for cleanup. No custom protocol, no orchestrator service, no daemon outside the cluster. ## Where to go next -- **[How-to guides](../how-to/index.md)** — install on a real cloud, expose the dashboard, set budget alerts -- **[Reference](../reference/index.md)** — every CRD field and Helm value documented -- **[Explanation](../explanation/index.md)** — how the file-based mailbox protocol actually works under the hood +- **[How-to guides](../how-to/index.md)**. Install on a real cloud, expose the dashboard, set budget alerts +- **[Reference](../reference/index.md)**. Every CRD field and Helm value documented +- **[Explanation](../explanation/index.md)**. How the file-based mailbox protocol actually works under the hood ## Common errors ??? warning "`PVCs stuck in Pending`" - The operator requires a ReadWriteMany-capable StorageClass. On a Kind cluster, `make kind-create` sets one up under the alias `nfs`. If you're using your own cluster, check `kubectl get sc` — there must be one named `nfs` (or you need to pass `--set storage.storageClassName=` when installing the chart). + The operator requires a ReadWriteMany-capable StorageClass. On a Kind cluster, `make kind-create` sets one up under the alias `nfs`. If you're using your own cluster, check `kubectl get sc`. There must be one named `nfs` (or you need to pass `--set storage.storageClassName=` when installing the chart). ??? warning "`Pod stuck in CrashLoopBackOff`" Check the agent pod logs: `kubectl logs -n dev-agents hello-team-writer`. The most common cause is a missing or invalid Anthropic API key. Re-create the Secret with `kubectl create secret generic anthropic-api-key --namespace dev-agents --from-literal=ANTHROPIC_API_KEY=... --dry-run=client -o yaml | kubectl apply -f -`. diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index ad15ef6..b076289 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -1,12 +1,12 @@ # Tutorials -Step-by-step lessons that take you from zero to a working AgentTeam. Read these top-to-bottom — they assume you're new to the project. +Step-by-step lessons that take you from zero to a working AgentTeam. Read these top-to-bottom. They assume you're new to the project. ## Available tutorials -- **[Getting started](getting-started.md)** — install kagents on a Kind cluster and run your first AgentTeam end-to-end. ~15 minutes. +- **[Getting started](getting-started.md)**. Install kagents on a Kind cluster and run your first AgentTeam end-to-end. ~15 minutes. -More tutorials will be added as the project matures. Have a use case you'd like a tutorial for — a security review team, a doc-generation team, multi-cluster fan-out? [Open a Discussion](https://github.com/amcheste/claude-teams-operator/discussions/categories/ideas) and tell us about it. +More tutorials will be added as the project matures. Have a use case you'd like a tutorial for. A security review team, a doc-generation team, multi-cluster fan-out? [Open a Discussion](https://github.com/amcheste/claude-teams-operator/discussions/categories/ideas) and tell us about it. ## Looking for something else? From f06bf14d075ade617c0684fae2c1bd8bd85b4339 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 17:07:09 -0400 Subject: [PATCH 20/28] chore: declare No-Linear-Issue trailer for CI The brand-alignment sweep in 25fcf54 has no associated Linear ticket. The validate.yml Linear-ref check requires either an AMC-N reference or a No-Linear-Issue trailer. PR body was updated with the trailer; this empty commit re-triggers the synchronize event on the workflow. No-Linear-Issue: brand-alignment doc cleanup, no associated Linear ticket Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> From bfcbe659ce57d1c5e75a2427f0c53029a943bf07 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 17:11:27 -0400 Subject: [PATCH 21/28] ci: remove Linear Issue Reference gate from validate.yml Linear is used for major project tracking only, not every PR. Requiring an AMC-N reference (or the No-Linear-Issue opt-out trailer) on every PR adds friction without proportional value for the work that doesn't map to a Linear ticket. Particularly ritualistic for bot-authored PRs, which categorically don't have tickets. The original incident that prompted this check (AMC-71/72/73/74 staying in Backlog after their work shipped, 2026-05-02) is real, but the failure mode is acceptable: Linear tickets for major projects that DO get referenced will still auto-close on merge. Tickets that nobody referenced just need a manual close, which is the same operation the team is already doing for tickets that never had PRs. Removes the `linear-ref` job (49 lines) from validate.yml. No other changes; all other validation jobs (Lint, Tests, Build Operator Image, Commit Lint, Detect Changed Files, Semgrep) keep firing as before. No-Linear-Issue: removing the gate itself, the gate would be self-referentially required to allow this PR to merge Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .github/workflows/validate.yml | 49 ---------------------------------- 1 file changed, 49 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ba9fb18..3df25c7 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -200,52 +200,3 @@ jobs: echo "" echo "Tag the next release with: \`./scripts/bump-version.sh $BUMP\`" } | tee -a "$GITHUB_STEP_SUMMARY" - - # ── Linear Issue Reference (PRs to develop) ─────────────────────────────── - # Enforces that PRs reference a Linear issue (AMC-N) so the Linear ↔ GitHub - # sync auto-closes the right ticket on merge. Without this, Linear tickets - # stay in Backlog after their work ships (verified 2026-05-02 with - # AMC-71/72/73/74). Skipped for PRs to main (release-promotion rolls up - # commits already checked at the develop merge) and for dependabot. - linear-ref: - name: Linear Issue Reference - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' && github.base_ref != 'main' && github.actor != 'dependabot[bot]' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Check PR body or commits reference an AMC-N issue - env: - PR_BODY: ${{ github.event.pull_request.body }} - BASE_SHA: ${{ github.event.pull_request.base.sha }} - HEAD_SHA: ${{ github.sha }} - run: | - BUFFER="$PR_BODY"$'\n'"$(git log --format='%B' "${BASE_SHA}..${HEAD_SHA}")" - - if echo "$BUFFER" | grep -qE 'AMC-[0-9]+'; then - ID=$(echo "$BUFFER" | grep -oE 'AMC-[0-9]+' | head -1) - echo "✓ References Linear issue: $ID" - exit 0 - fi - - if echo "$BUFFER" | grep -qE '^No-Linear-Issue:'; then - REASON=$(echo "$BUFFER" | grep -E '^No-Linear-Issue:' | head -1 | sed 's/^No-Linear-Issue:[[:space:]]*//') - echo "✓ Explicit opt-out (No-Linear-Issue: $REASON)" - exit 0 - fi - - echo "✗ This PR does not reference a Linear issue." - echo "" - echo "Add an AMC-N reference to the PR body (e.g. 'Fixes AMC-123')" - echo "or any commit message, so the Linear ↔ GitHub sync auto-closes" - echo "the right ticket on merge." - echo "" - echo "Linear project: https://linear.app/amcheste/project/claude-teams-operator-32aab082f36b" - echo "" - echo "If this PR genuinely is not associated with a Linear issue (e.g." - echo "trivial doc fix, dependency bump, release promotion), add a" - echo "trailer to the PR body:" - echo " No-Linear-Issue: " - exit 1 From 0620c9fe8bee27dbdb1c83d5c65b6678305d0887 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 21:18:34 +0000 Subject: [PATCH 22/28] chore: Bump release-drafter/release-drafter from 6.4.0 to 7.3.0 Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 6.4.0 to 7.3.0. - [Release notes](https://github.com/release-drafter/release-drafter/releases) - [Commits](https://github.com/release-drafter/release-drafter/compare/6a93d829887aa2e0748befe2e808c66c0ec6e4c7...c2e2804cc59f45f57076a99af580d0fedb697927) --- updated-dependencies: - dependency-name: release-drafter/release-drafter dependency-version: 7.2.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release-drafter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 1d7df64..44ef789 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -16,6 +16,6 @@ jobs: name: Update Release Draft runs-on: ubuntu-latest steps: - - uses: release-drafter/release-drafter@6a93d829887aa2e0748befe2e808c66c0ec6e4c7 # v6 + - uses: release-drafter/release-drafter@c2e2804cc59f45f57076a99af580d0fedb697927 # v7.3.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e3d837661bdb3e455e9391077aa30e2228578e54 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 17:35:27 -0400 Subject: [PATCH 23/28] fix(controller): pass error as printf arg, not format string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `recordEvent` is a printf-shaped helper (`messageFmt string, args ...interface{}`). The `create-pr` failure path was passing `err.Error()` directly as `messageFmt`, so any `%` in the error text would be misinterpreted as a format directive and produce garbled event messages. This is also what trips `go vet`'s printf analyzer on Go 1.26 — every other `recordEvent` call in the file already uses the `"%v", err` pattern, so just line up with the rest. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- internal/controller/agentteam_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/agentteam_controller.go b/internal/controller/agentteam_controller.go index 70e1636..d21ce28 100644 --- a/internal/controller/agentteam_controller.go +++ b/internal/controller/agentteam_controller.go @@ -1494,7 +1494,7 @@ func (r *AgentTeamReconciler) executeOnComplete(ctx context.Context, team *claud case "create-pr": if err := r.executeCreatePR(ctx, team); err != nil { log.Error(err, "create-pr failed") - r.recordEvent(team, corev1.EventTypeWarning, "PRCreationFailed", err.Error()) + r.recordEvent(team, corev1.EventTypeWarning, "PRCreationFailed", "%v", err) return err } case "push-branch": From 21d72aa02ba3359de69840c227019ea6ba55215c Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 17:51:52 -0400 Subject: [PATCH 24/28] chore(ci): remove auto-assign-PR-creator workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workflow assigned every PR creator to their own PR, which: - Conflicts with the global "leave assignee unset by default" rule — bot-authored PRs already route to @amcheste via CODEOWNERS, so auto-assigning the same person on every PR is noise. - Fails outright on bot-authored PRs: GitHub's REST API refuses `addAssignees` for agent accounts when authenticated with an App installation token ("Assigning agents is not supported…"). Deleting the workflow restores green CI on bot PRs and lines the repo up with the documented assignee policy. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .github/workflows/auto-assign.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/auto-assign.yml diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml deleted file mode 100644 index 08c25d5..0000000 --- a/.github/workflows/auto-assign.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Auto-assign PR creator -on: - pull_request: - types: [opened] - -jobs: - assign: - runs-on: ubuntu-latest - permissions: - pull-requests: write - issues: write - steps: - - uses: actions/github-script@v9 - with: - script: | - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - assignees: [context.payload.pull_request.user.login] - }); From 7e0590ca66a0c0ab0924e8a7b6dc106189d0bf31 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 18:14:11 -0400 Subject: [PATCH 25/28] chore(brand): table-cell em-dash sweep + scorecard fixes Second pass following PR #224. Three small things: 1. Em-dash sweep in table cells (12 occurrences across 9 files). PR #224's mechanical sweep protected lines starting with `|` as code structure, but table cells contain rendered prose and the voice rule applies. Each em-dash replaced contextually: period-and-capitalize, comma, or word-rewrite. The one remaining em-dash in CONTRIBUTING.md:198 is inside a Go ```go code block (a kubebuilder marker comment example), correctly preserved. 2. scorecard.yml: publish_results now uses `github.ref_name == github.event.repository.default_branch` instead of hardcoded `refs/heads/main`. Same fix as engineering-handbook PR #16 and repo-template PR #11. The default here is `main`, so no behavior change today, but the workflow is now correct regardless of which branch is set as default (consistent with the family). 3. scorecard.yml: github/codeql-action/upload-sarif SHA pin replaced. The old pin (d4b3ca9fa7f69d38bfcd667bdc45bc373d16277e) is an imposter commit per OSSF Scorecard's anti-supply-chain check, which is why every Scorecard run since at least 2026-04-29 has failed with `error sending scorecard results to webapp: ... imposter commit ... does not belong to github/codeql-action/upload-sarif`. New pin (68bde559dea0fdcac2102bfdf6230c5f70eb485e) is the real v4 tag commit, verified via gh api. The push trigger also now fires on `develop` in addition to `main`, matching the family pattern. publish_results gating keeps the public score canonical to default-branch state. No-Linear-Issue: brand-alignment follow-up to PR #224 Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .github/workflows/scorecard.yml | 6 +++--- ARCHITECTURE.md | 2 +- README.md | 6 +++--- SECURITY.md | 2 +- docs/explanation/resources.md | 2 +- docs/helm-values.md | 2 +- docs/how-to/install/aks.md | 4 ++-- docs/how-to/operate/shared-storage.md | 2 +- docs/reference/api/index.md | 2 +- docs/tutorials/getting-started.md | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3716ce2..54bcfb7 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -5,7 +5,7 @@ on: schedule: - cron: '30 1 * * 1' # Every Monday at 01:30 UTC push: - branches: [main] + branches: [main, develop] workflow_dispatch: permissions: read-all @@ -28,7 +28,7 @@ jobs: with: results_file: results.sarif results_format: sarif - publish_results: ${{ github.ref == 'refs/heads/main' }} + publish_results: ${{ github.ref_name == github.event.repository.default_branch }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: @@ -36,6 +36,6 @@ jobs: path: results.sarif retention-days: 5 - - uses: github/codeql-action/upload-sarif@d4b3ca9fa7f69d38bfcd667bdc45bc373d16277e # v4 + - uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 with: sarif_file: results.sarif diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index db18a59..5dce9d5 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -104,7 +104,7 @@ The operator itself has no opinion about the CSI driver. It asks for a PVC with | Platform | Driver | Notes | |----------|--------|-------| | Kind (multi-node dev) | `nfs-ganesha/nfs-server-provisioner` | Installed by `hack/kind-setup.sh` as StorageClass `nfs`. Real RWX over an in-cluster NFS server. | -| Kind (single-node acceptance) | `rancher.io/local-path` under the `nfs` StorageClass alias | Installed by `hack/acceptance-setup.sh`. See "Single-node fallback" — not true RWX. | +| Kind (single-node acceptance) | `rancher.io/local-path` under the `nfs` StorageClass alias | Installed by `hack/acceptance-setup.sh`. See "Single-node fallback"; not true RWX. | | Amazon EKS | [EFS CSI driver](https://github.com/kubernetes-sigs/aws-efs-csi-driver) | StorageClass pointing at an EFS file system. RWX natively. | | Google GKE | [Filestore CSI driver](https://cloud.google.com/filestore/docs/csi-driver) | Enable the Filestore CSI add-on; Filestore instances advertise RWX. | | Azure AKS | [Azure Files CSI driver](https://learn.microsoft.com/azure/aks/azure-files-csi) | SMB or NFS-protocol file shares; both support RWX. | diff --git a/README.md b/README.md index d6db3f3..982b5e0 100644 --- a/README.md +++ b/README.md @@ -298,12 +298,12 @@ This README is the entry point. For deeper dives, every topic lives in a dedicat | Document | Read when you want to… | |----------|-----------------------| -| [ARCHITECTURE.md](ARCHITECTURE.md) | Understand how the operator models Agent Teams — phase state machine, PVC layout, RWX storage backends, coordination protocol, key design tradeoffs. | +| [ARCHITECTURE.md](ARCHITECTURE.md) | Understand how the operator models Agent Teams. Phase state machine, PVC layout, RWX storage backends, coordination protocol, key design tradeoffs. | | [TESTING.md](TESTING.md) | See the test strategy (unit / integration / acceptance / E2E), how to run each suite, and what each one actually verifies. | | [CONTRIBUTING.md](CONTRIBUTING.md) | Set up a dev environment, run the full build/test loop, follow the branch + PR workflow, and walk through "How to add a new reconciler feature." | -| [docs/helm-values.md](docs/helm-values.md) | Tune the Helm chart — every value documented with defaults and production override recipes. | +| [docs/helm-values.md](docs/helm-values.md) | Tune the Helm chart. Every value documented with defaults and production override recipes. | | [SECURITY.md](SECURITY.md) | Report a vulnerability or review the project's security policy. | -| [KUBECON.md](KUBECON.md) | See the talk framing and "interesting problems" log — useful context for why specific architectural choices were made. | +| [KUBECON.md](KUBECON.md) | See the talk framing and "interesting problems" log. Useful context for why specific architectural choices were made. | ## Development diff --git a/SECURITY.md b/SECURITY.md index 96690f6..49cb413 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,7 +7,7 @@ Only the latest released version is actively maintained. Security fixes are issu | Version | Supported | |---------|:---------:| | Latest release | ✅ | -| Older releases | ❌ — please upgrade | +| Older releases | ❌ please upgrade | The latest release is the most recent `v*` tag on https://github.com/amcheste/claude-teams-operator/releases. diff --git a/docs/explanation/resources.md b/docs/explanation/resources.md index 97d5c40..873be1b 100644 --- a/docs/explanation/resources.md +++ b/docs/explanation/resources.md @@ -4,7 +4,7 @@ kagents manages three custom resources. Most users only ever touch the first one | CRD | What it represents | When to use | |-----|-------------------|-------------| -| `AgentTeam` | A specific team running a specific job | One-off work — refactor, code review, report draft | +| `AgentTeam` | A specific team running a specific job | One-off work, e.g. refactor, code review, report draft | | `AgentTeamTemplate` | A reusable team blueprint | You'll instantiate the same team shape against many inputs | | `AgentTeamRun` | One instantiation of a template | Used together with `AgentTeamTemplate` | diff --git a/docs/helm-values.md b/docs/helm-values.md index 793e1cf..b160b1a 100644 --- a/docs/helm-values.md +++ b/docs/helm-values.md @@ -86,7 +86,7 @@ Defaults applied to PVCs the operator creates per AgentTeam. **Required:** the s | `metrics.serviceMonitor.enabled` | `false` | **Production:** set to `true` when running with kube-prometheus-stack. Requires the `monitoring.coreos.com` CRDs. | | `metrics.serviceMonitor.namespace` | `""` | Namespace for the ServiceMonitor. Defaults to the release namespace. Set this to the Prometheus namespace when using a namespace-scoped selector. | | `metrics.serviceMonitor.interval` | `30s` | Prometheus scrape interval. | -| `metrics.serviceMonitor.additionalLabels` | `{}` | Extra labels on the ServiceMonitor. Match your Prometheus CR's selector — e.g. `{release: kube-prometheus-stack}`. | +| `metrics.serviceMonitor.additionalLabels` | `{}` | Extra labels on the ServiceMonitor. Match your Prometheus CR's selector, e.g. `{release: kube-prometheus-stack}`. | ## Metrics. Grafana dashboard diff --git a/docs/how-to/install/aks.md b/docs/how-to/install/aks.md index 10a0c7e..f6509ec 100644 --- a/docs/how-to/install/aks.md +++ b/docs/how-to/install/aks.md @@ -30,8 +30,8 @@ Azure Files supports two protocols, and only one is suitable for kagents: | Protocol | RWX? | Use? | |----------|------|------| -| **NFS v4.1** | ✅ Yes | **Yes — use this.** | -| **SMB** | ⚠️ Partial | No — POSIX semantics on the agent's mailbox writes don't work reliably. | +| **NFS v4.1** | ✅ Yes | **Yes, use this.** | +| **SMB** | ⚠️ Partial | No. POSIX semantics on the agent's mailbox writes don't work reliably. | NFS shares require a Premium storage account (FileStorage SKU). The good news is Premium pricing is reasonable for the small share sizes kagents needs. diff --git a/docs/how-to/operate/shared-storage.md b/docs/how-to/operate/shared-storage.md index 038b403..29f8d68 100644 --- a/docs/how-to/operate/shared-storage.md +++ b/docs/how-to/operate/shared-storage.md @@ -10,7 +10,7 @@ The chart's default sizes are conservative; raise them if your teams handle larg | Volume | Default Helm value | Default size | When to raise | |--------|-------------------:|-------------:|---------------| -| Team state (mailboxes + tasks) | `storage.teamStateSize` | `5Gi` | Almost never — mailbox JSON is tiny. 5 GiB holds thousands of messages. | +| Team state (mailboxes + tasks) | `storage.teamStateSize` | `5Gi` | Almost never. Mailbox JSON is tiny. 5 GiB holds thousands of messages. | | Repo (coding mode) | `storage.repoSize` | `20Gi` | If your monorepo + per-teammate worktrees together exceed 20 GiB. Each worktree is roughly the size of your `git checkout`. For a 5-teammate team on a 4 GiB repo, 20 GiB might tip over. | | Output (Cowork mode) | `spec.workspace.output.size` (per-team) | n/a | Set per AgentTeam based on expected artifact volume. 1 GiB is fine for documents; raise for image/video output. | diff --git a/docs/reference/api/index.md b/docs/reference/api/index.md index f6ad1b4..f9af454 100644 --- a/docs/reference/api/index.md +++ b/docs/reference/api/index.md @@ -321,7 +321,7 @@ _Appears in:_ | `onComplete` _string_ | OnComplete determines what happens when the team finishes. | notify | Enum: [create-pr push-branch notify none]
| | `pullRequest` _[PullRequestSpec](#pullrequestspec)_ | PullRequest configures PR creation when onComplete is "create-pr". | | Optional: \{\}
| | `approvalGates` _[ApprovalGateSpec](#approvalgatespec) array_ | ApprovalGates pause execution before specified events until human approval is recorded.
Grant approval by annotating the AgentTeam: kubectl annotate agentteam approved.claude.amcheste.io/=true | | Optional: \{\}
| -| `maxRestarts` _integer_ | MaxRestarts bounds how many times each teammate pod may be re-spawned
after a Failed phase before the team itself is marked Failed. The lead
pod is not subject to this limit — a lead crash always fails the team. | 3 | Minimum: 0
Optional: \{\}
| +| `maxRestarts` _integer_ | MaxRestarts bounds how many times each teammate pod may be re-spawned
after a Failed phase before the team itself is marked Failed. The lead
pod is not subject to this limit; a lead crash always fails the team. | 3 | Minimum: 0
Optional: \{\}
| | `githubTokenSecret` _string_ | GitHubTokenSecret names a Secret in the team's namespace carrying a
GitHub token under the key GITHUB_TOKEN. Used by OnComplete=create-pr
(and OnComplete=push-branch, once implemented) to authenticate against
the GitHub REST API. | | Optional: \{\}
| | `prTitleTemplate` _string_ | PRTitleTemplate overrides the title template used by OnComplete=create-pr.
Available variables: .TeamName, .Namespace. When empty, falls back to
Spec.Lifecycle.PullRequest.TitleTemplate, then to the default
"claude-teams: \{\{.TeamName\}\}". | | Optional: \{\}
| | `gitCredentialsSecret` _string_ | GitCredentialsSecret names a Secret in the team's namespace carrying git
push credentials. The Secret must contain either 'ssh-privatekey' or
'token'. Used by OnComplete=push-branch (and OnComplete=create-pr when
push-branch runs ahead of it). Falls back to Spec.Repository.CredentialsSecret
when unset, so teams that already configured clone credentials with push
scope don't need to duplicate. | | Optional: \{\}
| diff --git a/docs/tutorials/getting-started.md b/docs/tutorials/getting-started.md index 6d89051..b0e533d 100644 --- a/docs/tutorials/getting-started.md +++ b/docs/tutorials/getting-started.md @@ -16,7 +16,7 @@ You don't need any cloud accounts or external services. Everything runs on your | [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) | 0.25+ | Single-node Kubernetes for dev | | [kubectl](https://kubernetes.io/docs/tasks/tools/) | 1.28+ | Interact with the cluster | | [helm](https://helm.sh/docs/intro/install/) | 3.14+ | Install the operator chart | -| [Anthropic API key](https://console.anthropic.com/) | — | Required for agents to actually call Claude | +| [Anthropic API key](https://console.anthropic.com/) | (any) | Required for agents to actually call Claude | You'll also need the kagents repo cloned locally so you can use the included `make kind-create` setup script (which provisions a Kind cluster with the NFS-style RWX storage the operator needs): From 520ff4d4c34060a9d96aad511db670045ee0b80e Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 18:24:36 -0400 Subject: [PATCH 26/28] chore: release v0.7.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps VERSION and Helm chart to 0.7.0, cutting the "Documentation Site" milestone release. v0.7.0 delivers: - kagents project brand introduction across README, badges, and KUBECON.md, plus the new mascot/banner imagery (#172, #199, #220, #224). - A full mkdocs-material documentation site under docs/ with CI deploy workflow (#194): Diátaxis nav, polished homepage, and a Getting Started tutorial (#195); three concept pages covering resources, coordination, and operations (#196); how-to guides for cloud installs (AKS, EKS, GKE) and operational recipes — budget alerts, dashboard exposure, shared storage (#197); and an auto-generated API reference produced from kubebuilder markers via crd-ref-docs, with a `make docs-api` target that always reinstalls the pinned version (#198). - Community baseline (#201): Code of Conduct, polished CONTRIBUTING, hardened SECURITY policy, expanded issue templates. - KubeCon NA 2026 CFP draft (#168) and submission tracking — the talk has now been submitted ahead of the May 31 deadline. - Dependency bumps: sigs.k8s.io/controller-runtime 0.23.3 → 0.24.0 (#215, which transitively raises k8s.io/api, apimachinery, client-go to 0.36.0 and the go directive to 1.26.0), github.com/onsi/ginkgo/v2 2.28.2 → 2.28.3 (#216), actions/setup-python 5 → 6 (#219), release-drafter 6.4.0 → 7.3.0 (#218). - Toolchain alignment for the Go 1.26 vet rules: fix(controller): the create-pr error path now passes the error as a printf argument instead of as the format string itself (#226), which also defends against `%` characters in error messages producing garbled Kubernetes events. - CI cleanup: remove the auto-assign-PR-creator workflow (#227) which conflicted with the documented assignee policy and failed outright on bot-authored PRs, and drop the Linear Issue Reference gate from validate.yml (#225). Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- VERSION | 2 +- charts/claude-teams-operator/Chart.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index a918a2a..faef31a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.0 +0.7.0 diff --git a/charts/claude-teams-operator/Chart.yaml b/charts/claude-teams-operator/Chart.yaml index 1ee0f84..1ab984e 100644 --- a/charts/claude-teams-operator/Chart.yaml +++ b/charts/claude-teams-operator/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: claude-teams-operator description: A Kubernetes operator for running Claude Code Agent Teams as distributed pods type: application -version: 0.6.0 -appVersion: "0.6.0" +version: 0.7.0 +appVersion: "0.7.0" keywords: - claude - ai From 9e8e36c0ec33716e2d16100a6660f77e9e9b0812 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 18:34:09 -0400 Subject: [PATCH 27/28] =?UTF-8?q?chore(brand):=20em-dash=20sweep=20?= =?UTF-8?q?=E2=80=94=20sync=20MaxRestarts=20doc=20comment=20to=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #228 changed the regenerated docs/reference/api/index.md to swap an em-dash for a semicolon in the MaxRestarts description, but missed the matching Go doc comment in api/v1alpha1/agentteam_types.go. The `make docs-api` regeneration step in CI then pulled the em-dash back from source and the Lint job's diff check failed. Sync the source comment to match the rendered docs. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- api/v1alpha1/agentteam_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1alpha1/agentteam_types.go b/api/v1alpha1/agentteam_types.go index f3b4804..ec86c6c 100644 --- a/api/v1alpha1/agentteam_types.go +++ b/api/v1alpha1/agentteam_types.go @@ -306,7 +306,7 @@ type LifecycleSpec struct { // MaxRestarts bounds how many times each teammate pod may be re-spawned // after a Failed phase before the team itself is marked Failed. The lead - // pod is not subject to this limit — a lead crash always fails the team. + // pod is not subject to this limit; a lead crash always fails the team. // +kubebuilder:default=3 // +kubebuilder:validation:Minimum=0 // +optional From d1810e4a76c01ee71d50f68dc5bec129adbe635d Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 18:37:36 -0400 Subject: [PATCH 28/28] chore(brand): regenerate CRD manifests for em-dash sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `make manifests` regenerates the CRD YAMLs from the Go type descriptions, so the MaxRestarts comment change also has to be reflected in: - charts/claude-teams-operator/crds/claude.amcheste.io_agentteams.yaml - charts/claude-teams-operator/crds/claude.amcheste.io_agentteamruns.yaml - charts/claude-teams-operator/crds/claude.amcheste.io_agentteamtemplates.yaml - config/crd/bases/claude.amcheste.io_agentteams.yaml - config/crd/bases/claude.amcheste.io_agentteamruns.yaml - config/crd/bases/claude.amcheste.io_agentteamtemplates.yaml Pure regeneration — no logic change. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .../crds/claude.amcheste.io_agentteamruns.yaml | 2 +- .../crds/claude.amcheste.io_agentteams.yaml | 2 +- .../crds/claude.amcheste.io_agentteamtemplates.yaml | 2 +- config/crd/bases/claude.amcheste.io_agentteamruns.yaml | 2 +- config/crd/bases/claude.amcheste.io_agentteams.yaml | 2 +- config/crd/bases/claude.amcheste.io_agentteamtemplates.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/claude-teams-operator/crds/claude.amcheste.io_agentteamruns.yaml b/charts/claude-teams-operator/crds/claude.amcheste.io_agentteamruns.yaml index 0d574a7..62c1c6e 100644 --- a/charts/claude-teams-operator/crds/claude.amcheste.io_agentteamruns.yaml +++ b/charts/claude-teams-operator/crds/claude.amcheste.io_agentteamruns.yaml @@ -262,7 +262,7 @@ spec: description: |- MaxRestarts bounds how many times each teammate pod may be re-spawned after a Failed phase before the team itself is marked Failed. The lead - pod is not subject to this limit — a lead crash always fails the team. + pod is not subject to this limit; a lead crash always fails the team. format: int32 minimum: 0 type: integer diff --git a/charts/claude-teams-operator/crds/claude.amcheste.io_agentteams.yaml b/charts/claude-teams-operator/crds/claude.amcheste.io_agentteams.yaml index 72acd2a..13f07a8 100644 --- a/charts/claude-teams-operator/crds/claude.amcheste.io_agentteams.yaml +++ b/charts/claude-teams-operator/crds/claude.amcheste.io_agentteams.yaml @@ -307,7 +307,7 @@ spec: description: |- MaxRestarts bounds how many times each teammate pod may be re-spawned after a Failed phase before the team itself is marked Failed. The lead - pod is not subject to this limit — a lead crash always fails the team. + pod is not subject to this limit; a lead crash always fails the team. format: int32 minimum: 0 type: integer diff --git a/charts/claude-teams-operator/crds/claude.amcheste.io_agentteamtemplates.yaml b/charts/claude-teams-operator/crds/claude.amcheste.io_agentteamtemplates.yaml index c0135fb..a9ff55d 100644 --- a/charts/claude-teams-operator/crds/claude.amcheste.io_agentteamtemplates.yaml +++ b/charts/claude-teams-operator/crds/claude.amcheste.io_agentteamtemplates.yaml @@ -155,7 +155,7 @@ spec: description: |- MaxRestarts bounds how many times each teammate pod may be re-spawned after a Failed phase before the team itself is marked Failed. The lead - pod is not subject to this limit — a lead crash always fails the team. + pod is not subject to this limit; a lead crash always fails the team. format: int32 minimum: 0 type: integer diff --git a/config/crd/bases/claude.amcheste.io_agentteamruns.yaml b/config/crd/bases/claude.amcheste.io_agentteamruns.yaml index 0d574a7..62c1c6e 100644 --- a/config/crd/bases/claude.amcheste.io_agentteamruns.yaml +++ b/config/crd/bases/claude.amcheste.io_agentteamruns.yaml @@ -262,7 +262,7 @@ spec: description: |- MaxRestarts bounds how many times each teammate pod may be re-spawned after a Failed phase before the team itself is marked Failed. The lead - pod is not subject to this limit — a lead crash always fails the team. + pod is not subject to this limit; a lead crash always fails the team. format: int32 minimum: 0 type: integer diff --git a/config/crd/bases/claude.amcheste.io_agentteams.yaml b/config/crd/bases/claude.amcheste.io_agentteams.yaml index 72acd2a..13f07a8 100644 --- a/config/crd/bases/claude.amcheste.io_agentteams.yaml +++ b/config/crd/bases/claude.amcheste.io_agentteams.yaml @@ -307,7 +307,7 @@ spec: description: |- MaxRestarts bounds how many times each teammate pod may be re-spawned after a Failed phase before the team itself is marked Failed. The lead - pod is not subject to this limit — a lead crash always fails the team. + pod is not subject to this limit; a lead crash always fails the team. format: int32 minimum: 0 type: integer diff --git a/config/crd/bases/claude.amcheste.io_agentteamtemplates.yaml b/config/crd/bases/claude.amcheste.io_agentteamtemplates.yaml index c0135fb..a9ff55d 100644 --- a/config/crd/bases/claude.amcheste.io_agentteamtemplates.yaml +++ b/config/crd/bases/claude.amcheste.io_agentteamtemplates.yaml @@ -155,7 +155,7 @@ spec: description: |- MaxRestarts bounds how many times each teammate pod may be re-spawned after a Failed phase before the team itself is marked Failed. The lead - pod is not subject to this limit — a lead crash always fails the team. + pod is not subject to this limit; a lead crash always fails the team. format: int32 minimum: 0 type: integer