Fill a case per client
By the end of this tutorial you will have client onboarding modelled as a playbook: one case per client, which an agent fills by asking the client for what the case still misses, drafting the welcome email the moment the profile is complete and recomputing only the steps a correction reaches, proven by a seat-count change made in conversation that rewrites the order and leaves the email untouched.
Playbook, workflow or orchestration? An orchestration runs forward once and ends. A workflow holds a task in one state at a time. A case keeps a value per field for as long as the client lives, and answers what is still missing before a step can be reached.
Eleven steps:
- Create the writer agent.
- Declare the playbook.
- Open a case.
- Ask what is missing.
- Fill the profile — the writer drafts.
- Give an agent this case.
- Create the onboarding agent.
- Open the client's session.
- The client picks a plan.
- The client corrects the seat count.
- Count the drafts — the proof.
Every call is shown for all three clients. The ids in the responses, and the agents' wording, are examples — copy the ids your own calls return.
Prerequisites
- A credential exported as
NATURALI_TOKEN, a project id asPROJECTand a working AI provider id asPROVIDER— from Your first agent generation. - A client set up as in that tutorial.
export NATURALI_TOKEN=nat_sk_...
export PROJECT=proj_V1StGXR8Z5jdHi6B
export PROVIDER=aip_V1StGXR8Z5jdHi6B
The welcome email, each conversation turn and each tool call inside a turn count as runs against your plan and draw their managed tokens on the balance. The writes that can run a node are refused while the project owes for usage already served — see A negative balance stops what a case would run.
1. Create the writer agent
The agent the playbook calls to draft the email. It receives the node's inputs as one JSON object.
- CLI
- SDK
- curl
naturali create-agent \
--project-id "$PROJECT" \
--ai-provider-id "$PROVIDER" \
--name welcome-writer \
--instructions "You write the welcome email we send a new business client. You receive a JSON object: company is the client's company and contact is the first name of the person there. In three sentences addressed to the contact, thank them for choosing us, say we are setting up the account for their company, and say a specialist will reach out within one business day. Reply with only the email body."
const { data: writer } = await naturali.agents.createAgent({
path: { project_id: process.env.PROJECT! },
body: {
ai_provider_id: process.env.PROVIDER!,
name: 'welcome-writer',
instructions:
"You write the welcome email we send a new business client. You receive a JSON object: company is the client's company and contact is the first name of the person there. In three sentences addressed to the contact, thank them for choosing us, say we are setting up the account for their company, and say a specialist will reach out within one business day. Reply with only the email body.",
},
});
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/agents" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"ai_provider_id\": \"$PROVIDER\",
\"name\": \"welcome-writer\",
\"instructions\": \"You write the welcome email we send a new business client. You receive a JSON object: company is the client's company and contact is the first name of the person there. In three sentences addressed to the contact, thank them for choosing us, say we are setting up the account for their company, and say a specialist will reach out within one business day. Reply with only the email body.\"
}"
{
"id": "agent_mS7xw2C47a9HGsUJ",
"name": "welcome-writer",
"ai_provider_id": "aip_CLcDNRe8GpP1pBNs",
"model": null,
"version": 1
}
export WRITER=agent_mS7xw2C47a9HGsUJ
2. Declare the playbook
Four nodes. Two are filled by a person, two are goals (final) computed from
them:
profileandplanarecollectnodes withmanualinputs: a person writes them, and acollectnode's output is its inputs. Each input'sschemarefuses a bad value and itsdescriptionis what step 4 asks for.welcomeis anexecutenode: it readscompanyandcontactfromprofile's output and runs the writer agent on them.ordercollects what billing needs from both: the company fromprofile, the seats and tier fromplan.
The source of each input is an edge: welcome reads only profile, so
nothing written to plan can reach it.
DEFINITION=$(cat <<EOF
{
"nodes": [
{ "id": "profile", "title": "Client profile", "kind": "collect",
"inputs": [
{ "name": "company", "schema": { "type": "string", "minLength": 1 }, "required": true,
"source": "manual", "description": "The client's company name" },
{ "name": "contact", "schema": { "type": "string", "minLength": 1 }, "required": true,
"source": "manual", "description": "First name of the person we talk to" }
] },
{ "id": "plan", "title": "Plan", "kind": "collect",
"inputs": [
{ "name": "seats", "schema": { "type": "integer", "minimum": 1 }, "required": true,
"source": "manual", "description": "How many people will use the account" },
{ "name": "tier", "schema": { "type": "string", "enum": ["starter", "growth"] }, "required": true,
"source": "manual", "description": "starter or growth" }
] },
{ "id": "welcome", "title": "Welcome email", "kind": "execute", "final": true,
"executor": { "type": "agent", "id": "$WRITER" },
"inputs": [
{ "name": "company", "schema": { "type": "string" }, "required": true,
"source": { "node_id": "profile", "field": "output.company" } },
{ "name": "contact", "schema": { "type": "string" }, "required": true,
"source": { "node_id": "profile", "field": "output.contact" } }
] },
{ "id": "order", "title": "Order for billing", "kind": "collect", "final": true,
"inputs": [
{ "name": "company", "schema": { "type": "string" }, "required": true,
"source": { "node_id": "profile", "field": "output.company" } },
{ "name": "seats", "schema": { "type": "integer" }, "required": true,
"source": { "node_id": "plan", "field": "output.seats" } },
{ "name": "tier", "schema": { "type": "string" }, "required": true,
"source": { "node_id": "plan", "field": "output.tier" } }
] }
]
}
EOF
)
- CLI
- SDK
- curl
naturali create-playbook \
--project-id "$PROJECT" \
--name client-onboarding \
--definition "$DEFINITION"
const manual = (description: string, schema: object) => {
return { schema, required: true, source: 'manual' as const, description };
};
const from = (nodeId: string, field: string, type: string) => {
return {
schema: { type },
required: true,
source: { node_id: nodeId, field },
};
};
const { data: playbook } = await naturali.playbooks.createPlaybook({
path: { project_id: process.env.PROJECT! },
body: {
name: 'client-onboarding',
definition: {
nodes: [
{
id: 'profile',
title: 'Client profile',
kind: 'collect',
inputs: [
{
name: 'company',
...manual("The client's company name", {
type: 'string',
minLength: 1,
}),
},
{
name: 'contact',
...manual('First name of the person we talk to', {
type: 'string',
minLength: 1,
}),
},
],
},
{
id: 'plan',
title: 'Plan',
kind: 'collect',
inputs: [
{
name: 'seats',
...manual('How many people will use the account', {
type: 'integer',
minimum: 1,
}),
},
{
name: 'tier',
...manual('starter or growth', {
type: 'string',
enum: ['starter', 'growth'],
}),
},
],
},
{
id: 'welcome',
title: 'Welcome email',
kind: 'execute',
final: true,
executor: { type: 'agent', id: writer!.id },
inputs: [
{ name: 'company', ...from('profile', 'output.company', 'string') },
{ name: 'contact', ...from('profile', 'output.contact', 'string') },
],
},
{
id: 'order',
title: 'Order for billing',
kind: 'collect',
final: true,
inputs: [
{ name: 'company', ...from('profile', 'output.company', 'string') },
{ name: 'seats', ...from('plan', 'output.seats', 'integer') },
{ name: 'tier', ...from('plan', 'output.tier', 'string') },
],
},
],
},
},
});
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/playbooks" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"client-onboarding\",
\"definition\": $DEFINITION
}"
{
"id": "pbk_6z0YiJtaV2e8WT33",
"name": "client-onboarding",
"version": 1,
"definition": {
"nodes": [
{
"id": "profile",
"title": "Client profile",
"kind": "collect",
"final": false,
"inputs": ["…"]
},
{
"id": "plan",
"title": "Plan",
"kind": "collect",
"final": false,
"inputs": ["…"]
},
{
"id": "welcome",
"title": "Welcome email",
"kind": "execute",
"final": true,
"inputs": ["…"],
"executor": { "type": "agent", "id": "agent_mS7xw2C47a9HGsUJ" }
},
{
"id": "order",
"title": "Order for billing",
"kind": "collect",
"final": true,
"inputs": ["…"]
}
]
}
}
export PLAYBOOK=pbk_6z0YiJtaV2e8WT33
The agent runs as you, the author of version 1, with your permissions as they stand when it runs.
3. Open a case
A case is one client filled against one version of the playbook.
- CLI
- SDK
- curl
naturali create-playbook-case \
--project-id "$PROJECT" \
--playbook-id "$PLAYBOOK" \
--subject "Globex S.A."
const { data: playbookCase } = await naturali.playbooks.createPlaybookCase({
path: { project_id: process.env.PROJECT!, playbook_id: playbook!.id },
body: { subject: 'Globex S.A.' },
});
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/playbooks/$PLAYBOOK/cases" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "subject": "Globex S.A." }'
Every node is blocked: the two the person fills are missing their inputs,
and the two goals name them in blocked_by. The response, trimmed to each
node's state:
{
"id": "pbk_case_omhpw1B6BKiVrPM9",
"playbook_version": 1,
"subject": "Globex S.A.",
"revision": 0,
"automation_status": "idle",
"nodes": [
{
"id": "profile",
"state": "blocked",
"missing": ["company", "contact"],
"blocked_by": []
},
{
"id": "plan",
"state": "blocked",
"missing": ["seats", "tier"],
"blocked_by": []
},
{
"id": "welcome",
"state": "blocked",
"missing": [],
"blocked_by": ["profile"]
},
{
"id": "order",
"state": "blocked",
"missing": [],
"blocked_by": ["profile", "plan"]
}
]
}
export CASE=pbk_case_omhpw1B6BKiVrPM9
4. Ask what is missing
GET /v1/projects/{project_id}/playbook-cases/{case_id}/missing
answers, for one goal, every input still to be written — with the schema and
description an agent needs to ask for it. The agent of
step 7 makes this same call each turn.
- CLI
- SDK
- curl
naturali get-playbook-case-missing \
--project-id "$PROJECT" \
--case-id "$CASE" \
--node-id order
const { data: missing } = await naturali.playbooks.getPlaybookCaseMissing({
path: { project_id: process.env.PROJECT!, case_id: playbookCase!.id },
query: { node_id: 'order' },
});
curl -G "https://api.naturali.ai/v1/projects/$PROJECT/playbook-cases/$CASE/missing" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-d node_id=order
{
"case_id": "pbk_case_omhpw1B6BKiVrPM9",
"node_id": "order",
"waves": [
[
{
"node_id": "profile",
"input": "company",
"schema": { "type": "string", "minLength": 1 },
"description": "The client's company name",
"required": true
},
{
"node_id": "profile",
"input": "contact",
"schema": { "type": "string", "minLength": 1 },
"description": "First name of the person we talk to",
"required": true
},
{
"node_id": "plan",
"input": "seats",
"schema": { "type": "integer", "minimum": 1 },
"description": "How many people will use the account",
"required": true
},
{
"node_id": "plan",
"input": "tier",
"schema": { "type": "string", "enum": ["starter", "growth"] },
"description": "starter or growth",
"required": true
}
]
]
}
All four sit in one wave because nothing orders them: they can be filled in any order, by different people, days apart.
5. Fill the profile
Your sales team already knows the company and the contact, so write them
yourself.
POST /v1/projects/{project_id}/playbook-cases/{case_id}/inputs
writes a batch, all or none. Each value names the revision of the field it read
in expected_revision —
null for a field still unset.
wait: true answers once every node the write starts has settled.
- CLI
- SDK
- curl
naturali write-playbook-case-inputs \
--project-id "$PROJECT" \
--case-id "$CASE" \
--inputs '[
{ "node_id": "profile", "name": "company", "value": "Globex S.A.", "expected_revision": null },
{ "node_id": "profile", "name": "contact", "value": "Marina", "expected_revision": null }
]' \
--wait true
const { data: drafted } = await naturali.playbooks.writePlaybookCaseInputs({
path: { project_id: process.env.PROJECT!, case_id: playbookCase!.id },
body: {
inputs: [
{
node_id: 'profile',
name: 'company',
value: 'Globex S.A.',
expected_revision: null,
},
{
node_id: 'profile',
name: 'contact',
value: 'Marina',
expected_revision: null,
},
],
wait: true,
},
});
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/playbook-cases/$CASE/inputs" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "inputs": [
{ "node_id": "profile", "name": "company", "value": "Globex S.A.", "expected_revision": null },
{ "node_id": "profile", "name": "contact", "value": "Marina", "expected_revision": null }
], "wait": true }'
Nobody called the writer: completing profile made welcome runnable, and the
case ran it on its own. The response, trimmed to each node's state:
{
"revision": 4,
"automation_status": "idle",
"nodes": [
{
"id": "profile",
"state": "done",
"output": {
"value": { "company": "Globex S.A.", "contact": "Marina" },
"revision": 3
}
},
{
"id": "plan",
"state": "blocked",
"missing": ["seats", "tier"],
"output": null
},
{
"id": "welcome",
"state": "done",
"output": {
"value": "Dear Marina,\n\nThank you for choosing us to partner with Globex S.A. We are currently setting up your company's account in our system. A specialist will reach out to you within one business day to ensure everything is ready for you.",
"revision": 4
}
},
{ "id": "order", "state": "blocked", "blocked_by": ["plan"], "output": null }
]
}
The email is drafted while the plan is still open: welcome never needed it.
The plan is the client's to choose.
6. Give an agent this case
A naturali tool lets an
agent call this API's playbook operations. Pinning case_id to
{{context:case_id}} confines it to one case:
the model never sees the parameter, and each conversation supplies its own
client's case. Every action listed takes a case, so none reaches another one.
- CLI
- SDK
- curl
naturali create-tool \
--project-id "$PROJECT" \
--name this-case \
--type naturali \
--actions get-playbook-case \
--actions get-playbook-case-missing \
--actions write-playbook-case-inputs \
--preset-parameters '{ "case_id": "{{context:case_id}}" }'
const { data: tool } = await naturali.tools.createTool({
path: { project_id: process.env.PROJECT! },
body: {
name: 'this-case',
type: 'naturali',
actions: [
'get-playbook-case',
'get-playbook-case-missing',
'write-playbook-case-inputs',
],
preset_parameters: { case_id: '{{context:case_id}}' },
},
});
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/tools" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "this-case",
"type": "naturali",
"actions": ["get-playbook-case", "get-playbook-case-missing", "write-playbook-case-inputs"],
"preset_parameters": { "case_id": "{{context:case_id}}" }
}'
{
"id": "tool_tWmPlmy4qh7ldiDG",
"type": "naturali",
"name": "this-case",
"actions": [
"get-playbook-case",
"get-playbook-case-missing",
"write-playbook-case-inputs"
],
"preset_parameters": { "case_id": "{{context:case_id}}" }
}
export TOOL=tool_tWmPlmy4qh7ldiDG
7. Create the onboarding agent
The agent that talks to the client. Its instructions turn the case into the
conversation: ask for what missing lists, write what the client answers with
wait: true so the turn sees what the write started, and name the revision it
read when it changes a value.
The client sees only the agent's last message of a turn, so the instructions
also say to put everything in it. This step names claude-haiku-4.5, a
naturali model that calls tools reliably; on a provider
of your own, name one of its models that does.
- CLI
- SDK
- curl
naturali create-agent \
--project-id "$PROJECT" \
--ai-provider-id "$PROVIDER" \
--name onboarding-assistant \
--model claude-haiku-4.5 \
--tool-bindings "[{\"tool_id\":\"$TOOL\"}]" \
--instructions "You onboard a new business client in chat, through tools that work on this client's onboarding case. Every turn: 1) call get-playbook-case-missing with node_id order; 2) record every listed input the client's messages already answer with one write-playbook-case-inputs call, wait true; expected_revision is null for a field still unset, and for a field already set it is the revision get-playbook-case shows on that input; 3) reply. Never invent a value and never ask for one the client already gave. The client only sees your last message: say what you recorded and ask, in one short message, for what is still missing, using each input's description."
const { data: assistant } = await naturali.agents.createAgent({
path: { project_id: process.env.PROJECT! },
body: {
ai_provider_id: process.env.PROVIDER!,
name: 'onboarding-assistant',
model: 'claude-haiku-4.5',
tool_bindings: [{ tool_id: tool!.id }],
instructions:
"You onboard a new business client in chat, through tools that work on this client's onboarding case. Every turn: 1) call get-playbook-case-missing with node_id order; 2) record every listed input the client's messages already answer with one write-playbook-case-inputs call, wait true; expected_revision is null for a field still unset, and for a field already set it is the revision get-playbook-case shows on that input; 3) reply. Never invent a value and never ask for one the client already gave. The client only sees your last message: say what you recorded and ask, in one short message, for what is still missing, using each input's description.",
},
});
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/agents" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"ai_provider_id\": \"$PROVIDER\",
\"name\": \"onboarding-assistant\",
\"model\": \"claude-haiku-4.5\",
\"tool_bindings\": [{ \"tool_id\": \"$TOOL\" }],
\"instructions\": \"You onboard a new business client in chat, through tools that work on this client's onboarding case. Every turn: 1) call get-playbook-case-missing with node_id order; 2) record every listed input the client's messages already answer with one write-playbook-case-inputs call, wait true; expected_revision is null for a field still unset, and for a field already set it is the revision get-playbook-case shows on that input; 3) reply. Never invent a value and never ask for one the client already gave. The client only sees your last message: say what you recorded and ask, in one short message, for what is still missing, using each input's description.\"
}"
{
"id": "agent_DVPP84Fee7Y3CMtX",
"name": "onboarding-assistant",
"ai_provider_id": "aip_CLcDNRe8GpP1pBNs",
"model": "claude-haiku-4.5",
"tool_bindings": [{ "tool_id": "tool_tWmPlmy4qh7ldiDG" }],
"version": 1
}
export ASSISTANT=agent_DVPP84Fee7Y3CMtX
8. Open the client's session
A session is the conversation with one client. Its
tool_context carries the case every tool call of the session acts on — the
value the preset of step 6 reads. Your
application sets it when it opens the session; no read returns it.
- CLI
- SDK
- curl
naturali create-session \
--project-id "$PROJECT" \
--agent-id "$ASSISTANT" \
--tool-context "{\"case_id\":\"$CASE\"}"
const { data: session } = await naturali.sessions.createSession({
path: { project_id: process.env.PROJECT! },
body: {
agent_id: assistant!.id,
tool_context: { case_id: playbookCase!.id },
},
});
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/sessions" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d "{ \"agent_id\": \"$ASSISTANT\", \"tool_context\": { \"case_id\": \"$CASE\" } }"
{
"id": "sess_2vRXuNgmfS3CT0e7",
"agent_id": "agent_DVPP84Fee7Y3CMtX",
"conversation_id": "conv_SjgixjyBpyYLXk8x",
"status": "open",
"auto_generate": false
}
export SESSION=sess_2vRXuNgmfS3CT0e7
A turn without case_id in its context fails the tool call with
MISSING_TOOL_CONTEXT_KEY instead of reaching another case.
9. The client picks a plan
Add the client's message, then
generate the reply
with ?wait=true.
- CLI
- SDK
- curl
naturali add-session-message \
--project-id "$PROJECT" \
--session-id "$SESSION" \
--message 'Hi, Marina here. We want the growth plan for 12 people.'
naturali generate-session-response \
--project-id "$PROJECT" \
--session-id "$SESSION" \
--wait true
await naturali.sessions.addSessionMessage({
path: { project_id: process.env.PROJECT!, session_id: session!.id },
body: { message: 'Hi, Marina here. We want the growth plan for 12 people.' },
});
const { data: reply } = await naturali.sessions.generateSessionResponse({
path: { project_id: process.env.PROJECT!, session_id: session!.id },
query: { wait: true },
});
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/sessions/$SESSION/messages" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "message": "Hi, Marina here. We want the growth plan for 12 people." }'
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/sessions/$SESSION/generate?wait=true" \
-H "Authorization: Bearer $NATURALI_TOKEN"
The reply, trimmed:
{
"status": "completed",
"content": "Perfect! I've recorded your growth plan for 12 people. All set—we're processing your order now and a specialist will reach out within one business day to get everything ready for Globex S.A.",
"generation_id": "gen_2seG3c8JSLrsyFdy"
}
In one turn the agent read what was missing, found both answers in the message
and wrote them: plan and order are done, with seats at revision 5.
10. The client corrects the seat count
The client asks for 15 seats. The agent reads the revision seats holds and
writes the new value against it.
- CLI
- SDK
- curl
naturali add-session-message \
--project-id "$PROJECT" \
--session-id "$SESSION" \
--message 'Sorry, make it 15 seats.'
naturali generate-session-response \
--project-id "$PROJECT" \
--session-id "$SESSION" \
--wait true
await naturali.sessions.addSessionMessage({
path: { project_id: process.env.PROJECT!, session_id: session!.id },
body: { message: 'Sorry, make it 15 seats.' },
});
const { data: corrected } = await naturali.sessions.generateSessionResponse({
path: { project_id: process.env.PROJECT!, session_id: session!.id },
query: { wait: true },
});
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/sessions/$SESSION/messages" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "message": "Sorry, make it 15 seats." }'
curl -X POST "https://api.naturali.ai/v1/projects/$PROJECT/sessions/$SESSION/generate?wait=true" \
-H "Authorization: Bearer $NATURALI_TOKEN"
{
"status": "completed",
"content": "Done! I've updated your order to 15 seats on the growth plan for Globex S.A.",
"generation_id": "gen_TuhBgi40v1jzeeQJ"
}
A write of seats with "expected_revision": null answers
409 PLAYBOOK_INPUT_CONFLICT and writes nothing: the field is no longer unset.
That is why the instructions tell the agent to name the revision
get-playbook-case shows. The same rule stops two people, or a person and the
agent, correcting one field over each other.
11. Count the drafts
Read the case:
- CLI
- SDK
- curl
naturali get-playbook-case \
--project-id "$PROJECT" \
--case-id "$CASE"
const { data: current } = await naturali.playbooks.getPlaybookCase({
path: { project_id: process.env.PROJECT!, case_id: playbookCase!.id },
});
curl "https://api.naturali.ai/v1/projects/$PROJECT/playbook-cases/$CASE" \
-H "Authorization: Bearer $NATURALI_TOKEN"
{
"revision": 11,
"automation_status": "idle",
"nodes": [
{ "id": "profile", "state": "done" },
{
"id": "plan",
"state": "done",
"output": { "value": { "seats": 15, "tier": "growth" }, "revision": 10 }
},
{
"id": "welcome",
"state": "done",
"output": {
"value": "Dear Marina,\n\nThank you for choosing us to partner with Globex S.A.…",
"revision": 4
}
},
{
"id": "order",
"state": "done",
"output": {
"value": { "company": "Globex S.A.", "seats": 15, "tier": "growth" },
"revision": 11
}
}
]
}
order carries 15 seats. welcome still holds revision 4, the draft from
step 5.
GET /v1/projects/{project_id}/playbook-cases/{case_id}/executions
counts its runs:
- CLI
- SDK
- curl
naturali list-playbook-case-executions \
--project-id "$PROJECT" \
--case-id "$CASE" \
--node-id welcome
const { data: drafts } = await naturali.playbooks.listPlaybookCaseExecutions({
path: { project_id: process.env.PROJECT!, case_id: playbookCase!.id },
query: { node_id: 'welcome' },
});
console.log(drafts!.total);
curl -G "https://api.naturali.ai/v1/projects/$PROJECT/playbook-cases/$CASE/executions" \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-d node_id=welcome
{
"data": [
{
"id": "pbk_exe_vQj57ixVSf4xZbcj",
"node_id": "welcome",
"executor": { "type": "agent", "id": "agent_mS7xw2C47a9HGsUJ" },
"status": "completed",
"result": "current",
"run_id": "gen_V6BE4tOf15XExjEe",
"input": { "company": "Globex S.A.", "contact": "Marina" },
"output_revision": 4
}
],
"total": 1,
"limit": 50,
"offset": 0
}
That is the case, proven: one draft, made the moment the profile was complete,
and never repeated — the plan the client chose in conversation and its
correction reached order and nothing else.
Each value the agent wrote is
attributed to the turn that wrote it.
GET /v1/projects/{project_id}/playbook-cases/{case_id}/entries
with node_id=plan shows the correction as an input entry whose
on_behalf_of names the tool, the agent, the session and the generation:
{
"revision": 9,
"node_id": "plan",
"kind": "input",
"input": "seats",
"value": 15,
"on_behalf_of": {
"tool_id": "tool_tWmPlmy4qh7ldiDG",
"agent_id": "agent_DVPP84Fee7Y3CMtX",
"session_id": "sess_2vRXuNgmfS3CT0e7",
"generation_id": "gen_TuhBgi40v1jzeeQJ",
"actor_id": null
}
}
The entries you wrote in step 5 carry on_behalf_of: null.
A channel's sessions carry no case, so your application opens the session and relays the client's messages to it, turn by turn, as shown here.
What's next
- Correct the profile — write a new
contactandwelcomegoesstaleand is redrafted, because it reads that field. - Have a person sign off — a
confirmnode waits for someone to approve or reject its inputs before anything reading it runs; addconfirm-playbook-case-nodeto the tool'sactionsto let the agent record the client's answer. See Playbooks. - Render a goal as a file — a final node's
transformationsturn its output into a PDF, a spreadsheet or a document in the project's files; see Exports render a final node. - Change the process safely — editing a playbook leaves cases in flight on their version until you upgrade them; see Upgrading a case.