Ga naar inhoud

Workflows via de API

Het concept achter workflows staat in Workflows.

Op deze pagina leg je een bedrijfsproces vast als workflow, controleer je het, start je een run en volg je die tot het einde. Een workflow is een graaf die je opslaat: de knooppunten zijn stukken werk -- een agent, een losse prompt, een aanroep van een tool, een voorwaarde, een samenkomst -- en de verbindingen geven de uitvoer van het ene knooppunt door aan het volgende. Core voert de graaf uit die je hebt opgeslagen, stap voor stap op de achtergrond, dus een proces met een agent, een opzoeking en een tweede agent naast elkaar loopt zonder dat jouw applicatie ervoor hoeft te blijven draaien.

Je sleutel heeft de capability workflows nodig, en die heeft runs en threads nodig: elke agent- of promptstap is een achtergrondrun op een eigen gesprek. Een toolstap heeft ook tooling nodig. Je operator zet workflows aan voor de hele installatie; zolang dat niet is gebeurd, antwoordt elke route hieronder 404. Hetzelfde doe je met de muis in Workflows in de console.

Een workflow schrijven

curl -s -X POST "$CORE_URL/workflows" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "intake",
         "description": "Een bericht indelen en een antwoord opstellen",
         "input_schema": {"type": "object",
                          "properties": {"message": {"type": "string"}},
                          "required": ["message"]},
         "graph": {
           "nodes": [
             {"id": "start", "kind": "start"},
             {"id": "classify", "kind": "agent", "config": {"agent": "triage"},
              "input": "run.input.message"},
             {"id": "route", "kind": "condition"},
             {"id": "draft", "kind": "agent", "config": {"agent": "writer"},
              "input": "run.input.message"},
             {"id": "reply", "kind": "end"},
             {"id": "spam", "kind": "end", "input": "'\''dropped'\''"}
           ],
           "edges": [
             {"source": "start", "target": "classify"},
             {"source": "classify", "target": "route"},
             {"source": "route", "target": "draft",
              "when": "nodes.classify.output != '\''spam'\''"},
             {"source": "route", "target": "spam"},
             {"source": "draft", "target": "reply"}
           ]}}'

Een geslaagd verzoek antwoordt 201 en een Location, met de workflow en zijn eerste versie. Een naam is uniek binnen je organisatie: een tweede workflow met de naam intake geeft 409.

Soort knooppunt Wat het doet config
start de invoer van de run, gecontroleerd tegen input_schema geen
end zijn invoer wordt de uitvoer van de run; als twee end-knooppunten klaar zijn, is de uitvoer een object met hun id's als sleutels geen
agent laat een agent lopen; zijn laatste tekst, of het geparste object waar de agent een JSON-schema opgeeft agent (een naam), version, thread: {"continue": "<knooppunt-id>"}
prompt één beurt van een model met een opgeslagen prompt of instructies prompt of instructions, model, text_format
tool roept één tool aan; zijn JSON-antwoord, of zijn tekst tool (een volledige naam zoals api__crm_lookup), version
condition neemt elke verbinding waarvan de when klopt, of de ene verbinding zonder when geen
join wacht op de takken die erin samenkomen mode: all, any of n_of met n
transform zijn invoer is zijn uitvoer: gegevens omvormen tussen stappen geen
wait houdt de run vast tot een tijdstip; zijn invoer is zijn uitvoer seconds, of until: een expressie die een tijdstip geeft zoals 2026-11-01T09:00:00+01:00
workflow start een run van een andere workflow en wacht erop; zijn invoer is de invoer van die run, en de uitvoer van die run is zijn uitvoer workflow (een naam), version
map start één run van een andere workflow per item van een lijst; een lijst met de uitvoer van die runs, in de volgorde van de items items (een expressie die een lijst geeft), workflow (een naam), version, concurrency
decision houdt de run vast tot iemand antwoordt, of vraagt het een agent; {outcome, fields, decided_by} outcomes, instruction, assignee, en eventueel form, deadline_seconds, on_deadline (hieronder)

Een run die een workflow- of map-knooppunt startte, is een gewone run met "trigger_kind": "workflow"; GET /workflow_runs?parent_run_id=... toont de runs die één run startte. Elk daarvan is een eigen start, één gebruik van workflow_run, dus een map over honderd items kost honderd starts naast de run waarin hij staat. Een map neemt geen input en geen retry. Hoe zulke runs er in de console uitzien en welke grenzen gelden, staat in Workflows in de console.

