Project Configuration¶
The configuration surface of the Project custom resource: what each field controls, its type, its default, and the behavior it selects. It is the companion to Your First Project, which covers the minimal Project only.
This page is organized by the thing being configured. For the same object presented as an API schema, including the .status surface and the fields no project normally sets, see the Project CRD reference. Where the two pages describe the same field they are describing the same field; the CRD reference is authoritative on type and shape.
API group / version: tatara.dev/v1alpha1 Kind: Project Scope: Namespaced
Required and key top-level fields¶
scmSecretRef is the only field the API requires at the top level. Within spec.scm, provider, owner, and botLogin are required.
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
scmSecretRef | string | - | yes | Name of the Secret in the same namespace. Key token holds the bot PAT (GitHub) or project access token (GitLab). |
scm.provider | string | - | yes | One of github, gitlab. |
scm.owner | string | - | yes | GitHub organization name or GitLab group path, as it appears in repository URLs. All enrolled repositories must live under this owner. |
scm.botLogin | string | - | yes | Bot account username on the provider. Must be non-empty and must not appear in maintainerLogins or reporterLogins; both constraints are enforced by CEL rules on the CRD. |
triggerLabel | string | tatara | no | Issue label that admits an issue into the agent loop. |
maxConcurrentAgents | int | 3 | no | Caps simultaneously admitted agent pods project-wide. The admission unit is the pod-spawn, not the Task. 0 is the full-project pause: no QueuedEvent is admitted, so no pod and no Task is created. Minimum 0; there is deliberately no minimum of 1. |
agentPodTTLSeconds | int | 3600 | no | Bounds one agent pod's life. The Task persists across as many pods as it needs; on expiry the operator takes a handoff turn and a fresh pod resumes. Minimum 300. |
maxLivePods | int | 2 | no | Caps how many Tasks may sit in a live (conversational) state at once. Clamped to maxConcurrentAgents - 1 at read time so chatty threads cannot occupy the whole concurrency budget, except at maxConcurrentAgents: 1 where the ceiling equals the cap. |
maxNewTasksPerSweep | int | 5 | no | Caps how many Tasks one sweep pass may mint. Minimum 1. |
maxOpenTasks | int | 6 | no | Caps active Tasks: every Task whose stage is pod-eligible. A Task creation budget, distinct from the concurrency budget above. Minimum 1. |
maxBundleBytes | int | 400000 | no | Hard byte budget for a rendered context bundle. Minimum 50000. |
maxConcurrentAgents: 0 is the pause switch
The pause is a direct check at the top of admission, not a derived queue capacity. Every scan, brainstorm, and webhook-triggered event queues and nothing is admitted, including the next pod of a Task already in flight. See Tuning.
SCM Secret format¶
The Secret must exist in the Project's namespace before the Project is applied.
apiVersion: v1
kind: Secret
metadata:
name: tatara-scm
namespace: tatara
type: Opaque
stringData:
token: "ghp_..." # bot PAT: contents, issues, pull requests, metadata, org members read
# (GitHub fine-grained) or api + read/write_repository (GitLab). No webhook scope.
No webhook scope is needed: the operator never registers webhooks. It validates inbound payloads against a separately configured secret.
Memory sizing¶
Each Project gets a dedicated memory stack: a CNPG-managed PostgreSQL cluster, a Neo4j graph database, a LightRAG graph indexer, and the tatara-memory API server the other components query. The stack persists the code-knowledge graph agents traverse at runtime.
graph TD
CR["Project CR"] -->|reconcile| OP["tatara-operator"]
OP -->|provisions| PG["CNPG Cluster\n(pgInstances replicas,\npgStorage / pgWalStorage)"]
OP -->|provisions| N4J["Neo4j StatefulSet\n(neo4jStorage)"]
OP -->|provisions| LR["LightRAG\n(graph indexer)"]
OP -->|provisions| MEM["tatara-memory\n(API server)"]
PG & N4J & LR --> MEM
MEM -->|all components healthy| RD["status.memory.phase = Ready"]
RD --> WH["status.webhookURL available\nRepository ingestion unblocked"] Field (spec.memory.*) | Type | Default | Description |
|---|---|---|---|
enabled | *bool | true | Tri-state: nil and true both mean enabled. Only an explicit false disables the whole stack, including its ServiceMonitor, PodMonitor, and PrometheusRule. Teardown is deliberately asymmetric: the PostgreSQL and Neo4j volumes are retained and reattached by name if the stack is re-enabled, while the LightRAG volume is deleted because its index is derived data rebuilt by re-ingesting. |
pgInstances | int | 1 | CNPG cluster replica count. 1 for development, 3 for HA production. |
pgStorage | string | 10Gi | PVC size per PostgreSQL replica (PGDATA). Grows with repository count and embedding volume. |
pgWalStorage | string | 8Gi | PVC size per PostgreSQL replica for CNPG's dedicated WAL volume, separate from PGDATA. A WAL burst, or WAL retained for a lagging standby, cannot fill PGDATA and stop writes. |
neo4jStorage | string | 10Gi | PVC for the Neo4j graph. Sized proportionally to total ingested lines. |
Scaling guidance:
- Development or a single repository: the defaults are sufficient for evaluation.
- Production, multiple repositories: set
pgInstances: 3for PostgreSQL HA and raise storage to20Gior above once ingestion consistently reaches 80% of capacity. The live tatara self-hosting project runspgInstances: 3,pgStorage: 10Gi,neo4jStorage: 10Giacross eight repositories. - Large monorepos above 500k lines: start at
neo4jStorage: 20GiandpgStorage: 20Gi.
Storage provisioning is one-way
PVC expansion requires a storage class supporting allowVolumeExpansion, and the CNPG cluster must be restarted after a resize. CNPG's admission webhook rejects a shrink outright, so these values only ever go up. Plan capacity before the initial repository ingest.
Memory readiness and what it gates¶
status.memory.phase is one of Provisioning, Ready, Degraded, Failed, or Disabled. status.memory.notReady names the stack components currently below their readiness gate (postgres, neo4j, lightrag, memory-api) and is empty when the stack is Ready.
Readiness is debounced. A consumer treats the stack as stably ready only after it has held Ready for three minutes, measured from status.memory.readySince. That debounce gates repository ingestion, the TATARA_MEMORY_DEGRADED pod environment variable, and the degraded turn-0 prompt appendix. It is not a pod-spawn gate: agent pods spawn and submit turns whatever the stack's phase, with the degradation declared to them, because an unbounded memory outage otherwise held every Task indefinitely.
During the debounce window a Repository carries a MemoryNotReady condition with message waiting for project <name> memory stack to become stably Ready even though status.memory.phase already reads Ready.
Agent configuration¶
spec.agent configures every agent pod the operator schedules for this Project. The fields below are the ones a project normally sets; see AgentSpec for the complete list, including the extra containers, volumes, MCP servers, and skill sources.
Field (spec.agent.*) | Type | Default | Description |
|---|---|---|---|
model | string | operator default | Claude model ID. No CRD default: when empty the operator's compiled-in default applies. A single model serves all agent kinds unless overridden in modelByKind. |
image | string | operator default | Full tatara-claude-code-wrapper image reference, registry and tag. Pin to an explicit tag; the operator uses the value verbatim in every agent Pod spec. |
effort | string | xhigh | Reasoning effort forwarded to the wrapper as the EFFORT env var. Enum low, medium, high, xhigh, max. |
permissionMode | string | bypassPermissions | Claude Code permission mode. Headless agents require the default. |
turnTimeoutSeconds | int | 1800 | Per-turn inactivity window in seconds. It does not kill the turn: after this long with no agent activity the operator sends a probe instead. See Stall detection. |
stallProbeGraceSeconds | int | 300 | How long the operator waits for a stall probe to be answered before counting the attempt unanswered. The probe is delivered at the agent's next tool-call boundary, so a healthy agent inside one long tool call answers late rather than never. Minimum 60. |
stallProbeMaxAttempts | int | 2 | Unanswered probes before the operator interrupts the session and runs the stop-and-handoff sequence. Range 1 to 5. |
modelByKind | map[string]string | {} | Per-agent-kind override of model. A missing or empty entry falls back to model. Values must start with claude-. |
effortByKind | map[string]string | {} | Per-agent-kind override of effort, same keying as modelByKind. |
skillsRef | string | main when empty | Git ref of the tatara-agent-skills repo the wrapper clones at boot. Hand-managed; no pipeline rewrites it. See AgentSpec. |
hooks | LifecycleHooks | - | Optional shell commands run at fixed session points. See below. |
Deprecated agent fields¶
Four spec.agent fields are retained in the CRD with zero effect. They exist only so helmfile values that already set them keep validating; removal is a breaking API change reserved for a later major version. Setting any of them changes nothing.
| Field | Default | What it used to do | What replaced it |
|---|---|---|---|
maxTurnsPerPod | 40 | Capped turns within one pod's run. | agentPodTTLSeconds and the boot-crash watchdog. It had no enforcement reader even before it was retired. |
maxTurnsPerTask | 300 | Lifetime turn ceiling across every pod of a Task. | Stall probes, and failing those, the hardcoded 24h residency cap. A turn count measures how much an agent has done, not whether it is stuck. |
maxReviewRounds | 3 | Parked the Task after this many request_changes verdicts. | Nothing. The reviewing/implementing cycle is not capped by a round count. status.reviewRounds is still incremented for observability only. |
maxPodRecreations | 3 | Parked the Task after this many respawns. | An alert, not a cap: operator_pod_recreations_total is still counted and exported, and the residency cap is the only remaining backstop. See Runbooks. |
Nothing in that group survives as a live field. The bound that people reach for here - "how many times does a human PR comment un-park a review-kind Task" - is real and is 5, but it is the MaxHumanReviewRounds constant in tatara-operator/api/v1alpha1/constants.go, not agent.maxHumanReviewRounds. Writing that key into a Project is pruned silently by the apiserver and changes nothing. The residency cap is a constant for the same stated reason: a bound that structural-schema pruning can drop is no bound at all.
spec:
agent:
model: claude-opus-4-8
image: harbor.example.com/tatara-claude-code-wrapper:v1.2.3
effort: xhigh
turnTimeoutSeconds: 1800 # 30 minutes of inactivity, not wall-clock age
stallProbeGraceSeconds: 300
stallProbeMaxAttempts: 2
Turn timeout semantics
turnTimeoutSeconds measures inactivity, not elapsed time. An agent writing a large Go implementation that keeps emitting tool-call output is never killed by this timer. Only a turn producing no output at all for the timeout duration is probed, and only an unanswered probe sequence interrupts it. The default of 1800 seconds accommodates large file writes and long compilation steps.
Lifecycle hooks¶
Optional shell commands the wrapper runs at fixed lifecycle points. Each is executed via sh -c. An empty field is skipped. A non-zero exit is logged and counted as a metric but never aborts the agent run.
spec:
agent:
hooks:
preClone: "echo cloning $1"
postClone: "mise install"
conversationStart: "notify-start.sh"
agentTurnFinished: "run-metrics-push.sh"
| Hook | When it fires | Arguments |
|---|---|---|
preClone | Before each repository clone | Repo URL as $1 |
postClone | After each successful clone and checkout | Clone destination as $1 |
conversationStart | Once, after the agent session boots successfully | Task context from pod env (TATARA_TASK, TATARA_PROJECT) |
conversationRestart | Each time the session is relaunched after a crash | Same as conversationStart |
agentTurnFinished | After each agent turn completes | Same as conversationStart |
conversationFinished | Once, during session teardown | Same as conversationStart |
Approval and intake¶
The intake model determines which accounts can drive the agent loop. A separate allow-list determines which accounts can release an issue into implementation.
Approval is a comment, never a label. When the implement agent has settled the scope, a maintainer posts a comment. The agent judges whether that comment approves and cites it - its forge external_id plus a verbatim quote - in submit_outcome(action=approved), alongside the plan note it wants approved. The operator does not take the agent's judgment on faith. It independently verifies that the cited comment exists, that its author is a verified maintainer and not the bot, and that the quoted text occurs in the comment body the operator itself holds. It then pins the comment id on the Issue as single-use evidence and lets the work start. The operator holds no wordlist and never decides what a comment means.
What approves, and what does not¶
| A maintainer comments | Result |
|---|---|
go ahead, I approve | Approves, if the implement agent cites it. The agent reads this as unambiguous consent. |
LGTM | Approves, for the same reason. There is no required phrase; the agent judges ordinary language. |
I can't approve this until the tests pass | Should not approve. This is a judgment call, not a structural guarantee. An implement agent reading it correctly does not cite it. The operator only confirms the quote exists in the comment it is given, so a misjudging agent that cited it anyway would not be caught here. |
An approval from an account not in maintainerLogins | Does not approve. Identity is checked before the quoted text is compared. |
The bot posting go ahead | Does not approve. The bot is excluded structurally, before the quoted text is compared. |
If no citation passes, the Task parks. Nothing is posted to the issue: the reason lives in the operator's logs and metrics, not on the thread, so no prompt tells anyone a comment is needed. A comment posted at any time afterwards un-parks the Task to a fresh implement pod, which picks up the conversation where it left off. See Approval Gates for the full rules, including why the cited comment need not be the thread's most recent one.
Allow-lists¶
| Field | Type | Effect when empty | Effect when set |
|---|---|---|---|
scm.maintainerLogins | []string | Closed by default. No login is a maintainer, so no comment can approve anything and no issue - human-filed or bot-authored - advances out of refined. | Only a comment authored by a listed login can be cited as approval. The set also forms the trusted-insider set for intake. |
scm.reporterLogins | []string | Issues and comments from any author are processed. | Only the bot, maintainers, and listed reporters trigger the agent loop. Everything else is dropped at intake, on both the cron and webhook paths. |
Both lists are capped at 100 entries, and neither may contain scm.botLogin; the API rejects a Project that violates either constraint. Both are overridable per-repository via RepositorySpec.maintainerLogins and RepositorySpec.reporterLogins.
Security recommendation
Set both lists to the humans who hold commit access to the enrolled repositories. Leaving reporterLogins empty permits any account that can file an issue to steer an agent holding elevated SCM permissions. See Prompt-Injection Defenses for the threat model.
Label set¶
The operator projects a small set of labels onto an issue as a one-way, write-only mirror of Issue.status.status and the Task's state. The labels are useful for dashboards and for humans scanning an issue list, and no label is ever read back to derive that status. Defaults work without configuration; override only to match organizational naming conventions.
Field (spec.scm.*) | Default | Written when |
|---|---|---|
brainstormingLabel | tatara-brainstorming | The issue is at refined, meaning pre-approval triage or discussion. |
approvedLabel | tatara-approved | Issue.status.status becomes approved, after the approval grammar accepts a maintainer comment. Never itself read to grant approval. |
implementationLabel | tatara-implementation | The Task's running agent becomes implement. |
declinedLabel | tatara-declined | Issue.status.status becomes rejected. |
incidentLabel | tatara-incident (applied by the operator; no CRD default) | The issue originated from an incident investigation. Applied additively alongside brainstormingLabel and never swept by the phase-label reconciler. |
priorityLabel | (empty) | Optional priority tag, applied to high-priority tasks when set. |
The old label fields that configured a label-driven approval trigger are removed from the CRD outright. See Project: Label set.
Merge policy¶
An agent-opened PR is never merged by an agent. Once a review pod calls submit_outcome(verdict=approve) from a separate pod that structurally cannot decide its own diff's fate on the forge, the operator reads the live PR head, posts a COMMENT-type review carrying the verdict, and merges it. The review is never a native forge APPROVE, because GitHub blocks a PR's own author from approving it and one bot identity makes that call fail with a 422.
| Field | Type | Default | Enum | Description |
|---|---|---|---|---|
scm.mergePolicy | string | afterApproval | afterApproval, autoMergeOnGreenCI | Gates the operator's own merge. afterApproval trusts the agent's relayed approving signal and does not consult live PR review state. autoMergeOnGreenCI additionally requires green CI, falling back to afterApproval when the PR has no CI at all. |
No tatara-opened PR is ever created with the forge's own merge-when-green setting switched on; mergePolicy is evaluated inside the operator.
There is no SCM branch-protection rule that adds a human merge step on top of this. A rule requiring an approving review would deadlock every merge, because the platform cannot satisfy it on its own PR. Combining autoMergeOnGreenCI with a branch-protection rule that requires an approved review before CI can pass is the supported way to reinstate a human gate. See the accepted-risk note for what defense-in-depth looks like under one bot identity.
PR reaction scope¶
spec.scm.prReactionScope gates which human PRs and MRs the sweep's re-review path reacts to. It has no default, and unset is the widest setting rather than the narrowest.
| Value | Behavior |
|---|---|
unset, or all | The sweep reviews every open human PR or MR in every enrolled repository. This is the historical open behavior; all is an explicit synonym for it. |
labeledOrMentioned | Re-review is restricted to PRs labeled with triggerLabel or @mentioning the bot account, so unlabeled, un-mentioned PRs are not re-reviewed every scan cycle. |
Set this explicitly to narrow scheduled re-review
The field is deliberately not defaulted to labeledOrMentioned: a defaulted value is indistinguishable from an explicit one, so auto-defaulting would silently gate every project. The inbound-webhook PR path is separately hardcoded to labeled-or-mentioned regardless of this field; this setting governs the scheduled re-review loop.
Cron activities¶
All schedules use standard 5-field cron syntax. An empty schedule disables that activity. Five activities exist under spec.scm.cron.
scm.cron.mrScan no longer exists
The MR-scan cron was removed from the CRD when the sweep became the single issue and PR intake. A Project that still carries an mrScan block applies without error and the block is pruned silently - there is no warning anywhere. Scheduled PR re-review is now part of the sweep and is scoped by prReactionScope above.
spec:
scm:
cron:
issueScan:
schedule: "0 * * * *" # every hour at :00
maxPerRepo: 1 # concurrent issue-scan Tasks per repository lane
| Field | Type | Default | Description |
|---|---|---|---|
schedule | string | - | 5-field cron expression. Empty disables the activity. |
maxPerRepo | int | 1 | Concurrent in-progress Tasks of this type per repository lane. A repository whose lane is full is skipped until the in-flight Task completes. Minimum 1. |
spec:
scm:
cron:
brainstorm:
enabled: true
schedule: "0 * * * *"
targetOpenProposals: 3 # backlog target the operator refills toward
historyWindow: 20
staleProposalDays: 14
sources: # docs | memory | internet
- docs
- memory
- internet
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Must be true to activate. |
schedule | string | - | 5-field cron expression. |
targetOpenProposals | *int | 3 | The backlog target: how many proposals the operator keeps open and awaiting a maintainer decision across all repositories in the project. The controller refills toward it and never closes a proposal to reconcile downward. An explicit 0 disables refill. |
maxOpenProposals | int | 5 | Deprecated. The pre-target ceiling, retained as an alias honoured as the target only while targetOpenProposals is unset. Set targetOpenProposals instead. |
historyWindow | *int | 20 | How many recent proposals are rendered into the session's turn-0 prompt as the <proposal_history> block, with their outcome and maintainer comments. An explicit 0 omits the block. |
staleProposalDays | int | 0 | Declared but unconsumed. Its godoc describes a staleness reaper over bot-authored proposals with no human engagement - positive an explicit window in days, 0 (unset) a default window, negative the opt-out - but no code reads the field, so no proposal is auto-closed at any age. It is in the CRD and applies cleanly, which is why setting it looks like it worked. |
minSessionIntervalMinutes | int | 12 | Floors the wall-clock gap between two brainstorm sessions, whichever path dispatched the prior one. A rate limit, not a circuit breaker: it delays a refill, never suppresses one, and it never inspects how the prior session ended. Positive is an explicit floor, 0 (unset) is the 12-minute default, negative disables the floor. Unlike staleProposalDays above, this one is live. |
maxPerCycle | int | 1 | Deprecated and ignored. The controller hard-caps brainstorm at one Task per project per cycle. |
sources | []string | - | Knowledge sources the agent may consult. Enum per item: docs, memory, internet. An empty list uses only repository contents. |
| Source | What the agent reads |
|---|---|
docs | Repository documentation and code already ingested into the memory graph |
memory | The structured knowledge graph, meaning entity and relationship queries |
internet | External web search for relevant context and prior art |
spec:
documentation:
enabled: true
repo: https://github.com/my-org/my-docs
scm:
cron:
documentation:
schedule: "0 2 * * *" # nightly at 02:00
maxPerRepo: 1
Two gates, both required. The CronActivity under scm.cron.documentation has no enabled field of its own; the on-switch is the top-level spec.documentation block.
| Field | Type | Default | Description |
|---|---|---|---|
documentation.enabled | bool | false | Master switch for the documentation agent. No CRD default. |
documentation.repo | string | - | Git URL of the docs repo the agent maintains. Must also be enrolled as a Repository CR under this Project so the bot has push access and CI runs. |
scm.cron.documentation.schedule | string | - | 5-field cron expression. Empty disables the activity. |
scm.cron.documentation.maxPerRepo | int | 1 | Concurrent documentation Tasks per repository lane. Minimum 1. |
Each tick spawns a documentation Task scoped to the docs repo, covering every enrolled component repository that advanced since status.lastDocumentation. There is no webhook path; only the cron tick drives it. See the Documentation workflow.
Refine carries its own schedule and is not a pre-step of any other activity. Grooming the backlog is periodic work; refilling it is not, and brainstorm is demand-driven with no cadence to hang a barrier off.
| Field | Type | Default | Description |
|---|---|---|---|
schedule | string | - | 5-field cron expression. Empty disables refine. |
closedLookbackDays | int | 30 | How far back closed issues are loaded for already-implemented detection. Zero uses the default of 30. |
spec:
upgradePolicy:
engine: none
majorStrategy: nextHopOnly
scm:
cron:
upgrade:
schedule: "58 */4 * * *"
maxOpenUpgrades: 1
The dependency-upgrade cron, off by default for every project: enabling it lets an agent open merge requests that change deployed versions.
| Field | Type | Default | Description |
|---|---|---|---|
schedule | string | - | 5-field cron expression. Empty disables the activity. |
maxOpenUpgrades | int | 1 | Caps concurrent open upgrade lanes: live upgrade Tasks plus enqueued events not yet minted into one. Range 1 to 10. Set it explicitly in the enrollment values - a CRD default applies only on write and never reaches a Project CR that already exists. |
Each due tick mints at most one upgrade Task, so throughput is the cron frequency rather than a fan-out. The policy the agent is handed lives in the top-level upgradePolicy block.
Grafana incident-response integration¶
When enabled, the operator provisions a per-project grafana-mcp Deployment backed by a read-only Grafana Viewer service account, and exposes an alert-webhook receiver at <webhookURL>/grafana. Agent pods reach the deployment over the network via the TATARA_GRAFANA_MCP_URL env var the operator injects; it is a standalone in-cluster service, not a sidecar container in each agent pod. Grafana alert rules that POST to the webhook receiver trigger incident-response tasks.
Field (spec.grafana.*) | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Master switch. Must be true for any other field to take effect. |
url | string | - | Grafana base URL that grafana-mcp queries. Non-sensitive. |
secretRef | string | - | Name of the Secret holding Grafana credentials. |
cooldownSeconds | int | 3600 | Deprecated, zero effect. Retained for API compatibility. Per-alert-group refire dedup is handled at admission time via in-flight idempotency. |
spec:
grafana:
enabled: true
url: http://prometheus-grafana.monitoring.svc.cluster.local
secretRef: tatara-grafana
The referenced Secret must contain two keys:
apiVersion: v1
kind: Secret
metadata:
name: tatara-grafana
namespace: tatara
type: Opaque
stringData:
serviceAccountToken: "glsa_..." # Grafana Viewer SA token (mounted into grafana-mcp)
webhookSecret: "..." # bearer the Grafana contact point presents to the webhook
Project board projection¶
Field (spec.scm.board.*) | Type | Default | Description |
|---|---|---|---|
githubProjectNumber | int | - | GitHub Projects (V2 or classic) project number. |
gitlabBoardId | int | - | GitLab board ID. Use instead of githubProjectNumber on GitLab. |
statusField | string | Status | Name of the board field the operator writes task phase into. |
Project guidance¶
spec.scm.guidance is free-form text appended verbatim to the brainstorm goal context. It scopes the agents' focus area for this project.
spec:
scm:
guidance: >-
Treat the helm charts, CI pipelines, and Kubernetes configuration as in-scope
alongside application features. Prioritize reliability and observability
improvements.
Queue tuning¶
Omit spec.queue entirely if the capacity derived from maxConcurrentAgents is sufficient.
Field (spec.queue.*) | Type | Default | Description |
|---|---|---|---|
capacity | int | value of maxConcurrentAgents, else 3 | Maximum concurrently admitted normal-class pod-spawns. Events above the limit wait in Queued state until a slot frees. |
alertCapacity | int | 1 | Reserved concurrent slots for alert-class events, meaning Grafana-sourced incidents. Kept separate so a saturated normal queue cannot starve incident response. |
Annotated full Project YAML¶
A production-shaped example for a GitHub organization, using the commonly set fields.
- The Project name must be unique per namespace. It becomes the label
tatara.dev/projecton every downstream resource: agent pods, the memory stack, and the cron-driven Tasks. - Name of the
Secretin the same namespace. It must contain keytokenwith the bot PAT and must exist before the Project is applied. - Issues carrying this label enter the agent loop. Defaults to
tatara. - The project's kill switch: maximum concurrent agent pods across all repositories. The admission unit is the pod-spawn, not the Task.
0fully pauses the project - noQueuedEventis admitted, so no pod and no Task is created. - Bounds one agent pod's life in seconds. The Task persists across as many pods as it needs; a pod that runs past this deadline is stopped with a guaranteed handoff note written to
Task.status.notes, and a fresh pod resumes from it. Minimum300. - Caps Tasks in a live conversational state. It is clamped to
maxConcurrentAgents - 1so open conversations cannot consume the entire concurrency budget. - Caps active Tasks, meaning every Task at a pod-eligible stage. A Task creation budget, separate from the pod concurrency budget above.
- Claude model for all agent kinds in this Project, unless overridden per kind in
modelByKind. Changing it affects new pods immediately; an in-flight pod continues on the model it started with. - Full image reference for the
tatara-claude-code-wrappercontainer. The operator uses this value verbatim in every agent Pod spec, so pin an explicit tag or digest. - Reasoning effort.
xhighis the default and the recommended starting point. Lower values reduce API cost and also agent quality on complex multi-file implementation work. - Per-turn inactivity window in seconds. It does not kill the turn: past this long with no agent output the operator probes the agent, and only an unanswered probe sequence interrupts the session. A turn actively writing files or running tests is never probed.
- Unanswered probes before the operator interrupts the session and runs the stop-and-handoff sequence.
maxTurnsPerPod,maxTurnsPerTask,maxReviewRoundsandmaxPodRecreationsare deprecated with zero effect and are omitted here on purpose;maxHumanReviewRoundsis omitted because it is a package constant and has never been a field on this spec. - CNPG PostgreSQL replica count.
1is acceptable for development;3gives HA and is the production setting. - PVC storage per PostgreSQL replica (PGDATA). Scale with the number and size of enrolled repositories.
- PVC for the Neo4j graph database. The code-knowledge graph grows with total ingested line count across every enrolled repository.
- SCM provider:
githuborgitlab. - GitHub organization name or GitLab group path, as it appears in repository URLs.
- Username of the dedicated bot account. Its PAT must grant repository contents read and write, issues, pull requests, and org-membership read on all target repositories. No webhook-admin scope is needed; the operator never registers webhooks.
- GitHub noreply commit-author email for the bot. It links agent commits to the bot account in the GitHub web UI, and is found in the bot account's GitHub email settings.
- Human maintainer logins. Required for anything to ever be approved: empty means no approval is possible. Only a comment from one of these accounts can be cited as approval by the
implementagent, and the operator verifies the citation independently. The set also forms the trusted-insider set for intake. Must not containbotLogin. Overridable per-repository. - Reporter allow-list. When set, issues and comments from accounts not in this list, not in
maintainerLogins, and not the bot are dropped at intake. This closes the primary prompt-injection vector. Must not containbotLogin. Overridable per-repository. - Gate on the operator's own merge.
afterApprovalis the default and trusts the relayed approving signal;autoMergeOnGreenCIadditionally requires green CI. - Scope of scheduled PR re-review. This example opts in to the narrow setting. Left unset, or set to
all, the sweep re-reviews every open human PR or MR each cycle. The field has no default, so set it explicitly to narrow the loop. - Free-form project charter text appended verbatim to the brainstorm goal context. Use it to focus agent attention on this project's priorities.
- Concurrent issue-scan Tasks per repository lane.
1is the safe default; one scan agent per repository prevents conflicting concurrent scans. - The backlog target the operator refills toward across all repositories. It never closes a proposal to reconcile downward. The deprecated
maxOpenProposalsalias is honoured only while this field is unset. - Opts the staleness reaper into an explicit 14-day window: bot proposals with no human engagement for that long are auto-closed, keeping the backlog from filling with dead proposals. Unset enables a default window; a negative value disables the reaper.
- Refine carries its own schedule. It is not a pre-step of the scan or brainstorm cycles, and an empty schedule disables it entirely.
- The real on-switch for the nightly documentation agent, alongside
documentation.repo.scm.cron.documentation.scheduleabove is a separate and also-required gate. - Enables the per-project Grafana integration: a read-only
grafana-mcpDeployment provisioned with a Viewer service account token, reached by agents through the injectedTATARA_GRAFANA_MCP_URL, plus an alert-webhook receiver at<webhookURL>/grafana. - Kubernetes Secret containing two keys:
serviceAccountToken, the Grafana Viewer SA token mounted into the grafana-mcp container, andwebhookSecret, the bearer token the configured Grafana contact point must present to the webhook. - Queue admission capacity: maximum simultaneously admitted normal-class events. Defaults to
maxConcurrentAgentswhen unset. Override only to decouple queue capacity from the concurrency cap. - Reserved concurrent slots for alert-class events. Alert slots are separate from
capacity, so an incoming incident still gets an agent pod when the normal queue is saturated.