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}}:

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).