Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ name: CI Build
on:
push:
branches: [main, "release/**"]
paths-ignore:
- "**/*.md"
paths:
- "**"
- "!**/*.md"
- "docs/env.md"
pull_request:
branches: [main, "release/**"]
paths-ignore:
- "**/*.md"
paths:
- "**"
- "!**/*.md"
- "docs/env.md"
merge_group:
workflow_dispatch:
inputs:
Expand Down Expand Up @@ -134,7 +138,6 @@ jobs:
--set controller.substrate.enabled=true
--set controller.substrate.ateApiEndpoint=dns:///api.ate-system.svc:443
--set controller.substrate.atenetRouterURL=http://atenet-router.ate-system.svc:80
--set controller.substrate.defaultWorkerPool.name=kagent-default
--set substrateWorkerPool.create=true
--set substrateWorkerPool.replicas=4
--set-string substrateWorkerPool.workerImage=ghcr.io/kagent-dev/substrate/ateom-gvisor:v${{ env.SUBSTRATE_VERSION }}
Expand All @@ -154,7 +157,7 @@ jobs:
' _
# Resolve the guest on the runner: localhost:5001 is not reachable
# from the controller pod. Substrate rewrites registry pulls on workers.
GUEST_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/sandbox-guest:${VERSION}" | awk '$1 == "Digest:" { print $2; exit }')
GUEST_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/sandbox-guest:${VERSION}" | awk '$1 == "Digest:" { print $2 }')
test -n "$GUEST_DIGEST"
export KAGENT_HELM_EXTRA_ARGS="${KAGENT_HELM_EXTRA_ARGS} --set-string controller.sandbox.guestImage.digest=${GUEST_DIGEST}"
make helm-install-provider
Expand All @@ -166,16 +169,17 @@ jobs:
working-directory: go
shell: bash
run: |
RUNTIME_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/golang-adk:${VERSION}" | awk '$1 == "Digest:" { print $2; exit }')
# Read all output so Docker does not get a broken pipe under pipefail.
RUNTIME_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/golang-adk:${VERSION}" | awk '$1 == "Digest:" { print $2 }')
test -n "$RUNTIME_DIGEST"
export KAGENT_E2E_RUNTIME_IMAGE="localhost:5001/kagent-dev/kagent/golang-adk@${RUNTIME_DIGEST}"
BYO_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/byo-a2a:${VERSION}" | awk '$1 == "Digest:" { print $2; exit }')
BYO_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/byo-a2a:${VERSION}" | awk '$1 == "Digest:" { print $2 }')
test -n "$BYO_DIGEST"
export KAGENT_E2E_BYO_IMAGE="localhost:5001/kagent-dev/kagent/byo-a2a@${BYO_DIGEST}"
CLAUDE_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/claude-harness:${VERSION}" | awk '$1 == "Digest:" { print $2; exit }')
CLAUDE_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/claude-harness:${VERSION}" | awk '$1 == "Digest:" { print $2 }')
test -n "$CLAUDE_DIGEST"
export KAGENT_E2E_CLAUDE_IMAGE="localhost:5001/kagent-dev/kagent/claude-harness@${CLAUDE_DIGEST}"
CODEX_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/codex-harness:${VERSION}" | awk '$1 == "Digest:" { print $2; exit }')
CODEX_DIGEST=$(docker buildx imagetools inspect "localhost:5001/kagent-dev/kagent/codex-harness:${VERSION}" | awk '$1 == "Digest:" { print $2 }')
test -n "$CODEX_DIGEST"
export KAGENT_E2E_CODEX_IMAGE="localhost:5001/kagent-dev/kagent/codex-harness@${CODEX_DIGEST}"
envsubst < core/test/e2e/manifests/lifecycle.yaml.tmpl | kubectl apply -f -
Expand All @@ -192,7 +196,7 @@ jobs:
export KAGENT_E2E_CLI="$PWD/core/bin/kagent-local"
envsubst < core/test/e2e/manifests/tracing.yaml.tmpl | kubectl apply -f -
export KAGENT_E2E_API_URL="$KAGENT_API_URL"
export KAGENT_LOCAL_HOST=172.17.0.1
export KAGENT_E2E_LOCAL_HOST=172.17.0.1
# TestMain keeps the receiver alive for every runtime's response flush.
export KAGENT_E2E_OTLP_LISTEN_ADDRESS=:14317
# Concurrent actors can push an earlier failure out of the final log tail.
Expand Down Expand Up @@ -608,6 +612,9 @@ jobs:
cache: true
cache-dependency-path: go/go.sum

