Ga naar inhoud

Een codeeragent aansluiten

Een codeeragent zoals OpenCode of Claude Code roept een model-API rechtstreeks aan. Hij houdt het gesprek zelf bij, stuurt het bij elk verzoek in zijn geheel mee en voert zijn eigen tools uit. Core beantwoordt twee van de API's die zulke agents spreken, dus je sluit de agent op Core aan door zijn adres en zijn sleutel te wijzigen:

De agent spreekt Route van Core Basis-URL om in te stellen
de Responses-API van OpenAI POST /api/v1/responses https://<jouw-core>/api/v1
de Messages-API van Anthropic POST /api/v1/messages https://<jouw-core>/api — de clients van Anthropic zetten er zelf /v1/messages achter

Deze pagina beschrijft wat Core op die twee routes aanneemt, negeert en weigert. Ze is gebaseerd op de eigen tests van Core met de verzoeken die deze agents sturen. Daarbij is geen volledige sessie van OpenCode, Claude Code of Codex tegen Core gedraaid, dus probeer je agent uit tegen je eigen installatie voordat je erop vertrouwt.

Geef de agent een sleutel

Registreer een sleutel voor de agent (Sleutels) en kies de capabilities met de agent in gedachten:

  • Voor de eigen tools van de agent is geen capability nodig. Een tool die de agent zelf definieert en uitvoert, is een tool die je applicatie uitvoert; tooling is alleen nodig voor tools die Core uitvoert.
  • Laat trust_envelope uit, tenzij je wilt dat Core de berichten van de agent verpakt. Staat hij aan, dan bereikt elk gebruikersbericht en elk toolresultaat van de agent het model binnen een <untrusted>-element, onder een inleiding van Core. Op een nieuwe sleutel staat hij uit. Zie De capability trust_envelope voor wat je daarmee opgeeft.
  • Laat threads uit voor een agent die elke keer zijn hele geschiedenis meestuurt. Met threads aan en store op de standaardwaarde wordt elk verzoek als een eigen gesprek bewaard (Opslaginstellingen).
  • Laat subjects uit op een sleutel die Claude Code gebruikt. Claude Code vult metadata.user_id met een eigen kenmerk, en een sleutel met subjects weigert dat bij elk verzoek (Claude Code).
  • Sta de modellen toe die de agent gaat noemen, in de policy van de sleutel. GET /models geeft de id's die je sleutel mag gebruiken (Een model kiezen).

Wat een sleutel zonder threads antwoordt

Een sleutel zonder threads bewaart niets, dus een later verzoek heeft niets om naar te verwijzen:

De agent stuurt Core antwoordt
store: true de beurt wordt uitgevoerd en niet bewaard; het antwoord is 200
previous_response_id, conversation of thread_id 404, met de melding hieronder
GET /responses/{id} 404, met dezelfde melding
dezelfde Idempotency-Key opnieuw, nadat de eerste aanroep is afgerond 409: Core herkent de herhaling, maar heeft geen bewaard antwoord om terug te geven (Idempotentiesleutels)

De 404 zegt waarom, in de foutvorm van de leverancier. De melding is Engels:

This key does not hold the `threads` capability, so no response or thread was kept for it to
continue or read back. Send the whole conversation in the request, or use a key that holds
`threads`.

De 409 doet dat ook, ook als hij als de gebeurtenis idempotency_conflict van een stream aankomt:

A request with this idempotency key was already processed and its response was not kept, because
this key does not hold the `threads` capability. Send the request under a new idempotency key to
run it again, or use a key that holds `threads` so a retry is answered with the original response.

Beide meldingen gaan over je eigen sleutel en zeggen niets over het id dat je stuurde. Een sleutel die threads wel heeft, krijgt een kale Not Found voor een id dat niet bestaat.

Heeft je agent een van deze nodig, schakel dan threads in op zijn sleutel.

De sleutel meesturen

Core controleert de secret van een sleutel die als bearer-token wordt meegestuurd:

Authorization: Bearer ack_...

Een OpenAI-client stuurt zijn api_key op deze manier, dus een agent die de Responses-API gebruikt, heeft niets anders nodig.

De clients van Anthropic sturen hun api_key in een header x-api-key. Core neemt die header aan op de twee routes die een Anthropic-client aanroept, POST /messages en GET /models, en nergens anders:

Het verzoek bevat Op POST /messages en GET /models Op elke andere route
alleen x-api-key aangenomen als de sleutel geweigerd met 401: de header wordt niet gelezen
Authorization en x-api-key Authorization wordt gebruikt Authorization wordt gebruikt
alleen Authorization aangenomen aangenomen

Met de SDK van Anthropic werken beide schrijfwijzen:

import anthropic

# stuurt x-api-key
client = anthropic.Anthropic(base_url="https://<jouw-core>/api", api_key="ack_...")

# stuurt Authorization: Bearer
client = anthropic.Anthropic(base_url="https://<jouw-core>/api", auth_token="ack_...")

De Anthropic-provider van OpenCode (@ai-sdk/anthropic) stuurt x-api-key, wat om dezelfde reden werkt op die twee routes.

De modellen opvragen

GET /models antwoordt met de lijst die de SDK's van beide leveranciers lezen, dus client.models.list() geeft elk chatmodel dat je sleutel mag gebruiken, in één pagina (Een model kiezen). De route neemt de sleutel op beide manieren aan, als bearer-token of als x-api-key, dus een Anthropic-client die met api_key is aangemaakt, kan ook modellen opvragen.

Wat Core met de velden van de agent doet

Agents sturen veel velden die de API van hun leverancier kent. Core deelt elk veld in een van drie klassen in. De API-referentie noemt elk veld van beide routes; bij een genegeerd veld staat in de beschrijving dat het genegeerd wordt.

Genegeerd. Een hint die niet verandert wat het antwoord betekent, wordt aangenomen en weggelaten. Ze gaat niet naar het model, wordt niet bewaard en komt niet terug.

Route Genegeerde velden
POST /responses prompt_cache_key, client_metadata, safety_identifier, service_tier, include, text.verbosity
POST /messages cache_control overal waar Anthropic het toestaat, eager_input_streaming op een tool, context_management, betas, service_tier, elke sleutel van metadata behalve user_id, en de headers anthropic-version en anthropic-beta

Core kiest zelf het voorvoegsel voor de promptcache; daarom vallen de cachinghints in deze klasse.

metadata.user_id op /messages is geen vrij label. Een waarde in de vorm van een subject-id — 1 tot 128 tekens uit letters, cijfers en . _ : @ - — noemt de persoon voor wie de beurt is, precies zoals user dat doet op /responses (Bouwen voor personen). Wat er met elke andere waarde gebeurt, tot 1.024 tekens, hangt af van de sleutel:

De sleutel Een metadata.user_id die geen subject-id is
heeft subjects niet genegeerd: de beurt draait voor niemand in het bijzonder, alsof het veld ontbrak, en de waarde wordt niet bewaard of doorgestuurd
heeft subjects geweigerd met 400, met metadata.user_id in de melding

Een sleutel met subjects scheidt wat hij bereikt per persoon; daarom weigert Core daar, in plaats van de beurt voor niemand te draaien. user op /responses wordt op elke sleutel geweigerd als het geen subject-id is.

Scheidt je eigen applicatie haar gebruikers via /messages, dan gaat dit jou ook aan, niet alleen Claude Code. Een persoon noemen bij een beurt vraagt geen capability, maar op een sleutel zonder subjects wordt een verkeerd getypte of anderszins niet passende metadata.user_id zonder foutmelding weggelaten, en die beurt draait voor niemand: hij bereikt wat een beurt bereikt die niemand noemt, niet de eigen gesprekken, bestanden en het geheugen van die persoon. Doe een van deze dingen om te zorgen dat dat nooit ongemerkt gebeurt:

  • geef de sleutel subjects, zodat een niet passende waarde in plaats daarvan met 400 wordt geweigerd;
  • zet settings.subjects_required op true in het beleid van de sleutel (Wat een verzoek zonder subject bereikt), zodat een beurt die uiteindelijk niemand noemt met 400 wordt geweigerd;
  • stuur alleen id's die aan de vorm hierboven voldoen.

Gehonoreerd. Een veld dat een modelleverancier kan aannemen, wordt doorgegeven: temperature, top_p, de uitvoerlimiet, een afgedwongen tool, parallel_tool_calls (tool_choice.disable_parallel_tool_use op /messages), de inspanning en samenvatting van de redenering, een JSON Schema voor de uitvoer, en op /messages ook top_k en stop_sequences. Kan het model dat je noemde een veld dat je instelde niet aannemen, dan wordt de beurt vóór elke modelaanroep geweigerd met 400 en de naam van dat veld; Core laat het veld niet weg om de aanroep te laten slagen.

