Reference

July 13, 2026 · View on GitHub

Task

Exactly one execution source is required: spec.worker (preferred), spec.workerPoolRef, or legacy flat fields (spec.type + spec.credentials).

FieldDescriptionRequired
spec.workerExecution environment (see WorkerSpec below). Creates a Job. Mutually exclusive with workerPoolRefOne of worker, workerPoolRef, or type+credentials
spec.workerPoolRef.nameName of a WorkerPool resource. Task is dispatched to a pre-warmed worker pod instead of creating a JobOne of worker, workerPoolRef, or type+credentials
spec.promptTask prompt for the agentYes
spec.type(Deprecated) Agent type — use spec.worker.type insteadLegacy
spec.credentials.type(Deprecated) Credential type — use spec.worker.credentials insteadLegacy
spec.credentials.secretRef.name(Deprecated) Secret name — use spec.worker.credentials.secretRef insteadLegacy
spec.model(Deprecated) Model override — use spec.worker.model insteadLegacy
spec.effort(Deprecated) Reasoning effort — use spec.worker.effort insteadLegacy
spec.image(Deprecated) Custom agent image — use spec.worker.image insteadLegacy
spec.workspaceRef.name(Deprecated) Workspace reference — use spec.worker.workspaceRef insteadLegacy
spec.agentConfigRefs[].name(Deprecated) AgentConfig references — use spec.worker.agentConfigRefs insteadLegacy
spec.dependsOnTask names that must succeed before this Task starts (creates Waiting phase). Not supported with workerPoolRefNo
spec.branchGit branch to work on; only one Task with the same branch runs at a time (mutex). Not supported with workerPoolRefNo
spec.ttlSecondsAfterFinishedAuto-delete task after N seconds (0 for immediate)No
spec.podFailurePolicyKubernetes Job pod failure policy copied to Job.spec.podFailurePolicy. If omitted, Kelos leaves it unset and Kubernetes default Job failure handling appliesNo
spec.podOverrides(Deprecated) Pod customization — use spec.worker.podOverrides insteadLegacy
spec.podOverrides.labelsAdditional labels to apply to the Job and its Pod. Merged with built-in labels; built-in labels take precedence on conflictNo
spec.podOverrides.resourcesCPU/memory requests and limits for the agent containerNo
spec.podOverrides.activeDeadlineSecondsMaximum duration in seconds before the agent pod is terminatedNo
spec.podOverrides.envAdditional environment variables (built-in vars take precedence on conflict)No
spec.podOverrides.nodeSelectorNode selection labels to constrain which nodes run agent podsNo
spec.podOverrides.tolerationsTolerations for the agent pod; use with nodeSelector or affinity to target dedicated node pools (e.g., GPU nodes, agent-specific pools)No
spec.podOverrides.affinityNode, pod, and pod-anti-affinity rules. Use for spreading agents across nodes or expressing scheduling preferences beyond nodeSelectorNo
spec.podOverrides.imagePullSecretsSecrets used to pull container images from private registries. Required when the agent image or any init container image is in a private registryNo
spec.podOverrides.serviceAccountNameService account name for the agent pod; use with workload identity systems (IRSA, GKE Workload Identity, Azure)No
spec.podOverrides.volumesAdditional volumes to attach to the agent pod. Names must not be workspace or use the Kelos-reserved kelos- prefixNo
spec.podOverrides.volumeMountsAdditional 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 or kelos-github-token)No
spec.podOverrides.podSecurityContextPod-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 accessNo
spec.podOverrides.containerSecurityContextSecurity context applied to the agent container. Use to declare allowPrivilegeEscalation: false, capabilities.drop: [ALL], readOnlyRootFilesystem: true, etc., for PSS-restricted namespacesNo
spec.podOverrides.extraContainersAdditional 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.extraInitContainersAdditional 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 or kelos-github-token); workspace write access requires running as a UID in the pod's fsGroup. Same name constraints as extraContainers (see Extra Containers below)No

Pod Override Volumes

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.

Task Pod Failure Policy

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]

Extra Containers

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 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).

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: 5432

Dependency Result Passing

When 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:

VariableTypeDescription
{{index .Deps "<name>" "Results" "<key>"}}stringA specific key-value result from the dependency (e.g., branch, commit, pr)
{{index .Deps "<name>" "Outputs"}}[]stringRaw output lines from the dependency
{{index .Deps "<name>" "Name"}}stringThe 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.

Task Credential Secret Format

The secret referenced by spec.credentials.secretRef.name must contain a single key whose name depends on spec.type and spec.credentials.type:

Agent typeCredential typeSecret key
claude-codeapi-keyANTHROPIC_API_KEY
claude-codeoauthCLAUDE_CODE_OAUTH_TOKEN
codexapi-keyCODEX_API_KEY
codexoauthCODEX_AUTH_JSON (full ~/.codex/auth.json content)
geminiapi-key or oauthGEMINI_API_KEY
opencodeapi-key or oauthOPENCODE_API_KEY
cursorapi-key or oauthCURSOR_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.

Codex OAuth Token Refresh

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=true

For 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.

WorkerSpec

spec.worker on a Task (or spec.taskTemplate.worker on a TaskSpawner) is the preferred way to define execution environment. Mutually exclusive with workerPoolRef.

FieldDescriptionRequired
worker.typeAgent type (claude-code, codex, gemini, opencode, or cursor)Yes for inline Task execution (CEL-enforced)
worker.credentials.typeapi-key, oauth, or noneYes for inline Task execution (CEL-enforced)
worker.credentials.secretRef.nameSecret name (not required when type is none)Conditional
worker.modelModel override passed as KELOS_MODELNo
worker.effortReasoning effort passed as KELOS_EFFORTNo
worker.imageCustom agent image overrideNo
worker.workspaceRef.nameName of a Workspace resourceNo
worker.agentConfigRefs[].nameOrdered AgentConfig resources. Configs are merged in orderNo
worker.podOverridesPod customization (same fields as the legacy spec.podOverrides)No

