Ga naar inhoud

Bouwen voor personen

Het concept achter sleutels en policy staat in Sleutels, capabilities en policy.

Gebruik een subject wanneer meerdere personen je applicatie via dezelfde API-sleutel gebruiken. Een subject bevat een id dat jouw applicatie kiest en optionele attributen, zoals een afdeling. Core gebruikt die gegevens om toegang per persoon te controleren.

Met een subject beperk je toegang tot de gesprekken, het geheugen en de bestanden van die persoon. Daarnaast kun je regels instellen voor toegang tot modellen, tools, prompts, skills, agents, collecties en indexen. Die regels vergelijken de attributen van de persoon met de labels op een resource.

Core beheert geen gebruikersaccounts voor deze personen. Je applicatie levert de identiteit aan; Core registreert het subject-id bij de bijbehorende activiteit. Attributen worden per verzoek beoordeeld; een achtergrondrun bewaart ze tijdelijk versleuteld om later verder te kunnen. Voor een applicatie zonder gedeelde persoonsgegevens kunnen de rechten van de API-sleutel voldoende zijn.

Een subject meesturen op een beurt

De persoon reist in de header X-Core-Subject, op elke route die gegevens van een persoon leest of schrijft: beurten, gesprekken, geheugen, bestanden, artefacten, kennisbronnen en zoeken, datastore-records, het lezen van modellen, tools, prompts, skills, agents, collecties en indexen, en het schrijven van agents, prompts en workflows. De API-referentie noemt de header precies op de routes die hem lezen:

curl -s -X POST "$CORE_URL/responses" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -H 'X-Core-Subject: {"id": "anna", "attributes": {"department": ["payroll"], "office": ["jansen"]}}' \
    -d '{"model": "<een model-id>",
         "input": "wat is er gebeurd met de loonaangifte van De Vries vorige maand?"}'
  • id is je eigen ondoorzichtige identificatie voor de persoon -- nooit een naam of adres. De grammatica is dezelfde als OpenAI's eigen user-veld: letters, cijfers, . _ : @ -, 1 tot 128 tekens.
  • attributes is wat dit verzoek over diegene beweert: maximaal 32 namen, elk met maximaal 32 waarden van maximaal 256 tekens. Eén losse string ("department": "payroll") telt als één waarde. Core bewaart hier niets van, nergens -- niet op de beurt, niet in een auditregel. Stop je met een attribuut meesturen, dan is het bereik dat het kocht bij de eerstvolgende aanroep alweer weg. De enige uitzondering is een beurt die op de achtergrond doorloopt: die houdt de attributen van het verzoek versleuteld bij zich zolang de run duurt, zodat een tweede ronde tegen dezelfde bewering wordt afgewogen als de eerste, en raakt ze kwijt zodra de run klaar is.

Een agent, een prompt of een workflow wijzigen wordt beslist voor de persoon die je noemt. Er een aanmaken, wijzigen of verwijderen, of er een nieuwe versie van maken (POST /agents, POST /agents/{id}/versions, DELETE /agents/{id}, dezelfde drie onder /prompts, en POST /workflows, PATCH en DELETE /workflows/{id}, POST /workflows/{id}/versions), leest de header. Je regels over write en delete van die soort beslissen voor haar (Zien, gebruiken en wijzigen scheiden), en het auditspoor van de schrijfactie noemt haar naast de sleutel. Zonder de header is de schrijfactie van je backend en wordt die beslist zoals altijd, behalve onder een policy van sleutel of organisatie die settings.subjects_required zet: daar is het 422, zoals op elke route die de header leest (hieronder). Een subject-token wijzigt de bibliotheek nooit, wie het ook noemt (hieronder).

Sleutels beheren (/keys), indelen (PUT/DELETE /labels/...) en elke andere route waarbij de referentie geen X-Core-Subject noemt, negeren de header. Bij een workflowrun (POST /workflows/{id}/runs) is de persoon de subject_id in de body; de header wordt daar niet gelezen.

Er zijn drie manieren om een persoon te noemen, in aflopende volgorde van gezag:

  1. Een subject-token dat je backend heeft aangevraagd. Het bindt elk verzoek waarop het wordt aangeboden aan één persoon; een andere noemen is 403 (hieronder).
  2. X-Core-Subject, met de JSON hierboven of met een token dat je eigen identity provider heeft ondertekend (hieronder). Het eerste teken bepaalt wat het is: { is de JSON, drie stukken gescheiden door punten is een token.
  3. user, het veld dat OpenAI's eigen clients al versturen. Het is een alias van het id in de header en draagt geen attributen. Beide met een andere waarde noemen is 400, met header.X-Core-Subject in error.param.

De body is die van OpenAI. Er is geen veld subject in de body. Een body die het meestuurt, krijgt de weigering die elk onbekend veld krijgt: 400 op POST /responses, 422 op POST /threads, POST /memory en de twee kennis-zoekroutes. Zet in plaats daarvan de header op je client; zie Het verzoekformaat is dat van OpenAI.

Een token van je identity provider

Authenticeert je applicatie haar mensen al, dan heb je van je eigen identity provider een token dat zegt wie iemand is en waar diegene bij hoort. Dat hoef je niet over te typen in JSON: geef het token door in X-Core-Subject en laat Core de handtekening van je provider controleren. Core leest er de persoon en zijn attributen uit, bewaart het token niet en bewaart geen enkele claim.

De provider registreren

Core gelooft alleen providers die je organisatie heeft geregistreerd -- maximaal acht. In de console staat de kaart Identity providers op Instellingen van de organisatie (Een organisatie). Vanuit de API doet een sleutel met management hetzelfde met PUT /organisation/subject-issuers, die de hele lijst vervangt:

curl -s -X PUT "$CORE_URL/organisation/subject-issuers" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"issuers": [{
           "issuer": "https://login.jansen.example/",
           "audience": "artific-core",
           "algorithms": ["RS256"],
           "jwks": {"keys": [{"kty": "RSA", "kid": "k1", "n": "...", "e": "AQAB"}]},
           "claims": {"id": "sub", "attributes": {"department": "groups"}}
         }]}'