Elk knooppunt kan een input opgeven, een JMESPath-expressie over de context van de run: run.input en nodes.<id>.output voor elke stap die klaar is (nodes.<id>.error.code voor een stap die mislukte). Zonder input krijgt een knooppunt de uitvoer van zijn ene voorganger, en een join een object met per voltooide tak een sleutel. Een tool-, agent- of prompt-knooppunt mag timeout_seconds en retry zetten ({"max_attempts": 3, "backoff_seconds": 10}). Een tool-knooppunt mag retry alleen zetten als de registratie van de tool zegt dat twee keer versturen veilig is (retry_safe); anders weigert het opslaan de retry van het knooppunt. Een verbinding met "on_error": true is het vangnet van een knooppunt: een mislukte stap volgt die in plaats van zijn andere verbindingen.

Alles wordt gecontroleerd bij het opslaan. Een ontbrekende agent, een tool die je sleutel niet mag aanroepen, een kringloop, twee verbindingen die buiten een join samenkomen of een expressie die niet te lezen is, geeft 422 met het veld erbij, zoals graph.nodes[1].config.agent -- niet drie dagen later een mislukte run. Een sleutel zonder de capability tooling, of met een beleid dat een agent vereist, mag helemaal geen tool-knooppunt hebben: een toolstap loopt buiten elke agent, dus het opslaan geeft 422 op graph.nodes[i].config.tool. Een toolstap kan ook geen agent noemen (agent__<naam>): draai een agent met een stap van soort agent. Verwijzingen zijn namen: een waarde in de vorm van een id, zoals agt_01J..., wordt geweigerd. Om te controleren zonder op te slaan, neemt POST /workflows/validate dezelfde graph en input_schema.

Een run starten

curl -s -X POST "$CORE_URL/workflows/wf_01J.../runs" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -H "Idempotency-Key: ticket-4711" \
    -d '{"input": {"message": "Mijn factuur klopt niet."}}'

Het antwoord is 202 met de run, queued. Een sleutel start een run als zichzelf: elke stap handelt als jouw sleutel, wordt beslist door het beleid van jouw sleutel op het moment dat hij loopt, en besteedt het budget van jouw sleutel. Het starten van een run is één gebruik van de capability op je rekening; wat de stappen laten lopen wordt gerekend zoals altijd. version legt een oudere versie vast, subject_id noemt de persoon voor wie elke stap is, labels labelt elk gesprek dat de stappen openen en elke run die ze starten (overerving), en dezelfde Idempotency-Key antwoordt met de run die hij startte in plaats van een nieuwe te starten; dezelfde waarde gebruikt door een andere API-sleutel geeft 409. Een Idempotency-Key die begint met schedule:, trigger:, webhook: of task: geeft 422: die vier voorvoegsels zijn de sleutels die Core geeft aan de runs die een planning, een trigger of een taak start. Invoer die groter is dan de installatie toestaat, geeft 413. Heeft je organisatie al zoveel open runs als de installatie toestaat, of heeft je sleutel zijn maandplafond bereikt, dan is het antwoord 429.

Starten als er iets gebeurt

Een trigger start een run telkens als Core een gebeurtenis van één soort voortbrengt, dezelfde gebeurtenissen die de feed en webhooks dragen. Noem de workflow, de soort, en eventueel welke gebeurtenissen van die soort en wat de invoer van de run is:

curl -s -X POST "$CORE_URL/workflow_triggers" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"workflow": "intake",
         "event_kind": "knowledge.source.ingested",
         "filter": {"status": "completed"},
         "input": "{source: event.refs.source_id}"}'

filter is gelijkheid over de refs van de gebeurtenis: elke naam die je geeft moet er staan met precies die waarde, en {} past op elke gebeurtenis van de soort. input is een JMESPath-expressie over event (id, type, refs, subject_id, created_at); laat je hem weg, dan is de invoer van de run dat gebeurtenisdocument. Elke run handelt als de sleutel die de trigger schreef en heeft "trigger_kind": "event"; GET /workflow_runs?trigger_id=wft_... geeft de runs die één trigger startte. Eén gebeurtenis start per trigger hoogstens één run, hoe vaak hij ook wordt afgeleverd.

