Give an agent its input
This guide gives an agent its input, from a direct run or a flow step, fills its instructions with parameters, and reads what a turn returns.
An agent runs one turn at a time. Run directly, a turn's input is the user's message and a few optional fields. Run as a step of a flow, the agent gets the step's input instead.
Before you start
Section titled “Before you start”- A pack on a runtime such as
kindgi dev, with Kindgi 0.1's CLI, and the agents and tools from Write an agent. - A registered model for answers worth reading. Without one,
dev-echoanswers. - For the app's half: Node 22.12 or later, Python 3.11 or later, or Java
17 or later, with Kindgi 0.1's client, and
KINDGI_API_URLandKINDGI_API_TOKENset.
A direct run
Section titled “A direct run”A direct run gives the agent a message:
pnpm exec kindgi runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}'uv run kindgi runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}'./kindgiw runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}'./kindgiw runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}'--agent is the agent, and --input the turn's input, as JSON. It has these
fields:
| Field | |
|---|---|
userMessage |
The turn's message. Required. |
conversationId |
Continue a conversation: see Hold a conversation. Without it, the turn opens a new one. |
participantId |
Who the conversation is with, kept on the conversation the turn opens. |
parameters |
Values for the agent's parameters, as Parameters describes: strings, numbers or booleans. |
Without a userMessage, the run is refused:
Error [invalid-request]: An agent run expects input { userMessage: string, conversationId?: string, participantId?: string, parameters?: { [name]: string | number | boolean } }.From your app, the same input goes in runs.start. Ask your coding agent:
"I want my app to run an agent with a customer's message, and print its
answer." For reference, the code looks like this. It runs the
acme.reply-drafter agent from the next section:
import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
const run = await kindgi.runs.start({ agent: 'acme.reply-drafter', input: { userMessage: 'My parcel is a week late.', parameters: { tone: 'formal', signature: 'Sam at Acme' }, },});const turn = run.output as { response: { content: string } };console.log(turn.response.content);from kindgi.client import Kindgi
kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi devrun = kindgi.runs.start( agent="acme.reply-drafter", input={ "userMessage": "My parcel is a week late.", "parameters": {"tone": "formal", "signature": "Sam at Acme"}, },)if run.output is None: # a run that didn't complete has no output raise SystemExit(f"Run {run.id} ended {run.status}: {run.failure_message}")print(run.output["response"]["content"])package com.acme.app;
import com.kindgi.client.Kindgi;import com.kindgi.client.models.Run;import com.kindgi.client.models.StartRunBody;import java.util.Map;
public final class Drafter { public static void main(String[] args) { Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
Run run = kindgi.runs().start(StartRunBody.WithAgent.builder() .agent("acme.reply-drafter") .input(Map.of( "userMessage", "My parcel is a week late.", "parameters", Map.of("tone", "formal", "signature", "Sam at Acme"))) .build()); if (!(run.output() instanceof Map<?, ?> output)) { // a run that didn't complete has no output throw new IllegalStateException("Run " + run.id() + " ended " + run.status() + ": " + run.failureMessage()); } System.out.println(((Map<?, ?>) output.get("response")).get("content")); }}package acme.app
import com.kindgi.client.Kindgiimport com.kindgi.client.models.StartRunBodyimport scala.jdk.CollectionConverters._
object Drafter { def main(args: Array[String]): Unit = { val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
val run = kindgi.runs().start(StartRunBody.WithAgent.builder() .agent("acme.reply-drafter") .input(Map[String, AnyRef]( "userMessage" -> "My parcel is a week late.", "parameters" -> Map("tone" -> "formal", "signature" -> "Sam at Acme").asJava).asJava) .build()) run.output match { case output: java.util.Map[_, _] => val response = output.get("response").asInstanceOf[java.util.Map[_, _]] println(response.get("content")) case _ => // a run that didn't complete has no output throw new IllegalStateException(s"Run ${run.id} ended ${run.status}: ${run.failureMessage}") } }}Each prints the agent's answer. A run waits for the turn to end. To get the
run back as soon as it exists, add --no-wait, or options: { wait: false }
in the clients. It comes back pending, and kindgi runs get <run-id> shows
it completed, with its output, once the turn ends.
Parameters
Section titled “Parameters”Parameters are your own variables in the agent's instructions. Declare each one, and the caller supplies the values. Ask your coding agent: "I want an agent that drafts replies to customers in the tone I ask for." Kindgi's skills in your pack tell it how (How it knows Kindgi). For reference, the agent looks like this:
import { defineAgent } from '@kindgi/sdk/define';
const defined = defineAgent({ id: 'acme.reply-drafter', version: '0.1.0', name: 'Reply drafter', instructions: [ 'You draft short replies to Acme customers, in a {{ tone }} tone.', 'Sign every reply as {{ signature }}.', ].join('\n'), parameters: [ { name: 'tone', type: 'string', description: 'friendly, formal, …' }, { name: 'signature', type: 'string', default: 'The Acme team' }, ], capabilities: [{ needs: [] }], tools: [], retrieval: [], guardrails: [],});
if (defined.kind === 'err') throw new Error(defined.error.message);
export default defined.value;from kindgi import Agent
reply_drafter = Agent( id="acme.reply-drafter", version="0.1.0", name="Reply drafter", instructions="\n".join([ "You draft short replies to Acme customers, in a {{ tone }} tone.", "Sign every reply as {{ signature }}.", ]), parameters=[ {"name": "tone", "type": "string", "description": "friendly, formal, …"}, {"name": "signature", "type": "string", "default": "The Acme team"}, ], capabilities=[{"needs": []}],)package acme.agents;
import com.kindgi.pack.Agent;import java.util.List;import java.util.Map;
public final class ReplyDrafter { public static final Agent AGENT = Agent.define("acme.reply-drafter") .version("0.1.0") .name("Reply drafter") .instructions(String.join("\n", "You draft short replies to Acme customers, in a {{ tone }} tone.", "Sign every reply as {{ signature }}.")) .set("parameters", List.of( Map.of("name", "tone", "type", "string", "description", "friendly, formal, …"), Map.of("name", "signature", "type", "string", "default", "The Acme team"))) .capability(Map.of("needs", List.of())) .build();
private ReplyDrafter() {}}package acme.agents
import com.kindgi.pack.scaladsl._
object ReplyDrafter { val agent: Agent = Agent("acme.reply-drafter") .version("0.1.0") .name("Reply drafter") .instructions(Seq( "You draft short replies to Acme customers, in a {{ tone }} tone.", "Sign every reply as {{ signature }}.").mkString("\n")) .set("parameters", Seq( Map("name" -> "tone", "type" -> "string", "description" -> "friendly, formal, …"), Map("name" -> "signature", "type" -> "string", "default" -> "The Acme team"))) .capability(Map("needs" -> Seq())) .build()}A parameter is string, number, boolean or date. Give it a default
to make it optional: here tone is required, and signature falls back to
its default. required: false without a default isn't enough: the
instructions still name the variable, and rendering fails without a value.
Run it with a tone:
pnpm exec kindgi runs start --agent=acme.reply-drafter --input='{"userMessage":"My parcel is a week late.","parameters":{"tone":"friendly"}}'uv run kindgi runs start --agent=acme.reply-drafter --input='{"userMessage":"My parcel is a week late.","parameters":{"tone":"friendly"}}'./kindgiw runs start --agent=acme.reply-drafter --input='{"userMessage":"My parcel is a week late.","parameters":{"tone":"friendly"}}'./kindgiw runs start --agent=acme.reply-drafter --input='{"userMessage":"My parcel is a week late.","parameters":{"tone":"friendly"}}'parameters in --input gives the parameters' values. It prints the run:
{ … "status": "completed", … "output": { … "response": { "role": "agent", "content": "Hi there!\n\nI'm sorry to hear your parcel is running late – we know how frustrating that can be! … Thanks for your patience!\n\nThe Acme team", … }, … }}The journal shows what the model was told, in the render-prompt step. Read
it with kindgi runs journal <run-id>, where <run-id> is the run's id:
You draft short replies to Acme customers, in a friendly tone.Sign every reply as The Acme team.A required parameter with no value fails the turn before the model is called:
Error [model-invocation-failed]: Prompt render failed: Required parameter missing: toneIn a flow step
Section titled “In a flow step”A flow runs an agent as a step: one turn, as a child run of the flow's run.
The step's inputMapping builds the agent's input, and config.parameters
fills its parameters. Ask your coding agent: "I want a flow that looks an
order up, then has an agent write the customer an update about it." For
reference, the flow looks like this. It looks the order up with
acme.lookup-order, from Write an agent.
Then it has an agent write the customer an update:
import { defineFlow } from '@kindgi/sdk/define';
const defined = defineFlow({ id: 'acme.notify-customer', version: '0.1.0', name: 'Notify a customer', nodes: [ { id: 'lookup', kind: 'tool', ref: 'acme.lookup-order', inputMapping: { orderId: { path: 'runInput.orderId' } }, }, { id: 'update', kind: 'agent', ref: 'acme.order-update', inputMapping: { order: { path: 'nodeOutputs.lookup' } }, config: { parameters: { tone: 'friendly' } }, }, ], edges: [ { id: 'e-start', from: '$start', to: 'lookup' }, { id: 'e-update', from: 'lookup', to: 'update' }, { id: 'e-end', from: 'update', to: '$end' }, ], output: { mapping: { update: { path: 'nodeOutputs.update.text' } } },});
if (defined.kind === 'err') throw new Error(defined.error.message);
export default defined.value;from kindgi import Flow
from ..agents.order_update import order_update
notify_customer = Flow( id="acme.notify-customer", version="0.1.0", name="Notify a customer", nodes=[ { "id": "lookup", "kind": "tool", "ref": "acme.lookup-order", "inputMapping": {"orderId": {"path": "runInput.orderId"}}, }, { "id": "update", "kind": "agent", "ref": order_update, "inputMapping": {"order": {"path": "nodeOutputs.lookup"}}, "config": {"parameters": {"tone": "friendly"}}, }, ], edges=[ {"id": "e-start", "from": "$start", "to": "lookup"}, {"id": "e-update", "from": "lookup", "to": "update"}, {"id": "e-end", "from": "update", "to": "$end"}, ], output={"mapping": {"update": {"path": "nodeOutputs.update.text"}}},)package acme.flows;
import acme.agents.OrderUpdate;import acme.tools.LookupOrder;import com.kindgi.pack.Flow;import java.util.Map;
public final class NotifyCustomer { public static final Flow FLOW = Flow.define("acme.notify-customer") .version("0.1.0") .set("name", "Notify a customer") .node(Map.of( "id", "lookup", "kind", "tool", "ref", LookupOrder.TOOL, "inputMapping", Map.of("orderId", Map.of("path", "runInput.orderId")))) .node(Map.of( "id", "update", "kind", "agent", "ref", OrderUpdate.AGENT, "inputMapping", Map.of("order", Map.of("path", "nodeOutputs.lookup")), "config", Map.of("parameters", Map.of("tone", "friendly")))) .edge("e-start", "$start", "lookup") .edge("e-update", "lookup", "update") .edge("e-end", "update", "$end") .set("output", Map.of("mapping", Map.of("update", Map.of("path", "nodeOutputs.update.text")))) .build();
private NotifyCustomer() {}}package acme.flows
import acme.agents.OrderUpdateimport acme.tools.LookupOrderimport com.kindgi.pack.scaladsl._
object NotifyCustomer { val flow: Flow = Flow("acme.notify-customer") .version("0.1.0") .set("name", "Notify a customer") .node(Map( "id" -> "lookup", "kind" -> "tool", "ref" -> LookupOrder.tool, "inputMapping" -> Map("orderId" -> Map("path" -> "runInput.orderId")))) .node(Map( "id" -> "update", "kind" -> "agent", "ref" -> OrderUpdate.agent, "inputMapping" -> Map("order" -> Map("path" -> "nodeOutputs.lookup")), "config" -> Map("parameters" -> Map("tone" -> "friendly")))) .edge("e-start", "$start", "lookup") .edge("e-update", "lookup", "update") .edge("e-end", "update", "$end") .set("output", Map("mapping" -> Map("update" -> Map("path" -> "nodeOutputs.update.text")))) .build()}The agent gets the step's input two ways:
- as
{{ input }}in its instructions, so it can use the fields; - as its user message: the input as JSON.
For reference, the agent the flow runs looks like this:
import { defineAgent } from '@kindgi/sdk/define';
const defined = defineAgent({ id: 'acme.order-update', version: '0.1.0', name: 'Order update', instructions: [ 'Write a two-sentence update for the customer about order {{ input.order.orderId }}.', 'Its status is {{ input.order.status }}{% if input.order.eta %}, expected on {{ input.order.eta }}{% endif %}.', 'Use a {{ tone }} tone.', ].join('\n'), parameters: [{ name: 'tone', type: 'string' }], capabilities: [{ needs: [] }], tools: [], retrieval: [], guardrails: [],});
if (defined.kind === 'err') throw new Error(defined.error.message);
export default defined.value;from kindgi import Agent
order_update = Agent( id="acme.order-update", version="0.1.0", name="Order update", instructions="\n".join([ "Write a two-sentence update for the customer about order {{ input.order.orderId }}.", "Its status is {{ input.order.status }}" "{% if input.order.eta %}, expected on {{ input.order.eta }}{% endif %}.", "Use a {{ tone }} tone.", ]), parameters=[{"name": "tone", "type": "string"}], capabilities=[{"needs": []}],)package acme.agents;
import com.kindgi.pack.Agent;import java.util.List;import java.util.Map;
public final class OrderUpdate { public static final Agent AGENT = Agent.define("acme.order-update") .version("0.1.0") .name("Order update") .instructions(String.join("\n", "Write a two-sentence update for the customer about order {{ input.order.orderId }}.", "Its status is {{ input.order.status }}{% if input.order.eta %}, expected on {{ input.order.eta }}{% endif %}.", "Use a {{ tone }} tone.")) .set("parameters", List.of(Map.of("name", "tone", "type", "string"))) .capability(Map.of("needs", List.of())) .build();
private OrderUpdate() {}}package acme.agents
import com.kindgi.pack.scaladsl._
object OrderUpdate { val agent: Agent = Agent("acme.order-update") .version("0.1.0") .name("Order update") .instructions(Seq( "Write a two-sentence update for the customer about order {{ input.order.orderId }}.", "Its status is {{ input.order.status }}{% if input.order.eta %}, expected on {{ input.order.eta }}{% endif %}.", "Use a {{ tone }} tone.").mkString("\n")) .set("parameters", Seq(Map("name" -> "tone", "type" -> "string"))) .capability(Map("needs" -> Seq())) .build()}Run the flow:
pnpm exec kindgi runs start --flow=acme.notify-customer --input='{"orderId":"A-1001"}'uv run kindgi runs start --flow=acme.notify-customer --input='{"orderId":"A-1001"}'./kindgiw runs start --flow=acme.notify-customer --input='{"orderId":"A-1001"}'./kindgiw runs start --flow=acme.notify-customer --input='{"orderId":"A-1001"}'--flow is the flow, and --input its input. It prints the run, with the
agent's update as the flow's output:
{ … "flowId": "acme.notify-customer", … "status": "completed", … "output": { "update": "Great news! Your order A-1001 is on its way and should arrive by October 6th, 2026. Thanks for your patience, and we can't wait for you to receive it!" }}In the agent's turn, the instructions rendered as:
Write a two-sentence update for the customer about order A-1001.Its status is shipped, expected on 2026-10-06.Use a friendly tone.Its user message was the step's input:
{ "order": { "orderId": "A-1001", "status": "shipped", "eta": "2026-10-06" }}The step's output, which later steps and the flow's output read as
nodeOutputs.update.<field>:
{ "text": "Great news! Your order A-1001 is on its way …", "runId": "c2cd458f-3364-4c0d-aefc-e6a7765f96d2", "usage": { "steps": 1, "durationMs": 1212, "promptTokens": 90, "totalCostUsd": 0.00031999999999999997, "completionTokens": 46 }, "violations": [], "conversationId": "1cf8b177-7f18-4513-b3fe-178f8aea6738"}text is the answer. An agent with a typed answer also has output, as
Give an agent a typed answer shows.
What a turn returns
Section titled “What a turn returns”A turn's result is the run's output:
| Field | |
|---|---|
response.content |
The answer, as text. |
output |
The typed answer, for an agent with an output schema. |
conversationId, turnNumber |
The conversation and the turn's number in it. |
usage |
steps, the model calls, then promptTokens, completionTokens, totalCostUsd and durationMs. |
provider |
The provider id and model that answered. |
appended |
The messages the turn added to the conversation: the user message, tool calls and their results, the answer. |
warnings |
For example fallback-provider and dev-echo-not-a-model, when dev-echo answered. |
violations |
Guardrail results. |
provenance |
The turn's provenance record, as Trace an answer and its cost describes. |
In the TypeScript client, run.output is unknown: cast it to the fields you
read. In the Python client, it's a dict.