Session

A Session is one interactive Claude Code, Codex, or OpenCode conversation backed by a one-replica StatefulSet. The StatefulSet's headless governing Service is used only for Kubernetes workload identity; web and terminal clients still connect through the Session control path. The spec is immutable. Conversation events stay out of the Kubernetes API and are retained on the Session workspace.

FieldDescriptionRequired
spec.worker.typeAgent provider: claude-code, codex, or opencodeYes
spec.worker.credentialsProvider credentials (api-key, oauth, or none)Yes
spec.worker.modelProvider model overrideNo
spec.worker.effortProvider reasoning-effort overrideNo
spec.worker.imageAgent image override implementing the Session image contractNo
spec.worker.workspaceRef.nameWorkspace cloned into the Session PodNo
spec.worker.agentConfigRefs[].nameOrdered AgentConfig resourcesNo
spec.worker.podOverridesPod resources, scheduling, environment, volumes, and sidecarsNo
spec.volumeClaimTemplatePersistentVolumeClaimSpec for the Session workspace; omit to use emptyDirNo
status.phaseInfrastructure phase: Pending, Ready, or FailedOutput
status.podNameSession Pod nameOutput
status.podUIDIdentity of the Pod running the live conversationOutput

Use kelos session connect NAME for terminal chat. Web chat is served by the optional shared kelos-session-server; both clients use the same event stream and provider conversation. Both clients can stream agent and tool activity, answer user-input requests, and interrupt active work without ending the provider conversation. If the Pod is deleted or evicted, the StatefulSet creates a replacement. Web and terminal clients reconnect to it. Work active at the time of failure is reported as interrupted and is not submitted again automatically. The terminal client also does not retry a request whose delivery cannot be confirmed; it reports that uncertainty so the user can decide whether to submit it again.

When spec.volumeClaimTemplate is set, the StatefulSet provisions the Session workspace from that template and reuses it across Pod replacement. Built-in workspace initialization is skipped after the persistent workspace has been initialized, while Workspace.spec.setupCommand runs in each replacement container. Deleting the Session deletes the claim; PersistentVolume retention afterward follows the StorageClass reclaim policy. When the field is omitted, the workspace uses emptyDir, so conversation history and workspace changes do not survive Pod replacement.

The shared web server can create, list, delete, and connect to Sessions across namespaces while the web application operates on one active namespace at a time. Users can switch the active namespace live from the sidebar. sessionServer.defaultNamespace sets its initial value, and Session, Workspace, AgentConfig, and credential options are loaded only from the active namespace. The creation form accepts provider, credentials, model, Workspace, AgentConfig references, and an optional persistent volume claim. YAML mode server-side applies one kelos.dev/v1alpha2 Session manifest in the active namespace with the same worker fields as the form. The manifest may also include labels, annotations, and an optional persistent volume claim. Image and Pod overrides require direct Kubernetes API access governed by Kubernetes RBAC.

WorkerPool

A WorkerPool manages a fleet of persistent worker pods backed by a StatefulSet. Tasks reference a WorkerPool via spec.workerPoolRef to execute on pre-warmed infrastructure instead of creating per-task Jobs.

A WorkerPool's Workspace may use either a PAT-style or a GitHub App secret. For App secrets the controller mints a short-lived installation token into a <pool-name>-github-token Secret and re-mints it before expiry (see Workspace authentication), so long-lived worker pods keep a valid token without restarts.

FieldDescriptionRequired
spec.worker.typeAgent typeYes
spec.worker.credentialsCredentials for the workersYes
spec.worker.workspaceRef.nameWorkspace referenceYes
spec.worker.modelDefault model for workersNo
spec.worker.effortDefault effort for workersNo
spec.worker.imageCustom agent imageNo
spec.worker.agentConfigRefs[].nameAgentConfig referencesNo
spec.worker.podOverridesPod customization for worker podsNo
spec.replicasNumber of persistent worker pods (defaults to 1)No
spec.volumeClaimTemplatePersistentVolumeClaimSpec for each worker pod's storageYes

Workspace

