Docs
Jan Agent
Host Tools

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

FieldPurpose
nameA short identifier, such as get_time. Jan advertises it as host__get_time to avoid collisions.
descriptionTell the model when to use the tool and what it returns.
parametersA JSON Schema for the arguments. Jan validates supported constraints before calling you.
capabilityUse read for observation, or actuator for actions with side effects.
handlerYour 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:

FieldGoes to the model?Example use
textYesA database query result or sensor reading
imagesYesAn array of file paths, data URLs or image objects
detailsNoHost-only metadata for your UI
error or isErrorYes, as a failed tool resultExplain 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_request events and answer them. Otherwise the turn waits. See permission prompts.
  • Your app can own approval with permissions: 'host' in JavaScript or permissions="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=True lets the model delegate with dispatch_subagent and list_subagents, and steer or stop a running child with message_subagent and stop_subagent, even with builtins: 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's run_id on the request. Subagents are off by default when builtins is 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; only ephemeral turns 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.