Documentatie

Agent Harness

Draai Claude Code en Codex op je eigen machine onder het beleid van je organisatie: bestuurd modelverkeer, bestuurde tools, één gemeten en verzegeld auditspoor.

Niets van de agent verhuist naar de cloud. Claude Code en Codex blijven op de machine van de ontwikkelaar draaien, met eigen processen, bestanden, ingebouwde tools en eigen agent-loop. Wat Sluis bestuurt is het verkeer dat naar buiten gaat: de modelverzoeken en de tool-calls.

Regio wordt niet afgedwongen voor code-agentsEen code-agent praat met je eigen provider-abonnement (Anthropic, OpenAI of Cursor), en de provider bepaalt waar dat verzoek wordt bediend. Sluis kan die regio niet vastleggen, dus op het agentvlak wordt residency vastgelegd, niet afgedwongen: een agentbeurt wordt nooit geweigerd op jurisdictie, en de auditregel noteert de regio als niet geverifieerd in plaats van er een te claimen. Al het andere blijft gelden: agentsleutels met een afgeschermd doel, uitrol per organisatie, DLP-scanning en pseudonimisering (ook op de protobuf-verbinding van Cursor), plugin- en toolgovernance, budget- en snelheidsplafonds, en verzegelde audit.

De lokale client authenticeert bij Sluis met een agent-key en spreekt de ingang van zijn eigen protocol aan: /agent/claude biedt de Anthropic-API voor Claude Code, /agent/codex/v1 de OpenAI Responses-API voor Codex, /agent/cursor het Connect-protocol van Cursor over protobuf, en /agent/mcp is een stateless Streamable HTTP MCP-endpoint dat de bestuurde tools draagt.

Claude Code en Codex zijn voorbeelden, geen gesloten lijst. Pi, T3 Code en elke client die een Anthropic Messages-, OpenAI Responses- of MCP-URL en API-key accepteert, gebruikt dezelfde Agent Harness.

De hele setup volgt één orde, en elke stap heeft de vorige nodig:

  • 1. Koppel een subscription. Claude of Codex, één keer per provideraccount gekoppeld.
  • 2. Maak een agent-key. Het secret wordt één keer getoond, bij het aanmaken.
  • 3. Koppel de key aan dat account. Een key besteedt alleen de accounts waaraan hij gekoppeld is.
  • 4. Configureer de lokale client. De key staat in SLUIS_AGENT_KEY, en de base URL wijst naar /agent/claude voor Claude Code, /agent/codex/v1 voor Codex of /agent/cursor voor Cursor, dat dezelfde key in Authorization: Bearer meestuurt.
  • 5. Registreer het tool-endpoint. Tools komen alleen via /agent/mcp, apart geregistreerd: een base URL voegt er geen toe.

Een subscription koppelen

Een organisatie koppelt een of meer Claude- en Codex-subscriptions in de Agent Harness-weergave. Claude koppel je met een token uit claude setup-token, Codex via de device authorization van OpenAI. Sluis controleert de credential bij de provider voordat er iets wordt opgeslagen.

Claude: setup-token

Je hebt Claude Code op je machine nodig, ingelogd op een Claude-subscription die modelverzoeken mag doen.

  • 1. Voer claude setup-token uit in een terminal. Dat print een langlopend token, ongeveer een jaar geldig, dat begint met sk-ant-oat.
  • 2. Plak het in het Claude-veld in de Console. Het veld is gemaskeerd en autocomplete staat uit.
  • 3. Bij verzenden controleert Sluis het token bij Anthropic voordat er iets wordt opgeslagen.
  • 4. Een gecontroleerd token wordt versleuteld in de vault van de gateway verzegeld. Het wordt nooit meer getoond, nooit aan een client teruggegeven en nooit ergens anders dan naar Anthropic gestuurd.

Een token dat Anthropic niet bevestigt wordt geweigerd met een 422: er komt geen account, er wordt niets opgeslagen en er wordt niets gefactureerd. Dezelfde subscription later opnieuw koppelen hergebruikt het bestaande account in plaats van een tweede factureerbaar account toe te voegen.

Codex: device authorization

Je hebt een ChatGPT-plan nodig dat Codex bevat.

  • 1. Start de device login in de Console. Sluis vraagt het bij OpenAI op en toont je een verificatie-URL en een user code.
  • 2. Open die URL met de knop, log in bij OpenAI, vul de code in en keur goed. OpenAI kan MFA vragen.
  • 3. De Console pollt terwijl je goedkeurt. De uitwisseling loopt in dit browsertabblad: de pagina verlaten, of naar een ander tabblad van de weergave gaan, annuleert de login en slaat niets op, precies zoals de actie Annuleren.
  • 4. Na goedkeuring bewaart Sluis de access- en refresh-tokens versleuteld in de vault van de gateway en vernieuwt ze voor je; rotatie van de refresh-token is geserialiseerd over replica's.

