How to fix: On each tool, declare annotations.readOnlyHint, annotations.destructiveHint, and annotations.openWorldHint as booleans. The policy review separately assesses whether their values match the tool's actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Show who you are authenticated as, the orgs you belong to (with their ids and names), and which org is currently active for this session. Call this first when an org-scoped tool says you must choose an org — then pass one of the listed org_ids to set_active_org.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No input fields.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
{
"title": "whoami",
"readOnlyHint": true
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "whoami",
"description": "Show who you are authenticated as, the orgs you belong to (with their ids and names), and which org is currently active for this session. Call this first when an org-scoped tool says you must choose an org — then pass one of the listed org_ids to set_active_org.",
"inputSchema": {
"type": "object",
"properties": {}
},
"annotations": {
"title": "whoami",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description82/100
Set the active org for this session. Call this when an org-scoped tool reports that you belong to multiple orgs and must choose one. Pass the org_id (begins with 'og_').
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
org_id
The org to make active (begins with 'og_').
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations50/100
Declare idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "set_active_org",
"description": "Set the active org for this session. Call this when an org-scoped tool reports that you belong to multiple orgs and must choose one. Pass the org_id (begins with 'og_').",
"inputSchema": {
"type": "object",
"required": [
"org_id"
],
"properties": {
"org_id": {
"type": "string",
"description": "The org to make active (begins with 'og_')."
}
}
},
"annotations": {
"title": "set_active_org",
"readOnlyHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Ask a follow-up question in an existing Roboto agent thread and wait for the answer. One call drives one complete assistant turn: the message is sent, the agent works (calling Roboto tools as it needs to), and the answer comes back. Use it on a thread you own -- fork_agent_thread gives you one from any thread you can read -- because a thread started on another surface (Slack, say) refuses new turns. Retrying after a dropped connection is safe: if the same question is already running, this attaches to that turn instead of asking twice. If the turn outlives this call's wait ceiling you get a non-error result telling you to call wait_agent_thread; nothing is lost. Returns the assistant's answer plus the NAMES of the tools the turn used (never their arguments or results -- read those with get_agent_thread).
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
thread_id
The agent thread to ask in. Begins with 'ath_'; older threads begin with 'ch_' or 'ags_' and work the same way.
Clear enough to supply this argument.
message
The question or instruction to send, as you would type it into the Roboto chat. The thread's full history is already loaded, so refer to it directly rather than restating it.
Clear enough to supply this argument.
Annotations50/100
Declare idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "ask_agent_thread",
"description": "Ask a follow-up question in an existing Roboto agent thread and wait for the answer. One call drives one complete assistant turn: the message is sent, the agent works (calling Roboto tools as it needs to), and the answer comes back. Use it on a thread you own -- fork_agent_thread gives you one from any thread you can read -- because a thread started on another surface (Slack, say) refuses new turns. Retrying after a dropped connection is safe: if the same question is already running, this attaches to that turn instead of asking twice. If the turn outlives this call's wait ceiling you get a non-error result telling you to call wait_agent_thread; nothing is lost. Returns the assistant's answer plus the NAMES of the tools the turn used (never their arguments or results -- read those with get_agent_thread).",
"inputSchema": {
"type": "object",
"required": [
"thread_id",
"message"
],
"properties": {
"message": {
"type": "string",
"description": "The question or instruction to send, as you would type it into the Roboto chat. The thread's full history is already loaded, so refer to it directly rather than restating it."
},
"thread_id": {
"type": "string",
"description": "The agent thread to ask in. Begins with 'ath_'; older threads begin with 'ch_' or 'ags_' and work the same way."
}
}
},
"annotations": {
"title": "ask_agent_thread",
"readOnlyHint": false,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Wait for the assistant turn currently running in a Roboto agent thread and return its answer. Sends nothing and starts nothing, so it is always safe to call and safe to repeat. Call it after ask_agent_thread reports that a turn is still running, or to pick up a thread someone else started. If the thread is already idle it returns the latest answer immediately.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
thread_id
The agent thread to wait on. Begins with 'ath_'; older threads begin with 'ch_' or 'ags_' and work the same way.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "wait_agent_thread",
"description": "Wait for the assistant turn currently running in a Roboto agent thread and return its answer. Sends nothing and starts nothing, so it is always safe to call and safe to repeat. Call it after ask_agent_thread reports that a turn is still running, or to pick up a thread someone else started. If the thread is already idle it returns the latest answer immediately.",
"inputSchema": {
"type": "object",
"required": [
"thread_id"
],
"properties": {
"thread_id": {
"type": "string",
"description": "The agent thread to wait on. Begins with 'ath_'; older threads begin with 'ch_' or 'ags_' and work the same way."
}
}
},
"annotations": {
"title": "wait_agent_thread",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Looks up any single Roboto entity — action, agent, collection, comment, custom field, dataset, device, event, file, invocation, message path, metric definition, session, skill, skill version, topic, trigger, view — by its ID and returns the record as JSON. The entity type is inferred from the ID prefix. Roboto IDs are prefixed with their type: agt_ agent, cf_ custom field, cl_ collection, cm_ comment, ds_ dataset, ev_ event, fl_ file, iv_ invocation, mp_ message path, mt_ metric definition, se_ session, sk_ skill, ti_ topic, tp_ topic, tr_ trigger, vw_ view. Devices (by device ID) have no prefix. A topic's ti_ ID names it across every file that carries it; a tp_ ID names the topic in one file. An action's identity is its owning org plus its name: pass 'og_.../name' to address one exactly, or a bare name to look within your own org. One version of a skill is addressed by qualifying the skill's ID: 'sk_...@3'. An unprefixed identifier is looked up as an action name, an agent name, a device ID or a skill name; every match is returned. Pass entity_type whenever you already know which of those an unprefixed identifier is -- that resolves it in one lookup instead of trying each in turn. It is also how you disambiguate a name collision, or force a lookup (e.g. a device whose user-chosen ID happens to start with a Roboto prefix).
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
entity_id
The entity's ID (prefixed, e.g. 'ds_...' or 'ev_...'), or an action name, an agent name, a device ID or a skill name for those unprefixed types.
Clear enough to supply this argument.
entity_type
For unprefixed identifiers only: name the kind whenever you know it, so it is looked up once instead of being tried as each. Also how you pick between an action name and a device ID that collide, or force a lookup when a device ID starts with another type's prefix. A prefixed ID already implies its type.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
{
"title": "get_entity",
"readOnlyHint": true
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_entity",
"description": "Looks up any single Roboto entity — action, agent, collection, comment, custom field, dataset, device, event, file, invocation, message path, metric definition, session, skill, skill version, topic, trigger, view — by its ID and returns the record as JSON. The entity type is inferred from the ID prefix. Roboto IDs are prefixed with their type: agt_ agent, cf_ custom field, cl_ collection, cm_ comment, ds_ dataset, ev_ event, fl_ file, iv_ invocation, mp_ message path, mt_ metric definition, se_ session, sk_ skill, ti_ topic, tp_ topic, tr_ trigger, vw_ view. Devices (by device ID) have no prefix. A topic's ti_ ID names it across every file that carries it; a tp_ ID names the topic in one file. An action's identity is its owning org plus its name: pass 'og_.../name' to address one exactly, or a bare name to look within your own org. One version of a skill is addressed by qualifying the skill's ID: 'sk_...@3'. An unprefixed identifier is looked up as an action name, an agent name, a device ID or a skill name; every match is returned. Pass entity_type whenever you already know which of those an unprefixed identifier is -- that resolves it in one lookup instead of trying each in turn. It is also how you disambiguate a name collision, or force a lookup (e.g. a device whose user-chosen ID happens to start with a Roboto prefix).",
"inputSchema": {
"type": "object",
"required": [
"entity_id"
],
"properties": {
"entity_id": {
"type": "string",
"description": "The entity's ID (prefixed, e.g. 'ds_...' or 'ev_...'), or an action name, an agent name, a device ID or a skill name for those unprefixed types."
},
"entity_type": {
"enum": [
"action",
"agent",
"device",
"skill"
],
"type": "string",
"description": "For unprefixed identifiers only: name the kind whenever you know it, so it is looked up once instead of being tried as each. Also how you pick between an action name and a device ID that collide, or force a lookup when a device ID starts with another type's prefix. A prefixed ID already implies its type."
}
}
},
"annotations": {
"title": "get_entity",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Lists the entities related to a source entity, or enumerates org-level resources. Supported listings (source -> related types): action -> invocation; collection -> comment, dataset, event, file, session; dataset -> comment, event, file, invocation, session, topic; device -> session; file -> comment, event, topic; org -> action, agent, collection, custom_field, device, metric_definition, session, skill, trigger, view; session -> device, file, metric, topic; skill -> skill_version. The source type is inferred from entity_id's prefix; omit entity_id to use your org as the source (e.g. related_type='device' lists the org's devices). Each listing returns a page of records and is paginated via page_token, except where noted here -- collection -> dataset, event, file, session: pages bare member IDs rather than records, unfiltered by the requested type; each ID carries its own type prefix | dataset -> event: accepts file_ids; unnarrowed this is every event in the dataset and is the default; narrowing keeps the dataset's own events but drops the other files', so empty is not proof | dataset -> file: accepts include_pattern | dataset -> topic: accepts detailed; compact by default, keyed by topic name; a name can recur across pages, so merge its file_ids | file -> event: also carries the parent dataset's own dataset-level events, never the sibling files'; empty here is not proof, list the dataset's events before concluding | org -> agent: listed without their request_template; fetch one by name for its full definition | org -> custom_field: spans every entity type, not just one | org -> skill: no bodies; includes skills you have not subscribed to. Fetch one by name for its text -- reading it, not running it; load_skill runs your pinned version | org -> trigger: every trigger, newest first. You cannot create or change one: users do that in the web app (Actions -> Triggers) or with the Python SDK | org -> view: accepts target; unpaged: every View you can see across the six View tables, or one table when narrowed; a View's name is not unique, so address one by its vw_ ID | session -> metric: unpaged: every metric's value for the session but topic_stats.*, which aggregate_metric reads by name; get a metric_id for its meaning | session -> topic: each topic_id is the topic's ti_ ID, which get_entity takes; roboto://topic links need a tp_ ID, and the data tools take the topic_name | skill -> skill_version: newest first, without bodies; get_entity on a version's handle (sk_...@3) returns that body -- reading it, not running it; load_skill runs your pinned version. Events, if present, are the primary mechanism by which meaningful system behavior and anomalies are recorded.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
related_type
The type of related entity to list.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
entity_id
The source entity's ID (prefixed), or the name / device ID of an unprefixed source (action, device, skill). Omit to list org-level resources.
Clear enough to supply this argument.
entity_type
Only for the unprefixed source types: pick one when an action name and a device ID collide. A prefixed entity_id already implies its type, and omitting entity_id lists your org's resources.
Clear enough to supply this argument.
page_token
The page token to use for pagination.
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
include_pattern
dataset->file only: a gitignore-style glob pattern of files to include, e.g. '**/*.jpg'.
Clear enough to supply this argument.
detailed
dataset->topic only: if true, return full per-topic records (paginated, 50 per page) instead of the compact name-grouped view.
Explain what happens when this optional value is omitted.
target
org->view only: list the Views of this one table instead of all six.
Clear enough to supply this argument.
file_ids
dataset->event only: restrict results to events on these specific files and their topics and message paths. Every ID must belong to entity_id and begins with 'fl_'. Omit to search the entire dataset -- the default, and the way to get a complete picture.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
{
"title": "list_related",
"readOnlyHint": true
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "list_related",
"description": "Lists the entities related to a source entity, or enumerates org-level resources. Supported listings (source -> related types): action -> invocation; collection -> comment, dataset, event, file, session; dataset -> comment, event, file, invocation, session, topic; device -> session; file -> comment, event, topic; org -> action, agent, collection, custom_field, device, metric_definition, session, skill, trigger, view; session -> device, file, metric, topic; skill -> skill_version. The source type is inferred from entity_id's prefix; omit entity_id to use your org as the source (e.g. related_type='device' lists the org's devices). Each listing returns a page of records and is paginated via page_token, except where noted here -- collection -> dataset, event, file, session: pages bare member IDs rather than records, unfiltered by the requested type; each ID carries its own type prefix | dataset -> event: accepts file_ids; unnarrowed this is every event in the dataset and is the default; narrowing keeps the dataset's own events but drops the other files', so empty is not proof | dataset -> file: accepts include_pattern | dataset -> topic: accepts detailed; compact by default, keyed by topic name; a name can recur across pages, so merge its file_ids | file -> event: also carries the parent dataset's own dataset-level events, never the sibling files'; empty here is not proof, list the dataset's events before concluding | org -> agent: listed without their request_template; fetch one by name for its full definition | org -> custom_field: spans every entity type, not just one | org -> skill: no bodies; includes skills you have not subscribed to. Fetch one by name for its text -- reading it, not running it; load_skill runs your pinned version | org -> trigger: every trigger, newest first. You cannot create or change one: users do that in the web app (Actions -> Triggers) or with the Python SDK | org -> view: accepts target; unpaged: every View you can see across the six View tables, or one table when narrowed; a View's name is not unique, so address one by its vw_ ID | session -> metric: unpaged: every metric's value for the session but topic_stats.*, which aggregate_metric reads by name; get a metric_id for its meaning | session -> topic: each topic_id is the topic's ti_ ID, which get_entity takes; roboto://topic links need a tp_ ID, and the data tools take the topic_name | skill -> skill_version: newest first, without bodies; get_entity on a version's handle (sk_...@3) returns that body -- reading it, not running it; load_skill runs your pinned version. Events, if present, are the primary mechanism by which meaningful system behavior and anomalies are recorded.",
"inputSchema": {
"type": "object",
"required": [
"related_type"
],
"properties": {
"target": {
"enum": [
"datasets",
"files",
"events",
"sessions",
"devices",
"collections"
],
"type": "string",
"description": "org->view only: list the Views of this one table instead of all six."
},
"detailed": {
"type": "boolean",
"description": "dataset->topic only: if true, return full per-topic records (paginated, 50 per page) instead of the compact name-grouped view."
},
"file_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "dataset->event only: restrict results to events on these specific files and their topics and message paths. Every ID must belong to entity_id and begins with 'fl_'. Omit to search the entire dataset -- the default, and the way to get a complete picture."
},
"entity_id": {
"type": "string",
"description": "The source entity's ID (prefixed), or the name / device ID of an unprefixed source (action, device, skill). Omit to list org-level resources."
},
"page_token": {
"type": "string",
"description": "The page token to use for pagination."
},
"entity_type": {
"enum": [
"action",
"device",
"skill"
],
"type": "string",
"description": "Only for the unprefixed source types: pick one when an action name and a device ID collide. A prefixed entity_id already implies its type, and omitting entity_id lists your org's resources."
},
"related_type": {
"enum": [
"action",
"agent",
"collection",
"comment",
"custom_field",
"dataset",
"device",
"event",
"file",
"invocation",
"metric",
"metric_definition",
"session",
"skill",
"skill_version",
"topic",
"trigger",
"view"
],
"type": "string",
"description": "The type of related entity to list."
},
"include_pattern": {
"type": "string",
"description": "dataset->file only: a gitignore-style glob pattern of files to include, e.g. '**/*.jpg'."
}
}
},
"annotations": {
"title": "list_related",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Returns the revision history of a collection: for each version, the resources and tags added or removed, when the change was applied, and who applied it. Use this to answer questions about how a collection changed over time — what was added recently, who added a particular member, or when a resource joined or left. Changes are returned most recent first, one page at a time; pass the returned next_token back as page_token to walk further into the past. Narrow to a version range with from_version and to_version.
The description explains the capability, when to use it, and what to expect.
Inputs94/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
collection_id
The universally unique ID for the collection. Always begins with 'cl_'
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
from_version
Only return changes applied to this version or later. Omit for no lower bound.
Clear enough to supply this argument.
to_version
Only return changes that produced this version or earlier. Omit for no upper bound.
Clear enough to supply this argument.
page_token
The next_token from a previous call to this tool, to continue further back in history.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_collection_history",
"description": "Returns the revision history of a collection: for each version, the resources and tags added or removed, when the change was applied, and who applied it. Use this to answer questions about how a collection changed over time — what was added recently, who added a particular member, or when a resource joined or left. Changes are returned most recent first, one page at a time; pass the returned next_token back as page_token to walk further into the past. Narrow to a version range with from_version and to_version.",
"inputSchema": {
"type": "object",
"required": [
"collection_id"
],
"properties": {
"page_token": {
"type": "string",
"description": "The next_token from a previous call to this tool, to continue further back in history."
},
"to_version": {
"type": "integer",
"minimum": 0,
"description": "Only return changes that produced this version or earlier. Omit for no upper bound."
},
"from_version": {
"type": "integer",
"minimum": 0,
"description": "Only return changes applied to this version or later. Omit for no lower bound."
},
"collection_id": {
"type": "string",
"description": "The universally unique ID for the collection. Always begins with 'cl_'"
}
}
},
"annotations": {
"title": "get_collection_history",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Looks up the existing LLM-generated summary of a dataset, if available. If none exists, returns a message to that effect. This cannot be used to generate a new summary. To find which datasets' summaries mention something, use search_roboto with `summary CONTAINS` instead of reading them one by one.
Describe the returned information so the agent knows whether it will answer the request.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
dataset_id
The universally unique ID for the dataset. Always begins with 'ds_'
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_dataset_summary",
"description": "Looks up the existing LLM-generated summary of a dataset, if available. If none exists, returns a message to that effect. This cannot be used to generate a new summary. To find which datasets' summaries mention something, use search_roboto with `summary CONTAINS` instead of reading them one by one.",
"inputSchema": {
"type": "object",
"required": [
"dataset_id"
],
"properties": {
"dataset_id": {
"type": "string",
"description": "The universally unique ID for the dataset. Always begins with 'ds_'"
}
}
},
"annotations": {
"title": "get_dataset_summary",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Fetches a topic's complete record, including identity (topic_id, topic_name, association), time range, audit fields (created, modified), and user-set metadata. Identify the topic either by topic_id (a bare `tp_`-prefixed ID), or by topic_name plus the file_id of the file it belongs to (topic names are unique per file). Use this when the user asks for topic-level metadata. The response includes the full message-path list and can be large for topics with many paths. Do NOT use this tool: (1) to understand a topic's schema or column types for writing SQL — call `get_topic_schema`; (2) purely to look up message path IDs (mp_ids) for visualizer links — pass the `tp_` ID directly to `resolve_msgpath_ids` as `topic_id` alongside your `queries` (msgpath_name + timestamp).
The description explains the capability, when to use it, and what to expect.
Inputs99/100
Input documentation gives an agent enough context to populate the arguments.
topic_id
The ID of a topic in one file, beginning with 'tp_'. Provide this alone, or omit it and provide topic_name + file_id instead. A 'ti_' ID (from list_related(session -> topic), a session event or a session-scoped result) names a topic across every file that carries it. This tool refuses it, and changing its prefix does not turn it into a 'tp_' ID: call get_entity with it for the topic's name and schemas.
Clear enough to supply this argument.
topic_name
The name of the topic (e.g. '/imu/data'). Must be paired with file_id. Do not pass it with topic_id.
Clear enough to supply this argument.
file_id
The ID of the file (begins with 'fl_') containing the named topic. Must be paired with topic_name. Do not pass it with topic_id.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
{
"title": "get_topic",
"readOnlyHint": true
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_topic",
"description": "Fetches a topic's complete record, including identity (topic_id, topic_name, association), time range, audit fields (created, modified), and user-set metadata. Identify the topic either by topic_id (a bare `tp_`-prefixed ID), or by topic_name plus the file_id of the file it belongs to (topic names are unique per file). Use this when the user asks for topic-level metadata. The response includes the full message-path list and can be large for topics with many paths. Do NOT use this tool: (1) to understand a topic's schema or column types for writing SQL — call `get_topic_schema`; (2) purely to look up message path IDs (mp_ids) for visualizer links — pass the `tp_` ID directly to `resolve_msgpath_ids` as `topic_id` alongside your `queries` (msgpath_name + timestamp).",
"inputSchema": {
"type": "object",
"properties": {
"file_id": {
"type": "string",
"description": "The ID of the file (begins with 'fl_') containing the named topic. Must be paired with topic_name. Do not pass it with topic_id."
},
"topic_id": {
"type": "string",
"description": "The ID of a topic in one file, beginning with 'tp_'. Provide this alone, or omit it and provide topic_name + file_id instead. A 'ti_' ID (from list_related(session -> topic), a session event or a session-scoped result) names a topic across every file that carries it. This tool refuses it, and changing its prefix does not turn it into a 'tp_' ID: call get_entity with it for the topic's name and schemas."
},
"topic_name": {
"type": "string",
"description": "The name of the topic (e.g. '/imu/data'). Must be paired with file_id. Do not pass it with topic_id."
}
}
},
"annotations": {
"title": "get_topic",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Looks up a topic's schema and structure by name. Returns only the information needed to write queries for analyze_topic: topic name, schema name, message count, time range, and message paths — each with its `sql_path` (the exact, already-quoted column reference to copy verbatim into an analyze_topic sql_query), data type, and statistics. An entry without `sql_path` (a struct parent such as `header`) is not a column: query its child entries. STATS SCOPE: statistics are never limited to an analysis window. With dataset_id or file_id they span the topic's entire file(s); with session_id or event_id they span the whole session's data. Treat min/max as screening extremes: a threshold never crossed over that span was never crossed in your window, and the converse needs an in-window analyze_topic query. With session_id or event_id, statistics and message_count are absent until Roboto has computed them, and notes.statistics says why the statistics are absent. Call this before analyze_topic to learn which columns exist and how to reference them; do not guess column names from the topic name or message type. Pass topic_name and exactly one of dataset_id, file_id, session_id or event_id. When scoped to a dataset and the topic spans multiple files: if every file's schema is identical the schema is returned once with a chronologically-ordered 'file_ids' list, and every per-file value (message_count, start_time, end_time, and each statistic in a message path's metadata) is a list positionally aligned with file_ids (index i is file i); if the files' schemas differ structurally, a per-file schema is returned for each under 'schemas'. With event_id, the result covers the event's whole session, as session_id would. With session_id or event_id, a topic recorded with several schemas gets one entry per schema under 'schemas', each listing the 'file_ids' recorded with that schema, and every field carries its `field_id`, the ID that a session event uses to name the field. `topic_ids` holds the per-file topic IDs ('tp_', which get_entity and roboto://topic links take) where the files carry them, else the topic's 'ti_' ID, which get_entity takes but roboto://topic links do not.
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
topic_name
The name of the topic (e.g. '/imu/data'). Required, together with exactly one of dataset_id, file_id, session_id or event_id.
Clear enough to supply this argument.
dataset_id
Dataset ID (begins with 'ds_'). Reads the topic in every file of the dataset that carries it.
Clear enough to supply this argument.
file_id
File ID (begins with 'fl_'). Reads the topic in that one file. Use when the user asked about a specific file.
Clear enough to supply this argument.
session_id
Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time.
Clear enough to supply this argument.
event_id
Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_topic_schema",
"description": "Looks up a topic's schema and structure by name. Returns only the information needed to write queries for analyze_topic: topic name, schema name, message count, time range, and message paths — each with its `sql_path` (the exact, already-quoted column reference to copy verbatim into an analyze_topic sql_query), data type, and statistics. An entry without `sql_path` (a struct parent such as `header`) is not a column: query its child entries. STATS SCOPE: statistics are never limited to an analysis window. With dataset_id or file_id they span the topic's entire file(s); with session_id or event_id they span the whole session's data. Treat min/max as screening extremes: a threshold never crossed over that span was never crossed in your window, and the converse needs an in-window analyze_topic query. With session_id or event_id, statistics and message_count are absent until Roboto has computed them, and notes.statistics says why the statistics are absent. Call this before analyze_topic to learn which columns exist and how to reference them; do not guess column names from the topic name or message type. Pass topic_name and exactly one of dataset_id, file_id, session_id or event_id. When scoped to a dataset and the topic spans multiple files: if every file's schema is identical the schema is returned once with a chronologically-ordered 'file_ids' list, and every per-file value (message_count, start_time, end_time, and each statistic in a message path's metadata) is a list positionally aligned with file_ids (index i is file i); if the files' schemas differ structurally, a per-file schema is returned for each under 'schemas'. With event_id, the result covers the event's whole session, as session_id would. With session_id or event_id, a topic recorded with several schemas gets one entry per schema under 'schemas', each listing the 'file_ids' recorded with that schema, and every field carries its `field_id`, the ID that a session event uses to name the field. `topic_ids` holds the per-file topic IDs ('tp_', which get_entity and roboto://topic links take) where the files carry them, else the topic's 'ti_' ID, which get_entity takes but roboto://topic links do not.",
"inputSchema": {
"type": "object",
"required": [
"topic_name"
],
"properties": {
"file_id": {
"type": "string",
"description": "File ID (begins with 'fl_'). Reads the topic in that one file. Use when the user asked about a specific file."
},
"event_id": {
"type": "string",
"description": "Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id."
},
"dataset_id": {
"type": "string",
"description": "Dataset ID (begins with 'ds_'). Reads the topic in every file of the dataset that carries it."
},
"session_id": {
"type": "string",
"description": "Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time."
},
"topic_name": {
"type": "string",
"description": "The name of the topic (e.g. '/imu/data'). Required, together with exactly one of dataset_id, file_id, session_id or event_id."
}
}
},
"annotations": {
"title": "get_topic_schema",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Reads file content from storage. Use it for READMEs, configs, YAML, JSON and plain logs. Two hard rules. (1) NEVER use it on an ingested file: its data lives in topics; query them with analyze_topic, or search them for log text with search_logs. (2) NEVER use it on an un-ingested time-series file (a CSV or similar with a timestamp column) to answer questions about its contents or timing. The only permitted use there is to understand its structure (columns, delimiter, time column, unit) so you can tell the user the file is not ingested and help them configure ingestion: the csv_ingestion action's TIMESTAMP_KEY and TIMESTAMP_FORMAT, plus a trigger so future uploads are ingested. Reading raw rows is never a substitute for ingestion. Every result's `note` says whether the file is ingested; an ingested file's result also carries `ingestion_status` and `topic_ids`. Supports four operations:
- 'info': Returns file size, content type, and magic bytes without downloading the full file. Call this first to check whether a file is text or binary and how large it is.
- 'read_text': Returns decoded text content. Use 'offset' and 'length' to read specific byte ranges for large files.
- 'search': Searches file content for a regex pattern and returns matching lines with line numbers.
- 'view_image': Shows you the file as an image: PNG, JPEG, GIF, WebP, BMP or TIFF, at most 25 MB, scaled to fit. Use it for screenshots, photos and plots that are files. Frames of an image or video topic are not files; use get_keyframes or analyze_video for those.
The description explains the capability, when to use it, and what to expect.
Inputs79/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
file_id
The universally unique ID for the file. Always begins with 'fl_'.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
operation
The operation to perform. 'info' returns file metadata (size, content type, magic bytes). 'read_text' returns decoded file content. 'search' finds lines matching a regex pattern. 'view_image' shows you the file as an image.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
offset
Byte offset to start reading from (for read_text). Defaults to 0. Use with 'length' to page through large files.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
length
Maximum number of bytes to read (for read_text). Defaults to 48000. Capped at 48000.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
encoding
Text encoding for read_text. Defaults to 'utf-8'.
Clear enough to supply this argument.
pattern
Regex pattern to search for (required for 'search' operation).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
{
"title": "load_file",
"readOnlyHint": true
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "load_file",
"description": "Reads file content from storage. Use it for READMEs, configs, YAML, JSON and plain logs. Two hard rules. (1) NEVER use it on an ingested file: its data lives in topics; query them with analyze_topic, or search them for log text with search_logs. (2) NEVER use it on an un-ingested time-series file (a CSV or similar with a timestamp column) to answer questions about its contents or timing. The only permitted use there is to understand its structure (columns, delimiter, time column, unit) so you can tell the user the file is not ingested and help them configure ingestion: the csv_ingestion action's TIMESTAMP_KEY and TIMESTAMP_FORMAT, plus a trigger so future uploads are ingested. Reading raw rows is never a substitute for ingestion. Every result's `note` says whether the file is ingested; an ingested file's result also carries `ingestion_status` and `topic_ids`. Supports four operations:\n- 'info': Returns file size, content type, and magic bytes without downloading the full file. Call this first to check whether a file is text or binary and how large it is.\n- 'read_text': Returns decoded text content. Use 'offset' and 'length' to read specific byte ranges for large files.\n- 'search': Searches file content for a regex pattern and returns matching lines with line numbers.\n- 'view_image': Shows you the file as an image: PNG, JPEG, GIF, WebP, BMP or TIFF, at most 25 MB, scaled to fit. Use it for screenshots, photos and plots that are files. Frames of an image or video topic are not files; use get_keyframes or analyze_video for those.",
"inputSchema": {
"type": "object",
"required": [
"file_id",
"operation"
],
"properties": {
"length": {
"type": "integer",
"description": "Maximum number of bytes to read (for read_text). Defaults to 48000. Capped at 48000."
},
"offset": {
"type": "integer",
"description": "Byte offset to start reading from (for read_text). Defaults to 0. Use with 'length' to page through large files."
},
"file_id": {
"type": "string",
"description": "The universally unique ID for the file. Always begins with 'fl_'."
},
"pattern": {
"type": "string",
"description": "Regex pattern to search for (required for 'search' operation)."
},
"encoding": {
"type": "string",
"description": "Text encoding for read_text. Defaults to 'utf-8'."
},
"operation": {
"enum": [
"info",
"read_text",
"search",
"view_image"
],
"type": "string",
"description": "The operation to perform. 'info' returns file metadata (size, content type, magic bytes). 'read_text' returns decoded file content. 'search' finds lines matching a regex pattern. 'view_image' shows you the file as an image."
}
}
},
"annotations": {
"title": "load_file",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Returns the org's vocabulary: the distinct tags on datasets, files, devices, and sessions; the top-level
metadata keys on datasets, files, and devices (sessions have no metadata field to search); topic names; event
names; every Ready custom field as `<entity>.custom.<name>`; and the metric names. Use it to ground
`search_roboto` queries and `aggregate_metric` calls in the org's actual spellings instead of guessing. A
`<entity>.custom.<name>` path is written as `custom.<name>` on that entity's own search target, and with the
entity prefix in an `aggregate_metric` condition (session, device, and collection fields only). A session
custom field is also written as `session.custom.<name>` on the datasets, files, and topics search targets. A
metric name is `metric.<name>` on the sessions search target and `name` in `aggregate_metric`.
Any slot may come back empty with a sibling `<slot>_error` if its query exceeds the per-slot timeout or hits a
transient database error. Treat that slot as unknown and answer using the rest; do not retry.
`<slot>_truncated: true` means the slot hit the per-slot cap, so an absent value may still exist in the org.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No input fields.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_org_vocabulary",
"description": "Returns the org's vocabulary: the distinct tags on datasets, files, devices, and sessions; the top-level\nmetadata keys on datasets, files, and devices (sessions have no metadata field to search); topic names; event\nnames; every Ready custom field as `<entity>.custom.<name>`; and the metric names. Use it to ground\n`search_roboto` queries and `aggregate_metric` calls in the org's actual spellings instead of guessing. A\n`<entity>.custom.<name>` path is written as `custom.<name>` on that entity's own search target, and with the\nentity prefix in an `aggregate_metric` condition (session, device, and collection fields only). A session\ncustom field is also written as `session.custom.<name>` on the datasets, files, and topics search targets. A\nmetric name is `metric.<name>` on the sessions search target and `name` in `aggregate_metric`.\n\nAny slot may come back empty with a sibling `<slot>_error` if its query exceeds the per-slot timeout or hits a\ntransient database error. Treat that slot as unknown and answer using the rest; do not retry.\n`<slot>_truncated: true` means the slot hit the per-slot cap, so an absent value may still exist in the org.",
"inputSchema": {
"type": "object",
"required": [],
"properties": {}
},
"annotations": {
"title": "get_org_vocabulary",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description74/100
Resolve (field, timestamp) references into Roboto message-path ids and roboto:// links that open that data in the Roboto web app.
Describe the returned information so the agent knows whether it will answer the request.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
topic_id
The ID of a topic in one file (begins with 'tp_'). Overrides `dataset_id`. Use when you already have the ID from a prior tool call or user context. A 'ti_' ID (from list_related(session -> topic), a session event or a session-scoped result) names a topic across every file that carries it. This tool refuses it, and changing its prefix does not turn it into a 'tp_' ID: pass the topic's name as `topic_name` with `dataset_id` or `session_id` instead.
Clear enough to supply this argument.
dataset_id
The dataset ID (begins with 'ds_') to resolve within. Required unless `topic_id` is provided; omit it with `session_id`.
Clear enough to supply this argument.
session_id
Session ID (begins with 'se_'). Each result carries the IDs a session event takes: `topic_id`, the topic's 'ti_' ID, and `field_id`, the 'sf_' ID of the path in the schema that the session's data has at the citation's timestamp. Every citation needs a `topic_name`. Do not combine with `topic_id` or `dataset_id`.
Clear enough to supply this argument.
topic_name
Default topic name (e.g. '/imu/data') for queries that don't set their own.
Clear enough to supply this argument.
queries › items › msgpath_name
The message path name to resolve (e.g. 'voltage', 'pose.x').
Clear enough to supply this argument.
queries › items › timestamp
The timestamp in nanoseconds since epoch. Always required. With `dataset_id` or `topic_id` it picks the topic chunk and must lie within 1 s of that chunk's time range; with `session_id` it picks the schema the session's data has at that time and must lie within 1 s of the topic's data in the session.
Clear enough to supply this argument.
queries › items › topic_name
Topic name for this citation; overrides the call-level one.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "resolve_msgpath_ids",
"description": "Resolve (field, timestamp) references into Roboto message-path ids and roboto:// links that open that data in the Roboto web app.",
"inputSchema": {
"type": "object",
"required": [
"queries"
],
"properties": {
"queries": {
"type": "array",
"items": {
"type": "object",
"required": [
"msgpath_name",
"timestamp"
],
"properties": {
"timestamp": {
"type": "integer",
"description": "The timestamp in nanoseconds since epoch. Always required. With `dataset_id` or `topic_id` it picks the topic chunk and must lie within 1 s of that chunk's time range; with `session_id` it picks the schema the session's data has at that time and must lie within 1 s of the topic's data in the session."
},
"topic_name": {
"type": "string",
"description": "Topic name for this citation; overrides the call-level one."
},
"msgpath_name": {
"type": "string",
"description": "The message path name to resolve (e.g. 'voltage', 'pose.x')."
}
}
},
"minItems": 1,
"description": "Batched citations to resolve. Results are returned in the same order."
},
"topic_id": {
"type": "string",
"description": "The ID of a topic in one file (begins with 'tp_'). Overrides `dataset_id`. Use when you already have the ID from a prior tool call or user context. A 'ti_' ID (from list_related(session -> topic), a session event or a session-scoped result) names a topic across every file that carries it. This tool refuses it, and changing its prefix does not turn it into a 'tp_' ID: pass the topic's name as `topic_name` with `dataset_id` or `session_id` instead."
},
"dataset_id": {
"type": "string",
"description": "The dataset ID (begins with 'ds_') to resolve within. Required unless `topic_id` is provided; omit it with `session_id`."
},
"session_id": {
"type": "string",
"description": "Session ID (begins with 'se_'). Each result carries the IDs a session event takes: `topic_id`, the topic's 'ti_' ID, and `field_id`, the 'sf_' ID of the path in the schema that the session's data has at the citation's timestamp. Every citation needs a `topic_name`. Do not combine with `topic_id` or `dataset_id`."
},
"topic_name": {
"type": "string",
"description": "Default topic name (e.g. '/imu/data') for queries that don't set their own."
}
}
},
"annotations": {
"title": "resolve_msgpath_ids",
"readOnlyHint": true
}
}
Add a human-readable title so users do not have to interpret the tool identifier.
Description95/100
Searches Roboto's metadata index — the catalog of datasets, files, topics, events, collections, devices, sessions, and topic message paths — for entities matching a RoboQL query. It matches indexed properties (names, tags, metadata, timestamps, entity relations, and per-file message-path statistics); it never reads topic data itself. Routing: use this tool to discover WHICH entities matter ('find the files/datasets where X'), search_logs to grep log/message text inside topics already in scope, and analyze_topic to compute over in-window values. Preferred: `roboql` with `target`, executed verbatim with no translation. Fall back to `prompt` (translated to RoboQL server-side) when either the field names or the syntax for addressing them is unknown — knowing a field's name is not the same as knowing how to write a condition against it; use `term` for a quick substring scan instead of a query.
RoboQL: `<field> <op> <value>` conditions with =, !=, >, >=, <, <=, CONTAINS, NOT_CONTAINS, BEGINS_WITH, LIKE or NOT_LIKE (SQL syntax, % wildcards), and `<field> <op>` conditions with IS_NOT_NULL or IS_NULL, which take no value and follow the field. EXISTS and NOT_EXISTS mean the same as IS_NOT_NULL and IS_NULL but work only on metadata and boolean fields. Combine conditions with AND/OR and parentheses; values in double quotes; timestamp fields (created, modified, min_timestamp_ns/start_time, max_timestamp_ns/end_time) accept ISO-8601 strings: write min_timestamp_ns >= "2026-08-01" rather than computing epoch nanoseconds, or integer nanoseconds unquoted on the _ns/start_time/end_time fields (quoted numbers and decimals are rejected); optional trailing `SORT BY <field> ASC|DESC` and `LIMIT n`. CONTAINS on a text field is a case-insensitive substring match, so partial words match ("temp" also finds "temperature" and "attempt"); on tags it matches one whole tag exactly, case included. A query must start with a condition or `*` (match all) — target=datasets, `* SORT BY created DESC LIMIT 10` is the newest ten (sessions sort by min_timestamp_ns, not created). Files and datasets can filter on their related topics and message paths. Datasets, files, and topics can filter on the sessions they belong to with session.session_id, session.name, session.tags, and session.custom.<name>. A positive condition matches an entity in at least one session that satisfies it. A negated condition (!=, NOT_CONTAINS, NOT_LIKE, IS_NULL) matches an entity in no session that satisfies the positive form, including an entity in no session at all. A topic belongs to a session only when the session holds the topic's data, so a session that holds part of a file may hold only some of that file's topics. Sessions can filter on the topics and files they hold with topic.name and file.file_id (target=sessions, topic.name = "/gps"). Files carry their own tags/metadata, separate from their dataset's — to scope files by the containing dataset, use the dataset. prefix (dataset.tags, dataset.metadata, dataset.name), not bare `tags`.
Metadata keys use a dotted path: metadata.<key> (e.g. metadata.release = "1.2.3"), and dataset.metadata.<key> for a file's dataset.
Filter sessions on a metric's value with metric.<name> and a numeric comparison, where <name> is a metric_definition's whole name, dots included (metric.speed.max_mps > 10); list_related(related_type='metric_definition') lists the org's metric names.
Message-path statistics are pre-indexed at ingestion: count, max, mean, median, min, p25, p75, p95, p99, stddev for numeric paths; true_count, false_count for booleans, referenced as topics[t].msgpaths[<topic_name>.<message_path>].<stat>. The bracketed 't' is a correlation label, not a positional index: each label binds ONE topic instance, so conditions sharing a label must name the same <topic_name> (different topic names under one label is rejected — use a second label, e.g. topics[1], for a different topic). Conditions on the SAME msgpaths[...] reference apply to one message-path instance (so `topics[0].msgpaths[cpuload.load].mean > 0.2 AND topics[0].msgpaths[cpuload.load].mean < 0.8` expresses a range). On target=topics drop the topics[t]. prefix but keep the topic name: msgpaths[<topic_name>.<message_path>].<stat>. For fleet-wide questions ("has X ever happened across these datasets/devices?"), query the statistics FIRST to find which files matter, then run analyze_topic on only those files. Examples:
- target=files, roboql: topics[0].msgpaths[ConsoleLog.Hpp_OverSpeed].true_count > 0 (a boolean was ever TRUE)
- target=files, roboql: dataset.tags CONTAINS "site-A" AND topics[0].msgpaths[/vehicle/status.speed].max > 20 (a value ever exceeded a threshold, in files whose dataset carries a tag)
- target=datasets, roboql: tags CONTAINS "site-A" AND created > "2026-01-01"
- target=datasets, roboql: (summary CONTAINS "overheat" OR summary CONTAINS "over-temperature") AND created > "2026-09-15" (prior AI analysis of each dataset mentions it: check summaries first for questions about past incidents. They match text, not meaning, so OR the likely phrasings; a stem ("collaps") catches every form of a word. The lines that matched come back in summary_matches; dataset.summary filters files, topics, and events too)
- target=files, roboql: metadata.release = "2.4.0" (a file metadata key, dotted)
- target=files, roboql: metadata.recording IS_NOT_NULL (files whose metadata holds a non-null value for the key)
- target=files, roboql: session.tags CONTAINS "night-run" (files in at least one session with that tag)
- target=sessions, roboql: device.device_id = "rover-01" AND start_time >= "2026-06-01" AND metric.gripper_stall_count > 20 SORT BY min_timestamp_ns ASC (one device's sessions since a date where a metric's value exceeded a threshold, oldest first; each result carries its device_ids and custom_fields, and list_related(session -> metric) reads all of a session's metric values. custom.<name> filters on a custom field the same way on any target that has one: custom.site = "pier")
Cost: `=` on an id field, ANDed with the rest of the query, uses an index and is usually fast even in a large org. The id fields are dataset_id and device_id on datasets; file_id, dataset_id and device_id on files; topic_id and file_id on topics. Topics have no dataset_id or device_id: use dataset.dataset_id or dataset.device_id. On topics, dataset.<id field> and file.<id field> use the index too, and on files, dataset.<id field> and topics[x].topic_id. A top-level metadata.<key> = with a text value, ANDed the same way on datasets, files or topics, can use an index too. Other metadata conditions (nested keys, BEGINS_WITH, CONTAINS, ranges, numeric or true/false values, metadata on other targets) read every row's metadata and can take minutes in a large org. When an id field and a metadata key hold the same value (a device id often appears in both), filter on the id field: the same rows, faster. Check that the entity you filter carries the id: ingested files often have no device_id while their dataset does. Never drop or loosen a condition to save time.
Caveats: statistics are whole-file (use analyze_topic afterwards for time-window precision), and only exist for ingested files — files ingested without statistics will not match stat conditions. If the query fails to parse, the error lists the valid fields for your target. On the first page, and on the last page a LIMIT allows, total_count is included whenever more results may exist than were returned, unless counting them times out (the `note` field then says so); page through them with page_token. LIMIT caps the total results across all pages; each page holds at most 50 rows; total_count is the number of matches before LIMIT. A page is a subset, so a name missing from it never proves the entity is absent: to check whether something exists, query it by name (e.g. target=topics, topic_name = "gps") instead of listing and scanning.
On a zero-result stat query, the response includes a `coverage` field stating, per referenced message path, whether its statistics are indexed anywhere in scope. A zero-result the coverage field calls definitive answers ever/never questions authoritatively — do not re-verify it with analyze_topic. A zero-result it calls NOT definitive is not evidence of absence at all: the statistics needed to answer were missing, so check with analyze_topic instead of reporting a negative. When it says the topic, path, or statistic does not exist, fix the query from what it lists and retry.
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
roboql
RoboQL query to execute verbatim: conditions with optional SORT BY / LIMIT. Provide together with 'target'.
Clear enough to supply this argument.
term
Single substring matched case-insensitively against the target entity's indexed text fields (tags must match whole); no RoboQL parsing. Provide together with 'target'.
Clear enough to supply this argument.
target
The entity type to search. Required with 'roboql' or 'term'; ignored with 'prompt' (the translator chooses the target).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
prompt
Natural-language search request, translated to RoboQL server-side. Use only when field names are unknown.
Clear enough to supply this argument.
page_token
next_token from a previous response, to fetch the following page.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "search_roboto",
"description": "Searches Roboto's metadata index — the catalog of datasets, files, topics, events, collections, devices, sessions, and topic message paths — for entities matching a RoboQL query. It matches indexed properties (names, tags, metadata, timestamps, entity relations, and per-file message-path statistics); it never reads topic data itself. Routing: use this tool to discover WHICH entities matter ('find the files/datasets where X'), search_logs to grep log/message text inside topics already in scope, and analyze_topic to compute over in-window values. Preferred: `roboql` with `target`, executed verbatim with no translation. Fall back to `prompt` (translated to RoboQL server-side) when either the field names or the syntax for addressing them is unknown — knowing a field's name is not the same as knowing how to write a condition against it; use `term` for a quick substring scan instead of a query.\n\nRoboQL: `<field> <op> <value>` conditions with =, !=, >, >=, <, <=, CONTAINS, NOT_CONTAINS, BEGINS_WITH, LIKE or NOT_LIKE (SQL syntax, % wildcards), and `<field> <op>` conditions with IS_NOT_NULL or IS_NULL, which take no value and follow the field. EXISTS and NOT_EXISTS mean the same as IS_NOT_NULL and IS_NULL but work only on metadata and boolean fields. Combine conditions with AND/OR and parentheses; values in double quotes; timestamp fields (created, modified, min_timestamp_ns/start_time, max_timestamp_ns/end_time) accept ISO-8601 strings: write min_timestamp_ns >= \"2026-08-01\" rather than computing epoch nanoseconds, or integer nanoseconds unquoted on the _ns/start_time/end_time fields (quoted numbers and decimals are rejected); optional trailing `SORT BY <field> ASC|DESC` and `LIMIT n`. CONTAINS on a text field is a case-insensitive substring match, so partial words match (\"temp\" also finds \"temperature\" and \"attempt\"); on tags it matches one whole tag exactly, case included. A query must start with a condition or `*` (match all) — target=datasets, `* SORT BY created DESC LIMIT 10` is the newest ten (sessions sort by min_timestamp_ns, not created). Files and datasets can filter on their related topics and message paths. Datasets, files, and topics can filter on the sessions they belong to with session.session_id, session.name, session.tags, and session.custom.<name>. A positive condition matches an entity in at least one session that satisfies it. A negated condition (!=, NOT_CONTAINS, NOT_LIKE, IS_NULL) matches an entity in no session that satisfies the positive form, including an entity in no session at all. A topic belongs to a session only when the session holds the topic's data, so a session that holds part of a file may hold only some of that file's topics. Sessions can filter on the topics and files they hold with topic.name and file.file_id (target=sessions, topic.name = \"/gps\"). Files carry their own tags/metadata, separate from their dataset's — to scope files by the containing dataset, use the dataset. prefix (dataset.tags, dataset.metadata, dataset.name), not bare `tags`.\n\nMetadata keys use a dotted path: metadata.<key> (e.g. metadata.release = \"1.2.3\"), and dataset.metadata.<key> for a file's dataset.\n\nFilter sessions on a metric's value with metric.<name> and a numeric comparison, where <name> is a metric_definition's whole name, dots included (metric.speed.max_mps > 10); list_related(related_type='metric_definition') lists the org's metric names.\n\nMessage-path statistics are pre-indexed at ingestion: count, max, mean, median, min, p25, p75, p95, p99, stddev for numeric paths; true_count, false_count for booleans, referenced as topics[t].msgpaths[<topic_name>.<message_path>].<stat>. The bracketed 't' is a correlation label, not a positional index: each label binds ONE topic instance, so conditions sharing a label must name the same <topic_name> (different topic names under one label is rejected — use a second label, e.g. topics[1], for a different topic). Conditions on the SAME msgpaths[...] reference apply to one message-path instance (so `topics[0].msgpaths[cpuload.load].mean > 0.2 AND topics[0].msgpaths[cpuload.load].mean < 0.8` expresses a range). On target=topics drop the topics[t]. prefix but keep the topic name: msgpaths[<topic_name>.<message_path>].<stat>. For fleet-wide questions (\"has X ever happened across these datasets/devices?\"), query the statistics FIRST to find which files matter, then run analyze_topic on only those files. Examples:\n- target=files, roboql: topics[0].msgpaths[ConsoleLog.Hpp_OverSpeed].true_count > 0 (a boolean was ever TRUE)\n- target=files, roboql: dataset.tags CONTAINS \"site-A\" AND topics[0].msgpaths[/vehicle/status.speed].max > 20 (a value ever exceeded a threshold, in files whose dataset carries a tag)\n- target=datasets, roboql: tags CONTAINS \"site-A\" AND created > \"2026-01-01\"\n- target=datasets, roboql: (summary CONTAINS \"overheat\" OR summary CONTAINS \"over-temperature\") AND created > \"2026-09-15\" (prior AI analysis of each dataset mentions it: check summaries first for questions about past incidents. They match text, not meaning, so OR the likely phrasings; a stem (\"collaps\") catches every form of a word. The lines that matched come back in summary_matches; dataset.summary filters files, topics, and events too)\n- target=files, roboql: metadata.release = \"2.4.0\" (a file metadata key, dotted)\n- target=files, roboql: metadata.recording IS_NOT_NULL (files whose metadata holds a non-null value for the key)\n- target=files, roboql: session.tags CONTAINS \"night-run\" (files in at least one session with that tag)\n- target=sessions, roboql: device.device_id = \"rover-01\" AND start_time >= \"2026-06-01\" AND metric.gripper_stall_count > 20 SORT BY min_timestamp_ns ASC (one device's sessions since a date where a metric's value exceeded a threshold, oldest first; each result carries its device_ids and custom_fields, and list_related(session -> metric) reads all of a session's metric values. custom.<name> filters on a custom field the same way on any target that has one: custom.site = \"pier\")\n\nCost: `=` on an id field, ANDed with the rest of the query, uses an index and is usually fast even in a large org. The id fields are dataset_id and device_id on datasets; file_id, dataset_id and device_id on files; topic_id and file_id on topics. Topics have no dataset_id or device_id: use dataset.dataset_id or dataset.device_id. On topics, dataset.<id field> and file.<id field> use the index too, and on files, dataset.<id field> and topics[x].topic_id. A top-level metadata.<key> = with a text value, ANDed the same way on datasets, files or topics, can use an index too. Other metadata conditions (nested keys, BEGINS_WITH, CONTAINS, ranges, numeric or true/false values, metadata on other targets) read every row's metadata and can take minutes in a large org. When an id field and a metadata key hold the same value (a device id often appears in both), filter on the id field: the same rows, faster. Check that the entity you filter carries the id: ingested files often have no device_id while their dataset does. Never drop or loosen a condition to save time.\n\nCaveats: statistics are whole-file (use analyze_topic afterwards for time-window precision), and only exist for ingested files — files ingested without statistics will not match stat conditions. If the query fails to parse, the error lists the valid fields for your target. On the first page, and on the last page a LIMIT allows, total_count is included whenever more results may exist than were returned, unless counting them times out (the `note` field then says so); page through them with page_token. LIMIT caps the total results across all pages; each page holds at most 50 rows; total_count is the number of matches before LIMIT. A page is a subset, so a name missing from it never proves the entity is absent: to check whether something exists, query it by name (e.g. target=topics, topic_name = \"gps\") instead of listing and scanning.\n\nOn a zero-result stat query, the response includes a `coverage` field stating, per referenced message path, whether its statistics are indexed anywhere in scope. A zero-result the coverage field calls definitive answers ever/never questions authoritatively — do not re-verify it with analyze_topic. A zero-result it calls NOT definitive is not evidence of absence at all: the statistics needed to answer were missing, so check with analyze_topic instead of reporting a negative. When it says the topic, path, or statistic does not exist, fix the query from what it lists and retry.",
"inputSchema": {
"type": "object",
"required": [],
"properties": {
"term": {
"type": "string",
"description": "Single substring matched case-insensitively against the target entity's indexed text fields (tags must match whole); no RoboQL parsing. Provide together with 'target'."
},
"prompt": {
"type": "string",
"description": "Natural-language search request, translated to RoboQL server-side. Use only when field names are unknown."
},
"roboql": {
"type": "string",
"description": "RoboQL query to execute verbatim: conditions with optional SORT BY / LIMIT. Provide together with 'target'."
},
"target": {
"enum": [
"collections",
"datasets",
"devices",
"files",
"sessions",
"topics",
"topic_message_paths",
"events"
],
"type": "string",
"description": "The entity type to search. Required with 'roboql' or 'term'; ignored with 'prompt' (the translator chooses the target)."
},
"page_token": {
"type": "string",
"description": "next_token from a previous response, to fetch the following page."
}
}
},
"annotations": {
"title": "search_roboto",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Counts how many collections, datasets, devices, events, files or sessions a RoboQL scope matches (the whole org when roboql is omitted) hold each value of one field, most common first, in one call instead of paging search_roboto results. field is a name search_roboto sorts that entity type by, one of its aliases, or custom.<name> for a ready custom field (files have none). matched is how many entities the scope matches, without_value how many of them hold no value, and distinct_values how many values they hold. A number or time field, such as duration, start_time, created or a custom number or timestamp field, returns its min and max instead of counts, and ignores limit and time_field. On sessions and events each counted value also carries first_time and last_time, the earliest and latest start_time among the entities holding it, which shows when a value changed; two values whose spans overlap alternated rather than changed once. time_field takes these times from another time field, such as created on datasets, devices, files and collections, or end_time on events. roboql is a search_roboto condition for the same entity type, without SORT BY or LIMIT, e.g. device.device_id = "rover-01" for sessions; list_related(related_type="custom_field") names the custom fields.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
4 of 5 inputs have descriptions. Add descriptions to the missing inputs, including where to obtain IDs and how to supply values.
entity_type
Not provided
Missing description. Explain what this value means and how to supply it.
field
A field entity_type sorts by, one of its aliases, or custom.<name>.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
roboql
Optional search_roboto condition that narrows which entities are counted.
Clear enough to supply this argument.
limit
Most distinct values to return; default 50.
Clear enough to supply this argument.
time_field
The time field first_time and last_time come from; default start_time on sessions and events, none elsewhere.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_field_values",
"description": "Counts how many collections, datasets, devices, events, files or sessions a RoboQL scope matches (the whole org when roboql is omitted) hold each value of one field, most common first, in one call instead of paging search_roboto results. field is a name search_roboto sorts that entity type by, one of its aliases, or custom.<name> for a ready custom field (files have none). matched is how many entities the scope matches, without_value how many of them hold no value, and distinct_values how many values they hold. A number or time field, such as duration, start_time, created or a custom number or timestamp field, returns its min and max instead of counts, and ignores limit and time_field. On sessions and events each counted value also carries first_time and last_time, the earliest and latest start_time among the entities holding it, which shows when a value changed; two values whose spans overlap alternated rather than changed once. time_field takes these times from another time field, such as created on datasets, devices, files and collections, or end_time on events. roboql is a search_roboto condition for the same entity type, without SORT BY or LIMIT, e.g. device.device_id = \"rover-01\" for sessions; list_related(related_type=\"custom_field\") names the custom fields.",
"inputSchema": {
"type": "object",
"required": [
"entity_type",
"field"
],
"properties": {
"field": {
"type": "string",
"description": "A field entity_type sorts by, one of its aliases, or custom.<name>."
},
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Most distinct values to return; default 50."
},
"roboql": {
"type": "string",
"description": "Optional search_roboto condition that narrows which entities are counted."
},
"time_field": {
"type": "string",
"description": "The time field first_time and last_time come from; default start_time on sessions and events, none elsewhere."
},
"entity_type": {
"enum": [
"collection",
"dataset",
"device",
"event",
"file",
"session"
],
"type": "string"
}
}
},
"annotations": {
"title": "get_field_values",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description88/100
Loads topic data and executes a SQL query against it. Successful results are a text payload: `key: <json>` preamble lines, a blank line, then a TOON block `data[N]{col1,col2,...}:` with N indented comma-separated rows (`null`, `true`/`false`, bare numbers, double-quoted strings with backslash escapes for `"`, `\` and newlines). The preamble always carries `topic_ids` (each topic's per-file 'tp_' IDs, which get_entity and roboto://topic links take; for a topic whose files carry none, its 'ti_' ID, which get_entity takes but roboto://topic links do not), `coverage` (the dataset, file or session read, plus the event for an event_id; `files`, how many files it read; `files_read`, up to 50 of them with their datasets; `files_capped: true` when a dataset read stopped at 200 files) and, when known, `window` (the span the load drew from: the topic's data in that scope, clamped to the analysis scope and start_time/end_time, not the rows your WHERE kept). Every call covers exactly one scope: read these before combining results across calls, and report the scope you actually covered rather than the one you were asked about. Failures after topic resolution lead with the same lines. FACTS: `spans` gives `window`, `matched` (start, end, duration_ns, rows and, up to 2M rows, distinct_ts of every row the query matched) and `returned` (the rows shown: in time order they cover `returned.duration_ns` of `matched.duration_ns`; in any other order, such as a top-N by value, only their count, since their times measure no duration). `matched` runs from the first match to the last across every episode between: for how long a condition holds, use recipe runs_outside. When more rows matched than are shown, the preamble adds `total_matching_rows` and, up to 4M matching rows (above, a note says how to aggregate instead), `profile` (per numeric column, over ALL matched rows: n, nulls, min and max, the first time each extreme occurs and how many rows hold it; `max_n: 1` is a single-sample spike), `shape` (8 equal-time buckets of n, min, avg, max for the first 2 numeric columns over ALL matched rows: state trends, steadiness, onsets and dips from `shape` or aggregation, never from the rows shown) and `values` (per low-cardinality column: every value with its row count and first/last row time; on change-on-publish topics a value persists past its last row); `data` holds the first rows in your query's order and, only for time-ordered results, `data_tail` the last rows after a `... K rows omitted (t_from -> t_to, d=<ns>ns) ...` line. Shown rows are a sample: never infer absence, trend, peak, onset or steadiness from them alone; use the facts, check `data_tail`, or aggregate. `rows` and `n` count rows, not samples: compare them with `distinct_ts` (UNNEST and pooled files repeat timestamps). A `note:` names your SQL's own LIMIT (the facts count past it), explains an empty result (no rows in the window, or your WHERE excluded every row: then verify literal values with a small unfiltered sample), and flags a time literal that fits the data only in another unit. TIME WINDOWS: instants and durations are int ns (name instants `*_ts`, durations `*_ns`; `d=<ns>ns` in text); state durations in replies in readable units (ms, s, min) converted from these ns values. To look at a window, pass start_time/end_time, which narrow the load itself, rather than WHERE literals, LIMIT or `% N` sampling; aggregate (recipe bucket_stats or rate_over_time) over long spans. anchors adds `<name>_offset_ns` columns (negative = before the anchor) and offsets in spans and profile, so never subtract timestamps by hand. Failures are returned as a plain human-readable error message, which — when the topics' columns are known — appends each table's available columns (capped for very wide topics) so you can correct a mistyped column and retry. That list reflects what was ACTUALLY loaded: nested columns carry their element type — `(STRUCT[])` means UNNEST it and access item."field"; an object array shown as `(JSON[])` or `(INTEGER[])` had no elements in the queried window (treat it as empty; do not retry other syntaxes); columns marked `(unnest)` are array-nested and only reachable via UNNEST (see ARRAY HANDLING). A topic is always identified by topic_name, scoped by exactly one of dataset_id, file_id, session_id or event_id. With a dataset_id, all files in the dataset containing that topic are loaded together. With a session_id, only the session's data is loaded: its files, which can come from several datasets, and of each file only the part the session holds. With an event_id, the event's session is loaded, over the event's time span unless start_time or end_time is given. With dataset_id, session_id or event_id, the load has a 'filename' column naming each row's file and a `_roboto_file_id` column with the file's ID, even when it reads one file; with file_id it has neither. A dataset's data stored before topic partitions existed has only 'filename'. Relative paths can repeat across a session's datasets, so group by `_roboto_file_id` when it is present. When files disagree about a column, the load reconciles it instead of failing: a column missing from some files is NULL there, and one typed inconsistently across files is loaded as text — you get a `note:` naming it with its per-file types, and must TRY_CAST before comparing or aggregating it numerically (as text, MAX and ORDER BY compare lexicographically). Naming such a column in message_paths fails the call instead, with the per-file types, so drop it from message_paths to get it as text. With a file_id, only that single file's instance of the topic is loaded. RECIPES: for common analyses (state transitions, dwell time, per-bucket stats, rate over time, threshold crossings, timestamp gaps, derivatives, moving averages, array unnest) prefer the `recipe` parameter over hand-written SQL — see its parameter description for the recipe menu. MULTI-TOPIC JOINS: join_topics loads up to 4 additional topics from the same scope as extra SQL tables named by your aliases, next to the primary topic's `df`. All tables are identically time-scoped and each carries its own `_roboto_timestamp`. Time-align streams with ASOF JOIN, which pairs each left row with the nearest right row at-or-before it: SELECT d._roboto_timestamp, d."state", o."pose.x" FROM df d ASOF JOIN odom o ON d._roboto_timestamp >= o._roboto_timestamp. Prefer one joined query over separate calls when correlating signals across topics. ALWAYS call get_topic_schema for every topic you query (including join_topics entries) before querying it, and reference columns only by the exact `sql_path` it returns. Do NOT guess column names from the topic name or from message-type conventions (e.g. assuming a ROS-style `status.level` or `is_charging`) — guessed names are the most common cause of query failures. COLUMNS: nested message fields are flattened to dot-delimited names that MUST be double-quoted in SQL, e.g. SELECT "fault.message" FROM df. An unquoted dotted name like fault.message is parsed as table.column and fails, since the only table is `df`. Copy the `sql_path` from get_topic_schema verbatim — it is already quoted correctly. A scalar field (e.g. `status`) has no sub-fields; do not append `.level`/`.status` to it. TIMESTAMPS: `_roboto_timestamp` is BIGINT nanoseconds since the Unix epoch (UTC). All times must be BIGINT nanoseconds — convert if necessary. `_roboto_timestamp` is INTERNAL — it exists only inside this tool's DuckDB view, not in any user file and not in the Roboto SDK. Never reference it in user-facing replies, code suggestions, or SDK examples. If a user asks about it, say it is a tool-internal synthesized column, not part of their data. ARRAY HANDLING: Fields with requires_unnest=true must be unnested before accessing nested fields. The dotted child names under an array (e.g. `status_list.status`) are NOT columns — always UNNEST the array column itself: WITH exploded AS (SELECT UNNEST("array_field") AS item FROM df) SELECT item."field" FROM exploded. Lists are 1-based in SQL: element 0 of a message field (`esc[0]` in ROS/PX4 names) is `"esc"[1]`, and a query that reads a list at `[0]` is rejected. To work per element, number the elements in the same SELECT as the UNNEST, converted to the message's 0-based numbering: WITH e AS (SELECT UNNEST("esc") AS item, generate_subscripts("esc", 1) - 1 AS slot FROM df) SELECT slot, MAX(item."esc_rpm") FROM e GROUP BY slot. Positions from WITH ORDINALITY and list_position also start at 1. A slice `x[a:b]` includes element b. JSON values stay 0-based: `j[0]` and `'$[0]'` are the first element.
The description explains the capability, when to use it, and what to expect.
Inputs90/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
topic_name
The name of the topic to query (e.g. '/camera/image', '/imu/data'). Always required. Scoped by exactly one of dataset_id, file_id, session_id or event_id.
Clear enough to supply this argument.
dataset_id
Dataset ID (begins with 'ds_'). Reads the topic in every file of the dataset that carries it.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
file_id
File ID (begins with 'fl_'). Reads the topic in that one file. Use when the user asked about a specific file.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
session_id
Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
event_id
Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id.
Clear enough to supply this argument.
sql_query
A SQL query that will be run against the topic's table as a Pandas DataFrame. The primary topic's table is named `df`; each join_topics entry is additionally available under its alias. Reference columns by the exact `sql_path` from get_topic_schema: nested fields are flattened dot-paths that must be double-quoted (e.g. "fault.message"), since an unquoted dotted name is read as table.column and fails. Do not add LIMIT or `% N` sampling to see a window: narrow it with start_time/end_time, or aggregate (recipe bucket_stats or rate_over_time). To bucket time in SQL, divide with integer `//` (`/` is float division), clamp the last sample into the last bucket (LEAST(i, n - 1)), and select each bucket's start as a `*_ts` column (bucket_start_ts), not a bare bucket number, so spans and offsets exist. There is always a message path marked with canonical_time: true. Use it as the default timestamp. With dataset_id, session_id or event_id, df has a 'filename' column to filter or group by. Provide exactly one of sql_query or recipe.
Clear enough to supply this argument.
recipe
Name of a built-in analysis recipe to run instead of writing sql_query; the server expands it into vetted SQL over `df`. Prefer a recipe over hand-written SQL whenever one matches the analysis. Recipes: transitions(column) — rows where the column's value changes, with from/to values and the gap since the previous sample; dwell_time(column) — duration of each run of equal values, run start to next run's start (the final run is open: null end_ts and dwell_ns); sample_count and max_gap_ns expose unobserved stretches; rate_over_time(bucket_ns) — message count and Hz per fixed time bucket; bucket_stats(column, buckets[, by]) — n, min_value, avg_value, max_value (min_at/max_at: when each extreme first occurs) of a numeric or array column in `buckets` (1-50) equal-time buckets from first to last sample, NULL/NaN skipped, with bucket_start_ts, bucket_last_ts, bucket_offset_ns, bucket_ns; by (a label array zipped with an array column, e.g. by=name, column=effort) adds key: buckets x labels rows, 50 shown; threshold_crossing(column, threshold) — timestamps where a numeric column crosses a threshold, either direction, with the gap since the previous sample; runs_outside(column[, low, high]) — each run of consecutive samples below low or above high (give either or both; |x| > t is low=-t, high=t; on an array column a sample is outside when any element is): start_ts, last_ts, duration_ns from its first to last sample outside (0 for one sample), sample_count, min_value, max_value, and on every row runs and total_duration_ns over all runs (the time outside, without the gaps between runs); gap_detection(max_gap_ns) — consecutive-timestamp gaps larger than max_gap_ns; derivative(column) — per-sample rate of change of a numeric column, per second, with the interval it was measured over; moving_average(column, window_ns) — trailing time-windowed average of a numeric column, with the count of non-null samples per window; unnest(column[, field]) — explode an array column to (timestamp, value) rows, optionally selecting a struct field. Pass parameters via recipe_params; *_ns parameters are BIGINT nanoseconds. Provide exactly one of sql_query or recipe. Recipes are incompatible with join_topics.
Clear enough to supply this argument.
recipe_params
Parameters for recipe, e.g. {"column": "fault.message"}. Column/field values must be exact sql_path names from get_topic_schema; *_ns parameters are integer nanoseconds (at least 1000); other numeric parameters may be numbers or numeric strings.
Clear enough to supply this argument.
limit
How many result rows to show (default and maximum 50). spans, profile and values always cover every matched row.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_time
Load only rows at or after this instant (int nanoseconds since the Unix epoch), intersected with any analysis scope. Narrows the load and the reported window, unlike a WHERE literal.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_time
Load only rows at or before this instant (int nanoseconds since the Unix epoch), intersected with any analysis scope.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
anchors
Up to 3 named instants (int nanoseconds), e.g. {"disarm": 1789169704191265000}: adds a <name>_offset_ns column after the time column (negative = before the anchor) and offsets to spans and profile.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
message_paths
Optional list of message path names to include when loading the topic data. When provided, only these columns will be loaded into the DataFrame, which can significantly improve performance for wide topics. This will be ignored for topics that are stored as parquet files, and applies only to the primary topic.
Clear enough to supply this argument.
join_topics › items › topic_name
Additional topic to load from the same dataset, file, session or event as the primary topic, with the same start_time and end_time.
Clear enough to supply this argument.
join_topics › items › alias
SQL table name to bind this topic to (letters, digits, underscores; must not be 'df').
Clear enough to supply this argument.
scope_start_time
Optional lower bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no lower bound.
Clear enough to supply this argument.
scope_end_time
Optional upper bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no upper bound.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "analyze_topic",
"description": "Loads topic data and executes a SQL query against it. Successful results are a text payload: `key: <json>` preamble lines, a blank line, then a TOON block `data[N]{col1,col2,...}:` with N indented comma-separated rows (`null`, `true`/`false`, bare numbers, double-quoted strings with backslash escapes for `\"`, `\\` and newlines). The preamble always carries `topic_ids` (each topic's per-file 'tp_' IDs, which get_entity and roboto://topic links take; for a topic whose files carry none, its 'ti_' ID, which get_entity takes but roboto://topic links do not), `coverage` (the dataset, file or session read, plus the event for an event_id; `files`, how many files it read; `files_read`, up to 50 of them with their datasets; `files_capped: true` when a dataset read stopped at 200 files) and, when known, `window` (the span the load drew from: the topic's data in that scope, clamped to the analysis scope and start_time/end_time, not the rows your WHERE kept). Every call covers exactly one scope: read these before combining results across calls, and report the scope you actually covered rather than the one you were asked about. Failures after topic resolution lead with the same lines. FACTS: `spans` gives `window`, `matched` (start, end, duration_ns, rows and, up to 2M rows, distinct_ts of every row the query matched) and `returned` (the rows shown: in time order they cover `returned.duration_ns` of `matched.duration_ns`; in any other order, such as a top-N by value, only their count, since their times measure no duration). `matched` runs from the first match to the last across every episode between: for how long a condition holds, use recipe runs_outside. When more rows matched than are shown, the preamble adds `total_matching_rows` and, up to 4M matching rows (above, a note says how to aggregate instead), `profile` (per numeric column, over ALL matched rows: n, nulls, min and max, the first time each extreme occurs and how many rows hold it; `max_n: 1` is a single-sample spike), `shape` (8 equal-time buckets of n, min, avg, max for the first 2 numeric columns over ALL matched rows: state trends, steadiness, onsets and dips from `shape` or aggregation, never from the rows shown) and `values` (per low-cardinality column: every value with its row count and first/last row time; on change-on-publish topics a value persists past its last row); `data` holds the first rows in your query's order and, only for time-ordered results, `data_tail` the last rows after a `... K rows omitted (t_from -> t_to, d=<ns>ns) ...` line. Shown rows are a sample: never infer absence, trend, peak, onset or steadiness from them alone; use the facts, check `data_tail`, or aggregate. `rows` and `n` count rows, not samples: compare them with `distinct_ts` (UNNEST and pooled files repeat timestamps). A `note:` names your SQL's own LIMIT (the facts count past it), explains an empty result (no rows in the window, or your WHERE excluded every row: then verify literal values with a small unfiltered sample), and flags a time literal that fits the data only in another unit. TIME WINDOWS: instants and durations are int ns (name instants `*_ts`, durations `*_ns`; `d=<ns>ns` in text); state durations in replies in readable units (ms, s, min) converted from these ns values. To look at a window, pass start_time/end_time, which narrow the load itself, rather than WHERE literals, LIMIT or `% N` sampling; aggregate (recipe bucket_stats or rate_over_time) over long spans. anchors adds `<name>_offset_ns` columns (negative = before the anchor) and offsets in spans and profile, so never subtract timestamps by hand. Failures are returned as a plain human-readable error message, which — when the topics' columns are known — appends each table's available columns (capped for very wide topics) so you can correct a mistyped column and retry. That list reflects what was ACTUALLY loaded: nested columns carry their element type — `(STRUCT[])` means UNNEST it and access item.\"field\"; an object array shown as `(JSON[])` or `(INTEGER[])` had no elements in the queried window (treat it as empty; do not retry other syntaxes); columns marked `(unnest)` are array-nested and only reachable via UNNEST (see ARRAY HANDLING). A topic is always identified by topic_name, scoped by exactly one of dataset_id, file_id, session_id or event_id. With a dataset_id, all files in the dataset containing that topic are loaded together. With a session_id, only the session's data is loaded: its files, which can come from several datasets, and of each file only the part the session holds. With an event_id, the event's session is loaded, over the event's time span unless start_time or end_time is given. With dataset_id, session_id or event_id, the load has a 'filename' column naming each row's file and a `_roboto_file_id` column with the file's ID, even when it reads one file; with file_id it has neither. A dataset's data stored before topic partitions existed has only 'filename'. Relative paths can repeat across a session's datasets, so group by `_roboto_file_id` when it is present. When files disagree about a column, the load reconciles it instead of failing: a column missing from some files is NULL there, and one typed inconsistently across files is loaded as text — you get a `note:` naming it with its per-file types, and must TRY_CAST before comparing or aggregating it numerically (as text, MAX and ORDER BY compare lexicographically). Naming such a column in message_paths fails the call instead, with the per-file types, so drop it from message_paths to get it as text. With a file_id, only that single file's instance of the topic is loaded. RECIPES: for common analyses (state transitions, dwell time, per-bucket stats, rate over time, threshold crossings, timestamp gaps, derivatives, moving averages, array unnest) prefer the `recipe` parameter over hand-written SQL — see its parameter description for the recipe menu. MULTI-TOPIC JOINS: join_topics loads up to 4 additional topics from the same scope as extra SQL tables named by your aliases, next to the primary topic's `df`. All tables are identically time-scoped and each carries its own `_roboto_timestamp`. Time-align streams with ASOF JOIN, which pairs each left row with the nearest right row at-or-before it: SELECT d._roboto_timestamp, d.\"state\", o.\"pose.x\" FROM df d ASOF JOIN odom o ON d._roboto_timestamp >= o._roboto_timestamp. Prefer one joined query over separate calls when correlating signals across topics. ALWAYS call get_topic_schema for every topic you query (including join_topics entries) before querying it, and reference columns only by the exact `sql_path` it returns. Do NOT guess column names from the topic name or from message-type conventions (e.g. assuming a ROS-style `status.level` or `is_charging`) — guessed names are the most common cause of query failures. COLUMNS: nested message fields are flattened to dot-delimited names that MUST be double-quoted in SQL, e.g. SELECT \"fault.message\" FROM df. An unquoted dotted name like fault.message is parsed as table.column and fails, since the only table is `df`. Copy the `sql_path` from get_topic_schema verbatim — it is already quoted correctly. A scalar field (e.g. `status`) has no sub-fields; do not append `.level`/`.status` to it. TIMESTAMPS: `_roboto_timestamp` is BIGINT nanoseconds since the Unix epoch (UTC). All times must be BIGINT nanoseconds — convert if necessary. `_roboto_timestamp` is INTERNAL — it exists only inside this tool's DuckDB view, not in any user file and not in the Roboto SDK. Never reference it in user-facing replies, code suggestions, or SDK examples. If a user asks about it, say it is a tool-internal synthesized column, not part of their data. ARRAY HANDLING: Fields with requires_unnest=true must be unnested before accessing nested fields. The dotted child names under an array (e.g. `status_list.status`) are NOT columns — always UNNEST the array column itself: WITH exploded AS (SELECT UNNEST(\"array_field\") AS item FROM df) SELECT item.\"field\" FROM exploded. Lists are 1-based in SQL: element 0 of a message field (`esc[0]` in ROS/PX4 names) is `\"esc\"[1]`, and a query that reads a list at `[0]` is rejected. To work per element, number the elements in the same SELECT as the UNNEST, converted to the message's 0-based numbering: WITH e AS (SELECT UNNEST(\"esc\") AS item, generate_subscripts(\"esc\", 1) - 1 AS slot FROM df) SELECT slot, MAX(item.\"esc_rpm\") FROM e GROUP BY slot. Positions from WITH ORDINALITY and list_position also start at 1. A slice `x[a:b]` includes element b. JSON values stay 0-based: `j[0]` and `'$[0]'` are the first element.",
"inputSchema": {
"type": "object",
"required": [
"topic_name"
],
"properties": {
"limit": {
"type": "integer",
"default": 50,
"maximum": 50,
"minimum": 1,
"description": "How many result rows to show (default and maximum 50). spans, profile and values always cover every matched row."
},
"recipe": {
"type": "string",
"description": "Name of a built-in analysis recipe to run instead of writing sql_query; the server expands it into vetted SQL over `df`. Prefer a recipe over hand-written SQL whenever one matches the analysis. Recipes: transitions(column) — rows where the column's value changes, with from/to values and the gap since the previous sample; dwell_time(column) — duration of each run of equal values, run start to next run's start (the final run is open: null end_ts and dwell_ns); sample_count and max_gap_ns expose unobserved stretches; rate_over_time(bucket_ns) — message count and Hz per fixed time bucket; bucket_stats(column, buckets[, by]) — n, min_value, avg_value, max_value (min_at/max_at: when each extreme first occurs) of a numeric or array column in `buckets` (1-50) equal-time buckets from first to last sample, NULL/NaN skipped, with bucket_start_ts, bucket_last_ts, bucket_offset_ns, bucket_ns; by (a label array zipped with an array column, e.g. by=name, column=effort) adds key: buckets x labels rows, 50 shown; threshold_crossing(column, threshold) — timestamps where a numeric column crosses a threshold, either direction, with the gap since the previous sample; runs_outside(column[, low, high]) — each run of consecutive samples below low or above high (give either or both; |x| > t is low=-t, high=t; on an array column a sample is outside when any element is): start_ts, last_ts, duration_ns from its first to last sample outside (0 for one sample), sample_count, min_value, max_value, and on every row runs and total_duration_ns over all runs (the time outside, without the gaps between runs); gap_detection(max_gap_ns) — consecutive-timestamp gaps larger than max_gap_ns; derivative(column) — per-sample rate of change of a numeric column, per second, with the interval it was measured over; moving_average(column, window_ns) — trailing time-windowed average of a numeric column, with the count of non-null samples per window; unnest(column[, field]) — explode an array column to (timestamp, value) rows, optionally selecting a struct field. Pass parameters via recipe_params; *_ns parameters are BIGINT nanoseconds. Provide exactly one of sql_query or recipe. Recipes are incompatible with join_topics."
},
"anchors": {
"type": "object",
"description": "Up to 3 named instants (int nanoseconds), e.g. {\"disarm\": 1789169704191265000}: adds a <name>_offset_ns column after the time column (negative = before the anchor) and offsets to spans and profile.",
"additionalProperties": {
"type": "integer"
}
},
"file_id": {
"type": "string",
"description": "File ID (begins with 'fl_'). Reads the topic in that one file. Use when the user asked about a specific file."
},
"end_time": {
"type": "integer",
"description": "Load only rows at or before this instant (int nanoseconds since the Unix epoch), intersected with any analysis scope."
},
"event_id": {
"type": "string",
"description": "Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id."
},
"sql_query": {
"type": "string",
"description": "A SQL query that will be run against the topic's table as a Pandas DataFrame. The primary topic's table is named `df`; each join_topics entry is additionally available under its alias. Reference columns by the exact `sql_path` from get_topic_schema: nested fields are flattened dot-paths that must be double-quoted (e.g. \"fault.message\"), since an unquoted dotted name is read as table.column and fails. Do not add LIMIT or `% N` sampling to see a window: narrow it with start_time/end_time, or aggregate (recipe bucket_stats or rate_over_time). To bucket time in SQL, divide with integer `//` (`/` is float division), clamp the last sample into the last bucket (LEAST(i, n - 1)), and select each bucket's start as a `*_ts` column (bucket_start_ts), not a bare bucket number, so spans and offsets exist. There is always a message path marked with canonical_time: true. Use it as the default timestamp. With dataset_id, session_id or event_id, df has a 'filename' column to filter or group by. Provide exactly one of sql_query or recipe."
},
"dataset_id": {
"type": "string",
"description": "Dataset ID (begins with 'ds_'). Reads the topic in every file of the dataset that carries it."
},
"session_id": {
"type": "string",
"description": "Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time."
},
"start_time": {
"type": "integer",
"description": "Load only rows at or after this instant (int nanoseconds since the Unix epoch), intersected with any analysis scope. Narrows the load and the reported window, unlike a WHERE literal."
},
"topic_name": {
"type": "string",
"description": "The name of the topic to query (e.g. '/camera/image', '/imu/data'). Always required. Scoped by exactly one of dataset_id, file_id, session_id or event_id."
},
"join_topics": {
"type": "array",
"items": {
"type": "object",
"required": [
"topic_name",
"alias"
],
"properties": {
"alias": {
"type": "string",
"description": "SQL table name to bind this topic to (letters, digits, underscores; must not be 'df')."
},
"topic_name": {
"type": "string",
"description": "Additional topic to load from the same dataset, file, session or event as the primary topic, with the same start_time and end_time."
}
}
},
"description": "Optional extra topics (max 4) to load as additional SQL tables alongside `df`, enabling cross-topic correlation in a single query — e.g. ASOF JOIN on _roboto_timestamp to time-align streams."
},
"message_paths": {
"type": "array",
"items": {
"type": "string"
},
"description": "Optional list of message path names to include when loading the topic data. When provided, only these columns will be loaded into the DataFrame, which can significantly improve performance for wide topics. This will be ignored for topics that are stored as parquet files, and applies only to the primary topic."
},
"recipe_params": {
"type": "object",
"description": "Parameters for recipe, e.g. {\"column\": \"fault.message\"}. Column/field values must be exact sql_path names from get_topic_schema; *_ns parameters are integer nanoseconds (at least 1000); other numeric parameters may be numbers or numeric strings."
},
"scope_end_time": {
"type": "integer",
"description": "Optional upper bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no upper bound."
},
"scope_start_time": {
"type": "integer",
"description": "Optional lower bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no lower bound."
}
}
},
"annotations": {
"title": "analyze_topic",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Searches one or more log-style topics for text patterns in a single call — use this instead of issuing per-pattern analyze_topic LIKE queries. Patterns are case-insensitive substrings, matched against every scalar string column of every listed topic (no get_topic_schema needed first; the tool discovers the string columns itself). Pass topic_names: the topics named by the skill, task, or earlier results; a scoped search takes seconds. Set scan_all instead only to discover which topic carries a signature, or when the named topics don't exist in this scope — it searches every string-searchable topic in scope, takes minutes, and may cover only part of them. Don't run the same patterns both scoped and with scan_all. Never trim a topic list to fit the cap: a trimmed guess can silently miss the topic that actually has the answer; split it across calls instead. Scope the search with exactly one of dataset_id, file_id, session_id or event_id. A session search reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. An event search reads the event's session over the event's time span unless start_time or end_time is given. Returns two TOON blocks plus a `coverage:` preamble line — always check it: a result may cover only part of the requested (or in-scope) topics. `coverage` holds the ID of the scope searched (`dataset_id`, `file_id`, `session_id`, or `event_id` with its `session_id`). Its `files` counts the files searched and `files_read` lists up to 50 of them; `files_capped: true` means a topic reached the limit of 200 files one search reads, and `note:` names that topic. Its `searchable_topics_in_scope`, `searched` and `not_searched` report how many string-searchable topics exist in scope, how many this call actually searched, and which in-scope names weren't, including topics stored only as one record per file, which a session or event search cannot read, and topics whose text sits only inside arrays, which search_logs never searches (`incomplete: true` instead of `searchable_topics_in_scope` when the topic inventory itself was truncated). A zero-everywhere result with a non-empty not_searched is not evidence that the signature never occurred: search those topics too (with topic_names, or as `note:` says for a topic it names), or say explicitly that they weren't searched. A large or slow-loading topic set may also stop partway through a time/deadline budget — `note:` then names exactly which topics to pass in a follow-up call to finish. `counts[N]{topic,pattern,matches}`: exact match counts per topic and pattern over the files searched in the searched window, including zeros — a zero is a real answer ('this signature did not occur in this topic's searched files'), not an error. `matches[M]{topic,file_id,_roboto_timestamp,level,column,text}`: the earliest `limit` matching rows across all topics — `file_id` is the file the row came from, `_roboto_timestamp` is BIGINT ns since the Unix epoch (UTC), `level` is null where a topic has no level column, `column` names the string field that matched, and `text` is its value truncated to 300 chars with ANSI color codes stripped. When a count exceeds the sampled rows, narrow the window or patterns, or drill into that topic with analyze_topic. level_at_least keeps only rows whose `level` column is at or above the threshold, on topics whose level is numeric (e.g. /rosout, where DEBUG=1 through FATAL=16). Topics without a level column are searched unfiltered; topics whose level is text (`INFO`, `Error`) are searched unfiltered and say so in `note:`. A text level is itself searched, so it can appear in `column`. start_time/end_time (BIGINT ns) narrow the searched window; they can only narrow, never widen, any scope_start_time/scope_end_time window on the call. A `window:` preamble line gives the searched window whenever it has a bound: on a session or event search when the session's data has timestamps, and on any search that start_time, end_time or any scope_start_time/scope_end_time window on the call narrows. Topics that cannot be searched (not found, no string columns, no rows in the window) are reported in a `note:` preamble line while the remaining topics still return results.
The description explains the capability, when to use it, and what to expect.
Inputs92/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
topic_names
Topics to search (max 8), e.g. ['/rosout_agg_lite', '/system/events']. Each is resolved in the call's scope. Required unless scan_all is true; omitting it otherwise fails and lists the string-searchable topics in scope.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
scan_all
Search every string-searchable topic in scope instead of topic_names (pass one or the other, not both). Slow: no topic-count cap, bounded only by a time budget, so the result may be partial.
Clear enough to supply this argument.
dataset_id
The dataset ID (begins with 'ds_') to search within. Each topic is loaded across the dataset's files that hold it, up to 200 files per topic (`coverage.files_capped` says when a topic reached that limit). Provide exactly one of dataset_id, file_id, session_id or event_id.
Clear enough to supply this argument.
file_id
A single file ID (begins with 'fl_') whose topics to search. Provide exactly one of dataset_id, file_id, session_id or event_id.
Clear enough to supply this argument.
session_id
Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time.
Clear enough to supply this argument.
event_id
Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
patterns
Case-insensitive substring patterns (max 10), e.g. ['ethercat', 'encoder fault', 'working counter']. Each pattern is counted separately so you can tell which signature fired.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
level_at_least
Optional minimum value for a topic's numeric `level` column (e.g. 4 for ROS ERROR). Ignored on topics without a level column, and ignored with a `note:` on topics whose level is text rather than a severity number.
Clear enough to supply this argument.
limit
Maximum sampled match rows across all topics (default 20, max 50). Counts are always exact regardless of this limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_time
Optional window start, BIGINT nanoseconds since the Unix epoch. Narrows the search within the analysis scope.
Clear enough to supply this argument.
end_time
Optional window end, BIGINT nanoseconds since the Unix epoch. Narrows the search within the analysis scope.
Clear enough to supply this argument.
scope_start_time
Optional lower bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no lower bound.
Clear enough to supply this argument.
scope_end_time
Optional upper bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no upper bound.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
{
"title": "search_logs",
"readOnlyHint": true
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "search_logs",
"description": "Searches one or more log-style topics for text patterns in a single call — use this instead of issuing per-pattern analyze_topic LIKE queries. Patterns are case-insensitive substrings, matched against every scalar string column of every listed topic (no get_topic_schema needed first; the tool discovers the string columns itself). Pass topic_names: the topics named by the skill, task, or earlier results; a scoped search takes seconds. Set scan_all instead only to discover which topic carries a signature, or when the named topics don't exist in this scope — it searches every string-searchable topic in scope, takes minutes, and may cover only part of them. Don't run the same patterns both scoped and with scan_all. Never trim a topic list to fit the cap: a trimmed guess can silently miss the topic that actually has the answer; split it across calls instead. Scope the search with exactly one of dataset_id, file_id, session_id or event_id. A session search reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. An event search reads the event's session over the event's time span unless start_time or end_time is given. Returns two TOON blocks plus a `coverage:` preamble line — always check it: a result may cover only part of the requested (or in-scope) topics. `coverage` holds the ID of the scope searched (`dataset_id`, `file_id`, `session_id`, or `event_id` with its `session_id`). Its `files` counts the files searched and `files_read` lists up to 50 of them; `files_capped: true` means a topic reached the limit of 200 files one search reads, and `note:` names that topic. Its `searchable_topics_in_scope`, `searched` and `not_searched` report how many string-searchable topics exist in scope, how many this call actually searched, and which in-scope names weren't, including topics stored only as one record per file, which a session or event search cannot read, and topics whose text sits only inside arrays, which search_logs never searches (`incomplete: true` instead of `searchable_topics_in_scope` when the topic inventory itself was truncated). A zero-everywhere result with a non-empty not_searched is not evidence that the signature never occurred: search those topics too (with topic_names, or as `note:` says for a topic it names), or say explicitly that they weren't searched. A large or slow-loading topic set may also stop partway through a time/deadline budget — `note:` then names exactly which topics to pass in a follow-up call to finish. `counts[N]{topic,pattern,matches}`: exact match counts per topic and pattern over the files searched in the searched window, including zeros — a zero is a real answer ('this signature did not occur in this topic's searched files'), not an error. `matches[M]{topic,file_id,_roboto_timestamp,level,column,text}`: the earliest `limit` matching rows across all topics — `file_id` is the file the row came from, `_roboto_timestamp` is BIGINT ns since the Unix epoch (UTC), `level` is null where a topic has no level column, `column` names the string field that matched, and `text` is its value truncated to 300 chars with ANSI color codes stripped. When a count exceeds the sampled rows, narrow the window or patterns, or drill into that topic with analyze_topic. level_at_least keeps only rows whose `level` column is at or above the threshold, on topics whose level is numeric (e.g. /rosout, where DEBUG=1 through FATAL=16). Topics without a level column are searched unfiltered; topics whose level is text (`INFO`, `Error`) are searched unfiltered and say so in `note:`. A text level is itself searched, so it can appear in `column`. start_time/end_time (BIGINT ns) narrow the searched window; they can only narrow, never widen, any scope_start_time/scope_end_time window on the call. A `window:` preamble line gives the searched window whenever it has a bound: on a session or event search when the session's data has timestamps, and on any search that start_time, end_time or any scope_start_time/scope_end_time window on the call narrows. Topics that cannot be searched (not found, no string columns, no rows in the window) are reported in a `note:` preamble line while the remaining topics still return results.",
"inputSchema": {
"type": "object",
"required": [
"patterns"
],
"properties": {
"limit": {
"type": "integer",
"maximum": 50,
"minimum": 1,
"description": "Maximum sampled match rows across all topics (default 20, max 50). Counts are always exact regardless of this limit."
},
"file_id": {
"type": "string",
"description": "A single file ID (begins with 'fl_') whose topics to search. Provide exactly one of dataset_id, file_id, session_id or event_id."
},
"end_time": {
"type": "integer",
"description": "Optional window end, BIGINT nanoseconds since the Unix epoch. Narrows the search within the analysis scope."
},
"event_id": {
"type": "string",
"description": "Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id."
},
"patterns": {
"type": "array",
"items": {
"type": "string"
},
"description": "Case-insensitive substring patterns (max 10), e.g. ['ethercat', 'encoder fault', 'working counter']. Each pattern is counted separately so you can tell which signature fired."
},
"scan_all": {
"type": "boolean",
"description": "Search every string-searchable topic in scope instead of topic_names (pass one or the other, not both). Slow: no topic-count cap, bounded only by a time budget, so the result may be partial."
},
"dataset_id": {
"type": "string",
"description": "The dataset ID (begins with 'ds_') to search within. Each topic is loaded across the dataset's files that hold it, up to 200 files per topic (`coverage.files_capped` says when a topic reached that limit). Provide exactly one of dataset_id, file_id, session_id or event_id."
},
"session_id": {
"type": "string",
"description": "Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time."
},
"start_time": {
"type": "integer",
"description": "Optional window start, BIGINT nanoseconds since the Unix epoch. Narrows the search within the analysis scope."
},
"topic_names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Topics to search (max 8), e.g. ['/rosout_agg_lite', '/system/events']. Each is resolved in the call's scope. Required unless scan_all is true; omitting it otherwise fails and lists the string-searchable topics in scope."
},
"level_at_least": {
"type": "integer",
"description": "Optional minimum value for a topic's numeric `level` column (e.g. 4 for ROS ERROR). Ignored on topics without a level column, and ignored with a `note:` on topics whose level is text rather than a severity number."
},
"scope_end_time": {
"type": "integer",
"description": "Optional upper bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no upper bound."
},
"scope_start_time": {
"type": "integer",
"description": "Optional lower bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no lower bound."
}
}
},
"annotations": {
"title": "search_logs",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Surgical video analysis — sends frames from a time range to a vision model and returns a structured answer. Expensive; use on narrow windows, not entire topics. Workflow: call get_keyframes first to scout, then this tool on specific ranges. Samples frames at up to 10 Hz, capped by 'frame_budget' (default 'standard' = 50 frames; at that budget, ranges up to 5s get full 10 Hz density — wider ranges are subsampled). Prefer multiple short, dense calls over one wide sparse call — VLM answer quality degrades as frame count rises (attention dilutes). Response includes an 'analyzed_range' object (start_ns, end_ns, frame_count, fps, truncated, frame_budget) — inspect it to judge coverage and re-call on sub-ranges if 'truncated' is true. It also carries 'window' and 'coverage': the time window, and the files with data in it, including any that yielded no frame. Pass exactly one of dataset_id, file_id, session_id or event_id. Without start_time and end_time, the window is the topic's whole span in that scope, or the event's span under event_id. A whole span is usually too wide: pass a narrow window. Frame times are nanoseconds since the Unix epoch; under session_id or event_id they include the session's time anchoring. Supports ROS sensor_msgs/Image and CompressedImage, and compressed video (foxglove CompressedVideo) in H.264, H.265, VP9 or AV1. Choose 'frame_mode' per call ('raw' for scene/content, 'motion_overlay' for change-focused questions on a static camera) AND 'frame_budget' per call ('low' to pinpoint, 'standard' for most questions, 'high' only for broad event hunting where you don't yet know when the thing happens).
The description explains the capability, when to use it, and what to expect.
Inputs94/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
topic_name
The name of the image/video topic (e.g. '/camera/image', '/camera/compressed'). Every file in the scope that has this topic is read, in time order.
Clear enough to supply this argument.
dataset_id
Dataset ID (begins with 'ds_'). Reads the topic in every file of the dataset that carries it.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
file_id
File ID (begins with 'fl_'). Reads the topic in that one file. Use when the user asked about a specific file.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
session_id
Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
event_id
Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id.
Clear enough to supply this argument.
start_time
Start of the time window in nanoseconds since the Unix epoch (inclusive). Optional: defaults to the start of the topic's data in the scope, or to the event's start under event_id.
Clear enough to supply this argument.
end_time
End of the time window in nanoseconds since the Unix epoch (inclusive). Optional: defaults to the end of the topic's data in the scope, or to the event's end under event_id.
Clear enough to supply this argument.
context
Strictly required background context about this robot, scenario, or environment to help the vision model interpret the frames. The more relevant and precise context is provided, the better the answer will be. You are talking to a VLM: be concise, efficient, and skip polite pleasantries.
Clear enough to supply this argument.
question
The question to answer about the video frames.
Clear enough to supply this argument.
frame_mode
How frames are rendered before being sent to the vision model. 'raw' (default): original frames — pick this for scene/content questions (identification, counting, reading text, describing the environment). 'motion_overlay': each middle frame is the original scene tinted CYAN where pixels changed vs. the previous frame (trailing edge — where something just was), MAGENTA where they change vs. the next frame (leading edge — where something is about to move), and WHITE where both fire (sustained motion). First and last frames pass through untinted as reference anchors. Pick this for change- and direction-focused questions on a static or slow-moving camera (did X move, which direction, when did Y appear). Preserves scene context. Unreliable when ego-motion dominates (whole scene shifts every frame) — prefer 'raw' for moving robot cameras.
Clear enough to supply this argument.
frame_budget
How many frames to sample from the time range. Trades temporal coverage against attention density per frame — MORE FRAMES DOES NOT MEAN A BETTER ANSWER, attention dilutes as frame count rises. Prefer narrowing the range over raising the budget. 'low' (25 frames): pinpoint questions or follow-up calls where you already know roughly when the event happens. Cheapest, tightest attention per frame. Good for 'exactly what's in this narrow window' or confirming a specific hypothesis. 'standard' (50 frames, default): the right choice for most questions — scene analysis, short-to-medium windows, light event hunting. 'high' (100 frames): broad event hunting over a long window where you don't yet know where in time the thing is. Last resort — prefer calling get_keyframes first to narrow the range, then using 'standard' or 'low'.
Clear enough to supply this argument.
scope_start_time
Optional lower bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no lower bound.
Clear enough to supply this argument.
scope_end_time
Optional upper bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no upper bound.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "analyze_video",
"description": "Surgical video analysis — sends frames from a time range to a vision model and returns a structured answer. Expensive; use on narrow windows, not entire topics. Workflow: call get_keyframes first to scout, then this tool on specific ranges. Samples frames at up to 10 Hz, capped by 'frame_budget' (default 'standard' = 50 frames; at that budget, ranges up to 5s get full 10 Hz density — wider ranges are subsampled). Prefer multiple short, dense calls over one wide sparse call — VLM answer quality degrades as frame count rises (attention dilutes). Response includes an 'analyzed_range' object (start_ns, end_ns, frame_count, fps, truncated, frame_budget) — inspect it to judge coverage and re-call on sub-ranges if 'truncated' is true. It also carries 'window' and 'coverage': the time window, and the files with data in it, including any that yielded no frame. Pass exactly one of dataset_id, file_id, session_id or event_id. Without start_time and end_time, the window is the topic's whole span in that scope, or the event's span under event_id. A whole span is usually too wide: pass a narrow window. Frame times are nanoseconds since the Unix epoch; under session_id or event_id they include the session's time anchoring. Supports ROS sensor_msgs/Image and CompressedImage, and compressed video (foxglove CompressedVideo) in H.264, H.265, VP9 or AV1. Choose 'frame_mode' per call ('raw' for scene/content, 'motion_overlay' for change-focused questions on a static camera) AND 'frame_budget' per call ('low' to pinpoint, 'standard' for most questions, 'high' only for broad event hunting where you don't yet know when the thing happens).",
"inputSchema": {
"type": "object",
"required": [
"topic_name",
"context",
"question"
],
"properties": {
"context": {
"type": "string",
"description": "Strictly required background context about this robot, scenario, or environment to help the vision model interpret the frames. The more relevant and precise context is provided, the better the answer will be. You are talking to a VLM: be concise, efficient, and skip polite pleasantries."
},
"file_id": {
"type": "string",
"description": "File ID (begins with 'fl_'). Reads the topic in that one file. Use when the user asked about a specific file."
},
"end_time": {
"type": "integer",
"description": "End of the time window in nanoseconds since the Unix epoch (inclusive). Optional: defaults to the end of the topic's data in the scope, or to the event's end under event_id."
},
"event_id": {
"type": "string",
"description": "Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id."
},
"question": {
"type": "string",
"description": "The question to answer about the video frames."
},
"dataset_id": {
"type": "string",
"description": "Dataset ID (begins with 'ds_'). Reads the topic in every file of the dataset that carries it."
},
"frame_mode": {
"enum": [
"raw",
"motion_overlay"
],
"type": "string",
"description": "How frames are rendered before being sent to the vision model. 'raw' (default): original frames — pick this for scene/content questions (identification, counting, reading text, describing the environment). 'motion_overlay': each middle frame is the original scene tinted CYAN where pixels changed vs. the previous frame (trailing edge — where something just was), MAGENTA where they change vs. the next frame (leading edge — where something is about to move), and WHITE where both fire (sustained motion). First and last frames pass through untinted as reference anchors. Pick this for change- and direction-focused questions on a static or slow-moving camera (did X move, which direction, when did Y appear). Preserves scene context. Unreliable when ego-motion dominates (whole scene shifts every frame) — prefer 'raw' for moving robot cameras."
},
"session_id": {
"type": "string",
"description": "Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time."
},
"start_time": {
"type": "integer",
"description": "Start of the time window in nanoseconds since the Unix epoch (inclusive). Optional: defaults to the start of the topic's data in the scope, or to the event's start under event_id."
},
"topic_name": {
"type": "string",
"description": "The name of the image/video topic (e.g. '/camera/image', '/camera/compressed'). Every file in the scope that has this topic is read, in time order."
},
"frame_budget": {
"enum": [
"low",
"standard",
"high"
],
"type": "string",
"description": "How many frames to sample from the time range. Trades temporal coverage against attention density per frame — MORE FRAMES DOES NOT MEAN A BETTER ANSWER, attention dilutes as frame count rises. Prefer narrowing the range over raising the budget. 'low' (25 frames): pinpoint questions or follow-up calls where you already know roughly when the event happens. Cheapest, tightest attention per frame. Good for 'exactly what's in this narrow window' or confirming a specific hypothesis. 'standard' (50 frames, default): the right choice for most questions — scene analysis, short-to-medium windows, light event hunting. 'high' (100 frames): broad event hunting over a long window where you don't yet know where in time the thing is. Last resort — prefer calling get_keyframes first to narrow the range, then using 'standard' or 'low'."
},
"scope_end_time": {
"type": "integer",
"description": "Optional upper bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no upper bound."
},
"scope_start_time": {
"type": "integer",
"description": "Optional lower bound (inclusive) of the analysis window, in nanoseconds since the Unix epoch (UTC). Restricts which rows the tool may read/return. Omit for no lower bound."
}
}
},
"annotations": {
"title": "analyze_video",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Returns up to ~10 low-resolution thumbnail frames sampled across a video/image topic's time range. This is a fast scouting tool — thumbnails are returned directly to you (no vision model involved), so you can visually identify what is happening and when. WORKFLOW: Call this ONCE per topic first. Use the returned timestamps to identify regions of interest, then call analyze_video on those narrow time windows for detailed analysis. Do NOT call this repeatedly on sub-ranges — use analyze_video for that. Frame numbers are for your internal reasoning only — in your answers, always reference events by their nanosecond timestamps, never by frame number (frame numbers are arbitrary and depend on sampling). SCOPE: pass exactly one of dataset_id, file_id, session_id or event_id. Omit start_time and end_time to scout the topic's whole span in that scope, across all of its files; under event_id the span is the event's. The result's 'window' and 'coverage' give the time window scouted, and the files with data in it, including any that yielded no frame. SAMPLING: keyframes are spaced evenly across the frames that ACTUALLY EXIST in the topic, not evenly across the clock. On a dense topic that is effectively even in time; but on a SPARSE topic (few frames, or frames bunched into part of the range) the keyframes bunch where the imagery is, so the time gap between adjacent keyframes can be large AND uneven. The 'sampling' block's inter_frame_gap is only the nominal range/N spacing — the per-frame [t=...ns] timestamps are the ground truth for when each frame occurred. Do NOT infer continuity, nor assume a uniform time gap, between adjacent keyframes. Use analyze_video on narrow sub-ranges (or call it repeatedly on short windows) for per-second dynamics. ANALYSIS SCOPE: when this chat has an analysis scope set, the window you pass is narrowed to that scope.
The description explains the capability, when to use it, and what to expect.
Inputs89/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
topic_name
The name of the image/video topic (e.g. '/camera/image', '/camera/compressed'). Every file in the scope that has this topic is read, in time order.
Clear enough to supply this argument.
dataset_id
Dataset ID (begins with 'ds_'). Reads the topic in every file of the dataset that carries it.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
file_id
File ID (begins with 'fl_'). Reads the topic in that one file. Use when the user asked about a specific file.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
session_id
Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time.
Clear enough to supply this argument.
event_id
Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_time
Start of the time range in nanoseconds since the Unix epoch (inclusive). Optional: defaults to the start of the topic's data in the scope, or to the event's start under event_id.
Clear enough to supply this argument.
end_time
End of the time range in nanoseconds since the Unix epoch (inclusive). Optional: defaults to the end of the topic's data in the scope, or to the event's end under event_id.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_keyframes",
"description": "Returns up to ~10 low-resolution thumbnail frames sampled across a video/image topic's time range. This is a fast scouting tool — thumbnails are returned directly to you (no vision model involved), so you can visually identify what is happening and when. WORKFLOW: Call this ONCE per topic first. Use the returned timestamps to identify regions of interest, then call analyze_video on those narrow time windows for detailed analysis. Do NOT call this repeatedly on sub-ranges — use analyze_video for that. Frame numbers are for your internal reasoning only — in your answers, always reference events by their nanosecond timestamps, never by frame number (frame numbers are arbitrary and depend on sampling). SCOPE: pass exactly one of dataset_id, file_id, session_id or event_id. Omit start_time and end_time to scout the topic's whole span in that scope, across all of its files; under event_id the span is the event's. The result's 'window' and 'coverage' give the time window scouted, and the files with data in it, including any that yielded no frame. SAMPLING: keyframes are spaced evenly across the frames that ACTUALLY EXIST in the topic, not evenly across the clock. On a dense topic that is effectively even in time; but on a SPARSE topic (few frames, or frames bunched into part of the range) the keyframes bunch where the imagery is, so the time gap between adjacent keyframes can be large AND uneven. The 'sampling' block's inter_frame_gap is only the nominal range/N spacing — the per-frame [t=...ns] timestamps are the ground truth for when each frame occurred. Do NOT infer continuity, nor assume a uniform time gap, between adjacent keyframes. Use analyze_video on narrow sub-ranges (or call it repeatedly on short windows) for per-second dynamics. ANALYSIS SCOPE: when this chat has an analysis scope set, the window you pass is narrowed to that scope.",
"inputSchema": {
"type": "object",
"required": [
"topic_name"
],
"properties": {
"file_id": {
"type": "string",
"description": "File ID (begins with 'fl_'). Reads the topic in that one file. Use when the user asked about a specific file."
},
"end_time": {
"type": "integer",
"description": "End of the time range in nanoseconds since the Unix epoch (inclusive). Optional: defaults to the end of the topic's data in the scope, or to the event's end under event_id."
},
"event_id": {
"type": "string",
"description": "Session event ID (begins with 'evt_'). Reads the data of the event's session. Without start_time and end_time, the window is the event's time span; a wider window reads around the event, but never past the session's data. A dataset event (ID beginning 'ev_') is refused: read its data with dataset_id or file_id."
},
"dataset_id": {
"type": "string",
"description": "Dataset ID (begins with 'ds_'). Reads the topic in every file of the dataset that carries it."
},
"session_id": {
"type": "string",
"description": "Session ID (begins with 'se_'). Reads only the session's data: its files, which can come from several datasets, and of each file only the part the session holds. Timestamps are nanoseconds since the Unix epoch, including any offset that anchors the session's data to wall-clock time."
},
"start_time": {
"type": "integer",
"description": "Start of the time range in nanoseconds since the Unix epoch (inclusive). Optional: defaults to the start of the topic's data in the scope, or to the event's start under event_id."
},
"topic_name": {
"type": "string",
"description": "The name of the image/video topic (e.g. '/camera/image', '/camera/compressed'). Every file in the scope that has this topic is read, in time order."
}
}
},
"annotations": {
"title": "get_keyframes",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Compare slices of the org's data and report what changed between them: which event
templates, numeric signals and log lines are new, gone, up or down, ranked by how much they
moved. Use it for "what changed", "why is this run different from that one", "what is
different since the update", "is it getting worse month over month" or "how does this device
compare to the fleet".
A slice is an entity scope (file_ids, dataset_ids, dataset_tags or collection_id; none for
the whole org) plus an optional window on event data time, in epoch nanoseconds (use
convert_time_format). Slices may be completely disjoint -- two unrelated recordings is the
normal case. ``bucket`` expands a time range into week/month slices for trends.
Sources:
- events: event-template rates from the event index. Fast.
- signals: precomputed per-recording statistics of numeric message paths. Fast, but whole
recordings only, so it is skipped for time-bounded and bucket slices. Data ingested with
roboto.experimental.ingest carries none of these statistics, so signals cannot compare it.
- log_lines: reads raw log topics live and diffs normalized line templates. Its 32
(file x topic) scan budget is split equally between slices, so it suits a few files or
small datasets, not fleets or many slices.
Reading the result:
- Movers are directional against the ``reference`` slice (default: the first).
- ``slices[].coverage`` says how much data each source had. Many datasets have no events but
rich signals, so check it before concluding a source found nothing.
- Each source's ``skipped`` and the ``caveats`` list name every blind spot: thin slices,
unscanned topics, little key overlap, and whole-org slices on large orgs too big to count
or to pool signals for. Skipped or unscanned means unknown, not quiet, and a quiet diff is
not proof nothing changed.
- A signal mover with a ``bookkeeping`` reason (a clock, counter or process id, or a constant
reference) ranks last and can usually be ignored.
- A "new" event template can mean the extraction pipeline changed rather than the device;
``caveats`` flags event-type drift.
When a slice is too large, narrow it: dataset_ids, dataset_tags or file_ids instead of the
whole org, a shorter window, or fewer buckets.
The description explains the capability, when to use it, and what to expect.
Inputs92/100
22 of 23 inputs have descriptions. Add descriptions to the missing inputs, including where to obtain IDs and how to supply values.
slices › items › label
Unique short name; appears in the result.
Clear enough to supply this argument.
slices › items › file_ids
Scope to these files (fl_...) -- the common case for comparing one recording against another. At most one of file_ids / dataset_ids / dataset_tags / collection_id.
Clear enough to supply this argument.
slices › items › dataset_ids
Scope to these datasets.
Clear enough to supply this argument.
slices › items › dataset_tags
Scope to datasets carrying ALL of these tags.
Clear enough to supply this argument.
slices › items › collection_id
Scope to a collection's latest membership (cl_...). Any collection type is accepted: dataset and file collections are used directly; event and session collections resolve to the files their members occur in.
Clear enough to supply this argument.
slices › items › start_time
Inclusive lower bound on DATA time, as EPOCH NANOSECONDS (integer). No other unit or format is accepted -- use convert_time_format to turn a human date into ns. Note: the signals source cannot serve time-bounded slices (whole-recording stats) and is skipped for them.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
slices › items › end_time
Exclusive upper bound on data time, in epoch nanoseconds (integer): data at end_time belongs to the next slice.
Clear enough to supply this argument.
bucket › period
Not provided
Missing description. Explain what this value means and how to supply it.
bucket › start_time
Inclusive start of the range, in epoch nanoseconds (integer).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
bucket › end_time
Exclusive end of the range, in epoch nanoseconds (integer).
Clear enough to supply this argument.
bucket › file_ids
Scope to these files (fl_...) -- the common case for comparing one recording against another. At most one of file_ids / dataset_ids / dataset_tags / collection_id.
Clear enough to supply this argument.
bucket › dataset_ids
Scope to these datasets.
Clear enough to supply this argument.
bucket › dataset_tags
Scope to datasets carrying ALL of these tags.
Clear enough to supply this argument.
bucket › collection_id
Scope to a collection's latest membership (cl_...). Any collection type is accepted: dataset and file collections are used directly; event and session collections resolve to the files their members occur in.
Clear enough to supply this argument.
sources
Which comparison surfaces to run. Default: all three when every slice is file-scoped, otherwise ["events", "signals"]. events and signals are indexed and fast, and carry no scan budget. log_lines reads raw topic data live and is bounded by a 32 (file x topic) scan budget split equally between slices, so each slice may scan only 32//n_slices pairs -- with many slices, or slices of many files, log_lines scans little or nothing. Prefer ["events", "signals"] for many-slice, bucket and fleet-wide questions. Unscanned topics are reported as such while the other sources still answer.
Clear enough to supply this argument.
reference
Label of the slice to compare against (the baseline side). Default: the first slice; in bucket mode, the earliest bucket that holds any events, so an empty leading month does not make every template rank 'new'.
Clear enough to supply this argument.
template_field
events source: what identifies a template -- 'name' (default) or 'metadata.<dot.path>'. Events missing the path are tallied as untemplated, not dropped.
Clear enough to supply this argument.
count_field
events source: metadata key holding the occurrence count an event row represents (e.g. 'count', 'occurrence_count'). Default 'auto' (recognizes those two); pass a key explicitly to override, or 'none' to count rows. Non-numeric values weigh 1.
Clear enough to supply this argument.
normalize_templates
Collapse numeric runs / ANSI escapes in template text so '725 errors: ArmTask' and '16 errors: ArmTask' share a bucket (hex codes like 0xC00D3EA2 are preserved). Default true.
Clear enough to supply this argument.
normalize
Rate denominator for events/log_lines. per_session_hour (default) divides by the slice's recorded extent in hours (a session = one source topic's recorded extent); per_entity divides by the entities backing the slice -- its files when it is file-scoped, otherwise its datasets; raw_count applies no denominator. The result echoes the mode used as `normalize`, and each mover's rate array is named for it: `rates_per_hour` or `rates_per_entity`.
Clear enough to supply this argument.
log_topic_names
log_lines source: topics to scan (max 4). Omit and the tool picks up to 4 itself -- the log-bearing topic names common to every slice, so both sides scan the same topics. Scanned and unscanned topics are always reported.
Clear enough to supply this argument.
min_count
Ignore templates whose best slice weight is below this. Default 3.
Clear enough to supply this argument.
top_n
Cap on returned movers per source. Default 25.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "compare_slices",
"description": "Compare slices of the org's data and report what changed between them: which event\ntemplates, numeric signals and log lines are new, gone, up or down, ranked by how much they\nmoved. Use it for \"what changed\", \"why is this run different from that one\", \"what is\ndifferent since the update\", \"is it getting worse month over month\" or \"how does this device\ncompare to the fleet\".\n\nA slice is an entity scope (file_ids, dataset_ids, dataset_tags or collection_id; none for\nthe whole org) plus an optional window on event data time, in epoch nanoseconds (use\nconvert_time_format). Slices may be completely disjoint -- two unrelated recordings is the\nnormal case. ``bucket`` expands a time range into week/month slices for trends.\n\nSources:\n- events: event-template rates from the event index. Fast.\n- signals: precomputed per-recording statistics of numeric message paths. Fast, but whole\n recordings only, so it is skipped for time-bounded and bucket slices. Data ingested with\n roboto.experimental.ingest carries none of these statistics, so signals cannot compare it.\n- log_lines: reads raw log topics live and diffs normalized line templates. Its 32\n (file x topic) scan budget is split equally between slices, so it suits a few files or\n small datasets, not fleets or many slices.\n\nReading the result:\n- Movers are directional against the ``reference`` slice (default: the first).\n- ``slices[].coverage`` says how much data each source had. Many datasets have no events but\n rich signals, so check it before concluding a source found nothing.\n- Each source's ``skipped`` and the ``caveats`` list name every blind spot: thin slices,\n unscanned topics, little key overlap, and whole-org slices on large orgs too big to count\n or to pool signals for. Skipped or unscanned means unknown, not quiet, and a quiet diff is\n not proof nothing changed.\n- A signal mover with a ``bookkeeping`` reason (a clock, counter or process id, or a constant\n reference) ranks last and can usually be ignored.\n- A \"new\" event template can mean the extraction pipeline changed rather than the device;\n ``caveats`` flags event-type drift.\n\nWhen a slice is too large, narrow it: dataset_ids, dataset_tags or file_ids instead of the\nwhole org, a shorter window, or fewer buckets.",
"inputSchema": {
"type": "object",
"properties": {
"top_n": {
"type": "integer",
"minimum": 1,
"description": "Cap on returned movers per source. Default 25."
},
"bucket": {
"type": "object",
"required": [
"period",
"start_time",
"end_time"
],
"properties": {
"period": {
"enum": [
"week",
"month"
],
"type": "string"
},
"end_time": {
"type": "integer",
"description": "Exclusive end of the range, in epoch nanoseconds (integer)."
},
"file_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Scope to these files (fl_...) -- the common case for comparing one recording against another. At most one of file_ids / dataset_ids / dataset_tags / collection_id."
},
"start_time": {
"type": "integer",
"description": "Inclusive start of the range, in epoch nanoseconds (integer)."
},
"dataset_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Scope to these datasets."
},
"dataset_tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Scope to datasets carrying ALL of these tags."
},
"collection_id": {
"type": "string",
"description": "Scope to a collection's latest membership (cl_...). Any collection type is accepted: dataset and file collections are used directly; event and session collections resolve to the files their members occur in."
}
},
"description": "Sugar: expand into calendar slices (<= 12) sharing one optional entity scope. Provide exactly one of slices or bucket. The signals source never runs in bucket mode."
},
"slices": {
"type": "array",
"items": {
"type": "object",
"required": [
"label"
],
"properties": {
"label": {
"type": "string",
"description": "Unique short name; appears in the result."
},
"end_time": {
"type": "integer",
"description": "Exclusive upper bound on data time, in epoch nanoseconds (integer): data at end_time belongs to the next slice."
},
"file_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Scope to these files (fl_...) -- the common case for comparing one recording against another. At most one of file_ids / dataset_ids / dataset_tags / collection_id."
},
"start_time": {
"type": "integer",
"description": "Inclusive lower bound on DATA time, as EPOCH NANOSECONDS (integer). No other unit or format is accepted -- use convert_time_format to turn a human date into ns. Note: the signals source cannot serve time-bounded slices (whole-recording stats) and is skipped for them."
},
"dataset_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Scope to these datasets."
},
"dataset_tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Scope to datasets carrying ALL of these tags."
},
"collection_id": {
"type": "string",
"description": "Scope to a collection's latest membership (cl_...). Any collection type is accepted: dataset and file collections are used directly; event and session collections resolve to the files their members occur in."
}
}
},
"maxItems": 12,
"minItems": 2,
"description": "The slices to compare. Provide exactly one of slices or bucket."
},
"sources": {
"type": "array",
"items": {
"enum": [
"events",
"signals",
"log_lines"
],
"type": "string"
},
"description": "Which comparison surfaces to run. Default: all three when every slice is file-scoped, otherwise [\"events\", \"signals\"]. events and signals are indexed and fast, and carry no scan budget. log_lines reads raw topic data live and is bounded by a 32 (file x topic) scan budget split equally between slices, so each slice may scan only 32//n_slices pairs -- with many slices, or slices of many files, log_lines scans little or nothing. Prefer [\"events\", \"signals\"] for many-slice, bucket and fleet-wide questions. Unscanned topics are reported as such while the other sources still answer."
},
"min_count": {
"type": "integer",
"minimum": 1,
"description": "Ignore templates whose best slice weight is below this. Default 3."
},
"normalize": {
"enum": [
"per_session_hour",
"per_entity",
"raw_count"
],
"type": "string",
"description": "Rate denominator for events/log_lines. per_session_hour (default) divides by the slice's recorded extent in hours (a session = one source topic's recorded extent); per_entity divides by the entities backing the slice -- its files when it is file-scoped, otherwise its datasets; raw_count applies no denominator. The result echoes the mode used as `normalize`, and each mover's rate array is named for it: `rates_per_hour` or `rates_per_entity`."
},
"reference": {
"type": "string",
"description": "Label of the slice to compare against (the baseline side). Default: the first slice; in bucket mode, the earliest bucket that holds any events, so an empty leading month does not make every template rank 'new'."
},
"count_field": {
"type": "string",
"description": "events source: metadata key holding the occurrence count an event row represents (e.g. 'count', 'occurrence_count'). Default 'auto' (recognizes those two); pass a key explicitly to override, or 'none' to count rows. Non-numeric values weigh 1."
},
"template_field": {
"type": "string",
"description": "events source: what identifies a template -- 'name' (default) or 'metadata.<dot.path>'. Events missing the path are tallied as untemplated, not dropped."
},
"log_topic_names": {
"type": "array",
"items": {
"type": "string"
},
"description": "log_lines source: topics to scan (max 4). Omit and the tool picks up to 4 itself -- the log-bearing topic names common to every slice, so both sides scan the same topics. Scanned and unscanned topics are always reported."
},
"normalize_templates": {
"type": "boolean",
"description": "Collapse numeric runs / ANSI escapes in template text so '725 errors: ArmTask' and '16 errors: ArmTask' share a bucket (hex codes like 0xC00D3EA2 are preserved). Default true."
}
}
},
"annotations": {
"title": "compare_slices",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Reads a metric's values over a window in one of three shapes: bucketed by calendar period (one row per bucket: start_time_ns, end_time_ns, the aggregated value, and total = sessions that contributed); period 'all', one bucket over the whole window (the exact count, mean, sum, min or max for 'the last 30 days' -- never add partial buckets up yourself); or period 'session', one row per session (session_id, device_id, value, the session's start/end in ns), oldest first and paged. Metric values are recorded once per session, so calendar buckets answer 'did unlocks_per_run get worse over the summer?', 'all' answers 'what is the rate this month?', and session rows answer 'which sessions, exactly?'. group_by splits buckets (and labels session rows) by device.device_id, session.custom.<name>, device.custom.<name>; `condition` filters the sessions behind the values by the session, its device, or its collections: session.<field> (session_id, name, min_timestamp_ns, max_timestamp_ns, duration, tags, created, modified), device.<field> (device_id, tags, metadata.<key>, created, modified), session.custom.<name> / device.custom.<name> / collection.custom.<name> (Ready custom fields), collection.collection_id. Buckets snap outward to whole UTC periods and only periods with data are returned; a step change inside a bucket is averaged away, so confirm onsets with session rows. Give the window as dates (integer Unix-epoch nanoseconds or an ISO-8601 date/time string (UTC), e.g. "2026-08-01"). Routing: metric names come from list_related(related_type='metric_definition'); one session's values across every metric come from list_related(entity_id=<se_...>, related_type='metric').
The description explains the capability, when to use it, and what to expect.
Inputs93/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
name
Exact name of the metric definition (not its mt_ ID).
Clear enough to supply this argument.
period
Calendar bucket size in UTC; 'all' for one bucket over the whole window; 'session' for one row per session with no aggregation.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregation
Function applied to the values in each bucket. Required for calendar periods and period 'all'; omit for period 'session'.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_time_ns
Inclusive start of the window, as integer Unix-epoch nanoseconds or an ISO-8601 date/time string (UTC), e.g. "2026-08-01". Prefer the date form over computing nanoseconds.
Clear enough to supply this argument.
end_time_ns
Exclusive end of the window, as integer Unix-epoch nanoseconds or an ISO-8601 date/time string (UTC), e.g. "2026-08-01".
Clear enough to supply this argument.
time_filter
Which session timestamp places a value in the window and its bucket: end_time (default) or start_time.
Clear enough to supply this argument.
group_by
Split each bucket into one row per distinct value of this field: device.device_id, session.custom.<name>, device.custom.<name>. With period 'session', labels each row with the value instead.
Clear enough to supply this argument.
condition
Filter on the sessions behind the values. Either a leaf {field, comparator, value} -- comparators EQUALS, NOT_EQUALS, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, CONTAINS, NOT_CONTAINS, BEGINS_WITH, LIKE, NOT_LIKE, IS_NULL, IS_NOT_NULL, EXISTS, NOT_EXISTS -- or a group {operator: AND|OR, conditions: [...]}. Fields: session.<field> (session_id, name, min_timestamp_ns, max_timestamp_ns, duration, tags, created, modified), device.<field> (device_id, tags, metadata.<key>, created, modified), session.custom.<name> / device.custom.<name> / collection.custom.<name> (Ready custom fields), collection.collection_id.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
device_ids
Only values published by these devices.
Clear enough to supply this argument.
session_ids
Only values from these sessions (se_ IDs).
Clear enough to supply this argument.
page_token
period 'session' only: the next_token of the previous page.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "aggregate_metric",
"description": "Reads a metric's values over a window in one of three shapes: bucketed by calendar period (one row per bucket: start_time_ns, end_time_ns, the aggregated value, and total = sessions that contributed); period 'all', one bucket over the whole window (the exact count, mean, sum, min or max for 'the last 30 days' -- never add partial buckets up yourself); or period 'session', one row per session (session_id, device_id, value, the session's start/end in ns), oldest first and paged. Metric values are recorded once per session, so calendar buckets answer 'did unlocks_per_run get worse over the summer?', 'all' answers 'what is the rate this month?', and session rows answer 'which sessions, exactly?'. group_by splits buckets (and labels session rows) by device.device_id, session.custom.<name>, device.custom.<name>; `condition` filters the sessions behind the values by the session, its device, or its collections: session.<field> (session_id, name, min_timestamp_ns, max_timestamp_ns, duration, tags, created, modified), device.<field> (device_id, tags, metadata.<key>, created, modified), session.custom.<name> / device.custom.<name> / collection.custom.<name> (Ready custom fields), collection.collection_id. Buckets snap outward to whole UTC periods and only periods with data are returned; a step change inside a bucket is averaged away, so confirm onsets with session rows. Give the window as dates (integer Unix-epoch nanoseconds or an ISO-8601 date/time string (UTC), e.g. \"2026-08-01\"). Routing: metric names come from list_related(related_type='metric_definition'); one session's values across every metric come from list_related(entity_id=<se_...>, related_type='metric').",
"inputSchema": {
"type": "object",
"required": [
"name",
"period",
"start_time_ns",
"end_time_ns"
],
"properties": {
"name": {
"type": "string",
"description": "Exact name of the metric definition (not its mt_ ID)."
},
"period": {
"enum": [
"daily",
"weekly",
"monthly",
"quarterly",
"yearly",
"all",
"session"
],
"type": "string",
"description": "Calendar bucket size in UTC; 'all' for one bucket over the whole window; 'session' for one row per session with no aggregation."
},
"group_by": {
"type": "string",
"description": "Split each bucket into one row per distinct value of this field: device.device_id, session.custom.<name>, device.custom.<name>. With period 'session', labels each row with the value instead."
},
"condition": {
"type": "object",
"description": "Filter on the sessions behind the values. Either a leaf {field, comparator, value} -- comparators EQUALS, NOT_EQUALS, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, CONTAINS, NOT_CONTAINS, BEGINS_WITH, LIKE, NOT_LIKE, IS_NULL, IS_NOT_NULL, EXISTS, NOT_EXISTS -- or a group {operator: AND|OR, conditions: [...]}. Fields: session.<field> (session_id, name, min_timestamp_ns, max_timestamp_ns, duration, tags, created, modified), device.<field> (device_id, tags, metadata.<key>, created, modified), session.custom.<name> / device.custom.<name> / collection.custom.<name> (Ready custom fields), collection.collection_id."
},
"device_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Only values published by these devices."
},
"page_token": {
"type": "string",
"description": "period 'session' only: the next_token of the previous page."
},
"aggregation": {
"enum": [
"sum",
"mean",
"max",
"min",
"count"
],
"type": "string",
"description": "Function applied to the values in each bucket. Required for calendar periods and period 'all'; omit for period 'session'."
},
"end_time_ns": {
"type": [
"integer",
"string"
],
"description": "Exclusive end of the window, as integer Unix-epoch nanoseconds or an ISO-8601 date/time string (UTC), e.g. \"2026-08-01\"."
},
"session_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Only values from these sessions (se_ IDs)."
},
"time_filter": {
"enum": [
"start_time",
"end_time"
],
"type": "string",
"description": "Which session timestamp places a value in the window and its bucket: end_time (default) or start_time."
},
"start_time_ns": {
"type": [
"integer",
"string"
],
"description": "Inclusive start of the window, as integer Unix-epoch nanoseconds or an ISO-8601 date/time string (UTC), e.g. \"2026-08-01\". Prefer the date form over computing nanoseconds."
}
}
},
"annotations": {
"title": "aggregate_metric",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description91/100
Find action invocations (runs) in your org, filtered by status, action name, and/or dataset id. Returns a compact list, newest first by default. Use this first to locate failed runs before explaining why they failed.
Describe the returned information so the agent knows whether it will answer the request.
Inputs95/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
status
Filter by status, e.g. ['Failed', 'Deadly']. Valid: Queued, Scheduled, Downloading, Processing, Uploading, Completed, Cancelled, Failed, Deadly.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
action_name
Filter to invocations of this action (exact action name).
Clear enough to supply this argument.
dataset_id
Filter to invocations that ran on this dataset.
Clear enough to supply this argument.
order
Sort by creation time. 'newest' (default) answers questions about recent or current runs; 'oldest' reads a dataset's runs in the order they happened.
Clear enough to supply this argument.
page_token
Token from a prior call's next_token, to fetch the next page. Repeat that call's other arguments unchanged.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "query_invocations",
"description": "Find action invocations (runs) in your org, filtered by status, action name, and/or dataset id. Returns a compact list, newest first by default. Use this first to locate failed runs before explaining why they failed.",
"inputSchema": {
"type": "object",
"required": [],
"properties": {
"order": {
"enum": [
"newest",
"oldest"
],
"type": "string",
"description": "Sort by creation time. 'newest' (default) answers questions about recent or current runs; 'oldest' reads a dataset's runs in the order they happened."
},
"status": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by status, e.g. ['Failed', 'Deadly']. Valid: Queued, Scheduled, Downloading, Processing, Uploading, Completed, Cancelled, Failed, Deadly."
},
"dataset_id": {
"type": "string",
"description": "Filter to invocations that ran on this dataset."
},
"page_token": {
"type": "string",
"description": "Token from a prior call's next_token, to fetch the next page. Repeat that call's other arguments unchanged."
},
"action_name": {
"type": "string",
"description": "Filter to invocations of this action (exact action name)."
}
}
},
"annotations": {
"title": "query_invocations",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Cheaply summarize an invocation's logs: scans every line for error/warning/traceback signals and returns a compact findings table plus the terminal status detail and which log chunks had hits. No model cost. Use this to explain why a run failed when the failure is error-tagged.
The description explains the capability, when to use it, and what to expect.
Inputs50/100
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
invocation_id
The unique ID of the invocation.
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "summarize_invocation_logs",
"description": "Cheaply summarize an invocation's logs: scans every line for error/warning/traceback signals and returns a compact findings table plus the terminal status detail and which log chunks had hits. No model cost. Use this to explain why a run failed when the failure is error-tagged.",
"inputSchema": {
"type": "object",
"required": [
"invocation_id"
],
"properties": {
"invocation_id": {
"type": "string",
"description": "The unique ID of the invocation."
}
}
},
"annotations": {
"title": "summarize_invocation_logs",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description82/100
Read the raw lines of one log chunk by chunk_id (as reported by summarize_invocation_logs or deep_scan_invocation_logs). Use it to zoom into a flagged chunk or read its neighbors. No model cost.
Describe the returned information so the agent knows whether it will answer the request.
Inputs75/100
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
invocation_id
The unique ID of the invocation.
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
chunk_id
The chunk ordinal to read, as reported by the log-summary tools.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "read_invocation_log_chunk",
"description": "Read the raw lines of one log chunk by chunk_id (as reported by summarize_invocation_logs or deep_scan_invocation_logs). Use it to zoom into a flagged chunk or read its neighbors. No model cost.",
"inputSchema": {
"type": "object",
"required": [
"invocation_id",
"chunk_id"
],
"properties": {
"chunk_id": {
"type": "integer",
"description": "The chunk ordinal to read, as reported by the log-summary tools."
},
"invocation_id": {
"type": "string",
"description": "The unique ID of the invocation."
}
}
},
"annotations": {
"title": "read_invocation_log_chunk",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description88/100
Deeply analyze an invocation's logs with an AI model that reads every chunk, surfacing anomalies even when nothing is error-tagged (e.g. 'processed 0 of N messages', silent fallbacks, partial parses). Escalate to this when a run failed but summarize_invocation_logs returned nothing useful. On a long log it scans the tail by default (where late failures surface); pass from='head' for a suspected setup failure. Costs AI credits; try summarize_invocation_logs first.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
invocation_id
The unique ID of the invocation.
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
from
Which end of the log to scan when it is longer than the chunk budget. 'tail' (default) reads the last chunks — best for a run that failed after doing work; 'head' reads the first chunks — best for a setup or startup failure.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "deep_scan_invocation_logs",
"description": "Deeply analyze an invocation's logs with an AI model that reads every chunk, surfacing anomalies even when nothing is error-tagged (e.g. 'processed 0 of N messages', silent fallbacks, partial parses). Escalate to this when a run failed but summarize_invocation_logs returned nothing useful. On a long log it scans the tail by default (where late failures surface); pass from='head' for a suspected setup failure. Costs AI credits; try summarize_invocation_logs first.",
"inputSchema": {
"type": "object",
"required": [
"invocation_id"
],
"properties": {
"from": {
"enum": [
"head",
"tail"
],
"type": "string",
"description": "Which end of the log to scan when it is longer than the chunk budget. 'tail' (default) reads the last chunks — best for a run that failed after doing work; 'head' reads the first chunks — best for a setup or startup failure."
},
"invocation_id": {
"type": "string",
"description": "The unique ID of the invocation."
}
}
},
"annotations": {
"title": "deep_scan_invocation_logs",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Returns an agent thread's conversation: its title, who created it, which agent produced it, and for each message the sequence number, role, status, text, and the names of the tools that message used. Tool arguments and tool results are omitted -- use fork_agent_thread to continue the conversation with all of that context still loaded. Only threads in your active org are readable, and a private thread only by the user who created it. Read a thread first to decide where to fork it: `default_fork_point` names the message a fork lands on by default. Returns the last 100 messages of a longer thread.
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
thread_id
The agent thread to read. Begins with 'ath_'; older threads begin with 'ch_' or 'ags_' and work the same way.
Clear enough to supply this argument.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_agent_thread",
"description": "Returns an agent thread's conversation: its title, who created it, which agent produced it, and for each message the sequence number, role, status, text, and the names of the tools that message used. Tool arguments and tool results are omitted -- use fork_agent_thread to continue the conversation with all of that context still loaded. Only threads in your active org are readable, and a private thread only by the user who created it. Read a thread first to decide where to fork it: `default_fork_point` names the message a fork lands on by default. Returns the last 100 messages of a longer thread.",
"inputSchema": {
"type": "object",
"required": [
"thread_id"
],
"properties": {
"thread_id": {
"type": "string",
"description": "The agent thread to read. Begins with 'ath_'; older threads begin with 'ch_' or 'ags_' and work the same way."
}
}
},
"annotations": {
"title": "get_agent_thread",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Convert times that are not already integer nanoseconds: user-typed or foreign-unit timestamps into the epoch ns every tool time input takes, a duration with a unit into ns, a duration relative to an instant into that absolute instant, two ns instants into the duration between them, or an ns instant into a calendar date to read. Tool outputs are already ns; do not convert them to restate them, and do not do this arithmetic by hand. Returns `results` for `times`, `duration_results` for `durations` and `interval_results` for `intervals`, each a dict keyed by the input's position (`"0"`, `"1"`, ...) whose entries echo their `input`, so nothing is dropped even if two inputs look alike. `times` recognizes: epoch numbers with a unit suffix (`...ns`, `...us`/`µs`, `...ms`, `...s`, optionally wrapped as `t=...`); ISO-8601, parsed as UTC (e.g. `2024-03-15T17:40:45Z` — a trailing `Z` or offset is honored, a zone-less timestamp is assumed UTC); decimal ROS `<sec>.<nsec>` strings (always seconds); and bare epoch integers, whose unit is inferred from magnitude (~10 digits = s, 13 = ms, 16 = us, 19 = ns). Pass `input_unit` to set the unit of bare integers. Output formats: `ns` (default), `us`, `ms`, `seconds-nanos` (e.g. `1771481251.468476035`), and `utc` (ISO-8601). Each converted time carries `epoch_ns` (canonical nanoseconds), `converted`, `output_format`, and the detected `input_format`. `durations` takes a number with a unit (`0.35 s`, `12 ms`, `-2 min`; a bare number is an error) and returns `duration_ns` and `duration_text` (`d=<ns>ns`); with `relative_to` (integer epoch ns, e.g. the user's t=0) each entry also carries the absolute `epoch_ns` and its `converted` rendering. For an offset or duration between two instants you already have (a peak to the end of the recording, a first threshold crossing to the peak), pass them in `intervals` and state each entry's `duration_ns` in a reply in readable units (ms, s, min); never subtract them yourself. An interval's `duration_ns` is `to` minus `from`, negative when `to` is earlier. Unparseable inputs carry an `error` instead. A resolved interval echoes `from` and `to` in place of `input`. For exact nanosecond fidelity, pass epoch values as integers or strings: ISO-8601 inputs resolve only to microsecond precision (sub-microsecond digits are dropped) and floating-point inputs may lose precision. Pre-epoch (negative) times are not supported.
The description explains the capability, when to use it, and what to expect.
Inputs46/100
5 of 7 inputs have descriptions. Add descriptions to the missing inputs, including where to obtain IDs and how to supply values.
times
Timestamps to convert. Each may be an epoch number (optionally unit-suffixed), an ISO-8601 string, or a decimal seconds string.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
output_format
Target format. Defaults to 'ns' (nanoseconds since the Unix epoch).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
input_unit
Unit to assume for bare integer inputs that have no unit suffix; overrides the magnitude heuristic. Ignored for values with a fractional part (always seconds), unit-suffixed values, and ISO-8601 strings.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
durations
Durations or offsets to convert to integer nanoseconds, each a number with a unit (ns, us, ms, s, min, h), e.g. '0.35 s'. Negative values are offsets before `relative_to`.
Resolve the mismatch between this description and its schema constraints.
relative_to
Integer nanoseconds since the Unix epoch. When set, each duration also resolves to the absolute instant `relative_to + duration`. Applies to `durations` only.
Clear enough to supply this argument.
intervals › items › from
Not provided
Missing description. Explain what this value means and how to supply it.
intervals › items › to
Not provided
Missing description. Explain what this value means and how to supply it.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "convert_time_format",
"description": "Convert times that are not already integer nanoseconds: user-typed or foreign-unit timestamps into the epoch ns every tool time input takes, a duration with a unit into ns, a duration relative to an instant into that absolute instant, two ns instants into the duration between them, or an ns instant into a calendar date to read. Tool outputs are already ns; do not convert them to restate them, and do not do this arithmetic by hand. Returns `results` for `times`, `duration_results` for `durations` and `interval_results` for `intervals`, each a dict keyed by the input's position (`\"0\"`, `\"1\"`, ...) whose entries echo their `input`, so nothing is dropped even if two inputs look alike. `times` recognizes: epoch numbers with a unit suffix (`...ns`, `...us`/`µs`, `...ms`, `...s`, optionally wrapped as `t=...`); ISO-8601, parsed as UTC (e.g. `2024-03-15T17:40:45Z` — a trailing `Z` or offset is honored, a zone-less timestamp is assumed UTC); decimal ROS `<sec>.<nsec>` strings (always seconds); and bare epoch integers, whose unit is inferred from magnitude (~10 digits = s, 13 = ms, 16 = us, 19 = ns). Pass `input_unit` to set the unit of bare integers. Output formats: `ns` (default), `us`, `ms`, `seconds-nanos` (e.g. `1771481251.468476035`), and `utc` (ISO-8601). Each converted time carries `epoch_ns` (canonical nanoseconds), `converted`, `output_format`, and the detected `input_format`. `durations` takes a number with a unit (`0.35 s`, `12 ms`, `-2 min`; a bare number is an error) and returns `duration_ns` and `duration_text` (`d=<ns>ns`); with `relative_to` (integer epoch ns, e.g. the user's t=0) each entry also carries the absolute `epoch_ns` and its `converted` rendering. For an offset or duration between two instants you already have (a peak to the end of the recording, a first threshold crossing to the peak), pass them in `intervals` and state each entry's `duration_ns` in a reply in readable units (ms, s, min); never subtract them yourself. An interval's `duration_ns` is `to` minus `from`, negative when `to` is earlier. Unparseable inputs carry an `error` instead. A resolved interval echoes `from` and `to` in place of `input`. For exact nanosecond fidelity, pass epoch values as integers or strings: ISO-8601 inputs resolve only to microsecond precision (sub-microsecond digits are dropped) and floating-point inputs may lose precision. Pre-epoch (negative) times are not supported.",
"inputSchema": {
"type": "object",
"properties": {
"times": {
"type": "array",
"items": {
"type": [
"string",
"number"
]
},
"description": "Timestamps to convert. Each may be an epoch number (optionally unit-suffixed), an ISO-8601 string, or a decimal seconds string."
},
"durations": {
"type": "array",
"items": {
"type": "string"
},
"description": "Durations or offsets to convert to integer nanoseconds, each a number with a unit (ns, us, ms, s, min, h), e.g. '0.35 s'. Negative values are offsets before `relative_to`."
},
"intervals": {
"type": "array",
"items": {
"type": "object",
"required": [
"from",
"to"
],
"properties": {
"to": {
"type": "integer"
},
"from": {
"type": "integer"
}
}
},
"description": "Pairs of instants, each integer nanoseconds since the Unix epoch, to return the signed duration `to - from` between."
},
"input_unit": {
"enum": [
"s",
"ms",
"us",
"ns"
],
"type": "string",
"description": "Unit to assume for bare integer inputs that have no unit suffix; overrides the magnitude heuristic. Ignored for values with a fractional part (always seconds), unit-suffixed values, and ISO-8601 strings."
},
"relative_to": {
"type": "integer",
"description": "Integer nanoseconds since the Unix epoch. When set, each duration also resolves to the absolute instant `relative_to + duration`. Applies to `durations` only."
},
"output_format": {
"enum": [
"ns",
"us",
"ms",
"seconds-nanos",
"utc"
],
"type": "string",
"description": "Target format. Defaults to 'ns' (nanoseconds since the Unix epoch)."
}
}
},
"annotations": {
"title": "convert_time_format",
"readOnlyHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Save a reusable Roboto visualizer layout: an arrangement of panels (plot / log / raw_message / map / image) and the signals each draws, matched by topic name + message path so it applies to any file with those topics. A layout holds no data; to open data with panels laid out, prefer create_visualizer_workspace, which can embed a layout inline. Signals are validated per panel type; a failure names the panel and signals so you can fix and retry, and nothing is saved on failure. Returns the layout_id and an https `url` that opens the layout in the Roboto web app.
The description explains the capability, when to use it, and what to expect.
Inputs50/100
13 of 22 inputs have descriptions. Add descriptions to the missing inputs, including where to obtain IDs and how to supply values.
name
Unique among your layouts. Omitted → 'AI layout <UTC timestamp> <suffix>'.
Clear enough to supply this argument.
accessibility
Who sees the saved layout. Defaults per surface (user in chat).
Clear enough to supply this argument.
arrangement
How panels are tiled. Default grid (near-square).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
scope › file_id
Not provided
Missing description. Explain what this value means and how to supply it.
scope › dataset_id
Not provided
Missing description. Explain what this value means and how to supply it.
panels › items › type
plot: numeric/boolean/categorical leaf signals, several per panel. log / raw_message: one topic's paths as text. image: exactly one image path. map: lat/lon pairs, one track each; a pair is two sibling fields of one topic, geo-typed or named lat/lon (gps_lat/gps_lon). A lone coordinate takes its one matching sibling.
Clear enough to supply this argument.
panels › items › title
Not provided
Missing description. Explain what this value means and how to supply it.
panels › items › style › color
CSS color (hex, rgb(a), hsl(a), or a name).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
panels › items › style › line_width
Not provided
Missing description. Explain what this value means and how to supply it.
panels › items › style › point_radius
Not provided
Missing description. Explain what this value means and how to supply it.
panels › items › style › y_axis
'y' (default), 'y1r', 'y2l', ...
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
panels › items › style › label
Not provided
Missing description. Explain what this value means and how to supply it.
panels › items › style › transform
A formula over y applied before plotting, e.g. 'y*180/pi'.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "create_visualizer_layout",
"description": "Save a reusable Roboto visualizer layout: an arrangement of panels (plot / log / raw_message / map / image) and the signals each draws, matched by topic name + message path so it applies to any file with those topics. A layout holds no data; to open data with panels laid out, prefer create_visualizer_workspace, which can embed a layout inline. Signals are validated per panel type; a failure names the panel and signals so you can fix and retry, and nothing is saved on failure. Returns the layout_id and an https `url` that opens the layout in the Roboto web app.",
"inputSchema": {
"type": "object",
"required": [
"panels"
],
"properties": {
"name": {
"type": "string",
"maxLength": 120,
"description": "Unique among your layouts. Omitted → 'AI layout <UTC timestamp> <suffix>'."
},
"scope": {
"type": "object",
"properties": {
"file_id": {
"type": "string"
},
"dataset_id": {
"type": "string"
}
},
"description": "Where topic_name/message_path signals are looked up: one file or one dataset. Not needed when every signal is an id, nor when creating a workspace, which looks in the datasets and files it opens.",
"additionalProperties": false
},
"panels": {
"type": "array",
"items": {
"type": "object",
"required": [
"type",
"signals"
],
"properties": {
"type": {
"enum": [
"plot",
"log",
"raw_message",
"map",
"image"
],
"type": "string",
"description": "plot: numeric/boolean/categorical leaf signals, several per panel. log / raw_message: one topic's paths as text. image: exactly one image path. map: lat/lon pairs, one track each; a pair is two sibling fields of one topic, geo-typed or named lat/lon (gps_lat/gps_lon). A lone coordinate takes its one matching sibling."
},
"style": {
"type": "object",
"properties": {
"color": {
"type": "string",
"description": "CSS color (hex, rgb(a), hsl(a), or a name)."
},
"label": {
"type": "string"
},
"y_axis": {
"type": "string",
"pattern": "^y(\\d+[lr])?$",
"description": "'y' (default), 'y1r', 'y2l', ..."
},
"transform": {
"type": "string",
"description": "A formula over y applied before plotting, e.g. 'y*180/pi'."
},
"line_width": {
"type": "number"
},
"point_radius": {
"type": "number"
}
},
"description": "Default style for every signal on this panel.",
"additionalProperties": false
},
"title": {
"type": "string"
},
"signals": {
"type": "array",
"items": {
"type": "object",
"properties": {
"style": {
"type": "object",
"properties": {
"color": {
"type": "string",
"description": "CSS color (hex, rgb(a), hsl(a), or a name)."
},
"label": {
"type": "string"
},
"y_axis": {
"type": "string",
"pattern": "^y(\\d+[lr])?$",
"description": "'y' (default), 'y1r', 'y2l', ..."
},
"transform": {
"type": "string",
"description": "A formula over y applied before plotting, e.g. 'y*180/pi'."
},
"line_width": {
"type": "number"
},
"point_radius": {
"type": "number"
}
},
"additionalProperties": false
},
"topic_name": {
"type": "string",
"description": "e.g. '/imu/data'. Requires message_path."
},
"message_path": {
"type": "string",
"description": "Dotted source path within the topic, e.g. 'gyro.x'."
},
"message_path_id": {
"type": "string",
"description": "An mp_… id."
}
},
"description": "One signal, named either by topic_name + message_path (no id lookup needed) or by a message_path_id you have. A signal never carries a file_id or dataset_id.",
"additionalProperties": false
},
"maxItems": 20,
"minItems": 1
}
},
"additionalProperties": false
},
"maxItems": 12,
"minItems": 1
},
"arrangement": {
"enum": [
"grid",
"rows",
"columns"
],
"type": "string",
"description": "How panels are tiled. Default grid (near-square)."
},
"accessibility": {
"enum": [
"organization",
"user"
],
"type": "string",
"description": "Who sees the saved layout. Defaults per surface (user in chat)."
}
},
"additionalProperties": false
},
"annotations": {
"title": "create_visualizer_layout",
"readOnlyHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Create a saved Roboto visualizer workspace -- the files/datasets to open plus the panels to show -- and return a URL that opens it in the Roboto web app with the data already laid out. Give the panels either inline via `layout` (plot / log / raw_message / map / image, each with its signals by message_path_id or topic_name + message_path within a `scope`) or as a saved layout via `layout_id`, never both. Pass `t` (epoch ns) to open at a moment. Signals are validated per panel type; a failure names the panel and signals so you can fix and retry, and nothing is saved on failure. Prefer this over the dataset/file page URLs whenever the user wants to see data plotted.
The description explains the capability, when to use it, and what to expect.
Inputs53/100
15 of 24 inputs have descriptions. Add descriptions to the missing inputs, including where to obtain IDs and how to supply values.
files
fl_… ids to open.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
datasets
ds_… ids to open (all their files).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
layout_id
A saved layout (ly_…) to open with.
Clear enough to supply this argument.
layout › arrangement
How panels are tiled. Default grid (near-square).
Clear enough to supply this argument.
layout › scope › file_id
Not provided
Missing description. Explain what this value means and how to supply it.
layout › scope › dataset_id
Not provided
Missing description. Explain what this value means and how to supply it.
layout › panels › items › type
plot: numeric/boolean/categorical leaf signals, several per panel. log / raw_message: one topic's paths as text. image: exactly one image path. map: lat/lon pairs, one track each; a pair is two sibling fields of one topic, geo-typed or named lat/lon (gps_lat/gps_lon). A lone coordinate takes its one matching sibling.
Clear enough to supply this argument.
layout › panels › items › title
Not provided
Missing description. Explain what this value means and how to supply it.
layout › panels › items › style › color
CSS color (hex, rgb(a), hsl(a), or a name).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
layout › panels › items › style › line_width
Not provided
Missing description. Explain what this value means and how to supply it.
layout › panels › items › style › point_radius
Not provided
Missing description. Explain what this value means and how to supply it.
layout › panels › items › style › y_axis
'y' (default), 'y1r', 'y2l', ...
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
layout › panels › items › style › label
Not provided
Missing description. Explain what this value means and how to supply it.
layout › panels › items › style › transform
A formula over y applied before plotting, e.g. 'y*180/pi'.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "create_visualizer_workspace",
"description": "Create a saved Roboto visualizer workspace -- the files/datasets to open plus the panels to show -- and return a URL that opens it in the Roboto web app with the data already laid out. Give the panels either inline via `layout` (plot / log / raw_message / map / image, each with its signals by message_path_id or topic_name + message_path within a `scope`) or as a saved layout via `layout_id`, never both. Pass `t` (epoch ns) to open at a moment. Signals are validated per panel type; a failure names the panel and signals so you can fix and retry, and nothing is saved on failure. Prefer this over the dataset/file page URLs whenever the user wants to see data plotted.",
"inputSchema": {
"type": "object",
"properties": {
"t": {
"type": "integer",
"minimum": 0,
"description": "Epoch nanoseconds to open the workspace at (optional)."
},
"files": {
"type": "array",
"items": {
"type": "string"
},
"description": "fl_… ids to open."
},
"layout": {
"type": "object",
"required": [
"panels"
],
"properties": {
"scope": {
"type": "object",
"properties": {
"file_id": {
"type": "string"
},
"dataset_id": {
"type": "string"
}
},
"description": "Where topic_name/message_path signals are looked up: one file or one dataset. Not needed when every signal is an id, nor when creating a workspace, which looks in the datasets and files it opens.",
"additionalProperties": false
},
"panels": {
"type": "array",
"items": {
"type": "object",
"required": [
"type",
"signals"
],
"properties": {
"type": {
"enum": [
"plot",
"log",
"raw_message",
"map",
"image"
],
"type": "string",
"description": "plot: numeric/boolean/categorical leaf signals, several per panel. log / raw_message: one topic's paths as text. image: exactly one image path. map: lat/lon pairs, one track each; a pair is two sibling fields of one topic, geo-typed or named lat/lon (gps_lat/gps_lon). A lone coordinate takes its one matching sibling."
},
"style": {
"type": "object",
"properties": {
"color": {
"type": "string",
"description": "CSS color (hex, rgb(a), hsl(a), or a name)."
},
"label": {
"type": "string"
},
"y_axis": {
"type": "string",
"pattern": "^y(\\d+[lr])?$",
"description": "'y' (default), 'y1r', 'y2l', ..."
},
"transform": {
"type": "string",
"description": "A formula over y applied before plotting, e.g. 'y*180/pi'."
},
"line_width": {
"type": "number"
},
"point_radius": {
"type": "number"
}
},
"description": "Default style for every signal on this panel.",
"additionalProperties": false
},
"title": {
"type": "string"
},
"signals": {
"type": "array",
"items": {
"type": "object",
"properties": {
"style": {
"type": "object",
"properties": {
"color": {
"type": "string",
"description": "CSS color (hex, rgb(a), hsl(a), or a name)."
},
"label": {
"type": "string"
},
"y_axis": {
"type": "string",
"pattern": "^y(\\d+[lr])?$",
"description": "'y' (default), 'y1r', 'y2l', ..."
},
"transform": {
"type": "string",
"description": "A formula over y applied before plotting, e.g. 'y*180/pi'."
},
"line_width": {
"type": "number"
},
"point_radius": {
"type": "number"
}
},
"additionalProperties": false
},
"topic_name": {
"type": "string",
"description": "e.g. '/imu/data'. Requires message_path."
},
"message_path": {
"type": "string",
"description": "Dotted source path within the topic, e.g. 'gyro.x'."
},
"message_path_id": {
"type": "string",
"description": "An mp_… id."
}
},
"description": "One signal, named either by topic_name + message_path (no id lookup needed) or by a message_path_id you have. A signal never carries a file_id or dataset_id.",
"additionalProperties": false
},
"maxItems": 20,
"minItems": 1
}
},
"additionalProperties": false
},
"maxItems": 12,
"minItems": 1
},
"arrangement": {
"enum": [
"grid",
"rows",
"columns"
],
"type": "string",
"description": "How panels are tiled. Default grid (near-square)."
}
},
"description": "Inline layout: the panels to show. Exactly one of layout / layout_id.",
"additionalProperties": false
},
"datasets": {
"type": "array",
"items": {
"type": "string"
},
"description": "ds_… ids to open (all their files)."
},
"layout_id": {
"type": "string",
"description": "A saved layout (ly_…) to open with."
}
},
"additionalProperties": false
},
"annotations": {
"title": "create_visualizer_workspace",
"readOnlyHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Saves a View: a named, shareable search over one table (datasets, files, events, sessions, devices, or collections) that the user can reopen from that table's View picker. Define it with structured filters (never RoboQL), or set from_current_table to save the table the user is looking at in the Roboto web app as it is. The caller becomes the View's author; private unless visibility says otherwise, and confirm with the user before sharing one with the whole org. Returns the view_id and an https url that opens the View. To rename or change an existing View use update_view; to remove one use delete_view.
The description explains the capability, when to use it, and what to expect.
Inputs55/100
9 of 14 inputs have descriptions. Add descriptions to the missing inputs, including where to obtain IDs and how to supply values.
name
Short human-readable name, e.g. 'Flights from last week'.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
target
Which table the View searches. Required unless from_current_table is set, in which case it is taken from the table on screen.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
visibility
Who can see the View. Defaults to private (the user alone) in chat and to organization in Slack; organization shares it with every member of the org.
Clear enough to supply this argument.
filters › match_mode
Whether a row must match every filter (AND, the default) or any.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
filters › filters › items › type
Not provided
Missing description. Explain what this value means and how to supply it.
filters › filters › items › field
Not provided
Missing description. Explain what this value means and how to supply it.
filters › filters › items › comparator
Not provided
Missing description. Explain what this value means and how to supply it.
filters › filters › items › values
Not provided
Missing description. Explain what this value means and how to supply it.
filters › filters › items › or_null
EQUALS / NOT_EQUALS only: also match where the field is not set.
Clear enough to supply this argument.
display › visible_columns
Columns to show, in order. Omit to keep the table's defaults.
Clear enough to supply this argument.
display › sort_by
Column to sort by.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
display › sort_direction
Not provided
Missing description. Explain what this value means and how to supply it.
display › page_size
Rows per page.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
from_current_table
Save the table the user is looking at in the Roboto web app exactly as it is: its filters, columns, sort, and page size, as reported in the client context's table_state. Use this for 'save what I'm looking at'. Mutually exclusive with filters and display; unavailable outside the web app.
Clear enough to supply this argument.
Annotations50/100
Declare idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "create_view",
"description": "Saves a View: a named, shareable search over one table (datasets, files, events, sessions, devices, or collections) that the user can reopen from that table's View picker. Define it with structured filters (never RoboQL), or set from_current_table to save the table the user is looking at in the Roboto web app as it is. The caller becomes the View's author; private unless visibility says otherwise, and confirm with the user before sharing one with the whole org. Returns the view_id and an https url that opens the View. To rename or change an existing View use update_view; to remove one use delete_view.",
"inputSchema": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 120,
"description": "Short human-readable name, e.g. 'Flights from last week'."
},
"target": {
"enum": [
"datasets",
"files",
"events",
"sessions",
"devices",
"collections"
],
"type": "string",
"description": "Which table the View searches. Required unless from_current_table is set, in which case it is taken from the table on screen."
},
"display": {
"type": "object",
"properties": {
"sort_by": {
"type": "string",
"description": "Column to sort by."
},
"page_size": {
"type": "integer",
"minimum": 1,
"description": "Rows per page."
},
"sort_direction": {
"enum": [
"ASC",
"DESC"
],
"type": "string"
},
"visible_columns": {
"type": "array",
"items": {
"type": "string"
},
"description": "Columns to show, in order. Omit to keep the table's defaults."
}
},
"description": "How results are shown. Any field may be omitted to leave the table's own default in place.",
"additionalProperties": false
},
"filters": {
"type": "object",
"required": [
"filters"
],
"properties": {
"filters": {
"type": "array",
"items": {
"type": "object",
"required": [
"type",
"field",
"comparator"
],
"properties": {
"type": {
"enum": [
"string",
"numeric",
"date",
"boolean",
"set",
"enum",
"identity",
"metric"
],
"type": "string"
},
"field": {
"type": "string"
},
"values": {
"type": "array",
"items": {
"type": [
"string",
"number",
"boolean"
]
}
},
"or_null": {
"type": "boolean",
"description": "EQUALS / NOT_EQUALS only: also match where the field is not set."
},
"comparator": {
"type": "string"
}
},
"additionalProperties": false
},
"minItems": 1
},
"match_mode": {
"enum": [
"AND",
"OR"
],
"type": "string",
"description": "Whether a row must match every filter (AND, the default) or any."
}
},
"description": "Filters on the View's table, matched by every filter (match_mode AND) or any (OR).\nFields by table, as field: type (an enum's values) [comparators when the field accepts only some]. The files ingestion_status filter takes a file's ingestion_label values, not RoboQL's not_ingested:\ndatasets: dataset_id: string; name: string; description: string; device_id: string; created: date; created_by: identity; modified: date; modified_by: identity; tags: set\nfiles: file_id: string; name: string; relative_path: string; description: string; uri: string; device_id: string; origination: string; upload_id: string; association_id: string; created: date; created_by: identity; modified: date; modified_by: identity; size: numeric; ingestion_status: enum (ingested | partly_ingested | awaiting_ingestion | stored_as_is); tags: set\nevents: event_id: string; name: string; description: string; duration: numeric; created: date; created_by: identity; modified: date; modified_by: identity; tags: set; session.id: string [EQUALS, NOT_EQUALS]; session.name: string; session.tags: string [CONTAINS, NOT_CONTAINS]; topics[0].topic_name: string [EQUALS, NOT_EQUALS, CONTAINS, NOT_CONTAINS, LIKE, NOT_LIKE]\nsessions: id: string [EQUALS, NOT_EQUALS]; name: string; start_time: date; end_time: date; duration: numeric; created_by: identity; modified_by: identity; tags: string [CONTAINS, NOT_CONTAINS]; dataset.dataset_id: string [EQUALS, NOT_EQUALS]; device.device_id: string [EQUALS, NOT_EQUALS]\ndevices: device_id: string; tags: string [CONTAINS, NOT_CONTAINS]; created_by: identity; created: date; modified: date\ncollections: collection_id: string [EQUALS, NOT_EQUALS]; name: string; description: string; resource_type: enum (dataset | event | file | session); created: date; created_by: string; tags: set\nComparators by type: string: CONTAINS, EQUALS, IS_NOT_NULL, IS_NULL, LIKE, NOT_CONTAINS, NOT_EQUALS, NOT_LIKE; numeric and metric: EQUALS, GREATER_THAN, GREATER_THAN_OR_EQUAL, IS_NOT_NULL, IS_NULL, LESS_THAN, LESS_THAN_OR_EQUAL, NOT_EQUALS; date: BETWEEN, GREATER_THAN, IS_NOT_NULL, IS_NULL, LESS_THAN, or a preset with no values: TODAY, LAST_3_HOURS, LAST_8_HOURS, LAST_24_HOURS, LAST_7_DAYS, LAST_30_DAYS, LAST_90_DAYS, THIS_MONTH; boolean: EQUALS, IS_NOT_NULL, IS_NULL; set: CONTAINS, NOT_CONTAINS; enum: EQUALS, IS_NOT_NULL, IS_NULL, NOT_EQUALS; identity: IS_USER, IS_DEVICE, IS_INVOCATION, IS_INTEGRATION, IS_ORG with principal ids as values (user:<id>, device:<id>@<org>), or IS_ANY_USER, IS_ANY_DEVICE, IS_ANY_INVOCATION, IS_ANY_INTEGRATION, IS_ANY_ORG with no values.\nValues: strings for string, date (ISO-8601 or YYYY-MM-DD), set, enum and identity; numbers for numeric and metric; one or both booleans for boolean. EQUALS over several values matches any of them and NOT_EQUALS none of them; neither matches a field that is not set unless or_null: true. BETWEEN takes two dates. IS_NULL, IS_NOT_NULL, the date presets and IS_ANY_* take none.\nAlso filterable, per org: custom fields as custom.<field_name> on every table but files, typed from list_related (related_type custom_field): string, number->numeric, timestamp->date, boolean, enum. Metrics as metric.<name> with type metric on sessions (list_related with related_type metric_definition).",
"additionalProperties": false
},
"visibility": {
"enum": [
"private",
"organization"
],
"type": "string",
"description": "Who can see the View. Defaults to private (the user alone) in chat and to organization in Slack; organization shares it with every member of the org."
},
"from_current_table": {
"type": "boolean",
"description": "Save the table the user is looking at in the Roboto web app exactly as it is: its filters, columns, sort, and page size, as reported in the client context's table_state. Use this for 'save what I'm looking at'. Mutually exclusive with filters and display; unavailable outside the web app."
}
},
"additionalProperties": false
},
"annotations": {
"title": "create_view",
"readOnlyHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description84/100
Renames a View, or replaces what it searches for and how it shows results. Give a new name, new filters (with an optional display), or set from_current_table to save the table the user is looking at in the Roboto web app into the View. A View's table cannot change. Only the View's author, an editor they granted, or an org admin may change it. Use create_view to make a new one.
Explain mutations, irreversible effects, or prerequisites that matter before calling this tool.
Inputs52/100
8 of 13 inputs have descriptions. Add descriptions to the missing inputs, including where to obtain IDs and how to supply values.
view_id
The View to change, e.g. 'vw_abc123'.
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
name
A new name for the View.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
filters › match_mode
Whether a row must match every filter (AND, the default) or any.
Clear enough to supply this argument.
filters › filters › items › type
Not provided
Missing description. Explain what this value means and how to supply it.
filters › filters › items › field
Not provided
Missing description. Explain what this value means and how to supply it.
filters › filters › items › comparator
Not provided
Missing description. Explain what this value means and how to supply it.
filters › filters › items › values
Not provided
Missing description. Explain what this value means and how to supply it.
filters › filters › items › or_null
EQUALS / NOT_EQUALS only: also match where the field is not set.
Clear enough to supply this argument.
display › visible_columns
Columns to show, in order. Omit to keep the table's defaults.
Clear enough to supply this argument.
display › sort_by
Column to sort by.
Explain what happens when this optional value is omitted.
display › sort_direction
Not provided
Missing description. Explain what this value means and how to supply it.
display › page_size
Rows per page.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
from_current_table
Replace the View's definition with the table the user is looking at in the Roboto web app exactly as it is (the client context's table_state). The table must be the View's own table. Mutually exclusive with filters and display; unavailable outside the web app.
Clear enough to supply this argument.
Annotations50/100
Declare idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "update_view",
"description": "Renames a View, or replaces what it searches for and how it shows results. Give a new name, new filters (with an optional display), or set from_current_table to save the table the user is looking at in the Roboto web app into the View. A View's table cannot change. Only the View's author, an editor they granted, or an org admin may change it. Use create_view to make a new one.",
"inputSchema": {
"type": "object",
"required": [
"view_id"
],
"properties": {
"name": {
"type": "string",
"maxLength": 120,
"description": "A new name for the View."
},
"display": {
"type": "object",
"properties": {
"sort_by": {
"type": "string",
"description": "Column to sort by."
},
"page_size": {
"type": "integer",
"minimum": 1,
"description": "Rows per page."
},
"sort_direction": {
"enum": [
"ASC",
"DESC"
],
"type": "string"
},
"visible_columns": {
"type": "array",
"items": {
"type": "string"
},
"description": "Columns to show, in order. Omit to keep the table's defaults."
}
},
"description": "How results are shown. Any field may be omitted to leave the table's own default in place.",
"additionalProperties": false
},
"filters": {
"type": "object",
"required": [
"filters"
],
"properties": {
"filters": {
"type": "array",
"items": {
"type": "object",
"required": [
"type",
"field",
"comparator"
],
"properties": {
"type": {
"enum": [
"string",
"numeric",
"date",
"boolean",
"set",
"enum",
"identity",
"metric"
],
"type": "string"
},
"field": {
"type": "string"
},
"values": {
"type": "array",
"items": {
"type": [
"string",
"number",
"boolean"
]
}
},
"or_null": {
"type": "boolean",
"description": "EQUALS / NOT_EQUALS only: also match where the field is not set."
},
"comparator": {
"type": "string"
}
},
"additionalProperties": false
},
"minItems": 1
},
"match_mode": {
"enum": [
"AND",
"OR"
],
"type": "string",
"description": "Whether a row must match every filter (AND, the default) or any."
}
},
"description": "Filters on the View's table, matched by every filter (match_mode AND) or any (OR).\nFields by table, as field: type (an enum's values) [comparators when the field accepts only some]. The files ingestion_status filter takes a file's ingestion_label values, not RoboQL's not_ingested:\ndatasets: dataset_id: string; name: string; description: string; device_id: string; created: date; created_by: identity; modified: date; modified_by: identity; tags: set\nfiles: file_id: string; name: string; relative_path: string; description: string; uri: string; device_id: string; origination: string; upload_id: string; association_id: string; created: date; created_by: identity; modified: date; modified_by: identity; size: numeric; ingestion_status: enum (ingested | partly_ingested | awaiting_ingestion | stored_as_is); tags: set\nevents: event_id: string; name: string; description: string; duration: numeric; created: date; created_by: identity; modified: date; modified_by: identity; tags: set; session.id: string [EQUALS, NOT_EQUALS]; session.name: string; session.tags: string [CONTAINS, NOT_CONTAINS]; topics[0].topic_name: string [EQUALS, NOT_EQUALS, CONTAINS, NOT_CONTAINS, LIKE, NOT_LIKE]\nsessions: id: string [EQUALS, NOT_EQUALS]; name: string; start_time: date; end_time: date; duration: numeric; created_by: identity; modified_by: identity; tags: string [CONTAINS, NOT_CONTAINS]; dataset.dataset_id: string [EQUALS, NOT_EQUALS]; device.device_id: string [EQUALS, NOT_EQUALS]\ndevices: device_id: string; tags: string [CONTAINS, NOT_CONTAINS]; created_by: identity; created: date; modified: date\ncollections: collection_id: string [EQUALS, NOT_EQUALS]; name: string; description: string; resource_type: enum (dataset | event | file | session); created: date; created_by: string; tags: set\nComparators by type: string: CONTAINS, EQUALS, IS_NOT_NULL, IS_NULL, LIKE, NOT_CONTAINS, NOT_EQUALS, NOT_LIKE; numeric and metric: EQUALS, GREATER_THAN, GREATER_THAN_OR_EQUAL, IS_NOT_NULL, IS_NULL, LESS_THAN, LESS_THAN_OR_EQUAL, NOT_EQUALS; date: BETWEEN, GREATER_THAN, IS_NOT_NULL, IS_NULL, LESS_THAN, or a preset with no values: TODAY, LAST_3_HOURS, LAST_8_HOURS, LAST_24_HOURS, LAST_7_DAYS, LAST_30_DAYS, LAST_90_DAYS, THIS_MONTH; boolean: EQUALS, IS_NOT_NULL, IS_NULL; set: CONTAINS, NOT_CONTAINS; enum: EQUALS, IS_NOT_NULL, IS_NULL, NOT_EQUALS; identity: IS_USER, IS_DEVICE, IS_INVOCATION, IS_INTEGRATION, IS_ORG with principal ids as values (user:<id>, device:<id>@<org>), or IS_ANY_USER, IS_ANY_DEVICE, IS_ANY_INVOCATION, IS_ANY_INTEGRATION, IS_ANY_ORG with no values.\nValues: strings for string, date (ISO-8601 or YYYY-MM-DD), set, enum and identity; numbers for numeric and metric; one or both booleans for boolean. EQUALS over several values matches any of them and NOT_EQUALS none of them; neither matches a field that is not set unless or_null: true. BETWEEN takes two dates. IS_NULL, IS_NOT_NULL, the date presets and IS_ANY_* take none.\nAlso filterable, per org: custom fields as custom.<field_name> on every table but files, typed from list_related (related_type custom_field): string, number->numeric, timestamp->date, boolean, enum. Metrics as metric.<name> with type metric on sessions (list_related with related_type metric_definition).",
"additionalProperties": false
},
"view_id": {
"type": "string",
"description": "The View to change, e.g. 'vw_abc123'."
},
"from_current_table": {
"type": "boolean",
"description": "Replace the View's definition with the table the user is looking at in the Roboto web app exactly as it is (the client context's table_state). The table must be the View's own table. Mutually exclusive with filters and display; unavailable outside the web app."
}
},
"additionalProperties": false
},
"annotations": {
"title": "update_view",
"readOnlyHint": false,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Deletes a View permanently, for everyone it was shared with. Only the View's author (or an org admin) may delete it. Confirm with the user before calling this; there is no undo. Read the View with get_entity first if you are not sure which one is meant.
The description explains the capability, when to use it, and what to expect.
Inputs50/100
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
view_id
The View to delete, e.g. 'vw_abc123'.
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
Annotations50/100
Declare idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "delete_view",
"description": "Deletes a View permanently, for everyone it was shared with. Only the View's author (or an org admin) may delete it. Confirm with the user before calling this; there is no undo. Read the View with get_entity first if you are not sure which one is meant.",
"inputSchema": {
"type": "object",
"required": [
"view_id"
],
"properties": {
"view_id": {
"type": "string",
"description": "The View to delete, e.g. 'vw_abc123'."
}
},
"additionalProperties": false
},
"annotations": {
"title": "delete_view",
"readOnlyHint": false,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Copies an agent thread's history up to a chosen message into a NEW thread owned by you, and returns an https url that opens it in the Roboto web app. Use this to pick up an investigation an agent already did -- a CI incident summary, say -- with everything it loaded still in context; the copy carries the full tool arguments and results that get_agent_thread omits. The source thread is not modified. Only threads in your active org can be forked, and a private thread only by the user who created it. Forking creates the thread but does not run a turn in it: continue the conversation at the returned url.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
thread_id
The agent thread to fork. Begins with 'ath_'; older threads begin with 'ch_' or 'ags_' and work the same way.
Clear enough to supply this argument.
message_sequence_num
Highest message sequence number to copy, inclusive. Must name a COMPLETED message; call get_agent_thread to see which are. Omit to fork at the last COMPLETED message, which is what you want unless you are deliberately rewinding the conversation.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations50/100
Declare idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "fork_agent_thread",
"description": "Copies an agent thread's history up to a chosen message into a NEW thread owned by you, and returns an https url that opens it in the Roboto web app. Use this to pick up an investigation an agent already did -- a CI incident summary, say -- with everything it loaded still in context; the copy carries the full tool arguments and results that get_agent_thread omits. The source thread is not modified. Only threads in your active org can be forked, and a private thread only by the user who created it. Forking creates the thread but does not run a turn in it: continue the conversation at the returned url.",
"inputSchema": {
"type": "object",
"required": [
"thread_id"
],
"properties": {
"thread_id": {
"type": "string",
"description": "The agent thread to fork. Begins with 'ath_'; older threads begin with 'ch_' or 'ags_' and work the same way."
},
"message_sequence_num": {
"type": "integer",
"minimum": 0,
"description": "Highest message sequence number to copy, inclusive. Must name a COMPLETED message; call get_agent_thread to see which are. Omit to fork at the last COMPLETED message, which is what you want unless you are deliberately rewinding the conversation."
}
}
},
"annotations": {
"title": "fork_agent_thread",
"readOnlyHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description86/100
Search Roboto platform documentation, Python SDK reference, REST API specs, and CLI reference to answer questions about Roboto features, APIs, concepts, and usage patterns. Use this tool when the user asks about how to use Roboto, what features are available, API endpoints, SDK methods, CLI commands, or general platform questions, and to check an SDK class's import path and method signatures before writing code that uses it. Do NOT use this for querying the user's actual data (use the other data tools for that).
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
query
Natural language question about the Roboto platform, its features, APIs, SDK, CLI, or usage patterns.
Clear enough to supply this argument.
top_k
Number of documentation results to return (default 5, max 10).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations25/100
Declare destructiveHint, idempotentHint, openWorldHint based on the tool’s actual behavior.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "explain_roboto",
"description": "Search Roboto platform documentation, Python SDK reference, REST API specs, and CLI reference to answer questions about Roboto features, APIs, concepts, and usage patterns. Use this tool when the user asks about how to use Roboto, what features are available, API endpoints, SDK methods, CLI commands, or general platform questions, and to check an SDK class's import path and method signatures before writing code that uses it. Do NOT use this for querying the user's actual data (use the other data tools for that).",
"inputSchema": {
"type": "object",
"required": [
"query"
],
"properties": {
"query": {
"type": "string",
"description": "Natural language question about the Roboto platform, its features, APIs, SDK, CLI, or usage patterns."
},
"top_k": {
"type": "integer",
"maximum": 10,
"minimum": 1,
"description": "Number of documentation results to return (default 5, max 10)."
}
}
},
"annotations": {
"title": "explain_roboto",
"readOnlyHint": true
}
}
Loading score analytics…
Put your MCP to the test
A few tasks worth trying with this server, chosen from its reviewed tools. These have not been run.
01explain_robotoSearch Roboto platform documentation, Python SDK reference, REST API specs, and CLI reference to answer questions about Roboto features, APIs, concepts, and…
02Convert time formatConvert times that are not already integer nanoseconds: user-typed or foreign-unit timestamps into the epoch ns every tool time input takes, a duration with…
03Search commentsFull-text substring search over the comments in your organization.
04whoamiShow who you are authenticated as, the orgs you belong to (with their ids and names), and which org is currently active for this session.
05Get entityLooks up any single Roboto entity — action, agent, collection, comment, custom field, dataset, device, event, file, invocation, message path, metric definiti…
Want to run these tasks against your MCP server with us?
roboto-mcp setup, tools, quality score, publishing readiness, and alternatives.
How do I add roboto-mcp to Claude, ChatGPT, or Cursor?
Add roboto-mcp to Claude, ChatGPT, or Cursor by connecting its remote MCP URL in each client's MCP settings. In Claude, add a custom connector. In ChatGPT, add an MCP app. In Cursor, add an MCP server under Settings. The MCP URL is https://mcp.roboto.ai/mcp.
Is roboto-mcp MCP ready for ChatGPT and Claude?
As of November 11, 2026, roboto-mcp scores 90/100 on Manufact's MCP publishing checklist. The scan does not yet meet ChatGPT publishing checks and meets Claude publishing checks. Passing these checks does not guarantee marketplace approval.
How clear is roboto-mcp's MCP tool documentation?
As of November 11, 2026, roboto-mcp scores 72/100 in Manufact's review of tool titles, descriptions, input and output schemas, and behavior annotations. The review covers 36 of 36 discovered tools. The quality score measures documentation clarity; runtime behavior and marketplace readiness require separate checks.
What are the best alternatives to roboto-mcp?
As of November 12, 2026, alternatives to roboto-mcp in data-analytics on the Manufact MCP leaderboard include Neon, ClickHouse Cloud MCP Server, Crash Round Lab, Polygraph, and Ploti.
How can roboto-mcp improve its MCP documentation?
convert_time_format is the lowest-scoring reviewed tool at 62/100. No output schema is declared. Add one for structured results so clients can interpret returned fields. Open its review for feedback and a copyable fix prompt, then rerun the documentation review after updating your tool metadata.
Not your MCP?
Review any public or OAuth MCP server for protocol compatibility, tool documentation, and ChatGPT and Claude publishing readiness. No account required.