Specification · core repository · v3.3.185 · bd6e2c3

Triage Core

The decision layer of Triage: one controller on Kubernetes that receives signals from agents, alert providers and people, decides which of them are issues, keeps what it learns as MemoryCells, and tells the right people through channels, a messenger, an API and MCP. Rules, the service graph and recorded patterns decide in the event path; a decision model answers typed questions; a reasoning model reflects a few times a day and never opens an issue.

CoreDecides. Classifies events, opens and resolves issues, keeps memory, runs the reasoning loops and serves every interface.this specification
AgentCollects. Discovers virtual apps, measures them with eBPF, cgroups and logs, and streams the result over one outbound WebSocket.separate repository
UI bundleShows. A web and terminal interface the core serves from an OCI artifact on the same origin as the API. The core compiles no UI in.named by a cell
SOURCES CORE · ONE CONTROLLER OUTSIDE THE CORE AgentsWSS, join certificate Event webhooksalert providers, bearer People and toolsUI · TUI · MCP · voice Event routerper tenant, caps, dedup Classifier, ledgerallocation, evidence Issue engineopeners, holds, lifecycle Interrupt shieldwhat a channel hears HTTP API, MCP/v2, WebSockets, voice Tenant registryworkspaces, keys Service graphdeclared + observed MemoryMemoryCells in Kubernetes Reasoning loopsframe, reflect, RSI Workspace messengerrooms, chats, spots Decision modeltyped questions, own budget Reasoning modelVertex AI, Gemini, gateway Notification channelswebhooks, Web Push, FCM Messenger homeserverMatrix, cluster-internal Event path to a channel Model calls Reads, writes, requests
Figure 1. Components and boundaries. The reasoning model is reached by the loops and the author pass; the decision model by the openers, the severity call and the prediction ranking.
01

Architecture

The core is one Go binary run as a Kubernetes controller. It reconciles MemoryCell objects, serves the HTTP API with the MCP server on port 8080, runs a second MCP listener on 8083, an events server on 8086 for alert webhooks and the agent WebSocket, an MS Teams connector on 8085, health probes on 8082 and, with several replicas, a gossip mesh on 8088. Nothing is compiled in for the UI; a ui cell names an OCI artifact that the core pulls and serves.

All persistent state is a MemoryCell of the xray.ai/v1 kind in the core's namespace: events, issues, knowledge, patterns, but also the configuration (policy, runtime document, prompts, notification routing, the tenant registry and each tenant's service graph). The chart ships that one CRD. The state that is only useful while the process runs, such as the event hub, the service ledger, the situation frame and the rate limiters, lives in memory, belongs to one replica and starts empty after a restart.

Two kinds of decision run side by side. The event path is deterministic code driven by policy, the service graph and recorded patterns, with one decision model for typed questions; it runs on every event. The reasoning loops are model passes that run a few times a day per tenant and write what the event path reads next. No model opens an issue.

Runtime

PropertyValue
FormOne static binary in one container image; Helm chart triage-core; one Deployment, headless
Pod securityNon-root user 65532, read-only root filesystem, all capabilities dropped, no privilege escalation, seccomp RuntimeDefault
ResourcesRequests 500m CPU and 256 Mi; limits 3 CPU and 512 Mi
Kubernetes accessMemoryCells (cluster-wide read and write), Leases, Events (read), TokenReview (create); in its own namespace Secrets (read), Roles and RoleBindings (read, and write with the tenant registry)
Optional sidecarsA local embedder and a local chat model (llama.cpp), both off; when on, model traffic stays on the pod
Optional workloadA Matrix homeserver StatefulSet for the messenger, off by default, reachable only from the core
MetricsThe Prometheus endpoint is off by default (--metrics-bind-address); metrics and traces leave through the OTLP exporter set in the runtime document

Edges of the system

  • The core opens connections outward only to model providers, the OAuth and OIDC providers, Firebase, notification webhooks, OCI registries and the remote MCP or A2A agents the runtime document lists. Agents connect to the core; the core never connects into a cluster.
  • Optional pieces fail soft. With no decision model the engine keeps its own severity; with no reasoning model the loops do not run; with no homeserver the messenger routes answer 503.
02

Workspaces and tenants

A tenant is the unit of isolation: its own service graph, events, issues, memory, goals, notification channels, budgets and agents. People see a tenant as a workspace. Every event, cell, frame and API row carries a tenant, and every read and write is checked against what the caller may do on that tenant.

Tenants come from one of two places. With tenants.registry on, the registry cell is the source and the API creates and edits them. With it off, a tenant exists when a service-graph cell for it is applied. In both cases there is no default tenant and no static tenant flag; write ingress fails closed until a tenant exists.

Registry record

FieldRule
idOpaque UUIDv7, assigned at creation, never reused
nameDisplay name, unique inside its account
stateactive, disabled or purging; a delete is soft and purges after 30 days, and an admin can restore it before then
licencesHow many agents may be bound to the workspace; 0 means no limit. Counted over bound agents plus unused, unexpired keys, and checked when a key is minted, when an agent joins and when a binding changes
trialEndsRecorded for trial workspaces and not enforced
accountEmpty for an operator tenant, otherwise up to 128 letters, digits, ., _ and -; compared ignoring case; a change is logged with the old value, the new value and the caller
spotNameThe name of the workspace's spot (section 11): 2 to 32 characters, starting with a letter or digit, never tria; empty means <workspace name>-spot

Members and roles

A member holds one of three workspace roles, stored as a Role per role and a RoleBinding per member in the core namespace, labelled with the tenant id and named triage-tenant-<tenant>-…. The subject is <provider>:<id>, at most 200 characters without a space or slash.

RoleVerbs on the workspaceCan do
ownerget list create update execute ingest voice observeGiven to the person who creates the workspace
adminthe same verbsKeys, agents, members, binding requests, notification channels and the spot name need write and execute
viewerget list observeRead issues, memory, graph and rooms

Trial workspaces

A person who is not an instance admin can create a workspace when they hold a GitHub session whose organisations include the requested account. They become its owner. The limits come from flags and never from the request: --tenant-trial-days (14), --tenant-trial-max-per-org (1) and --tenant-trial-licences (3).

Isolation in memory

A cell is visible to every tenant when it carries the shared tag and to one tenant when it carries tenant/<name>. An untagged cell of a tenant-scoped kind resolves to no tenant on a multi-tenant instance and is not served. The rule is applied on the model-facing paths, on the REST surface and on MCP, and a caller who is not an admin cannot write a tenant/, shared or author/ tag. Private cells carry scope: private and the hash of one user; only that user sees them, and MCP treats them as absent.