Bij naam geweigerd. Een veld dat Core niet kan waarmaken, terwijl een 200 zou suggereren van wel, is een 400 waarvan de melding het veld noemt:

Route Geweigerd
POST /responses truncation: "auto" (Core laat nooit context weg om een aanroep passend te maken), max_tool_calls, een tool_choice-object dat geen functietool bij naam noemt, elk tooltype dat Core niet uitvoert: file_search, code_interpreter, computer_use, image_generation, local_shell, mcp; de grammar van een custom-tool op een model dat niet via de Responses-API van OpenAI loopt; en op web_search een niet-lege filters.allowed_domains of een search_content_types zonder text
POST /messages mcp_servers, container, speed, fallbacks, inference_geo, background, callback_url, output_config.task_budget, thinking.budget_tokens, thinking van het type enabled of disabled (gebruik adaptive), een tekstblok met citations erin, een andere servertool dan web search (zoals code execution), en op web search een niet-lege allowed_domains of blocked_domains, of allowed_callers zonder direct

Eigen velden van Core. /messages neemt ook eigen velden van Core die de API van Anthropic niet kent: agent, skills, tool_variables (waarden voor de aanroepvariabelen van de tools van de beurt, nooit zichtbaar voor het model), hosting, store, previous_response_id, thread_id, tool_search en max_tool_rounds. Een agent die ze niet stuurt, krijgt het gedrag van de leverancier.

Elk ander veld dat geen van beide leveranciers kent, is een 400 met de naam erbij. Fouten komen in de eigen foutvorm van de leverancier, dus de SDK van de agent gooit zijn gebruikelijke exceptie (De modelroutes antwoorden met de fout van de leverancier).

Teruggestuurde geschiedenis

Een agent stuurt wat hij ontving terug als deel van het volgende verzoek. Core neemt daar zijn eigen uitvoer aan:

  • Een assistentbericht, een function_call (tool_use op /messages) en het resultaat van de agent daarvoor worden aangenomen met de extra velden die de SDK's uitschrijven, zoals id, status en null-waarden. Die velden worden genegeerd.
  • Meerdere toolaanroepen uit één ronde gaan als één assistentbericht naar het model, met elk resultaat er direct achter.
  • Een reasoning-item op /responses en een thinking-blok op /messages worden aangenomen en niet naar het model gestuurd.
  • Een bericht met role: "system" midden in messages wordt op /messages aangenomen, met alleen tekstblokken.
  • Een toolresultaat mag tekst zijn, of tekst en afbeeldingen (Afbeeldingen).
  • Een mcp_call-item — een tool die Core uitvoerde — wordt op /responses niet teruggenomen. Dat speelt alleen als je de agent ook tools aanbiedt die Core uitvoert.

Redenering

De redenering van een denkend model komt terug als reasoning-item op /responses en als thinking-blok op /messages, met een lege signature: Core bewaart zelf wat de leverancier nodig heeft om verder te gaan en hoeft dat niet van de agent terug te krijgen. Core vraagt om een samenvatting, tenzij het verzoek, je sleutel of je organisatie iets anders zegt; op /messages zegt het verzoek nee met thinking.display: "omitted".

Sommige modellen die je installatie via een OpenAI-compatibele chat-completions-API bereikt, geven hun eigen denkwerk terug in plaats van een samenvatting. Core toont dat op dezelfde plek. Zie Antwoordvelden voor hoe die tekst verschilt en voor de servers die helemaal geen redenering teruggeven.

Limieten

Limiet Waarde Bij overschrijding
Body van het verzoek op POST /responses, POST /messages en POST /responses/{id}/tool_outputs 8 MiB, tenzij je installatie een andere limiet heeft ingesteld 413 in de foutvorm van de leverancier; de melding noemt de limiet in bytes. Kort de geschiedenis in of vat haar samen, en stuur afbeeldingen na de eerste met file_id in plaats van inline.
Tools in één verzoek 128 400
De description van een tool 16.384 tekens 400
Eén tekstdeel, of één tooluitvoer 1 MiB 400 met de naam van het veld
instructions, en input als dat één tekenreeks is 262.144 tekens; 1 MiB 400 met de naam van het veld
Berichten in één verzoek op /messages 1.024 400
Een meegestuurde afbeelding 5 MiB; PNG, JPEG, GIF of WebP 400
metadata.user_id op /messages 1.024 tekens 400
Een synchrone beurt vijf minuten (Time-outs) de beurt mislukt

