Skip to content
Open
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,22 @@ kelos run -p "Fix the bug" --agent-config my-config
- `plugins` are mounted as plugin directories and passed via `--plugin-dir`.
- `mcpServers` are written to the agent's native MCP configuration. Supports `stdio`, `http`, and `sse` transport types.

To source agent settings from a Kanon repository instead, set `spec.kanon`.
The referenced repository must contain `kanon.yaml` at its root. This mode is
mutually exclusive with inline `agentsMD`, `plugins`, `skills`, and
`mcpServers` fields.

```yaml
apiVersion: kelos.dev/v1alpha2
kind: AgentConfig
metadata:
name: kanon-config
spec:
kanon:
repo: https://github.com/example/kanon-config.git
ref: main
```

See the [full AgentConfig spec](docs/reference.md#agentconfig) for plugins, skills, and agents configuration.

> Browse all ready-to-apply YAML manifests in the [`examples/`](examples/) directory.
Expand Down
26 changes: 26 additions & 0 deletions api/v1alpha2/agentconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@ import (
)

// AgentConfigSpec defines the desired state of AgentConfig.
//
// +kubebuilder:validation:XValidation:rule="!has(self.kanon) || ((!has(self.agentsMD) || size(self.agentsMD) == 0) && (!has(self.plugins) || size(self.plugins) == 0) && (!has(self.skills) || size(self.skills) == 0) && (!has(self.mcpServers) || size(self.mcpServers) == 0))",message="kanon is mutually exclusive with agentsMD, plugins, skills, and mcpServers"
type AgentConfigSpec struct {
// AgentsMD is written to the agent's instruction file
// (e.g., ~/.claude/CLAUDE.md for Claude Code).
// This is additive and does not overwrite the repo's own instruction files.
// +optional
AgentsMD string `json:"agentsMD,omitempty"`

// Kanon references a Kanon source repository. When set, the Task pod
// clones the repository and applies its root kanon.yaml to the agent's
// user-level configuration before the agent starts.
// Mutually exclusive with AgentsMD, Plugins, Skills, and MCPServers.
// +optional
Kanon *KanonSourceSpec `json:"kanon,omitempty"`

// Plugins defines plugin bundles containing skills and agents.
// Each plugin is mounted as a directory and installed using the
// agent's native mechanism (e.g., --plugin-dir for Claude Code,
Expand All @@ -33,6 +42,23 @@ type AgentConfigSpec struct {
MCPServers []MCPServerSpec `json:"mcpServers,omitempty"`
}

// KanonSourceSpec references a Kanon source repository.
type KanonSourceSpec struct {
// Repo is the Git repository URL containing kanon.yaml at its root.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Repo string `json:"repo"`

// Ref is an optional branch, tag, or commit to check out.
// +optional
Ref string `json:"ref,omitempty"`

// SecretRef references a Secret containing GITHUB_TOKEN for cloning
// private Kanon source repositories.
// +optional
SecretRef *SecretReference `json:"secretRef,omitempty"`
}

// PluginSpec defines a plugin bundle containing skills and agents.
type PluginSpec struct {
// Name is the plugin name. Used as the plugin directory name
Expand Down
22 changes: 13 additions & 9 deletions api/v1alpha2/task_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,36 +170,38 @@ type PodOverrides struct {
// ContainerSecurityContext fields.
// Container names must not use the Kelos-reserved "kelos-" prefix or
// collide with a built-in init container name: git-clone, remote-setup,
// branch-setup, workspace-files, plugin-setup, skills-install.
// branch-setup, workspace-files, plugin-setup, skills-install,
// kanon-source.
// +optional
// +kubebuilder:validation:MaxItems=8
// +listType=map
// +listMapKey=name
// +kubebuilder:validation:XValidation:rule="self.all(c, c.name != '')",message="extraContainers[].name must not be empty"
// +kubebuilder:validation:XValidation:rule="self.all(c, !c.name.startsWith('kelos-') && !(c.name in ['git-clone', 'remote-setup', 'branch-setup', 'workspace-files', 'plugin-setup', 'skills-install']))",message="extraContainers[].name must not use the reserved 'kelos-' prefix or a built-in init container name"
// +kubebuilder:validation:XValidation:rule="self.all(c, !c.name.startsWith('kelos-') && !(c.name in ['git-clone', 'remote-setup', 'branch-setup', 'workspace-files', 'plugin-setup', 'skills-install', 'kanon-source']))",message="extraContainers[].name must not use the reserved 'kelos-' prefix or a built-in init container name"
ExtraContainers []corev1.Container `json:"extraContainers,omitempty"`

// ExtraInitContainers is a list of additional init containers to run
// in the agent pod. They are placed after all Kelos built-in init
// containers (git-clone, remote-setup, branch-setup, workspace-files,
// plugin-setup, skills-install), ensuring the workspace is ready
// before they start. Set restartPolicy: Always for sidecar semantics
// (long-running services like databases) or leave it unset for
// one-shot init tasks.
// plugin-setup, skills-install, kanon-source), ensuring the workspace
// and AgentConfig sources are ready before they start. Set
// restartPolicy: Always for sidecar semantics (long-running services
// like databases) or leave it unset for one-shot init tasks.
// Containers can mount user-supplied volumes from the Volumes field
// as well as Kelos-managed volumes (workspace, kelos-plugin). Note
// that the workspace volume uses FSGroup-based permissions; containers
// running as a UID outside the pod's FSGroup will not have write
// access to the workspace.
// Container names must not use the Kelos-reserved "kelos-" prefix or
// collide with a built-in init container name: git-clone, remote-setup,
// branch-setup, workspace-files, plugin-setup, skills-install.
// branch-setup, workspace-files, plugin-setup, skills-install,
// kanon-source.
// +optional
// +kubebuilder:validation:MaxItems=8
// +listType=map
// +listMapKey=name
// +kubebuilder:validation:XValidation:rule="self.all(c, c.name != '')",message="extraInitContainers[].name must not be empty"
// +kubebuilder:validation:XValidation:rule="self.all(c, !c.name.startsWith('kelos-') && !(c.name in ['git-clone', 'remote-setup', 'branch-setup', 'workspace-files', 'plugin-setup', 'skills-install']))",message="extraInitContainers[].name must not use the reserved 'kelos-' prefix or a built-in init container name"
// +kubebuilder:validation:XValidation:rule="self.all(c, !c.name.startsWith('kelos-') && !(c.name in ['git-clone', 'remote-setup', 'branch-setup', 'workspace-files', 'plugin-setup', 'skills-install', 'kanon-source']))",message="extraInitContainers[].name must not use the reserved 'kelos-' prefix or a built-in init container name"
ExtraInitContainers []corev1.Container `json:"extraInitContainers,omitempty"`
}

Expand Down Expand Up @@ -241,7 +243,9 @@ type TaskSpec struct {
// AgentConfigRefs references an ordered list of AgentConfig resources.
// Configs are merged in order: agentsMD is concatenated, plugins/skills
// are appended, mcpServers are appended with later entries winning on
// name collision.
// name collision. At most one referenced AgentConfig may set spec.kanon,
// and Kanon-backed configs cannot be combined with inline AgentConfig
// fields from the same or another referenced config.
// +optional
// +kubebuilder:validation:MinItems=1
AgentConfigRefs []AgentConfigReference `json:"agentConfigRefs,omitempty"`
Expand Down
4 changes: 3 additions & 1 deletion api/v1alpha2/taskspawner_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,9 @@ type TaskTemplate struct {
// AgentConfigRefs references an ordered list of AgentConfig resources.
// Configs are merged in order: agentsMD is concatenated, plugins/skills
// are appended, mcpServers are appended with later entries winning on
// name collision.
// name collision. At most one referenced AgentConfig may set spec.kanon,
// and Kanon-backed configs cannot be combined with inline AgentConfig
// fields from the same or another referenced config.
// When set, spawned Tasks inherit this agent config reference list.
// +optional
// +kubebuilder:validation:MinItems=1
Expand Down
25 changes: 25 additions & 0 deletions api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions claude-code/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ RUN ARCH=$(dpkg --print-architecture) \

ENV PATH="/usr/local/go/bin:${PATH}"

ARG KANON_VERSION=3f2ad4b8171d46aaf7541e9efa61aef009c233e2
RUN GOBIN=/usr/local/bin go install github.com/kelos-dev/kanon@${KANON_VERSION}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The go install command leaves the Go module cache (/root/go/pkg/mod) and build cache (/root/.cache/go-build) in the final image, adding unnecessary size (~100MB+).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At claude-code/Dockerfile, line 31:

<comment>The `go install` command leaves the Go module cache (`/root/go/pkg/mod`) and build cache (`/root/.cache/go-build`) in the final image, adding unnecessary size (~100MB+).</comment>

<file context>
@@ -27,6 +27,9 @@ RUN ARCH=$(dpkg --print-architecture) \
 ENV PATH="/usr/local/go/bin:${PATH}"
 
+ARG KANON_VERSION=3f2ad4b8171d46aaf7541e9efa61aef009c233e2
+RUN GOBIN=/usr/local/bin go install github.com/kelos-dev/kanon@${KANON_VERSION}
+
 ARG CLAUDE_CODE_VERSION=2.1.177
</file context>


ARG CLAUDE_CODE_VERSION=2.1.177
RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}

Expand Down
11 changes: 11 additions & 0 deletions claude-code/kelos_entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ fs.writeFileSync(cfgPath, JSON.stringify(existing, null, 2));
'
fi

if [ -n "${KELOS_KANON_HOME:-}" ]; then
printf '\n---KELOS_KANON_APPLY_START---\n' >&2
kanon apply --yes --home "$KELOS_KANON_HOME" --agent claude
KANON_EXIT_CODE=$?
if [ "$KANON_EXIT_CODE" -ne 0 ]; then
printf '\n---KELOS_KANON_APPLY_FAILED--- exit=%s\n' "$KANON_EXIT_CODE" >&2
exit "$KANON_EXIT_CODE"
fi
printf '\n---KELOS_KANON_APPLY_DONE---\n' >&2
fi

# Run pre-agent setup command if configured. KELOS_SETUP_COMMAND is the
# JSON-encoded exec-form array from Workspace.spec.setupCommand. A non-zero
# exit aborts the task before the agent starts.
Expand Down
3 changes: 3 additions & 0 deletions codex/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ RUN ARCH=$(dpkg --print-architecture) \

ENV PATH="/usr/local/go/bin:${PATH}"

ARG KANON_VERSION=3f2ad4b8171d46aaf7541e9efa61aef009c233e2
RUN GOBIN=/usr/local/bin go install github.com/kelos-dev/kanon@${KANON_VERSION}

ARG CODEX_VERSION=0.139.0
RUN npm install -g @openai/codex@${CODEX_VERSION}

Expand Down
11 changes: 11 additions & 0 deletions codex/kelos_entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ process.stdout.write(toml);
' >>~/.codex/config.toml
fi

if [ -n "${KELOS_KANON_HOME:-}" ]; then
printf '\n---KELOS_KANON_APPLY_START---\n' >&2
kanon apply --yes --home "$KELOS_KANON_HOME" --agent codex
KANON_EXIT_CODE=$?
if [ "$KANON_EXIT_CODE" -ne 0 ]; then
printf '\n---KELOS_KANON_APPLY_FAILED--- exit=%s\n' "$KANON_EXIT_CODE" >&2
exit "$KANON_EXIT_CODE"
fi
printf '\n---KELOS_KANON_APPLY_DONE---\n' >&2
fi

# Run pre-agent setup command if configured. KELOS_SETUP_COMMAND is the
# JSON-encoded exec-form array from Workspace.spec.setupCommand. A non-zero
# exit aborts the task before the agent starts.
Expand Down
12 changes: 9 additions & 3 deletions docs/agent-image-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ Kelos sets the following reserved environment variables on agent containers:
| `KELOS_BASE_BRANCH` | The base branch (workspace `ref`) for the task | When workspace has a non-empty `ref` |
| `KELOS_AGENTS_MD` | User-level instructions from AgentConfig | When `agentConfigRefs` is set and `agentsMD` is non-empty |
| `KELOS_PLUGIN_DIR` | Path to plugin directory containing skills and agents. Each subdirectory is one plugin in the `<plugin>/skills/<skill>/SKILL.md` layout; skills.sh packages from `spec.skills` appear under the `skills-sh` plugin | When `agentConfigRefs` is set and `plugins` or `skills` is non-empty |
| `KELOS_KANON_HOME` | Path to a cloned Kanon source repository. Codex and Claude Code entrypoints run `kanon apply --yes --home "$KELOS_KANON_HOME" --agent <agent>` before starting the agent | When `agentConfigRefs` resolves to `spec.kanon` and the agent type is `codex` or `claude-code` |
| `KELOS_SETUP_COMMAND` | JSON-encoded exec-form array from `Workspace.spec.setupCommand`, executed by the entrypoint before the agent starts | When the workspace defines `setupCommand` |

> The names listed in this table are reserved for Kelos behavior. When Kelos
> sets one on a Task, `PodOverrides.Env` entries that reuse the same name are
> dropped so the built-in value wins; do not set them manually.
> `KELOS_SETUP_COMMAND` is additionally dropped from `PodOverrides.Env` even
> when the workspace does not define `setupCommand`, because it drives
> entrypoint command execution.
> `KELOS_SETUP_COMMAND` and `KELOS_KANON_HOME` are additionally dropped from
> `PodOverrides.Env` even when Kelos does not define them, because they drive
> entrypoint behavior.

The bundled agent images handle `KELOS_EFFORT` as follows:

Expand All @@ -62,6 +63,11 @@ The bundled agent images handle `KELOS_EFFORT` as follows:
- `opencode`: writes agent model options including `reasoningEffort` and provider variants where available.
- `cursor`: adds effort guidance to user-level instructions because Cursor CLI does not expose a documented effort flag.

The bundled Codex and Claude Code images include the Kanon CLI and apply
Kanon-backed AgentConfigs before running the agent. Custom images for those
agent types must include `kanon` on `PATH` and handle `KELOS_KANON_HOME` the
same way if they need to support `AgentConfig.spec.kanon`.

### 4. User ID

The agent image must be configured to run as **UID 61100**. This UID is shared
Expand Down
7 changes: 5 additions & 2 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
| `spec.podOverrides.volumeMounts` | Additional volume mounts on the agent container; names must reference either a user-supplied volume from `volumes` or a Kelos-managed volume (`workspace` or a `kelos-` volume such as `kelos-plugin`) | No |
| `spec.podOverrides.podSecurityContext` | Pod-level security context applied to the agent pod. Fields set here override Kelos defaults; `fsGroup` retains the Kelos default when unset so the agent user keeps workspace access | No |
| `spec.podOverrides.containerSecurityContext` | Security context applied to the agent container. Use to declare `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`, `readOnlyRootFilesystem: true`, etc., for PSS-restricted namespaces | No |
| `spec.podOverrides.extraContainers` | Additional containers to run alongside the agent container in the same pod (max 8). They share the pod's network namespace (reachable via `localhost`) and can mount user-supplied volumes from `volumes`. Use for sidecars such as a database for integration tests or a proxy. Names must not use the Kelos-reserved `kelos-` prefix, collide with a built-in init container name (`git-clone`, `remote-setup`, `branch-setup`, `workspace-files`, `plugin-setup`, `skills-install`), duplicate another entry, or appear in `extraInitContainers` (see [Extra Containers](#task-extra-containers) below) | No |
| `spec.podOverrides.extraContainers` | Additional containers to run alongside the agent container in the same pod (max 8). They share the pod's network namespace (reachable via `localhost`) and can mount user-supplied volumes from `volumes`. Use for sidecars such as a database for integration tests or a proxy. Names must not use the Kelos-reserved `kelos-` prefix, collide with a built-in init container name (`git-clone`, `remote-setup`, `branch-setup`, `workspace-files`, `plugin-setup`, `skills-install`, `kanon-source`), duplicate another entry, or appear in `extraInitContainers` (see [Extra Containers](#task-extra-containers) below) | No |
| `spec.podOverrides.extraInitContainers` | Additional init containers (max 8), appended after all Kelos built-in init containers so the workspace is ready before they run. Set `restartPolicy: Always` for sidecar semantics (long-running services, K8s 1.29+) or leave it unset for one-shot pre-agent setup. They can mount user-supplied volumes from `volumes` as well as Kelos-managed volumes (`workspace` or a `kelos-` volume such as `kelos-plugin`); workspace write access requires running as a UID in the pod's `fsGroup`. Same name constraints as `extraContainers` (see [Extra Containers](#task-extra-containers) below) | No |

### Pod Override Volumes
Expand Down Expand Up @@ -66,7 +66,7 @@ spec:
`spec.podOverrides.extraContainers` and `spec.podOverrides.extraInitContainers` let a Task run user-defined containers alongside the agent. Both lists accept a standard Kubernetes [Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#container-v1-core) and are subject to these constraints (validated by the API server and the controller):

- Maximum 8 entries per list.
- Container names must not use the Kelos-reserved `kelos-` prefix, and must not collide with built-in init container names: `git-clone`, `remote-setup`, `branch-setup`, `workspace-files`, `plugin-setup`, `skills-install`.
- Container names must not use the Kelos-reserved `kelos-` prefix, and must not collide with built-in init container names: `git-clone`, `remote-setup`, `branch-setup`, `workspace-files`, `plugin-setup`, `skills-install`, `kanon-source`.
- A name must not be duplicated within a list, and must not appear in both `extraContainers` and `extraInitContainers` (Kubernetes requires container names to be unique within a pod).
- `extraInitContainers` run after every Kelos built-in init container, so the cloned workspace and installed plugins are already in place. Write access to the `workspace` volume requires running as a UID in the pod's `fsGroup` (the agent UID `61100` by default).

Expand Down Expand Up @@ -240,6 +240,9 @@ GitHub Apps are preferred over PATs for production use because they offer fine-g
| Field | Description | Required |
|-------|-------------|----------|
| `spec.agentsMD` | Agent instructions written to the agent's user-level instructions file, additive with repo files. The destination depends on the agent type: `~/.claude/CLAUDE.md` (Claude Code), `~/.gemini/GEMINI.md` (Gemini), `~/.codex/AGENTS.md` (Codex), `~/.config/opencode/AGENTS.md` (OpenCode), `~/.cursor/AGENTS.md` (Cursor) | No |
| `spec.kanon.repo` | Git repository URL for a Kanon source repository containing `kanon.yaml` at the repository root. Mutually exclusive with `agentsMD`, `plugins`, `skills`, and `mcpServers`. Applied to Codex and Claude Code user-level settings before the agent starts; ignored with a Warning event for other agent types | Yes (when `kanon` is set) |
| `spec.kanon.ref` | Branch, tag, or commit to check out from the Kanon source repository | No |
| `spec.kanon.secretRef.name` | Secret containing `GITHUB_TOKEN` for cloning a private Kanon source repository | No |
| `spec.plugins[].name` | Plugin name (used as directory name and namespace) | Yes (per plugin) |
| `spec.plugins[].skills[].name` | Skill name (becomes `skills/<name>/SKILL.md`) | Yes (per skill) |
| `spec.plugins[].skills[].content` | Skill content (markdown with frontmatter) | Yes (per skill) |
Expand Down
Loading
Loading