03

Event intake

Every event enters through one router that stamps the tenant, applies the admission limits and collapses repeats. The source string decides how much the pipeline trusts the event: sources that begin with k8s/, app/, flux/ or events/ are machine sources; everything else, including mcp/, msteams/ and a2a/, is a human source.

PathCarriesCredential
Agent WebSocket
/events/ws
Virtual-app snapshots and deltas, Warning events, RED, USE and log metrics, topology, sensor state, heartbeat (section 8)Join certificate, checked in the first frame
Alert webhooks
POST /events/<provider>/
Provider alerts and cloud events; body up to 64 KiB except the streaming GCP decoderBearer: a Kubernetes ServiceAccount token or a key of the api-tokens Secret, with the i bit on the tenant
Status ingress
POST /events/status/
Readiness for services that are not Application objectsAs above
MS Teams
POST /msteams/webhook
Outgoing-webhook messages; body up to 1 MiBHMAC-SHA256 of the body under a per-conversation secret
MCPemit_event, comment_on_issue, resolve_issue and the rest of the tool surface; always the human pathServiceAccount token or MCP OAuth token

A webhook names its tenant with X-Tenant, the tenant query parameter or a body field. The value auto resolves the tenant from the tenants' service graphs and fails closed when no graph or more than one graph matches. --events-ingest-authz is enforce by default: a scoped token without the i bit on the resolved tenant gets 403. In log mode the request proceeds and the decision is counted, which is the way to migrate tokens.

Admission

LimitBehaviour
Agent event capA daily cap per (cluster, tenant) from policy (agentEventsDaily, default none), the registry default or an admin's per-agent value; a token bucket holds one hour's share. Over the cap the frame is refused and counted
Frame rateA token bucket per connection (agentFramesPerSecond)
DedupEvents collapse by (tenant, source, type) fingerprint into a count; the fingerprint table holds up to 10,000 entries. Dedup keeps the count and the service ledger is credited for every repeat
Hub ringThe live window of events is a ring in memory; the durable archive is the event cells, kept 7 days

Screening

A regex pre-screen for prompt injection runs on every event. A human-source match is dropped. A machine-source match increments a counter and is logged, and the event is never dropped on the regex. Human events then go to the guardrail model, on by default (models.guardrailEnabled), which labels each INFRA, NOISE or INJECTION; an error or an empty answer drops the event (fail closed), and a semantic cache (similarity 0.88, window 24 h) answers repeats. With the decision model on, its human-guardrail label is applied as well, and NOISE and INJECTION end the event. Machine events never reach a guardrail model.

Event text that reaches a prompt is wrapped as untrusted data between per-call random fences with an instruction not to follow it.

04

Issue lifecycle

An issue (a concern in the code) is the unit a person acts on: a service, a severity, a summary, the events behind it, a cause and the patterns that bear on it. Its status is new, investigating or resolved. Figure 2 shows the path from an event to an issue.

Routertenant, caps, dedup Screeningregex, guardrail (human) Allocationgraph names the service Service ledgererror families per instance Openers level state (O1) 2 families in 10 min (O2) 15 min persistence (O3) cloud provider critical situation frame, reflection Holds human verdict, 1 to 30 days quiet scope flap gate after 5 reopens recorded pattern, judged a held event stays counted Issue new, investigating, resolved info, warning, critical severity from blast band reopens inside 6 h decision model: one step Author pass words once per issue links caused-by, duplicate may not open or re-rate then the interrupt shield decides what a channel hears
Figure 2. From event to announced issue. The decision model is asked one question before an open on a service with a recorded pattern, and holds the open at 0.5 or above.

Allocation and the ledger

The service graph decides which service and instance an event belongs to. It never decides whether the event matters: noise means unallocated, an event no graph node takes, and it opens nothing. An allocated event is never dropped for its tier, its dependents or its blast radius. Every error-bearing event is credited to its service instance in the service ledger by signal family: level, request, log, resource, pod and provider. A row keeps 60 one-minute and 288 five-minute slots, rows are capped at 2,000 per tenant, and the least recently credited row goes first. The ledger answers the question "why did Triage say nothing" and feeds the situation frame, the tenant ledger endpoint and the MCP tools inspect_service and get_tenant_situation.

Openers

An issue opens on evidence that something happened to a service, never on blast radius. These are the only openers; each is code, and a model takes no part in the decision.

OpenerRule
O1 level stateAn AppDegraded that the level state has not superseded opens an issue at any blast radius
O2 corroborationTwo or more independent error families credited on one instance within classification.evidence.corroborationWindowSeconds (10 minutes) open an issue; three or more raise its severity one step
O3 persistenceAn instance continuously NotReady, or an error family firing without a break, for persistenceDwellSeconds (15 minutes) with no open issue on the service opens one; twice the dwell raises info to warning
Cloud criticalA critical event from a provider that speaks for a cloud (the event names its cloud, or its provider segment is gcp, aws or azure) opens at once. A monitoring webhook such as Alertmanager or Datadog is evidence: it corroborates under O2, is absorbed by an open issue and never opens alone
Situation frameWith situation.autoRaise on, a service the frame lists with a level reason (down, crashloop, pressure, correlated, shared-sink) is raised
Reflection raiseBehind situation.actions; held when the frame would raise the same level fact itself, unless the cause passed the verification pass (section 6)

Pod-family events (BackOff, Unhealthy, Failed, FailedScheduling and the rest of the pod-failure role) are one instance fact and never open an issue by themselves. A crash loop opens through O1, a second family through O2, and a probe that keeps failing on a Ready workload through O3.

Severity

The band comes from blast radius, the share of the catalog that depends on the service. At or above the tenant's INCIDENT threshold an issue is critical; between the NOISE and INCIDENT thresholds it is warning; below the NOISE threshold, for a leaf, or for a noise-tier node it is info. Evidence raises an info issue to warning and never to critical. A provider-critical event keeps critical. Each channel's minimum severity decides who is paged.

