Tools — state, databases, and visualization
🔵 State storage (scoped store, DuckDB SQL database) and generation of visual blocks (charts/tables). For a summary table of all engines, see "Tools Reference".
5.11 state_tool
Scoped state storage. kind: state_tool opens the core's scoped state
store to the agent. Each YAML binds one operation (op) to one scope +
key — the agent is given a narrow, targeted capability (e.g., "only
read user/name").
The concrete scope id (session/user/template) is resolved at runtime from the context of the calling tool — the LLM never sees someone else's id.
| Field | Required | Description |
|---|---|---|
kind |
yes | state_tool |
name |
yes | name for the LLM |
description |
yes | description for the LLM |
op |
yes | one operation (see the table below) |
scope |
yes | user | session | agent | team:<id> |
key |
yes | storage key |
key_param |
no | input argument overriding key |
default |
no | value of get on a miss |
ttl_secs |
no | TTL for entries |
limit / offset |
no | pagination for reading collections |
filter |
no | JSON filter over collections |
parameters |
no | override the auto-schema |
Scopes
| Scope | Stored under | Resolved from |
|---|---|---|
user |
user:<user_id> |
X-User-Id of the session |
session |
session:<session_id> |
current session |
agent |
agent:<template> |
agent template name |
team:<id> |
team:<id> |
explicitly given id; the caller must be a member of the team (see §45) |
Value kinds
| Kind | Write | Read |
|---|---|---|
| scalar | set |
get |
| list | push |
list, range |
| set | sadd, srem |
smembers, scard |
delete deletes any key; keys lists the keys of the scope.
Reading state in templates
The same scoped store is available in rest_tool/site_tool templates via the
state('scope', 'key') function. Write settings into the agent scope — and they will be
substituted into the request without passing them as arguments:
# tools/my_api.yaml
fetch:
url: "https://api.example.com/repos/{{ state('agent', 'repo') }}/issues"
state(...) resolves the scope from the calling session (just like state_tool); thus
an "agent setting" applies to its own tools.
op — reference
| op | Description | Auto-parameters | Returns |
|---|---|---|---|
get |
read a scalar (or default/null) |
— | value |
set |
upsert a scalar | value |
{"ok":true} |
delete |
delete a key | — | {"deleted":true|false} |
keys |
list the keys of the scope | limit, offset |
[{key,kind,size}] |
push |
append to a list | value |
{"seq":N} |
list |
read a list | limit, offset |
["a", …] |
range |
slice of a list [from..=to] |
from, to |
["a", …] |
sadd |
add to a set | value |
{"added":true|false} |
smembers |
members of a set | limit, offset |
["x", …] |
srem |
remove from a set | value |
{"removed":true|false} |
scard |
size of a set | — | {"count":N} |
Reading collections is bounded (1000 elements by default).
filter
filter:
path: meta.priority
op: gte # eq | ne | contains | prefix | gt | gte | lt | lte | exists
value: 3
String operations are case-insensitive; gt/gte/lt/lte compare numerically.
5.17 duckdb_tool
A dedicated DuckDB database for the agent. Universal SQL access to DuckDB: the
agent gets its own database (or access to a shared one), while connection and
schema management stays in the engine. Suitable when key/value state_tool is not
enough — you need tables, JOINs, aggregations, indexes.
kind: duckdb_tool
name: memory
description: SQL-база фактов пользователя.
path: data/dbs/{user}.db # файл, ":memory:" или шаблон пути
operation: query # необязательно: query | execute | schema
init: # необязательно: выполняются ОДИН раз (идемпотентно)
- |
CREATE TABLE IF NOT EXISTS facts (
id BIGINT PRIMARY KEY,
topic TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
readonly: false # запретить execute
limit: 100 # страница по умолчанию
max_rows: 1000 # жёсткий кап строк
emit_visual: false # не рисовать авто-таблицу в чате (по умолч. true)
sandbox: true # запретить доступ к ФС/ATTACH + lock_configuration
| Field | Required | Description |
|---|---|---|
kind |
yes | duckdb_tool |
name |
yes | name for the LLM |
description |
no | description for the LLM |
path |
yes | database file or :memory:. Supports {user}, {session}, {template}/{agent} |
operation |
no | bind to a single operation; without it, the operation is selected by the operation argument (default query) |
init |
no | SQL string or list; executed once when the database is first opened |
readonly |
no | true forbids execute |
limit / max_rows |
no | default page / row cap |
emit_visual |
no | false — do not send the auto-table query to the chat visual (default true) |
sandbox |
no | true — disable access to files/ATTACH/extensions and lock the DuckDB configuration |
operations |
no | declarative operations (see below); when present, disables free-form sql |
parameters |
no | override the auto-schema |
Declarative operations (operations:)
If the agent does not need arbitrary SQL, describe a fixed set of operations —
each with its own SQL template. Only operation + parameters are exposed; sql
does not appear in the schema and does not get into the query.
kind: duckdb_tool
name: content
path: data/dbs/{template}.db
emit_visual: false
sandbox: true
operations:
plan_list:
sql: "SELECT * FROM content_plan WHERE ({{status}} IS NULL OR status = {{status}}) ORDER BY updated_at DESC"
plan_upsert:
mode: execute # query (по умолчанию) | execute
sql: |
INSERT INTO content_plan (id, title, status) VALUES ({{id}}, {{title}}, COALESCE({{status}}, 'idea'))
ON CONFLICT (id) DO UPDATE SET title = EXCLUDED.title, status = EXCLUDED.status
Rules for substituting {{param}}:
- string → SQL literal in single quotes,
'is doubled; - number /
true/false→ as is; - missing or empty parameter →
NULL(convenient for filters like({{status}} IS NULL OR status = {{status}})).
If parameters is not set, the schema is inferred automatically: operation (enum
of names) + a string parameter for each placeholder found. mode defaults to
query; execute — for DDL/DML (respects readonly).
When operations is set, the sql argument is ignored — the engine executes only
the SQL of the selected operation.
Operations
Without operations: (raw mode), three operations with free-form sql are available:
| operation | Purpose | Parameters |
|---|---|---|
query |
execute SQL and return columns + rows |
sql, limit |
execute |
DDL/DML (CREATE, INSERT, UPDATE, DELETE, …) |
sql |
schema |
tables/views and their columns | table (optional) |
query materializes up to limit rows and sets has_more when there are more —
the agent repeats the request with its LIMIT/OFFSET. The result query is additionally
placed into data (for composition via visualize_tool source) and into visual
(table for the UI); content — JSON for the LLM. Temporal types (TIMESTAMP, DATE,
TIME) are returned as ISO 8601 strings.
Custom databases via user / session / agent
Placeholders in path are resolved from the call context:
path: data/dbs/{user}.db # одна база на пользователя
path: data/dbs/{template}.db # одна база на шаблон агента
path: data/dbs/{session}.db # одна база на сессию
An unresolvable placeholder (for example {user} without a user id) is an explicit error, not a
silent fallback to a shared file. Substitution is sanitized (you cannot escape the
directory).
Shared database for multiple tools and instances
DuckDB allows only one writer per file, so the engine maintains a
process-global connection pool keyed by canonical path: multiple
definitions of duckdb_tool (and parallel agent sessions) pointing to the same
file share a single mutex-protected connection. init is then executed
exactly once. Example — memory + memory_schema on a shared {user}.db in
config/agents/db-demo/.
5.22 sql_tool
SQL access to an external PostgreSQL or MySQL — the networked sibling of
duckdb_tool with the same query/execute/schema interface,
but for connecting to an existing accounting / CRM / ERP database.
kind: sql_tool
name: crm_db
description: Чтение из базы CRM.
dialect: postgres # postgres | mysql
url_env: CRM_DATABASE_URL # URL подключения из env (postgres://… / mysql://…)
operation: query # необязательно: query | execute | schema
readonly: false # запретить execute
limit: 100 # страница по умолчанию
max_rows: 1000 # жёсткий кап строк
init: # необязательно: выполняется один раз при создании пула
- SET NAMES utf8mb4;
| Field | Required | Description |
|---|---|---|
kind |
yes | sql_tool |
name |
yes | name for the LLM |
description |
no | description for the LLM |
dialect |
yes | postgres | mysql |
url_env |
yes¹ | name of the env with the connection URL (secrets — not in YAML) |
url |
no | literal URL (dev only; prefer url_env) |
operation |
no | bind to a single operation; without it, the operation is selected by the operation argument (default query) |
readonly |
no | true forbids execute |
limit / max_rows |
no | default page / row cap |
parameters |
no | override the auto-schema |
¹ either url_env or url — one of the two is required.
Operations
| operation | Purpose | Parameters |
|---|---|---|
query |
execute SQL and return columns + rows |
sql, limit |
execute |
DDL/DML (CREATE, INSERT, UPDATE, DELETE, …) |
sql |
schema |
tables/views and their columns | table (optional) |
query materializes up to limit rows and sets has_more; the result is placed
into data (composition via visualize_tool source) and visual (table).
Connections are shared via a process-global pool keyed by URL: multiple definitions
of sql_tool for the same database use one pool, init is executed once.
Temporal types, uuid, etc. are returned as strings.
5.12 chart_tool and table_tool
Static visual data. Engines for precomputed/static data:
a compact content (summary for the LLM) + visual (full block for the UI).
kind: table_tool
name: price_list
description: Показать прайс.
parameters: { type: object, properties: {} }
data_file: data/prices.json
title: "Цены"
summary: "{{ count }} позиций"
columns:
- { key: model, title: "Модель" }
- { key: price, title: "Цена", align: right }
options: { sortable: true, searchable: true }
kind: chart_tool
name: sales_chart
description: Продажи по кварталам.
parameters: { type: object, properties: {} }
data_file: data/sales.json # { type, title, labels, series }
summary: "{{ count }} точек"
Common fields: kind, name, description, parameters, data_file (path
relative to the YAML) or inline data, summary (MiniJinja, context count),
ui. table_tool additionally: title, columns, options (sortable /
searchable / paged). chart_tool takes data as the spec (type = bar |
line | area | pie | scatter).
Both engines accept a dynamic data argument in the call — it overrides
the static data/data_file.
5.13 visualize_tool
Charts and tables from arbitrary data. The universal sibling of
chart_tool/table_tool: it turns data passed as arguments into a
chart or table, with a transformation pipeline. The LLM provides rows (or a spec),
and the engine aggregates deterministically.
kind: visualize_tool
name: make_chart
description: Построить график/таблицу из переданных строк.
| Argument | Description |
|---|---|
data |
array of row objects, or a full table/chart spec |
source |
reference the result of a previous tool call: { tool, index, field, path, pages } |
group_by |
grouping field |
aggregate |
{ count, sum, avg, min, max } — results with a prefix (sum_cost, …) |
filter |
{ field: { op: value } } |
sort |
{ by, dir: asc|desc } |
limit |
maximum rows |
as |
table | bar | line | area | pie | scatter |
title, x, series, columns, options |
styling |
Tool composition (source)
Instead of copying data, you can reference the result of a previous call in the same session:
{ "source": { "tool": "trace_sessions" }, "group_by": "agent",
"aggregate": { "sum": ["errors"] }, "as": "bar" }
trace_tool's sessions returns
structured rows in data — a ready-made source. An explicit data wins over
source.
5.14 map_tool
Display a map with one or more markers. The engine resolves the place
(geocoding via Photon — a fast OSM geocoder) or accepts explicit
coordinates and returns a visual block of type map. In the web widget an
interactive map (Leaflet) is rendered; Telegram / VK receive a static image +
a link; Android gets "Open in maps" / "in browser" buttons.
kind: map_tool
name: show_map
description: Показать карту с маркером.
provider: osm # osm (по умолчанию) | google | yandex | 2gis | custom
| Field | Description |
|---|---|
provider |
tile preset: osm (no keys), google, yandex, 2gis, custom |
tile_url |
custom tile URL ({z}/{x}/{y}/{s}) — overrides provider |
attribution |
source attribution (overrides the preset) |
subdomains |
array of subdomains for {s} |
geocode_url |
geocoding endpoint (default Photon photon.komoot.io/api/) |
zoom |
default zoom (1–20, default 14) |
Call arguments (any one source):
{ "place": "Академгородок, Новосибирск" } // геокодинг
{ "lat": 54.85, "lon": 83.10, "label": "Академ" } // координаты
{ "markers": [ {"lat": 54.85, "lon": 83.10, "label": "A"}, {"lat": 55.0, "lon": 83.5} ] }
label / title / zoom are optional. Google/Yandex/2GIS tiles are
unofficial presets; if needed, specify your own tile_url and attribution
(complying with the provider's terms of use).
The map block on the wire: { type, title?, center{lat,lon}, zoom?, markers[], tile_url?, attribution?, subdomains?, link } — see block format.
5.14 scratch_tool
Files in the session scratchpad (ToolContext.temp_dir) — a temporary directory
that the kernel creates for each session. The agent can store
intermediate results, notes, and patches there, read them in chunks, and edit them without
having access to the host file system. The engine provides the scratchpad module;
a single YAML file describes one operation (op).
kind: scratch_tool
name: scratch_read
description: Прочитать файл из черновика сессии (с постраничным чтением)
op: read # list | read | write | edit | delete | mkdir | move
op |
Arguments | What it does |
|---|---|---|
list |
path?, recursive? |
list entries {path, dir, size} (up to 1000) |
read |
path, start_char?, max_chars?, start_line?, line_count?, line_numbers? |
read in a window (by characters or lines) |
write |
path, content, append? |
create/overwrite/append (directories are created) |
edit |
path, old_string, new_string?, replace_all? or path, start_char, end_char, content |
replace a fragment (unique) or a character range |
delete |
path |
delete a file or directory (recursively) |
mkdir |
path |
create a directory with parents |
move |
path, to |
rename/move within the scratchpad |
All paths are relative to the scratchpad root; absolute paths and ..
are rejected. Large files are read in windows: the response contains a hint with
the offset (start_char=) so you can continue. The engineer agent connects
scratch_list/read/write/edit/delete/mkdir/move from
config/agents/admin/tools/.
A ready-made example is in config/agents/admin/tools/scratch_*.yaml; the type reference
is tools/scratch_tool in the platform (the "Documentation" tab).