Op /messages is de limiet van instructions de limiet van system: een system van meer dan 262.144 tekens wordt geweigerd met 400, met system als veld.

Een body groter dan 1 MiB wordt alleen gelezen voor een geldige sleutel. Met een ontbrekende of onjuiste sleutel wordt hij ongelezen geweigerd met 401, en de agent kan de verbinding zien sluiten in plaats van de 401.

De uitvoerlimiet en een afgekapt antwoord

Agents vragen vaak aan elk model dezelfde hoge uitvoerlimiet. Waar de catalogus van je installatie het maximum van een model vermeldt, verlaagt Core een hogere max_output_tokens (max_tokens op /messages) naar dat maximum in plaats van het verzoek te weigeren. Het antwoord toont nog steeds de waarde waar de agent om vroeg. Is er geen maximum vermeld, dan gaat de waarde ongewijzigd naar de leverancier, en een leverancier die haar afwijst laat de beurt mislukken met de code model_request_rejected.

Voor welke modellen een maximum is vermeld, bepaalt de eigen catalogus van je installatie, die je operator bijhoudt. Mislukt een model op de limiet waar je agent om vraagt, vraag je operator dan het maximum van dat model te vermelden.

Stopt het model omdat het de uitvoerlimiet bereikte, dan is de beurt niet mislukt. De tekst die er al stond komt terug, een toolaanroep niet: een aanroep die halverwege is afgekapt wordt weggelaten, omdat zijn argumenten nooit af waren.

Route Een afgekapt antwoord
POST /responses status: "incomplete" met incomplete_details.reason: "max_output_tokens"; het berichtitem heeft status: "incomplete". Een stream eindigt met response.incomplete.
POST /messages stop_reason: "max_tokens", op het bericht en op de message_delta van een stream

Zie Een afgekapt antwoord voor het verschil met een beurt die door zijn toolrondes heen is.

Afbeeldingen

Een afbeelding stuur je in het verzoek zelf mee, of als bestand dat je naar Core hebt geüpload (Bestanden uploaden):

  • Op /responses bevat een input_image-deel image_url als base64-data:-URL, of een file_id.
  • Op /messages bevat een image-blok een base64-bron, of {"type": "file", "file_id": "..."} met een bestand van Core.

Core haalt geen afbeelding op van een webadres: een http- of https-URL wordt geweigerd met 400.

detail. Op /responses zegt input_image.detail (auto, low of high) hoe nauwkeurig het model moet kijken. Het gaat mee naar een model uit de OpenAI-familie en wordt voor elk ander model weggelaten; het weigert nooit een verzoek. /messages heeft zo'n veld niet.

Afbeeldingen in een toolresultaat. Een tool die de agent uitvoerde, kan met een afbeelding antwoorden, zoals een schermafbeelding:

  • Op /responses is function_call_output.output een string, of een lijst van input_text- en input_image-delen. Hetzelfde geldt op POST /responses/{id}/tool_outputs.
  • Op /messages is de content van een tool_result-blok een string, of een lijst van text- en image-blokken.

De afbeelding geef je op dezelfde twee manieren mee als hierboven, en ook hier nooit via een URL. Het model leest haar als deel van het resultaat van die tool, naast de tekst van het resultaat. Een model dat je installatie zonder afbeeldingsinvoer heeft opgegeven, weigert het verzoek met 400.

Een model dat misschien geen afbeeldingen aanneemt. Een model waarvan je installatie de features niet heeft opgegeven, krijgt de afbeelding mee, en de leverancier beslist. Weigert die het verzoek, dan mislukt de beurt met de code model_request_rejected en de melding "The model provider rejected the request Core sent for this turn. The request contained an image; this model may not accept image input." Elke latere beurt die de afbeelding meestuurt, mislukt op dezelfde manier. Een agent die zijn hele geschiedenis opnieuw stuurt, haalt het afbeeldingsdeel eruit. Een gesprek dat een bewaard antwoord voortzet, houdt een als bestand geüploade afbeelding vast; begin dan een nieuw gesprek zonder die afbeelding. In beide gevallen kun je een model kiezen dat afbeeldingen aanneemt, of je operator vragen op te geven wat dit model aanneemt.