Veld Wat je invult
issuer precies de iss die een token van deze provider draagt. Exact vergeleken, nooit op prefix
audience precies de aud waarvoor je je eigen tokens laat maken. Verplicht: een token voor een andere applicatie bij dezelfde provider is niet van jou
algorithms RS256, ES256 of allebei; standaard ["RS256"]. Hier vastgelegd, nooit uit de header van het token gelezen
jwks de publieke sleutels van je provider, hier ingeplakt. Dit is wat we aanraden: Core hoeft er geen netwerk voor op. Een JWKS met een privé-helft erin wordt geweigerd
jwks_uri het https-adres van diezelfde sleutels, als alternatief voor jwks. De host moet bij de toegestane hosts van je organisatie staan en mag niet door de installatie zijn geblokkeerd; sta hem toe voordat je het adres invult
claims.id de claim met de eigen identificatie van de persoon; standaard sub. De waarde moet aan dezelfde grammatica voldoen als een id dat je zelf meestuurt
claims.attributes per attribuut de claim waar het uit komt: maximaal 32. Een pad met punten leest een geneste claim (org.unit), en een claim die zelf punten in zijn naam heeft (https://jansen.example/department) wordt eerst als geheel gezocht

Bij Jansen zet {"department": "groups"} de "groups": ["payroll"] van de provider om in Anna's department: [payroll]. De regel die je over department had geschreven beslist daarna op wat de identity provider zei in plaats van op wat de applicatie typte.

Een claim die het token niet draagt, is een attribuut dat dit verzoek niet beweert: geen fout, wel minder bereik. Een claim die er wél is maar geen string of lijst van strings is, wordt geweigerd -- 5 lezen als "5" zou één regel op twee manieren laten beslissen.

Wat een weigering zegt

Elk token dat niet door de controle komt levert dezelfde weigering op, over het veld header.X-Core-Subject. Op POST /responses is dat een 400 in het foutobject van OpenAI:

{
  "error": {
    "message": "X-Core-Subject must be a JSON object naming a person, or a token an issuer of this organisation signed.",
    "type": "invalid_request_error",
    "param": "header.X-Core-Subject",
    "code": "invalid_request"
  }
}

POST /messages geeft dezelfde zin in de envelop van Anthropic. Elke andere route antwoordt met 422 problem+json, met het veld in errors.

Eén zin, altijd dezelfde: voor een verlopen token, een handtekening die niet klopt, een aud die niet die van jou is, en een provider die je nooit hebt geregistreerd. Dat is opzet. Wie alleen een sleutel heeft, mag met een reeks probeersels niet kunnen uitvinden hoe jouw identiteiten zijn ingericht. Zoek de oorzaak dus bij je provider en bij de registratie hierboven, niet in het antwoord.

Eén geval antwoordt anders: kan Core de sleutels op je jwks_uri niet ophalen, dan is het 503 met The identity provider's keys could not be reached. Die mag je opnieuw proberen; de weigering hierboven niet.

Een token voor de browser

Praat een browser of mobiele app rechtstreeks met Core -- een gestreamde beurt, een upload -- dan mag die nooit je sleutel dragen: die geldt voor iedereen. Je backend vraagt daarom een subject-token aan, je sleutel versmald tot één persoon, en geeft dat aan het apparaat:

curl -s -X POST "$CORE_URL/subjects/tokens" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"subject": {"id": "anna", "attributes": {"department": ["payroll"]}}, "expires_in": 900}'
{"token": "ast_...", "subject_id": "anna", "expires_at": "2026-09-20T11:15:00Z"}

Heb je een token van je identity provider, stuur dan assertion in plaats van subject, met dat token als waarde -- precies één van de twee, allebei of geen van beide is 422. De persoon en de attributen zijn dan het woord van je provider in plaats van dat van je backend; verder verandert er niets.

Een token bevat hoogstens 4096 tekens. Het apparaat stuurt het mee als header, dus Core weigert een token te maken dat langer zou worden. Dat zijn ongeveer twintig attributen van gewone lengte; het precieze aantal hangt af van hoe lang je namen en waarden zijn, dus vooraf tellen kan niet. Een aanvraag boven de grens antwoordt 422 met subject.attributes als veld in errors en de melding The attributes are too large to bind into a token; assert fewer or shorter values. Zet in een token alleen de attributen die je regels lezen. Een subject buiten de algemene grenzen (32 namen, 32 waarden per naam, 256 tekens per waarde) wordt eerder geweigerd, met 422 op het veld dat niet klopt onder body.subject.attributes.

Het apparaat stuurt het token mee als Authorization: Bearer ast_... en noemt zelf niemand: een verzoek dat een andere persoon noemt is 403. Verder geldt:

  • Het leeft expires_in seconden, standaard 900 en hoogstens 3600. Core houdt geen lijst bij van de tokens die het maakte, dus één los token intrekken kan niet. Drie dingen beëindigen elk token dat een sleutel ooit maakte, in één keer: die sleutel roteren, hem verwijderen, en het verstrijken van de eigen expires_at van de sleutel. Een beëindigd token antwoordt 401 met WWW-Authenticate: Bearer, hetzelfde antwoord als een onbekende sleutel.
  • Het bereikt de gegevens van die persoon en leest de bibliotheek van je organisatie -- modellen, prompts, skills, agents, tools, collecties -- zodat je applicatie kan tonen waaruit iemand kan kiezen. Beheren doet het niets: elke schrijfactie op de bibliotheek, GET /key, de routes onder /subjects en alles onder beheer antwoorden 403 met A subject token may not reach this route. Doe die aanroepen vanuit je backend.
  • Het wijzigt alleen rijen die voor zijn eigen persoon zijn geschreven. Elke andere rij die het kan lezen wijzigen -- een gesprek dat niemand in het bijzonder opende, een feit dat de organisatie zelf schreef, een bestand dat je backend uploadde -- antwoordt 403 met A subject token may not reach this route. Dat geldt voor bewerken en verwijderen, voor iets toevoegen aan zo'n gesprek en voor het voortzetten ervan met een beurt. Een rij die het token helemaal niet kan lezen, antwoordt 404, net als bij een sleutel.
  • Het leest alleen de traces van de aanroepen van zijn eigen persoon; elke andere trace antwoordt 404.
  • Core slaat het token nergens op, dus je krijgt het één keer te zien. De aanroep zit achter de capability subjects.

Core aanroepen vanuit een webpagina. De gateway beantwoordt cross-origin-verzoeken van elke origin, dus je hoeft geen domein te registreren. Credentials staat hij nooit toe: de browser stuurt geen cookies naar Core, en je pagina stuurt het token zelf mee in de header Authorization: Bearer. De preflight van de browser -- een OPTIONS-verzoek onder /api/v1 met Origin en Access-Control-Request-Method -- beantwoordt de gateway zelf, en de browser mag dat antwoord 600 seconden hergebruiken. Elke methode en elke requestheader die de API kent is toegestaan, X-Core-Subject inbegrepen. Je pagina kan deze responseheaders lezen: Content-Type, Location, Retry-After, X-Request-Id, Deprecation, Sunset, X-RateLimit-Limit, X-RateLimit-Remaining en X-RateLimit-Reset.

Een sleutel die een browser stuurt, wordt geweigerd. Een verzoek met het geheim van je sleutel dat een browser verstuurt, krijgt 403 met reason_code key_in_browser, op elke route en vanaf elke pagina behalve de eigen gids van deze installatie, waarvan de verkenner je sleutel aanneemt: iedereen die de pagina opent, zou de sleutel kunnen lezen en namens je hele organisatie handelen. Core herkent een browser aan de headers Origin en Sec-Fetch-Site, die browsers toevoegen en die een pagina niet kan weghalen. Je backend blijft met de sleutel werken: curl, Node, Python, de SDK's van OpenAI en Anthropic en andere clients op een server sturen geen van beide. Een backend die het verzoek van een browser doorgeeft aan Core, stuurt die twee headers niet mee. X-Api-Key is voor een browser helemaal geen toegestane header, dus de preflight ervan mislukt voordat de sleutel wordt verstuurd.

Bronnen labelen bij upload

De attributen van een subject reizen mee met het verzoek; de labels van een bron staan erop opgeslagen. Voeg labels toe bij het aanmaken of uploaden van een kennisbron:

curl -s -X POST "$CORE_URL/knowledge/collections/col_2Pf7.../sources" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"kind": "text", "title": "Loondossier De Vries",
         "text": "...",
         "labels": {"department": "payroll", "client": "devries"}}'