With the decision model on, one score question on the ordered scale info, warning, critical is asked per new issue, from evidence only (the change, what was already said in the last 24 hours, the service's place in the graph, its seven-day issues, its situation line and its ledger families in the last 10 minutes). The issue takes the engine's severity moved at most one step toward the answer. A failing or absent model leaves the engine's severity.

Holds

HoldBehaviour
Human verdictA person resolves with noise (never an issue) or expected (known and accepted) and a window as they word it (7d, 36h; default seven days, ceiling thirty). noise holds every machine open of the same source, expected every machine open of the same service in the tenant. A human report is never held
Quiet scopeclassification.quiet names service tokens (default synthetic, e2e, canary, smoke) and namespaces for which no machine opener creates an issue. The events are allocated, counted and shown in the frame, and an open issue on the service still absorbs them
Flap gateA recurrence after flapSuppression.minReopens (5) reopens is suppressed unless the instance is NotReady past the bypass duration. Reopens inside the reopen window (6 hours; provider sources use hours in providerReopenWindowHours) reuse the issue
Recorded patternBefore any machine open on a service that has a recorded pattern, the decision model is asked one question: is this an instance of a known pattern, expected, with nothing new to open? At 0.5 or above the open is held. A service with no pattern makes no call. A schedule written in a pattern is a hint the judge weighs, never a rule

Author pass

After every machine open, one model pass rewrites the engine's templated summary into words an on-call engineer reads in five seconds and, with link_concerns, draws caused-by and duplicate-of edges to the tenant's other active issues. It may not create an issue, change its severity or source, or call any other tool. An issue is authored once; the pass runs again only when its severity has changed since the words were written. The write is its own mutation kind and does not move the issue's LastSeen, since words are not evidence.

Resolution

PathRule
Level reconcilerResolves an app/ issue once the instance has been Ready for the recovery window and its non-level error families have been quiet for the same time
Provider noticeA webhook whose status is resolved is marked recovered whatever its type says; cloud incidents that close are decoded into such a notice. A provider issue with no activity for 72 hours resolves on its own
Personresolve_issue or the API, with verdict fixed (default), noise or expected, a root cause and a fix
Recognised patternAn open issue with linked patterns is asked about again when a fresh machine event joins it (at most hourly), when a pattern is linked, and once at start. A recognised issue resolves with verdict expected, resolved by pattern, naming the pattern, the judge, its probability and its reason

A resolve is evidence. The resolved record, its verdict and its root cause are what the judges and the pattern track read next. Patterns link to issues in both directions: matches is written by code at open and reopen from the tenant's active patterns on the service, and explains is written when a person or the reflection pass names the pattern as the cause.

05

Memory

Memory is one substrate. Every cell is an xray.ai/v1 MemoryCell with a kind, a subject (the producing instance), tags, an origin, links to other cells and an optional embedding. The core watches the cells in its namespace whose subject matches --memory-subject. Embeddings come from text-embedding-005 on Vertex AI, or from a gateway or sidecar (nomic-embed-text, 256 dimensions); an HNSW index serves similarity search.

Kinds

GroupKindsWritten by
Raw inputevent, issue_claim, analytics_counter, precursor_patternCode only. A model or MCP write of these kinds is rejected
Knowledgestable_fact, operational_pattern, watch_item, hypothesis, mindmap, noteReflection, knowledge and RSI passes, MCP and chat for people
Identitysystem_identity prompt cellsThe chart, overridable by flags
Configurationmental_map (a tenant's service graph), system_configuration, policy, policy_rsi, reflex_rule, notify_channels, tenant_notify, tenant_goals, agent_join, tenant_registry, ui, doc_page, landing, about, howtoOperators and the API. Exempt from retention, never minted from the in-memory map, never writable by a model

Provenance

Every cell has an origin: observed (code wrote it from facts), derived (a model wrote it), human (an identified user wrote it through MCP, chat or voice) or verified (derived, then passed a check in section 6). A cell without an origin is derived. Prompts receive the origin on every cell with one rule: human outranks derived, and a derived cell is a prior claim, never evidence. Code enforces the rule where a model chooses what to rely on: an id accepted as a pattern or a basis must be observed or human.

Retention

DataKept
Event cells7 days
Resolved issues90 days after resolution; open issues are never deleted; a working-set cap of 2,000 per tenant
Analytics counters and precursor patterns30 days, rolling
Knowledge (facts, patterns)90 days; the mind map has a stale_after of 7 days (bounds 1 hour to 30 days)
Episodic digests180 days
Provider issuesAuto-resolve after 72 hours without activity

Windows are tenant-overridable in policy. Knowledge writes go through an upsert that merges a near-duplicate in place, never against a shared or a foreign-author cell. Event cells collapse per (tenant, source, type) fingerprint.

How cells are applied

Configuration cells carry their document as base64 bytes under spec.data. The reconciler decodes the document with strict field checking and then a validator. A rejected document keeps the previous good state, so a typo never replaces a working configuration. A cell write is a Get then Create or Update with optimistic concurrency on the resource version, with labels set wholesale and the embedding preserved. A service graph is either inline (gzip of a gob, decoded under a 64 MiB bound with a panic-to-error guard) or a digest-pinned OCI reference; a tag-only reference is refused and a failed pull keeps the loaded graph.

06

Reasoning loops

The loops are the slow half of the core. A deterministic situation frame describes each tenant; reflection and knowledge passes read the frame and memory and write what the event path reads next; RSI tunes the knobs and the patterns from the outcomes people confirm. Figure 3 shows the loop.

SYSTEM 2 · REASONING MODEL, A FEW TIMES A DAY Situation framedeterministic, per tenant Reflectionevery 3 h, or on a change Knowledgeevery 8 h, facts and patterns Memoryorigin: derived, verified RSI · FROM CONFIRMED OUTCOMES Human verdictfixed, noise, expected RSIknob track, pattern track Knobs and patternstenant overlay, pattern cells Event pathopeners, holds, judges patterns, resolved issues, counters resolves by people and the machine
Figure 3. The loops. Solid: what is written and read. Dashed: evidence returning to the loop.

Situation frame

The frame is built without a model from the graph, the RED and USE snapshots, the signals ring, the event cells, the classifier's matches and the ledger. It has one entry per notable service, each with its facts, how long they have held and a one-word reason, capped at situation.maxServices (12). A service is notable when it is not Ready; when memory has stood above the floor (85 percent of its limit) for the hold (30 minutes); when OOM kills are increasing; when log, request or resource anomalies appear in more than streamWindows (5) windows of the last hour; when the error rate is above its floor (5 percent); when an issue is older than the stale age (12 hours) with no level evidence; when more than one issue is open on a service; or when a catalog node's workload names never matched. It also lists sharedMatches, the graph nodes that the anomaly text of two or more other services matched. The frame is served at GET /v2/tenants/{tenant}/situation and by get_tenant_situation, and it wakes reflection when it changes, at most every wakeEvery (10 minutes) and wakeCooldown (30 minutes).

The core describes its own dependencies the way it describes a tenant's. A ledger of self-signals records, for the decision model, the reasoning model and the embedder, the last error with its status, the failures in the last hour and the last success. It appears in every frame, in the RCA brief when the issue's service is the core, and in get_triage_agent_status.

Reflection and knowledge

PassCadenceReadsWrites
Reflectionloops.reflection.interval 3 h; a new issue wakes a scoped pass at most once per tenant every 10 minutes after the loop's 2-minute debounceThe frame first, then issues, memory, predictions, the changes waiting to be announcedwatch_item cells, mind-map cards for up to 5 services a pass, noticed observations, announcements (one message per story); raise, resolve and merge only when situation.actions enables each
Knowledgeloops.knowledge.interval 8 hMemory, resolved issues and their verdicts, patterns without a factstable_fact and operational_pattern cells; one fact per group of patterns that share a cause, at most patternFactsMaxPerPass (5) groups a pass

Each loop has a cooldown, one hour by default, that separates two passes on one tenant. Model output is parsed as strict JSON into operations; kinds outside the allowed set are dropped, a mind-map operation must name a service in the pass's hot set, every tenant/ and shared tag a model writes is replaced by the sub-pass's tenant, and malformed output yields a summary and no operations.

Verified reasoning

A derived claim is checked before it is trusted. With loops.verify on, a fact's cause is tested against live evidence, with at most 3 checks per cause and 500 a day and a minimum support of 0.6. A fact whose cause cannot be tested is demoted to a hypothesis, which expires; a fact that passes becomes verified. Edges in the observed graph carry a role, request, telemetry or control, taken from the destination port, the protocol and a policy list of telemetry-sink names. A telemetry edge stays in the graph for provenance but is not a dependency: blast radius, the dependency layer, the classifier's "Depends on" line and the RCA call path never see it.

Predictions

A precursor matcher raises candidate services from the sequences that preceded past issues (predictions.window 14 days, precisionWeighting on). Each candidate is ranked by the decision model (how likely an issue is within the hour, from its evidence), and the answer becomes the prediction's confidence. An elevated prediction needs a confidence of 0.5; a notification needs 0.8 and re-arms after the confidence falls below 0.5.

Patterns and RSI

A pattern is an operational_pattern cell: a state of one service that people recorded as expected, such as a nightly scale-down, with an optional window. It holds machine opens (section 4), is linked to the issues it explains, and is the base of knowledge: the knowledge pass writes the fact behind it. RSI (recursive self-improvement) reads the outcomes the core can measure and proposes changes. It is off by default.

ModeBehaviour
shadowThe default. Records what it would change and applies nothing. A person decides every pattern proposal
applyApplies knob changes under the gates below. A person still decides pattern proposals
autonomousApplies knob changes and decides its own pattern proposals in the pass that wrote them, inside applyTenants. A new pattern is activated as derived knowledge with medium confidence, a revision writes the new text and window, a retirement expires its target. Earlier waiting proposals are decided too, oldest first

The gates are a consistency streak (applyAfter, 2 passes), the applyTenants list, one applied change per tenant per day, an evaluator that reverts a change that did not improve its objective by minImprovement (0.2) over the evaluation window (6 hours), and the operator's own settings, which win field by field over the RSI overlay. The loop runs every 30 minutes, and a burst of alerts (wakeAlertsPerHour, 6) wakes it with a 30-minute cooldown. The knobs are registered with their bounds: the flap gate, the alert repeat interval and the like.

The pattern track has three parts. New: from one issue a person resolved with a verdict or a root cause, or a flap the machine closed after at least minReopens reopens with nobody's verdict, it drafts a pattern. Revise: when an issue opened on a service after its pattern was recorded and a person resolved it as expected or noise, the reasoning model reads the pattern and the misses and answers with the text or window that would have held them, or with nothing. Retire: a derived pattern that explains nothing is pruned. People see all of it: list_patterns and the memory explorer show the author tag rsi, the status block counts decided and pending proposals, and refute_memory_item, expire_memory_item and approve_pattern remain for a person who disagrees.

07

Models and budgets

The core consumes models and hosts none. Every role is configured in the runtime document, and a role's credential is a Secret reference resolved into memory; there is no API-key flag. Vertex AI uses Workload Identity when googleCloudProject is set on the role's block.

RoleDefaultNotes
Routing, reflection, knowledge, mind map, RSIgemini-2.5-flashPer-role overrides in the runtime document; Vertex AI, the Gemini API or an OpenAI-compatible gateway (baseURL)
RCAthe main modelmodels.rca. build_rca returns a brief built from the graph; the model call is made by the client
Guardrailonmodels.guardrail, models.guardrailEnabled; human sources only
Embeddingstext-embedding-005Or a gateway or the local sidecar
VoiceGemini Live native audioVertex AI or the Gemini API on a Google AI key
Text chatgemini-3-flash-preview--chat-model, on the voice block's project or key
Decision modeloff until a key is namedA hosted decision service or any server that speaks the same protocol, set in decisions (url, model, apiKeySecretRef or tokenFile)

The decision model

The decision model answers typed questions about a JSON state: a probability, a score on an ordered scale or a choice among options. It is the only judge on the event path. A call has a 15-second timeout and a 1 MiB response cap. It is consulted at these points, each with its own switch in decisions (all on by default, tenant-overridable, and all inert when no model is wired):

SwitchQuestion
severityHow severe is this new issue (a score; the engine's severity moves at most one step)
humanRoutingWhich service is this human report about
humanGuardrailIs this human message INFRA, NOISE or INJECTION
predictionHow likely is an issue on this service within the hour
gateIs a new or reopened, non-critical issue worth the on-call channel once it has outlived the blip window; held below 0.5, else on to reflection
patternsIs this open an instance of a recorded pattern of the service; held at 0.5 or above

The URL must be https, or http to a loopback host or a .svc name, checked when the document is validated and on every send. A token file is read on every call and must resolve under tokenFileRoots (default one directory); the service-account directory is refused. A 429 with Retry-After up to 10 seconds is waited out once; a longer one makes the client refuse calls until the time is up, and no pause exceeds 60 seconds.

Budgets

Every figure is a sum over today's rows of one usage ring, per tenant and per call kind, so the gate and the status read the same numbers. Failed calls count, since the ring records what the provider billed. One resolver reports spent, cap and the limit that blocked: instance, kind or tenant. The day is UTC.

BudgetDefaultNotes
Instance LLM tokens1,000,000 a daybudgets.llmDailyTokens; fail-closed in the chat and request paths; covers routing, guardrail, RCA, reflection, knowledge and RSI
Tenant LLM tokens100,000 a dayllmTenantDailyTokens per tenant, llmTenantDailyDefaultTokens otherwise; guardrail calls are exempt
Kind capsagent 400k, reflection 250k, knowledge 250k, rca, guardrail, chat and voice 100k, rsi 50kllmKindDailyTokens; other kinds 100k
Guardrail reserve20,000Guardrail calls are refused only at the instance limit plus the reserve
Decision model200,000 per tenant a daySeparate from the model budget; over it, the caller proceeds without an answer
Spot1,000,000 per workspace a dayPaid tokens of the turns a spot reports; not counted in the instance, tenant or kind caps; 0 is no cap
Chat1,000,000 per user a dayPlus the chat kind cap; a write-class tool call asks the client to confirm
Voice100,000 per user a day--voice-user-daily-token-limit; MCP guardrail_event and decide_event spend counts against it
Per-call output8,192 tokensWhen maxTokens is unset
Circuit breaker5 failures, 60 sllmCircuitBreaker in the runtime document
08

Agents and joining

An agent opens a WebSocket to /events/ws and proves who it is in the first frame. The HTTP request is upgraded without a credential and authenticated in-band; the first frame has a 90-second deadline and an 8 MiB read limit, and a same-origin check admits requests that carry no Origin. Authentication is the join certificate and nothing else. A hello without a certificate is refused.

Certificate join

  • Joining is enabled when the agent CA is configured (clusterAgents.join.caSecret). The CA is Ed25519; a previous CA can be set for rotation and cross-signs the current one.
  • The core sends a challenge nonce first. The agent's join, renew or hello frame signs it, and the agent verifies the core's CA signature over the same nonce.
  • A CSR carries the cluster name and the tenant list. It is issued only when an approved agent with the same fingerprint and tenant list exists. Certificates last 24 hours (--agent-cert-validity), a certificate issued before the latest approval is refused, and revoking an agent closes its live sessions.
  • A request that is not yet approved waits in a pending list (100 entries, 5 per source, 5 per cluster, 24-hour TTL). An admin approves, denies or revokes through /v2/agents and can set an agent's own daily event cap.
  • Join attempts are throttled per presented identity (6 a minute) and per source (60 a minute). The source is the peer address, or the Nth X-Forwarded-For entry from the right when trustedProxyHops is N.

One-time keys

A workspace manager mints a key (tak_<id>_<secret>, 256 bits of secret, only its SHA-256 stored) that lasts seven days and is spent by the first successful join. Kinds: cluster (several tenants), host (one tenant, a Linux host) and otlp (one tenant, no Kubernetes watch). The join frame carries the key, a CSR and an Ed25519 proof over the nonce; the CSR's cluster name and tenant list must equal the key's, the certificate is issued at once, and the core assigns the cluster name (the label plus an eight-character suffix). The API returns the install command: a Helm line with core.host, core.caFingerprint, core.clusterName, core.tenants and core.agentKey, or for a host the install.sh line.

Bindings

A key-joined agent holds one binding per tenant, each with an optional namespace list (* or empty means all). The receiver filters snapshot, delta, event, log, RED, USE and topology frames to the binding's namespaces and drops frames for tenants outside the bindings. Heartbeat and sensor-state frames are not tenant-filtered, but each OTLP source entry in a sensor-state frame carries a tenant stamp that is checked against the bindings. Bindings change through POST /v2/agents/{cluster}/bindings: replace the namespaces of one, add a tenant the caller manages, or file a request that the other tenant's managers answer (7-day TTL, 1,000 requests globally). An agent with no binding left is revoked, and deleting a tenant ends its bindings and unused keys at once.

Frames

DirectionKinds
Agent to corehello, join, renew, snapshot, snapshot_end, app_delta, log_metrics, red_metrics, use_metrics, red_summary, use_summary, topology_snapshot, sensor_state, heartbeat
Core to agentchallenge, join_result, pong, resync, limits
Bounds100 services per metrics or summary frame; 200 nodes and 500 edges per topology frame; samples truncated to 256 characters; percentages clamped to 0 to 100; labels checked against Kubernetes label rules

Every frame carries a tenant. The hello's tenant, an id or a label unique among the certificate's tenants, and every frame's stamp are validated against the certificate's tenant set. A second connection for the same cluster is refused while the first is live.

09

Authentication and grants

The HTTP API, the WebSockets and the MCP server accept two credentials: an SSO session, and a Kubernetes ServiceAccount token verified by TokenReview with audience triage-core. Static bearer tokens are not accepted there; the keys of the api-tokens Secret authenticate the events server only. With no SSO provider and no ServiceAccount, every gated request gets 401. sso.authDisabled (--auth-disabled) is the only way to run open, for a lab.

Credentials

CredentialDetails
GitHub sign-inOrganisation required, team optional. The session identity is encrypted with AES-256-GCM; the cookie triage_session is host-only, HttpOnly, Secure and SameSite Lax; state travels in a signed cookie valid for 600 seconds. The session records the admitted organisations for /v2/auth/me
OIDC sign-inAuthorization code with PKCE S256 and a nonce, ID token verified for issuer, audience, signature and expiry. The subject is <prefix>:<lower-cased sub>; no groups, no organisations
ServiceAccount tokenTokenReview, audience triage-core, only from the core's own namespace; results are cached no longer than the token lives; expiry is tracked and warned before it lapses
Device tokenA seven-day token a signed-in user mints (10 per account), scope empty or watch (read and voice only); exchanged for a 24-hour session and a replacement
MCP OAuth tokenAn EdDSA JWT from the core's own OAuth 2.1 server (below), audience <public-url>/mcp, 15 minutes, no refresh

Grants

Authorization is a map from tenant to a set of six capability bits. Bits never imply one another, and the wildcard tenant * is the instance.

BitCapability
rRead
wWrite
xExecute: run a loop, call a tool with effects
iIngest events
vVoice and chat
oObserve: the tenant name list only
Named roleBits
readerr
ingesteri
operatorrwx
assistantrxv
observero
adminrwxivo

Grants come from Kubernetes RBAC: Roles and RoleBindings in the core namespace, with rules on the resource tenants of the group triageagent.dev and the tenant names (or *) in resourceNames. Verbs map to bits: get and list to r, create and update to w, execute to x, ingest to i, voice to v, observe to o, and * to all. Subjects are User github:<login>, Group group:<name> and a ServiceAccount in the core namespace. The chart ships the six named Roles on every tenant. Grants are resolved per request for a session, so a change applies at once. Older grant sources (admin-user lists and team-grant maps in the api-tokens Secret) still exist; configuring any of them arms enforcement, and an unarmed instance gives every session the admin grant while RBAC-resolved callers stay scoped.

Enforcement

  • REST and WebSockets resolve grants once per request (captured at upgrade for sockets). A ?tenant= route answers 403 for a name the caller cannot read, whether or not it exists; an unknown name is listed back only to a caller who could read some tenant of that name.
  • MCP filters every list, get and search by what the caller may read, hides write-scoped tools from a read-only caller and rejects them if called.
  • Voice and chat need v on the bound tenant. The pprof handlers need the admin grant. The tenant name list needs o.
  • Workspace management (keys, agents, members, binding requests, notification channels) needs write and execute on the tenant; account, licences, trial dates and restore need admin.

MCP OAuth server

When --public-url is set and an SSO provider exists, the core is its own OAuth 2.1 authorization server for MCP clients, co-located on port 8080: protected-resource and authorization-server metadata, dynamic client registration restricted to loopback and custom-scheme redirects, PKCE S256 required, a resource indicator that must match, single-use authorization codes that last two minutes, and an EdDSA token whose signing key derives from the configured client secrets so replicas agree. Tokens carry a scope, triageagent:mcp.read or triageagent:mcp.write, and either a grants string or an RBAC subject whose grants are read from the cache on every request. A shorter lifetime for write tokens can be set. Authorization codes live in one pod, so several replicas need session affinity for this flow.

10

Interfaces

Every service is a ClusterIP by default. A Gateway API route can expose one hostname that carries the MCP path, the events paths and the API; an nginx Ingress and a GCP backend policy are optional. The messenger homeserver has no route at all.

PortSurfaceCredential
8080HTTP API /v2/*, /mcp, the MCP OAuth endpoints, /login, /docs, /debug/*, WebSockets, the UI bundle on every other pathSSO session or ServiceAccount token; the pprof handlers also need the admin grant
8083MCP server over Streamable HTTPServiceAccount token or MCP OAuth token; always enforced
8086Events: POST /events/<provider>/, POST /events/status/, GET /events/wsBearer with the i bit; join certificate on the WebSocket
8085MS Teams connector POST /msteams/webhookHMAC per conversation
8082/healthz, /readyzNone
8088Replica gossip /gossip/ws, only with several replicasMutual TLS from the chart's gossip CA
6167Matrix homeserver pod, only with the messengerApplication-service token; a NetworkPolicy admits only the release's pods

HTTP API

The API is versioned under /v2 and described by an OpenAPI 3.1 document the core serves live at /v2/openapi.json and /v2/openapi.yaml, with Swagger UI at /docs. The groups:

GroupCovers
AuthGitHub and OIDC login and callback, logout, session reset, device tokens, /v2/auth/me (username, grants, roles, tenant labels, organisations) and /v2/auth/providers
Agent dataIssues and their detail and fractal graph, services, applications, analytics, memory, the status and memory WebSockets
TenantsThe registry, restore, keys, bound agents, binding requests, members, notification channels, the situation frame and the ledger
Agents (admin)/v2/agents: list, pending, approve, deny, revoke, limits; present when the agent CA is configured
Sessions/v2/agent/voice/session and /v2/agent/chat WebSockets (below)
Push/v2/push/subscribe, /v2/push/unsubscribe and /v2/push/device
Messenger/v2/messenger/* (section 11)

The status WebSocket also sends {"changed":[…]} frames that name what changed, from the set apps, graph, analytics, events, memory, tenants, goals and ledger. A frame carries names only, at most one per topic per gap of 1 to 30 seconds, and a client refetches what it shows.

MCP

The MCP server defines 55 tools; six of them exist only when the messenger is wired. Tools split by who may call them:

ClassToolsGate
ReadIssues, services, memory, situation, goals, analytics, status, patterns, instructionsRead grant; every list, get and search is filtered by what the caller may read; private and foreign items answer as absent
Write (15)emit_event, upsert_memory_item, supersede_memory_item, expire_memory_item, refute_memory_item, build_memory, build_knowledge, resolve_issue, comment_on_issue, update_issue_severity, link_issues, test_push, test_notify, set_tenant_goals, say_in_workspaceHidden from a read-only token's tool list and rejected by the scope check
Model-calling (4)guardrail_event, decide_event, recognize_pattern, verify_hypothesisPer-caller limit of 1 a second with a burst of 10, plus the daily quota
Instancerun_rsi, consolidate_memory, approve_pattern, forget_issuex on *, or write on the issue's tenant for forget_issue

List-shaped results are compressed to 500 characters a row (--headroom-limit, 0 disables). analyze_issue uses the client's own model through MCP sampling and costs the core no tokens.

Voice and chat

Voice is a Gemini Live WebSocket; chat is a text WebSocket on the same tool set. Both answer 503 while disabled or without a credential, both need the v bit on the bound tenant before the upgrade, and both check the origin. A write-class tool call asks the client to confirm, except save_session_note and ask_spot. Quotas are in section 7.

UI bundle

A ui cell holds a digest-pinned OCI reference (uiRef) and optionally the name of a pull Secret. The core pulls the artifact, takes every regular file of every layer (64 MiB and 5,000 files at most, names normalised) and serves it on port 8080 for every path that is not an API or infrastructure path. A failed pull keeps the bundle being served. The home document carries an enforcing Content-Security-Policy naming its inline script hash, and a kill-switch service worker is served at /ui/sw.js.

11

Messenger and spots

Each workspace has a chat built on a Matrix homeserver that only the core can reach. The core is the only Matrix client: people use /v2/messenger, and the homeserver has no public route. The routes answer 503 while no homeserver is configured.

Rooms

RoomMembersNotes
Team roomThe workspace's members and its spot and TriaAlias ws-<tenant>
Agent roomThe spot and TriaAlias ws-<tenant>.agents; not listed to clients
Direct chatTwo peopleEvery new direct chat is end-to-end encrypted; there is no key recovery and devices verify by emoji comparison
Private chatOne person and Tria, per workspaceAlso reachable as text chat and voice; turns land in the same room

Every room route checks the read grant on the room's workspace per call. The write grant gates deleting other people's messages and pinning. A message the core posts for a person into a workspace room carries their level on the workspace in the event key app.triage.grant (read, write or admin); a forward carries the forwarder's level. Removing a member kicks their Matrix user from the workspace room and from their private chat, and clears their push registrations if they are in no other workspace room. Text is 1 to 4,000 characters and an upload up to 2 MiB.

An @tria mention in a room is answered as the mentioner, with their read and voice grants. The 40 earlier messages become turns, confirm-class tools and ask_spot are not offered, and the answer carries the asker's level. It gets 16 tool calls, 8,192 output tokens a call, a 4-minute window and a 30-second posting deadline, and counts against the per-user chat quota and the global chat budget. With messenger.multimodal on in the runtime document, the images and files of a room are shown to the model when it answers.

Spots

A spot is a per-workspace agent that lives outside the core, acts through MCP tools and speaks in the workspace's rooms under its spotName. It holds no Matrix credential. The core gives it a trigger stream, a budget and a status line.

PieceBehaviour
IdentityOne Matrix user per workspace, spot.<tenant>, member of the team and agent rooms. A name change through PATCH /v2/tenants/{id} updates the display name and the room member name at once
Toolssay_in_workspace (write; the caller identity must start sa:; team or agent room; 1 to 2,000 characters; hop 0 to 3), read_workspace_room (up to 50 messages, each with the sender's level), read_workspace_file (images, text, JSON and YAML up to 5 MiB, text cut at 64 KiB, labelled as attachment content), set_workspace_typing, set_spot_status (one line of 140 characters) and get_spot_identity
Post capsPer workspace and room per hour: team 12, agents 60. A message equal, after whitespace and case folding, to one of the last 5 in the room is refused. Counters live in each replica's memory
Tria to spotask_spot posts to the agent room as Tria with hop 1; read_spot_room reads it. Both run without a confirm in private chat and voice
Trigger streamGET /v2/messenger/spot/ws?tenant=, for a ServiceAccount with write on the workspace; a person's session is refused. Downstream frames: hello, issues (any state change, at most one a second), rooms (a message from someone whose level is write or admin, or from Tria other than her answers to a reader), name, budget. Upstream: turn (model, input, cached, output, duration, failed) and stats, 8 KiB a frame. The stream closes when the ServiceAccount loses its write grant
BudgetPaid tokens of reported turns count against spotDailyDefaultTokens (1,000,000 a day) per workspace. When it is spent, say_in_workspace and set_spot_status refuse. A turn report with an empty model, cached above input, a count over 50 million or a duration over a day is dropped
State shownLiveness (up for 90 seconds), the status line (6 hours), the stats snapshot (10 minutes), returned to readers in the room list
12

Notifications

One component, the interrupt shield, decides everything a channel hears. The engine only offers a candidate: a new issue, a resolved issue, a prediction crossing or an observation. The shield sets a new issue's severity with the decision model (section 4), queues the change for its tenant, and lets reflection write the message in its next pass. A new issue wakes a scoped reflection pass at most once per tenant every 10 minutes; changes that arrive meanwhile ride in the same pass, and recoveries, risk crossings and observations never wake one.

  • A new issue that is critical once its severity is set is sent at once, verbatim.
  • A repeat gate suppresses the same (kind, tenant, service) inside alerts.repeatInterval (1 hour, bounds 0 to 24 hours). Critical is never suppressed. RSI tunes this knob.
  • With the decision model's gate switch on, a non-critical issue that outlives the blip window reaches reflection only above 0.5.

Channels

Operator channelsWorkspace channels
RoutingThe notify_channels cell: per channel a type, a secret key, event kinds, a minimum severity and a tenant list (empty means all)A tenant_notify cell per workspace; the tenant list is the workspace's own id, taken from the cell and never from input
TypesTeams, Discord, Slack, Opsgenie, generic webhookDiscord, Slack, Teams, Opsgenie; the generic webhook is operator-only. Each type is bound to its provider hosts
URL storageOne dedicated Secret, one key per channelOne Secret keyed <tenant id>.<channel>; the API returns only urlConfigured, never the URL
LimitsPer-channel token bucket, 20 a minute with a burst of 5At most 5 channels a workspace; name 1 to 40 of [a-z0-9-]; maxPerMinute 1 to 30
Eventsissue_new, issue_resolved, prediction, observation; minSeverity is info, warning (default) or critical

A URL must be https, name a host (no IP literal, no localhost), carry no user information and resolve to no blocked address range where DNS answers; the check runs at load and, for workspace channels, at write. Delivery uses one shared client with a 10-second timeout and no redirects, one drain worker and a 256-deep queue, and only the leader announces. URLs are never logged or shown; status surfaces list keys only. The test endpoint sends one fixed message, one a minute per channel, and returns the provider's error text up to 512 bytes. --issue-url-template substitutes {id} and {tenant} into the link a message carries.

Push

Web Push uses VAPID keys read from a Secret at start (a per-pod pair is generated and gossiped when none is set). A subscription's endpoint is validated at subscribe, at gossip apply and at restore with the same rules as a webhook URL plus a dial-time check. Android registrations are Firebase tokens stored as fcm:<token>, sent to Firebase with a service account or Workload Identity. The server overwrites any grants a client supplies with the caller's own, and delivery is filtered by them.

Observations

When memory notices something before anything is down, such as a service that has held 95 percent of its memory limit for six hours, the reflection write emits an observation. It goes through the same router and the same channel floors, leader-gated, with a minimum interval between them.

13

High availability

The Deployment can run several replicas. With controller.leaderElection on, one replica holds a Lease and does the work that must happen once; with several replicas (or gossip.enabled) the chart renders the gossip mesh, and every replica serves the API, MCP and the intake.

ConcernBehaviour
Issue writesOne mutation path: an issue change from any replica (a resolve, a link, an author write) travels to the leader as an issue_mutation frame and is acknowledged
Leader-onlyThe level reconciler, the issue sweeps, announcements and the observation sink
GossipMutual TLS, TLS 1.3, certificate from the chart's own CA (kept across upgrades; delete the Secret and upgrade to rotate). 20 frame kinds: signals, hub snapshots, application, RED and observed-edge status, agent sessions, replica counters, issue mutations, memory hits and changes, prediction ranks, user tokens, tenant stats, user sessions, the VAPID key and push subscriptions. Frames are capped at 8 MiB with a 90-second idle deadline
Replica-localThe service ledger, the situation frame, rate limiters, spot post caps and MCP authorization codes. A request that depends on them sees one replica's share; the MCP OAuth flow needs session affinity
RestartCells are the durable state. The hub, the ledger and the frame start empty and refill from live frames; an agent reconnects with its certificate and resends its snapshot (resync). The counters in the status are instance-wide totals
DisruptionAn optional PodDisruptionBudget (podDisruptionBudget.enabled, off by default)
14

Configuration

The core has few flags and no configuration file. Behaviour is set in cells that are applied while the core runs, and the Helm chart renders them from values.

LayerHoldsApplied
FlagsListen addresses, leader election, gossip, the agent CA and certificate lifetime, trusted proxy hops, the public URL, trial limits, ingest authorization modeAt start
Runtime document triage.runtime/v1Models and their credential references, the decision model, SSO providers, Web Push and Firebase, the messenger, OTLP tracing, remote MCP servers and A2A agents, the LLM circuit breaker. Secrets are named, never inlined; values are resolved into memory and never written to a cellLive, where safe to change
Policy document triage.policy/v1Parameters at the decision points the code defines (below), with per-tenant overlaysLive; atomic swap
RSI overlayThe knob values RSI wrote; laid under the operator's document field by fieldLive
Other cellsPrompts, reflex rules, notification routing, tenant goals, the registry, the join state, doc pages, the UI referenceLive

Policy

Mechanism stays in code and policy is parameters. There is no rules engine and no expression language. The schema cannot express turning off the safety invariants: the guardrail fails closed, cross-tenant access is denied by default, issue state follows the graph one way, cell writes follow the write topology, and every model call has an output cap. A preset typo cannot become a security incident. Sections:

SectionSetsTenant overlay
classificationThresholds, caps by event type, reopen windows, flap gate, evidence windows, quiet scopes, ignored types, observed-edge retention, the event vocabularyPartly: quiet scopes, ignored types, evidence windows and the flap gate; the event vocabulary is instance-only
concernsRecovery hysteresis, fade after idle, reconcile interval, resolved issues listedYes
loops, predictions, situationIntervals, cooldowns, mind-map size, verification limits, prediction thresholds, frame thresholds and actionsYes
observations, alertsWhat the colleague voice says unprompted, the repeat gateYes
decisionsThe six switches and the daily decision budgetYes
retentionWindows in section 5Yes
budgets, models, rsiToken budgets by instance, tenant, kind and spot; model names; the RSI mode and gatesInstance only; a tenant cannot turn the loop on for itself
apiVersion: triage.policy/v1
name: example
version: 1
classification:
  flapSuppression:
    minReopens: 5
  quiet:
    serviceTokens: [synthetic, e2e, canary, smoke]
budgets:
  llmDailyTokens: 1000000
  llmTenantDailyDefaultTokens: 100000
rsi:
  mode: shadow
tenants:
  tenant-a:
    classification:
      quiet:
        namespaces: [sandbox]

Five presets ship with the repository: balanced (the compiled defaults as a document), cost-saver, high-recall, quiet and system2-delegate. Offline validators for the policy document, the runtime document, notification routing and a service catalog run the same checks as the reconciler. A document that fails validation, or fails to decode with strict field names, is rejected and the previous good one stays in force.

Chart

The chart triage-core groups its values as: the controller (replicas, leader election, probe and metrics addresses), the HTTP API and MCP listeners, the events server, the MS Teams connector, auth and access (grant sources), sso, runtimeConfig, prompts, embedding and chatModel sidecars, messenger, voice, tenants (the registry, trials, members), clusterAgents.join (the agent CA), gossip, the exposure objects (httpRoute, externalIngress, health and backend policies) and pod settings. With nothing set the chart generates an api-tokens Secret with one admin key that works on the events server only.

15

FAQ

Why does a service nobody depends on still get an issue?

Blast radius sets severity; the evidence decides whether anything happened. A leaf service that goes degraded opens an info issue through the level state, and corroboration can raise it to warning, never to critical. Whether anyone is paged is a property of each channel's minimum severity, which defaults to warning. A leaf that should stay silent belongs in a quiet scope or under a recorded pattern.

Why does a lone BackOff or Unhealthy event open nothing?

A pod-family event is one fact about an instance. It is counted and shown in the frame, and an open issue on the service absorbs it. A crash loop reaches the level state and opens through O1 within a minute, a second error family opens through O2, and a probe that keeps failing on a Ready workload opens through O3 after the dwell. A flapping probe on a workload that stays Ready opens nothing.

What happens when the decision model or the reasoning model is down?

The event path goes on. Without the decision model the engine keeps its own severity, the machine open is not judged against patterns, a human message passes on the regex alone, and predictions use the matcher's score. Without the reasoning model the loops do not run: no new reflection, knowledge or RSI output, while everything already written stays in force. Each dependency's failure count and last success appear in the situation frame, and a burst of decision-model failures raises one event on the core's own service, at most every ten minutes, when that service is in a tenant graph. The guardrail model is the exception: on an error it drops the human event.

How do I tell the core that a state is known and fine?

Three ways, from the narrowest. Resolve the issue with the verdict expected (or noise) and a window, which holds machine opens on that service (or source) for up to 30 days. Record a pattern, in one sentence through link_pattern, chat or voice, with an optional schedule; the opener asks the decision model whether a new open is an instance of it. Or set a quiet scope in policy for services that should never open. A human report is never held by any of them.

How does a new cluster or host reach a workspace?

A workspace manager mints a one-time key in the workspace and runs the install command the API returns: a Helm line for a cluster, an install.sh line for a host. The agent sends the key with a CSR and an Ed25519 proof, and the core issues a 24-hour certificate at once and renews it. Without a key, the agent prints its fingerprint and an instance admin approves it. A key is spent by the first successful join and lasts seven days, and the workspace's licence count limits how many agents it can bind.

What leaves the cluster toward a model provider?

Event text, service-graph facts, recent issue lines and the memory a prompt needs, in the roles of section 7. Human messages are screened first, and event and graph text inside a prompt is fenced as untrusted data. A tenant that must not send its text to the decision model turns its own decisions switches off. Embeddings can stay on the pod with the local sidecar, and with the local chat sidecar every model role can too. Token use is bounded by the budgets of section 7 and visible in the status.

What is lost on a restart or a failover?

Nothing durable. Issues, memory, configuration and the registry are cells. The event hub, the service ledger, the situation frame and the rate limiters are in memory and refill from the agents, which reconnect and resend their snapshots. Corroboration and persistence start from what has arrived since, so a service can take a few minutes to reopen on a fresh pod.