- name: Check environment documentation
run: make env-docs-check

- name: Generate controller manifests
run: make controller-manifests

Expand Down
35 changes: 35 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# Development

The [environment variable reference](docs/env.md) is generated from
`go/core/pkg/env`. Register user-configurable settings there, including settings
consumed by Python, the UI, or standalone runtimes. Exclude controller-generated
payloads, credentials, private paths, and other internal process wiring.
Keep defaults and descriptions aligned with
their readers, then run `make env-docs`. Use `make env-docs-check` to run the same
freshness check as CI.

Register shared settings once, passing each consuming component to
`RegisterStringVar`, `RegisterBoolVar`, `RegisterIntVar`, or
`RegisterDurationVar`, for example:

```go
RegisterStringVar("KAGENT_LOG_LEVEL", "info", "Logging level.", ComponentController, ComponentCLI, ComponentAgentRuntime)
```

The reference lists shared settings under each component. `kagent env --component`
matches any registered component; JSON output includes a `components` array.

Kagent-owned settings use the `KAGENT_` prefix. Keep names defined by upstream
SDKs and tools, such as `OTEL_*`, provider credentials, and `KUBECONFIG`, unchanged.
The pre-release rename replaces the old unprefixed names: for example,
`LOG_LEVEL` becomes `KAGENT_LOG_LEVEL`, `HTTP_BIND_ADDRESS` becomes
`KAGENT_HTTP_BIND_ADDRESS`, and the Go ADK's `PORT` becomes `KAGENT_PORT`.
The old names are no longer read by kagent.

Both ADKs use `KAGENT_PORT` for their A2A listener; Python's former
`KAGENT_A2A_GRPC_ADDRESS` is removed. The controller sets `80`. Standalone
defaults remain `8080` for Go's shared HTTP/gRPC listener and `80` for Python's
gRPC listener; Python's HTTP port is configured separately with `--port`.

UI containers and Vite development use the same `KAGENT_*` inputs, documented in
`ui/.env.example`. UI build-time switches use `KAGENT_UI_VITE_*`; extension settings
use `KAGENT_UI_EXTENSION_*`. Other `KAGENT_*` settings are not exposed to the browser.

To understand how to develop for kagent, it's important to understand the architecture of the project. Please refer to the [README.md](README.md#architecture) file for an overview of the project.

When making changes to `kagent`, the most important thing is to figure out which piece of the project is affected by the change, and then make the change in the appropriate folder. Each piece of the project has its own README with more information about how to setup the development environment and run that piece of the project.
Expand Down
49 changes: 34 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,25 @@ proto-check: proto-lint proto-generate ## Regenerate protobuf artifacts and fail
exit 1; \
fi

##@ Environment documentation

ENV_DOCS_COMMAND = cd go && go run ./core/cli/cmd/kagent env --format markdown

.PHONY: env-docs
env-docs: ## Generate docs/env.md from the environment variable registry
@set -eu; generated=$$(mktemp); trap 'rm -f "$$generated"' EXIT; \
($(ENV_DOCS_COMMAND)) > "$$generated"; \
cp "$$generated" docs/env.md

.PHONY: env-docs-check
env-docs-check: ## Check docs/env.md matches the environment variable registry
@set -eu; generated=$$(mktemp); trap 'rm -f "$$generated"' EXIT; \
($(ENV_DOCS_COMMAND)) > "$$generated"; \
if ! diff -u docs/env.md "$$generated"; then \
echo "Environment documentation is out of date. Run 'make env-docs' and commit docs/env.md."; \
exit 1; \
fi

##@ Telemetry contract

