Internal Documentation internal
TalkIDE internal documentation

Status: Accepted Datum: 2026-05-16 Oblast: Platform architektura / AI runtime / Horizontální škálování Supersedes: — Navazuje na: ADR-023 §5 (namespace-per-tenant-env jako substrát), ADR-015 (K8s namespace provisioning), ADR-019 (Kaniko Job pattern — rozšiřuje na gradle/test), ADR-014 (K8s client foundation — reuse pro Job dispatch)

Context

Problém: škálovací zeď v AgentSidecarExecutor

Driver tohoto ADR je horizontální škálování exekuce pod zátěží, nikoliv dekompozice domény. Doména zůstává v platform BE (Spring Boot, Kotlin, control plane).

Současný stav: třída AgentSidecarExecutor (~1670 řádků, Kotlin @Service singleton v BE) spawnuje jeden dlouhoběžící Node sidecar přes ProcessBuilder a multiplexuje všechny konverzace všech tenantů přes jedinou NDJSON stdin/stdout pipe:

  • jeden synchronized stdinWriter
  • jeden stdout reader daemon thread
  • ConcurrentHashMap živých konverzací
  • životnost sidecaru == životnost BE podu

Sidecar volá zpět BE na http://localhost:$serverPort; HMAC secret se injectuje přes ProcessBuilder.environment() (talkide-be#104).

Image build je již odsazen na Kaniko K8s Jobs (KanikoBuildService / KanikoJobBuilder, ADR-019). Ale gradle build/test v dev-loopu volá agenta (Mara) jako tool přímo ve working tree na NFS v kontextu jednoho BE podu. Při N simultánních uživatelích výsledek:

  • OOM a CPU hladovění BE podu
  • single-process strop — jeden JVM + jeden Node proces obsluhují všechny tenanty
  • gradle JVM warmup × N konverzací souběžně = degradace pro všechny
  • lifecycle sidecaru svázaný s BE redeployem → přerušení 3-week session resume

Toto je škálovací zeď, kterou ADR řeší.

Odmítnuté alternativy

AlternativaDůvod odmítnutí
A) Decouple sidecar přes síť, zachovat oba jazykyPřepisuje nejbolestivější kus (pipe multiplexer) na distribuovaný síťový protokol → více failure módů, ne méně. Distributed state k udržení → složitost roste, ne klesá. Odmítnuto.
B) Kotlin mikroservisa, sidecar pořád child procesPřesune pipe boundary do tenant ns, získá izolaci + billing, ale složitost nemaže — jen ji stěhuje. Odmítnuto jako fallback-only varianta, nevolí se primárně.

Decision

1. Node/TypeScript thin worker — jedna instance per tenant-environment namespace

Sidecar přestává být spawnovaný child proces — stává se in-process knihovnou.