Voor wie een run is, volgt de gebeurtenis. Een trigger die niemand noemt start elke run voor de persoon bij wie de gebeurtenis hoort, dus een run uit een gebeurtenis van Anna is van Anna: alleen haar subjecttoken en je eigen sleutels lezen hem, en hij wordt met haar gewist. Een gebeurtenis die bij niemand hoort start een run voor niemand. Een trigger die in subject_id een persoon noemt start runs voor haar uit haar eigen gebeurtenissen en uit gedeelde, en een gebeurtenis die bij iemand anders hoort past er helemaal niet op.

Nieuwe mail

Een mailkoppeling waarvan de versie een watch instelt (folder, poll_seconds van minstens 60, tenzij je beheerder een andere ondergrens instelde, en enabled, geschreven met POST /mail_connectors of PATCH /mail_connectors/{id}) brengt voor elk nieuw bericht in die map één gebeurtenis mail.received voort. De gebeurtenis draagt het id van de koppeling in mail_connector, de folder en de uid van het bericht, nooit een adres, een onderwerp of een tekst. Ze hoort bij niemand, dus de run die ze start is voor niemand, tenzij de trigger een persoon noemt. Filter op de koppeling om voor elk nieuw bericht een workflow te starten:

curl -s -X POST "$CORE_URL/workflow_triggers" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"workflow": "triage-mail",
         "event_kind": "mail.received",
         "filter": {"mail_connector": "mail_01J..."},
         "input": "{folder: event.refs.folder, uid: event.refs.uid}"}'

De agentstap van de workflow leest het bericht zelf met de tool mail__<naam>__read van de koppeling. Mail die al in de map stond toen de watch begon, start geen run. Hoe je de koppeling en haar watch instelt, staat in Mailkoppelingen via de API.

Grenzen en mislukte starts

Twee grenzen voorkomen dat een trigger zichzelf voedt. De soorten die elke agentstap voortbrengt (response.*, run.*, thread.*) worden als event_kind geweigerd. En een trigger op een workflowgebeurtenis, bijvoorbeeld workflow.run.completed, mag runs maar een paar diep aan elkaar rijgen (drie, tenzij je beheerder anders instelde): de run daarna wordt niet gestart.

Niets houdt een workflow tegen die schrijft naar een datastore waar zijn eigen trigger op luistert: elke run voedt de volgende tot de limiet aan starts per uur (rate_limited, 600 tenzij je beheerder anders instelde) hem stopt.

Een start die niet kon doorgaan wordt niet opnieuw geprobeerd. Hij staat in je audittrail als workflow.trigger.failed met een korte code:

Code Wat de start tegenhield
target_gone De workflow is er niet voor de sleutel van de trigger: verwijderd, of afgeschermd door de regels van de sleutel
request_invalid De workflow weigerde de run, bijvoorbeeld omdat de invoer niet bij het invoerschema past
expression_error De input-expressie van de trigger mislukte op deze gebeurtenis of dit verzoek
policy_refused Het beleid van je organisatie weigerde de run
cap_exceeded De sleutel van de trigger bereikte zijn maandlimiet
capability_withdrawn Je organisatie verloor een mogelijkheid die een workflowrun nodig heeft
key_revoked De sleutel van de trigger is verwijderd of verlopen
tenant_unavailable Je organisatie is opgeschort of wordt verwijderd
subject_mismatch De trigger noemt een persoon en de gebeurtenis hoort bij iemand anders
source_gone De workflowrun die de gebeurtenis voortbracht bestaat niet meer
depth_exceeded De run zou workflows dieper aan elkaar rijgen dan toegestaan
too_many_open Je organisatie heeft al zoveel open runs als de installatie toestaat
rate_limited De triggers van deze bron van je organisatie startten dit uur zoveel runs als de installatie toestaat (600, tenzij je beheerder anders instelde); starts door gebeurtenissen en door webhooks worden apart geteld
expired_unstarted Het verzoek aan een webhooktrigger kon niet binnen een uur worden gestart (tenzij je beheerder anders instelde)

Een organisatie heeft hoogstens 50 triggers, gebeurtenis- en webhooktriggers samen, tenzij je beheerder anders instelde; een nieuwe daarboven krijgt 429. Met PATCH /workflow_triggers/{id} en {"enabled": false} zet je een trigger uit, en DELETE verwijdert hem; de runs die hij startte blijven. Elke sleutel die een trigger kan zien mag hem uitzetten, ook nadat de sleutel van de trigger zelf is ingetrokken. Weer aanzetten met {"enabled": true} is voor de sleutel waaronder de trigger runs start: een andere sleutel krijgt 403 met not_the_acting_key.

