| Field | Description | Required |
|---|---|---|
spec.type |
Agent type (claude-code, codex, gemini, opencode, or cursor) |
Yes |
spec.prompt |
Task prompt for the agent | Yes |
spec.credentials.type |
api-key, oauth, or none. Use none to skip built-in credential injection (e.g., for Bedrock, Vertex AI, or Azure OpenAI credentials provided via podOverrides.env) |
Yes |
spec.credentials.secretRef.name |
Secret name with credentials (see secret format below; not required when type is none) |
Conditional |
spec.model |
Model override. The value is passed through to the agent container as KELOS_MODEL without validation, so either an agent-native shorthand accepted by the CLI (e.g., sonnet, opus for Claude Code) or a versioned ID (e.g., claude-sonnet-4-6) is valid |
No |
spec.effort |
Agent reasoning effort. The value is passed through as KELOS_EFFORT without validation. Bundled agents translate common values (minimal, low, medium, high, xhigh, max) to their native controls where supported |
No |
spec.image |
Custom agent image override (see Agent Image Interface) | No |
spec.workspaceRef.name |
Name of a Workspace resource to use | No |
spec.agentConfigRefs[].name |
Ordered AgentConfig resources to use. Configs are merged in order | No |
spec.dependsOn |
Task names that must succeed before this Task starts (creates Waiting phase) |
No |
spec.branch |
Git branch to work on; only one Task with the same branch runs at a time (mutex) | No |
spec.ttlSecondsAfterFinished |
Auto-delete task after N seconds (0 for immediate) | No |
spec.podFailurePolicy |
Kubernetes Job pod failure policy copied to Job.spec.podFailurePolicy. If omitted, Kelos leaves it unset and Kubernetes default Job failure handling applies |
No |
spec.podOverrides.labels |
Additional labels to apply to the Job and its Pod. Merged with built-in labels; built-in labels take precedence on conflict | No |
spec.podOverrides.resources |
CPU/memory requests and limits for the agent container | No |
spec.podOverrides.activeDeadlineSeconds |
Maximum duration in seconds before the agent pod is terminated | No |
spec.podOverrides.env |
Additional environment variables (built-in vars take precedence on conflict) | No |
spec.podOverrides.nodeSelector |
Node selection labels to constrain which nodes run agent pods | No |
spec.podOverrides.tolerations |
Tolerations for the agent pod; use with nodeSelector or affinity to target dedicated node pools (e.g., GPU nodes, agent-specific pools) |
No |
spec.podOverrides.affinity |
Node, pod, and pod-anti-affinity rules. Use for spreading agents across nodes or expressing scheduling preferences beyond nodeSelector |
No |
spec.podOverrides.imagePullSecrets |
Secrets used to pull container images from private registries. Required when the agent image or any init container image is in a private registry | No |
spec.podOverrides.serviceAccountName |
Service account name for the agent pod; use with workload identity systems (IRSA, GKE Workload Identity, Azure) | No |
spec.podOverrides.volumes |
Additional volumes to attach to the agent pod. Names must not be workspace or use the Kelos-reserved kelos- prefix |
No |
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 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 below) |
No |
Task.spec.podOverrides.volumes and TaskSpawner.spec.taskTemplate.podOverrides.volumes are for user-managed volumes. User-supplied volume names must not be workspace or start with kelos-; Kelos reserves those names for controller-managed pod wiring.
If an existing manifest uses a user volume name such as kelos-cache, rename that volume and every matching Task.spec.podOverrides.volumeMounts, Task.spec.podOverrides.extraContainers[].volumeMounts, or Task.spec.podOverrides.extraInitContainers[].volumeMounts reference to a non-reserved name such as cache. Apply the same rename under TaskSpawner.spec.taskTemplate.podOverrides for spawned task templates.
spec.podFailurePolicy accepts Kubernetes Job podFailurePolicy rules except FailIndex, which only applies to indexed Jobs and is rejected for Kelos Task Jobs. Kelos copies the field as a complete policy; it does not merge in default rules. Rule order matters because Kubernetes stops evaluating after the first match.
When the field is omitted, Kelos leaves Job.spec.podFailurePolicy unset. To ignore infrastructure disruptions while still failing the Job on non-zero container exits, set the policy explicitly:
spec:
podFailurePolicy:
rules:
- action: Ignore
onPodConditions:
- type: DisruptionTarget
status: "True"
- action: FailJob
onExitCodes:
operator: NotIn
values: [0]spec.podOverrides.extraContainers and spec.podOverrides.extraInitContainers let a Task run user-defined containers alongside the agent. Both lists accept a standard Kubernetes Container 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. - A name must not be duplicated within a list, and must not appear in both
extraContainersandextraInitContainers(Kubernetes requires container names to be unique within a pod). extraInitContainersrun after every Kelos built-in init container, so the cloned workspace and installed plugins are already in place. Write access to theworkspacevolume requires running as a UID in the pod'sfsGroup(the agent UID61100by default).
Example — run a PostgreSQL sidecar for integration tests alongside the agent (reachable at localhost:5432):
apiVersion: kelos.dev/v1alpha2
kind: Task
metadata:
name: integration-test
spec:
type: claude-code
prompt: Run the integration test suite against the local PostgreSQL instance.
credentials:
type: api-key
secretRef:
name: claude-credentials
podOverrides:
extraContainers:
- name: postgres
image: postgres:16
env:
- name: POSTGRES_PASSWORD
value: testpass
ports:
- containerPort: 5432When a Task has dependsOn, its prompt field supports Go text/template syntax for referencing upstream results. The template data has a single key .Deps containing a map keyed by dependency Task name:
| Variable | Type | Description |
|---|---|---|
{{index .Deps "<name>" "Results" "<key>"}} |
string | A specific key-value result from the dependency (e.g., branch, commit, pr) |
{{index .Deps "<name>" "Outputs"}} |
[]string | Raw output lines from the dependency |
{{index .Deps "<name>" "Name"}} |
string | The dependency Task name |
Example:
prompt: |
The scaffold task created code on branch {{index .Deps "scaffold" "Results" "branch"}}.
Open a PR for these changes.
dependsOn: [scaffold]If template rendering fails (e.g., missing key), the raw prompt string is used as-is.
The secret referenced by spec.credentials.secretRef.name must contain a single key whose name depends on spec.type and spec.credentials.type:
| Agent type | Credential type | Secret key |
|---|---|---|
claude-code |
api-key |
ANTHROPIC_API_KEY |
claude-code |
oauth |
CLAUDE_CODE_OAUTH_TOKEN |
codex |
api-key |
CODEX_API_KEY |
codex |
oauth |
CODEX_AUTH_JSON (full ~/.codex/auth.json content) |
gemini |
api-key or oauth |
GEMINI_API_KEY |
opencode |
api-key or oauth |
OPENCODE_API_KEY |
cursor |
api-key or oauth |
CURSOR_API_KEY |
Example for claude-code with an API key:
kubectl create secret generic claude-credentials \
--from-literal=ANTHROPIC_API_KEY=<your-api-key>Example for gemini:
kubectl create secret generic gemini-credentials \
--from-literal=GEMINI_API_KEY=<your-api-key>When spec.credentials.type is none, no secret is required; supply credentials via spec.podOverrides.env (e.g., for Bedrock, Vertex AI, or Azure OpenAI). For details on how these variables are consumed by agent containers, see Agent Image Interface.
A codex oauth credential (CODEX_AUTH_JSON) is a ChatGPT-mode bundle carrying a short-lived access_token plus a long-lived refresh_token. Kelos writes this bundle to ~/.codex/auth.json and configures Codex to use file-backed credentials (cli_auth_credentials_store = "file"), matching OpenAI's CI/CD auth guidance. Codex refreshes that file in place during runs, but agent pods are ephemeral, so refreshed tokens are lost unless they are written back to the Secret.
Kelos ships a controller that creates one CronJob per labeled credentials Secret, refreshing each bundle independently of agent activity. Label each credentials Secret to opt in:
# values.yaml
codexAuthRefresher:
schedule: "0 */6 * * *"kubectl label secret codex-credentials kelos.dev/codex-oauth-refresh=trueFor each labeled Secret with a non-empty CODEX_AUTH_JSON key, the controller manages a dedicated CronJob in the Secret's namespace with access limited to that Secret. The CronJob seeds auth.json from that one Secret, invokes Codex so the CLI performs its own refresh, and writes the refreshed file back to only the CODEX_AUTH_JSON key — other keys are preserved and the token is never logged. Removing the label or deleting the Secret removes the managed refresh resources. Secrets without a CODEX_AUTH_JSON bundle, or whose bundle carries no refresh_token (e.g. API-key credentials), are skipped. Externally-managed Secrets (ExternalSecrets, Vault, sealed-secrets) will overwrite the refreshed value on their next sync and are not supported.
| Field | Description | Required |
|---|---|---|
spec.repo |
Git repository URL to clone (HTTPS, git://, or SSH) | Yes |
spec.ref |
Branch, tag, or commit SHA to checkout (defaults to repo's default branch) | No |
spec.secretRef.name |
Secret containing credentials for git auth and gh CLI (see authentication methods below) |
No |
spec.remotes[].name |
Git remote name to add after cloning (must not be "origin") |
Yes (per remote) |
spec.remotes[].url |
Git remote URL | Yes (per remote) |
spec.files[].path |
Relative file path inside the repository (e.g., CLAUDE.md) |
Yes (per file) |
spec.files[].content |
File content to write | Yes (per file) |
spec.setupCommand |
Exec-form command run in /workspace/repo after the repo is cloned, the ref is checked out, remotes are configured, and files are written, but before the agent process starts. Runs as the agent UID with all injected env vars; a non-zero exit fails the Task. Use ["sh", "-c", "<script>"] for shell pipelines (see Setup Command below) |
No |
Use spec.setupCommand to install language dependencies, prime build caches, or run any other prerequisite step that must complete before the agent inspects the codebase. The command follows the same exec-form convention as Kubernetes container.command and lifecycle.postStart.exec.command — the array is passed directly to exec with no shell interpretation.
apiVersion: kelos.dev/v1alpha2
kind: Workspace
metadata:
name: node-app-workspace
spec:
repo: https://github.com/your-org/your-repo.git
ref: main
setupCommand: ["sh", "-c", "npm install && npm run build"]Notes:
- Runs after the repo has been cloned and checked out, additional remotes have been added, and any
spec.filesentries have been written. - Runs before the agent process starts; if it exits non-zero, the agent never runs and the Task fails.
- Executes in
/workspace/repoas the agent UID (61100), with access to all built-in Kelos env vars and anyTask.spec.podOverrides.enventries from the Task that references this Workspace. - The default form is exec-style; for shell pipelines, environment expansion, or multi-step scripts, wrap the command with
["sh", "-c", "<script>"].
The workspace secret referenced by spec.secretRef.name supports two authentication methods:
Personal Access Token (PAT):
The secret contains a single key:
| Key | Description |
|---|---|
GITHUB_TOKEN |
GitHub Personal Access Token for git auth and gh CLI |
kubectl create secret generic github-token \
--from-literal=GITHUB_TOKEN=<your-pat>GitHub App (recommended for production/org use):
The secret contains three keys, and the controller automatically exchanges them for a short-lived installation token before each task run:
| Key | Description |
|---|---|
appID |
GitHub App ID |
installationID |
GitHub App installation ID for the target organization |
privateKey |
PEM-encoded RSA private key (PKCS1 or PKCS8) |
kubectl create secret generic github-app-creds \
--from-literal=appID=12345 \
--from-literal=installationID=67890 \
--from-file=privateKey=my-app.private-key.pemGitHub Apps are preferred over PATs for production use because they offer fine-grained permissions, higher rate limits, no dependency on a specific user account, and automatically expiring tokens.
| 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.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) |
spec.plugins[].agents[].name |
Agent name (becomes agents/<name>.md) |
Yes (per agent) |
spec.plugins[].agents[].content |
Agent content (markdown with frontmatter) | Yes (per agent) |
spec.skills[].source |
skills.sh package in owner/repo format (e.g., vercel-labs/agent-skills). Installed skills are exposed to the agent as a plugin named skills-sh; when AgentConfig.spec.skills is set, that name is reserved and must not be used in AgentConfig.spec.plugins[].name |
Yes (per skill) |
spec.skills[].skill |
Specific skill name from the package (installs all if omitted) | No |
spec.mcpServers[].name |
MCP server name (used as key in agent config) | Yes (per server) |
spec.mcpServers[].type |
Transport type: stdio, http, or sse |
Yes (per server) |
spec.mcpServers[].command |
Executable to run (stdio only) | No |
spec.mcpServers[].args |
Command-line arguments (stdio only) | No |
spec.mcpServers[].url |
Server endpoint (http/sse only) | No |
spec.mcpServers[].headers |
HTTP headers (http/sse only) | No |
spec.mcpServers[].headersFrom.secretRef.name |
Secret whose data keys become HTTP header names and values (http/sse only). Values from headersFrom override headers on key conflicts |
No |
spec.mcpServers[].env |
Environment variables for the server process (stdio only), as an array of Kubernetes EnvVar objects. Literal entries use name and value |
No |
spec.mcpServers[].env[].valueFrom.secretKeyRef |
Secret key reference for an MCP env value. Set name and key; when optional: true, a missing Secret or key omits the variable instead of failing the Task |
No |
spec.mcpServers[].env[].valueFrom.configMapKeyRef |
ConfigMap key reference for an MCP env value. Set name and key; when optional: true, a missing ConfigMap or key omits the variable instead of failing the Task |
No |
spec.mcpServers[].env[].valueFrom |
Only secretKeyRef and configMapKeyRef are supported for MCP server env. Other Kubernetes EnvVarSource variants are rejected when a Task consumes the AgentConfig |
No |
spec.mcpServers[].envFrom.secretRef.name |
Secret whose data keys become stdio MCP environment variable names and values. Values from envFrom override inline env on key conflicts |
No |
| Field | Description | Required |
|---|---|---|
spec.taskTemplate.workspaceRef.name |
Workspace resource (repo URL, auth, and clone target for spawned Tasks) | Yes (when using githubIssues, githubPullRequests, githubWebhook, linearWebhook, or webhook) |
spec.when.githubIssues.repo |
Override repository to poll for issues (in owner/repo format or full URL); defaults to workspace repo URL |
No |
spec.when.githubIssues.labels |
Filter issues by labels | No |
spec.when.githubIssues.excludeLabels |
Exclude issues with these labels | No |
spec.when.githubIssues.state |
Filter by state: open, closed, all (default: open) |
No |
spec.when.githubIssues.types |
Filter by type: issues, pulls (default: issues) |
No |
spec.when.githubIssues.commentPolicy.triggerComment |
Requires a matching command in the issue body or comments to include the issue | No |
spec.when.githubIssues.commentPolicy.excludeComments |
Blocks items whose most recent matching command is an exclude comment | No |
spec.when.githubIssues.commentPolicy.allowedUsers |
Restrict comment control to specific GitHub usernames | No |
spec.when.githubIssues.commentPolicy.allowedTeams |
Restrict comment control to specific GitHub teams in org/team-slug format |
No |
spec.when.githubIssues.commentPolicy.minimumPermission |
Minimum repo permission required for comment control: read, triage, write, maintain, or admin |
No |
spec.when.githubIssues.assignee |
Filter by assignee username; use "*" for any assignee or "none" for unassigned |
No |
spec.when.githubIssues.author |
Filter by issue author username | No |
spec.when.githubIssues.excludeAuthors |
Exclude issues created by any of these usernames (client-side) | No |
spec.when.githubIssues.priorityLabels |
Priority-order labels for task selection when maxConcurrency is set; index 0 is highest priority |
No |
spec.when.githubIssues.reporting.enabled |
Post status comments (started, succeeded, failed) back to the GitHub issue | No |
spec.when.githubIssues.pollInterval |
Per-source poll interval (e.g., "30s", "5m"). Defaults to 5m when omitted |
No |
spec.when.githubPullRequests.repo |
Override repository to poll for PRs (in owner/repo format or full URL); defaults to workspace repo URL |
No |
spec.when.githubPullRequests.labels |
Filter pull requests by labels | No |
spec.when.githubPullRequests.excludeLabels |
Exclude pull requests with these labels | No |
spec.when.githubPullRequests.state |
Filter by state: open, closed, all (default: open) |
No |
spec.when.githubPullRequests.reviewState |
Filter by aggregated review state: approved, changes_requested, any (default: any) |
No |
spec.when.githubPullRequests.commentPolicy.triggerComment |
Requires a matching command in the PR body or comments to include the PR | No |
spec.when.githubPullRequests.commentPolicy.excludeComments |
Blocks PRs whose most recent matching command is an exclude comment | No |
spec.when.githubPullRequests.commentPolicy.allowedUsers |
Restrict comment control to specific GitHub usernames | No |
spec.when.githubPullRequests.commentPolicy.allowedTeams |
Restrict comment control to specific GitHub teams in org/team-slug format |
No |
spec.when.githubPullRequests.commentPolicy.minimumPermission |
Minimum repo permission required for comment control: read, triage, write, maintain, or admin |
No |
spec.when.githubPullRequests.author |
Filter by PR author username | No |
spec.when.githubPullRequests.excludeAuthors |
Exclude PRs opened by any of these usernames (client-side) | No |
spec.when.githubPullRequests.draft |
Filter by draft state | No |
spec.when.githubPullRequests.priorityLabels |
Priority-order labels for task selection when maxConcurrency is set; index 0 is highest priority |
No |
spec.when.githubPullRequests.reporting.enabled |
Post status comments (started, succeeded, failed) back to the GitHub pull request | No |
spec.when.githubPullRequests.reporting.checks.name |
Creates a GitHub Check Run for each PR task, enabling branch protection and merge queue integration. Sets the Check Run name (defaults to "Kelos: <taskspawner-name>", max 100 chars). The token used by the workspace must have checks:write permission. Not supported on githubIssues (rejected by CEL validation). |
No |
spec.when.githubPullRequests.pollInterval |
Per-source poll interval (e.g., "30s", "5m"). Defaults to 5m when omitted |
No |
spec.when.githubWebhook.events |
GitHub event types to listen for (e.g., "issues", "pull_request", "push", "issue_comment") |
Yes (when using githubWebhook) |
spec.when.githubWebhook.repository |
Restrict webhooks to a specific repository (owner/repo format); if empty, webhooks from any repository are accepted |
No |
spec.when.githubWebhook.excludeAuthors |
Exclude webhook events sent by any of these usernames; applied before filter evaluation | No |
spec.when.githubWebhook.filters[].event |
GitHub event type this filter applies to | Yes (per filter) |
spec.when.githubWebhook.filters[].action |
Filter by webhook action (e.g., "opened", "created", "submitted") |
No |
spec.when.githubWebhook.filters[].labels |
Require the issue/PR to have all of these labels | No |
spec.when.githubWebhook.filters[].excludeLabels |
Exclude issues/PRs with any of these labels | No |
spec.when.githubWebhook.filters[].state |
Filter by issue/PR state ("open", "closed") |
No |
spec.when.githubWebhook.filters[].branch |
Filter push and create (ref_type=branch) events by branch name (exact match or glob) | No |
spec.when.githubWebhook.filters[].tag |
Filter create (ref_type=tag) and release events by tag name (exact match or glob) | No |
spec.when.githubWebhook.filters[].draft |
Filter PRs by draft status | No |
spec.when.githubWebhook.filters[].author |
Filter by the event sender's username | No |
spec.when.githubWebhook.filters[].excludeAuthors |
Exclude events sent by any of these usernames | No |
spec.when.githubWebhook.filters[].bodyContains |
Deprecated. Filter by case-sensitive substring match on the comment/review body. Use bodyPattern instead |
No |
spec.when.githubWebhook.filters[].bodyPattern |
Require the comment/review body to match a Go re2 regular expression. When combined with excludeBodyPatterns, the body must match this pattern AND not match any exclude entry |
No |
spec.when.githubWebhook.filters[].excludeBodyPatterns |
Exclude events whose comment/review body matches any of these Go re2 regular expressions (OR semantics) | No |
spec.when.githubWebhook.filters[].commentOn |
Scope issue_comment events to comments posted on a specific subject: "Issue" matches plain issues, "PullRequest" matches pull requests. Empty matches both. Ignored for other events |
No |
spec.when.githubWebhook.reporting.enabled |
Post status comments (started, succeeded, failed) back to the originating issue or PR | No |
spec.when.githubWebhook.reporting.checks.name |
Creates a GitHub Check Run for tasks spawned by PR-related webhook events, enabling branch protection and merge queue integration. Sets the Check Run name (defaults to "Kelos: <taskspawner-name>", max 100 chars). The token used by the workspace must have checks:write permission. Requires events to include at least one of pull_request, pull_request_review, pull_request_review_comment, or pull_request_target (enforced by CEL validation). |
No |
spec.when.linearWebhook.types |
Linear resource types to listen for (e.g., "Issue", "Comment") |
Yes (when using linearWebhook) |
spec.when.linearWebhook.filters[].type |
Scope filter to a specific resource type | No |
spec.when.linearWebhook.filters[].action |
Filter by webhook action: create, update, or remove |
No |
spec.when.linearWebhook.filters[].states |
Filter by workflow state names (e.g., "Todo", "In Progress") |
No |
spec.when.linearWebhook.filters[].labels |
Require the issue to have all of these labels | No |
spec.when.linearWebhook.filters[].excludeLabels |
Exclude issues with any of these labels | No |
spec.when.slack.channels |
Restrict which Slack channels the bot listens in (channel IDs like "C0123456789"); when empty, listens in all invited channels |
No |
spec.when.slack.botMessagePolicy |
Controls whether bot-originated messages can trigger this spawner: None (default) rejects all bot messages, All allows all including self, OthersOnly allows other bots but rejects the bot's own output to prevent self-trigger loops |
No |
spec.when.slack.triggers[].pattern |
RE2 regex matched against message text (unanchored); leading <@USER_ID> mentions are stripped before matching; bot mention required unless mentionOptional is set; multiple triggers use OR semantics; when empty, every bot mention fires |
No |
spec.when.slack.triggers[].mentionOptional |
When true, fire on pattern match alone without requiring a bot @-mention |
No |
spec.when.slack.excludePatterns |
RE2 regex patterns that reject messages when any pattern matches (OR semantics); leading <@USER_ID> mentions are stripped before matching; does not apply to slash commands |
No |
spec.when.webhook.source |
Short identifier for the generic webhook source (lowercase alphanumeric with optional hyphens). Determines the URL path (/webhook/<source>). The endpoint is currently unauthenticated — see #1040 |
Yes (when using webhook) |
spec.when.webhook.fieldMapping |
Map of template variable name → JSONPath expression evaluated against the request body. Each key becomes a top-level template variable. Lowercase id, title, body, url are also exposed as {{.ID}}, {{.Title}}, {{.Body}}, {{.URL}}. The id key is required (used for delivery deduplication and Task naming) |
Yes (when using webhook) |
spec.when.webhook.filters[].field |
JSONPath expression selecting the payload field to match | Yes (per filter) |
spec.when.webhook.filters[].value |
Require an exact string match against the extracted field value (mutually exclusive with pattern) |
Conditional |
spec.when.webhook.filters[].pattern |
Require a regex match against the extracted field value (mutually exclusive with value) |
Conditional |
spec.when.jira.pollInterval |
Per-source poll interval (e.g., "30s", "5m"). Defaults to 5m when omitted |
No |
spec.when.cron.schedule |
Cron schedule expression (e.g., "0 * * * *") |
Yes (when using cron) |
spec.taskTemplate.type |
Agent type (claude-code, codex, gemini, opencode, or cursor) |
Yes |
spec.taskTemplate.credentials |
Credentials for the agent (same as Task) | Yes |
spec.taskTemplate.model |
Model override for spawned Tasks. Same pass-through behavior as Task.spec.model: either an agent-native shorthand (e.g., sonnet, opus for Claude Code) or a versioned ID (e.g., claude-sonnet-4-6) is valid |
No |
spec.taskTemplate.effort |
Reasoning effort for spawned Tasks. Same pass-through behavior as Task.spec.effort |
No |
spec.taskTemplate.image |
Custom agent image override (see Agent Image Interface) | No |
spec.taskTemplate.agentConfigRefs[].name |
Ordered AgentConfig resources for spawned Tasks. Configs are merged in order | No |
spec.taskTemplate.promptTemplate |
Go text/template for prompt (see template variables below) | No |
spec.taskTemplate.dependsOn |
Task names that spawned Tasks depend on | No |
spec.taskTemplate.branch |
Git branch template for spawned Tasks (supports Go template variables, e.g., kelos-task-{{.Number}}) |
No |
spec.taskTemplate.ttlSecondsAfterFinished |
Auto-delete spawned tasks after N seconds | No |
spec.taskTemplate.podFailurePolicy |
Kubernetes Job pod failure policy copied to spawned Tasks as Task.spec.podFailurePolicy |
No |
spec.taskTemplate.podOverrides |
Pod customization for spawned Tasks (labels, resources, activeDeadlineSeconds, env, nodeSelector, tolerations, affinity, imagePullSecrets, serviceAccountName, volumes, volumeMounts, podSecurityContext, containerSecurityContext, extraContainers, extraInitContainers). Same fields and constraints as Task.spec.podOverrides |
No |
spec.taskTemplate.metadata.labels |
Labels merged into spawned Tasks; values support the same Go template variables as branch/promptTemplate; the kelos.dev/taskspawner label is always set to the TaskSpawner name and overrides any user value for that key |
No |
spec.taskTemplate.metadata.annotations |
Annotations merged into spawned Tasks; values support the same Go template variables as branch/promptTemplate; source annotations (e.g. kelos.dev/source-kind) are applied after rendering and override conflicting user values |
No |
spec.taskTemplate.contextSources |
External data sources fetched in parallel before task creation; each source's value is exposed as {{.Context.NAME}} in branch, promptTemplate, and metadata templates (see Context Sources below). Maximum 8 entries; names must be unique |
No |
spec.taskTemplate.upstreamRepo |
Upstream repository in owner/repo format; injected as KELOS_UPSTREAM_REPO into the agent container. Typically auto-derived from githubIssues.repo/githubPullRequests.repo, but can be set explicitly for fork workflows |
No |
spec.maxConcurrency |
Limit max concurrent running tasks (important for cost control) | No |
spec.maxTotalTasks |
Lifetime limit on total tasks created by this spawner | No |
spec.suspend |
Pause the spawner without deleting it; resume with spec.suspend: false (default: false) |
No |
The promptTemplate field uses Go text/template syntax. Available variables depend on the source type:
| Variable | Description | GitHub Issues | GitHub Pull Requests | GitHub Webhook | Jira | Linear Webhook | Generic Webhook | Cron |
|---|---|---|---|---|---|---|---|---|
{{.ID}} |
Unique identifier | Issue/PR number as string (e.g., "42") |
Pull request number as string | Issue/PR number or commit ID | Jira issue key (e.g., "ENG-42") |
Linear resource ID | Mapped id field (required) |
Date-time string (e.g., "20260207-0900") |
{{.Number}} |
Issue or PR number | Issue/PR number (e.g., 42) |
Pull request number | Issue/PR number (when available) | Numeric suffix of the Jira key (e.g., 42 for ENG-42); 0 if the key has no -N suffix |
Empty | Empty | 0 |
{{.Title}} |
Title of the work item | Issue/PR title | Pull request title | Issue/PR title or "Push to <branch>" | Issue summary | Resource title | Mapped title field (if present) |
Trigger time (RFC3339) |
{{.Body}} |
Body text | Issue/PR body | Pull request body | Issue/PR body | Empty (description is not fetched; tracked in #990) | Empty | Mapped body field (if present) |
Empty |
{{.URL}} |
URL to the source item | GitHub HTML URL | GitHub PR URL | Issue/PR HTML URL | Jira browse URL (e.g., https://your-org.atlassian.net/browse/ENG-42) |
Empty | Mapped url field (if present) |
Empty |
{{.Labels}} |
Comma-separated labels | Issue/PR labels | Pull request labels | Empty | Issue labels | Issue labels | Empty | Empty |
{{.Comments}} |
Concatenated comments | Issue/PR comments | PR conversation comments | Empty | Issue comments | Empty | Empty | Empty |
{{.Kind}} |
Type of work item | "Issue" or "PR" |
"PR" |
"webhook" |
Jira issue type name (e.g., "Bug", "Story"), or "Issue" if empty |
"LinearWebhook" |
"GenericWebhook" |
"Issue" |
{{.Event}} |
GitHub event type | Empty | Empty | Event type (e.g., "issues", "pull_request", "push") |
Empty | Empty | Empty | Empty |
{{.Action}} |
Webhook action | Empty | Empty | Action (e.g., "opened", "created", "submitted") |
Empty | Action (e.g., "create", "update", "remove") |
Empty | Empty |
{{.Sender}} |
Event sender username | Empty | Empty | Username of person who triggered the event | Empty | Empty | Empty | Empty |
{{.Branch}} |
Git branch to update | Empty | PR head branch (e.g., "kelos-task-42") |
PR source branch or push branch | Empty | Empty | Empty | Empty |
{{.Ref}} |
Git ref | Empty | Empty | Git ref for push events (e.g., "refs/heads/main") or create events (ref name) |
Empty | Empty | Empty | Empty |
{{.Tag}} |
Tag name | Empty | Empty | Tag name for create (ref_type=tag) and release events |
Empty | Empty | Empty | Empty |
{{.RefType}} |
Ref type for create events | Empty | Empty | "branch", "tag", or "repository" (create events only) |
Empty | Empty | Empty | Empty |
{{.Repository}} |
Full repository name | Empty | Empty | Repository in owner/repo format |
Empty | Empty | Empty | Empty |
{{.RepositoryOwner}} |
Repository owner | Empty | Empty | Repository owner login | Empty | Empty | Empty | Empty |
{{.RepositoryName}} |
Repository name | Empty | Empty | Repository name only | Empty | Empty | Empty | Empty |
{{.Payload}} |
Raw event payload | Empty | Empty | Full parsed GitHub webhook payload | Empty | Full parsed Linear webhook payload | Full parsed JSON body | Empty |
{{.ReviewState}} |
Aggregated review state | Empty | approved, changes_requested, or empty |
Empty | Empty | Empty | Empty | Empty |
{{.ReviewComments}} |
Formatted inline review comments | Empty | Inline PR review comments | Empty | Empty | Empty | Empty | Empty |
{{.Type}} |
Resource type | Empty | Empty | Empty | Empty | Resource type (e.g., "Issue", "Comment") |
Empty | Empty |
{{.State}} |
Workflow state | Empty | Empty | Empty | Empty | Current state name (e.g., "Todo", "In Progress") |
Empty | Empty |
{{.IssueID}} |
Parent issue ID | Empty | Empty | Empty | Empty | Parent issue ID (Comment events only) | Empty | Empty |
{{.CommentBody}} |
Comment or review body | Empty | Empty | Comment/review body (issue_comment, pull_request_review, pull_request_review_comment events) |
Empty | Empty | Empty | Empty |
{{.CommentURL}} |
Comment or review URL | Empty | Empty | Comment/review HTML URL (issue_comment, pull_request_review, pull_request_review_comment events) |
Empty | Empty | Empty | Empty |
{{.Time}} |
Trigger time (RFC3339) | Empty | Empty | Empty | Empty | Empty | Empty | Cron tick time (e.g., "2026-02-07T09:00:00Z") |
{{.Schedule}} |
Cron schedule expression | Empty | Empty | Empty | Empty | Empty | Empty | Schedule string (e.g., "0 * * * *") |
Generic Webhook only: any additional keys declared in
spec.when.webhook.fieldMappingare also exposed as top-level template variables (e.g.,fieldMapping: {severity: "$.level"}makes{{.severity}}available).
Context sources: when
spec.taskTemplate.contextSourcesis configured, each entry's fetched value is exposed as{{.Context.NAME}}(e.g., a source namedjirais available as{{.Context.jira}}). The same.Contextmap is also available inspec.taskTemplate.branchandspec.taskTemplate.metadatatemplates. See Context Sources for details.
spec.taskTemplate.contextSources lets a TaskSpawner fetch external data at task-creation time and inject the result as template variables. For each work item, all of its sources are fetched in parallel during the spawning cycle, and the fetched value becomes available as {{.Context.NAME}} in promptTemplate, branch, and metadata templates. A TaskSpawner may declare up to 8 sources; names must be unique and match ^[a-zA-Z][a-zA-Z0-9_]*$.
| Field | Description | Required |
|---|---|---|
spec.taskTemplate.contextSources[].name |
Identifier used as the template key ({{.Context.<name>}}). Must match ^[a-zA-Z][a-zA-Z0-9_]*$, 1–64 characters |
Yes |
spec.taskTemplate.contextSources[].http |
HTTP(S) source configuration. Currently the only supported source kind; exactly one source kind must be set | Yes |
spec.taskTemplate.contextSources[].http.url |
Endpoint to fetch. Supports Go text/template variables from the work item (e.g., https://api.example.com/items/{{.Number}}). HTTPS is required unless allowInsecure is set |
Yes (per source) |
spec.taskTemplate.contextSources[].http.method |
HTTP method: GET or POST (default: GET) |
No |
spec.taskTemplate.contextSources[].http.headers |
Static HTTP headers. Values support Go text/template variables from the work item |
No |
spec.taskTemplate.contextSources[].http.headersFrom |
HTTP header values sourced from Kubernetes Secrets in the same namespace as the TaskSpawner. Each entry sets header to the HTTP header name, secretName to the Secret name, and secretKey to the key within the Secret. Merged with headers; headersFrom wins on conflict. Maximum 16 entries |
No |
spec.taskTemplate.contextSources[].http.body |
Request body template (Go text/template); used with POST |
No |
spec.taskTemplate.contextSources[].http.responseFilter.type |
Filter language for extracting a subset of the response. Currently only JSONPath is supported |
No |
spec.taskTemplate.contextSources[].http.responseFilter.expression |
Filter expression (e.g., $.data.value for JSONPath). When set, only the extracted value is stored; otherwise the entire response body is used |
Conditional |
spec.taskTemplate.contextSources[].http.allowInsecure |
Permit plain HTTP (non-TLS) URLs (default: false) |
No |
spec.taskTemplate.contextSources[].http.timeoutSeconds |
Per-request timeout in seconds, 1–60 (default: 10) |
No |
spec.taskTemplate.contextSources[].http.maxResponseBytes |
Maximum response body size in bytes, 1–131072 (default: 32768, i.e. 32 KiB). Caps the amount injected into the prompt |
No |
spec.taskTemplate.contextSources[].failurePolicy |
Behavior when the source fails to fetch: Fail skips task creation for the work item; Ignore substitutes an empty string and logs a warning (default: Fail) |
No |
Example — fetch a Jira issue description over HTTP and inject it into a prompt triggered by a GitHub issue:
apiVersion: kelos.dev/v1alpha2
kind: TaskSpawner
metadata:
name: enrich-from-jira
spec:
when:
githubIssues:
labels: ["needs-jira-context"]
taskTemplate:
type: claude-code
workspaceRef:
name: my-workspace
credentials:
type: api-key
secretRef:
name: claude-credentials
contextSources:
- name: jira
failurePolicy: Ignore
http:
# This example assumes the GitHub issue title is the Jira issue key
# (e.g. "PROJ-123"). Adjust the URL/template to however your issues
# reference Jira.
url: "https://your-org.atlassian.net/rest/api/3/issue/{{.Title}}"
headersFrom:
- header: Authorization
secretName: jira-credentials
secretKey: authorization
responseFilter:
type: JSONPath
expression: "$.fields.description"
timeoutSeconds: 15
promptTemplate: |
Address GitHub issue #{{.Number}}: {{.Title}}
Linked Jira description:
{{.Context.jira}}| Field | Description |
|---|---|
status.phase |
Current phase: Pending, Waiting, Running, Succeeded, or Failed |
status.jobName |
Name of the Job created for this Task |
status.podName |
Name of the Pod running the Task |
status.startTime |
When the Task started running |
status.completionTime |
When the Task completed |
status.message |
Additional information about the current status |
status.outputs |
Automatically captured outputs: branch, commit, base-branch, pr, cost-usd, input-tokens, output-tokens |
status.results |
Parsed key-value map from outputs (e.g., results.branch, results.commit, results.pr, results.input-tokens) |
| Field | Description |
|---|---|
status.phase |
Current phase: Pending, Running, Suspended, or Failed |
status.deploymentName |
Name of the Deployment running the spawner (polling-based sources) |
status.cronJobName |
Name of the CronJob running the spawner (cron-based sources) |
status.totalDiscovered |
Total number of items discovered from the source |
status.totalTasksCreated |
Total number of Tasks created by this spawner |
status.activeTasks |
Number of currently active (non-terminal) Tasks |
status.lastDiscoveryTime |
Last time the source was polled |
status.message |
Additional information about the current status |
status.conditions |
Standard Kubernetes conditions for detailed status |
Kelos reads defaults from ~/.kelos/config.yaml (override with --config). CLI flags always take precedence over config file values.
# ~/.kelos/config.yaml
oauthToken: <your-oauth-token>
# or: apiKey: <your-api-key>
model: sonnet # or a versioned ID like 'claude-sonnet-4-6' — see spec.model under Task
effort: high
namespace: my-namespace| Field | Description |
|---|---|
oauthToken |
OAuth token — Kelos auto-creates the Kubernetes secret. Use none for an empty credential |
apiKey |
API key — Kelos auto-creates the Kubernetes secret. Use none for an empty credential (e.g., free-tier OpenCode models) |
secret |
(Advanced) Use a pre-created Kubernetes secret |
credentialType |
Credential type when using secret (api-key or oauth) |
Precedence: --secret flag > secret in config > oauthToken/apiKey in config.
The workspace field supports two forms:
Reference an existing Workspace resource by name:
workspace:
name: my-workspaceSpecify inline with a PAT — Kelos auto-creates the Workspace resource and secret:
workspace:
repo: https://github.com/your-org/repo.git
ref: main
token: <your-github-token> # optional, for private repos and gh CLISpecify inline with a GitHub App (recommended for production/org use):
workspace:
repo: https://github.com/your-org/repo.git
ref: main
githubApp:
appID: "12345"
installationID: "67890"
privateKeyPath: ~/.config/my-app.private-key.pem| Field | Description |
|---|---|
workspace.name |
Name of an existing Workspace resource |
workspace.repo |
Git repository URL — Kelos auto-creates a Workspace resource |
workspace.ref |
Git reference (branch, tag, or commit SHA) |
workspace.token |
GitHub PAT — Kelos auto-creates the secret and injects GITHUB_TOKEN |
workspace.githubApp.appID |
GitHub App ID |
workspace.githubApp.installationID |
GitHub App installation ID |
workspace.githubApp.privateKeyPath |
Path to PEM-encoded RSA private key file |
The token and githubApp fields are mutually exclusive. If both name and repo are set, name takes precedence. The --workspace CLI flag overrides all config values.
| Field | Description |
|---|---|
type |
Default agent type (claude-code, codex, gemini, opencode, or cursor) |
model |
Default model override |
effort |
Default agent reasoning effort |
namespace |
Default Kubernetes namespace |
agentConfig |
Default AgentConfig resource name |
The kelos CLI lets you manage the full lifecycle without writing YAML.
| Command | Description |
|---|---|
kelos install |
Install Kelos CRDs and controller into the cluster |
kelos uninstall |
Uninstall Kelos from the cluster |
kelos init |
Initialize ~/.kelos/config.yaml |
kelos version |
Print version information |
kelos completion <shell> |
Generate a shell completion script for bash, zsh, fish, or powershell |
| Command | Description |
|---|---|
kelos run |
Create and run a new Task |
kelos create workspace |
Create a Workspace resource |
kelos create agentconfig |
Create an AgentConfig resource |
kelos get <resource> [name] |
List resources or view a specific resource (tasks, taskspawners, workspaces, agentconfigs) |
kelos delete <resource> [name] |
Delete a resource (tasks, taskspawners, workspaces, agentconfigs) |
kelos logs <task-name> [-f] |
View or stream logs from a task |
kelos suspend taskspawner <name> |
Pause a TaskSpawner (stops polling, running tasks continue) |
kelos resume taskspawner <name> |
Resume a paused TaskSpawner |
--values, -f: Load Helm values from a YAML file; repeat to merge multiple files, or use-to read from stdin--set: Set chart values with Helmkey=valuesyntax--set-string: Set string chart values with Helmkey=valuesyntax--set-file: Set chart values from file contents with Helmkey=pathsyntax--version: Override the image tag used for controller and bundled agent images; shorthand forimage.tag--image-pull-policy: SetimagePullPolicyon controller-managed images--disable-heartbeat: Do not install the telemetry heartbeat CronJob--spawner-resource-requests: Resource requests for spawner containers as comma-separatedname=valuepairs--spawner-resource-limits: Resource limits for spawner containers as comma-separatedname=valuepairs--ghproxy-resource-requests: Resource requests for workspace ghproxy containers as comma-separatedname=valuepairs--ghproxy-resource-limits: Resource limits for workspace ghproxy containers as comma-separatedname=valuepairs--ghproxy-allowed-upstreams: Comma-separated list of allowed upstream base URLs for ghproxy--ghproxy-cache-ttl: Cache TTL for workspace ghproxy instances--controller-resource-requests: Resource requests for the controller container as comma-separatedname=valuepairs, for examplecpu=10m,memory=64Mi--controller-resource-limits: Resource limits for the controller container as comma-separatedname=valuepairs, for examplecpu=500m,memory=128Mi
kelos install renders the embedded Helm chart but still manages CRDs separately, so crds.install must be omitted or set to false.
kelos install --dry-run prints the controller-side chart manifests only; it omits CRDs because real installs apply them in a staged sequence after certificate and conversion webhook readiness.
When the same key is set multiple ways, precedence is: chart defaults, then --values files, then compatibility install flags, then explicit --set, --set-string, and --set-file overrides.
--prompt, -p: Task prompt (required unless--prompt-fileis set)--prompt-file: Read task prompt from a file path; use-to read from stdin (mutually exclusive with--prompt)--type, -t: Agent type (default:claude-code)--model: Model override--effort: Agent reasoning effort--image: Custom agent image--name: Task name (auto-generated if omitted)--workspace: Workspace resource name--agent-config: AgentConfig resource name--depends-on: Task names this task depends on (repeatable)--branch: Git branch to work on--timeout: Maximum execution time (e.g.,30m,1h)--env: Additional env vars asNAME=VALUE(repeatable)--watch, -w: Watch task status after creation--secret: Pre-created secret name--credential-type: Credential type when using--secret(default:api-key)
--output, -o: Output format (yamlorjson)--detail, -d: Show detailed information for a specific resource--all-namespaces, -A: List resources across all namespaces--phase: (kelos get taskonly) Filter tasks by phase; repeatable or comma-separated. Valid values:Pending,Running,Waiting,Succeeded,Failed
--all: Delete every resource of the given type in the namespace; mutually exclusive with a resource name. Supported bytask,workspace,taskspawner, andagentconfigsubcommands
--config: Path to config file (default~/.kelos/config.yaml)--namespace, -n: Kubernetes namespace--kubeconfig: Path to kubeconfig file--dry-run: Print resources without creating them. Forinstall, this prints controller manifests only; CRDs are staged separately during real installs--yes, -y: Skip confirmation prompts
kelos completion <shell> prints a completion script for bash, zsh, fish, or powershell. Source it from your shell to enable <TAB> completion of subcommands, flags, and resource names.
Load the script for the current session:
# bash
source <(kelos completion bash)
# zsh
source <(kelos completion zsh)
# fish
kelos completion fish | source
# powershell
kelos completion powershell | Out-String | Invoke-ExpressionTo persist completion across sessions, add the matching source line to your shell's startup file (e.g., ~/.bashrc or ~/.zshrc), or write the script to your shell's completions directory. Run kelos completion <shell> --help for shell-specific installation paths.
In addition to subcommands and flags, the following arguments complete dynamically by querying the configured cluster — a reachable kubeconfig and the relevant list permission in the active namespace are required:
| Command | Completes |
|---|---|
kelos logs <TAB> |
task names |
kelos get task <TAB> |
task names |
kelos get taskspawner <TAB> |
taskspawner names |
kelos get workspace <TAB> |
workspace names |
kelos get agentconfig <TAB> |
agentconfig names |
kelos delete task <TAB> |
task names |
kelos delete taskspawner <TAB> |
taskspawner names |
kelos delete workspace <TAB> |
workspace names |
kelos delete agentconfig <TAB> |
agentconfig names |
kelos suspend taskspawner <TAB> |
taskspawner names |
kelos resume taskspawner <TAB> |
taskspawner names |
Enum-valued flags — kelos run --type, kelos run --credential-type, kelos get --output, and kelos get task --phase — complete from their fixed value set without contacting the cluster.
Kelos collects anonymous, aggregate usage data to help improve the project. A kelos-telemetry CronJob runs daily at 06:00 UTC and reports the following:
| Data | Description |
|---|---|
| Installation ID | Random UUID, generated once per cluster |
| Kelos version | Installed controller version |
| Kubernetes version | Cluster K8s version |
| Task counts | Total tasks, breakdown by type and phase |
| Feature adoption | Number of TaskSpawners, AgentConfigs, Workspaces, and source types in use |
| Scale | Number of namespaces with Kelos resources |
| Usage totals | Aggregate cost (USD), input tokens, and output tokens |
No personal data, repository names, prompts, or source code is collected.
Install (or reinstall) with the --disable-heartbeat flag:
kelos install --disable-heartbeat