Anthropic Agent SDK je již Node/TS; worker jej volá in-process bez meziprocesní pipe. Tím se maže (nikoliv migruje) celá mašinérie:

  • AgentSidecarExecutor ProcessBuilder / NDJSON-pipe / stdout-reader / cancel-přes-pipe
  • BuildAwaiter (talkide-be#105)

Nedávné hardening v té cestě bylo taktické, nikoliv trvalé — záměrně se neprojektuje do budoucna.

Worker běží jako dedikovaný pod v tenant-environment namespace (např. mirek-dev, mirek-prod) — stejný namespace, který je substrát izolace dle ADR-023 §5 a ADR-015.

2. Thin-seam kontrakt: control-plane vs. worker

Tohle je jádro ADR — jasná dělící čára mezi tím, co zůstává v platform BE a co jde do workeru.

Control-plane (Spring Boot Kotlin, namespace talkide-prod) drží:

OdpovědnostDetail
Identity / JWT issuanceAuth token pipeline, session validace
Tenant / project / billing perzistenceVeškerá data v control-plane DB
Quota / budget autoritaKdo smí kolik spotřebovat — worker se ptá, nestanoví
Worker orchestraceCreate / destroy worker podu v tenant ns
Gateway policyProxuje Anthropic volání — drží raw Anthropic API klíč

Worker (Node/TS, tenant-environment namespace) dělá:

OdpovědnostDetail
Mara / Anthropic SDK runtimeBěh agenta in-process, správa transcript na NFS
SSE stream do FEStreaming tokenů a event notifikací přímo do browseru
Dispatch build/test K8s JobůVytvoří ephemeral Job v tenant ns pro gradle build/test
Usage / activity reportingZpětný kanál do control-plane přes thin REST/event API (#104 HMAC seam)

3. Gateway-proxy: worker nikdy nedrží raw Anthropic klíč

Worker volá platform gateway endpoint (control-plane), která klíč drží a proxuje volání dál.

Důvody:

  • Minimalizuje blast-radius kompromitace podu v tenant ns
  • Umožňuje rate-limit + billing accounting na jednom místě (gateway)
  • Snižuje co je potřeba propagovat do worker podu (jen interní HMAC token, ne Anthropic klíč)

4. Hybrid topologie workloadů

Klíčové rozhodnutí — různé workloady mají různou topologii, protože mají různý charakter:

WorkloadTopologieProč
Agent / konverzace (Mara)Dlouhoběžící Node worker pod per tenant-envStateful (session, transcript na NFS, 3-week resume); I/O-bound (čeká na Anthropic API, ne na CPU); přežívá BE redeploy
Gradle build + testEphemeral K8s Job v tenant ns, worker je dispatchneStateless, bounded runtime, paralelní — cluster scheduler je přirozený concurrency manager; izoluje OOM per job
Image buildKaniko Job (již existuje — ADR-019)Pattern se rozšiřuje konzistentně; žádná duplicita

Ephemeral Job pattern pro gradle/test je přímé rozšíření Kaniko Job patternu (ADR-019) na dev-loop.

5. Škálovací páka: ResourceQuota per namespace dle plánu

ResourceQuota a LimitRange per tenant-environment namespace, klíčované plánem uživatele = plan-based vertikální sizing bez nutnosti sdíleného výpočetního prostoru.

Namespace-per-tenant-env (ADR-023 §5 / ADR-015) je substrát: namespace = jednotka kvóty

  • domov worker podu + domov build/test Jobů.

Consequences

Pozitiva

  • Maže nejbolestivější pipe boundary — odstraňuje složitost, nepřepisuje ji na jiné místo.
  • Konkurenční buildy/testy = nezávislé K8s Joby — cluster scheduler je concurrency manager; žádný single-process strop; OOM jednoho buildu nezabije ostatní konverzace.
  • Per-tenant izolace + billing attribution — worker pod žije v tenant ns; resource consumption je přirozeně atribuovatelný bez heuristik.
  • Worker lifecycle odpojený od BE redeployů — 3-week session resume je čistší; BE rolling update nepřerušuje živé Mara konverzace.
  • Jeden runtime ve worker podu — žádný JVM ~800 MB baseline navíc na tenantu; Node worker je řádově lehčí.
  • Gateway-proxy centralizuje klíč — billing metering, rate limiting a secret management na jednom místě.

Rizika — designovat dopředu, nikoliv objevit v produkci

  1. Cold-start ephemeral Job per build. Schedule latency + image pull + gradle/JVM daemon warmup → viditelný delay před prvním výstupem buildu. Stejný root cause jako žitá bolest „BE test suite runtime nás zabíjí”.

    Mitigace (patří do implementace, ne do budoucích ADR):

    • Gradle build cache na NFS (working tree je na NFS already — cache volume se přidá do Job specu)
    • Pre-pulled builder image na každém K8s nodu (DaemonSet image prepull nebo imagePullPolicy: IfNotPresent + warm cache)
    • Reused cache volume v Job specu (PVC sdílený přes Jobs v tenant ns)
    • Případně warm pool pre-started Jobů (post-alpha optimalizace)
  2. K8s RBAC pro worker pod. Worker v tenant ns potřebuje práva na dispatch Jobs (CREATE/GET/WATCH/DELETE) scopovaná na daný namespace. Privileged-ish komponenta — nutno omezit explicitním Role + RoleBinding (NE ClusterRole). Gateway-proxy minimalizuje co worker drží.

  3. Rewrite-risk. I „thin” worker je re-implementace SSE streaming, cancel, usage-reporting z Kotlinu do TypeScriptu. #104 HMAC seam pomáhá definovat rozhraní, ale parity testy jsou nutné před vypnutím staré cesty. Stará cesta (AgentSidecarExecutor) zůstává aktivní až do ověřeného cut-over.


Alternatives Considered

Viz sekce Context — alternativy A (síťový sidecar decouple) a B (Kotlin mikroservisa s child procesem) byly formálně zváženy a odmítnuty. Schválený tvar C (Node worker in-process per tenant-env) je jediný, který zároveň maže pipe složitost, přirozeně škáluje přes K8s scheduler a zachovává čistý boundary pro billing a klíč management.


Implementation Notes

Závislosti a pořadí

Tento ADR závisí na namespace-per-tenant-env modelu jako svém substrátu. Implementační pořadí:

  1. ADR-023 DB refactor (infra#18talkide-be#109 / talkide-be#110)
  2. Namespace-per-tenant-env model (ADR-015 plně aktivní v produkci)
  3. Tento ADR — worker extraction
  4. Test kolečko Stopy B (end-to-end preview + publish flow)

Tento ADR vědomě rozšiřuje definition-of-done Stopy B — user toto rozhodnutí explicitně přijal.

ReferenceVztah
ADR-023 §5Namespace-per-tenant-env substrát (nutná podmínka)
ADR-015Namespace provisioning (worker pod se provisonuje do existujícího ns)
ADR-019Kaniko Job pattern — rozšiřuje se na gradle/test Joby
ADR-014K8s klient (fabric8) — reuse pro Job dispatch z worker podu
talkide-be#104HMAC seam — stává se základem reporting kanálu worker → control-plane
talkide-be#105BuildAwaiter — maže se pod tímto ADR

Vysokoúrovňové refactor dopady

Dopady jsou popsány jako změny v architektuře, nikoliv jako issue tickety (PM vytvoří issues až sekvence dorazí na implementaci).

OblastDopad
Nová Node worker serviceNová TS codebase + image + CI pipeline + K8s Deployment manifest v tenant ns
Smazání pipe mašinérieAgentSidecarExecutor, stdout reader, NDJSON pipe, BuildAwaiter — smazat po cut-over
Thin orchestrační API v control-planeCreate/destroy worker endpoint, dispatch handshake, worker health check
Gateway-proxyNový endpoint v platform BE proxující Anthropic API volání; drží a neexpozuje Anthropic klíč
Gradle build/test jako K8s JobyNový GradleJobBuilder analogický KanikoJobBuilder (ADR-019); worker jej volá
ResourceQuota per ns dle plánuResourceQuota + LimitRange manifesty per tenant-env ns, parametrizované plánem
NFS cache strategieGradle build cache PVC per tenant ns; Job spec montuje cache volume; strategie invalidace
RBAC pro worker podRole + RoleBinding v tenant ns scopující Job dispatch práva

Was this page helpful?

Thanks for the feedback.