Starten vanuit een ander systeem

Een webhooktrigger geeft een workflow een adres. Een systeem van jou stuurt er JSON naartoe, ondertekend met een geheim, en elk verzoek start een run met die JSON als invoer. De afzender heeft geen API-sleutel nodig.

curl -s -X POST "$CORE_URL/workflow_triggers" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"workflow": "intake", "source": "webhook",
         "input": "{message: webhook.body.text}"}'

Het antwoord bevat de url van de trigger en, alleen deze keer, zijn secret (whsec_...). Bewaar het geheim nu: geen latere leesactie toont het, en POST /workflow_triggers/{id}/rotate-secret antwoordt met een nieuw geheim, waarna alleen het nieuwe werkt. De url is $CORE_URL/workflow_webhooks/<organisatie-id>/<trigger-id> en blijft dezelfde zolang de trigger bestaat. Een webhooktrigger krijgt geen event_kind en geen filter. input is een JMESPath-expressie over webhook (body, de JSON die je stuurde; delivery; received_at); laat je hem weg, dan is de invoer van de run de body zelf. Elke run handelt als de sleutel die de trigger schreef, is voor de persoon in subject_id van de trigger of voor niemand, en heeft "trigger_kind": "webhook".

De afzender ondertekent elk verzoek zoals Core de webhooks ondertekent die het jou stuurt: een header Artific-Signature met t=<unix-seconden>,v1=<handtekening>, waarbij de handtekening de hexadecimale HMAC-SHA256 is, onder het geheim, van het tijdstip, een punt en de exacte bytes van de body.

import hashlib, hmac, json, time
import httpx

def post_to_core(url: str, secret: str, payload: dict, delivery: str) -> int:
    body = json.dumps(payload).encode()
    stamp = int(time.time())
    digest = hmac.new(secret.encode(), f"{stamp}.".encode() + body, hashlib.sha256).hexdigest()
    answer = httpx.post(
        url,
        content=body,
        headers={
            "Artific-Signature": f"t={stamp},v1={digest}",
            "Idempotency-Key": delivery,
            "Content-Type": "application/json",
        },
    )
    return answer.status_code

Stuur precies de bytes die je ondertekende. Idempotency-Key is je eigen naam voor de levering, hoogstens 128 tekens zonder spaties: probeer je het na een time-out opnieuw, onderteken dan opnieuw en stuur dezelfde naam, en elke poging is één run. Hetzelfde ondertekende verzoek dat twee keer aankomt start ook één run.

Antwoord Wat het betekent
202, zonder body Aangenomen. De run wordt vlak daarna gestart; het antwoord bevat zijn id niet
404 Niet aangenomen, om een reden die het antwoord niet geeft: een verkeerd adres, een trigger die uit staat, een ontbrekende of verkeerde handtekening, of een tijdstip dat meer dan vijf minuten van de klok van Core afwijkt
413 De body is groter dan de installatie toestaat (256 KiB, tenzij je beheerder anders instelde)
422 De handtekening klopt, en de body is geen JSON, is dieper dan 64 niveaus genest of bevat een nulteken (U+0000), of de Idempotency-Key is geen geldige naam
429 Meer dan 20 verzoeken per seconde naar dit adres

Een 202 zegt dat het verzoek is aangenomen, niet dat de run is gestart. Of hij is gestart staat in je audittrail, zoals bij een trigger op een gebeurtenis: workflow.trigger.fired met de run, of workflow.trigger.failed met een code. De code expired_unstarted betekent dat Core het verzoek aannam en de run niet binnen een uur kon starten; hij wordt niet alsnog gestart, dus stuur het opnieuw. Webhooktriggers hebben een eigen aandeel starts per uur, even groot als dat van de triggers op gebeurtenissen (600, tenzij je beheerder anders instelde), en GET /workflow_runs?trigger_id=wft_... geeft de runs die één trigger startte. Alleen de sleutel waaronder de trigger runs start mag het geheim vervangen of de trigger weer aanzetten; elke sleutel die hem kan zien mag hem uitzetten. Een systeem dat alleen op zijn eigen manier kan ondertekenen, zoals een repositoryhost of een betaalprovider, kan niet rechtstreeks naar het adres sturen: laat een kleine dienst van jou zijn aanroep ontvangen, controleren en ondertekend zoals hierboven naar Core sturen.