Een regel leest deze labels om te beslissen voor wie een beurt de bron mag terugvinden. metadata blijft van jou, voor de zoekfilters die Kennis beschrijft; één release lang leest een regel het nog waar labels de naam missen (Labels en metadata).

Labels op alles

Een regel kan een label lezen op vijftien soorten dingen, en voor elke soort geldt dezelfde vraag: welk label draagt het ding dat de beurt pakt? De namen hieronder zijn de kind waarmee je een regel schrijft (Je eerste regel schrijven).

Soort Waar het label staat
thread, memory_record, knowledge_source, stored_file, datastore_record het veld labels van de rij zelf, zoals hierboven, met dezelfde waarden als bij een bibliotheekrij; bij datastore_record de labels van één record
prompt, skill, agent, schedule, workflow, knowledge_collection, knowledge_index, knowledge_graph, en als tool alles wat een tool oplevert -- API-koppelingen, SQL-koppelingen, scripts, MCP-servers, toolsets, tool-aliassen, zoektools het veld labels van die rij: maximaal 32 labels, elk met één waarde of een lijst van 1 tot 32 verschillende waarden; een waarde is een string van 1 tot 256 tekens
model, en een tool zonder eigen rij: een ingebouwde tool (builtin__*), een operatie van Core's eigen API (core__*) en de uploadtool een aparte route, PUT /labels/model/{id} en PUT /labels/tool/{naam}, vanaf een sleutel of vanuit de console; het label van de uploadtool geldt voor elke sleutel van de organisatie
curl -s -X PUT "$CORE_URL/labels/tool/builtin__web_search" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"labels": {"audience": "everyone"}}'

Een PUT vervangt de hele indeling van dat ene ding; GET /labels?kind=tool toont wat er staat en DELETE haalt het weg. Een tool die wél een eigen rij heeft, wordt daar ingedeeld en niet hier: die naam antwoordt 422.

Een label in een veld labels mag meerdere waarden hebben. Schrijf een lijst als één waarde niet genoeg is: een prompt voor twee afdelingen draagt {"department": ["sales", "finance"]}, en een regel over department: sales bereikt hem net zo goed als een regel over department: finance. Hetzelfde geldt voor PUT /labels/model/{id} en PUT /labels/tool/{naam}. Zodra je een vocabulaire vastlegt, wordt een lijst alleen aangenomen onder een label dat multiple is vastgelegd. Waar een regel tijdens de overstap naar labels nog de metadata van een rij leest, leest hij één string: een lijst daarin matcht geen enkele regel.

Uploads en artefacten zijn een stored_file, waar je ze ook bereikt. Onder water zijn het allebei kennisbronnen, maar op alle drie de plekken waar ze verschijnen beslissen de regels over stored_file: op GET /files, tussen de bronnen van een collectie, en in wat de zoektool knowledge__uploads vindt. Onder de standaardregels bereikt een persoon haar eigen uploads en de gedeelde -- geüpload door een verzoek dat niemand noemde -- en nooit die van een ander. Elke andere soort bron (tekst, records, wat een connector synchroniseert) is een knowledge_source.

Een tool erft het label van zijn bron. Een API-koppeling, een SQL-koppeling en een script leveren elk één tool op (api__<naam>, sql__<naam>, script__<naam>), die het label van de bron draagt; elke tool van een MCP-server draagt dat van de server, en een zoektool dat van haar eigen rij. Een alias draagt zijn eigen label en wordt daarnaast op zijn doel beoordeeld -- twee namen, twee indelingen, één aanroep. Een toolset draagt haar eigen label, en elke tool erin wordt nog steeds apart beoordeeld.

Het voorbeeld van Jansen erbij: de MCP-server salaris -- en dus elke mcp__salaris__*-tool -- krijgt department: payroll, de ingebouwde builtin__web_search krijgt audience: everyone, de agent payroll-assistant krijgt department: payroll en het model gpt-5 krijgt tier: premium. Labels alleen veranderen niets: de standaardregels staan iedereen elke tool, elke agent en elk model toe. Zodra het beleid van de sleutel regels over tool, agent en model bevat die deze labels vergelijken met de department en de tier van een persoon (Je eerste regel schrijven schrijft die voor tool), krijgt Anna (department: [payroll], geen tier) de mcp__salaris__*-tools en websearch aangeboden, mag ze de payroll-agent noemen en wordt gpt-5 haar geweigerd voordat de leverancier wordt gebeld. Bob van de receptie krijgt alleen websearch aangeboden en hoort dat de payroll-agent niet bestaat.

Twee vuistregels houden dit werkbaar:

  • Een label is een indeling, nooit het id of een attribuutwaarde van een persoon. department: payroll en tier: premium zeggen wat het ding is; wie het verzoek is voor, zeggen de attributen. Zet je iemands id in een label, dan heb je één rij per persoon te onderhouden en staat er in je bibliotheek wie je gebruikers zijn.
  • Niets invullen is niet hetzelfde als niets toestaan. Een ding zonder label matcht geen enkele regel die een label test, dus een permit die je over een label schrijft laat het weg. Zolang je voor die soort de standaardregels houdt, verandert er niets: die staan elke kennisbron en alles in elke bibliotheek toe.

In de console schrijf je labels op het scherm van de soort zelf: een veld Labels in het formulier, en een kolom Labels in de lijst (Prompts, Modellen, en zo voor elke bibliotheek). Wil je een label meerdere waarden geven, voeg dan per waarde een rij toe onder dezelfde naam, zoals department: sales en department: finance; de lijst toont elke waarde als een eigen badge.

Welke labels een persoon mag schrijven

Een regel die een thread met project: apollo laat zien aan iedereen van Apollo, is zo goed als het label. De labels van een rij schrijft het verzoek zelf, dus zonder meer kan een persoon haar eigen thread labelen met een project waar ze niet in zit, en hem zo aan elk lid van dat project geven. Dat sluit je met writable_labels in de settings van het beleid: per soort rij wijst elke labelnaam naar waar een waarde onder die naam aan moet voldoen. Dat is "subject" voor haar eigen id, {"attribute": "projects"} voor een van haar waarden voor dat attribuut, of {"value": "open"} voor één vaste string.

De regel die het gesprek aan het project laat zien, komt in de rules van het beleid:

{"id": "threads-of-my-projects", "effect": "permit", "kind": "thread",
 "when": [{"atom": "label_among", "label": "project", "attribute": "projects"}]}

en de map die het label bewaakt in de settings:

{"writable_labels": {"thread": {"project": {"attribute": "projects"}, "assignee": "subject"}}}

Voeg beide toe aan het document van de sleutel en stuur het in zijn geheel terug (Je eerste regel schrijven laat zien hoe).