include telemetry/versions.env
Expand Down Expand Up @@ -563,7 +582,7 @@ install-previous-release: ## Install the previous released kagent + kagent-crds
# Later Goose releases test previous-release behavior after the target migrations,
# data survival, schema equality, previous/current controller startup, and a
# complete application and schema rollback to the previous release.
# KAGENT_LOCAL_HOST lets the agent reach the local mock LLM.
# KAGENT_E2E_LOCAL_HOST lets the agent reach the local mock LLM.
# Prerequisite (provided by CI as a separate step; run it locally first): a kind
# cluster (make create-kind-cluster).
.PHONY: announce-upgrade-from
Expand All @@ -575,15 +594,15 @@ run-upgrade-tests: announce-upgrade-from build install-previous-release ## Test
@echo "=== Upgrade test: $(UPGRADE_FROM_VERSION) -> $(VERSION) (registry=$(DOCKER_REGISTRY)) ==="
@set -e; \
kind_gw="$$($(CONTAINER_RUNTIME) network inspect kind -f '{{range .IPAM.Config}}{{if .Gateway}}{{.Gateway}}{{"\n"}}{{end}}{{end}}' | grep -E '^[0-9]+\.' | head -1)"; \
echo "kind gateway (KAGENT_LOCAL_HOST): $$kind_gw"; \
echo "kind gateway (KAGENT_E2E_LOCAL_HOST): $$kind_gw"; \
cd go && \
RUN_UPGRADE_TESTS=true \
REPO_ROOT=$(CURDIR) \
KAGENT_LOCAL_HOST="$$kind_gw" \
UPGRADE_FROM_VERSION=$(UPGRADE_FROM_VERSION) \
VERSION=$(VERSION) \
DOCKER_REGISTRY=$(DOCKER_REGISTRY) \
KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) \
KAGENT_E2E_RUN_UPGRADE_TESTS=true \
KAGENT_E2E_REPO_ROOT=$(CURDIR) \
KAGENT_E2E_LOCAL_HOST="$$kind_gw" \
KAGENT_E2E_UPGRADE_FROM_VERSION=$(UPGRADE_FROM_VERSION) \
KAGENT_E2E_VERSION=$(VERSION) \
KAGENT_E2E_DOCKER_REGISTRY=$(DOCKER_REGISTRY) \
KAGENT_E2E_KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) \
OPENAI_API_KEY="$${OPENAI_API_KEY:-test}" \
go test ./core/test/upgrade -run TestUpgrade -count=1 -timeout=45m -v

Expand All @@ -595,12 +614,12 @@ run-rolling-upgrade-tests: UPGRADE_PREV_EXTRA_ARGS = --set controller.replicas=2
run-rolling-upgrade-tests: announce-upgrade-from build install-previous-release ## Install the previous release with 2 controller replicas, build the current images, and run the rolling upgrade e2e test
@echo "=== Rolling upgrade test: $(UPGRADE_FROM_VERSION) -> $(VERSION) (registry=$(DOCKER_REGISTRY)) ==="
cd go && \
RUN_ROLLING_UPGRADE_TESTS=true \
REPO_ROOT=$(CURDIR) \
UPGRADE_FROM_VERSION=$(UPGRADE_FROM_VERSION) \
VERSION=$(VERSION) \
DOCKER_REGISTRY=$(DOCKER_REGISTRY) \
KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) \
KAGENT_E2E_RUN_ROLLING_UPGRADE_TESTS=true \
KAGENT_E2E_REPO_ROOT=$(CURDIR) \
KAGENT_E2E_UPGRADE_FROM_VERSION=$(UPGRADE_FROM_VERSION) \
KAGENT_E2E_VERSION=$(VERSION) \
KAGENT_E2E_DOCKER_REGISTRY=$(DOCKER_REGISTRY) \
KAGENT_E2E_KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) \
OPENAI_API_KEY="$${OPENAI_API_KEY:-test}" \
go test ./core/test/upgrade -run TestRollingUpgradeCompatibility -count=1 -timeout=20m -v

Expand Down
17 changes: 15 additions & 2 deletions STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,23 @@ Go code lives in the `go/` workspace (`go/api`, `go/core`, `go/adk`). Run
HTTP calls use `http.NewRequestWithContext`. Never drop a `cancel` function.
- Context keys use unexported struct types, never strings.

### Environment variables

- Read user-configurable settings through registered accessors in `core/pkg/env`.
Register each setting once, listing every consuming component.
- Use `Get()` for the registered default and `Lookup()` when a caller must
distinguish unset values. Use boolean/integer `LookupWithError()` when invalid
input needs a diagnostic; it distinguishes invalid input from unset or empty.
- Registered booleans accept `true`, `t`, `1`, `false`, `f`, and `0`, ignoring case
and surrounding whitespace. Upstream settings with a different grammar use
string registrations and their own parser.
- Direct process reads require an explained lint exception, such as resolving
controller-generated payloads or arbitrary credential placeholders.