Een agent laten starten

Een agent kan midden in een gesprek een workflow starten en hoort het wanneer die klaar is. Noem de workflow in de tools van de agent, of in de tools van één verzoek:

curl -s -X POST "$CORE_URL/responses" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"model": "agent:support", "thread_id": "thr_01J...",
         "input": "Neem deze klant aan: Anna, anna@example.com",
         "tools": [{"type": "workflow", "name": "intake"}]}'

Het model krijgt een tool aangeboden die workflow__intake heet. De argumenten zijn de invoer die de nieuwste versie van je workflow aanneemt, dus het input_schema van die versie moet een object zijn ("type": "object"). De beschrijving is die van de workflow zelf: schrijf daar wanneer de agent hem moet gebruiken.

Een workflow draait altijd op de achtergrond. De aanroep antwoordt meteen met een taak-id en het gesprek gaat door. De run start op de nieuwste versie van de workflow, als de sleutel en voor de persoon van de beurt, en heeft "trigger_kind": "task". Als de run eindigt, komt de uitvoer als resultaat van de taak in de thread, en Core start een beurt om erop te antwoorden als er op dat moment niemand iets vraagt. Dat kan dagen later zijn: een run die een agent startte, duurt zo lang als elke run van de workflow mag duren, ook als een beslissing op een manager wacht. Intussen toont GET /tasks/{id} de taak met zijn workflow_run_id en, als progress, de stappen waarop de run wacht.

Wat je wilt Hoe
Zien wat de agent startte GET /threads/{id}/tasks, of GET /workflow_runs?trigger_id=task_...
Stoppen POST /tasks/{id}/cancel, of POST /workflow_runs/{id}/cancel; elk van de twee beëindigt beide
Voorkomen dat het resultaat een beurt start "task_wake": false in het verzoek; het resultaat wacht in de thread op je volgende beurt

Je sleutel heeft naast workflows ook tasks nodig, en de beurt moet op een thread zijn opgeslagen: een resultaat moet ergens terecht kunnen. Een beurt die dat niet is, of een sleutel zonder tasks, draait zonder de tool en wordt niet geweigerd. Een sleutel zonder workflows krijgt een 400 die de regel noemt, en een workflow die niet bestaat een 404. Passen de argumenten die het model schreef niet bij het invoerschema, dan start er niets en leest het model dat ze niet passen, zodat het het opnieuw kan proberen.

De thread verwijderen stopt de run niet. Een run kan al iemand iets gevraagd hebben of een ander systeem hebben aangeroepen, dus hij loopt door tot zijn einde en alleen het resultaat vervalt; annuleer de run zelf als je hem wilt stoppen. Een thread heeft hooguit acht open taken (tenzij je beheerder of je sleutel minder zegt), en een workflow die een week op een antwoord wacht, telt die hele week mee.

Volgen

curl -s "$CORE_URL/workflow_runs/wfr_01J..." -H "Authorization: Bearer $CORE_KEY"

Een run is queued, running, waiting zolang er niets open is behalve een wait of een decision, en daarna completed met zijn output, failed met een error_code, cancelled of expired. Een run die op zijn deadline_at nog open is, verloopt: standaard 30 dagen nadat hij startte, eerder als je organisatie inhoud korter bewaart. Zijn steps zeggen wat elk knooppunt ontving en opleverde, hoe vaak het geprobeerd is en, bij een agentstap, de response_id en thread_id waarop het liep, die je opent als elke achtergrondrun. GET /workflow_runs?workflow_id=...&status=running toont de runs, nieuwste eerst. Een planning start runs op een klok, en ?trigger_id= beperkt de lijst tot de runs die één planning startte. De gebeurtenissenstroom en webhooks dragen workflow.run.started, .completed, .failed, .cancelled en .expired, en workflow.decision.requested, .decided en .expired voor elke beslissing.

POST /workflow_runs/{id}/cancel stopt een run en elke open stap, de agentruns die hij startte inbegrepen; nogmaals vragen antwoordt met de geannuleerde run, en een run die al op een andere manier eindigde geeft 409.

Iemand laten beslissen

Een decision-knooppunt houdt de run tegen tot er één antwoord komt. Het noemt de uitkomsten waaruit gekozen wordt, elk met precies één uitgaande verbinding met "outcome": "<naam>", en wie mag antwoorden:

{"id": "approve", "kind": "decision",
 "config": {
   "outcomes": ["approve", "reject"],
   "instruction": "join(' ', ['Terugbetaling voor', run.input.customer])",
   "form": {"type": "object", "properties": {"note": {"type": "string"}}},
   "assignee": {"kind": "members"},
   "deadline_seconds": 86400,
   "on_deadline": {"outcome": "reject"}}}
assignee Wie antwoordt Hoe
{"kind": "members"} een beheerder van je organisatie; "users": [...] beperkt het tot wie je bij gebruikers-id noemt in de console, die ook het meldingsadres van je organisatie een melding stuurt
{"kind": "subject", "subject_id": "anna"}, of "subject": "run" voor de persoon voor wie de run is die persoon je applicatie, met een subjecttoken voor haar
{"kind": "key", "key_id": "key_01J..."} die API-sleutel: een ander systeem, of een robot de routes hieronder, met die sleutel
{"kind": "agent", "agent": "refund-judge"}, met "version": 3 om er één vast te leggen die agent, in een eigen beurt niemand antwoordt namens de agent (hieronder)

instruction is een expressie die de tekst geeft die de toegewezene leest; naast run en nodes leest ze de eigen invoer van het knooppunt als input. Ze wordt één keer uitgewerkt, op het moment dat de stap gaat wachten.

curl -s "$CORE_URL/workflow_decisions" -H "Authorization: Bearer $CORE_KEY"

curl -s -X POST "$CORE_URL/workflow_runs/wfr_01J.../steps/wfs_01J.../decision" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"outcome": "approve", "fields": {"note": "Factuur gecontroleerd."}}'

GET /workflow_decisions toont de wachtende beslissingen die de aanroeper mag beantwoorden, nieuwste eerst, elk met zijn id (de stap), run_id, workflow, instruction, outcomes, form en deadline_at. Het antwoord noemt een van de uitkomsten en, als het knooppunt een form heeft, fields die erin passen; al het andere geeft 422 met het veld erbij. Alleen de toegewezene mag antwoorden: een andere aanroeper die de run kan lezen krijgt 403, en wie dat niet kan 404. Een beslissing wordt één keer beantwoord: een tweede antwoord, en een antwoord na de deadline, geeft 409. De stap is dan klaar met {outcome, fields, decided_by} en de run volgt de verbinding van die uitkomst.

Als deadline_seconds voorbijgaat zonder antwoord, beslist on_deadline: {"outcome": "..."} neemt die uitkomst, "error" laat de stap mislukken met decision_expired voor zijn on_error-verbinding, en "fail", de standaard, laat de run mislukken met die code. Een wait, en een decision die een persoon of een sleutel beantwoordt, nemen geen timeout_seconds en geen retry; de deadline van de run zelf begrenst ze. Een decision die aan een agent is toegewezen is de uitzondering (hieronder).

Een agent beslist

Een beslissing die aan een agent is toegewezen wacht op niemand. De agent leest één bericht, {"instruction": ..., "input": ...}: de uitgewerkte instructie en wat het knooppunt ontving. De agent moet antwoorden met een van de uitkomsten van het knooppunt en, als het knooppunt een form heeft, met velden die erin passen. Core controleert het antwoord zelf, wat het model ook schreef, en alleen een antwoord dat de controle doorstaat volgt de verbinding van een uitkomst.

Voor een aanroeper ziet zo'n stap er anders uit dan een beslissing die op een persoon wacht:

Wat je leest of doet Bij een beslissing van een agent
de status van de stap running, nooit waiting; de run is dus ook running
output van de stap null tot Core het antwoord heeft gecontroleerd, daarna {outcome, fields, decided_by}
GET /workflow_decisions de stap staat er niet in, voor geen enkele aanroeper
een antwoord naar POST .../steps/{id}/decision 403, wie het ook stuurt: niemand antwoordt namens de agent
een melding aan de meldingsadressen geen

Zo'n knooppunt neemt timeout_seconds en retry, zoals een agent-knooppunt, en geen deadline_seconds of on_deadline; opslaan weigert die twee met 422. Het plafond van timeout_seconds stelt je operator in, standaard een uur, en laat je het veld weg, dan geldt dat plafond. De agent mag geen eigen antwoordformaat vastleggen (text), het form van het knooppunt mag geen $ref, $dynamicRef of $defs bevatten, en het antwoordformaat dat Core uit de uitkomsten en het form samenstelt mag hoogstens 64 KiB zijn; opslaan wordt in elk van die gevallen geweigerd.