Een user code verloopt. Een verlopen flow meldt expired en moet opnieuw worden gestart — net als een account waarvan OpenAI de opgeslagen credential later weigert: dat levert niets meer tot iemand het opnieuw koppelt.

Sluis versleutelt die credentials, bewaart ze in de vault van de gateway en vernieuwt ze daar. Een providertoken bereikt nooit de lokale client, de logs, de auditrecords of de traces: Claude Code en Codex hebben alleen hun Sluis agent-key.

Koppelen is self-service, dus een member kan zijn eigen subscription toevoegen. Owners en admins zien elk account in de organisatie; een member ziet en ontkoppelt alleen het eigen account.

Een agent-key aanmaken

Agent-keys maak je in diezelfde weergave, los van je API-keys, omdat de twee doelen gescheiden zijn. Een agent-key authenticeert alleen op /agent/*, een gewone API-key alleen op /v1/*. Beide op het verkeerde vlak geeft een 401, en de weigering noemt het doel van de key nooit.

Het secret wordt één keer getoond, bij het aanmaken, en alleen de hash wordt bewaard. Elke key loopt via de provideraccounts die je eraan hangt, dus het koppelen van de subscription van een tweede member verlegt nooit een key die al werkt.

Een key legt ook het plafond van de organisatie op de ingebouwde tools van de lokale client vast, in de tool-grammatica van Claude Code en gevalideerd bij het aanmaken. De client van de ontwikkelaar handhaaft die lijst op de machine; Sluis leest hem nooit tijdens een request, dus niemand kan zijn eigen key verruimen, en een lege lijst legt simpelweg geen plafond vast.

De tools die Sluis zelf per request bestuurt, zijn de MCP-tools die het aanbiedt: zie de plugins hieronder.

De clients op Sluis richten

Claude Code heeft één base URL en één credential nodig. Zet ANTHROPIC_BASE_URL op de Claude-ingang van deze gateway en ANTHROPIC_API_KEY op de agent-key uit SLUIS_AGENT_KEY. De Console genereert het blok hieronder met de publieke URL van je gateway al ingevuld.

Een base URL voegt geen tools toe: het Anthropic-protocol kent geen plugin-discovery, dus tools komen alleen via een MCP-endpoint dat je zelf registreert, met claude mcp add of een .mcp.json-entry. Beide verwijzen naar ${SLUIS_AGENT_KEY} in plaats van naar de key, zodat er geen secret in een gecommit bestand belandt.

Codex leest zijn providerblok uit ~/.codex/config.toml. Richt base_url op de Responses-ingang /agent/codex/v1, zet wire_api = "responses" en env_key = "SLUIS_AGENT_KEY", en voeg hetzelfde MCP-endpoint toe onder [mcp_servers.sluis].

# the agent key is shown once, at creation: keep it in the environment
export SLUIS_AGENT_KEY='sluis-9f2c…'
export ANTHROPIC_BASE_URL='https://api.sluis.ai/agent/claude'
export ANTHROPIC_API_KEY="$SLUIS_AGENT_KEY"

Cursor wijst naar dezelfde gateway. Zet zijn basis-URL op de Cursor-ingang van deze gateway, /agent/cursor, met de agent-key in de Authorization-header. De gedocumenteerde route is POST /agent/cursor/agent.v1.AgentService/Run, gedragen door Connect met content type application/connect+proto. Een client die geen basis-URL kan zetten, mag naar de blote origin wijzen, omdat het protocol van Cursor een absoluut pad vastlegt.

Sluis loopt die Connect-frames zonder schema door en past de databeschermingsmodus van de organisatie toe op de prompttekst erin: tokenize vervangt gevonden waarden door omkeerbare plaatshouders en zet de originelen terug in het gestreamde antwoord, mask redigeert ze volledig, block weigert de turn voordat er iets bij Cursor aankomt, en een frame dat Sluis niet kan decoderen wordt geweigerd in plaats van doorgestuurd. Alleen allow_log stuurt het door, en het auditspoor legt vast dat het niet inspecteerbaar was.

Twee grenzen horen bij dit transport. De naamdetectie en de prompt-injectiewacht lezen een JSON-requestbody, en Connect-protobuf is dat niet: daarom lopen ze niet, de auditregel noteert ze als niet uitgevoerd, en een organisatie die naamdetectie verplicht heeft gemaakt krijgt een weigering in plaats van een ongecontroleerde doorgifte. Het Connect-protocol rapporteert daarnaast geen tokengebruik, dus een Cursor-turn wordt zonder tokenaantallen in het auditspoor verzegeld.

Elk vlak accepteert zijn credential in precies één vorm: x-api-key op de Claude-ingang, wat Anthropic-clients sturen, en Authorization: Bearer op de Codex-, Cursor- en MCP-ingangen. Dezelfde key in de andere header wordt geweigerd.

MCP-plugins

Bestuurde tools komen binnen via één stateless endpoint, POST /agent/mcp. Het houdt geen sessie: elk request leidt de hele autorisatieketen opnieuw af, dus het intrekken van een key, plugin of signing key werkt bij de volgende call.

Er zijn twee klassen. Door Sluis onderhouden plugins zijn gecompileerde Sluis-code, organisatiebreed beschikbaar, en voeren geen klantcode uit. Organisatieplugins zijn de jouwe: een TOML-manifest, met Ed25519 ondertekend.

Een manifest wordt alleen geaccepteerd als de signature verifieert tegen een signing key die je organisatie heeft ingeschreven, en het is onveranderlijk per (organisation, id, version): een gewijzigde tooldefinitie is een nieuwe versie, nooit een stille aanpassing. Elke plugin is vastgezet op één bestuurde HTTPS MCP-server, waarvan het endpoint exact moet overeenkomen met de URL in het ondertekende manifest.

De operator doet drie stappen. De owner schrijft de signing key van de organisatie in. Een owner of admin registreert het ondertekende manifest tegen een bestuurde MCP-server. Daarna wordt de plugin gebonden aan de agent-keys die hem mogen gebruiken, en een plugin zonder binding is voor niemand zichtbaar.

Server-URL's, manifesten, signatures en upstream-credentials bereiken nooit een client. Een ontwikkelaar ziet toolnamen en schema's, en verder niets.

Context en skills die de gateway injecteert

Je engineeringstandaarden zijn alleen beleid als een ontwikkelaar ze niet kan vergeten. Een CLAUDE.md in de repository is een suggestie: je kunt hem aanpassen, weggooien of simpelweg niet lezen, en niets legt vast wat er gebeurde. Diezelfde tekst die de gateway in elk beheerd agentverzoek duwt, is de beslissing van de organisatie — schrijf eerst de falende test, voeg geen dependency toe zonder goedgekeurde ADR, deze repository bevat persoonsgegevens dus pseudonimiseer voordat je plakt. Dit is de helft van de harness die bepaalt hoe een agent werkt, en niet welk model en welke tools hij mag bereiken.

Twee pluginsoorten dragen dat, en geen van beide geeft zelf enige bevoegdheid:

  • Context. Letterlijke tekst, vóór de eigen prompt van de ontwikkelaar geïnjecteerd, tot 16 KiB per versie.
  • Skill. Een naam, de instructies en de toolnamen die hij verwacht. Die namen worden gesneden met het toolplafond dat op de key is vastgelegd: een skill verklaart wat hij nodig heeft en kan een key nooit verruimen — een tool die het plafond niet draagt, wordt simpelweg niet aan het model genoemd.

Een versie wordt op een van drie niveaus gebonden, en elk verzoek lost alle drie op:

  • Organisatie. Elke agent key in de organisatie, ook keys die na de binding zijn aangemaakt. Dit is het niveau voor een regel die niemand hoeft te onthouden aan te hangen.
  • Gebruiker. Elke agent key van één lid — een onboardingbriefing, of een strengere houding voor één persoon.
  • Key. Precies één agent key, wat een binding al deed.

De resolutie is deterministisch, want de injectievolgorde verandert de prompt. Sluis neemt de vereniging van de drie niveaus, verifieert elke plugin opnieuw precies zoals bij tools, gooit dubbele versies eruit en ordent eerst organisatie, dan gebruiker, dan key: de algemene regel wordt als eerste gelezen en de smalle uitzondering als laatste. Context gaat er letterlijk in, een skill als benoemde sectie. Op de Claude-ingress worden de blokken vooraan geplaatst in system, in beide vormen die die API toestaat; op de Codex-ingress in instructions, of als eerste developer-instructie wanneer het verzoek er geen heeft.

Alles wat in één verzoek wordt geïnjecteerd past binnen 32 KiB. Daarboven wordt het verzoek geweigerd met een 422 die de plugins noemt die het plafond overschreden. Er wordt niets stil afgekapt: een halve instructie is erger dan geen, en een organisatie die denkt dat een regel geldt, moet horen wanneer dat niet zo is.

Geïnjecteerde tekst wordt niet door gegevensbescherming herschreven, en de volgorde is eenrichtingsverkeer: eerst wordt de prompt van de ontwikkelaar gescand en gepseudonimiseerd, daarna komt jouw beleid ervoor. Deze blokken zijn je eigen beheerde inhoud, in je eigen Console geschreven, dus er is niemand tegen wie ze beschermd moeten worden — en tokeniseren zou kapotmaken wat ze zeggen. „Escaleer naar security@example.com voordat je plakt”, herschreven naar „escaleer naar «EMAIL_1» voordat je plakt”, is geen instructie meer die iemand kan volgen.

Deze twee soorten schrijf je in de Console en ze dragen geen signature. Een signature bestaat om te voorkomen dat een manifest een beheerde credential op een endpoint richt dat niemand heeft goedgekeurd; een blok tekst richt zich op niets. Al het andere dat een registratie betrouwbaar maakt geldt nog: een versie is onveranderlijk, de exacte bytes worden gehasht, en de rij legt vast wie hem schreef en wanneer. Een manifest met een MCP-entry houdt de verplichte signature en de controle op de ingeschreven signer bij elk verzoek. Het tabblad Plugins laat zien welk van de twee een versie is, zodat „in de Console geschreven” een uitgesproken feit is en geen ontbrekend veld, en het toont de opgeloste blokken in de volgorde waarin de gateway ze injecteert voor een gekozen key of lid.

Elk geïnjecteerd blok laat één marker achter op de verzegelde auditregel, context:<plugin_id>@<version>#<digest8>, waarbij de digest de eerste acht tekens van de hash van die versie is. Omdat een versie onveranderlijk is, is die marker genoeg om de exacte tekst te reconstrueren die het model kreeg: het auditspoor alleen antwoordt of een agent op het moment van handelen onder een bepaalde regel viel.

Cursor is het eerlijk benoemde gat. Zijn transport /agent/cursor is een propriëtair binair schema: Sluis kan de tekst binnen die frames vinden en herschrijven — zo werkt gegevensbescherming daar — maar kan niet weten welk veld de systeemprompt is, en schrijven in een gegokt veld zou het verzoek beschadigen in plaats van beheren. Op die ingress wordt dus niets geïnjecteerd, en een Cursor-turn draagt jouw context alleen als de client van de ontwikkelaar hem zelf meestuurt. De preview in de Console zegt hetzelfde.

Governance die blijft gelden

Agentverkeer is gewoon Sluis-verkeer, met één uitzondering. Pseudonimisering en DLP lopen voor dispatch, ook binnen de protobuf-frames van Cursor, de rate- en budgetlimieten van de key gelden per request, de MCP-tools worden call voor call vrijgegeven, en elke model- en tool-call wordt geauditeerd en gemeten. De uitzondering is residency: op het agentvlak wordt het beleid vastgelegd en niet afgedwongen, om de reden in de melding aan het begin van deze pagina.

Het zijn dezelfde regels als onder Gegevensbescherming en Budgetten & caching, en ze zijn organisatiebeleid: een agent-key kan ze niet oprekken.

Facturatie

Elk actief door Sluis beheerd provideraccount kost € 16,50 lijstprijs per kalendermaand, ten hoogste één keer per account per maand. Het gaat naar je hoogste billing root en verschijnt als eigen factuurregel, los van gemeten gebruik. Btw en de toeslag voor de betaalmethode werken precies als op de rest van de factuur.

Ontkoppelen stopt volgende maanden. Dezelfde upstream-subscription opnieuw koppelen hergebruikt het bestaande account in plaats van een tweede factureerbaar account toe te voegen, dus ontkoppelen en opnieuw koppelen binnen één maand kost één keer.

Het gatewaygebruik zelf blijft op verbruik gefactureerd, net als bij API-keys.

Beschikbaarheid

De Agent Harness-weergave verschijnt in de Console zodra de rollout-flag agent_harness voor je organisatie aanstaat. Vraag ons hem aan te zetten als je hem niet ziet.

Problemen oplossen

SymptoomOorzaak en oplossing
401 bij een /agent/*-aanroepDe key staat op het verkeerde vlak of in de verkeerde header. Een API-key levert niets op de agent-routes en een agent-key niets op het modelvlak; elke ingang accepteert één headervorm.
502 no credential configured for providerDe agent-key noemt een provider waarvoor hij geen gekoppeld account heeft. Een key besteedt alleen de subscriptions waaraan hij gekoppeld is en valt nooit terug op de andere credentials van de organisatie: koppel een account voor die provider, of noem een model dat het gekoppelde account levert.
Vernieuwen van abonnement geweigerdDe provider weigerde de refresh definitief, dus het account levert niets meer tot iemand het opnieuw koppelt. Koppel het opnieuw in de Console; dezelfde subscription hergebruikt het bestaande account en de bestaande maandprijs.
400 mcp_servers is not supportedEen remote-MCP-declaratie in een Responses-body wordt voor dispatch geweigerd, omdat een server die het model zelf bereikt de tool-sluis zou omzeilen. Registreer de server bij Sluis en bereik hem via de MCP-ingang.
Geen setup-snippets in de ConsoleDe deployment heeft geen gevalideerde publieke gateway-URL, dus kant-en-klare setup kan niet worden getoond. Configureer SLUIS_GATEWAY_PUBLIC_URL en lees de endpoints in het tabblad Connections.