# Host Tools (/agent/adk-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.

```text
Model asks for get_time -> your handler reads the clock -> model receives the time
```

First complete the [JavaScript](/agent/adk-ts) or [Python](/agent/adk-python) quickstart.
These examples use the same installed ADK and provider configuration.

## Add one real function [#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`.

<Tabs items={['JavaScript', 'Python']}>
  <Tabs.Tab>
```js filename="clock.mjs"
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()
}
```
  </Tabs.Tab>
  <Tabs.Tab>
```python filename="clock.py"
from datetime import datetime, timezone
from jan_adk import HostTool, JanRuntime

def get_time(args, call):
    return datetime.now(timezone.utc).isoformat()

with JanRuntime.start() as runtime:
    session = runtime.create_session(
        ephemeral=True,
        builtins=False,
        tools=[HostTool(
            name="get_time",
            description="Read the current UTC time from this machine.",
            parameters={"type": "object", "properties": {}, "additionalProperties": False},
            capability="read",
            handler=get_time,
        )],
    )

    turn = session.prompt("Use get_time and tell me the current UTC time.")
    for event in turn:
        if event["type"] == "token":
            print(event["text"], end="", flush=True)
    result = turn.result()
    if result.stop_reason == "error":
        raise RuntimeError(result.error)
    print(f"\nFinished: {result.stop_reason}")
```
  </Tabs.Tab>
</Tabs>

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 [#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 [#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](/agent/cli#tool-result-images) for provider-specific placement.

## Decide who approves actions [#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](/agent/adk-runtime#answer-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 [#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.

```js
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`](/agent/protocol) for the exact rules.

## Change tools or stop a callback [#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 [#next]

[Process & failures](/agent/adk-runtime) covers stopping turns, permissions, shutdown and
retries. [Protocol records](/agent/protocol#rpc-host-tools) is the lower-level reference if
you are implementing your own client.