Anna (projects: [apollo]) mag nu project: apollo en assignee: anna op een thread schrijven, en project: mercury of assignee: bob antwoordt 422 met het veld erbij (labels.project), zonder dat er iets wordt opgeslagen. Op POST /responses, waar het opgeslagen antwoord de labels van het verzoek draagt (in een bestaand gesprek die van het gesprek plus die van het verzoek, en alleen wat je toevoegt of wijzigt valt eronder; zie overerving), is de weigering de eigen 400 van de leverancier met error.param op labels.project -- ook met store: false, omdat de rij van het antwoord zijn labels hoe dan ook draagt. Eén release lang valt een waarde onder dezelfde naam in metadata, waar labels die missen, onder dezelfde map en wordt die geweigerd op metadata.project. Waar het voor geldt:

  • Elk verzoek dat een persoon noemt -- een subject-token, en een sleutel die iemand noemt in X-Core-Subject of user. Een sleutel die niemand noemt schrijft elk label, zoals altijd.
  • De vijf soorten rij: thread (een thread, en de labels van een opgeslagen antwoord), memory_record, knowledge_source, stored_file en datastore_record, elk met een eigen map. Een bron valt onder de map van de soort waaronder hij gelezen wordt, dus een upload is een stored_file, via welke route hij ook is opgeslagen -- en de purpose van een upload via POST /files is zijn label purpose, geweigerd op het veld purpose. Een model dat binnen haar beurt een geheugenrecord of datastore-record schrijft, valt er ook onder en hoort dat als toolresultaat.
  • Alleen wat verandert. Een patch die een label houdt dat de rij al draagt -- een backend zette het -- wordt niet geweigerd, en een label weghalen mag altijd. Hetzelfde geldt voor een beurt in een gelabeld gesprek: de labels die ze erft blijven staan en worden niet gewogen.
  • Een naam die de map niet noemt is vrij. Wie geen waarde voor het attribuut meestuurt -- een bezoeker zonder projects -- schrijft geen enkele waarde voor de namen die ernaar wijzen.

De policy van je organisatie en die van je sleutel worden allebei gevraagd; een waarde wordt alleen geschreven als geen van beide hem weigert. Met een vocabulaire moet elke genoemde naam een label zijn dat voor die soort is vastgelegd en elk attribuut er een dat een verzoek mag meesturen, anders antwoordt het schrijven van de policy 422 op settings.writable_labels.<soort>.<naam>.

Leg je vocabulaire vast

Zonder vocabulaire mag elke naam: elke naam in een labels-map, elk woord kan als attribuut worden beweerd, en elke naam kan in een regel staan. Een typefout is daardoor geen fout: departement in een regel wordt zonder klagen opgeslagen en matcht niets, dept in een header beweert een attribuut waar geen regel naar kijkt, en visibility: publik op een dossier verbergt het voor iedereen. Stuk voor stuk stil, en stuk voor stuk dicht -- precies de richting die eruitziet alsof het werkt.

Leg daarom eerst vast welke namen je organisatie gebruikt:

curl -s -X PUT "$CORE_URL/organisation/label-vocabulary" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"labels": {
           "department": {"values": ["payroll", "reception"],
                          "kinds": ["knowledge_source", "tool", "agent"],
                          "multiple": true,
                          "attribute": true,
                          "description": "de afdeling waar iets bij hoort"},
           "visibility": {"values": ["public", "internal"], "kinds": ["knowledge_source"]},
           "client":     {"values": null, "kinds": ["knowledge_source"]}
         }}'

Per label leg je tot vijf dingen vast. values is de gesloten verzameling waarden, of null voor "elke string" -- dat laatste is wat je wilt voor iets als een klantnaam. kinds zegt welke van de vijftien soorten dit label dragen, dus een label voor kennisbronnen kan niet op een prompt worden gezet of in een regel over threads worden gelezen. Met multiple: true mag een veld labels onder deze naam een lijst waarden hebben, zoals een tool die bij zowel payroll als reception hoort; zonder wordt een lijst geweigerd op labels.<naam>. De metadata van een rij wordt nooit tegen de vocabulaire gecontroleerd. attribute: true is de aparte toestemming om de naam over een persoon te beweren; een label dat alleen op rijen staat, laat je op false. description is de zin die de console naast de naam toont.

Vanaf dat moment wordt een naam geweigerd zodra hij niet in het vocabulaire staat -- bij het schrijven, met de naam erbij:

  • een regel over departement wordt geweigerd bij het opslaan van het beleid, met het pad van het onderdeel dat de naam noemt, zoals rules[3].when[0].label;
  • een prompt met labels: {"zonne": "noord"} wordt geweigerd op labels.zonne;
  • een verzoek met X-Core-Subject: {"id": "anna", "attributes": {"dept": "payroll"}} wordt geweigerd op header.X-Core-Subject -- 400 op een modelroute, 422 op elke andere.

Labels en regels worden gecontroleerd op het moment dat je ze schrijft en niet opnieuw tijdens een beurt; de subjectheader is het enige dat bij elk verzoek tegen het vocabulaire wordt gehouden. Elke sleutel van de organisatie mag het vocabulaire lezen met GET /organisation/label-vocabulary -- schrijven vraagt management.

Leg het vast voordat je regels en labels schrijft. Heb je al iets staan dat er niet in past, dan wordt de PUT geweigerd met een lijst conflicts van precies die rijen, en wordt er niets opgeslagen: ruim ze eerst op. Heb je geen vocabulaire, dan blijft alles zoals het was -- elke naam mag -- en DELETE /organisation/label-vocabulary brengt je daar weer terug.

Je eerste regel schrijven

Een beleidsdocument bestaat uit twee delen: rules, een lijst, en settings. De regels over personen staan in dezelfde lijst als elke andere regel waaronder je sleutel valt: welke modellen hij mag aanroepen, welke tools, welke hosts. Wijzig ze in de console, op de eigen pagina van de sleutel (Sleutels), of met PATCH /keys/{key_id} als je sleutel management heeft.

Een regel heeft deze onderdelen:

Onderdeel Wat het zegt
effect permit (toestaan) of forbid (verbieden)
kind de soort ding waar de regel over gaat (de tabel hieronder)
match.names patronen waarvan de naam van het ding er één moet matchen: *, ? en [a-z], hoofdlettergevoelig. Laat het weg voor elke naam
match.labels labels die het ding moet dragen, elk met deze waarde onder zijn waarden. subject is de eigenaar van de rij, en "" daarop is een rij die niemand in het bijzonder heeft gemaakt
when voorwaarden aan de persoon die het verzoek noemt; ze moeten allemaal gelden
actions optioneel. De acties waar de regel over gaat: see, use, write, delete (hieronder). Laat het weg voor elke actie die de soort neemt
id optioneel. Een eigen naam voor de regel: kleine letters, cijfers en koppeltekens, beginnend met een letter, uniek in het document. Het auditspoor noemt de regel die besliste bij deze naam

Een voorwaarde in when is een van vier:

Voorwaarde Geldt als
{"atom": "nobody_named"} het verzoek geen persoon noemt
{"atom": "label_is_subject", "label": "assignee"} de eigen id van de persoon onder de waarden van het ding voor assignee staat
{"atom": "label_among", "label": "department", "attribute": "department"} minstens één waarde van het ding voor department ook een van de waarden van de persoon voor het attribuut department is
{"atom": "attribute_has", "attribute": "role", "value": "admin"} de waarden van de persoon voor het attribuut role admin bevatten. Op het ding leest deze niets

Met een vocabulaire moet de waarde waar attribute_has om vraagt in de gesloten verzameling van dat label staan, anders antwoordt het schrijven van het beleid 422 op rules[<i>].when[<j>].value.

Drie feiten beslissen elk geval:

  • Een forbid die matcht weigert, wat een permit ook zegt.
  • Niets is toegestaan zonder een permit die matcht. De volgorde van de regels doet er niet toe.
  • Een regel geldt alleen als zijn hele when geldt. Een voorwaarde die de persoon leest, geldt nooit voor een verzoek dat niemand noemt.

Een sleutel die zonder beleid is geregistreerd, krijgt de standaardregels, uitgeschreven in zijn document:

Soort Een regel van deze soort beslist De standaardregels staan toe
model, router welke modellen en modelrouters een beurt mag gebruiken allemaal
tool welke tools het model krijgt aangeboden en mag aanroepen allemaal
prompt, skill, agent, schedule, workflow, file, knowledge_collection, knowledge_index, knowledge_graph, datastore welke bibliotheekitems een verzoek op naam bereikt allemaal
knowledge_source welke kennisbronnen een zoekopdracht of een leesactie bereikt allemaal, omdat de labels erop aan jou zijn om te bepalen en Core ze niet kan raden
thread, memory_record, stored_file, datastore_record welke rijen een verzoek bereikt elke rij als het verzoek niemand noemt; de eigen rijen van een persoon plus de rijen die niemand in het bijzonder heeft gemaakt als het er een noemt
egress_host welke hosts de sleutel mag bereiken, binnen de toegestane hosts van de organisatie geen regel: de sleutel bereikt de hosts van de organisatie
artifact_download of bestanden die een tool maakte gedownload mogen worden ja
memory_write of een beurt geheugen mag schrijven nee
direct_turn of een beurt die geen agent noemt mag draaien: een gesprek met een kaal model geen regel, en dat staat elke zulke beurt toe (hieronder)

Om kennisbronnen en tools te versmallen voor het voorbeeld van Anna, lees je het document van de sleutel met GET /key, haal je de twee standaardregels met de id's knowledge-source en tool uit rules, en zet je deze zes ervoor in de plaats:

[
  {"id": "sources-nobody-named", "effect": "permit", "kind": "knowledge_source",
   "when": [{"atom": "nobody_named"}]},
  {"id": "sources-own-department", "effect": "permit", "kind": "knowledge_source",
   "when": [{"atom": "label_among", "label": "department", "attribute": "department"}]},
  {"id": "sources-public", "effect": "permit", "kind": "knowledge_source",
   "match": {"labels": {"visibility": "public"}}},
  {"id": "tools-nobody-named", "effect": "permit", "kind": "tool",
   "when": [{"atom": "nobody_named"}]},
  {"id": "tools-own-department", "effect": "permit", "kind": "tool",
   "when": [{"atom": "label_among", "label": "department", "attribute": "department"}]},
  {"id": "tools-for-everyone", "effect": "permit", "kind": "tool",
   "match": {"labels": {"audience": "everyone"}}}
]

Stuur het document daarna in zijn geheel terug:

curl -s -X PATCH "$CORE_URL/keys/key_9Nx4..." \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d @policy.json

waarbij policy.json {"policy": {"rules": [...], "settings": {...}}} bevat met de aangepaste lijst. Anna bereikt een bron of een tool die is gelabeld met haar eigen department, plus alles wat voor iedereen is; een verzoek dat niemand noemt bereikt nog steeds elke bron en elke tool.

Een document is precies de regels die erin staan. PATCH vervangt het hele beleidsdocument: een document zonder rules krijgt de standaardregels, en een document met "rules": [] staat niets toe, wat een geldige stand is. Toestemmingen tellen op, dus wie wil dat iemand naast haar eigen gesprekken ook die van haar afdeling ziet, houdt de drie standaardregels voor thread en voegt een vierde toe. Een document bevat maximaal 500 regels; een regel maximaal 200 naampatronen, 16 labeltests en 16 voorwaarden.

Niet elke soort neemt elk onderdeel. Een rij heeft geen naam, dus match.names wordt geweigerd op thread, memory_record, knowledge_source, stored_file en datastore_record, en op artifact_download en memory_write. match.labels wordt geweigerd op router, egress_host, file, datastore, artifact_download en memory_write, waarvan de dingen geen labels dragen. when wordt geweigerd op router, egress_host, artifact_download en memory_write, waarbij nooit naar een persoon wordt gevraagd, en file en datastore nemen alleen nobody_named en attribute_has, de twee voorwaarden die geen label lezen: over de eigen rijen van de persoon wordt beslist op de rij, als stored_file en datastore_record. direct_turn neemt alleen when, met diezelfde twee voorwaarden, en alleen forbid. Een onderdeel dat een soort niet neemt, antwoordt 422.

Gesprekken, opgeslagen beurten en geheugen zijn van je organisatie, niet van de sleutel die ze schreef. De regels beslissen welke ervan een persoon bereikt, welke sleutel ze ook aanmaakte. Een subject-token van de ene sleutel bereikt dus een gesprek dat een andere sleutel van je organisatie voor dezelfde persoon opende, onder de regels van de sleutel waarmee het token is gemaakt. Bij bestanden werkt één ding anders: GET /files zonder filter toont alleen de uploads in de eigen collectie van de aanroepende sleutel. Lezen op id is niet zo beperkt. Het token van een andere sleutel, of een andere sleutel die dezelfde persoon noemt, leest het bestand op zijn id als haar regels de collectie van het bestand bereiken en de persoon klopt (Wat een bestands-id bereikt).

Een sleutel die een persoon noemt, handelt voor haar op elke rij die de regels haar toestaan. Noem je Anna in X-Core-Subject, dan kan je sleutel iets toevoegen aan, het label wijzigen van, verwijderen of voortzetten van elk gesprek dat de regels haar laten bereiken, ook een gedeeld gesprek of, onder een eigen regel, dat van een collega. Het gesprek houdt zijn eigenaar. Het auditspoor en de nieuwe opgeslagen beurt dragen Anna als persoon; onder de standaardregels krijgt de eigenaar van het gesprek daarna 404 op GET /responses/{id} voor Annas beurt, terwijl hij de items nog steeds via het gesprek leest. Alleen een subject-token mag uitsluitend de rijen van zijn eigen persoon wijzigen (hierboven).

Regels over knowledge_collection en knowledge_index beslissen wat een verzoek op id mag bereiken, zoals de collectie waarvan het de bronnen opvraagt of de index die het doorzoekt. Zoekresultaten filteren ze nooit; dat doen de regels over knowledge_source en stored_file. De regels over knowledge_collection en knowledge_index gelden niet voor knowledge__uploads, de zoektool over de eigen collectie van je sleutel: een regel van de soort tool die hem noemt, haalt hem weg. Het model laten zoeken legt uit hoe elk meetelt als het model zoekt.

Een geweigerde leesactie ziet er voor de aanroeper uit als afwezigheid: een lijst laat de rij weg, een id antwoordt 404, een model of een tool die de persoon niet bereikt bestaat niet voor deze beurt, en een tool wordt niet aan het model aangeboden. Twee weigeringen zijn geen afwezigheid. Een subject-token dat een rij wijzigt die het kan lezen maar die niet van zijn eigen persoon is, antwoordt 403 (hierboven). En een actie die de regels niet toestaan op iets wat de aanroeper mag zien, of een aanmaak of nieuwe labels die ze niet toestaan, antwoordt 403 en verandert niets (Zien, gebruiken en wijzigen scheiden): POST /threads of PATCH /threads/{id} met labels die geen write-regel over thread de persoon toestaat, wordt zo geweigerd. De aanroeper kent dat ding al, of het bestaat nog niet, dus er is niets voor een 404 om te verbergen. Het auditspoor van je organisatie legt elke weigering vast, met een van twee redenen: forbidden by a rule als een forbid matchte, en anders no rule permits.

Zien, gebruiken en wijzigen scheiden

