Skip to content
HaneDOCS日本語

Build apps

Embedding agents

How to write an agent definition, start AgentRuns from your app and get their results, and handle approvals for external actions.

This page explains how to embed agents in your app. An agent works within the purpose, input, tools, and output in its definition, and external actions are carried out by Hane only after approval. The concepts are covered in Agents.

apiVersion: agents/v1alpha1
kind: Agent
metadata:
name: vehicle-check
spec:
purpose: Look up a vehicle's model, inspection expiry, and odometer in the vehicle ledger by registration number
examples:
- When does the inspection for TEST 300 A 10-02 expire?
input:
type: object
required: [vehicle]
properties:
vehicle: { type: string, title: Registration number }
driver: llm
instructions: |-
1. Call data.find_record with type Vehicle and key set to the input vehicle.
2. If found, put the model, inspection_expiry, and odometer_km values into submit_result with status answered.
3. If not found, hand over with request_human_help and set status to handed_over.
Never make up values that are not in the ledger.
tools: [data.find_record, request_human_help]
handover:
assignment: fleet-desk
limits:
maxToolCalls: 4
maxSeconds: 60
output:
type: object
required: [status, vehicle]
properties:
status:
title: Result
oneOf:
- { const: answered, title: Answered }
- { const: handed_over, title: Handed over }
vehicle: { title: Registration number, type: string }
model: { title: Model, type: [string, "null"] }
inspection_expiry: { title: Inspection expiry, type: [string, "null"] }
odometer_km: { title: Odometer (km), type: [integer, "null"] }
Field What it means
purpose, examples What the agent does and how people ask for it. Reception and the chat agent use these to choose an agent
input JSON Schema of the input. Agents that can be asked from the chat have an input
driver How it runs. See the table below
tools Tools it may call. The Tool Gateway checks on every call
limits Number of tool calls (up to 50), seconds (up to 600), model tokens
output JSON Schema of the result. The submit_result value is checked against it. title becomes the label on screens
approval The assignment rule and DeciderPolicy for approving external actions
handover The assignment rule for Tasks created by request_human_help
driver How it runs Availability
scripted Calls tools in the order written in script Available
llm A model chooses tools. Requires instructions Available
opencode Runs a CLI in a sandbox; model calls go through the Model Gateway Preview
codex, claude Runs Codex CLI or Claude Code in a sandbox; tools are called through the Tool Gateway over MCP Preview Local development environment only

Don’t let agents make business decisions such as calculations or reconciliation; write those in calculations or Workflows. Agents call them through tools such as calculations.run, and the server decides the values and their value states.

const run = await unwrap(
hane.POST("/v1/agents/runs", { body: { agent: "vehicle-check", input: { vehicle: "TEST 300 A 10-02" } } }),
);
// run.status is queued. Read the state with run.run_id

Runs are asynchronous. The status moves from queued to running to succeeded, failed, cancelled, or limit_exceeded. When the run ends, result holds the result checked against the output schema.

async function waitRun(id: string) {
for (;;) {
const r = await unwrap(hane.GET("/v1/agents/runs/{id}", { params: { path: { id } } }));
if (r.status !== "queued" && r.status !== "running") return r;
await new Promise((ok) => setTimeout(ok, 2000));
}
}
const done = await waitRun(run.run_id);
console.log(done.status, done.result, done.trace.map((t) => `${t.kind} ${t.tool ?? ""} ${t.summary}`));
  • To show progress on screens, use the conversations of the embedded chat, which stream tool starts and ends and pending approvals over SSE. The API that starts a single AgentRun has no progress stream.
  • To learn when runs end in your own systems, subscribe to agents.run.succeeded, agents.run.failed, and similar events with a webhook (Webhooks).
  • With shadow: true, the run performs no external actions or handovers and only leaves records. Use this to check a definition before changing it.
  • To stop a run, call POST /v1/agents/runs/{id}/cancel (with body {}). Its next tool call is refused.

When you don’t know which agent to ask, pass free text to reception. Reception chooses an agent from the purposes and examples of the active agents, and picks input values from the text according to the input schema.

const r = await unwrap(hane.POST("/v1/agents/requests", { body: { messages: ["When does contract C-1001 end?"] } }));
if (r.outcome === "started") console.log(r.run?.run_id);
if (r.outcome === "needs_input") console.log(r.missing.map((m) => m.title)); // ask back
if (r.outcome === "unmatched") console.log(r.candidates);

After asking back and getting an answer, append it to messages and send again.

When an agent calls a tool for email, SMS, calls, writes to your APIs, or operations performed by a person, Hane creates an ActionRequest that fixes the recipient and content, adds a digest, and creates an approval Task for the approval.assignment group. The agent returns its result without waiting for approval, and Hane sends only after approval.

spec:
tools: [data.find_record, communication.send_email, request_human_help]
sender: fleet-desk # sender identity. Unusable until an administrator approves it
approval:
assignment: fleet-approval
decider: lease-application # optional. DeciderPolicy that sets deciders and stages by amount, for example

In your app, read the ActionRequests linked to a run and show their state.

const actions = await unwrap(hane.GET("/v1/agents/action-requests", { params: { query: { run_id: run.run_id } } }));
for (const a of actions.items) console.log(a.tool, a.state, a.parameters.contact?.masked, a.approval_task_id);

Someone other than the requester decides the approval as a Task. See Using data and Workflows for how. Before approval, you can cancel it with POST /v1/agents/action-requests/{id}/cancel.

For operations a person performs outside Hane, such as mailing or visiting a counter (human.perform), an execution Task goes to the assignee after approval. The assignee does exactly what was approved and records the result with POST /v1/agents/action-requests/{id}/perform. A result that differs from the approval cannot be recorded as accepted; recording it as a deviation creates a Task to get approval again.