Agent-native tatara tools + memory robustness - design¶
Date: 2026-06-09 Status: approved Repos touched: tatara-cli, tatara-operator, tatara-claude-code-wrapper, tatara-memory, infra/terraform/keycloak (verify)
Problem¶
An agent self-audit (issue szymonrychu/tatara-cli#10, PR #11) found that the spawned agent cannot use any tatara MCP tool natively: the pod's .mcp.json is empty and the in-pod tatara CLI has no token (auth: no token). The backends (operator REST, memory code-graph) are up and reachable; only the agent's path to them is missing. This is why the agent never created Subtasks - it cannot call subtask_create. Separately, a transient postgres blip crashlooped mem-tatara (7x), briefly 502ing /queries, and left a stale MemoryNotReady condition on the repos.
A. Native tatara tools for the agent¶
A1. tatara-cli: non-interactive client-credentials auth¶
internal/auth currently only loads a stored token (tatara login device flow) and returns ErrNoToken otherwise. Add a headless fallback: when no stored token exists AND CLI_OIDC_CLIENT_ID + CLI_OIDC_CLIENT_SECRET + OIDC_ISSUER are set, perform an OIDC client_credentials grant against the issuer's token endpoint (discovered via <issuer>/.well-known/openid-configuration), cache the access token in memory with its expiry, and refresh when it is within ~30s of expiring. tatara login remains the interactive path and a stored token still wins. No new flags; the fallback is transparent to tatara raw and tatara mcp.
Keycloak dependency (verify, fix in terraform if missing): the confidential client behind tatara-cli-oidc must have service accounts enabled (for the grant) and audience mappers for both tatara-memory and tatara-operator so one token is accepted by both REST APIs. If the client is public/device-only, add a confidential client or enable service accounts + the two audience mappers in infra/terraform/keycloak/tatara_clients.tf.
A2. operator: inject TATARA_OPERATOR_URL¶
internal/agent/pod.go injects TATARA_MEMORY_URL but not the operator REST URL, so the CLI's task_*/subtask_* tools have no endpoint. Add TATARA_OPERATOR_URL pointing at the in-cluster operator REST Service (the same host the wrapper callback uses, on the REST/HTTP port - a new PodConfig field OperatorURL, sourced from operator config, e.g. http://tatara-operator.tatara.svc:8080). The auth env (CLI_OIDC_CLIENT_ID/SECRET, OIDC_ISSUER) is already injected and matches the names A1 reads.
A3. wrapper: register the tatara MCP server at bootstrap¶
After bootstrap.Render writes /workspace/.mcp.json, run tatara mcp-config /workspace (the CLI baked in the wrapper image; merges a tatara entry into the existing .mcp.json, idempotent). Gate on the tatara binary being present and TATARA_MEMORY_URL set (i.e. a real agent pod), so tests/dev without the CLI are unaffected. claude then launches tatara mcp as a stdio MCP server that self-configures from TATARA_MEMORY_URL / TATARA_OPERATOR_URL / the auth env.
Result: the agent gains code_* (graph navigation), task_*/subtask_* (self-plan + report), and memory tools natively; real subtask decomposition becomes possible.
B. Memory robustness¶
B1. mem-tatara survives a postgres blip¶
The mem-tatara pod crashlooped 7x when mem-tatara-pg-rw:5432 briefly refused connections. Investigate the actual fatal point (DB open at startup vs the pool-resume WARN vs liveness). Fix: make startup wait for postgres with bounded backoff (e.g. retry db.Ping for up to ~60s) before serving, and keep pool.Resume non-fatal (already a WARN), so a transient pg restart does not take the memory API down or crashloop the pod. If the fatal point is liveness/readiness during the window, adjust the probe thresholds rather than the code.
B2. operator clears the stale MemoryNotReady condition¶
The RepositoryReconciler sets a MemoryNotReady condition while the Project memory provisions; once Project.status.memory.phase == Ready it is never cleared, so it lingers and misleads. When memory is Ready, set that condition False (Reason MemoryReady) on the Repository during reconcile.
B-verify: live /queries recheck¶
After B1, re-run a real /queries against the per-Project memory to confirm the semantic layer answers now that the stack is stable; record the result.
Testing¶
- tatara-cli:
internal/authunit test - no stored token + client-creds env -> performs aclient_credentialsgrant against a fake token endpoint and returns/caches the token; expiry triggers a refresh; stored token still wins; missing env ->ErrNoToken(unchanged). - operator:
pod_test.goassertsTATARA_OPERATOR_URLenv present with the configured value; controller test asserts the RepositoryMemoryNotReadycondition is cleared (False) oncememory.phase == Ready. - wrapper: bootstrap/app test asserts
tatara mcp-config /workspaceis invoked after.mcp.jsonis written when the CLI + memory env are present, and skipped otherwise (injected runner, likeGitRunner). - tatara-memory: startup wait-for-pg retries
Pingand proceeds once pg is reachable (fake/slow DB), and does not exit on a transient failure.
Out of scope¶
- Per-tool MCP allow-listing / scoping (the agent gets the full tatara tool set).
- Token persistence to disk (in-memory cache is enough for one pod/session).
- Reworking the device-flow
tatara loginpath.