Custom Resource Reference¶
All tatara custom resources live in the tatara.dev/v1alpha1 API group. There are six CRDs: two that you configure as an adopter, and four that the operator manages autonomously.
CRD taxonomy¶
Adopter-configured¶
You create and own these resources. The operator reads them but never overwrites spec fields.
| CRD | kubectl print columns | Description |
|---|---|---|
Project | Webhook | Top-level grouping: SCM provider, agent configuration, memory stack, queue policy, cron schedule |
Repository | Phase, Commit | A git remote ingested into tatara-memory; one per repo enrolled in a Project |
Operator-managed¶
The operator creates, updates, and garbage-collects these resources. Do not manually edit them in normal operation; editing .status directly will be overwritten on the next reconcile.
| CRD | kubectl print columns | Description |
|---|---|---|
Task | State, Park, Agent, Kind, Project, Turns, Description, Age | One durable unit of agent work, carried by a succession of agent pods across its state machine |
QueuedEvent | Seq, Class, Kind, State | Admission-queue entry; admitted into a pod-spawn (a new or existing Task) when a concurrency slot is available |
Issue (iss) | Task, Repo, Num, State, Status, Comments, Age | Mirror of one SCM issue: title, body, comments, and the platform's approval state. The bundle is rendered from this object, never a live forge call |
MergeRequest (mr) | Task, Repo, Num, State, Status, CI, Age | Mirror of one SCM pull/merge request: title, body, comments, CI status, mergeability |
API group and version¶
All resources are Namespaced. Deploy the operator and all CRs into the same namespace (typically tatara).
API stability
The v1alpha1 version signals that field names and defaults may change across releases. Pin your operator version in tatara-helmfile and review the changelog before upgrading.
Ownership and relationships¶
The diagram below is a summary. Issue and MergeRequest are multi-owned: any Task currently responsible for an artifact holds a plain owner ref, and exactly one owner - the Task responsible right now - carries controller=true. See Ownership, GC, and admission for the full treatment, including the controller-transfer rules.
graph TD
P["<b>Project</b><br/>(adopter-configured)"]
R["<b>Repository</b><br/>(adopter-configured)"]
QE["<b>QueuedEvent</b><br/>(operator-managed)"]
T1["<b>Task</b><br/>(controller owner)"]
T2["<b>Task</b><br/>(plain owner)"]
I["<b>Issue</b><br/>(mirror)"]
MR["<b>MergeRequest</b><br/>(mirror)"]
P -->|"owns 1..N"| R
P -->|"owns 0..N"| QE
P -->|"owns 0..N"| T1
P -->|"owns 0..N"| T2
QE -.->|"admitted, spawns a pod for"| T1
T1 ==>|"owns, controller=true"| I
T2 -->|"owns, plain ref"| I
T1 ==>|"owns, controller=true"| MR Key derivation rules:
- A
Projectmust exist before anyRepository,QueuedEvent,Task,Issue, orMergeRequestcan reference it. - A webhook event or cron scan produces a
QueuedEvent. The dispatcher admits it - either into an existingTask(a state-driven pod respawn) or by minting a new one - when a concurrency slot is available. IssueandMergeRequestare the mirror: the operator syncs them from the SCM and every agent read (scm_read(kind=issues|mr|comments)) is served from these objects, never a live forge call.scm_read(kind=ci)is the one exception - CI status is read live.- New-issue
implement-origin Tasks (SweepIssueKind- the roleclarifyused to play) are given a deterministic name so repeated webhook deliveries for the same issue collide onCreate(AlreadyExists) and stay idempotent. See Task naming.
Task kinds and scoping¶
Two enums, not one¶
A Task carries two kind-shaped fields and they mean different things. Conflating them is the single most common misreading of the model.
| Field | Meaning | Values |
|---|---|---|
Task.spec.kind | The origin. Why this Task exists. Immutable; baked into the Task name. | brainstorm, incident, implement, refine, review, documentation, takeover, upgrade |
Task.status.agentKind | The running agent. Which pod is executing right now. Changes as the Task advances through its state machine. | brainstorm, incident, implement, refine, review, documentation, upgrade - seven values |
implement is both an agent kind and an origin kind, since the #521 lifecycle redesign folded clarify into it on both fronts: a Task that started life with spec.kind: implement (a human filed an issue) runs the same implement agent for its approval-gate conversation and, once approved, for the code itself, then a review pod after that. One Task, one durable object, many pods.
| Origin kind | Scope | Description |
|---|---|---|
brainstorm | project | Surveys all project repos + external research; proposes a linked issue set across affected repos |
incident | project | Investigates a Grafana alert; files an evidence-backed incident proposal |
implement | project | Runs the triage/human conversation on a new or commented issue (the origin role clarify used to play), then writes the code once approved |
review | project | Reviews a human-authored PR/MR; can never itself reach under-implementation/merged - a human's PR is fixed by the human |
documentation | repo (docs repo) | Schedule-driven: updates docs when non-trivial changes have landed since the last run |
refine | project | Groom-only backlog peer: closes duplicates, dedups, recovers stalled Tasks |
takeover | project | Maintainer-gated hand-over of an existing, foreign-authored MR - minted straight into under-implementation, no triage and no gate: it owns zero Issues, and the maintainer's verified comment is the authorisation |
Only documentation is repo-scoped
repositoryRef is set only on documentation Tasks, which target one docs repo per run. Every other origin kind is project-scoped: the Task CR is a cross-repo umbrella, and its implement/review pods write back across every affected repo under that one Task.
Model and effort tiering (Project.spec.agent.modelByKind / effortByKind) keys on the agent kind, because that is what determines what the pod is about to do - see AgentSpec.
Conventions used in field tables¶
kubebuilder defaults¶
Fields annotated +kubebuilder:default=<value> are enforced at admission by the CRD validation webhook. The default is written into the object on create if the field is omitted, so kubectl get -o yaml always shows the effective value. Defaults are not applied retroactively on upgrade; existing objects keep their stored value.
Enum fields¶
Fields annotated +kubebuilder:validation:Enum=... are validated at admission. Only the listed values are accepted. The tables below list all valid enum values for each field.
Pointer fields (nil vs empty)¶
Several fields use pointer types (*bool, *[]string, *MemorySpec) to distinguish between "not set / inherit from parent" and "explicitly set to the zero value / empty":
*boolwith+kubebuilder:default=true: nil is treated astrueby the operator; setfalseexplicitly to disable.Repository.Spec.ReporterLogins *[]string: nil inherits the Project'sscm.reporterLogins; an explicit empty list[]opens intake for that repo only.Repository.Spec.MaintainerLogins *[]string: same inheritance pattern asReporterLogins.Project.Spec.Memory *MemorySpec: nil uses the type defaults (pgInstances: 1,pgStorage: 10Gi,neo4jStorage: 10Gi).
DEPRECATED fields¶
Several fields are retained for API backward-compatibility but have no effect. They are documented in the individual CRD pages and marked DEPRECATED. Do not set them in new configurations.
Status conditions¶
All CRDs with a status subresource expose status.conditions as []metav1.Condition (standard Kubernetes condition type). Use kubectl describe or the condition array to inspect readiness:
kubectl -n tatara get project my-project \
-o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.message}{"\n"}{end}'
kubectl printcolumns¶
The printcolumn markers define what kubectl get shows without -o yaml. All columns map to status.* fields so they reflect observed state, not desired spec:
# Task: shows Stage, Kind, Agent, Issues, MRs, Turns, Project, Description, Age at a glance
kubectl -n tatara get tasks
# Repository: shows ingest Phase and last ingested commit SHA
kubectl -n tatara get repositories
Project.Status fields at a glance¶
| Field | Type | Description |
|---|---|---|
webhookURL | string | Full webhook URL to register with GitHub/GitLab |
conditions | []metav1.Condition | Operator-set readiness conditions |
memory.phase | string | Phase of the per-project memory stack |
memory.endpoint | string | In-cluster LightRAG/memory URL |
memory.externalEndpoint | string | External URL when exposed |
grafana.phase | string | Phase of the grafana-mcp sidecar |
grafana.endpoint | string | In-cluster grafana-mcp endpoint |
lastIssueScan | *Time | Timestamp of most recent issue scan cycle |
lastBrainstorm | *Time | Timestamp of most recent brainstorm cycle |
lastDocumentation | *Time | Timestamp of most recent documentation cron cycle |
lastRefine | *Time | Timestamp of most recent refine pre-step |
lastUpgrade | *Time | Timestamp of most recent upgrade cron cycle |
tokenBudget | object | Token-budget accumulator/snapshot: the custom-window running total and the latest Claude-subscription usage snapshot reported by the wrapper. See Project reference. |
Three status timestamps were removed, not deprecated
lastMRScan, lastCDScan and lastHealthCheck are not in ProjectStatus or in the rendered CRD. They were previously listed here as read-only fields "kept for back-compat round-trip", which an absent field cannot be: it is pruned on write, silently. Their mechanisms went with them - mrScan was deleted in the 2026-07-13 redesign, there is no deploy-supervision backstop cron, and healthCheck no longer fires.
Task.status fields are documented on the Task reference.
QueuedEvent lifecycle¶
stateDiagram-v2
[*] --> Queued: producer enqueues
Queued --> Admitted: dispatcher finds a free slot, in (priority, seq) order
Admitted --> [*]: pod gone AND Task has left the requesting stage (or is terminal) The dispatcher evaluates the queue on every reconcile cycle. Capacity comes from Project.spec.maxConcurrentAgents (not a Task-count lever): the admission unit is one agent pod-spawn, not one Task. A Task advancing to another pod-spawning stage enqueues a new QueuedEvent, so every pod-spawn passes the same chokepoint - maxConcurrentAgents: 0 freezes the whole project mid-flight, including a Task already in flight.
Admission drains in ascending (priority, seq), not plain seq FIFO. QueuedEvent.spec.priority has three values: incident (0), webhook-originated (1, a human is waiting on a thread right now), and cron/sweep-originated (2, proactive work). FIFO is preserved within a priority tier. class=alert (incidents) still gets a reserved capacity pool (queue.alertCapacity) on top of the priority ordering, so a busy normal queue can never starve an incident.
Dedup is a natural key on a field, never a hash and never a label selector: QueuedEvent.spec.dedupKey holds iss:<repo>#<number> or mr:<repo>!<number> (or an alert-group hash for incidents), looked up through the issueKey/mrKey field indexes on Issue/MergeRequest and the queuedEventDedupKey index on QueuedEvent itself. Kubernetes label values cannot carry : or #, so a label-based "natural key" would have been silently hashed back into an opaque digest - the exact failure mode this design avoids.
Quick reference: required vs optional fields¶
apiVersion: tatara.dev/v1alpha1
kind: Project
metadata:
name: my-project
namespace: tatara
spec:
scmSecretRef: tatara-scm # required: Secret name with SCM token
scm:
provider: github # required: github | gitlab
owner: szymonrychu # required: org or user slug
botLogin: szymonrychu-bot # required: bot account login
agent:
model: claude-opus-4-8 # optional: defaults to operator env
memory: # optional block; all fields have defaults
pgInstances: 3 # default 1
apiVersion: tatara.dev/v1alpha1
kind: Repository
metadata:
name: my-project-tatara-operator
namespace: tatara
spec:
projectRef: my-project # required
url: https://github.com/org/repo # required
reingestSchedule: "0 6 * * *" # required: 5-field cron
defaultBranch: main # optional, default "main"
ingestEnabled: true # optional, default true
semanticIngest: true # optional, default true
See the individual CRD reference pages for the full field tables with all defaults, enums, and deprecation notices.