### Logging

- Use standard-library `log/slog`. Binaries write JSON to stderr and read the
minimum level from `LOG_LEVEL` (`debug`, `info`, `warn`, or `error`).
minimum level from `KAGENT_LOG_LEVEL` (`debug`, `info`, `warn`, or `error`).
- Carry loggers in `context.Context` with `pkg/logging`; do not add logger
parameters, package-global loggers, or `NewXxxWithLogger` constructors.
- Use `DebugContext`, `InfoContext`, `WarnContext`, or `ErrorContext` whenever
Expand Down Expand Up @@ -315,7 +328,7 @@ All new CRD API surface goes in `v1alpha3`.
Side effects and async logic belong in the reconciler (or a dedicated
controller).
- Every behavioral change to a translator **SHOULD** come with golden tests
(`UPDATE_GOLDEN=true go test ...` to regenerate).
(`KAGENT_TEST_UPDATE_GOLDEN=true go test ...` to regenerate).

### Database

Expand Down
4 changes: 3 additions & 1 deletion docs/architecture/configuration-and-compilation.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ Dedicated agent bindings are not compiled yet.
The Python ADK image's default entrypoint runs a named Python agent module. To
consume the kagent compiler's configuration, set `spec.workload.command` to
`["/.kagent/.venv/bin/kagent-adk", "static", "--host", "0.0.0.0", "--port", "8080"]`.
The compiler supplies the private A2A gRPC address separately.
The compiler sets `KAGENT_PORT=80` for private A2A traffic in both ADKs. Go uses
that port for its shared HTTP/gRPC listener; Python uses it for gRPC and keeps
the HTTP port separate.

`spec.kagent.compaction` on the Harness is runtime policy, like `spec.kagent.memory`:
it belongs to the runner that drives the root agent and is not part of the
Expand Down
15 changes: 6 additions & 9 deletions docs/architecture/oidc-proxy-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,16 @@ differs.
| `oauth2-proxy.enabled` | `false` | Install the authentication proxy |
| `oauth2-proxy.config.existingSecret` | Empty | Reference client and cookie credentials |
| `ui.auth.ssoRedirectPath` | `/oauth2/start` | Start or restart browser sign-in |
| `controller.auth.mode` | `unsecure` | Render the controller's `AUTH_MODE` environment variable |
| `controller.auth.userIdClaim` | Empty | Render `AUTH_USER_ID_CLAIM` when supplied |

The last two settings remain in the chart, but the current controller does not
consume them. Setting `controller.auth.mode: trusted-proxy` alone does not
activate `ProxyAuthenticator`, and the controller has no `--auth-mode` or
`--auth-user-id-claim` flags. A library integration must supply its own
`auth.AuthProvider` through `app.Options.Authenticator`.
Enabling the proxy does not activate controller authentication. A library
integration must supply its own `auth.AuthProvider` through
`app.Options.Authenticator`.

The UI reads `SSO_REDIRECT_PATH` from `window.environmentVariables` at runtime
and applies its configured base path. Helm supplies that value from
`ui.auth.ssoRedirectPath`. The chart allows unauthenticated access to the login
page, health checks, and the assets required to render the login page.
`ui.auth.ssoRedirectPath` through `KAGENT_UI_SSO_REDIRECT_PATH`. The chart allows
unauthenticated access to the login page, health checks, and the assets required
to render the login page.

## Trust boundary

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/sandboxes.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pin the runtime artifacts they need.

Sandbox execution is enabled by default. Lifecycle calls run on the serving API
replica; expiration workers coordinate through PostgreSQL.
Configure `SANDBOX_GUEST_IMAGE` with an `image@sha256:...` reference, or set
Configure `KAGENT_SANDBOX_GUEST_IMAGE` with an `image@sha256:...` reference, or set
`controller.sandbox.guestImage.digest` in Helm alongside its registry and
repository. The guest image is required for sandbox preparation and passes
unchanged to Substrate. The controller does not resolve tags or contact image
Expand Down
Loading
Loading