Host Tools
A host tool is a function in your app that the agent can ask to run.
The model chooses a tool and supplies arguments. The ADK calls your handler. Your handler's result goes back to the model, which can use it to answer or choose another action.
Model asks for get_time -> your handler reads the clock -> model receives the time
First complete the JavaScript or Python quickstart. These examples use the same installed ADK and provider configuration.
Add one real function
This tool reads your machine's clock. It needs no API key of its own and changes no application state.
Save the example in your quickstart directory and run it with node clock.mjs or python clock.py.
import { JanRuntime } from '@janhq/adk'const runtime = await JanRuntime.start()try { const session = await runtime.createSession({ ephemeral: true, builtins: false, tools: [{ name: 'get_time', description: 'Read the current UTC time from this machine.', parameters: { type: 'object', properties: {}, additionalProperties: false }, capability: 'read', handler: () => new Date().toISOString(), }], }) const turn = await session.prompt('Use get_time and tell me the current UTC time.') for await (const event of turn) { if (event.type === 'token') process.stdout.write(event.text) } const result = await turn.result() if (result.stopReason === 'error') throw new Error(result.error) console.log(`\nFinished: ${result.stopReason}`)} finally { await runtime.close()}
The model receives the tool description and argument schema, not your handler's source code. It asks to call the tool; the ADK runs the handler in your process. No manual JSON messages are needed.
What a tool needs
| Field | Purpose |
|---|---|
name | A short identifier, such as get_time. Jan advertises it as host__get_time to avoid collisions. |
description | Tell the model when to use the tool and what it returns. |
parameters | A JSON Schema for the arguments. Jan validates supported constraints before calling you. |
capability | Use read for observation, or actuator for actions with side effects. |
handler | Your function, receiving the arguments and call context. |
Use simple names containing letters, digits, _ or -; avoid __, reserved built-in names and
duplicates. Keep names at most 58 characters so the host__ prefix fits the wire limit.
A schema checks shape, not authorization or physical safety. Your handler must still check whether the requested action is allowed. For hardware or shared state, serialize operations in your own application when necessary; callbacks can overlap, including calls from delegated children.
Return only what the model should see
A handler can return a string, as above, or an object:
| Field | Goes to the model? | Example use |
|---|---|---|
text | Yes | A database query result or sensor reading |
images | Yes | An array of file paths, data URLs or image objects |
details | No | Host-only metadata for your UI |
error or isError | Yes, as a failed tool result | Explain why the action failed |
Throwing an exception also produces a failed tool result. The agent can respond to that failure; it does not automatically mean the whole turn failed.
For image tools, use a vision-capable model and check runtime.limits. See
tool result images for provider-specific placement.
Decide who approves actions
A read tool such as the clock is not prompted. For an actuator:
- Jan owns approval by default. Your app must handle
permission_requestevents and answer them. Otherwise the turn waits. See permission prompts. - Your app can own approval with
permissions: 'host'in JavaScript orpermissions="host"in Python. Jan will not ask for approval of host tools; your handler must enforce the policy.
builtins: false / builtins=False keeps this example limited to your declared tools. It is not
an operating-system sandbox for your callback: that function still has your process's access.
Delegate and bring your own prompt
Two more session options suit an app that drives Jan with only its own tools:
subagents: true/subagents=Truelets the model delegate withdispatch_subagentandlist_subagents, and steer or stop a running child withmessage_subagentandstop_subagent, even withbuiltins: false. Every subagent is limited to your session's host tools (no shell, files or web), and its calls reach your handlers like the parent's, with the subagent'srun_idon the request. Subagents are off by default whenbuiltinsis off.systemPrompt/system_prompt=replaces Jan's whole system prompt with yours, byte for byte. Jan adds no identity, guides, environment, date or recalled memory, though it may still append runtime notices (such as a finished subagent) to the conversation. Subagents keep their own prompts. The session's answers are still saved to project memory for later sessions; onlyephemeralturns that off, and it also stops the thread being saved.
const session = await runtime.createSession({ builtins: false, subagents: true, systemPrompt: 'You control a robot arm. Delegate camera checks to a subagent.', tools: [/* your host tools */],})
See session/start for the exact rules.
Change tools or stop a callback
Between turns, use session.setTools(...) / session.set_tools(...) to replace the host tool set.
The session's tools and toolSpecs / tool_specs report what Jan actually advertises.
When a request is cancelled, JavaScript receives an aborted call.signal; Python receives a set
call.aborted threading event. Make long-running handlers cooperate with cancellation. An action
that has already happened is not undone.
Next
Process & failures covers stopping turns, permissions, shutdown and retries. Protocol records is the lower-level reference if you are implementing your own client.