Een achtergrondrun laadt geen afbeeldingen: het model leest [image unavailable] op de plek van een afbeelding, in een gebruikersbericht en in een toolresultaat. Een afbeelding die je in het verzoek zelf meestuurt, wordt ook niet bewaard, dus een latere beurt die een bewaard gesprek voortzet, leest op die plek [image unavailable]; een agent die zijn hele geschiedenis opnieuw stuurt, merkt daar niets van.

Vrije tools

Een tool van het type custom voert je agent zelf uit, net als een function-tool, maar het model roept hem aan met vrije tekst in plaats van JSON; apply_patch van Codex is er een. Core geeft de aanroep terug als een custom_tool_call met de tekst in input, gestreamd als response.custom_tool_call_input.delta en .done, en neemt de custom_tool_call_output van je agent aan als het resultaat. Een model dat via de Responses-API van OpenAI loopt, krijgt de tool zoals hij is, format inbegrepen. Elk ander model krijgt een functie met één tekstargument; zo'n model kan Core niet aan een grammar-format houden, dus daar wordt een custom tool met een grammatica geweigerd met 400 en de naam tools.N.format. Een text-format, of geen format, werkt op elk model.

Wat dit betekent voor Codex. Codex biedt apply_patch aan als custom tool met een lark-grammatica wanneer het model een GPT-5-model van OpenAI is; in de eigen conformance-runs van Core was dat gpt-5.4, en bij de andere modellen bood Codex geen custom tool aan. Komt zo'n model van een leverancier op de Responses-API van OpenAI, dan werkt het verzoek zoals bij OpenAI. Komt het van een leverancier op chat completions -- bijvoorbeeld een Azure OpenAI-deployment zonder de Responses-API -- dan wordt het geweigerd met 400 en de naam van het format van het apply_patch-item. Naar welke modellen Codex de grammatica stuurt, kiest Codex zelf en kan per versie veranderen; kijk wat jouw versie stuurt.

Tools in een namespace

Een tool van het type namespace groepeert functie- en custom tools die je agent uitvoert, zoals Codex de tools van elke MCP-server groepeert. Core biedt elke tool aan het model aan als ns_<namespace>__<naam> en geeft een aanroep terug onder de eigen name van de tool, met de namespace ernaast, zodat je agent hem doorstuurt zoals bij OpenAI. Een teruggestuurde aanroep die zijn namespace meedraagt, hoort bij dezelfde tool. Twee dingen om te weten:

  • De samengestelde naam is hoogstens 64 tekens. Een tool waarvan ns_<namespace>__<naam> langer is, of die een andere tool van het verzoek al onder die naam aanbiedt, wordt geweigerd met 400 en de naam tools.N.tools.M.name.
  • Toolbeleid ziet de samengestelde naam. Om één tool van een namespace te weigeren, noem je ns_<namespace>__<naam> in een forbid-regel van de soort tool in het beleid van de sleutel; voor de hele namespace ns_<namespace>__*.

Een tool die je agent buiten een namespace stuurt, houdt de naam die de agent hem gaf, ook als die naam geschreven is als mcp__<server>__<tool>: Core geeft de aanroep terug aan je agent en voert zelf niets uit (Tools die je applicatie uitvoert).

Zoeken op het web

{"type": "web_search"} (en web_search_preview) van OpenAI op /responses en de servertool web_search_20250305 van Anthropic op /messages draaien de eigen webzoekfunctie van Core, builtin__web_search. De sleutel heeft nodig wat het aanbieden van die tool bij naam nodig heeft: tooling met web_search in zijn system_tools, en een installatie waarop een zoekleverancier is ingesteld. Elke andere sleutel krijgt een 400 die het tools-item noemt. De zoekopdracht komt terug als een toolronde van Core zelf, een mcp_call op /responses en een mcp_tool_use met zijn mcp_tool_result op /messages, niet als het zoekitem van de leverancier. Het country van een locatie bepaalt uit welk land de resultaten komen; stad, regio, tijdzone en contextomvang worden aangenomen en genegeerd. max_uses van Anthropic wordt gehonoreerd: na zoveel zoekopdrachten in één beurt krijgt het model bij een volgende een zin dat er geen zoekopdrachten meer mochten, en de beurt gaat door. Een lijst met domeinen wordt bij naam geweigerd, omdat de zoekfunctie van Core zich daar niet aan kan houden.