FieldDescriptionRequired
spec.repoGit repository URL to clone (HTTPS, git://, or SSH)Yes
spec.refBranch, tag, or commit SHA to checkout (defaults to repo's default branch)No
spec.secretRef.nameSecret containing credentials for git auth and gh CLI (see authentication methods below)No
spec.ghproxyEnables the workspace-scoped ghproxy when set to {}; omitted or null disables itNo
spec.remotes[].nameGit remote name to add after cloning (must not be "origin")Yes (per remote)
spec.remotes[].urlGit remote URLYes (per remote)
spec.files[].pathRelative file path inside the repository (e.g., CLAUDE.md)Yes (per file)
spec.files[].contentFile content to writeYes (per file)
spec.setupCommandExec-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

Set spec.ghproxy: {} only for Workspaces that should run a workspace-scoped ghproxy. Existing Workspaces that need to keep ghproxy after upgrading must add that field; omitting it removes workspace ghproxy resources.

Workspace Setup Command

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.files entries 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/repo as the agent UID (61100), with access to all built-in Kelos env vars and any Task.spec.podOverrides.env entries 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>"].

Workspace Authentication

The workspace secret referenced by spec.secretRef.name supports two authentication methods:

Personal Access Token (PAT):

The secret contains a single key:

KeyDescription
GITHUB_TOKENGitHub 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:

KeyDescription
appIDGitHub App ID
installationIDGitHub App installation ID for the target organization
privateKeyPEM-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.pem

GitHub 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.

The installation token is minted to a derived Secret and mounted into the agent pod as a file at /kelos/github-token/GITHUB_TOKEN. For per-Task Jobs the Secret is <task-name>-github-token (minted at admission); for WorkerPools it is <pool-name>-github-token (minted on first reconcile). In both cases the controller re-mints the token before it expires and updates the Secret in place. The kubelet syncs the file contents automatically, and the agent image's git credential helper and gh wrapper read the file on each invocation, so runs that last longer than the ~1h installation-token TTL keep working without a pod restart. This makes GitHub App secrets usable for persistent WorkerPools, not just ephemeral Task pods.

AgentConfig

FieldDescriptionRequired
spec.agentsMDAgent 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[].namePlugin name (used as directory name and namespace)Yes (per plugin)
spec.plugins[].skills[].nameSkill name (becomes skills/<name>/SKILL.md)Yes (per skill)
spec.plugins[].skills[].contentSkill content (markdown with frontmatter)Yes (per skill)
spec.plugins[].agents[].nameAgent name (becomes agents/<name>.md)Yes (per agent)
spec.plugins[].agents[].contentAgent content (markdown with frontmatter)Yes (per agent)
spec.skills[].sourceskills.sh package in owner/repo format for github.com (e.g., vercel-labs/agent-skills) or a full HTTPS git URL for private/GitHub Enterprise Server repositories (e.g., https://ghe.example.com/org/private-skills.git). 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[].nameYes (per skill)
spec.skills[].skillSpecific skill name from the package (installs all if omitted)No
spec.skills[].secretRef.nameSecret in the Task namespace containing a GITHUB_TOKEN key for HTTPS token auth when installing private skills.sh packages. Missing Secrets, missing GITHUB_TOKEN, or empty tokens fail the Task before Job creation. SSH deploy keys are not supported by this fieldNo
spec.mcpServers[].nameMCP server name (used as key in agent config)Yes (per server)
spec.mcpServers[].typeTransport type: stdio, http, or sseYes (per server)
spec.mcpServers[].commandExecutable to run (stdio only)No
spec.mcpServers[].argsCommand-line arguments (stdio only)No
spec.mcpServers[].urlServer endpoint (http/sse only)No
spec.mcpServers[].headersHTTP headers (http/sse only)No
spec.mcpServers[].headersFrom.secretRef.nameSecret whose data keys become HTTP header names and values (http/sse only). Values from headersFrom override headers on key conflictsNo
spec.mcpServers[].envEnvironment variables for the server process (stdio only), as an array of Kubernetes EnvVar objects. Literal entries use name and valueNo
spec.mcpServers[].env[].valueFrom.secretKeyRefSecret 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 TaskNo
spec.mcpServers[].env[].valueFrom.configMapKeyRefConfigMap 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 TaskNo
spec.mcpServers[].env[].valueFromOnly secretKeyRef and configMapKeyRef are supported for MCP server env. Other Kubernetes EnvVarSource variants are rejected when a Task consumes the AgentConfigNo
spec.mcpServers[].envFrom.secretRef.nameSecret whose data keys become stdio MCP environment variable names and values. Values from envFrom override inline env on key conflictsNo

TaskSpawner

FieldDescriptionRequired
spec.taskTemplate.workspaceRef.nameWorkspace resource (repo URL, auth, and clone target for spawned Tasks)Yes (when using githubIssues, githubPullRequests, githubWebhook, linearWebhook, or webhook)
spec.when.githubIssues.repoOverride repository to poll for issues (in owner/repo format or full URL); defaults to workspace repo URLNo
spec.when.githubIssues.labelsFilter issues by labelsNo
spec.when.githubIssues.excludeLabelsExclude issues with these labelsNo
spec.when.githubIssues.stateFilter by state: open, closed, all (default: open)No
spec.when.githubIssues.typesFilter by type: issues, pulls (default: issues)No
spec.when.githubIssues.commentPolicy.triggerCommentRequires a matching command in the issue body or comments to include the issueNo
spec.when.githubIssues.commentPolicy.excludeCommentsBlocks items whose most recent matching command is an exclude commentNo
spec.when.githubIssues.commentPolicy.allowedUsersRestrict comment control to specific GitHub usernamesNo
spec.when.githubIssues.commentPolicy.allowedTeamsRestrict comment control to specific GitHub teams in org/team-slug formatNo
spec.when.githubIssues.commentPolicy.minimumPermissionMinimum repo permission required for comment control: read, triage, write, maintain, or adminNo
spec.when.githubIssues.assigneeFilter by assignee username; use "*" for any assignee or "none" for unassignedNo
spec.when.githubIssues.authorFilter by issue author usernameNo
spec.when.githubIssues.excludeAuthorsExclude issues created by any of these usernames (client-side)No
spec.when.githubIssues.priorityLabelsPriority-order labels for task selection when maxConcurrency is set; index 0 is highest priorityNo
spec.when.githubIssues.reporting.enabledPost status comments (started, succeeded, failed) back to the GitHub issueNo
spec.when.githubIssues.pollIntervalPer-source poll interval (e.g., "30s", "5m"). Defaults to 5m when omittedNo
spec.when.githubPullRequests.repoOverride repository to poll for PRs (in owner/repo format or full URL); defaults to workspace repo URLNo
spec.when.githubPullRequests.labelsFilter pull requests by labelsNo
spec.when.githubPullRequests.excludeLabelsExclude pull requests with these labelsNo
spec.when.githubPullRequests.stateFilter by state: open, closed, all (default: open)No
spec.when.githubPullRequests.reviewStateFilter by aggregated review state: approved, changes_requested, any (default: any)No
spec.when.githubPullRequests.commentPolicy.triggerCommentRequires a matching command in the PR body or comments to include the PRNo
spec.when.githubPullRequests.commentPolicy.excludeCommentsBlocks PRs whose most recent matching command is an exclude commentNo
spec.when.githubPullRequests.commentPolicy.allowedUsersRestrict comment control to specific GitHub usernamesNo
spec.when.githubPullRequests.commentPolicy.allowedTeamsRestrict comment control to specific GitHub teams in org/team-slug formatNo
spec.when.githubPullRequests.commentPolicy.minimumPermissionMinimum repo permission required for comment control: read, triage, write, maintain, or adminNo
spec.when.githubPullRequests.authorFilter by PR author usernameNo
spec.when.githubPullRequests.excludeAuthorsExclude PRs opened by any of these usernames (client-side)No
spec.when.githubPullRequests.draftFilter by draft stateNo
spec.when.githubPullRequests.priorityLabelsPriority-order labels for task selection when maxConcurrency is set; index 0 is highest priorityNo
spec.when.githubPullRequests.reporting.enabledPost status comments (started, succeeded, failed) back to the GitHub pull requestNo
spec.when.githubPullRequests.reporting.checks.nameCreates 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.filePatterns.includeDoublestar globs for changed files to include after exclude patterns are removed. When omitted, any remaining changed file passesNo
spec.when.githubPullRequests.filePatterns.excludeDoublestar globs for changed files to remove before include matching. A PR with no remaining changed files is skippedNo
spec.when.githubPullRequests.pollIntervalPer-source poll interval (e.g., "30s", "5m"). Defaults to 5m when omittedNo
spec.when.githubWebhook.eventsGitHub event types to listen for (e.g., "issues", "pull_request", "push", "issue_comment")Yes (when using githubWebhook)
spec.when.githubWebhook.repositoryRestrict webhooks to a specific repository (owner/repo format); if empty, webhooks from any repository are acceptedNo
spec.when.githubWebhook.excludeAuthorsExclude webhook events sent by any of these usernames; applied before filter evaluationNo
spec.when.githubWebhook.filters[].eventGitHub event type this filter applies toYes (per filter)
spec.when.githubWebhook.filters[].actionFilter by webhook action (e.g., "opened", "created", "submitted")No
spec.when.githubWebhook.filters[].labelsRequire the issue/PR to have all of these labelsNo
spec.when.githubWebhook.filters[].excludeLabelsExclude issues/PRs with any of these labelsNo
spec.when.githubWebhook.filters[].stateFilter by issue/PR state ("open", "closed")No
spec.when.githubWebhook.filters[].branchFilter push and create (ref_type=branch) events by branch name (exact match or glob)No
spec.when.githubWebhook.filters[].tagFilter create (ref_type=tag) and release events by tag name (exact match or glob)No
spec.when.githubWebhook.filters[].draftFilter PRs by draft statusNo
spec.when.githubWebhook.filters[].authorFilter by the event sender's usernameNo
spec.when.githubWebhook.filters[].excludeAuthorsExclude events sent by any of these usernamesNo
spec.when.githubWebhook.filters[].filePatterns.includeDoublestar globs for changed files to include after exclude patterns are removed. Applies to push and pull_request webhook filtersNo
spec.when.githubWebhook.filters[].filePatterns.excludeDoublestar globs for changed files to remove before include matching. Events with no remaining changed files are skippedNo
spec.when.githubWebhook.filters[].bodyContainsDeprecated. Filter by case-sensitive substring match on the comment/review body. Use bodyPattern insteadNo
spec.when.githubWebhook.filters[].bodyPatternRequire 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 entryNo
spec.when.githubWebhook.filters[].excludeBodyPatternsExclude events whose comment/review body matches any of these Go re2 regular expressions (OR semantics)No
spec.when.githubWebhook.filters[].commentOnScope issue_comment events to comments posted on a specific subject: "Issue" matches plain issues, "PullRequest" matches pull requests. Empty matches both. Ignored for other eventsNo
spec.when.githubWebhook.reporting.enabledPost status comments (started, succeeded, failed) back to the originating issue or PRNo
spec.when.githubWebhook.reporting.checks.nameCreates 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.typesLinear resource types to listen for (e.g., "Issue", "Comment")Yes (when using linearWebhook)
spec.when.linearWebhook.filters[].typeScope filter to a specific resource typeNo
spec.when.linearWebhook.filters[].actionFilter by webhook action: create, update, or removeNo
spec.when.linearWebhook.filters[].statesFilter by workflow state names (e.g., "Todo", "In Progress")No
spec.when.linearWebhook.filters[].labelsRequire the issue to have all of these labelsNo
spec.when.linearWebhook.filters[].excludeLabelsExclude issues with any of these labelsNo
spec.when.slack.channelsRestrict which Slack channels the bot listens in (channel IDs like "C0123456789"); when empty, listens in all invited channelsNo
spec.when.slack.botMessagePolicyControls 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 loopsNo
spec.when.slack.triggers[].patternRE2 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 firesNo
spec.when.slack.triggers[].mentionOptionalWhen true, fire on pattern match alone without requiring a bot @-mentionNo
spec.when.slack.excludePatternsRE2 regex patterns that reject messages when any pattern matches (OR semantics); leading <@USER_ID> mentions are stripped before matching; does not apply to slash commandsNo
spec.when.webhook.sourceShort identifier for the generic webhook source (lowercase alphanumeric with optional hyphens). Determines the URL path (/webhook/<source>). The endpoint is currently unauthenticated — see #1040Yes (when using webhook)
spec.when.webhook.fieldMappingMap 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[].fieldJSONPath expression selecting the payload field to matchYes (per filter)
spec.when.webhook.filters[].valueRequire an exact string match against the extracted field value (mutually exclusive with pattern)Conditional
spec.when.webhook.filters[].patternRequire a regex match against the extracted field value (mutually exclusive with value)Conditional
spec.when.jira.pollIntervalPer-source poll interval (e.g., "30s", "5m"). Defaults to 5m when omittedNo
spec.when.cron.scheduleCron schedule expression (e.g., "0 * * * *")Yes (when using cron)
spec.taskTemplate.workerExecution environment for spawned Tasks (see WorkerSpec). When used alone, spawned Tasks create Jobs. Mutually exclusive with workerPoolRefOne of worker, workerPoolRef, or type+credentials
spec.taskTemplate.workerPoolRef.nameWorkerPool for persistent executionOne of worker, workerPoolRef, or type+credentials
spec.taskTemplate.type(Deprecated) Agent type — use taskTemplate.worker.type insteadLegacy
spec.taskTemplate.credentials(Deprecated) Credentials — use taskTemplate.worker.credentials insteadLegacy
spec.taskTemplate.model(Deprecated) Model override — use taskTemplate.worker.model insteadLegacy
spec.taskTemplate.effort(Deprecated) Reasoning effort — use taskTemplate.worker.effort insteadLegacy
spec.taskTemplate.image(Deprecated) Custom agent image — use taskTemplate.worker.image insteadLegacy
spec.taskTemplate.workspaceRef.name(Deprecated) Workspace reference — use taskTemplate.worker.workspaceRef insteadLegacy
spec.taskTemplate.agentConfigRefs[].name(Deprecated) AgentConfig references — use taskTemplate.worker.agentConfigRefs insteadLegacy
spec.taskTemplate.promptTemplateGo text/template for prompt (see template variables below)No
spec.taskTemplate.dependsOnTask names that spawned Tasks depend on. Not supported with workerPoolRefNo
spec.taskTemplate.branchGit branch template for spawned Tasks (supports Go template variables, e.g., kelos-task-{{.Number}}). Not supported with workerPoolRefNo
spec.taskTemplate.ttlSecondsAfterFinishedAuto-delete spawned tasks after N secondsNo
spec.taskTemplate.podFailurePolicyKubernetes Job pod failure policy copied to spawned Tasks as Task.spec.podFailurePolicyNo
spec.taskTemplate.podOverrides(Deprecated) Pod customization — use taskTemplate.worker.podOverrides insteadLegacy
spec.taskTemplate.metadata.labelsLabels 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 keyNo
spec.taskTemplate.metadata.annotationsAnnotations 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 valuesNo
spec.taskTemplate.contextSourcesExternal 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 uniqueNo
spec.taskTemplate.upstreamRepoUpstream 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 workflowsNo
spec.maxConcurrencyLimit max concurrent running tasks (important for cost control)No
spec.maxTotalTasksLifetime limit on total tasks created by this spawnerNo
spec.suspendPause the spawner without deleting it; resume with spec.suspend: false (default: false)No

Manual Task Creation

Run a standalone Task from any TaskSpawner's taskTemplate:

kelos run --from taskspawner/daily-audit
kelos run --from taskspawner/issue-worker -f values.yaml

The values file is a YAML or JSON object whose top-level keys are exposed directly to the Go template. Use -f - to read it from stdin. For example:

ID: "42"
Number: 42
Title: Fix the login timeout
Body: Reproduce and fix the timeout under load.
Kind: Issue

Kelos supplies TriggerType: manual and the current UTC time as TriggerTime. Cron TaskSpawners also receive the current UTC time as Time and their configured expression as Schedule; these cron values cannot be overridden by -f. A static template or a cron template that only uses the supplied defaults does not require a values file. Rendering fails if a template uses a key that is not supplied.

Manual creation bypasses source filters and creates a standalone Task. It does not apply spec.suspend, spec.maxConcurrency, or spec.maxTotalTasks, does not enable source reporting, and does not update TaskSpawner status. The Task has no TaskSpawner owner reference or kelos.dev/taskspawner label. Instead, Kelos records its origin with kelos.dev/created-from-taskspawner, kelos.dev/trigger-type, and kelos.dev/trigger-time annotations.

Configured contextSources are fetched after the values are resolved, matching automatic Task creation. --dry-run still connects to the cluster to read the TaskSpawner and any Secrets referenced by context sources.

promptTemplate Variables

The promptTemplate field uses Go text/template syntax. Available variables depend on the source type:

VariableDescriptionGitHub IssuesGitHub Pull RequestsGitHub WebhookJiraLinear WebhookGeneric WebhookCron
{{.ID}}Unique identifierIssue/PR number as string (e.g., "42")Pull request number as stringIssue/PR number or commit IDJira issue key (e.g., "ENG-42")Linear resource IDMapped id field (required)Date-time string (e.g., "20260207-0900")
{{.Number}}Issue or PR numberIssue/PR number (e.g., 42)Pull request numberIssue/PR number (when available)Numeric suffix of the Jira key (e.g., 42 for ENG-42); 0 if the key has no -N suffixEmptyEmpty0
{{.Title}}Title of the work itemIssue/PR titlePull request titleIssue/PR title or "Push to <branch>"Issue summaryResource titleMapped title field (if present)Trigger time (RFC3339)
{{.Body}}Body textIssue/PR bodyPull request bodyIssue/PR bodyEmpty (description is not fetched; tracked in #990)EmptyMapped body field (if present)Empty
{{.URL}}URL to the source itemGitHub HTML URLGitHub PR URLIssue/PR HTML URLJira browse URL (e.g., https://your-org.atlassian.net/browse/ENG-42)EmptyMapped url field (if present)Empty
{{.Labels}}Comma-separated labelsIssue/PR labelsPull request labelsEmptyIssue labelsIssue labelsEmptyEmpty
{{.Comments}}Concatenated commentsIssue/PR commentsPR conversation commentsEmptyIssue commentsEmptyEmptyEmpty
{{.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 typeEmptyEmptyEvent type (e.g., "issues", "pull_request", "push")EmptyEmptyEmptyEmpty
{{.Action}}Webhook actionEmptyEmptyAction (e.g., "opened", "created", "submitted")EmptyAction (e.g., "create", "update", "remove")EmptyEmpty
{{.Sender}}Event sender usernameEmptyEmptyUsername of person who triggered the eventEmptyEmptyEmptyEmpty
{{.Branch}}Git branch to updateEmptyPR head branch (e.g., "kelos-task-42")PR source branch or push branchEmptyEmptyEmptyEmpty
{{.Ref}}Git refEmptyEmptyGit ref for push events (e.g., "refs/heads/main") or create events (ref name)EmptyEmptyEmptyEmpty
{{.Tag}}Tag nameEmptyEmptyTag name for create (ref_type=tag) and release eventsEmptyEmptyEmptyEmpty
{{.RefType}}Ref type for create eventsEmptyEmpty"branch", "tag", or "repository" (create events only)EmptyEmptyEmptyEmpty
{{.Repository}}Full repository nameEmptyEmptyRepository in owner/repo formatEmptyEmptyEmptyEmpty
{{.RepositoryOwner}}Repository ownerEmptyEmptyRepository owner loginEmptyEmptyEmptyEmpty
{{.RepositoryName}}Repository nameEmptyEmptyRepository name onlyEmptyEmptyEmptyEmpty
{{.Payload}}Raw event payloadEmptyEmptyFull parsed GitHub webhook payloadEmptyFull parsed Linear webhook payloadFull parsed JSON bodyEmpty
{{.ReviewState}}Aggregated review stateEmptyapproved, changes_requested, or emptyEmptyEmptyEmptyEmptyEmpty
{{.ReviewComments}}Formatted inline review commentsEmptyInline PR review commentsEmptyEmptyEmptyEmptyEmpty
{{.Type}}Resource typeEmptyEmptyEmptyEmptyResource type (e.g., "Issue", "Comment")EmptyEmpty
{{.State}}Workflow stateEmptyEmptyEmptyEmptyCurrent state name (e.g., "Todo", "In Progress")EmptyEmpty
{{.IssueID}}Parent issue IDEmptyEmptyEmptyEmptyParent issue ID (Comment events only)EmptyEmpty
{{.CommentBody}}Comment or review bodyEmptyEmptyComment/review body (issue_comment, pull_request_review, pull_request_review_comment events)EmptyEmptyEmptyEmpty
{{.CommentURL}}Comment or review URLEmptyEmptyComment/review HTML URL (issue_comment, pull_request_review, pull_request_review_comment events)EmptyEmptyEmptyEmpty
{{.Time}}Trigger time (RFC3339)EmptyEmptyEmptyEmptyEmptyEmptyCron tick time (e.g., "2026-02-07T09:00:00Z")
{{.Schedule}}Cron schedule expressionEmptyEmptyEmptyEmptyEmptyEmptySchedule string (e.g., "0 * * * *")

Generic Webhook only: any additional keys declared in spec.when.webhook.fieldMapping are also exposed as top-level template variables (e.g., fieldMapping: {severity: "$.level"} makes {{.severity}} available).

Context sources: when spec.taskTemplate.contextSources is configured, each entry's fetched value is exposed as {{.Context.NAME}} (e.g., a source named jira is available as {{.Context.jira}}). The same .Context map is also available in spec.taskTemplate.branch and spec.taskTemplate.metadata templates. See Context Sources for details.

Context Sources

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_]*$.

FieldDescriptionRequired
spec.taskTemplate.contextSources[].nameIdentifier used as the template key ({{.Context.<name>}}). Must match ^[a-zA-Z][a-zA-Z0-9_]*$, 1–64 charactersYes
spec.taskTemplate.contextSources[].httpHTTP(S) source configuration. Currently the only supported source kind; exactly one source kind must be setYes
spec.taskTemplate.contextSources[].http.urlEndpoint 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 setYes (per source)
spec.taskTemplate.contextSources[].http.methodHTTP method: GET or POST (default: GET)No
spec.taskTemplate.contextSources[].http.headersStatic HTTP headers. Values support Go text/template variables from the work itemNo
spec.taskTemplate.contextSources[].http.headersFromHTTP 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 entriesNo
spec.taskTemplate.contextSources[].http.bodyRequest body template (Go text/template); used with POSTNo
spec.taskTemplate.contextSources[].http.responseFilter.typeFilter language for extracting a subset of the response. Currently only JSONPath is supportedNo
spec.taskTemplate.contextSources[].http.responseFilter.expressionFilter expression (e.g., $.data.value for JSONPath). When set, only the extracted value is stored; otherwise the entire response body is usedConditional
spec.taskTemplate.contextSources[].http.allowInsecurePermit plain HTTP (non-TLS) URLs (default: false)No
spec.taskTemplate.contextSources[].http.timeoutSecondsPer-request timeout in seconds, 1–60 (default: 10)No
spec.taskTemplate.contextSources[].http.maxResponseBytesMaximum response body size in bytes, 1–131072 (default: 32768, i.e. 32 KiB). Caps the amount injected into the promptNo
spec.taskTemplate.contextSources[].failurePolicyBehavior 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}}

Task Status

FieldDescription
status.phaseCurrent phase: Pending, Waiting, Running, Succeeded, or Failed
status.jobNameName of the Job created for this Task
status.podNameName of the Pod running the Task
status.startTimeWhen the Task started running
status.completionTimeWhen the Task completed
status.messageAdditional information about the current status
status.outputsAutomatically captured outputs: branch, commit, base-branch, pr, cost-usd, input-tokens, output-tokens
status.resultsParsed key-value map from outputs (e.g., results.branch, results.commit, results.pr, results.input-tokens)
status.usage.costUSDReported agent cost in USD (non-negative resource.Quantity). Parsed from results["cost-usd"]
status.usage.inputTokensNumber of input tokens consumed (non-negative integer). Parsed from results["input-tokens"]
status.usage.outputTokensNumber of output tokens produced (non-negative integer). Parsed from results["output-tokens"]
status.conditionsStandard Kubernetes conditions. Includes BudgetBlocked when a matching TaskBudget has been exceeded

TaskBudget

TaskBudget defines observed-spend admission limits for Tasks. When a Task's labels match a TaskBudget's taskSelector and the accumulated spend in the current period meets or exceeds a limit, the Task stays in Waiting phase with a BudgetBlocked condition until the period resets.

FieldDescriptionRequired
spec.taskSelectorLabel selector matching Tasks and TaskRecords in the same namespace. An empty selector ({}) selects all TasksYes
spec.period.typePeriod boundary for budget accounting. Currently only Daily is supportedYes
spec.period.timezoneIANA timezone for period boundaries (default: UTC). Rejected at create/update if not a loadable IANA zoneNo
spec.maxCostUSDMaximum observed cost in USD admitted per period (non-negative resource.Quantity)At least one limit required
spec.maxInputTokensMaximum input tokens admitted per period (non-negative integer)At least one limit required
spec.maxOutputTokensMaximum output tokens admitted per period (non-negative integer)At least one limit required

TaskBudget Status

FieldDescription
status.observedGenerationMost recent generation observed by the controller
status.currentPeriodStartInclusive start of the current accounting period
status.currentPeriodEndExclusive end of the current accounting period
status.used.costUSDSummed cost from matching TaskRecords in the current period
status.used.inputTokensSummed input tokens from matching TaskRecords in the current period
status.used.outputTokensSummed output tokens from matching TaskRecords in the current period
status.conditionsIncludes Degraded when the budget hits an operational error (e.g. a list error while summing usage)

Budget Admission Behavior

  • A Task is checked against all TaskBudgets in its namespace before it starts — before Job creation for Job-backed Tasks, and before worker-pod assignment for Tasks using spec.workerPoolRef.
  • A budget matches if its taskSelector selects the Task's labels.
  • If any matching budget's limit is met or exceeded (using >= comparison), the Task is blocked.
  • spec.taskSelector operator/value combinations that the controller cannot compile, and timezones that are not loadable IANA zones, are rejected at create/update time — so a malformed selector or timezone cannot be admitted.
  • List errors when summing usage block admission (fail closed) and set a Degraded condition on the budget.
  • The Degraded condition is cleared automatically after a successful evaluation.
  • A zero limit (e.g., maxOutputTokens: 0) blocks all matching Tasks immediately.
  • status.used is refreshed both during admission and by a dedicated controller when matching TaskRecords change, and it resets when the accounting period rolls over.

TaskRecord

TaskRecord is an immutable terminal record for a completed Task that reported usage data. It preserves accounting data after the Task itself is deleted by TTL. Tasks that complete without usage (e.g., nil or missing usage output) do not generate a TaskRecord. The record name is derived from the Task UID to guarantee uniqueness. No ownerReference is set so garbage collection does not remove it.

FieldDescriptionRequired
spec.taskRef.nameName of the source TaskYes
spec.taskRef.uidUID of the source TaskYes
spec.typeEffective agent type of the Task (Task.spec.worker.type, falling back to Task.spec.type)No
spec.modelEffective model of the Task (Task.spec.worker.model, falling back to Task.spec.model)No
spec.phaseTerminal Task phase (Succeeded or Failed)Yes
spec.startTimeWhen the Task started runningNo
spec.completionTimeWhen the Task completedYes
spec.usage.costUSDReported cost in USDNo
spec.usage.inputTokensInput tokens consumedNo
spec.usage.outputTokensOutput tokens producedNo
spec.ttlSecondsAfterCompletionSeconds after completionTime before automatic deletion. If unset, the record is retained indefinitely. Controller-created records set this to 30 daysNo

TaskSpawner Status

FieldDescription
status.phaseCurrent phase: Pending, Running, Suspended, or Failed
status.deploymentNameName of the Deployment running the spawner (polling-based sources)
status.cronJobNameName of the CronJob running the spawner (cron-based sources)
status.totalDiscoveredTotal number of items discovered from the source
status.totalTasksCreatedTotal number of Tasks created by this spawner
status.activeTasksNumber of currently active (non-terminal) Tasks
status.lastDiscoveryTimeLast time the source was polled
status.messageAdditional information about the current status
status.conditionsStandard Kubernetes conditions for detailed status

Configuration

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

Credentials

FieldDescription
oauthTokenOAuth token — Kelos auto-creates the Kubernetes secret. Use none for an empty credential
apiKeyAPI 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
credentialTypeCredential type when using secret (api-key or oauth)

Precedence: --secret flag > secret in config > oauthToken/apiKey in config.

Workspace

The workspace field supports two forms:

Reference an existing Workspace resource by name:

workspace:
  name: my-workspace

Specify 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 CLI

Specify 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
FieldDescription
workspace.nameName of an existing Workspace resource
workspace.repoGit repository URL — Kelos auto-creates a Workspace resource
workspace.refGit reference (branch, tag, or commit SHA)
workspace.tokenGitHub PAT — Kelos auto-creates the secret and injects GITHUB_TOKEN
workspace.githubApp.appIDGitHub App ID
workspace.githubApp.installationIDGitHub App installation ID
workspace.githubApp.privateKeyPathPath 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.

Other Settings

FieldDescription
typeDefault agent type (claude-code, codex, gemini, opencode, or cursor)
modelDefault model override
effortDefault agent reasoning effort
namespaceDefault Kubernetes namespace
agentConfigDefault AgentConfig resource name

Environment Variables

The env field defines additional environment variables injected into task pods via Task.spec.podOverrides.env. CLI --env flags take precedence over config values on name collision.

FieldDescription
env[].nameVariable name (must match [A-Za-z_][A-Za-z0-9_]*)
env[].valuePlain-text value (mutually exclusive with valueFrom)
env[].valueFrom.secretKeyRefReference a Kubernetes Secret (name and key required). Resolves in the Task pod's namespace.
env[].valueFrom.configMapKeyRefReference a Kubernetes ConfigMap (name and key required). Resolves in the Task pod's namespace.
env:
  - name: CLAUDE_CODE_USE_BEDROCK
    value: "1"
  - name: AWS_REGION
    value: us-west-2
  - name: MY_SECRET
    valueFrom:
      secretKeyRef:
        name: my-k8s-secret
        key: token
  - name: APP_CONFIG
    valueFrom:
      configMapKeyRef:
        name: my-configmap
        key: app.conf

CLI Reference

The kelos CLI lets you manage the full lifecycle without writing YAML.

Core Commands

CommandDescription
kelos installInstall Kelos CRDs and controller into the cluster
kelos uninstallUninstall Kelos from the cluster
kelos initInitialize ~/.kelos/config.yaml
kelos versionPrint version information
kelos completion <shell>Generate a shell completion script for bash, zsh, fish, or powershell

Resource Management

CommandDescription
kelos runCreate and run a new Task
kelos run --from taskspawner/<name>Run a standalone Task from a TaskSpawner template
kelos session connect NAMEContinue a ready Session through terminal chat
kelos create workspaceCreate a Workspace resource
kelos create agentconfigCreate an AgentConfig resource
kelos get <resource> [name]List resources or view a specific resource (tasks, sessions, taskspawners, workspaces, agentconfigs, workerpools)
kelos delete <resource> [name]Delete a resource (tasks, sessions, taskspawners, workspaces, agentconfigs, workerpools)
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

kelos install Flags

  • --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 Helm key=value syntax
  • --set-string: Set string chart values with Helm key=value syntax
  • --set-file: Set chart values from file contents with Helm key=path syntax
  • --version: Override the image tag used for controller and bundled agent images; shorthand for image.tag
  • --image-pull-policy: Set imagePullPolicy on controller-managed images
  • --disable-heartbeat: Do not install the telemetry heartbeat CronJob
  • --spawner-resource-requests: Resource requests for spawner containers as comma-separated name=value pairs
  • --spawner-resource-limits: Resource limits for spawner containers as comma-separated name=value pairs
  • --ghproxy-resource-requests: Resource requests for workspace ghproxy containers as comma-separated name=value pairs
  • --ghproxy-resource-limits: Resource limits for workspace ghproxy containers as comma-separated name=value pairs
  • --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-separated name=value pairs, for example cpu=10m,memory=64Mi
  • --controller-resource-limits: Resource limits for the controller container as comma-separated name=value pairs, for example cpu=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.

kelos run Flags

  • --prompt, -p: Task prompt (required unless --prompt-file or --from is set)
  • --prompt-file: Read task prompt from a file path; use - to read from stdin (mutually exclusive with --prompt)
  • --from: Run the Task template from a taskspawner/<name> reference
  • --values, -f: Read top-level template values from a YAML or JSON file; use - to read from stdin (requires --from)
  • --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 as NAME=VALUE (repeatable)
  • --watch, -w: Watch task status after creation
  • --secret: Pre-created secret name
  • --credential-type: Credential type when using --secret (default: api-key)
  • --dry-run: Render the Task without creating it; with --from, the TaskSpawner and any context-source Secrets are still read

kelos get Flags

  • --output, -o: Output format (yaml or json)
  • --detail, -d: Show detailed information for a specific resource
  • --all-namespaces, -A: List resources across all namespaces
  • --phase: (kelos get task only) Filter tasks by phase; repeatable or comma-separated. Valid values: Pending, Running, Waiting, Succeeded, Failed

kelos delete Flags

  • --all: Delete every resource of the given type in the namespace; mutually exclusive with a resource name. Supported by task, session, workspace, taskspawner, agentconfig, and workerpool subcommands

Common Flags

  • --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. For install, this prints controller manifests only; CRDs are staged separately during real installs
  • --yes, -y: Skip confirmation prompts

Shell Completion

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-Expression

To 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:

CommandCompletes
kelos logs <TAB>task names
kelos get task <TAB>task names
kelos get session <TAB>session names
kelos get taskspawner <TAB>taskspawner names
kelos get workspace <TAB>workspace names
kelos get agentconfig <TAB>agentconfig names
kelos get workerpool <TAB>workerpool names
kelos delete task <TAB>task names
kelos delete session <TAB>session names
kelos delete taskspawner <TAB>taskspawner names
kelos delete workspace <TAB>workspace names
kelos delete agentconfig <TAB>agentconfig names
kelos delete workerpool <TAB>workerpool names
kelos suspend taskspawner <TAB>taskspawner names
kelos resume taskspawner <TAB>taskspawner names
kelos session connect <TAB>session 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.

Prometheus Metrics

The Kelos controller and spawner pods expose Prometheus metrics on their /metrics endpoint.

Controller Metrics

MetricTypeLabelsDescription
kelos_task_created_totalCounternamespace, typeTotal Tasks for which a Job was created
kelos_task_completed_totalCounternamespace, type, phaseTotal Tasks that reached a terminal phase
kelos_task_duration_secondsHistogramnamespace, type, phaseDuration of Task execution from start to completion
kelos_task_cost_usd_totalCounternamespace, type, spawner, modelCumulative cost in USD of completed Tasks
kelos_task_input_tokens_totalCounternamespace, type, spawner, modelCumulative input tokens consumed by completed Tasks
kelos_task_output_tokens_totalCounternamespace, type, spawner, modelCumulative output tokens consumed by completed Tasks
kelos_reconcile_errors_totalCountercontrollerReconciliation errors

Spawner Metrics

Each spawner pod emits metrics scoped to its own TaskSpawner:

MetricTypeDescription
kelos_spawner_discovery_totalCounterCompleted discovery cycles
kelos_spawner_discovery_errors_totalCounterFailed discovery cycles
kelos_spawner_items_discovered_totalCounterWork items discovered
kelos_spawner_tasks_created_totalCounterTasks created by this spawner
kelos_spawner_discovery_duration_secondsHistogramDuration of discovery cycles

Telemetry

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:

DataDescription
Installation IDRandom UUID, generated once per cluster
Kelos versionInstalled controller version
Kubernetes versionCluster K8s version
Task countsTotal tasks, breakdown by type and phase
Feature adoptionNumber of TaskSpawners, AgentConfigs, Workspaces, and source types in use
ScaleNumber of namespaces with Kelos resources
Usage totalsAggregate cost (USD), input tokens, and output tokens

No personal data, repository names, prompts, or source code is collected.

Disabling Telemetry

Install (or reinstall) with the --disable-heartbeat flag:

kelos install --disable-heartbeat