Een regel kan de acties noemen waar hij over gaat. Zonder acties dekt een regel alles wat een verzoek met een ding kan doen. Met acties kun je bijvoorbeeld iedereen een agent laten gebruiken, terwijl alleen beheerders hem mogen wijzigen.

Actie Gevraagd als
see het ding in een lijst staat, of op zijn id wordt gelezen
use een beurt, een run of een aanroep het gebruikt: een agent of prompt die een beurt noemt, een skill die ze koppelt, een tool die aan het model wordt aangeboden of wordt aangeroepen, een model dat wordt aangeroepen, een index die wordt doorzocht, een bestand dat wordt bijgevoegd, een workflow die wordt gestart
write het ding wordt aangemaakt, gewijzigd, hernoemd, opnieuw gelabeld of krijgt een nieuwe versie; bij een gesprek of een andere rij ook als er iets aan wordt toegevoegd
delete het ding wordt verwijderd
{"id": "agents-everyone", "effect": "permit", "kind": "agent", "actions": ["see", "use"]}

Een soort neemt alleen de acties die er iets voor betekenen:

Soorten Acties
prompt, skill, agent, schedule, workflow, knowledge_collection, knowledge_index, knowledge_graph, datastore, file, tool see, use, write, delete
model see, use
thread, memory_record, knowledge_source, stored_file, datastore_record see, write, delete
router, egress_host, artifact_download, share_link, memory_write, direct_turn geen

Een actie die de soort niet neemt, actions op een soort die er geen neemt, en een lege lijst of een lijst met een actie twee keer erin antwoorden 422 op de actions van de regel.

  • Een regel zonder actions dekt elke actie die zijn soort neemt. De standaardregels noemen geen acties, dus een beleid dat je zonder acties schreef beslist precies als voorheen.
  • Geen actie omvat een andere. Een permit voor alleen write laat een persoon het ding niet vinden; geef haar ook see. Core vraagt eerst see en dan de actie die het verzoek nodig heeft. Een ding dat de persoon niet mag zien antwoordt 404. Een ding dat ze ziet maar niet mag gebruiken, wijzigen of verwijderen antwoordt 403 met policy-denied, en het auditspoor legt de regel vast die besliste.
  • Een tool staat in de lijst door see en wordt aangeboden door use. GET /tools toont de tools die een persoon mag zien; een beurt biedt het model alleen de tools aan die ze mag gebruiken.
  • Een forbid met actions weigert alleen die acties. Deze regel voorkomt dat agents in productie worden verwijderd en verandert verder niets: {"effect": "forbid", "kind": "agent", "actions": ["delete"], "match": {"labels": {"stage": "production"}}}.

Iets aanmaken is een write, beslist voor de persoon en met de labels in de body. Een permit voor write waarvan de labels passen bij die van het nieuwe ding laat de persoon het aanmaken; een forbid voor write op een label weigert het aanmaken van iets dat dat label draagt. Een wijziging van labels wordt beslist op de oude labels en op de nieuwe, dus een persoon kan een ding niet naar een afdeling verplaatsen, of eruit halen, waarin ze niet mag schrijven. Een beleid zonder regel over een soort staat daarvan niets toe, dus het weigert een nieuw ding van die soort net zoals het de bestaande verbergt.

Uitgewerkt voorbeeld: twee afdelingen en beheerders

Een organisatie heeft een afdeling Sales en een afdeling Finance, een API-koppeling die beide gebruiken, en beheerders die agents mogen wijzigen. De applicatie legt eenmalig haar vocabulaire vast, als body van PUT /organisation/label-vocabulary (Leg je vocabulaire vast):

{"labels": {
  "department": {"values": ["sales", "finance"], "kinds": ["tool", "agent"],
                 "attribute": true, "multiple": true},
  "role":       {"values": ["member", "admin"], "kinds": ["agent"], "attribute": true},
  "stage":      {"values": ["draft", "production"], "kinds": ["agent"], "attribute": false}
}}

Ze labelt haar bibliotheek:

Ding Labels
API-koppeling crm (tool api__crm) department: "sales"
API-koppeling boekhouding (tool api__boekhouding) department: "finance"
API-koppeling agenda (tool api__agenda) department: ["sales", "finance"]
agent offerte department: "sales", stage: "production"
agent facturen department: "finance", stage: "draft"

De sleutel houdt de standaardregels voor elke andere soort. Deze zes regels komen in de plaats van de standaard-permits met de id's tool en agent:

{"rules": [
  {"id": "backend-tools", "effect": "permit", "kind": "tool",
   "when": [{"atom": "nobody_named"}]},
  {"id": "department-tools", "effect": "permit", "kind": "tool", "actions": ["see", "use"],
   "when": [{"atom": "label_among", "label": "department", "attribute": "department"}]},

  {"id": "backend-agents", "effect": "permit", "kind": "agent",
   "when": [{"atom": "nobody_named"}]},
  {"id": "department-agents", "effect": "permit", "kind": "agent", "actions": ["see", "use"],
   "when": [{"atom": "label_among", "label": "department", "attribute": "department"}]},
  {"id": "admins-change-agents", "effect": "permit", "kind": "agent",
   "actions": ["see", "write", "delete"],
   "when": [{"atom": "attribute_has", "attribute": "role", "value": "admin"}]},
  {"id": "production-stays", "effect": "forbid", "kind": "agent", "actions": ["delete"],
   "match": {"labels": {"stage": "production"}}}
]}

admins-change-agents gebruikt de voorwaarde attribute_has: die kijkt alleen naar de persoon, dus hij geldt voor elke agent zodra de role van het verzoek admin bevat. Elk verzoek noemt zijn persoon:

X-Core-Subject: {"id": "anna", "attributes": {"department": ["sales"], "role": ["member"]}}
X-Core-Subject: {"id": "bram", "attributes": {"department": ["finance"], "role": ["admin"]}}
Verzoek Anna (Sales, member) Bram (Finance, admin) Backend, niemand genoemd
GET /api_connectors crm, agenda boekhouding, agenda alle drie
de tools van een beurt api__crm, api__agenda api__boekhouding, api__agenda alle drie
GET /agents offerte facturen, offerte: beheerders zien elke agent beide
een beurt die de agent offerte noemt toegestaan 403: hij mag hem zien, maar alleen Sales mag hem gebruiken toegestaan
een nieuwe versie van facturen 404: ze ziet hem niet toegestaan (admins-change-agents) toegestaan
POST /agents met label department: sales 403: geen regel staat haar write toe toegestaan toegestaan
DELETE /agents/{agent_id} voor offerte 403 403: production-stays verbiedt het 403: de forbid geldt voor iedereen

De koppeling agenda draagt beide afdelingen, dus één regel bereikt hem voor Anna en voor Bram. Twee varianten hebben geen nieuwe onderdelen nodig. Wil je dat beheerders alleen de agents van hun eigen afdeling wijzigen, voeg dan de voorwaarde label_among toe aan admins-change-agents: de voorwaarden in één when moeten allemaal gelden. Wil je dat beheerders ook elke agent gebruiken, voeg dan use toe aan de acties.

Een persoon niet met een kaal model laten praten

Verkoop je abonnementen per persoon, dan wil je misschien dat iemand met een basisabonnement met de agents van je organisatie praat, haar tools gebruikt en in haar kennis zoekt, maar niet rechtstreeks met een model chat. Schrijf daarvoor één forbid over de soort direct_turn, een beurt die geen agent noemt:

{"id": "no-playground", "effect": "forbid", "kind": "direct_turn",
 "when": [{"atom": "attribute_has", "attribute": "plan", "value": "basic"}]}

Een beurt voor een persoon van wie plan de waarde basic bevat, moet dan een agent noemen, in agent of als agent:<naam> in model. Een beurt die er geen noemt, antwoordt 403 met agent in error.param (op POST /messages begint de melding met agent:), en er wordt geen model aangeroepen. De melding zegt de aanroeper een agent te noemen en nooit welk attribuut weigerde; het auditspoor legt de reden direct turn forbidden vast en de id van de regel.

  • Zonder de regel verandert er niets. Een beurt die geen agent noemt mag, tenzij een forbid haar raakt, dus de standaardregels bevatten geen regel over direct_turn. Een regel erover kan alleen verbieden: een permit antwoordt 422. Hij neemt geen match en geen actions, en in when alleen nobody_named en attribute_has, omdat een beurt geen labels draagt.
  • Regels kennen geen «niet». Verbied op een waarde die de personen die je inperkt altijd hebben, zoals plan: basic, nooit op «iedereen behalve pro», wat een regel niet kan zeggen. Een persoon wiens verzoek geen plan meestuurt wordt niet geweigerd, en een verzoek dat niemand noemt ook niet.
  • Alleen beurten. Een rechtstreekse toolaanroep, een kenniszoekopdracht buiten een beurt en een toolset via MCP blijven toegestaan. Wil je die ook weigeren, voor elk verzoek van een sleutel, gebruik dan settings.agents_required (Agents via de API).
  • Elk document kan weigeren. Een verbod in het document van je organisatie of in dat van de sleutel telt allebei. Tooluitvoer insturen op een beurt die geen agent noemde, wordt beslist voor de persoon die het insturen noemt. Een achtergrondrun wordt opnieuw beslist als een worker hem start, voor de persoon die zijn verzoek noemde, dus een run die in de rij stond voordat je de regel schreef, faalt met agent_required.
  • Een vocabulaire controleert het attribuut. Met een vocabulaire moet plan als attribuut zijn vastgelegd, en moet basic in de gesloten verzameling liggen als het label er een heeft.
  • Een planning draagt alleen attributen als de persoon haar zelf schreef. Maak of wijzig je een planning met de sleutel waaronder ze loopt en met X-Core-Subject voor dezelfde persoon als haar subject_id, dan bewaart Core de attributen die zij opgeeft versleuteld bij de planning, en elke beurt van de 30 dagen daarna past ze toe. Een regel over plan weigert haar geplande beurt dan zoals hij haar live beurt weigert. Elke latere wijziging door haar schuift de 30 dagen op, en een zonder attributen haalt ze weg; subject_attributes_until op de planning zegt tot wanneer. Een planning die alleen je backend voor haar schreef, of die in de console is gemaakt, vuurt met alleen haar subject_id, net als een planning waarvan de 30 dagen voorbij zijn. Een workflowstap draagt alleen de subject_id van zijn run. Een planning wordt bij het schrijven gecontroleerd voor niemand in het bijzonder, dus een verbod met de voorwaarde nobody_named weigert het schrijven van een planning die een subject_id noemt, al zouden haar beurten, gemaakt voor die persoon, er doorheen gaan.

Registraties volgen de regels over tools

Toolsets, tool-aliassen, API-koppelingen, MCP-servers, SQL-koppelingen, scripts en zoektools zijn de registraties waar tools uit voortkomen. Voor een verzoek dat een persoon noemt, beslissen de regels over tool ook over die registraties: welke hun lijsten tonen (GET /toolsets, /tool-aliases, /api_connectors, /mcp_servers, /sql_connectors, /scripts en /knowledge/retrieval-tools), en of het lezen, wijzigen of verwijderen van een registratie mag. Een registratie wordt beslist met haar eigen labels, de labels die haar tools dragen, en met de actie die het verzoek vraagt: see om haar te tonen of te lezen, write om haar te wijzigen, delete om haar te verwijderen. Een registratie die de persoon niet mag zien antwoordt 404; een die ze ziet maar niet mag wijzigen of verwijderen antwoordt 403.

Elke registratie wordt beslist onder een toolnaam:

Registratie Beslist als
toolset support support
tool-alias lookup alias__lookup
API-koppeling salaris api__salaris
SQL-koppeling orders sql__orders
script deck script__deck
zoektool handbook knowledge__handbook
MCP-server salaris zijn tools, mcp__salaris__<tool>

Een MCP-server heeft veel tools, dus een regel beslist over de server aan de hand van de toolnamen die zijn patroon kan matchen:

  • Een permit toont de server als zijn patroon minstens één mogelijke tool ervan matcht: mcp__salaris__get_*, mcp__* en * doen dat; mcp__salaris niet.
  • Een forbid verbergt de server alleen als zijn patroon elke mogelijke tool ervan matcht: mcp__salaris__*, mcp__* of *. Een forbid van een deel van de operaties, zoals mcp__salaris__delete_*, houdt die tools weg bij het model en laat de server in de lijst staan.

Een verzoek dat niemand noemt wordt niet versmald: het toont elke registratie, wat de regels van de sleutel over tool ook zeggen. De console toont ook elke registratie. Een registratie aanmaken, en de versies van een koppeling of de bestanden van een script lezen, valt niet onder deze regels.

Wat een verzoek zonder subject bereikt

Een regel waarvan de when de persoon leest, geldt niet voor een verzoek dat niemand noemt: zo'n permit staat niets toe en zo'n forbid weigert niets. Wat dat verzoek bereikt, beslissen de regels zonder when en de regels met nobody_named. Onder de standaardregels is dat alles wat zijn sleutel bereikt, van wie een rij ook is. Een achtergrondtaak die nooit een persoon noemt, blijft dus werken nadat je de bibliotheek hebt ingedeeld, zolang de regels die je schrijft er een permit voor houden, zoals de twee regels …-nobody-named hierboven. Laat je die weg, dan bereikt een verzoek zonder persoon alleen wat de regels zonder when toestaan.

Zet settings.subjects_required op true en een verzoek zonder subject wordt geweigerd: 400 op een modelroute, 422 op elke andere. Gebruik dit op een sleutel waarvan de aanroepen nooit mogen vergeten voor wie ze zijn.

Als je organisatie een eigen policy heeft

Een organisatie kan een beleidsdocument van zichzelf hebben, naast dat van elke sleutel. Elke beslissing vraagt het dan aan allebei: iets wordt alleen bereikt als de regels van elk document het toestaan. Het document van je sleutel kan dus alleen versmallen wat de organisatie toestaat, nooit verbreden.

GET /key blijft antwoorden wat er op je eigen sleutel staat en zegt niets over het document van de organisatie. Wil je dat lezen of schrijven, dan kan dat met GET/PUT/DELETE /organisation/policy als je sleutel management heeft, en in de console op Instellingen van de organisatie (Een organisatie). Heeft de organisatie geen eigen document, dan antwoordt die route {"policy": null} en beslist alleen je sleutel.

DELETE /organisation/policy antwoordt 204, of er nu een document was of niet, dus je kunt hem sturen zonder eerst te lezen. Een 404 op deze routes betekent dat je sleutel geen management heeft. Een 403 betekent dat de organisatie boven de jouwe het sleutelbeheer aan zichzelf heeft voorbehouden: lezen kan nog, wijzigen niet.