Tools zoeken

Een agent die {"type": "tool_search"} van OpenAI op /responses stuurt, tool_search_tool_bm25 of tool_search_tool_regex van Anthropic op /messages, of defer_loading: true op een van zijn tools, krijgt voor het hele verzoek de eigen toolrouter van Core, alsof hij "tool_search": {"mode": "router"} had gestuurd. Het model krijgt builtin__tool_search en builtin__tool_run aangeboden in plaats van de tools die Core uitvoert en de eigen functietools van je agent, vindt een tool door te beschrijven wat het nodig heeft, en voert die op naam uit. Een uitvoering van een tool van je agent komt terug als de gewone function_call of tool_use onder de eigen naam van de tool, die je agent beantwoordt zoals altijd. Custom tools (vrije tekst) en een tool die je tool_choice afdwingt, blijven in de lijst van het model. defer_loading geldt nooit voor één tool alleen: een verzoek dat sommige tools op true en andere op false zet, plaatst ze allemaal achter de router. Een tool_search die je verzoek zelf meestuurt, gaat boven deze velden. De sleutel heeft tooling.tool_search.enabled nodig en een organisatie die tool search toestaat. Een sleutel zonder die twee wordt geweigerd, ook als het verzoek alleen een van deze velden van de leverancier meestuurt en tool_search zelf nergens noemt: het antwoord is een 400 die tool_search.mode noemt. Zoekopdrachten en uitvoeringen komen terug als toolrondes van Core zelf, een mcp_call op /responses en een mcp_tool_use met zijn mcp_tool_result op /messages, niet als de tool-search-items van de leverancier.

Wat niet wordt ondersteund

  • /chat/completions. Core heeft geen chat-completions-route; een verzoek ernaar geeft 404. Stel de agent in op de Responses-API of de Messages-API.
  • Gehoste tools van de leveranciers anders dan web search en tool search, zoals file_search van OpenAI of code execution van Anthropic. Core weigert ze bij naam. Registreer de tool in plaats daarvan bij je organisatie en bied hem aan als tool van Core.
  • Afbeeldingen via een URL, zie hierboven.
  • Tokens tellen, batches en de Files-API van Anthropic. POST /v1/messages/count_tokens, de Batches-API en anthropic.files hebben geen route op Core.
  • Een bericht teruglezen. Er is geen GET /messages/{id}. Het id in een antwoord van /messages is een response-id van Core, dat GET /responses/{id} leest als de sleutel threads heeft.

OpenCode

Core neemt de verzoeken aan die OpenCode op beide routes stuurt. Stel in OpenCode een provider in met de basis-URL en de sleutel uit de tabellen hierboven en een model-id uit GET /models. Op de Messages-route stuurt OpenCode zijn sleutel als x-api-key, en die neemt Core daar en op GET /models aan.

Claude Code

Claude Code spreekt de Messages-API en gebruikt dus de basis-URL https://<jouw-core>/api. Van wat Claude Code voor zover bekend stuurt, verwerkt Core vandaag het volgende:

  • de header anthropic-beta bij elk verzoek: genegeerd;
  • berichten met role: "system" na het eerste verzoek: aangenomen;
  • een hoge max_tokens voor elk model: verlaagd naar het maximum van het model waar de catalogus er een vermeldt (hierboven);
  • metadata.user_id bij elk verzoek, gevuld met een eigen kenmerk van apparaat, account en sessie, dat geen subject-id is: genegeerd op een sleutel zonder subjects, geweigerd met 400 op een sleutel die het heeft (hierboven). Schakel subjects niet in op een sleutel die Claude Code gebruikt;
  • de verbindingscontrole voordat Claude Code start, HEAD <basis-URL>/api/hello: beantwoord met een lege 200, ook voor GET. Dat antwoord zegt dat het adres bereikbaar is. Het controleert de sleutel niet en zegt niets over de vraag of Core zelf draait.