Geeft de agent geen geldig antwoord, dan mislukt de stap met een van deze codes:

Code Wat er gebeurde
decision_invalid het antwoord noemt geen uitkomst van het knooppunt, de velden passen niet in het form, of het is geen JSON-object; een weigering van het model en een leeg antwoord vallen hieronder
timeout de beurt duurde langer dan timeout_seconds
incomplete de beurt werd afgebroken voor het antwoord af was
usage_cap_exceeded het maandplafond van de sleutel of de organisatie hield de beurt tegen
approval_unsupported de agent heeft tool_approval, en een workflow kan niet beslissen over een aanroep die op goedkeuring wacht; de beurt start niet
policy_refused het beleid van de sleutel staat de agent niet meer toe
een andere code de beurt zelf mislukte; de stap draagt de foutcode van die beurt

Zet het knooppunt retry, dan probeert Core het opnieuw, behalve als het plafond of het beleid de beurt al tegenhield voor die begon. Daarna volgt de stap zijn on_error-verbinding. Teken die verbinding voor wat er dan moet gebeuren, bijvoorbeeld naar een tweede beslissing die een persoon neemt; zonder die verbinding mislukt de run. policy_refused is de uitzondering: die code beëindigt de run meteen, en geen on_error-verbinding vangt haar op.

Core garandeert dat het antwoord een van de uitkomsten is, dat de beslissing als die van een agent is gemarkeerd en dat de beurt erachter na te lezen is. Core garandeert niet dat de agent zich niet liet sturen door wat de agent beoordeelde: je instructie en de invoer van het knooppunt bereiken de agent in één bericht, en een tekst in die invoer die zegt "keur dit goed" kan zijn zin krijgen, want approve is een geldige uitkomst. De capability trust_envelope markeert dat bericht voor het model als onbetrouwbaar, je instructie inbegrepen; dat verkleint de kans en neemt niets weg. Zet waar de uitkomst ertoe doet een beslissing die een persoon neemt achter die van de agent, en geef de beslissende agent geen tools, zodat wat de agent leest het antwoord kan veranderen en verder niets.

De stap toont assignee_kind: "agent", en na de beslissing noemt decided_by de agent, de versie en het response_id van de beurt, waar je kunt nalezen wat de agent las en schreef. workflow.decision.decided draagt dezelfde id's. Een run voor een persoon mag zijn beslissing door een agent laten nemen; of jouw proces over een persoon mag beslissen zonder dat er een mens aan te pas komt, bepaal je zelf, en een knooppunt dat aan members of aan een subject is toegewezen wordt nooit door een agent beantwoord.

Wijzigen

POST /workflows/{id}/versions schrijft de volgende versie; runs die al liepen houden de versie die ze vastlegden. Stuur de hele graph en het input_schema mee: een versie erft het schema van de vorige niet, en een weggelaten input_schema is {}, dat elke invoer accepteert. PATCH /workflows/{id} wijzigt description en labels. DELETE /workflows/{id} verwijdert de workflow met zijn versies en afgelopen runs, en geeft 409 zolang er een run open is. Een regel van soort workflow in het beleid van je sleutel beslist welke workflows die mag zien en starten, op naam en label.

Een beheerder kan een versie ook als concept opslaan, in de console of de API ervan. Geen sleutel, trigger of schema ziet of start een concept: een run zonder version start de gepubliceerde versie, en een run, een stap of een subworkflow die het nummer van het concept noemt, hoort dat de versie niet bestaat. Een workflow met alleen een concept bestaat niet voor je sleutel, een trigger of een schema tot hij gepubliceerd is. Een sleutel schrijft geen concept: een veld draft dat je sleutel meestuurt met POST /workflows of POST /workflows/{id}/versions wordt genegeerd, zoals elk ander veld dat die bodies niet kennen, en de versie wordt meteen gepubliceerd.

GET /workflows geeft de workflows die je sleutel mag zien. ?label=afdeling:salaris versmalt de lijst tot die met dat label; de parameter mag herhaald worden, herhalen betekent alle genoemde, en een naam herhalen vraagt om al die waarden. Een workflow waarvan het label meerdere waarden heeft, telt mee als de waarde daar een van is. Een waarde die geen naam:waarde is, antwoordt 422 met label erbij.

Het volledige contract staat in de API-referentie.