Loop je tegen een weigering aan die niet uit het beleid van je sleutel te verklaren is, kijk dan daar: de twee documenten worden nooit tot één samengevoegd. Een forbid in het ene wordt nooit opgeheven door een permit in het andere, en een soort waarvoor een document geen permit bevat, wordt door dat document geweigerd, wat het andere ook zegt.

Wat een persoon noemen bewaart

Maakt een verzoek dat een persoon noemt een rij aan, dan hangt het van de soort rij af wat er met haar id gebeurt.

Soort rij Wat er wordt bewaard
een gesprek, een opgeslagen beurt, een geheugenrecord, een datastore-record, een upload naar /files, een artefact dat een beurt maakte, en een kennisbron die je aanmaakt of uploadt (tekst, records, een gekopieerd bestand, een geüpload bestand) haar id, als eigenaar van de rij. De regels over de soort (thread, memory_record, stored_file, datastore_record of knowledge_source) beslissen wie de rij verder bereikt, en DELETE /subjects/{id} wist hem samen met de rest van haar gegevens
een definitie in de bibliotheek: een agent, een prompt, een skill, een API-koppeling, een MCP-server, een toolset, een tool-alias, een zoektool, een collectie, een index, een label niets over haar. De definitie is van je organisatie. Een schrijfactie op een agent, een prompt of een workflow wordt voor haar beslist en haar auditregel noemt haar (hierboven), maar de definitie zelf houdt geen eigenaar
een planning de persoon in het veld subject_id van de body, niet die in de header. Elke beurt die de planning afvuurt is voor die persoon. Wis je haar, dan wordt de planning uitgezet in plaats van verwijderd

Een rij die is geschreven door een verzoek dat niemand noemde, heeft geen eigenaar. Die is gedeeld: onder de standaardregels bereikt elke persoon haar, en geen enkele wisactie haalt haar weg.

Een persoon wissen

DELETE /subjects/{id} verwijdert alles wat Core onder één subject-id bewaart, op de eigen opslag van je organisatie:

  • de gesprekken van die persoon met hun werkgeheugen, en de computers die er nog voor draaien;
  • opgeslagen beurten, geheugenrecords, datastore-records, geüploade bronnen en artifacts, en de rechten erop;
  • pagina's die voor die persoon zijn gepubliceerd, exports die tot die persoon zijn beperkt, en de feedgebeurtenissen over haar rijen;
  • afgeronde taken die voor die persoon zijn gestart;
  • haar workflowruns, waarbij een open run eerst wordt geannuleerd;
  • verbetervoorstellen die alleen uit haar geheugen komen.

Sommige rijen blijven en verliezen alleen de persoon. Een beslissing die haar was toegewezen op een workflowrun die blijft, houdt de run, zonder haar id of haar antwoord; een beslissing die nog op haar wacht, mislukt met no_assignee, en de run gaat van daaruit verder. Een verbetervoorstel dat uit meer mensen komt, houdt zijn tekst en verliest de id's van haar beurten. Een planning die beurten voor die persoon afvuurt, en een gebeurtenistrigger die workflowruns voor haar start, wordt uitgezet in plaats van verwijderd, zodat je hem op iemand anders kunt richten. Het antwoordt 202 -- het werk loopt op de achtergrond -- en één auditgebeurtenis legt de aantallen vast zodra het klaar is. Audit- en verbruiksregels houden de id zelf: dat spoor is jouw eigen verslag van wat er gebeurde, en de id alleen identificeert niemand zonder de attributen die met het verzoek meekwamen.

curl -s -X DELETE "$CORE_URL/subjects/anna" \
    -H "Authorization: Bearer $CORE_KEY"

GET /subjects/{id} geeft terug wat Core op dit moment voor die id bewaart -- een aantal per soort, en verder niets, want meer heeft Core niet om mee te antwoorden. Beide routes staan achter de subjects-capability.

De DELETE antwoordt ook 202 voor een id waarvoor Core niets bewaart, zodat je backend een wisverzoek kan doorgeven zonder eerst uit te zoeken of de persoon bekend is. Wat de andere antwoorden betekenen:

Antwoord Betekenis
404 je sleutel heeft de capability subjects niet, of het id volgt de grammatica niet (letters, cijfers, . _ : @ -, 1 tot 128 tekens). Een 404 betekent nooit dat de persoon weg is
401 de sleutel is niet geldig; dat geldt ook voor elke sleutel van een verwijderde organisatie
403 je organisatie is opgeschort
409 je organisatie wordt nog ingericht of wordt verwijderd. Probeer het later opnieuw

Wil je zien wat er na het wissen over is, vraag dan GET /subjects/{id} op. Met de capability aan en een geldig id betekent de 404 daarvan dat Core niets meer voor die persoon bewaart. Een beurt die nog liep toen het wissen begon, wordt met rust gelaten en telt als skipped_in_flight in de auditgebeurtenis van het wissen; stuur de DELETE opnieuw zodra die beurt klaar is om ook die weg te halen.

Vraagt een andere sleutel van je organisatie om dezelfde persoon te wissen terwijl jouw wisopdracht nog in de wachtrij staat of loopt, dan krijgt die sleutel hetzelfde erasure_id en begint er geen tweede wisopdracht. In het spoor van die sleutel staat alleen zijn eigen gebeurtenis subject.erasure_requested; de gebeurtenis subject.erased met de aantallen staat in het spoor van jouw sleutel.

Anna, van begin tot eind

Alles samen: een virtuele medewerker voor accountantskantoren, één sleutel per kantoor, honderd klantdossiers eenmalig geüpload met labels client en department, de MCP-server salaris gelabeld department: payroll en websearch gelabeld audience: everyone. Anna, die op de loonadministratie werkt bij kantoor Jansen, vraagt de assistent die haar kantoor heeft aangeschaft:

curl -s -X POST "$CORE_URL/responses" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -H 'X-Core-Subject: {"id": "anna", "attributes": {"department": ["payroll"], "office": ["jansen"]}}' \
    -d '{"model": "<een model-id>",
         "input": "wat is er gebeurd met de loonaangifte van De Vries vorige maand?"}'

Met de regels van hierboven van kracht, bereikt het zoeken alleen bronnen gelabeld department: payroll of visibility: public -- het loondossier van De Vries, nooit het belastingdossier van een andere klant, geüpload onder een andere afdeling -- en krijgt het model de mcp__salaris__*-tools plus websearch aangeboden en verder niets. Het antwoord:

{
  "id": "resp_9Ktq...",
  "object": "response",
  "status": "completed",
  "output": [
    {"type": "message", "role": "assistant",
     "content": [{"type": "output_text", "text": "De loonaangifte van De Vries is ingediend op..."}]}
  ]
}

Bob van de receptie stuurt hetzelfde verzoek met {"id": "bob", "attributes": {"department": ["front-office"]}}: hij krijgt websearch aangeboden, geen enkele mcp__salaris__*-tool, en het loondossier komt in zijn zoekresultaten niet voor.

Het eigen gesprek van Anna, als deze beurt op een gesprek staat, is van haar: een later verzoek met een ander subject-id krijgt hetzelfde 404 als een gesprek dat nooit heeft bestaan. Van Anna zelf wordt nooit meer bewaard dan "anna" en de attributen van dit ene verzoek.