Egy AI-agent egy tenant nélkül egy demó. Egy tenant egy AI-agent nélkül egy weboldal. A kettő közötti összerendelés a tenant_agents tábla, és ennek az életciklusa az, ami megmondja, hogy a tenant chat-paneljén tényleg az fut-e ma délután, amit a cockpitben tegnap módosítottál. Ez egy poszt a tábláról, a CRUD-róutokról, és arról a két edge case-ről, amit hat hónap után tanultunk meg.
A tábla
CREATE TABLE eng_tenant_agents ( id UUID PRIMARY KEY, tenant_id TEXT NOT NULL, agent_id UUID NOT NULL REFERENCES eng_agents(id), kind TEXT NOT NULL CHECK (kind IN ('site_chat','workflow','voice')), status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','active','paused','archived')), config_version INTEGER NOT NULL DEFAULT 1, config JSONB NOT NULL DEFAULT '{}', prompt_slug TEXT, mcp_tool_ids UUID[] DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE (tenant_id, agent_id, kind) );
A kind mező három értéke a három fő use-case: site_chat (storefront chat panel), workflow (queue-alapú batch-feladat), voice (real-time voice agent). Egy tenant ugyanazt az agent-et több kind-ban is használhatja — például egy „rendelésfelvevő” agent lehet egyszerre site_chat és voice.
MCP tool kapcsolat
A mcp_tool_ids egy UUID array, ami az MCP toolokat rendeli az agenthez. A toolok tenant-szinten engedélyezettek (a mcp_tenant_assignments táblán keresztül), és az agent csak olyan toolt használhat, ami egyszerre szerepel az mcp_tool_ids-ben ÉS a tenant assignment listán. Ez a kettős kapu fontos: az agent-konfiguráció elvileg megmondhatja, hogy egy tool elérhető, de a tenant-szintű policy felülírja, ha a tenant nem fizet azért a toolért, vagy nem szerepel a GDPR-kompatibilis listán.
CRUD végpontok
A /admin/tenants/:tenantId/agents route-család az alapja. Öt művelet:
GET /— listázás, filterable kind, status, archived inclusion.POST /— új agent assignment, defaultstatus='draft', config_version=1.PATCH /:id— config update, automatikusconfig_versionincrement, audit-log sor.POST /:id/activate—draft|paused→active, validáció (prompt_slug nem null, mcp_tool_ids minden eleme tenant-assigned).DELETE /:id— soft delete, status →archived, a sor megmarad audit célból.
Audit + versioning
Minden PATCH egy eng_tenant_agents_audit sort generál: tenant_agent_id, config_version_before, config_version_after, config_before, config_after, changed_by, changed_at. Ez két célt szolgál: visszanézhető, hogy ki mit változtatott (compliance), és egy gombnyomásra visszaállítható egy korábbi verzió (revert button a cockpitben). A revert maga is egy új config_version, nem egy „időutazás” — a sor története lineáris marad.
Az első edge case
Az agent-config tartalmaz egy system_prompt-et inline. Egy tenant áthelyezett egy agent-et site_chat-ről voice-re, és nem vette észre, hogy a system prompt utasítást tartalmazott a kimenet formázására (Markdown). A voice-szintézis a Markdown-jelölőket felolvasta. „Aszteriszk félkövér aszteriszk” hangzott a hangszórón. A javítás: a kind váltás most explicit prompt validációt fut, ami figyelmeztet, ha a prompt formázási utasításokat tartalmaz olyan kind-on, ahol az nem értelmes.
A második edge case
Két tenant megosztotta ugyanazt az agent-et (ugyanaz az agent_id). Egyik módosította a config-ot a saját assignmentjén. A másik tenant a prompt_slug-ot is ezzel együtt módosítottnak hitte. A valóság: a prompt_slug a tenant_agents sorban van, NEM az eng_agents sorban. Tenant-A változtatása nem érintheti Tenant-B-t. A confusion oka egy admin UI bug volt, amit azóta a Headline-ből (Editing agent X for Tenant-A) explicitebbé tettünk.
Az életciklus mint stabilitási garancia
A draft → active → paused → archived négy állapot egyszerű, de a paused az, ami a legtöbbet ér. Ha egy agent rosszul viselkedik (cost-spike, hallucinációk), a pause egy gombnyomás, ami azonnal eltünteti a tenant chat-paneljéből anélkül, hogy törölné a config-ot. Az on-call mérnök egy percen belül egy rossz agent-et offline tud venni; az újraindítás (paused → active) szintén egy gombnyomás. A delete (archived) csak akkor jön, amikor biztos, hogy soha többet nem kell.