TopicForge
TopicForge is a read-only MCP server for inspecting ROS2 graphs, DDS discovery/QoS, and recorded bags without any write/publish ability.
Check health, effective mode, ROS/DDS backend availability, domain, sample caps.
List ROS2 topics and get per-topic type, publisher/subscriber counts, QoS reliability/durability.
Sample recent ROS2 messages with count/array controls.
Analyze ROS2 bags (.mcap/.db3/rosbag2 dirs), and peek decoded bag samples including ROS1 .bag.
List DDS participants and their lifecycle events (discovered/lost).
List DDS endpoints (writers/readers) with QoS, participant info, and orphan detection per topic.
Detect QoS mismatches between DDS readers/writers (reliability, durability, deadline, partitions, etc.).
Peek raw DDS samples on builtin discovery topics; user-topic payload decoding is disabled.
Get temporal DDS topic metrics (frequency, sequence gaps, latency) where supported.
Run in mock, live, or auto mode with deterministic fixtures for demos/CI.
Cannot publish, command a robot, alter QoS, or write to bags/bus.
Provides read-only inspection of ROS2 graphs, topics, QoS policies, and recorded bag files. Enables AI agents to discover ROS2 topics, sample messages, analyze .mcap/.db3 recordings, inspect DDS participants and endpoints, and debug QoS mismatches between readers and writers.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@TopicForgewhy isn't /scan reaching nav_planner? check for QoS mismatches"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
TopicForge
A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a ROS2 graph, recorded bag files and the DDS layer underneath ROS. The code has no write path: it cannot publish to the bus or command a robot, and there is no permission system to configure.
Every tool also declares itself read-only in the MCP protocol (readOnlyHint, destructiveHint=false, idempotentHint, with an honest openWorldHint), and a test fails if any tool lacks it. See SECURITY.md for the guarantee and its limits.
It gives the agent fourteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one note, units in the field names; see docs/CONTRACT.md), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why nav_planner gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see examples/02-debug-qos-mismatch.md). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with rmw_fastrtps_cpp) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See docs/dds-interop-matrix.md.
Quickstart
No ROS2 is needed; the mock adapter serves deterministic fixtures for a small differential robot (LIDAR + RGB camera). Python 3.10 to 3.13.
pip install topicforge
TOPICFORGE_MODE=mock python -m topicforge
# Windows PowerShell: $env:TOPICFORGE_MODE="mock"; python -m topicforgeThe server speaks MCP over stdio and waits for a client, so wire it into one. For Claude Desktop, add to claude_desktop_config.json:
{
"mcpServers": {
"topicforge": {
"command": "python",
"args": ["-m", "topicforge"],
"env": { "TOPICFORGE_MODE": "auto" }
}
}
}Then ask it to list the topics or to analyze /tmp/demo.mcap. For Claude Code: claude mcp add topicforge -- topicforge. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), topicforge --transport streamable-http --port 8765 serves http://127.0.0.1:8765/mcp on loopback only; see docs/CLIENTS.md.
Other MCP clients. Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in docs/CLIENTS.md. One-click: Add to Cursor | Install in VS Code.
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in docs/TESTING.md; recurring monitoring prompts and the privacy contract are in docs/TUTORIEL.md.
Related MCP server: ros2-mcp
Tools
Every response except health_check carries mode_effective ("live" or "mock"), so a caller can tell a real graph from fixtures, and a single note that explains anything unusual. health_check.contract_version is 2. The full reference, with every field, is generated in docs/TOOLS.md; upgrading from 0.6.x: docs/MIGRATION-0.7.md.
Tool | Purpose |
| Environment and mode introspection. Always succeeds; reports |
| Discover the ROS2 graph |
| Message type, publisher/subscriber counts and QoS for one topic |
| ROS 2 nodes on the graph: names, namespaces, duplicate names (one |
| One node's publishers, subscribers, services, actions, parameters (secrets masked) and |
| Peek recent messages on a ROS2 topic (count capped at 50) |
| Summarize a |
| DDS participants on the domain: vendor, |
| Incompatible QoS pairs between DDS readers and writers |
| Raw DDS samples; structured on the three builtin discovery topics, presence-only on user topics |
| Timeline of participant |
| Frequency, sequence-gap and latency schema; data only for builtin discovery topics |
| Decoded samples from a recorded bag, including ROS 1 |
| DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer) |
The server also offers two MCP prompts, diagnose-dds-bus and inspect-ros2-robot, that give a client the read-only call order, and short server instructions for clients that do not show prompts (see docs/CLIENTS.md).
Walkthroughs against the mock, each with the exact tool calls and payloads, are in examples/. To run the DDS tools against a real bus with several programs and vendors, see examples/dds/README.md (python examples/dds/run_all.py).
Modes
Mode | When to use | Backend |
| Development, demos, CI, screencasts | Deterministic fixtures |
| ROS2 sourced and on PATH, and/or a DDS backend |
|
| Detect what is available, else mock (default) | Best available |
live and auto degrade instead of failing: if neither ros2 nor a DDS backend comes up, the server serves the mock fixtures and health_check reports mode: "mock" next to requested_mode. Only TOPICFORGE_MODE=mock forces fixtures unconditionally. The live ROS2 adapter shells out to the ros2 CLI, so rclpy does not need to be importable.
DDS backends
pip install topicforge # includes Eclipse CycloneDDS where wheels exist (Python 3.10-3.13; Linux x86_64, Windows x64, macOS)
pip install "topicforge[dds]" # alias, kept so older commands work; forces a source build elsewhere (needs the Cyclone C library)
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforgeTOPICFORGE_DDS_BACKEND accepts mock (default), cyclone, fast and auto (fast, then cyclone, then mock, whichever binding imports). The default mock selects no DDS backend: installing the Cyclone binding is not enough, you must also set TOPICFORGE_DDS_BACKEND=cyclone. With TOPICFORGE_MODE=live and no backend selected, the DDS tools raise DDS module is not active: ... with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and health_check reports dds_backend: "none" plus dds_inactive_note. An explicit value is honoured with or without ros2 on PATH, in any mode except mock. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both ros2 and a DDS backend are up, a composite adapter routes the six ros2 CLI tools (list_topics, get_topic_info, list_nodes, get_node_info, sample_messages, analyze_bag) to the CLI and the six DDS tools to the DDS backend; peek_bag_samples reads bags without the CLI.
A Fast DDS adapter exists but has never run against a bus, and its fastdds Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no [dds-fast] extra. Any other value (rti, opensplice, coredx, intercom, opendds, dust) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in docs/DDS_QUICKSTART.md; error messages are in docs/TROUBLESHOOTING.md.
Configuration reference
Variable | Default | Description |
|
|
|
|
|
|
|
| Name or path of the ROS2 CLI binary |
|
| Opt-in anonymous telemetry; an unrecognized value aborts startup. See Telemetry |
|
|
|
|
| DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
| Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note |
Command-line options (not environment variables): --transport stdio|streamable-http (default stdio) and --port N (default 8765, only with streamable-http). The HTTP transport binds 127.0.0.1 only, with DNS-rebinding protection and no authentication; see docs/CLIENTS.md.
Samples with comments are in .env.example. Any invalid value stops the server with topicforge: configuration error: ... and exit code 2, so a typo cannot silently change behaviour.
Limitations
External validation against a simulated robot's ground truth (OmniSim): docs/VALIDATION.md.
DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (
.github/workflows/demo.yml,.github/workflows/ros2-live.yml). The Fast DDS adapter (TopicForge joining through thefastddsbinding) has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.User-topic payloads are not decoded.
peek_dds_sampleson a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read.topic_metricstherefore has data only for the builtin discovery topics and says so in itsstatus. It is a discovery-layer probe, not a publish-rate monitor.Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and
lost_nsis an upper bound of the death.Cyclone vendor ids: participants that do not follow the RTPS vendor-id convention in their GUID prefix (Dust DDS, and RTI by default) are reported with vendor
unknown.Single domain: the server observes the domain it joined at startup; changing it needs a restart.
DDS Security is not handled. A participant without credentials sees an empty secure bus.
detect_qos_mismatcheschecks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them inpolicies_unchecked. It returns aMismatchScanenvelope: readreportsfor the mismatches.Fast DDS serves no
list_endpoints.sample_messages(live) streamsros2 topic echountilcountmessages arrive ortimeout_s(1..40, default 10, for the whole call) runs out, and returns what arrived with anote(N of M messages within T s, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (payload.header.stamp.sec);timestamp_nsisheader.stamp(the publisher's clock, sim time on a simulation), the time in the body of/clock,/tfand/rosoutmessages (stamp_sourcepayload), or 0 for other headerless types, withstamp_sourceandreceived_ns(wall clock when the CLI printed it). Arrays are cut at 128 elements by default;max_array_length(1..65536, or null for no cut) andarrays_summary_onlychange that, and a cut is listed under_truncated_fields.nanandinfcome back as strings.countabove 50 is capped with a note.analyze_bag(live) parsesros2 bag infotext for the totals and counts; anomaly detection is mock-only. Per-topic times, rates ((n - 1) / span) andlatchedare added when the bag can be read locally (.db3with the standard library,.mcapwithrosbags), else rates fall back to count / bag duration (frequency_basis).peek_bag_samplesreads the file itself, throughrosbags, returns the firstcountmessages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; itstimestamp_nsis the message'sheader.stamp(stamp_sourceheader), the body time ofClock,TFMessageandLogmessages (payload) or, without either, the bag record time (stamp_sourcerecorded), andrecorded_nsis always the bag record time; bags that embed no message definitions (Humble.db3) are decoded with the Humble definitions, or the distro the bag records, andnotesays so. Withoutros2, bag tools return fixtures: checkhealth_checkformode: "mock"before trusting bag output.health_checkalso reportssim_clock_published(live only): true when/clockhas a publisher, a hint that header stamps may be simulation time.Calls are serialized per backend (one
ros2call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once withbusy.health_checkandpeek_bag_samplestake no lock.No streaming or push subscriptions: tools are strictly request/response.
Next: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (Fast DDS, RTI, OpenDDS), and DDS Security. Open work is tracked in issues.
Telemetry
Opt-in, anonymous and off by default. When off, instrumentation returns the handler unchanged: no event is built, no transport is constructed, no network code runs (pinned by tests/test_telemetry.py::test_build_app_off_makes_no_transport_calls).
TOPICFORGE_TELEMETRY=on python -m topicforgeOn-values: on, 1, true, yes, enabled. Off-values: unset, off, 0, false, no, disabled. Anything else is a configuration error rather than a silent "off".
When on, each tool call emits one event with exactly six fields:
Field | Example | Notes |
|
| One of the fourteen tools, never argument values |
|
| Handler wall-clock duration, 2 decimals |
|
| Mode of the adapter actually serving: |
|
| TopicForge server version |
|
| Random UUID per process, never persisted |
|
| Whether the handler returned or raised |
Never sent: topic names, message types or payloads, bag paths or contents, hostnames, usernames, IP addresses, environment variables, error messages. The field set is fenced by tests/test_telemetry.py::test_payload_contains_only_whitelisted_keys; adding a field requires updating this section. The default transport is a structured log line; there is no HTTP endpoint yet. The implementation is in src/topicforge/telemetry/.
Security model
TopicForge is designed for local trust: it runs as a subprocess of your MCP client on a machine you control and inspects your own ROS2 graph, DDS domain and bag files. It is not hardened for adversarial inputs.
TOPICFORGE_ROS2_BINaccepts an arbitrary path; treat it the way you treatPATH.analyze_bagandpeek_bag_samplesopen whatever path the client passes (no workspace isolation, no symlink restriction).All
ros2invocations usesubprocess.runwith an argument list, nevershell=True. ROS2 topic names are validated against^/[A-Za-z0-9_/]+$first.The server loads no third-party code at startup.
No outbound network calls unless telemetry is turned on.
Before exposing TopicForge to untrusted MCP clients (hosted endpoints, shared environments), add path isolation and revisit the TOPICFORGE_ROS2_BIN policy. Vulnerability reports: see SECURITY.md.
Development
git clone https://github.com/yaniswav/TopicForge.git && cd TopicForge
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
pip install -e ".[dev]"
python -m ruff check src tests
python -m pytestTests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the cyclonedds or fastdds binding skip themselves when it is absent, and the integration marker (real-bus tests) is deselected by default. The Makefile (make check) uses POSIX shell syntax; on plain PowerShell run the commands above. See CONTRIBUTING.md.
Upgrading
TopicForge is pre-1.0; CHANGELOG.md lists every change, including the yanked releases and removed extras. 0.7.0 changed the shape of tool results (contract_version 2): docs/MIGRATION-0.7.md maps every old field to its new one.
Layout
src/topicforge/
server/ MCP bootstrap, build_app(settings), prompts and instructions
tools/ thin MCPServer handlers, no backend logic
services/ input validation, orchestration, adapter factory
adapters/ ros2_live, ros2_mock, dds_cyclone, dds_fast, common/ (binding-free logic)
models/ frozen Pydantic schemas, the contract with MCP clients
config/ settings and mode resolution
telemetry/ opt-in, off by default
examples/ mock walkthroughs (*.md) and runnable live DDS examples (dds/)
scripts/ real-bus interop checks
docs/ guides, product plan
tests/ pytest suite, mock-only, no ROS2 or DDS requiredLayers are strictly separated: handlers never call subprocess, adapters are the only code that talks to a backend, and new backends implement the MiddlewareAdapter protocol in adapters/base.py.
License
MIT, see LICENSE. Integration or support work for a specific ROS 2 / DDS setup: ethvignot.yanis@gmail.com.
Available Tools
14 toolsanalyze_bagARead-onlyIdempotent
Summarize a ROS 2 bag at path as a BagAnalysis: storage format, duration, message count, per-topic stats and mode_effective. Per topic frequency_hz is (n - 1) / (last - first message time) (frequency_basis topic_span), with first_timestamp_ns, last_timestamp_ns and latched; a latched topic whose messages all fall within 1 s (e.g. /tf_static) has a null frequency_hz. When the bag cannot be read locally or is a .mcap over 200 MiB, the rate falls back to count / bag duration (bag_duration) and note says why. Live mode runs ros2 bag info on .mcap, .db3 or rosbag2_* directories (for ROS 1 .bag use peek_bag_samples); mock mode returns fixture data for any bag-like path, and only mock detects anomalies. Raises an MCP error for a malformed or missing path (live), unparseable output, or without a ros2 CLI. Read-only; no side effects. Duration: runs the ros2 CLI behind the ROS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another ros2 call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Bag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed path. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | Why the result is less detailed than usual (e.g. rates over the whole bag duration); else `None`. |
| path | Yes | Bag path as supplied. |
| topics | Yes | Per-topic statistics for every topic present in the bag. |
| anomalies | No | Notes on gaps or clock jumps; mock mode only. |
| bag_format | No | Detected container: `mcap`, `db3`, `bag` or `unknown`; `None` for `ros2 bag info` text. |
| duration_s | Yes | Bag duration in seconds. |
| message_count | Yes | Total number of messages across all recorded topics. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
| storage_format | No | `mcap`, `sqlite3`, or other storage identifier when known. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnly/idempotent/non-destructive), and the description goes well beyond them: the 45 s budget, exclusive ROS lock behavior, immediate `busy` error with retry, error conditions, and the mode-dependent anomaly detection. This is exactly the extra context annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Core purpose and the sibling pointer are front-loaded, and the dense detail (frequency formula, fallback, modes, errors, duration) is organized with bold lead-ins. It is long, but nearly every sentence carries operational information; a minor trim of the formula detail is possible.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite an output schema existing, the description explains the non-obvious return semantics (`mode_effective`, `frequency_basis`, latched-topic null `frequency_hz`, `note` fallback) that an agent needs to interpret results. Combined with error and timing disclosure, nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% for the single `path` parameter, and the schema already documents the accepted formats and the ROS 1 exclusion, so the baseline of 3 applies. The description reinforces format acceptance but adds little parameter meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Summarize a ROS 2 bag at `path`') and names the return type `BagAnalysis` with its fields. Explicitly distinguishes itself from the sibling `peek_bag_samples` for ROS 1 `.bag`, so an agent can separate the two without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit routing rules: live mode for `.mcap`/`.db3`/`rosbag2_*`, direct the agent to `peek_bag_samples` for ROS 1 `.bag`, and mock mode semantics. It also names the failure conditions (malformed/missing path, unparseable output, missing `ros2` CLI) and the busy/retry case, which is complete when-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
detect_qos_mismatchesARead-onlyIdempotent
Explain why DDS readers and writers on a topic do not talk, and who will. Pairs every reader with every writer per topic and returns a MismatchScan: summary (one line, read it first), matched (pairs DDS will connect given the announced QoS; data flow is not observed), reports (incompatible or risky QoS pairs, with participant names, requested vs offered values and the failed rule in details), not_matched (pairs DDS never matches: different partitions or type names; the QoS rules are NOT evaluated for them, and latent_incompatible_policies lists what would also break once fixed), hints (probable typos in orphan topic names, type id notes), plus pairs_checked, topics_scanned, policies_checked and policies_unchecked. Checked: Partition (* and ? wildcards), type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership, DestinationOrder, DataRepresentation, History (risky only, where announced). An empty reports with a non-empty not_matched still means no data flows, and an all-empty result does not prove the bus healthy: discovery shows the QoS DECLARED, not runtime behavior (a 'deadline missed' log with compatible QoS means the writer's real period exceeds the deadline, which TopicForge cannot see). Pass topic to scope the scan. The lists are capped at 200 each (truncated; *_total keep the counts). A late_joiner match is a VOLATILE writer whose reader joined later on the same host: normal. Reports and partition splits carry suggested_fixes: text for a human, never applied by TopicForge; verify_with names the call that confirms the repair. Read-only by architecture. Raises an MCP error when no DDS module is active; mock returns fixtures. Duration: uses the DDS binding behind the DDS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another DDS call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
| topic | No | Optional DDS topic to scope the scan to: `scan` or `rt/scan`. Omit to scan all topics. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | One sentence of context, or `None`. |
| hints | Yes | Leads that are not findings: probable typos, type id notes, incomplete checks. |
| matched | No | Pairs DDS will connect given the announced QoS (data flow is not observed). |
| reports | Yes | Pairs sharing partition and type with incompatible or risky QoS. |
| summary | No | One-line reading of the scan: counts of incompatible, not matched and matched pairs. |
| truncated | No | True when `reports`, `matched` or `not_matched` hit the 200-entry cap (see `*_total`); narrow with `topic`. |
| not_matched | Yes | Pairs DDS never matches (partition or type name). Non-empty with empty `reports` is NOT all-clear: no data flows between them. |
| matched_total | No | Matched pairs before the size cap. |
| pairs_checked | Yes | Same-topic (reader, writer) pairs examined. |
| reports_total | No | Reports before the size cap. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
| topics_scanned | Yes | Topics that had at least one endpoint in scope. |
| policies_checked | Yes | Policies compared on every pair. |
| not_matched_total | No | Not-matched pairs before the size cap. |
| policies_unchecked | Yes | What the scan does not cover, each with a reason; a clean result says nothing about them. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Far exceeds the annotations: it discloses the 45 s duration and lock semantics, the busy error and retry behavior, the MCP error when no DDS module is active, mock fixtures, the 200-item truncation caps, and the crucial caveat that an empty result does not prove bus health. Read-only status is reinforced and no behavioral trait is left implicit.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the outcome and read-this-first `summary` guidance, which is strong. However, the description spends many clauses enumerating return fields of a `MismatchScan` even though an output schema already exists, making it longer than necessary.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity and the interpretive traps (not_matched vs empty reports, late_joiner being normal, suggested_fixes not applied), the description supplies everything needed to interpret results correctly, with the output schema freeing it from raw field-type explanation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one parameter with 100% schema description coverage, so the schema already documents `topic` including its 'scan'/'rt/scan' examples. The description's 'Pass `topic` to scope the scan' restates the schema without adding format or edge-case meaning, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: explaining why DDS readers/writers on a topic fail to communicate, and pairing readers with writers per topic. This is clearly distinguishable from siblings like list_topics or get_topic_info, which inventory rather than diagnose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear usage context (diagnosing QoS-driven connection failures, scoping with `topic`, retrying on a `busy` error) but never explicitly contrasts this with sibling diagnostics such as get_topic_info or health_check. No 'use this instead of X when Y' routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_node_infoARead-onlyIdempotent
ROS 2 graph only; on a DDS-only setup use list_participants. Describe one ROS 2 node as a NodeInfo: the topics it publishes and subscribes to, the services and actions it serves or calls (each {name, type}), its parameters and use_sim_time. Interfaces come from the graph (ros2 node info), which never waits for the node. Parameters come from a read (ros2 param dump) bounded by timeout_s: a node that does not answer in time is a finding, not an error: parameters is null, parameters_note says its executor is probably blocked, and the interfaces are still returned. A value whose name contains password, secret, token, api_key or credential is replaced by <masked> and a long value is cut (truncated, original_size), both said in parameters_note. With several nodes of that name duplicate_count is above 1 and note warns. Raises an MCP error when the node is not on the graph (the message lists close matches), the name is malformed, or without a ros2 CLI. Read-only: the only request TopicForge ever sends is a parameter read (list/get); it sends no other request to a node. Duration: runs the ros2 CLI behind the ROS lock, one call at a time: up to three CLI calls (node list and node info, 8 s each at most; then the parameter read, at most timeout_s, 20 s). The whole call, lock wait included, ends within 45 s: if another ros2 call holds the lock it fails at once with a busy error (retry), and a call that waited has less time for the parameter read.
| Name | Required | Description | Default |
|---|---|---|---|
| node | Yes | Fully qualified node name as `list_nodes` gives it, e.g. `/robot1/lidar_driver` (a missing leading `/` is added). | |
| timeout_s | No | Seconds to wait for the parameter read, 1..20, default 8. A node that does not answer is reported in `parameters_note`, not as an error. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | One sentence of context, or `None`. |
| full_name | Yes | Fully qualified node name, e.g. `/lidar_driver`. |
| parameters | No | Parameters sorted by name; `None` when unreadable (see `parameters_note`); an empty list means none. |
| publishers | Yes | Topics the node publishes. |
| subscribers | Yes | Topics the node subscribes to. |
| use_sim_time | No | Value of the node's `use_sim_time` parameter. `None` when unknown: see `use_sim_time_note`. |
| action_clients | Yes | Actions the node calls. |
| action_servers | Yes | Actions the node serves. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
| duplicate_count | Yes | Nodes using this exact full name; above 1, the data above belongs to one of them. |
| parameters_note | No | Why `parameters` is `None`, or what was masked or cut; `None` when complete. |
| service_clients | Yes | Services the node calls. |
| service_servers | Yes | Services the node offers, its own parameter services included. |
| use_sim_time_note | No | Why `use_sim_time` is `None`. `None` when it is set. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only/idempotent/non-destructive, yet the description adds substantial behavior beyond them: secret masking by keyword with <masked>, truncation with original_size, timeout semantics, lock contention producing a `busy` error, and a hard 45 s ceiling with the lock-wait tradeoff. It also clarifies that the only request sent is a parameter list/get.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the critical scope restriction before any detail, and each sentence carries real information (masking, timeouts, error conditions, duration). It is dense and long for a single paragraph, which costs it a point, but there is little waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Even though an output schema exists, the description explains the non-obvious return semantics callers must interpret: null parameters vs. error, parameters_note, duplicate_count/note for ambiguous names, and masking/truncation markers. Combined with the duration and error model, an agent has everything needed to call and interpret it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and both params are documented in the schema, so the baseline is 3. The description largely restates node-name format and the 1..20/default-8 timeout, though it does add the nuance that a call that waited on the lock has less time for the parameter read.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource ('Describe one ROS 2 node') and enumerates exactly what is returned (pub/sub topics, services/actions, params, use_sim_time). It also names the sibling list_participants as the alternative for DDS-only setups, so an agent can distinguish it from siblings without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit routing rule ('ROS 2 graph only; on a DDS-only setup use list_participants') plus clear when-not conditions: raises an MCP error for off-graph nodes, malformed names, or missing ros2 CLI, and a non-answering node is a finding to be reported rather than retried as an error.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_topic_infoARead-onlyIdempotent
ROS 2 graph only; on a DDS-only setup use list_endpoints. Describe one ROS 2 topic (fully qualified, e.g. /cmd_vel) as a TopicInfo: counts and, in live mode, publisher_qos / subscription_qos (reliability, durability where transient_local marks a latched topic such as /tf_static, endpoint_count; a null side says why in its _note) and publisher_nodes / subscriber_nodes. Raises an MCP error for a malformed or unknown topic, or without a ros2 CLI. Read-only; no side effects. Duration: runs the ros2 CLI behind the ROS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another ros2 call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
| topic | Yes | Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI. |
Output Schema
| Name | Required | Description |
|---|---|---|
| name | Yes | |
| note | No | One sentence of context, or `None`. |
| message_type | Yes | |
| publisher_qos | No | QoS of the publishers. `None` when it was not read: see `publisher_qos_note`. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
| publisher_count | Yes | |
| publisher_nodes | No | Publisher node names (`ros2 topic info --verbose`); empty when unknown. |
| subscriber_count | Yes | |
| subscriber_nodes | No | Subscriber node names; empty when unknown. |
| subscription_qos | No | QoS of the subscriptions. `None` when it was not read: see `subscription_qos_note`. |
| publisher_qos_note | No | Why `publisher_qos` is `None`. `None` when it is set. |
| subscription_qos_note | No | Why `subscription_qos` is `None`. `None` when it is set. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only/idempotent/no-destruction, and the description goes well beyond them: it discloses error-raising behavior, serializes via a ROS lock (one call at a time), a 45 s ceiling, and an immediate 'busy' failure with retry guidance. Lock contention and latency behavior are exactly the traits annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The critical scope constraint is front-loaded before any detail. Information density is high and every sentence carries content, though the deeply nested parentheticals (QoS fields, endpoint notes) make it slightly harder to scan than ideal.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read tool it covers scope, prerequisites, failure modes, latency/concurrency behavior, and even enumerates return fields despite an output schema existing. Nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single parameter already documents the fully-qualified `/`-prefixed format and segment rules. The description restates the same format in prose without adding syntax or semantics beyond the schema, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Describe one ROS 2 topic') with an output type ('TopicInfo') and precisely names the sibling it is not for ('on a DDS-only setup use list_endpoints'). An agent can distinguish it from list_topics, list_endpoints, and get_node_info without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly scopes the tool to the 'ROS 2 graph only' and routes to the alternative ('list_endpoints') for DDS-only setups. It further specifies failure conditions (malformed/unknown topic, missing ros2 CLI) that tell the agent when the call is and isn't appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
health_checkARead-onlyIdempotent
Report TopicForge environment state as a HealthReport: mode, ros_backend, dds_backend, ros_tools_available, ros2_distro, rmw_implementation, dds_domain_id and observed_domain_note (only the domain joined at startup is observed), server version and sample cap; for a live DDS backend also observer_started_ns / now_ns (nothing earlier was watched) and the discovery tracker status tracker_* (errors, evictions or a stale pass mean gaps). live with ros_backend none is a DDS-only setup: use list_endpoints. With dds_backend none, dds_inactive_note says why. payload_decoding is disabled (DDS user-topic payloads are not decoded); dds_security is not_supported. Always succeeds: call it first when something looks wrong. Read-only; no side effects. Duration: never waits for a lock and never runs the ros2 CLI; it answers at once.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | Yes | Mode actually serving: `mock` or `live` (`live` with `ros_backend` `none`: DDS tools only). May differ from `requested_mode` after a fallback. |
| note | No | One sentence of context, or `None`. |
| now_ns | No | Server wall-clock time (ns since epoch) when this report was built. |
| rmw_source | No | `env`, `ros2_cli`, `distro_default` (not verified on the graph) or `none`. |
| dds_backend | No | DDS backend serving: `mock`, `cyclone`, `fast`, or `none` (DDS module inactive). |
| ros2_distro | No | `ROS_DISTRO` if set (disclosed by design under the local-trust model). |
| ros_backend | No | ROS 2 backend: `ros2_cli`, `mock` or `none`. |
| dds_security | No | DDS Security is not handled: protected endpoints and data are invisible. |
| dds_domain_id | No | DDS domain id observed when the DDS module is active. |
| requested_mode | Yes | Mode requested by configuration (may be `auto`). |
| ros2_available | Yes | Whether a `ros2` CLI is on PATH. |
| server_version | Yes | TopicForge version. |
| tracker_errors | No | Tracker passes that raised; non-zero means gaps. |
| tracker_passes | No | Completed discovery tracker passes since start. |
| tracker_running | No | Whether the discovery tracker thread is alive (Cyclone); `None` without a tracker. |
| contract_version | No | Output contract version; changes only when a field is renamed, removed or retyped. |
| max_sample_count | Yes | Server cap on samples per call; larger requests are clamped. |
| payload_decoding | No | `disabled`: DDS user-topic payloads are not decoded. |
| dds_inactive_note | No | Why `dds_backend` is `none` while ROS 2 serves (not selected, binding missing, or failed to start); else `null`. |
| rmw_implementation | No | ROS 2 middleware, e.g. `rmw_fastrtps_cpp`; `None` when `rmw_source` is `none`. |
| observer_started_ns | No | ns when the DDS observer joined; nothing earlier was watched. `None` without one. |
| ros_tools_available | No | True when the ROS 2 tools can run (`ros_backend` is not `none`); on a DDS-only setup use `list_endpoints`. |
| sim_clock_published | No | Whether `/clock` had a publisher in the last graph read (a hint that stamps may be sim time). `None` in mock or without a recent read; `health_check` never runs `ros2`. |
| middleware_available | No | True when a DDS backend serves; else whether the configured binding is importable. |
| observed_domain_note | No | Which DDS domain is observed: only the one joined at startup; others are invisible. |
| tracker_last_pass_ns | No | ns of the last tracker pass; far older than `now_ns` means stale lifecycle. |
| payload_decoding_note | No | Reason for `payload_decoding`. |
| tracker_cache_evictions | No | Discovery entries dropped from a full tracker cache (4096 each); non-zero means the bus is bigger than listed. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already give readOnly/destructive/idempotent hints, yet the description adds substantial behavioral context: 'Always succeeds', 'never waits for a lock and never runs the ros2 CLI; it answers at once', and the fact that only the domain joined at startup is observed, so earlier activity is invisible. It also discloses capability limits: payload_decoding is disabled and dds_security is not supported.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is dense but every sentence carries load: field list, DDS-only routing, capability limits, success guarantee, and duration. It front-loads what the report contains and the 'call it first' guidance. Slightly long for a health check, but no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the output schema exists, the description still adds essential context: what the report fields mean, how to interpret tracker errors/evictions, why observed_domain_note is limited, and the two capability limitations. Nothing an agent needs to interpret or trust the result is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes no parameters, so there is nothing to document, and the description doesn't waste space on params. It instead explains what fields the response will contain, which is useful even though an output schema exists.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Report TopicForge environment state as a HealthReport'. It immediately lists the report's fields, making the tool's output scope unmistakable. Distinguished from siblings like list_endpoints, which is named as the right tool for a DDS-only setup.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'call it first when something looks wrong', and names the alternative 'use list_endpoints' for a DDS-only setup. It also gives the condition under which the backend is DDS-only ('live' with ros_backend none).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_endpointsARead-onlyIdempotent
List every DDS endpoint (writer and reader) announced on the bus as an EndpointListing; one EndpointInfo each with role, dds_topic (raw, e.g. rt/scan), ros_topic (/scan; null with ros_topic_note for a non-ROS topic), type_name, type_id, the owning participant_guid with participant_name, and a structured qos. Use it instead of parsing peek_dds_samples and joining GUID prefixes. Large buses: pass fields compact to replace each qos by a one-line qos_summary and drop type_id, announced_ns and ros_topic_note; the default full is verbose, so scope with topic or participant_guid to read one topic's full QoS. Orphans: by_topic rolls endpoints up per topic with writer_count, reader_count, orphan (no_reader = a writer nobody subscribes to, no_writer = a reader nobody publishes to) and the union of partitions. In qos, a duration or policy of None is infinite/unset or not announced. Among EXCLUSIVE writers the live one with the highest ownership_strength delivers; which one owns an instance is runtime state TopicForge cannot observe. Departed endpoints: a leaving participant's endpoints are remembered (last 200, 1 h) and shown in by_topic as departed_writers / departed_readers (listed in endpoints only with include_departed), so a topic that lost its only writer is explained in one call. Topic filter: rt/scan and scan match each other; a filter matching nothing returns a note with the closest topics. This lists discovery facts, not data flow: it cannot tell a silent or hung writer from a healthy one; pair it with detect_qos_mismatches. The observer's own endpoints are excluded unless include_observer; ROS 2 service and action endpoints (rq/, rr/, rs/, rp/, ra/, ros_discovery_info) unless include_internal. Capped at 500 endpoints (truncated, total); by_topic covers all matches. Read-only. Raises an MCP error when no DDS module is active; mock returns a fixture. Duration: uses the DDS binding behind the DDS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another DDS call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
| topic | No | Only endpoints on this DDS topic: `scan` or `rt/scan` (exact name first, then the alternate form). Omit for every topic. | |
| fields | No | `full` (default) or `compact`: one-line `qos_summary` instead of the full `qos`, no `type_id`, `announced_ns` or `ros_topic_note`. Use `compact` on large buses. | full |
| domain_id | No | Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value. | |
| include_departed | No | Also list endpoints whose participant left (flagged with `gone_ns`); `by_topic` reports them either way. Default false. | |
| include_internal | No | Also list ROS 2 service and action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`), most of a small robot's endpoints. Default false. | |
| include_observer | No | Include TopicForge's own observer endpoints. Default false. | |
| participant_guid | No | Only endpoints of this participant (a guid from `list_participants`). Omit for all. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | Set when a `topic` filter matched nothing: the closest known topics. |
| hints | No | Facts true of every endpoint, stated once (e.g. liveness is not observed). |
| total | Yes | Endpoints in the discovery cache before any filter. |
| fields | No | Detail level served: `compact` drops per-endpoint QoS detail, type id and notes. |
| by_topic | Yes | Roll-up over every matching endpoint. |
| returned | Yes | |
| domain_id | Yes | |
| endpoints | Yes | Matching endpoints, capped (see `truncated`). |
| truncated | Yes | True when matching endpoints exceeded the cap. |
| snapshot_ns | Yes | Wall-clock time of the snapshot, ns since epoch. |
| observer_guid | No | GUID of TopicForge's own participant, `None` in mock. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
| departed_endpoint_count | No | Departed endpoints matching the filters; listed in `endpoints` only with `include_departed`. |
| hidden_internal_endpoint_count | No | Service/action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`) left out while `include_internal` is false. |
| excluded_observer_endpoint_count | No | TopicForge's own endpoints left out of `endpoints` (counted in `total`). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only/idempotent/open-world safety, but the description adds substantial behavior: an MCP error when no DDS module is active, mock fixture fallback, a 45s duration bound under the DDS lock with an immediate `busy` failure, a 500-endpoint cap with `truncated`/`total`, departed-endpoint retention (last 200, 1h), and exclusive-writer ownership semantics.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded purpose followed by clearly delimited bold-labelled sections (**Large buses**, **Orphans**, **Topic filter**, etc.) that make it scannable despite its length. It is dense and occasionally restates schema enum values, so it is slightly over-sized for a list tool, but nearly every sentence carries behavioral payload.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return shape needn't be re-explained, and annotations carry the safety profile; the description still covers error modes, caps, lock contention, orphan/departed semantics, and default exclusions. Nothing an agent needs to call this correctly appears missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents each parameter in detail, so the baseline is 3. The description nonetheless adds cross-parameter meaning: what `compact` drops relative to `full`, that `by_topic` reports departed endpoints regardless of `include_departed`, that `domain_id` is only range-checked, and the `rt/scan` vs `scan` matching rule.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource ('List every DDS endpoint (writer and reader) announced on the bus as an EndpointListing') and enumerates the returned fields, so an agent knows exactly what it gets. It explicitly distinguishes itself from sibling `peek_dds_samples` and names `detect_qos_mismatches` as a complement.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use ('Use it instead of parsing peek_dds_samples and joining GUID prefixes'), when-to-scope (compact fields, topic/participant_guid on large buses), and when-not ('cannot tell a silent or hung writer from a healthy one; pair it with detect_qos_mismatches'). The alternative tool is named with the condition that selects it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_nodesARead-onlyIdempotent
ROS 2 graph only; on a DDS-only setup use list_participants. List the nodes on the ROS 2 graph (or the mock graph) from one ros2 node list call as a NodeListing: name, namespace, full_name, duplicate_count. duplicates lists full names used by several nodes (a per-node tool then answers for one of them, unidentified). Nodes starting with an underscore are hidden and a node on another ROS_DOMAIN_ID is invisible. Empty nodes when the graph has none. Use get_node_info for one node's interfaces. Raises an MCP error without a ros2 CLI. Read-only; cheap (one CLI call). Duration: runs the ros2 CLI behind the ROS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another ros2 call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | One sentence of context, or `None`. |
| nodes | Yes | Nodes on the graph, one entry per distinct full name, sorted by name. |
| total | Yes | Distinct node names on the graph (no cap applies). |
| returned | Yes | Length of `nodes`. |
| truncated | Yes | True when `returned` is less than `total` because of a cap. |
| duplicates | Yes | Full names that appear more than once on the graph. Empty when every name is unique. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, openWorld), and the description adds genuinely new behavior: empty `nodes` on an empty graph, an MCP error without the `ros2` CLI, hidden underscore-prefixed nodes, cross-ROS_DOMAIN_ID invisibility, and the lock semantics (45 s ceiling, immediate `busy` error to retry). These are runtime traits the annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the disambiguating scope clause before any detail, and each sentence carries distinct information (routing, return shape, edge cases, error/duration behavior). It is dense and bold-heavy, but nothing reads as filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present the description need not detail return values, yet it still flags the key semantic of `duplicates` and the empty-graph case. Combined with explicit failure modes (missing CLI, lock contention) and the sibling routing hint, an agent has everything needed to call this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline is 4; there is nothing for the description to clarify. Its explanation of return fields (`duplicates`, `full_name`) goes beyond parameters, which is bonus context rather than parameter semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the nodes on the ROS 2 graph') plus the data source ('one `ros2 node list` call') and the returned shape (`NodeListing` with name, namespace, full_name, duplicate_count). The opening clause immediately distinguishes it from `list_participants` for DDS-only setups, so an agent can route between the two without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use routing: 'ROS 2 graph only; on a DDS-only setup use `list_participants`' and 'Use `get_node_info` for one node's interfaces'. It also names the conditions that change behavior (mock graph, no ros2 CLI, busy lock) so the agent knows when this call will succeed or fail.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_participantsARead-onlyIdempotent
List DDS participants observed on the bus as a ParticipantListing: guid, vendor (read from the GUID prefix, unknown for vendors that do not put theirs there, e.g. Dust DDS and RTI Connext; see vendor_source), optional name (the announced EntityName, e.g. lidar_driver; a participant with none is normal, not a fault: identify it by its topics), hostname, domain_id and is_observer (TopicForge's own read-only participant, listed like any other). Lifecycle: status (active / left), first_seen_ns / last_seen_ns (TopicForge's clock), announced_ns (DDS source timestamp) and, once left, lost_ns + lost_time_source: an upper bound of the death (exact after a clean shutdown; after a crash the lease expiry, so up to one lease earlier: 10 s Cyclone, 20 s Fast DDS, 100 s RTI). Restarts: a restarted node is a new GUID with the same name; restarts_by_name gives lives (GUIDs) and departures per name, so do not count GUIDs by hand (seen_count is not a restart count). Cyclone tracks discovery in the background; right after start the call waits up to 3 s for it. Only the domain joined at startup is observed (health_check dds_domain_id): a participant on another domain is INVISIBLE here, and domain_id does not switch domains (restart with TOPICFORGE_DDS_DOMAIN_ID). Works at the raw DDS layer, so non-ROS participants appear too. Read-only by architecture: it cannot publish, modify QoS or alter the bus. Raises an MCP error when no DDS module is active (install topicforge[dds], set TOPICFORGE_DDS_BACKEND=cyclone); the mock backend returns fixtures. Duration: uses the DDS binding behind the DDS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another DDS call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
| domain_id | No | Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | One sentence of context, or `None`. |
| total | Yes | |
| returned | Yes | |
| domain_id | Yes | DDS domain observed (joined at startup). |
| truncated | Yes | |
| participants | Yes | |
| restarts_note | No | Set when unnamed participants were left out of `restarts_by_name`. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
| restarts_by_name | No | Names seen with several GUIDs or lost at least once (absent: ran once and stayed). A restart is a new GUID with the same name. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes far beyond the annotations by disclosing lock contention (DDS lock, one call at a time, 45 s bound, immediate 'busy' error with retry), lease-expiry error bounds per vendor, restart/GUID semantics, and the MCP error raised when no DDS module is active. It also confirms read-only 'by architecture', reinforcing rather than contradicting the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and return shape are front-loaded, and nearly every sentence carries non-obvious operational detail (restart counting, timing sources, domain limits). It is dense and heavily parenthetical, verging on overload, but the size is largely justified by the tool's subtle semantics.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only discovery tool with an output schema, it supplies everything an agent needs: semantics of each field, timing caveats, failure modes, and domain constraints. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single parameter is fully described in-schema, but the description still adds real meaning: domain_id is ignored, cannot switch domains, and the only way to change domain is restarting with TOPICFORGE_DDS_DOMAIN_ID. That goes beyond the field's range-check description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List DDS participants observed on the bus') and immediately names the return type (ParticipantListing) with its fields. The raw-DDS-layer and 'observed on the bus' scoping distinguishes it from the ROS-oriented siblings like list_nodes and from the event-oriented participant_events.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear operating context: only the startup domain is observed, a non-DDS participant is normal not a fault, and GUIDs should not be counted as restarts (use restarts_by_name). It does not, however, explicitly route the agent between this tool and alternatives such as participant_events or list_endpoints, so it stops short of full alternative selection guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_topicsARead-onlyIdempotent
ROS 2 graph only; on a DDS-only setup use list_endpoints. List the topics on the ROS 2 graph (or the mock graph) as a TopicListing: each has name, message_type, publisher_count and subscriber_count; mode_effective tells a real graph from fixtures. QoS is not listed: use get_topic_info. Empty topics when the graph has none or live discovery times out. Raises an MCP error without a ros2 CLI. Read-only; no side effects. Duration: runs the ros2 CLI behind the ROS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another ros2 call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | One sentence of context, or `None`. |
| total | Yes | |
| topics | Yes | |
| returned | Yes | |
| truncated | Yes | |
| mode_effective | Yes | `live` or `mock` (fixtures). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well past the annotations (readOnly/idempotent/openWorld) by disclosing failure modes: empty `topics` on no topics or discovery timeout, an MCP error when no `ros2` CLI exists, serialization behind a ROS lock, a 45 s ceiling including lock wait, and a fast `busy` error that should be retried. That is exactly the operational context annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the scope and the alternative, and every sentence carries actionable content. It is dense and slightly over-packed with bolded caveats, but no sentence is filler, so it stops short of a 5 only on sheer density.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite an output schema existing, the description still supplies the discriminating context an agent needs: the mock-vs-real signal, the empty-result semantics, the CLI prerequisite, and the locking/retry protocol. Nothing required to select or invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline of 4 applies. The description adds meaning to the *result* fields (`mode_effective`, counts) rather than parameters, which is beyond the schema's scope but correct for the dimension.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — 'List the topics on the ROS 2 graph' — and immediately scopes it against the DDS-only case by naming `list_endpoints`. It even enumerates the returned fields, so an agent knows exactly what it gets and how it differs from `list_nodes` or `get_topic_info`.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit routing: ROS 2 graph only, use `list_endpoints` on DDS-only setups, and use `get_topic_info` because QoS is deliberately not included here. Both the when-to-use and the when-not-to-use are stated with the named alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
participant_eventsARead-onlyIdempotent
Return DDS participant lifecycle events (discovered / lost) from a recent window, e.g. 'who left in the last 5 minutes?', as a ParticipantEventListing, newest first: guid, event_type, vendor, timestamp_ns, time_source, observed_ns, age_s (seconds before the response; live only), optional name (announced name), hostname and domain_id. time_source is dds_source_timestamp (timestamp of the announcement or dispose) or observed_local (when TopicForge noticed, weakest). Crash caveat: a lost timestamp is an upper bound of the death: exact after a clean shutdown, the lease expiry after a crash, so the process died up to one lease earlier (10 s Cyclone, 20 s Fast DDS, 100 s RTI); the two cases cannot be told apart. Restarts: a restarted node is a new participant (one lost and one discovered, different guid, same name); restarts_by_name gives lives and departures per name over the window, so do not count GUIDs by hand. Capped at 200 events (truncated, note; reduce lookback_s). TopicForge only knows what happened since it started watching (health_check.observer_started_ns). Backends: Fast DDS uses listener callbacks; Cyclone polls discovery in the background every 0.5 s, so a participant cycle faster than the reader's history depth between two passes can be missed; mock returns a fixture timeline. Right after start the call waits up to 3 s for discovery. Read-only by architecture. Raises an MCP error when no DDS module is active. Duration: uses the DDS binding behind the DDS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another DDS call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
| domain_id | No | Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value. | |
| lookback_s | No | Window in seconds, 1..86400, default 300. A larger window may hit the 200-event cap. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | One sentence of context, or `None`. |
| total | Yes | |
| events | Yes | |
| returned | Yes | |
| domain_id | Yes | DDS domain observed (joined at startup). |
| truncated | Yes | True when cut to the 200-event cap. |
| restarts_note | No | Set when unnamed participants were left out of `restarts_by_name`. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
| restarts_by_name | No | Names seen with several GUIDs or lost at least once (absent: ran once and stayed). A restart is a new GUID with the same name. Window only. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes far beyond the annotations, which already declare read-only/idempotent: it discloses the crash lease-expiry ambiguity (10/20/100 s), restart semantics, the 200-event cap with `truncated`/`note`, backend-specific polling gaps, a 3 s startup wait, an MCP error when no DDS module is active, and 45 s lock behavior with a `busy` retry. 'Read-only by architecture' is consistent with readOnlyHint=true.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core purpose and every later sentence (crash caveat, restarts, cap, backends, lock) carries operational weight. It is dense and somewhat long, with minor redundancy (enumerating output fields that the output schema already provides, restating read-only), keeping it from a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with an output schema, a 100%-covered input schema, and four annotations, the description covers the remaining risk surface completely: error conditions, lock contention, capping, backend variance, and time-source caveats. An agent can call it correctly without further inquiry.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% so the baseline is 3; the description adds marginal leverage by tying the 200-event cap to reducing `lookback_s` and reiterating that `domain_id` does not switch domains. Most semantics are still carried by the schema, but the description reinforces the operative constraint.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Return DDS participant lifecycle events (`discovered` / `lost`) from a recent window') with an example query that pins the use case. The lifecycle-event framing (vs. current-state listing) intrinsically separates it from siblings like list_participants.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a concrete scenario ('who left in the last 5 minutes?') and steers the agent to `restarts_by_name` rather than hand-counting GUIDs, which is a real alternative-routing hint. However it never explicitly states when to prefer this over sibling tools like list_participants or health_check.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
peek_bag_samplesARead-onlyIdempotent
Peek up to count samples from a recorded bag file as a SampleResult: offline, for post-mortem (peek_dds_samples and sample_messages are live). Formats, by extension: MCAP (.mcap), rosbag2 SQLite (.db3), ROS 1 (.bag). Returns the first count messages of the topic in recording order, not the last. Clocks: timestamp_ns is header.stamp when the message has a top-level header (stamp_source header), else the time in the body of Clock, TFMessage or Log (payload), else the bag record time (recorded); recorded_ns is always the record time. Each payload carries _decode_status (full / partial / raw). count defaults to 5, clamped to 50. Uses the rosbags library and needs the ROS 2 side of the runtime (an error on a DDS-only setup). Mock returns fixtures on canned paths. A bag without embedded definitions (Humble .db3) is decoded with those of its recorded distro, or Humble; note says which. Arrays over 4096 elements are cut and note lists them. Read-only by architecture. Raises an MCP error when the path does not exist, the topic is not in the bag, or rosbags is missing. Summary and rate: LaserScan, Odometry, Imu, Image and PointCloud2 samples carry a summary of the whole message (other types: null); rate gives frequency, interval spread and a verdict measured on recorded_ns (the bag record time of the returned messages). Use count >= 10 (50 above 20 Hz); a multi-publisher topic such as /tf reads erratic without being broken. Duration: takes no lock (in-process file read, so it runs while a ROS 2 or DDS call is busy); the time follows the bag's size and how deep the first samples are.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Bag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed path. | |
| count | No | Messages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower. | |
| topic | Yes | Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | Why `samples` is empty or limited, when not obvious; else `None`. |
| rate | No | How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10. |
| count | Yes | Number of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50). |
| topic | Yes | |
| samples | Yes | The samples, in backend order. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive), yet the description goes far beyond them: clock/timestamp resolution rules, `_decode_status` values, count clamping, `rosbags` dependency and runtime requirement, Humble `.db3` fallback decoding, 4096-element truncation with `note`, mock behavior, MCP-error conditions, and lock-free duration characteristics. This is unusually rich disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and the sibling distinction are front-loaded, and nearly every sentence carries operational detail. It is dense and heavily bolded, and some return-value semantics (summary/rate fields) lean on material the output schema could carry, but there is little actual filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter tool with an output schema, the description covers dependencies, supported formats, error conditions, edge cases (Humble decoding, array truncation) and timing, leaving nothing an agent needs to invoke it correctly or anticipate failure. Return-value explanation is largely redundant with the output schema but harmless.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema lacks: the `count` >= 10 recommendation for meaningful `rate`, and the note that a multi-publisher topic like `/tf` reads `erratic` without being broken. This contextualizes the parameters rather than restating them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource ('Peek up to `count` samples from a recorded bag file') and immediately scopes it ('offline, for post-mortem'), then explicitly distinguishes the two live siblings (`peek_dds_samples`, `sample_messages`). An agent can identify the tool and its unique niche without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly names the alternatives and the condition that selects them ('peek_dds_samples and sample_messages are live'), and the schema further notes `analyze_bag` cannot read ROS 1 `.bag`. It also gives prescriptive usage advice ('Use `count` >= 10 (50 above 20 Hz)') and the environment preconditions (needs the ROS 2 side of the runtime).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
peek_dds_samplesARead-onlyIdempotent
Peek recent samples on a raw DDS topic as a SampleResult, like sample_messages but read from the DDS layer (works without ROS 2). count defaults to 5, clamped to 50. (a) The builtin discovery topics (DCPSParticipant, DCPSSubscription, DCPSPublication) return structured discovery records: on Cyclone the CURRENT state (one per live participant or endpoint), not recent events (use participant_events); DCPSPublication / DCPSSubscription are the raw data behind list_endpoints. The topic may be /scan, scan or rt/scan. (b) User topics: payload decoding is DISABLED on every backend, so the call returns count 0, empty samples and a note; that does NOT mean the topic is silent. Use list_endpoints for its presence, writers, readers and QoS. A user topic not announced on the bus raises an error. Right after start the call waits up to 3 s for discovery. Read-only by architecture: it cannot publish. Raises an MCP error when no DDS module is active or the topic is not announced. Duration: uses the DDS binding behind the DDS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another DDS call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | Messages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower. | |
| topic | Yes | DDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | Why `samples` is empty or limited, when not obvious; else `None`. |
| rate | No | How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10. |
| count | Yes | Number of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50). |
| topic | Yes | |
| samples | Yes | The samples, in backend order. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
This description discloses rich behavioral context that goes far beyond the annotations. It explains the error conditions (no DDS module active, topic not announced), the locking/duration behavior (45 sec max, possible 'busy' error), the 3-second discovery wait, and the fact that user topics return empty samples with a note. All these traits are not derivable from the annotations or schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but well-structured and front-loaded. It uses bold emphasis and parenthetical clauses to separate key concepts (builtin topics, user topics, error conditions). While it is relatively long, every sentence provides crucial information without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity of the tool (interacting with DDS, multiple topic types, locking, error modes) and the presence of both annotations and an output schema, the description is extremely complete. It covers all edge cases an agent needs to know to call the tool correctly and interpret results, leaving no significant gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description adds meaning by explaining that 'count' defaults to 5, is clamped to 50, and that negatives raise an error, and that the result's 'count' may be lower. It also clarifies the acceptable topic name formats (bare, ROS 2 mangled, builtin discovery topics). This extra information enhances the agent's ability to use the parameters correctly beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Peek recent samples on a raw DDS topic') and explicitly differentiates itself from the closest sibling, 'sample_messages', by noting that it reads from the DDS layer (works without ROS 2). It further clarifies the tool's scope by distinguishing builtin discovery topics from user topics.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit when-to-use and when-not-to-use guidance. It directs the agent to use 'participant_events' for recent events on builtin topics and 'list_endpoints' for checking user topic presence and QoS. It also clearly warns that user topics are not decodable, preventing a common misuse (thinking a topic is silent when it is just undecodable).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sample_messagesARead-onlyIdempotent
ROS 2 graph only; on a DDS-only setup use list_endpoints or peek_dds_samples on DCPSPublication / DCPSSubscription. Collect up to count live messages from a ROS 2 topic, waiting at most timeout_s seconds, as a SampleResult. Live mode streams ros2 topic echo with the publishers' QoS (latched topics work) and returns what arrived by the deadline: count is the actual number and note says N of M messages and why it is short (no publisher, silent, slow or too large to print in time; a latched topic usually holds one message, so use count 1). Each sample has payload (nested named values), timestamp_ns (the publisher's clock, sim time on a simulation, 0 for a headerless message such as std_msgs/String; see stamp_source) and received_ns (wall clock when the CLI printed it). Arrays: arrays, strings and bytes are cut at 128 elements by default (a 541-beam LaserScan loses beams 128 and up); cut fields are listed in payload._truncated_fields and note. Raise max_array_length (up to 65536, or null) or set arrays_summary_only. A message over the 1 MiB cap (TOPICFORGE_MAX_SAMPLE_BYTES) is dropped with a note. Mock mode returns demo-robot samples instantly. Raises an MCP error without a ros2 CLI, for an unknown topic, or when the CLI fails. Read-only; never publishes. Summary and rate: LaserScan, Odometry, Imu, Image and PointCloud2 samples carry a summary of the whole message (other types: null); rate gives frequency, interval spread and a verdict measured on received_ns (when the CLI printed each message). Use count >= 10 (50 above 20 Hz); a multi-publisher topic such as /tf reads erratic without being broken. Duration: runs the ros2 CLI behind the ROS lock, one call at a time. It returns within timeout_s (at most 40) plus about 3 s to stop the CLI and decode; the whole call, lock wait included, ends within 45 s: a call that cannot get the lock fails at once with a busy error (retry), and one that waited has less time to collect.
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | Messages to wait for, default 5, at most 50 (more is capped and `note` says so); 0 returns nothing. Fewer arrive when the topic is slow or `timeout_s` runs out. | |
| topic | Yes | Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI. | |
| timeout_s | No | Seconds to wait, 1..40, default 10, covering topic lookup and CLI start-up. The call returns within `timeout_s` plus a few seconds, with what arrived and a `note`. Raise it for a topic slower than 1 Hz. | |
| max_array_length | No | Longest array, string or bytes value returned in full, 1..65536, default 128 (a 541-beam `LaserScan` is cut to 128 ranges; its `summary` still covers all). Longer values are cut and listed in `_truncated_fields`. Null returns everything (large for images and clouds; a message over 1 MiB is dropped with a note). | |
| arrays_summary_only | No | When true, arrays become a type-and-length summary: use it to read the non-array fields of large messages. Default false. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | Why `samples` is empty or limited, when not obvious; else `None`. |
| rate | No | How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10. |
| count | Yes | Number of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50). |
| topic | Yes | |
| samples | Yes | The samples, in backend order. |
| mode_effective | Yes | `live` or `mock` (fixtures). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnly, idempotent, non-destructive), but the description adds substantial behavior beyond them: MCP error conditions, the 128-element array cut and 1 MiB drop cap, lock serialization with a `busy` retry error, and the 45 s hard deadline. These are exactly the traits an agent needs and none are in the structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The routing constraint and core purpose are front-loaded, and bold section markers (Live mode, Arrays, Duration) aid scanning. It is dense and long, with some redundancy between the prose and schema (e.g. the `_truncated_fields` explanation appears in both), but nearly every sentence carries operational weight for a complex tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a five-parameter tool with dual modes, lock contention, and multiple failure paths, the description covers timing, error conditions, truncation, and rate measurement. An output schema exists, yet the extra detail on `SampleResult` fields and `note` values makes failure diagnosis possible without the caller guessing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real semantics: what `count` vs `note` mean, how `max_array_length` interacts with `_truncated_fields` and the byte cap, and what `arrays_summary_only` is for. It goes beyond the schema descriptions rather than repeating them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Collect up to `count` live messages from a ROS 2 `topic`') and immediately scopes it against alternatives by naming `list_endpoints` and `peek_dds_samples` for DDS-only setups. An agent can distinguish this from siblings without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to use this tool (ROS 2 graph) versus when not (DDS-only setups, routed to specific siblings), separates live versus mock mode, and gives operational rules like `count` >= 10 for rate and raising `timeout_s` for sub-1 Hz topics. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
topic_metricsARead-onlyIdempotent
Return temporal metrics (frequency, sequence gaps, latency percentiles) for a DDS topic over a recent window as a TopicMetrics. Read status first: unsupported_user_topic means the payload is not decoded, so no metric exists: every null or 0 is not a measurement; no_samples_yet is a builtin topic with nothing buffered; ok means computed. Limits: the buffer is filled only when peek_dds_samples runs on the topic, so observed_frequency_hz reflects how often it was called, not the publish rate: a coarse presence signal. declared_frequency_hz is 1 / deadline of the shortest writer Deadline announced in discovery (declared, not measured; null when none). Also sequence_gaps_count, latency_ns_p50/p95/p99 and availability flags. Read-only by architecture. Raises an MCP error when no DDS module is active or window_s is outside 1..3600. Right after start the call waits up to 3 s for discovery. Duration: uses the DDS binding behind the DDS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another DDS call holds the lock it fails at once with a busy error (retry).
| Name | Required | Description | Default |
|---|---|---|---|
| topic | Yes | DDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only. | |
| window_s | No | Window in seconds, 1..3600, default 60. | |
| domain_id | No | Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | One sentence of context, or `None`. |
| topic | Yes | |
| status | No | `ok`; `no_samples_yet` (empty buffer); `unsupported_user_topic` (payload not decoded: null fields are not measurements). |
| window_s | Yes | Requested window in seconds (echoed). |
| latency_ns_p50 | No | Median publish-to-receive latency in ns; `None` unless `latency_available`. |
| latency_ns_p95 | No | 95th-percentile publish-to-receive latency (ns). |
| latency_ns_p99 | No | 99th-percentile publish-to-receive latency (ns). |
| mode_effective | Yes | `live` or `mock` (fixtures). |
| window_actual_s | Yes | Seconds actually covered; shorter than `window_s` after a recent start; `0.0` without samples. |
| samples_observed | Yes | Samples buffered in the window. `0` does NOT mean no publisher, only that no `peek_dds_samples` call captured one. |
| latency_available | No | True when a sample carried both a publish and a receive timestamp. |
| sequence_gaps_count | No | Missing sequence numbers in the buffered samples; `0` also when none exist (see `sequence_numbers_available`). |
| declared_frequency_hz | No | `1 / deadline` of the shortest writer Deadline announced: the promised rate, not the observed one; `None` when none announced. |
| observed_frequency_hz | No | `samples_observed / window_actual_s`; `None` under 2 samples. |
| sequence_numbers_available | No | True when a sequence number could be read from a sample. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only/idempotent/non-destructive, and the description goes well beyond them: failure modes (MCP error when no DDS module is active or window_s is out of 1..3600), a 3 s discovery wait right after startup, a 45 s overall bound under the DDS lock, and immediate `busy` errors with a retry instruction when another DDS call holds the lock. Semantics of observed vs declared frequency and the caveat that null/0 under non-`ok` statuses are not measurements are disclosed too.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but deliberately structured with bolded section labels (status, Limits, Duration) and the core purpose front-loaded in the first sentence. It is longer than most descriptions and repeats a couple of schema-stated constraints, but nearly every sentence carries operational information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers everything an agent needs to call it correctly despite the complexity: status gating, measurement vs declared semantics, error and lock-timing behavior, and read-only architecture. Since an output schema exists, the description is not obligated to detail return fields and wisely spends its words on semantics and failure modes instead.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the topic format, window_s range/default, and the fact that domain_id is ignored (range-checked only) are already documented in the schema. The description largely restates those constraints rather than adding syntax, format, or default information beyond them, making the baseline 3 appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Return temporal metrics ... for a DDS topic over a recent window as a TopicMetrics') and enumerates what those metrics are (frequency, sequence gaps, latency percentiles). This clearly differentiates it from siblings like get_topic_info (metadata) and peek_dds_samples (raw samples), which the description also names as the buffer producer.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives actionable usage guidance: 'Read `status` first' with an interpretation of each status value, and explains the dependency on peek_dds_samples having run ('the buffer is filled only when peek_dds_samples runs'). It clarifies the coarse nature of observed_frequency_hz so the agent knows when not to trust it, though it never states a scenario in which a sibling should be preferred instead.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
14 tool updates
v0.7.1- Changed
analyze_bag42 fields changed- changed
Input schema / properties / path / descriptionPrevious value: -"Path to a bag: a file ending in `.mcap` or `.db3`, or a `rosbag2_*` directory. `peek_bag_samples` also reads ROS 1 `.bag` files; `analyze_bag` does not (`ros2 bag info` cannot open them). Leading/trailing whitespace is stripped. Null bytes and otherwise malformed filesystem paths are rejected. Existence and bag format are validated by the live adapter (mock mode accepts any well-formed path)."New value: +"Bag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed path." - removed
Input schema / properties / path / titleRemoved value: -"Path" - removed
Input schema / titleRemoved value: -"analyze_bagArguments" - removed
Output schema / $defs / BagTopicStats / descriptionRemoved value: -"Per-topic statistics inside a bag analysis result." - changed
Output schema / $defs / BagTopicStats / properties / first_timestamp_ns / descriptionPrevious value: -"Recorded time of the first message on this topic, in nanoseconds (bag receive time, normally epoch). `null` when the bag was summarized from `ros2 bag info` text only, or the topic has no messages."New value: +"Recorded ns of the first message; `null` for text-only summaries or no messages." - removed
Output schema / $defs / BagTopicStats / properties / first_timestamp_ns / titleRemoved value: -"First Timestamp Ns" - changed
Output schema / $defs / BagTopicStats / properties / frequency_basis / descriptionPrevious value: -"How `frequency_hz` was computed: `topic_span` (per-topic first and last message times, the accurate one) or `bag_duration` (count divided by whole-bag duration, used when per-topic times could not be read). `null` when there is no rate."New value: +"`topic_span` (accurate) or `bag_duration` (count over the whole bag, understates late topics); `null` without a rate." - removed
Output schema / $defs / BagTopicStats / properties / frequency_basis / titleRemoved value: -"Frequency Basis" - changed
Output schema / $defs / BagTopicStats / properties / frequency_hz / descriptionPrevious value: -"Average publish rate in Hz. With `frequency_basis` `topic_span` it is `(message_count - 1) / (last - first message time)` for this topic; with `bag_duration` it is `message_count / bag duration`, which understates the rate of a topic that started late or stopped early. `null` when fewer than 2 messages, a zero time span, or the topic is `latched` and all its messages fall within 1 second (a start-up burst, not a rate). A latched topic published over a longer span keeps its rate. Not a periodic rate for event-driven topics such as `/parameter_events`."New value: +"Average Hz: (count - 1) over the topic's own time span (see `frequency_basis`); `null` under 2 messages, a zero span, or a latched start-up burst (all within 1 s)." - removed
Output schema / $defs / BagTopicStats / properties / frequency_hz / titleRemoved value: -"Frequency Hz" - added
Output schema / $defs / BagTopicStats / properties / kindAdded value: +{ + "default": "user", + "description": "`user` (data, incl. `/tf` and `/clock`), `rosbag2_internal` or `ros_builtin` (e.g. `/rosout`).", + "enum": [ + "user", + "rosbag2_internal", + "ros_builtin" + ], + "type": "string" +} - changed
Output schema / $defs / BagTopicStats / properties / last_timestamp_ns / descriptionPrevious value: -"Recorded time of the last message on this topic; same rules as `first_timestamp_ns`."New value: +"Recorded ns of the last message; same rules." - removed
Output schema / $defs / BagTopicStats / properties / last_timestamp_ns / titleRemoved value: -"Last Timestamp Ns" - changed
Output schema / $defs / BagTopicStats / properties / latched / descriptionPrevious value: -"True when a publisher recorded this topic with transient_local durability (late joiners get the last samples, e.g. `/tf_static`, `/rosout`). Such a topic often publishes a start-up burst; when all its messages fall within 1 second `frequency_hz` is `null`. `false` when every recorded publisher was volatile; `null` when the bag carries no QoS information (ROS 1 bags report their latching flag)."New value: +"True if recorded with transient_local durability, false if volatile, `null` without QoS information." - removed
Output schema / $defs / BagTopicStats / properties / latched / titleRemoved value: -"Latched" - removed
Output schema / $defs / BagTopicStats / properties / message_count / titleRemoved value: -"Message Count" - removed
Output schema / $defs / BagTopicStats / properties / message_type / descriptionRemoved value: -"ROS2 message type recorded for this topic." - removed
Output schema / $defs / BagTopicStats / properties / message_type / titleRemoved value: -"Message Type" - removed
Output schema / $defs / BagTopicStats / properties / name / descriptionRemoved value: -"Fully qualified topic name as recorded in the bag." - removed
Output schema / $defs / BagTopicStats / properties / name / titleRemoved value: -"Name" - removed
Output schema / $defs / BagTopicStats / titleRemoved value: -"BagTopicStats" - removed
Output schema / $defs / ParticipantInfoRemoved value: -{ - "additionalProperties": false, - "description": "DDS participant discovered on the configured domain.\n\nCarries identity (`guid`, `name`, `vendor`) plus lifecycle fields\n(`first_seen_ns`, `last_seen_ns`, `status`, `seen_count`).", - "properties": { - "announced_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Nanoseconds since epoch (DDS source timestamp of the participant's most recent announcement, taken from the announcing side's clock; the local clock when the announcer sent none). `None` when the backend does not expose it. Distinct from `first_seen_ns` / `last_seen_ns`, which are TopicForge's own local clock.", - "title": "Announced Ns" - }, - "domain_id": { - "description": "DDS domain id the participant is bound to.", - "maximum": 232, - "minimum": 0, - "title": "Domain Id", - "type": "integer" - }, - "first_seen_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Wall-clock timestamp (nanoseconds since epoch) of the **first** discovery sample TopicForge observed for this participant. `None` when the adapter does not track lifecycle.", - "title": "First Seen Ns" - }, - "guid": { - "description": "DDS Global Unique Identifier of the participant: hex string, stable across discovery events within a single deployment.", - "title": "Guid", - "type": "string" - }, - "hostname": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Hostname announced in discovery (Cyclone `__Hostname` property). `None` when the vendor does not announce it.", - "title": "Hostname" - }, - "is_observer": { - "default": false, - "description": "True for TopicForge's own read-only observer participant, which joins the domain to watch it and appears in this list; false for every other participant.", - "title": "Is Observer", - "type": "boolean" - }, - "last_seen_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Wall-clock timestamp (nanoseconds since epoch) of the **most recent** discovery sample observed. Used by `participant_events` to distinguish currently-active participants from those that left the bus. `None` when the adapter does not track lifecycle.", - "title": "Last Seen Ns" - }, - "lost_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Nanoseconds since epoch of the moment the participant left, once `status` is `left`; `None` while it is active. How precise it is depends on `lost_time_source`. It is an upper bound of when the participant died: after a clean shutdown it is the exact leave time, after a crash it is when the lease expired, and the two cannot be told apart. The process died at or before `lost_ns`, and at most one lease earlier (10 s Cyclone default, 20 s Fast, 100 s RTI).", - "title": "Lost Ns" - }, - "lost_time_source": { - "anyOf": [ - { - "enum": [ - "dds_source_timestamp", - "observed_local" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Where `lost_ns` comes from: `dds_source_timestamp` (timestamp carried by the discovery dispose) or `observed_local` (the moment TopicForge noticed, the weakest). `None` while active.", - "title": "Lost Time Source" - }, - "mode_effective": { - "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.", - "enum": [ - "mock", - "live" - ], - "title": "Mode Effective", - "type": "string" - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Participant name the application announced through the standard DDS EntityName QoS (PID_ENTITY_NAME in RTPS discovery), for example `lidar_driver`. `None` when the application did not set one (Dust DDS cannot) or the backend does not expose it.", - "title": "Name" - }, - "seen_count": { - "default": 1, - "description": "Number of distinct discovery samples observed for this participant across all calls to `list_participants` during this server's lifetime. Starts at 1 and increments on each observation.", - "minimum": 1, - "title": "Seen Count", - "type": "integer" - }, - "status": { - "default": "unknown", - "description": "Lifecycle status. `active` means TopicForge has a recent discovery sample for this GUID and the bus has not signalled removal. `left` means the participant was observed earlier but has since disappeared (a Fast DDS `REMOVED` callback or a Cyclone polling delta). `unknown` when the adapter does not track lifecycle.", - "enum": [ - "active", - "left", - "unknown" - ], - "title": "Status", - "type": "string" - }, - "vendor": { - "description": "DDS implementation that announced this participant, decoded from the OMG-RTPS `vendor_id` field on the discovery sample. `cyclone` (Eclipse Cyclone DDS), `fast` (eProsima Fast DDS), `rti` (RTI Connext), `rti_micro` (RTI Connext Micro), `opensplice` (ADLink OpenSplice), `opendds` (OCI OpenDDS), `coredx` (Twin Oaks CoreDX), `intercom` (Kongsberg InterCOM), `dust` (S2E Dust DDS). `mock` is reserved for synthetic fixtures. `unknown` means the vendor could not be determined: TopicForge reads it from the vendor prefix of the participant GUID, and some vendors (for example Dust DDS and RTI Connext) do not put their vendor id there, while the Cyclone Python binding does not expose the RTPS header vendor id. The participant is still reported; see `vendor_source`. Any conformant DDS-RTPS participant should appear through standard discovery; Cyclone DDS and Dust DDS have been observed on a live bus. See `docs/dds-interop-matrix.md`.", - "enum": [ - "cyclone", - "fast", - "rti", - "rti_micro", - "opensplice", - "opendds", - "coredx", - "intercom", - "dust", - "mock", - "unknown" - ], - "title": "Vendor", - "type": "string" - }, - "vendor_source": { - "default": "none", - "description": "Where `vendor` came from: `guid_prefix` when it was read from the vendor prefix of the participant GUID, `none` when the vendor is unknown (or synthetic in mock mode).", - "enum": [ - "guid_prefix", - "none" - ], - "title": "Vendor Source", - "type": "string" - } - }, - "required": [ - "guid", - "vendor", - "domain_id", - "mode_effective" - ], - "title": "ParticipantInfo", - "type": "object" -} - changed
Output schema / descriptionPrevious value: -"Structured summary of a ROS2 bag.\n\nThe fields `bag_format`, `samples_decoded_count`,\n`recording_duration_ns` and `participants_recorded` are populated only\nwhen the `rosbags`-backed reader runs; the `ros2 bag info` path leaves\nthem at their defaults."New value: +"Structured summary of a ROS2 bag." - changed
Output schema / properties / anomalies / descriptionPrevious value: -"Human-readable notes about gaps, clock jumps, or other oddities. Populated in mock mode only; live mode does not detect anomalies."New value: +"Notes on gaps or clock jumps; mock mode only." - removed
Output schema / properties / anomalies / titleRemoved value: -"Anomalies" - changed
Output schema / properties / bag_format / descriptionPrevious value: -"Concrete bag container format detected by the reader: `mcap` (Foxglove MCAP), `db3` (ROS2 rosbag2 SQLite), `bag` (ROS1 legacy chunked), or `unknown` when the reader could not classify. `None` when the bag was summarized from `ros2 bag info` text, which carries no format information."New value: +"Detected container: `mcap`, `db3`, `bag` or `unknown`; `None` for `ros2 bag info` text." - removed
Output schema / properties / bag_format / titleRemoved value: -"Bag Format" - added
Output schema / properties / duration_sAdded value: +{ + "description": "Bag duration in seconds.", + "minimum": 0, + "type": "number" +} - removed
Output schema / properties / duration_secondsRemoved value: -{ - "description": "Total bag duration, in seconds (wall clock between first and last message).", - "minimum": 0, - "title": "Duration Seconds", - "type": "number" -} - removed
Output schema / properties / message_count / titleRemoved value: -"Message Count" - changed
Output schema / properties / mode_effective / descriptionPrevious value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)." - removed
Output schema / properties / mode_effective / titleRemoved value: -"Mode Effective" - changed
Output schema / properties / note / descriptionPrevious value: -"Why the result is less detailed than usual, for example per-topic rates computed over the whole bag duration because the bag was too large to read per-topic message times. `None` when there is nothing to add."New value: +"Why the result is less detailed than usual (e.g. rates over the whole bag duration); else `None`." - removed
Output schema / properties / note / titleRemoved value: -"Note" - removed
Output schema / properties / participants_recordedRemoved value: -{ - "description": "DDS participants recorded in the bag when the container format embeds participant metadata. MCAP can carry it via channel metadata records; ROS2 `.db3` and ROS1 `.bag` generally do not. Empty list when not available, which is the common case.", - "items": { - "$ref": "#/$defs/ParticipantInfo" - }, - "title": "Participants Recorded", - "type": "array" -} - changed
Output schema / properties / path / descriptionPrevious value: -"Path to the analyzed bag, as supplied by the caller. May point to a file (`.mcap`, `.db3`, or ROS 1 `.bag` for `peek_bag_samples`) or to a `rosbag2_*` directory."New value: +"Bag path as supplied." - removed
Output schema / properties / path / titleRemoved value: -"Path" - removed
Output schema / properties / recording_duration_nsRemoved value: -{ - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Recording duration in nanoseconds when readable from the bag's index. `None` when only `ros2 bag info` text was parsed; `duration_seconds` (float) is the always-populated fallback that downstream LLM consumers should prefer when this is `None`.", - "title": "Recording Duration Ns" -} - removed
Output schema / properties / samples_decoded_countRemoved value: -{ - "default": 0, - "description": "Total decoded sample count across all topics produced by the bag reader. `0` when the reader only parsed metadata or when `rosbags` is not installed on the host. Use `peek_bag_samples` to pull the actual sample payloads for a specific topic.", - "minimum": 0, - "title": "Samples Decoded Count", - "type": "integer" -} - removed
Output schema / properties / storage_format / titleRemoved value: -"Storage Format" - removed
Output schema / properties / topics / titleRemoved value: -"Topics" - changed
Output schema / requiredPrevious value: -[ - "path", - "duration_seconds", - "message_count", - "topics", - "mode_effective" -]New value: +[ + "path", + "duration_s", + "message_count", + "topics", + "mode_effective" +]
- Changed
detect_qos_mismatches110 fields changed- changed
Input schema / properties / topic / descriptionPrevious value: -"Optional DDS topic name to scope the scan to: a bare name such as `scan` or a ROS 2 mangled name such as `rt/scan`. Omit to scan all topics."New value: +"Optional DDS topic to scope the scan to: `scan` or `rt/scan`. Omit to scan all topics." - removed
Input schema / properties / topic / titleRemoved value: -"Topic" - removed
Input schema / titleRemoved value: -"detect_qos_mismatchesArguments" - removed
Output schema / $defs / MatchedPair / descriptionRemoved value: -"A reader and a writer that DDS will connect given their announced QoS." - changed
Output schema / $defs / MatchedPair / properties / late_joiner / descriptionPrevious value: -"True when the writer is VOLATILE and the reader was announced more than 1 s after it, both on the same host: samples published before the reader joined are not delivered to it. Normal for a VOLATILE writer, not a fault."New value: +"True for a VOLATILE writer whose reader joined over 1 s later on the same host: earlier samples are not delivered. Normal." - removed
Output schema / $defs / MatchedPair / properties / late_joiner / titleRemoved value: -"Late Joiner" - removed
Output schema / $defs / MatchedPair / properties / late_joiner_note / titleRemoved value: -"Late Joiner Note" - removed
Output schema / $defs / MatchedPair / properties / reader_guid / descriptionRemoved value: -"GUID of the reader endpoint." - removed
Output schema / $defs / MatchedPair / properties / reader_guid / titleRemoved value: -"Reader Guid" - removed
Output schema / $defs / MatchedPair / properties / reader_participant_guid / descriptionRemoved value: -"GUID of the reader's participant." - removed
Output schema / $defs / MatchedPair / properties / reader_participant_guid / titleRemoved value: -"Reader Participant Guid" - removed
Output schema / $defs / MatchedPair / properties / reader_participant_name / descriptionRemoved value: -"Announced name of the reader's participant." - removed
Output schema / $defs / MatchedPair / properties / reader_participant_name / titleRemoved value: -"Reader Participant Name" - removed
Output schema / $defs / MatchedPair / properties / topic / descriptionRemoved value: -"Topic name both endpoints use." - removed
Output schema / $defs / MatchedPair / properties / topic / titleRemoved value: -"Topic" - removed
Output schema / $defs / MatchedPair / properties / type_name / descriptionRemoved value: -"Type name both announced." - removed
Output schema / $defs / MatchedPair / properties / type_name / titleRemoved value: -"Type Name" - removed
Output schema / $defs / MatchedPair / properties / writer_guid / descriptionRemoved value: -"GUID of the writer endpoint." - removed
Output schema / $defs / MatchedPair / properties / writer_guid / titleRemoved value: -"Writer Guid" - removed
Output schema / $defs / MatchedPair / properties / writer_participant_guid / descriptionRemoved value: -"GUID of the writer's participant." - removed
Output schema / $defs / MatchedPair / properties / writer_participant_guid / titleRemoved value: -"Writer Participant Guid" - removed
Output schema / $defs / MatchedPair / properties / writer_participant_name / descriptionRemoved value: -"Announced name of the writer's participant." - removed
Output schema / $defs / MatchedPair / properties / writer_participant_name / titleRemoved value: -"Writer Participant Name" - removed
Output schema / $defs / MatchedPair / titleRemoved value: -"MatchedPair" - removed
Output schema / $defs / MismatchReport / descriptionRemoved value: -"A single reader/writer QoS incompatibility detected on a topic.\n\nOnly pairs that share a partition and a type name are reported here: those\nseparated by partition or type are `NotMatchedPair`s in the same scan." - changed
Output schema / $defs / MismatchReport / properties / details / descriptionPrevious value: -"Requested and offered value and the failed rule, one entry per policy."New value: +"Requested and offered value and the failed rule, per policy." - removed
Output schema / $defs / MismatchReport / properties / details / titleRemoved value: -"Details" - changed
Output schema / $defs / MismatchReport / properties / incompatible_policies / descriptionPrevious value: -"Names of the QoS policies that block communication or risk degradation: see `details` for the compared values."New value: +"Blocking or risky policies; values in `details`." - removed
Output schema / $defs / MismatchReport / properties / incompatible_policies / titleRemoved value: -"Incompatible Policies" - removed
Output schema / $defs / MismatchReport / properties / mode_effectiveRemoved value: -{ - "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.", - "enum": [ - "mock", - "live" - ], - "title": "Mode Effective", - "type": "string" -} - removed
Output schema / $defs / MismatchReport / properties / reader_guid / descriptionRemoved value: -"GUID of the reader endpoint involved in the mismatch, if known." - removed
Output schema / $defs / MismatchReport / properties / reader_guid / titleRemoved value: -"Reader Guid" - removed
Output schema / $defs / MismatchReport / properties / reader_participant_guid / descriptionRemoved value: -"GUID of the participant that owns the reader, if known." - removed
Output schema / $defs / MismatchReport / properties / reader_participant_guid / titleRemoved value: -"Reader Participant Guid" - removed
Output schema / $defs / MismatchReport / properties / reader_participant_name / descriptionRemoved value: -"Announced name of the reader's participant, if it set one." - removed
Output schema / $defs / MismatchReport / properties / reader_participant_name / titleRemoved value: -"Reader Participant Name" - removed
Output schema / $defs / MismatchReport / properties / reader_type_name / descriptionRemoved value: -"Type name the reader announced." - removed
Output schema / $defs / MismatchReport / properties / reader_type_name / titleRemoved value: -"Reader Type Name" - changed
Output schema / $defs / MismatchReport / properties / severity / descriptionPrevious value: -"`incompatible` means communication is definitely blocked; `risky` means it may degrade but is not strictly blocked by the DDS spec (History only). Useful for an LLM to triage user-facing advice."New value: +"`incompatible` (blocked) or `risky` (may degrade; History only)." - removed
Output schema / $defs / MismatchReport / properties / severity / titleRemoved value: -"Severity" - added
Output schema / $defs / MismatchReport / properties / suggested_fixesAdded value: +{ + "description": "Fixes as text for a human to apply, never applied; empty when none.", + "items": { + "$ref": "#/$defs/SuggestedFix" + }, + "type": "array" +} - removed
Output schema / $defs / MismatchReport / properties / topic / descriptionRemoved value: -"Topic name where the mismatch was detected." - removed
Output schema / $defs / MismatchReport / properties / topic / titleRemoved value: -"Topic" - changed
Output schema / $defs / MismatchReport / properties / unchecked / descriptionPrevious value: -"Policies that could not be compared for this pair because one side did not announce a value."New value: +"Policies not compared because one side announced no value." - removed
Output schema / $defs / MismatchReport / properties / unchecked / titleRemoved value: -"Unchecked" - removed
Output schema / $defs / MismatchReport / properties / writer_guid / descriptionRemoved value: -"GUID of the writer endpoint involved in the mismatch, if known." - removed
Output schema / $defs / MismatchReport / properties / writer_guid / titleRemoved value: -"Writer Guid" - removed
Output schema / $defs / MismatchReport / properties / writer_participant_guid / descriptionRemoved value: -"GUID of the participant that owns the writer, if known." - removed
Output schema / $defs / MismatchReport / properties / writer_participant_guid / titleRemoved value: -"Writer Participant Guid" - removed
Output schema / $defs / MismatchReport / properties / writer_participant_name / descriptionRemoved value: -"Announced name of the writer's participant, if it set one." - removed
Output schema / $defs / MismatchReport / properties / writer_participant_name / titleRemoved value: -"Writer Participant Name" - removed
Output schema / $defs / MismatchReport / properties / writer_type_name / descriptionRemoved value: -"Type name the writer announced." - removed
Output schema / $defs / MismatchReport / properties / writer_type_name / titleRemoved value: -"Writer Type Name" - changed
Output schema / $defs / MismatchReport / requiredPrevious value: -[ - "topic", - "incompatible_policies", - "severity", - "mode_effective" -]New value: +[ + "topic", + "incompatible_policies", + "severity" +] - removed
Output schema / $defs / MismatchReport / titleRemoved value: -"MismatchReport" - removed
Output schema / $defs / NotMatchedPair / descriptionRemoved value: -"A reader and a writer on one topic that DDS will not match, whatever their QoS." - removed
Output schema / $defs / NotMatchedPair / properties / detail / titleRemoved value: -"Detail" - changed
Output schema / $defs / NotMatchedPair / properties / latent_incompatible_policies / descriptionPrevious value: -"RxO policies that would ALSO be incompatible once the partition / type issue is fixed (same shape as `MismatchReport.details`). Empty when the QoS of the two endpoints would be compatible, or could not be compared."New value: +"RxO policies that would ALSO be incompatible once the partition or type is fixed (shape of `MismatchReport.details`)." - removed
Output schema / $defs / NotMatchedPair / properties / latent_incompatible_policies / titleRemoved value: -"Latent Incompatible Policies" - removed
Output schema / $defs / NotMatchedPair / properties / reader_guid / descriptionRemoved value: -"GUID of the reader endpoint." - removed
Output schema / $defs / NotMatchedPair / properties / reader_guid / titleRemoved value: -"Reader Guid" - removed
Output schema / $defs / NotMatchedPair / properties / reader_participant_guid / descriptionRemoved value: -"GUID of the reader's participant." - removed
Output schema / $defs / NotMatchedPair / properties / reader_participant_guid / titleRemoved value: -"Reader Participant Guid" - removed
Output schema / $defs / NotMatchedPair / properties / reader_participant_name / descriptionRemoved value: -"Announced name of the reader's participant." - removed
Output schema / $defs / NotMatchedPair / properties / reader_participant_name / titleRemoved value: -"Reader Participant Name" - changed
Output schema / $defs / NotMatchedPair / properties / reason / descriptionPrevious value: -"`partition`: no reader partition matches a writer partition. `type_name`: the endpoints announced different type names. The RxO QoS rules are not evaluated for such a pair."New value: +"`partition` (none in common) or `type_name` (different types); QoS rules are not evaluated." - removed
Output schema / $defs / NotMatchedPair / properties / reason / titleRemoved value: -"Reason" - added
Output schema / $defs / NotMatchedPair / properties / suggested_fixesAdded value: +{ + "description": "Fixes proposed as text for a human to apply; TopicForge never applies them. Only for a `partition` split, and only on the first pair of identical splits on a topic. Empty when it proposes none for this finding.", + "items": { + "$ref": "#/$defs/SuggestedFix" + }, + "type": "array" +} - removed
Output schema / $defs / NotMatchedPair / properties / topic / descriptionRemoved value: -"Topic name both endpoints use." - removed
Output schema / $defs / NotMatchedPair / properties / topic / titleRemoved value: -"Topic" - removed
Output schema / $defs / NotMatchedPair / properties / writer_guid / descriptionRemoved value: -"GUID of the writer endpoint." - removed
Output schema / $defs / NotMatchedPair / properties / writer_guid / titleRemoved value: -"Writer Guid" - removed
Output schema / $defs / NotMatchedPair / properties / writer_participant_guid / descriptionRemoved value: -"GUID of the writer's participant." - removed
Output schema / $defs / NotMatchedPair / properties / writer_participant_guid / titleRemoved value: -"Writer Participant Guid" - removed
Output schema / $defs / NotMatchedPair / properties / writer_participant_name / descriptionRemoved value: -"Announced name of the writer's participant." - removed
Output schema / $defs / NotMatchedPair / properties / writer_participant_name / titleRemoved value: -"Writer Participant Name" - removed
Output schema / $defs / NotMatchedPair / titleRemoved value: -"NotMatchedPair" - removed
Output schema / $defs / PolicyMismatch / descriptionRemoved value: -"One QoS policy of a reader/writer pair, with the values that were compared." - removed
Output schema / $defs / PolicyMismatch / properties / offered / descriptionRemoved value: -"What the writer offers, same format as `requested`." - removed
Output schema / $defs / PolicyMismatch / properties / offered / titleRemoved value: -"Offered" - changed
Output schema / $defs / PolicyMismatch / properties / policy / descriptionPrevious value: -"Policy name: `Reliability`, `Durability`, `Deadline`, `Liveliness`, `LatencyBudget`, `Ownership`, `DestinationOrder`, `DataRepresentation` or `History`."New value: +"Policy name, e.g. `Reliability`, `Deadline`, `Ownership`, `History`." - removed
Output schema / $defs / PolicyMismatch / properties / policy / titleRemoved value: -"Policy" - changed
Output schema / $defs / PolicyMismatch / properties / requested / descriptionPrevious value: -"What the reader requests, human-readable: `\"RELIABLE\"`, `\"100 ms\"`, `\"infinite\"`."New value: +"What the reader requests, e.g. `\"RELIABLE\"`, `\"100 ms\"`." - removed
Output schema / $defs / PolicyMismatch / properties / requested / titleRemoved value: -"Requested" - removed
Output schema / $defs / PolicyMismatch / properties / rule / titleRemoved value: -"Rule" - removed
Output schema / $defs / PolicyMismatch / titleRemoved value: -"PolicyMismatch" - added
Output schema / $defs / SuggestedFixAdded value: +{ + "additionalProperties": false, + "properties": { + "kind": { + "description": "Form of `text`: a CLI command, a ROS 2 QoS override, a DDS profile or a launch parameter.", + "enum": [ + "cli", + "qos_override", + "dds_profile", + "launch_param" + ], + "type": "string" + }, + "target": { + "description": "Which node or side to change, writer or reader.", + "type": "string" + }, + "text": { + "description": "The exact command, YAML or XML to apply.", + "type": "string" + }, + "tradeoff": { + "description": "What is lost by applying it.", + "type": "string" + }, + "verify_with": { + "description": "The TopicForge tool to run again to confirm the repair.", + "type": "string" + } + }, + "required": [ + "kind", + "target", + "text", + "tradeoff", + "verify_with" + ], + "type": "object" +} - changed
Output schema / properties / hints / descriptionPrevious value: -"Leads that are not findings: orphan topics with near-identical names (typos), type id differences, pairs that could not be fully checked."New value: +"Leads that are not findings: probable typos, type id notes, incomplete checks." - removed
Output schema / properties / hints / titleRemoved value: -"Hints" - changed
Output schema / properties / matched / descriptionPrevious value: -"Pairs that will be matched by DDS given the announced QoS: same topic and type name, overlapping partitions, no incompatible RxO policy (a pair with only a `risky` History finding still counts). Actual data flow is not observed."New value: +"Pairs DDS will connect given the announced QoS (data flow is not observed)." - removed
Output schema / properties / matched / titleRemoved value: -"Matched" - removed
Output schema / properties / matched_total / titleRemoved value: -"Matched Total" - changed
Output schema / properties / mode_effective / descriptionPrevious value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)." - removed
Output schema / properties / mode_effective / titleRemoved value: -"Mode Effective" - changed
Output schema / properties / not_matched / descriptionPrevious value: -"Pairs separated by partition or type name. No data flows between them. An empty `reports` with a non-empty `not_matched` does not mean the bus is healthy."New value: +"Pairs DDS never matches (partition or type name). Non-empty with empty `reports` is NOT all-clear: no data flows between them." - removed
Output schema / properties / not_matched / titleRemoved value: -"Not Matched" - removed
Output schema / properties / not_matched_total / titleRemoved value: -"Not Matched Total" - added
Output schema / properties / noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence of context, or `None`." +} - changed
Output schema / properties / pairs_checked / descriptionPrevious value: -"Same-topic (reader, writer) pairs examined, including those reported in `not_matched`."New value: +"Same-topic (reader, writer) pairs examined." - removed
Output schema / properties / pairs_checked / titleRemoved value: -"Pairs Checked" - removed
Output schema / properties / policies_checked / titleRemoved value: -"Policies Checked" - changed
Output schema / properties / policies_unchecked / descriptionPrevious value: -"Policies and facts this scan does not cover, each with a one-line reason. A clean result says nothing about them."New value: +"What the scan does not cover, each with a reason; a clean result says nothing about them." - removed
Output schema / properties / policies_unchecked / titleRemoved value: -"Policies Unchecked" - changed
Output schema / properties / reports / descriptionPrevious value: -"Pairs that share a partition and a type but have incompatible or risky QoS."New value: +"Pairs sharing partition and type with incompatible or risky QoS." - removed
Output schema / properties / reports / titleRemoved value: -"Reports" - removed
Output schema / properties / reports_total / titleRemoved value: -"Reports Total" - added
Output schema / properties / summaryAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One-line reading of the scan: counts of incompatible, not matched and matched pairs." +} - removed
Output schema / properties / topics_scanned / titleRemoved value: -"Topics Scanned" - changed
Output schema / properties / truncated / descriptionPrevious value: -"True when `reports`, `matched` or `not_matched` was cut to its cap (200 entries each, incompatible reports first): see the `*_total` fields. Narrow the scan with `topic`."New value: +"True when `reports`, `matched` or `not_matched` hit the 200-entry cap (see `*_total`); narrow with `topic`." - removed
Output schema / properties / truncated / titleRemoved value: -"Truncated"
- Added
get_node_info - Changed
get_topic_info28 fields changed- changed
Input schema / properties / topic / descriptionPrevious value: -"Fully qualified ROS2 topic name starting with `/`, e.g. `/cmd_vel` or `/camera/image_raw`. Each `/`-separated segment must start with a letter or underscore and contain only letters, digits, and underscores; everything else (whitespace, quotes, shell metacharacters, `//`, trailing `/`) is rejected before reaching the `ros2` CLI."New value: +"Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI." - removed
Input schema / properties / topic / titleRemoved value: -"Topic" - removed
Input schema / titleRemoved value: -"get_topic_infoArguments" - removed
Output schema / $defs / QosProfileRemoved value: -{ - "additionalProperties": false, - "description": "QoS profile of one DDS endpoint (reader or writer).\n\nCovers the policies behind most \"subscriber receives nothing\" cases.\nVendor-specific extensions are ignored.", - "properties": { - "data_representation": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Data representations the endpoint accepts or offers, from `XCDR1` and `XCDR2`. `None` when not announced.", - "title": "Data Representation" - }, - "deadline_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Deadline QoS in nanoseconds. `None` means no deadline (infinite, the DDS default). A reader deadline tighter (smaller) than a writer deadline is incompatible: the writer cannot guarantee the reader's promise.", - "title": "Deadline Ns" - }, - "destination_order": { - "anyOf": [ - { - "enum": [ - "BY_RECEPTION_TIMESTAMP", - "BY_SOURCE_TIMESTAMP" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "DestinationOrder QoS. `None` when not announced.", - "title": "Destination Order" - }, - "durability": { - "description": "DDS Durability QoS. `VOLATILE` writers do not retain samples for late joiners; `TRANSIENT_LOCAL` writers do. A `TRANSIENT_LOCAL` reader cannot match a `VOLATILE` writer.", - "enum": [ - "VOLATILE", - "TRANSIENT_LOCAL", - "TRANSIENT", - "PERSISTENT" - ], - "title": "Durability", - "type": "string" - }, - "history": { - "anyOf": [ - { - "enum": [ - "KEEP_LAST", - "KEEP_ALL" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "DDS History QoS. `KEEP_LAST` keeps a bounded ring buffer of size `history_depth`; `KEEP_ALL` keeps every sample (memory permitting). Mixed `KEEP_ALL` reader with `KEEP_LAST` writer is risky but not strictly incompatible. `None` when it is not known: DDS discovery does not carry History, so it is reported only for TopicForge's own endpoints and for Cyclone DDS peers that set something other than the default (see `history_note`).", - "title": "History" - }, - "history_depth": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Depth for `KEEP_LAST`. `None` for `KEEP_ALL` or when `history` is `None`.", - "title": "History Depth" - }, - "history_note": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Set when `history` was left out because discovery did not announce it.", - "title": "History Note" - }, - "latency_budget_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "LatencyBudget QoS in nanoseconds (a delivery-delay hint). `0` is the DDS default. `None` means infinite or not announced.", - "title": "Latency Budget Ns" - }, - "liveliness_kind": { - "anyOf": [ - { - "enum": [ - "AUTOMATIC", - "MANUAL_BY_PARTICIPANT", - "MANUAL_BY_TOPIC" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Liveliness QoS kind. `AUTOMATIC` is asserted by the middleware; the two `MANUAL_*` kinds need the application to write or assert liveliness, and the endpoint is declared not alive when it does not within `liveliness_lease_ns`. `None` when not announced.", - "title": "Liveliness Kind" - }, - "liveliness_lease_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Liveliness lease duration in nanoseconds. `None` means infinite (the DDS default) or not announced.", - "title": "Liveliness Lease Ns" - }, - "ownership_kind": { - "anyOf": [ - { - "enum": [ - "SHARED", - "EXCLUSIVE" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Ownership QoS kind. With `EXCLUSIVE`, only the writer with the highest `ownership_strength` delivers samples to a reader. `None` when not announced.", - "title": "Ownership Kind" - }, - "ownership_strength": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Ownership strength of a writer (`EXCLUSIVE` ownership). `None` when not announced.", - "title": "Ownership Strength" - }, - "partitions": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Partition names the endpoint's Publisher or Subscriber belongs to. No Partition policy is the default partition, reported as `[\"\"]` (never an empty list). Endpoints only match when their partitions intersect. `None` only when the backend cannot read partitions at all (unknown, not the default).", - "title": "Partitions" - }, - "reliability": { - "description": "DDS Reliability QoS. `RELIABLE` retries lost samples; `BEST_EFFORT` does not. A `RELIABLE` reader cannot match a `BEST_EFFORT` writer.", - "enum": [ - "RELIABLE", - "BEST_EFFORT" - ], - "title": "Reliability", - "type": "string" - }, - "unknown_policies": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Policies the endpoint announced but whose value could not be read (for example `Deadline`). Their duration fields are `None` here, which does NOT mean infinite for these: the scan does not compare them.", - "title": "Unknown Policies" - } - }, - "required": [ - "reliability", - "durability" - ], - "title": "QosProfile", - "type": "object" -} - added
Output schema / $defs / SideQosAdded value: +{ + "additionalProperties": false, + "properties": { + "durability": { + "description": "Shared durability of this side, or `mixed`; `transient_local` marks a latched topic such as `/tf_static`.", + "enum": [ + "volatile", + "transient_local", + "mixed" + ], + "type": "string" + }, + "endpoint_count": { + "description": "Endpoints of this side the values were read from.", + "minimum": 1, + "type": "integer" + }, + "reliability": { + "description": "Reliability shared by the endpoints of this side, or `mixed` when they disagree.", + "enum": [ + "reliable", + "best_effort", + "mixed" + ], + "type": "string" + } + }, + "required": [ + "reliability", + "durability", + "endpoint_count" + ], + "type": "object" +} - changed
Output schema / descriptionPrevious value: -"Description of a single ROS2 topic.\n\nCarries optional DDS-side enrichment fields when the active middleware\nbackend can resolve them (CycloneDDS / Fast DDS). The ROS2 CLI adapter and\nthe mock ROS2 path leave them `None`."New value: +"Detail of a single ROS2 topic: counts, QoS per side and the nodes on each side." - removed
Output schema / properties / message_type / descriptionRemoved value: -"ROS2 message type, e.g. `geometry_msgs/msg/Twist`." - removed
Output schema / properties / message_type / titleRemoved value: -"Message Type" - changed
Output schema / properties / mode_effective / descriptionPrevious value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)." - removed
Output schema / properties / mode_effective / titleRemoved value: -"Mode Effective" - removed
Output schema / properties / name / descriptionRemoved value: -"Fully qualified topic name, e.g. `/cmd_vel`." - removed
Output schema / properties / name / titleRemoved value: -"Name" - added
Output schema / properties / noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence of context, or `None`." +} - removed
Output schema / properties / publisher_count / descriptionRemoved value: -"Publishers known to the graph." - removed
Output schema / properties / publisher_count / titleRemoved value: -"Publisher Count" - added
Output schema / properties / publisher_nodesAdded value: +{ + "description": "Publisher node names (`ros2 topic info --verbose`); empty when unknown.", + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / publisher_qosAdded value: +{ + "anyOf": [ + { + "$ref": "#/$defs/SideQos" + }, + { + "type": "null" + } + ], + "default": null, + "description": "QoS of the publishers. `None` when it was not read: see `publisher_qos_note`." +} - added
Output schema / properties / publisher_qos_noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Why `publisher_qos` is `None`. `None` when it is set." +} - removed
Output schema / properties / qos_durabilityRemoved value: -{ - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Durability announced by the topic's publishers: `volatile`, `transient_local` (late subscribers receive the last samples; typical of latched topics such as `/tf_static`), or `mixed` when publishers disagree. `null` when unknown, with the same rules as `qos_reliability`.", - "title": "Qos Durability" -} - removed
Output schema / properties / qos_profileRemoved value: -{ - "anyOf": [ - { - "$ref": "#/$defs/QosProfile" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Effective DDS QoS profile for this topic when resolvable. `None` from the ROS2 CLI adapter or when the DDS module is inactive. The DDS module populates this on a best-effort basis (picks one representative endpoint if reader/writer QoS differ)." -} - removed
Output schema / properties / qos_reliabilityRemoved value: -{ - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Reliability announced by the topic's publishers: `reliable`, `best_effort`, or `mixed` when publishers disagree. `null` when unknown: the topic has no publisher, or the value was not read (`list_topics` does not read QoS; `get_topic_info` does).", - "title": "Qos Reliability" -} - removed
Output schema / properties / reader_countRemoved value: -{ - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "DDS reader-endpoint count when the active backend can resolve endpoint-level info (Cyclone / Fast DDS). `None` from the ROS2 CLI adapter or when the DDS module is inactive.", - "title": "Reader Count" -} - removed
Output schema / properties / subscriber_count / descriptionRemoved value: -"Subscribers known to the graph." - removed
Output schema / properties / subscriber_count / titleRemoved value: -"Subscriber Count" - added
Output schema / properties / subscriber_nodesAdded value: +{ + "description": "Subscriber node names; empty when unknown.", + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / subscription_qosAdded value: +{ + "anyOf": [ + { + "$ref": "#/$defs/SideQos" + }, + { + "type": "null" + } + ], + "default": null, + "description": "QoS of the subscriptions. `None` when it was not read: see `subscription_qos_note`." +} - added
Output schema / properties / subscription_qos_noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Why `subscription_qos` is `None`. `None` when it is set." +} - removed
Output schema / properties / writer_countRemoved value: -{ - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "DDS writer-endpoint count when the active backend can resolve endpoint-level info. `None` from the ROS2 CLI adapter or when the DDS module is inactive.", - "title": "Writer Count" -}
- Changed
health_check50 fields changed- removed
Input schema / titleRemoved value: -"health_checkArguments" - added
Output schema / properties / contract_versionAdded value: +{ + "default": 2, + "description": "Output contract version; changes only when a field is renamed, removed or retyped.", + "minimum": 1, + "type": "integer" +} - changed
Output schema / properties / dds_backend / descriptionPrevious value: -"DDS backend of the adapter actually serving requests. `none` when the DDS module is not active (default for ROS2-only installs). `mock` for synthetic fixtures. `cyclone` requires `pip install \"topicforge[dds-cyclone]\"` (Eclipse CycloneDDS); `fast` requires a Fast DDS Python binding built from eProsima sources (not on PyPI); `opendds` and `dust` are permanent stub adapters that never serve."New value: +"DDS backend serving: `mock`, `cyclone`, `fast`, or `none` (DDS module inactive)." - changed
Output schema / properties / dds_backend / enumPrevious value: -[ - "mock", - "cyclone", - "fast", - "opendds", - "dust", - "none" -]New value: +[ + "mock", + "cyclone", + "fast", + "none" +] - removed
Output schema / properties / dds_backend / titleRemoved value: -"Dds Backend" - removed
Output schema / properties / dds_domain_id / titleRemoved value: -"Dds Domain Id" - added
Output schema / properties / dds_inactive_noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Why `dds_backend` is `none` while ROS 2 serves (not selected, binding missing, or failed to start); else `null`." +} - removed
Output schema / properties / dds_inactive_reasonRemoved value: -{ - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Why `dds_backend` is `none` while the ROS 2 CLI serves: the backend was not selected (`TOPICFORGE_DDS_BACKEND` unset or `mock`), its Python binding is not installed, or the binding is installed but the adapter failed to start. `null` when a DDS backend is serving or the cause is not known.", - "title": "Dds Inactive Reason" -} - changed
Output schema / properties / dds_security / descriptionPrevious value: -"DDS Security is not handled. On a secured domain TopicForge can show participants but not protected endpoints or data."New value: +"DDS Security is not handled: protected endpoints and data are invisible." - removed
Output schema / properties / dds_security / titleRemoved value: -"Dds Security" - changed
Output schema / properties / max_sample_count / descriptionPrevious value: -"Server-side cap on the number of samples returned per `sample_messages` call. Requests above this limit are silently clamped; the value is exposed here so a client can size its requests proactively. Constant within a given server version."New value: +"Server cap on samples per call; larger requests are clamped." - removed
Output schema / properties / max_sample_count / titleRemoved value: -"Max Sample Count" - changed
Output schema / properties / middleware_available / descriptionPrevious value: -"True when a DDS backend is serving (`dds_backend` is not `none`). When the DDS module is inactive (`dds_backend == 'none'`), whether the *configured* backend's Python bindings are importable, so a missing binding is visible."New value: +"True when a DDS backend serves; else whether the configured binding is importable." - removed
Output schema / properties / middleware_available / titleRemoved value: -"Middleware Available" - changed
Output schema / properties / mode / descriptionPrevious value: -"Runtime mode of the adapter actually serving requests: `mock` or `live`. `live` with `ros_backend` `none` means the DDS tools are live and the ROS 2 tools are not available. Can differ from `requested_mode` when a live backend could not start (e.g. `live` requested without `ros2` installed falls back to `mock`)."New value: +"Mode actually serving: `mock` or `live` (`live` with `ros_backend` `none`: DDS tools only). May differ from `requested_mode` after a fallback." - removed
Output schema / properties / mode / titleRemoved value: -"Mode" - added
Output schema / properties / noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence of context, or `None`." +} - removed
Output schema / properties / now_ns / titleRemoved value: -"Now Ns" - changed
Output schema / properties / observed_domain_note / descriptionPrevious value: -"Plain statement of which DDS domain is observed, set when a DDS module is active: only the domain joined at startup is visible, a program on another domain is invisible."New value: +"Which DDS domain is observed: only the one joined at startup; others are invisible." - removed
Output schema / properties / observed_domain_note / titleRemoved value: -"Observed Domain Note" - changed
Output schema / properties / observer_started_ns / descriptionPrevious value: -"Wall-clock time (ns since epoch) when the DDS observer joined the bus. Nothing earlier than this was watched: `now_ns` minus this is how long TopicForge has been observing. `None` without a live DDS observer."New value: +"ns when the DDS observer joined; nothing earlier was watched. `None` without one." - removed
Output schema / properties / observer_started_ns / titleRemoved value: -"Observer Started Ns" - changed
Output schema / properties / payload_decoding / descriptionPrevious value: -"Whether DDS user-topic payloads are decoded. `disabled` today: `peek_dds_samples` and `topic_metrics` do not return message content for user topics."New value: +"`disabled`: DDS user-topic payloads are not decoded." - removed
Output schema / properties / payload_decoding / titleRemoved value: -"Payload Decoding" - added
Output schema / properties / payload_decoding_noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "user-topic payload decoding is switched off until it is validated on a real bus; builtin discovery topics are still readable", + "description": "Reason for `payload_decoding`." +} - removed
Output schema / properties / payload_decoding_reasonRemoved value: -{ - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": "user-topic payload decoding is switched off until it is validated on a real bus; builtin discovery topics are still readable", - "description": "One-line reason for `payload_decoding`.", - "title": "Payload Decoding Reason" -} - changed
Output schema / properties / requested_mode / descriptionPrevious value: -"Mode requested via configuration (may be `auto`)."New value: +"Mode requested by configuration (may be `auto`)." - removed
Output schema / properties / requested_mode / titleRemoved value: -"Requested Mode" - added
Output schema / properties / rmw_implementationAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "ROS 2 middleware, e.g. `rmw_fastrtps_cpp`; `None` when `rmw_source` is `none`." +} - added
Output schema / properties / rmw_sourceAdded value: +{ + "default": "none", + "description": "`env`, `ros2_cli`, `distro_default` (not verified on the graph) or `none`.", + "enum": [ + "env", + "ros2_cli", + "distro_default", + "none" + ], + "type": "string" +} - removed
Output schema / properties / ros2_available / titleRemoved value: -"Ros2 Available" - changed
Output schema / properties / ros2_distro / descriptionPrevious value: -"Value of `ROS_DISTRO` if set in the environment. **Env disclosure, by design**: under the local-trust threat model (see README 'Security model'), the MCP client is a trusted agent on a machine the user controls, and exposing the ROS2 distro lets it adapt to e.g. `humble`/`jazzy` differences. For a hosted multi-tenant TopicForge endpoint this field would be scrubbed ."New value: +"`ROS_DISTRO` if set (disclosed by design under the local-trust model)." - removed
Output schema / properties / ros2_distro / titleRemoved value: -"Ros2 Distro" - changed
Output schema / properties / ros_backend / descriptionPrevious value: -"Active ROS2 backend. `ros2_cli` when the `ros2` CLI is on PATH and live mode resolves to a Ros2CliAdapter (alone or as the ROS half of a composite). `mock` when MockAdapter serves the ROS surface. `none` when no ROS2 backend is active (e.g. DDS-only live install with no `ros2` CLI). Together with `dds_backend` it tells the ROS2 and DDS halves of the runtime apart."New value: +"ROS 2 backend: `ros2_cli`, `mock` or `none`." - removed
Output schema / properties / ros_backend / titleRemoved value: -"Ros Backend" - changed
Output schema / properties / ros_tools_available / descriptionPrevious value: -"True when the ROS 2 tools (`list_topics`, `get_topic_info`, `sample_messages`, `analyze_bag`, `peek_bag_samples`) can run, i.e. `ros_backend` is not `none`. False on a DDS-only setup: use `list_endpoints` for topics and wiring there."New value: +"True when the ROS 2 tools can run (`ros_backend` is not `none`); on a DDS-only setup use `list_endpoints`." - removed
Output schema / properties / ros_tools_available / titleRemoved value: -"Ros Tools Available" - changed
Output schema / properties / server_version / descriptionPrevious value: -"TopicForge server version (matches the PyPI release of the `topicforge` package)."New value: +"TopicForge version." - removed
Output schema / properties / server_version / titleRemoved value: -"Server Version" - changed
Output schema / properties / sim_clock_published / descriptionPrevious value: -"Whether `/clock` has at least one publisher on the ROS 2 graph. True suggests nodes may run on simulated time (`use_sim_time`), so `header.stamp` values are sim time, not wall time (compare with `received_ns`). It is a hint: a publisher on `/clock` does not prove a given node follows it. `None` in mock mode or when the `ros2` CLI could not answer."New value: +"Whether `/clock` had a publisher in the last graph read (a hint that stamps may be sim time). `None` in mock or without a recent read; `health_check` never runs `ros2`." - removed
Output schema / properties / sim_clock_published / titleRemoved value: -"Sim Clock Published" - changed
Output schema / properties / tracker_cache_evictions / descriptionPrevious value: -"Discovery entries dropped because a tracker cache was full (4096 per cache). Non-zero means the bus is bigger than what is listed."New value: +"Discovery entries dropped from a full tracker cache (4096 each); non-zero means the bus is bigger than listed." - removed
Output schema / properties / tracker_cache_evictions / titleRemoved value: -"Tracker Cache Evictions" - changed
Output schema / properties / tracker_errors / descriptionPrevious value: -"Discovery tracker passes that raised (swallowed and logged). Non-zero means gaps."New value: +"Tracker passes that raised; non-zero means gaps." - removed
Output schema / properties / tracker_errors / titleRemoved value: -"Tracker Errors" - changed
Output schema / properties / tracker_last_pass_ns / descriptionPrevious value: -"Wall-clock time (ns since epoch) of the last completed tracker pass. A value far older than `now_ns` means lifecycle is stale."New value: +"ns of the last tracker pass; far older than `now_ns` means stale lifecycle." - removed
Output schema / properties / tracker_last_pass_ns / titleRemoved value: -"Tracker Last Pass Ns" - removed
Output schema / properties / tracker_passes / titleRemoved value: -"Tracker Passes" - changed
Output schema / properties / tracker_running / descriptionPrevious value: -"Whether the continuous discovery tracker thread is alive (Cyclone). `None` when the backend has no tracker."New value: +"Whether the discovery tracker thread is alive (Cyclone); `None` without a tracker." - removed
Output schema / properties / tracker_running / titleRemoved value: -"Tracker Running"
- Changed
list_endpoints125 fields changed- changed
Input schema / properties / domain_id / descriptionPrevious value: -"Accepted for compatibility (0..232). TopicForge observes the domain it joined at startup (TOPICFORGE_DDS_DOMAIN_ID); this argument does not switch domains, and the response `domain_id` says which one was observed."New value: +"Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value." - removed
Input schema / properties / domain_id / titleRemoved value: -"Domain Id" - added
Input schema / properties / fieldsAdded value: +{ + "default": "full", + "description": "`full` (default) or `compact`: one-line `qos_summary` instead of the full `qos`, no `type_id`, `announced_ns` or `ros_topic_note`. Use `compact` on large buses.", + "enum": [ + "full", + "compact" + ], + "type": "string" +} - changed
Input schema / properties / include_departed / descriptionPrevious value: -"Also list endpoints whose participant left the bus (flagged with `gone_ns`). Defaults to false; `by_topic` reports them as `departed_writers` / `departed_readers` either way."New value: +"Also list endpoints whose participant left (flagged with `gone_ns`); `by_topic` reports them either way. Default false." - removed
Input schema / properties / include_departed / titleRemoved value: -"Include Departed" - added
Input schema / properties / include_internalAdded value: +{ + "default": false, + "description": "Also list ROS 2 service and action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`), most of a small robot's endpoints. Default false.", + "type": "boolean" +} - changed
Input schema / properties / include_observer / descriptionPrevious value: -"Include TopicForge's own observer participant's endpoints. Defaults to false."New value: +"Include TopicForge's own observer endpoints. Default false." - removed
Input schema / properties / include_observer / titleRemoved value: -"Include Observer" - changed
Input schema / properties / participant_guid / descriptionPrevious value: -"Only endpoints owned by this participant, as the guid reported by `list_participants` or by an earlier `list_endpoints`. Omit for all participants."New value: +"Only endpoints of this participant (a guid from `list_participants`). Omit for all." - removed
Input schema / properties / participant_guid / titleRemoved value: -"Participant Guid" - changed
Input schema / properties / topic / descriptionPrevious value: -"Only endpoints on this DDS topic name: a bare name such as `scan` or a ROS 2 mangled name such as `rt/scan` (exact name first, then the alternate form). Omit to list every topic."New value: +"Only endpoints on this DDS topic: `scan` or `rt/scan` (exact name first, then the alternate form). Omit for every topic." - removed
Input schema / properties / topic / titleRemoved value: -"Topic" - removed
Input schema / titleRemoved value: -"list_endpointsArguments" - removed
Output schema / $defs / DepartedEndpoint / descriptionRemoved value: -"An endpoint whose participant left the bus (crash, clean exit or lease expiry)." - removed
Output schema / $defs / DepartedEndpoint / properties / gone_ns / titleRemoved value: -"Gone Ns" - removed
Output schema / $defs / DepartedEndpoint / properties / guid / descriptionRemoved value: -"GUID of the departed endpoint." - removed
Output schema / $defs / DepartedEndpoint / properties / guid / titleRemoved value: -"Guid" - removed
Output schema / $defs / DepartedEndpoint / properties / participant_guid / descriptionRemoved value: -"GUID of the participant that owned it." - removed
Output schema / $defs / DepartedEndpoint / properties / participant_guid / titleRemoved value: -"Participant Guid" - removed
Output schema / $defs / DepartedEndpoint / properties / participant_name / descriptionRemoved value: -"Announced name of that participant, `None` when it set none." - removed
Output schema / $defs / DepartedEndpoint / properties / participant_name / titleRemoved value: -"Participant Name" - removed
Output schema / $defs / DepartedEndpoint / titleRemoved value: -"DepartedEndpoint" - removed
Output schema / $defs / EndpointInfo / descriptionRemoved value: -"One DDS endpoint (writer or reader) announced through discovery." - removed
Output schema / $defs / EndpointInfo / properties / activityRemoved value: -{ - "default": null, - "description": "Reserved for a future liveness signal. Always `None` today.", - "title": "Activity", - "type": "null" -} - removed
Output schema / $defs / EndpointInfo / properties / activity_noteRemoved value: -{ - "default": "not observed: TopicForge holds no reader on user topics, so it cannot tell a silent or hung writer from a healthy one", - "description": "Why `activity` is not populated.", - "title": "Activity Note", - "type": "string" -} - changed
Output schema / $defs / EndpointInfo / properties / announced_ns / descriptionPrevious value: -"Source timestamp of the discovery announcement, ns since epoch, read on the announcing side's clock (it can differ from this host's clock). `None` when not available."New value: +"Source timestamp of the announcement, on the announcer's clock; `None` if unavailable." - removed
Output schema / $defs / EndpointInfo / properties / announced_ns / titleRemoved value: -"Announced Ns" - added
Output schema / $defs / EndpointInfo / properties / dds_topicAdded value: +{ + "type": "string" +} - removed
Output schema / $defs / EndpointInfo / properties / domain_idRemoved value: -{ - "description": "DDS domain the endpoint was observed on.", - "maximum": 232, - "minimum": 0, - "title": "Domain Id", - "type": "integer" -} - changed
Output schema / $defs / EndpointInfo / properties / gone_ns / descriptionPrevious value: -"`None` for a live endpoint. For a departed endpoint (only listed with `include_departed`): when its participant was lost (the participant's `lost_ns`, an upper bound of the death)."New value: +"`None` for a live endpoint; for a departed one (`include_departed`) when its participant was lost (upper bound)." - removed
Output schema / $defs / EndpointInfo / properties / gone_ns / titleRemoved value: -"Gone Ns" - removed
Output schema / $defs / EndpointInfo / properties / guid / descriptionRemoved value: -"GUID of the endpoint, `xxxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx`." - removed
Output schema / $defs / EndpointInfo / properties / guid / titleRemoved value: -"Guid" - removed
Output schema / $defs / EndpointInfo / properties / is_observer / titleRemoved value: -"Is Observer" - removed
Output schema / $defs / EndpointInfo / properties / mode_effectiveRemoved value: -{ - "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.", - "enum": [ - "mock", - "live" - ], - "title": "Mode Effective", - "type": "string" -} - removed
Output schema / $defs / EndpointInfo / properties / participant_guid / descriptionRemoved value: -"GUID of the owning participant, same format as `list_participants`." - removed
Output schema / $defs / EndpointInfo / properties / participant_guid / titleRemoved value: -"Participant Guid" - removed
Output schema / $defs / EndpointInfo / properties / participant_name / descriptionRemoved value: -"Announced name of the owning participant, `None` when it set none." - removed
Output schema / $defs / EndpointInfo / properties / participant_name / titleRemoved value: -"Participant Name" - removed
Output schema / $defs / EndpointInfo / properties / participant_vendor / descriptionRemoved value: -"Vendor of the owning participant, same value as `list_participants` (`unknown` when it cannot be determined)." - removed
Output schema / $defs / EndpointInfo / properties / participant_vendor / titleRemoved value: -"Participant Vendor" - changed
Output schema / $defs / EndpointInfo / properties / qos / descriptionPrevious value: -"QoS the endpoint announced. Durations (`deadline_ns`, `liveliness_lease_ns`, `latency_budget_ns`) of `None` mean infinite or not set. `None` when reliability or durability could not be resolved."New value: +"Announced QoS; `None` when reliability or durability could not be resolved. A duration of `None` is infinite or unset." - added
Output schema / $defs / EndpointInfo / properties / qos_summaryAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One-line QoS, set only with `fields` `compact` (where `qos` is `None`)." +} - removed
Output schema / $defs / EndpointInfo / properties / role / titleRemoved value: -"Role" - added
Output schema / $defs / EndpointInfo / properties / ros_topicAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "ROS 2 name of the topic, for example `/scan`. `None` when the endpoint is not a ROS 2 topic: see `ros_topic_note`." +} - added
Output schema / $defs / EndpointInfo / properties / ros_topic_noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Why `ros_topic` is `None`. `None` when it is set." +} - removed
Output schema / $defs / EndpointInfo / properties / topicRemoved value: -{ - "description": "DDS topic name.", - "title": "Topic", - "type": "string" -} - removed
Output schema / $defs / EndpointInfo / properties / type_id / titleRemoved value: -"Type Id" - removed
Output schema / $defs / EndpointInfo / properties / type_name / descriptionRemoved value: -"Announced data type name." - removed
Output schema / $defs / EndpointInfo / properties / type_name / titleRemoved value: -"Type Name" - changed
Output schema / $defs / EndpointInfo / requiredPrevious value: -[ - "guid", - "role", - "participant_guid", - "topic", - "is_observer", - "domain_id", - "mode_effective" -]New value: +[ + "guid", + "role", + "participant_guid", + "dds_topic", + "is_observer" +] - removed
Output schema / $defs / EndpointInfo / titleRemoved value: -"EndpointInfo" - removed
Output schema / $defs / QosProfile / descriptionRemoved value: -"QoS profile of one DDS endpoint (reader or writer).\n\nCovers the policies behind most \"subscriber receives nothing\" cases.\nVendor-specific extensions are ignored." - changed
Output schema / $defs / QosProfile / properties / data_representation / descriptionPrevious value: -"Data representations the endpoint accepts or offers, from `XCDR1` and `XCDR2`. `None` when not announced."New value: +"`XCDR1` / `XCDR2` accepted or offered; `None` when not announced." - removed
Output schema / $defs / QosProfile / properties / data_representation / titleRemoved value: -"Data Representation" - changed
Output schema / $defs / QosProfile / properties / deadline_ns / descriptionPrevious value: -"Deadline QoS in nanoseconds. `None` means no deadline (infinite, the DDS default). A reader deadline tighter (smaller) than a writer deadline is incompatible: the writer cannot guarantee the reader's promise."New value: +"Deadline in ns; `None` is infinite. A reader deadline shorter than the writer's is incompatible." - removed
Output schema / $defs / QosProfile / properties / deadline_ns / titleRemoved value: -"Deadline Ns" - removed
Output schema / $defs / QosProfile / properties / destination_order / titleRemoved value: -"Destination Order" - changed
Output schema / $defs / QosProfile / properties / durability / descriptionPrevious value: -"DDS Durability QoS. `VOLATILE` writers do not retain samples for late joiners; `TRANSIENT_LOCAL` writers do. A `TRANSIENT_LOCAL` reader cannot match a `VOLATILE` writer."New value: +"`VOLATILE` keeps nothing for late joiners, `TRANSIENT_LOCAL` does; a TRANSIENT_LOCAL reader cannot match a VOLATILE writer." - removed
Output schema / $defs / QosProfile / properties / durability / titleRemoved value: -"Durability" - changed
Output schema / $defs / QosProfile / properties / history / descriptionPrevious value: -"DDS History QoS. `KEEP_LAST` keeps a bounded ring buffer of size `history_depth`; `KEEP_ALL` keeps every sample (memory permitting). Mixed `KEEP_ALL` reader with `KEEP_LAST` writer is risky but not strictly incompatible. `None` when it is not known: DDS discovery does not carry History, so it is reported only for TopicForge's own endpoints and for Cyclone DDS peers that set something other than the default (see `history_note`)."New value: +"`KEEP_LAST` (ring of `history_depth`) or `KEEP_ALL`; `None` when discovery did not announce it (see `history_note`)." - removed
Output schema / $defs / QosProfile / properties / history / titleRemoved value: -"History" - changed
Output schema / $defs / QosProfile / properties / history_depth / descriptionPrevious value: -"Depth for `KEEP_LAST`. `None` for `KEEP_ALL` or when `history` is `None`."New value: +"Depth for `KEEP_LAST`; else `None`." - removed
Output schema / $defs / QosProfile / properties / history_depth / titleRemoved value: -"History Depth" - changed
Output schema / $defs / QosProfile / properties / history_note / descriptionPrevious value: -"Set when `history` was left out because discovery did not announce it."New value: +"Why `history` is `None`." - removed
Output schema / $defs / QosProfile / properties / history_note / titleRemoved value: -"History Note" - changed
Output schema / $defs / QosProfile / properties / latency_budget_ns / descriptionPrevious value: -"LatencyBudget QoS in nanoseconds (a delivery-delay hint). `0` is the DDS default. `None` means infinite or not announced."New value: +"LatencyBudget in ns (a hint); `0` is the default, `None` is infinite or not announced." - removed
Output schema / $defs / QosProfile / properties / latency_budget_ns / titleRemoved value: -"Latency Budget Ns" - changed
Output schema / $defs / QosProfile / properties / liveliness_kind / descriptionPrevious value: -"Liveliness QoS kind. `AUTOMATIC` is asserted by the middleware; the two `MANUAL_*` kinds need the application to write or assert liveliness, and the endpoint is declared not alive when it does not within `liveliness_lease_ns`. `None` when not announced."New value: +"`AUTOMATIC`, or `MANUAL_*` (the app must assert liveliness within `liveliness_lease_ns`); `None` when not announced." - removed
Output schema / $defs / QosProfile / properties / liveliness_kind / titleRemoved value: -"Liveliness Kind" - changed
Output schema / $defs / QosProfile / properties / liveliness_lease_ns / descriptionPrevious value: -"Liveliness lease duration in nanoseconds. `None` means infinite (the DDS default) or not announced."New value: +"Liveliness lease in ns; `None` is infinite or not announced." - removed
Output schema / $defs / QosProfile / properties / liveliness_lease_ns / titleRemoved value: -"Liveliness Lease Ns" - changed
Output schema / $defs / QosProfile / properties / ownership_kind / descriptionPrevious value: -"Ownership QoS kind. With `EXCLUSIVE`, only the writer with the highest `ownership_strength` delivers samples to a reader. `None` when not announced."New value: +"With `EXCLUSIVE` only the writer with the highest `ownership_strength` delivers; `None` when not announced." - removed
Output schema / $defs / QosProfile / properties / ownership_kind / titleRemoved value: -"Ownership Kind" - changed
Output schema / $defs / QosProfile / properties / ownership_strength / descriptionPrevious value: -"Ownership strength of a writer (`EXCLUSIVE` ownership). `None` when not announced."New value: +"Writer strength under `EXCLUSIVE` ownership; `None` when not announced." - removed
Output schema / $defs / QosProfile / properties / ownership_strength / titleRemoved value: -"Ownership Strength" - changed
Output schema / $defs / QosProfile / properties / partitions / descriptionPrevious value: -"Partition names the endpoint's Publisher or Subscriber belongs to. No Partition policy is the default partition, reported as `[\"\"]` (never an empty list). Endpoints only match when their partitions intersect. `None` only when the backend cannot read partitions at all (unknown, not the default)."New value: +"Partition names; the default partition is `[\"\"]`, never `[]`. Endpoints match only if partitions intersect; `None` when unreadable." - removed
Output schema / $defs / QosProfile / properties / partitions / titleRemoved value: -"Partitions" - changed
Output schema / $defs / QosProfile / properties / reliability / descriptionPrevious value: -"DDS Reliability QoS. `RELIABLE` retries lost samples; `BEST_EFFORT` does not. A `RELIABLE` reader cannot match a `BEST_EFFORT` writer."New value: +"`RELIABLE` retries lost samples, `BEST_EFFORT` does not; a RELIABLE reader cannot match a BEST_EFFORT writer." - removed
Output schema / $defs / QosProfile / properties / reliability / titleRemoved value: -"Reliability" - changed
Output schema / $defs / QosProfile / properties / unknown_policies / descriptionPrevious value: -"Policies the endpoint announced but whose value could not be read (for example `Deadline`). Their duration fields are `None` here, which does NOT mean infinite for these: the scan does not compare them."New value: +"Policies announced but unreadable (e.g. `Deadline`): their fields are `None`, which does NOT mean infinite; the scan does not compare them." - removed
Output schema / $defs / QosProfile / properties / unknown_policies / titleRemoved value: -"Unknown Policies" - removed
Output schema / $defs / QosProfile / titleRemoved value: -"QosProfile" - removed
Output schema / $defs / TopicSummary / descriptionRemoved value: -"Per-topic roll-up of the listed endpoints, for spotting orphans." - added
Output schema / $defs / TopicSummary / properties / dds_topicAdded value: +{ + "type": "string" +} - changed
Output schema / $defs / TopicSummary / properties / departed_readers / descriptionPrevious value: -"Readers on this topic whose participant left, newest first."New value: +"Readers whose participant left, newest first." - removed
Output schema / $defs / TopicSummary / properties / departed_readers / titleRemoved value: -"Departed Readers" - changed
Output schema / $defs / TopicSummary / properties / departed_writers / descriptionPrevious value: -"Writers on this topic whose participant left, newest first (bounded memory: last 200 departed endpoints, 1 h). Explains a topic that lost its only writer."New value: +"Writers whose participant left, newest first (last 200, 1 h)." - removed
Output schema / $defs / TopicSummary / properties / departed_writers / titleRemoved value: -"Departed Writers" - changed
Output schema / $defs / TopicSummary / properties / orphan / descriptionPrevious value: -"`no_reader`: writers but no reader. `no_writer`: readers but no writer. `None` when both sides exist."New value: +"`no_reader` (writers only), `no_writer` (readers only), else `None`." - removed
Output schema / $defs / TopicSummary / properties / orphan / titleRemoved value: -"Orphan" - removed
Output schema / $defs / TopicSummary / properties / partitions / titleRemoved value: -"Partitions" - removed
Output schema / $defs / TopicSummary / properties / reader_count / descriptionRemoved value: -"Number of listed readers." - removed
Output schema / $defs / TopicSummary / properties / reader_count / titleRemoved value: -"Reader Count" - added
Output schema / $defs / TopicSummary / properties / ros_topicAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "ROS 2 name of the topic, for example `/scan`. `None` for a DDS topic that is not a ROS 2 topic." +} - removed
Output schema / $defs / TopicSummary / properties / topicRemoved value: -{ - "description": "DDS topic name.", - "title": "Topic", - "type": "string" -} - removed
Output schema / $defs / TopicSummary / properties / type_names / descriptionRemoved value: -"Distinct type names announced on this topic." - removed
Output schema / $defs / TopicSummary / properties / type_names / titleRemoved value: -"Type Names" - removed
Output schema / $defs / TopicSummary / properties / writer_count / descriptionRemoved value: -"Number of listed writers." - removed
Output schema / $defs / TopicSummary / properties / writer_count / titleRemoved value: -"Writer Count" - changed
Output schema / $defs / TopicSummary / requiredPrevious value: -[ - "topic", - "type_names", - "writer_count", - "reader_count", - "partitions" -]New value: +[ + "dds_topic", + "type_names", + "writer_count", + "reader_count", + "partitions" +] - removed
Output schema / $defs / TopicSummary / titleRemoved value: -"TopicSummary" - removed
Output schema / properties / by_topic / titleRemoved value: -"By Topic" - added
Output schema / properties / departed_endpoint_countAdded value: +{ + "default": 0, + "description": "Departed endpoints matching the filters; listed in `endpoints` only with `include_departed`.", + "minimum": 0, + "type": "integer" +} - removed
Output schema / properties / departed_endpointsRemoved value: -{ - "default": 0, - "description": "Departed endpoints (their participant left) matching the filters. They are in `endpoints` only with `include_departed`; `by_topic` always carries them as `departed_writers` / `departed_readers`.", - "minimum": 0, - "title": "Departed Endpoints", - "type": "integer" -} - removed
Output schema / properties / domain_id / descriptionRemoved value: -"DDS domain observed." - removed
Output schema / properties / domain_id / titleRemoved value: -"Domain Id" - removed
Output schema / properties / endpoints / titleRemoved value: -"Endpoints" - added
Output schema / properties / excluded_observer_endpoint_countAdded value: +{ + "default": 0, + "description": "TopicForge's own endpoints left out of `endpoints` (counted in `total`).", + "minimum": 0, + "type": "integer" +} - removed
Output schema / properties / excluded_observer_endpointsRemoved value: -{ - "default": 0, - "description": "Endpoints of TopicForge's own observer participant left out of `endpoints` (they are counted in `total_discovered`). Explains `total_discovered` vs `returned` together with the filters.", - "minimum": 0, - "title": "Excluded Observer Endpoints", - "type": "integer" -} - added
Output schema / properties / fieldsAdded value: +{ + "default": "full", + "description": "Detail level served: `compact` drops per-endpoint QoS detail, type id and notes.", + "enum": [ + "full", + "compact" + ], + "type": "string" +} - added
Output schema / properties / hidden_internal_endpoint_countAdded value: +{ + "default": 0, + "description": "Service/action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`) left out while `include_internal` is false.", + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / hintsAdded value: +{ + "description": "Facts true of every endpoint, stated once (e.g. liveness is not observed).", + "items": { + "type": "string" + }, + "type": "array" +} - changed
Output schema / properties / mode_effective / descriptionPrevious value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)." - removed
Output schema / properties / mode_effective / titleRemoved value: -"Mode Effective" - changed
Output schema / properties / note / descriptionPrevious value: -"Hint when a `topic` filter matched nothing: names the closest known topics."New value: +"Set when a `topic` filter matched nothing: the closest known topics." - removed
Output schema / properties / note / titleRemoved value: -"Note" - removed
Output schema / properties / observer_guid / titleRemoved value: -"Observer Guid" - removed
Output schema / properties / returned / descriptionRemoved value: -"Length of `endpoints`." - removed
Output schema / properties / returned / titleRemoved value: -"Returned" - removed
Output schema / properties / snapshot_ns / titleRemoved value: -"Snapshot Ns" - added
Output schema / properties / totalAdded value: +{ + "description": "Endpoints in the discovery cache before any filter.", + "minimum": 0, + "type": "integer" +} - removed
Output schema / properties / total_discoveredRemoved value: -{ - "description": "Endpoints in the discovery cache before any filter.", - "minimum": 0, - "title": "Total Discovered", - "type": "integer" -} - removed
Output schema / properties / truncated / titleRemoved value: -"Truncated" - changed
Output schema / requiredPrevious value: -[ - "domain_id", - "snapshot_ns", - "endpoints", - "by_topic", - "total_discovered", - "returned", - "truncated", - "mode_effective" -]New value: +[ + "domain_id", + "snapshot_ns", + "endpoints", + "by_topic", + "total", + "returned", + "truncated", + "mode_effective" +]
- Added
list_nodes - Changed
list_participants52 fields changed- changed
Input schema / properties / domain_id / descriptionPrevious value: -"Accepted for compatibility (0..232). TopicForge observes the domain it joined at startup (TOPICFORGE_DDS_DOMAIN_ID); this argument does not switch domains, and the response `domain_id` says which one was observed."New value: +"Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value." - removed
Input schema / properties / domain_id / titleRemoved value: -"Domain Id" - removed
Input schema / titleRemoved value: -"list_participantsArguments" - added
Output schema / $defs / NameLifecycleAdded value: +{ + "additionalProperties": false, + "properties": { + "departures": { + "description": "Times a participant with this name was lost.", + "minimum": 0, + "type": "integer" + }, + "lives": { + "description": "Distinct GUIDs that carried this name; restarts = `lives` - 1.", + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "lives", + "departures" + ], + "type": "object" +} - removed
Output schema / $defs / ParticipantInfo / descriptionRemoved value: -"DDS participant discovered on the configured domain.\n\nCarries identity (`guid`, `name`, `vendor`) plus lifecycle fields\n(`first_seen_ns`, `last_seen_ns`, `status`, `seen_count`)." - changed
Output schema / $defs / ParticipantInfo / properties / announced_ns / descriptionPrevious value: -"Nanoseconds since epoch (DDS source timestamp of the participant's most recent announcement, taken from the announcing side's clock; the local clock when the announcer sent none). `None` when the backend does not expose it. Distinct from `first_seen_ns` / `last_seen_ns`, which are TopicForge's own local clock."New value: +"Source timestamp of the latest announcement, on the announcer's clock; `None` if unavailable." - removed
Output schema / $defs / ParticipantInfo / properties / announced_ns / titleRemoved value: -"Announced Ns" - removed
Output schema / $defs / ParticipantInfo / properties / domain_id / descriptionRemoved value: -"DDS domain id the participant is bound to." - removed
Output schema / $defs / ParticipantInfo / properties / domain_id / titleRemoved value: -"Domain Id" - changed
Output schema / $defs / ParticipantInfo / properties / first_seen_ns / descriptionPrevious value: -"Wall-clock timestamp (nanoseconds since epoch) of the **first** discovery sample TopicForge observed for this participant. `None` when the adapter does not track lifecycle."New value: +"Wall-clock ns of the first discovery sample seen; `None` without lifecycle tracking." - removed
Output schema / $defs / ParticipantInfo / properties / first_seen_ns / titleRemoved value: -"First Seen Ns" - removed
Output schema / $defs / ParticipantInfo / properties / guid / descriptionRemoved value: -"DDS Global Unique Identifier of the participant: hex string, stable across discovery events within a single deployment." - removed
Output schema / $defs / ParticipantInfo / properties / guid / titleRemoved value: -"Guid" - removed
Output schema / $defs / ParticipantInfo / properties / hostname / descriptionRemoved value: -"Hostname announced in discovery (Cyclone `__Hostname` property). `None` when the vendor does not announce it." - removed
Output schema / $defs / ParticipantInfo / properties / hostname / titleRemoved value: -"Hostname" - changed
Output schema / $defs / ParticipantInfo / properties / is_observer / descriptionPrevious value: -"True for TopicForge's own read-only observer participant, which joins the domain to watch it and appears in this list; false for every other participant."New value: +"True for TopicForge's own read-only observer participant." - removed
Output schema / $defs / ParticipantInfo / properties / is_observer / titleRemoved value: -"Is Observer" - changed
Output schema / $defs / ParticipantInfo / properties / last_seen_ns / descriptionPrevious value: -"Wall-clock timestamp (nanoseconds since epoch) of the **most recent** discovery sample observed. Used by `participant_events` to distinguish currently-active participants from those that left the bus. `None` when the adapter does not track lifecycle."New value: +"Wall-clock ns of the latest discovery sample; `None` without lifecycle tracking." - removed
Output schema / $defs / ParticipantInfo / properties / last_seen_ns / titleRemoved value: -"Last Seen Ns" - changed
Output schema / $defs / ParticipantInfo / properties / lost_ns / descriptionPrevious value: -"Nanoseconds since epoch of the moment the participant left, once `status` is `left`; `None` while it is active. How precise it is depends on `lost_time_source`. It is an upper bound of when the participant died: after a clean shutdown it is the exact leave time, after a crash it is when the lease expired, and the two cannot be told apart. The process died at or before `lost_ns`, and at most one lease earlier (10 s Cyclone default, 20 s Fast, 100 s RTI)."New value: +"Once `left`: ns upper bound of the death (exact after a clean shutdown; after a crash the lease expiry, up to one lease earlier: 10 s Cyclone, 20 s Fast, 100 s RTI). `None` while active." - removed
Output schema / $defs / ParticipantInfo / properties / lost_ns / titleRemoved value: -"Lost Ns" - changed
Output schema / $defs / ParticipantInfo / properties / lost_time_source / descriptionPrevious value: -"Where `lost_ns` comes from: `dds_source_timestamp` (timestamp carried by the discovery dispose) or `observed_local` (the moment TopicForge noticed, the weakest). `None` while active."New value: +"Origin of `lost_ns`: `dds_source_timestamp` or `observed_local` (weakest); `None` while active." - removed
Output schema / $defs / ParticipantInfo / properties / lost_time_source / titleRemoved value: -"Lost Time Source" - removed
Output schema / $defs / ParticipantInfo / properties / mode_effectiveRemoved value: -{ - "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.", - "enum": [ - "mock", - "live" - ], - "title": "Mode Effective", - "type": "string" -} - changed
Output schema / $defs / ParticipantInfo / properties / name / descriptionPrevious value: -"Participant name the application announced through the standard DDS EntityName QoS (PID_ENTITY_NAME in RTPS discovery), for example `lidar_driver`. `None` when the application did not set one (Dust DDS cannot) or the backend does not expose it."New value: +"Announced EntityName (e.g. `lidar_driver`); `None` when the application set none (Dust DDS cannot)." - removed
Output schema / $defs / ParticipantInfo / properties / name / titleRemoved value: -"Name" - added
Output schema / $defs / ParticipantInfo / properties / node_namesAdded value: +{ + "description": "Fully qualified ROS 2 node names sharing this participant; empty when `node_names_source` is `none`.", + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / $defs / ParticipantInfo / properties / node_names_sourceAdded value: +{ + "default": "none", + "description": "`ros_discovery_info`, `ros2_cli` or `none` (no link made).", + "enum": [ + "ros_discovery_info", + "ros2_cli", + "none" + ], + "type": "string" +} - changed
Output schema / $defs / ParticipantInfo / properties / seen_count / descriptionPrevious value: -"Number of distinct discovery samples observed for this participant across all calls to `list_participants` during this server's lifetime. Starts at 1 and increments on each observation."New value: +"Discovery observations of this GUID in this server's lifetime. Not a restart count: a restart is a new GUID (see `restarts_by_name`)." - removed
Output schema / $defs / ParticipantInfo / properties / seen_count / titleRemoved value: -"Seen Count" - changed
Output schema / $defs / ParticipantInfo / properties / status / descriptionPrevious value: -"Lifecycle status. `active` means TopicForge has a recent discovery sample for this GUID and the bus has not signalled removal. `left` means the participant was observed earlier but has since disappeared (a Fast DDS `REMOVED` callback or a Cyclone polling delta). `unknown` when the adapter does not track lifecycle."New value: +"`active`, `left` (seen earlier, since gone) or `unknown` (no lifecycle tracking)." - removed
Output schema / $defs / ParticipantInfo / properties / status / titleRemoved value: -"Status" - changed
Output schema / $defs / ParticipantInfo / properties / vendor / descriptionPrevious value: -"DDS implementation that announced this participant, decoded from the OMG-RTPS `vendor_id` field on the discovery sample. `cyclone` (Eclipse Cyclone DDS), `fast` (eProsima Fast DDS), `rti` (RTI Connext), `rti_micro` (RTI Connext Micro), `opensplice` (ADLink OpenSplice), `opendds` (OCI OpenDDS), `coredx` (Twin Oaks CoreDX), `intercom` (Kongsberg InterCOM), `dust` (S2E Dust DDS). `mock` is reserved for synthetic fixtures. `unknown` means the vendor could not be determined: TopicForge reads it from the vendor prefix of the participant GUID, and some vendors (for example Dust DDS and RTI Connext) do not put their vendor id there, while the Cyclone Python binding does not expose the RTPS header vendor id. The participant is still reported; see `vendor_source`. Any conformant DDS-RTPS participant should appear through standard discovery; Cyclone DDS and Dust DDS have been observed on a live bus. See `docs/dds-interop-matrix.md`."New value: +"DDS implementation read from the GUID vendor prefix: cyclone, fast, rti, rti_micro, opensplice, opendds, coredx, intercom, dust, mock or `unknown` (Dust DDS and RTI Connext do not put theirs there); see `vendor_source`." - removed
Output schema / $defs / ParticipantInfo / properties / vendor / titleRemoved value: -"Vendor" - changed
Output schema / $defs / ParticipantInfo / properties / vendor_source / descriptionPrevious value: -"Where `vendor` came from: `guid_prefix` when it was read from the vendor prefix of the participant GUID, `none` when the vendor is unknown (or synthetic in mock mode)."New value: +"`guid_prefix` when `vendor` was read from the GUID, else `none`." - removed
Output schema / $defs / ParticipantInfo / properties / vendor_source / titleRemoved value: -"Vendor Source" - changed
Output schema / $defs / ParticipantInfo / requiredPrevious value: -[ - "guid", - "vendor", - "domain_id", - "mode_effective" -]New value: +[ + "guid", + "vendor", + "domain_id" +] - removed
Output schema / $defs / ParticipantInfo / titleRemoved value: -"ParticipantInfo" - added
Output schema / additionalPropertiesAdded value: +false - added
Output schema / descriptionAdded value: +"Envelope returned by `list_participants`." - added
Output schema / properties / domain_idAdded value: +{ + "description": "DDS domain observed (joined at startup).", + "maximum": 232, + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / mode_effectiveAdded value: +{ + "description": "`live` or `mock` (fixtures).", + "enum": [ + "mock", + "live" + ], + "type": "string" +} - added
Output schema / properties / noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence of context, or `None`." +} - added
Output schema / properties / participantsAdded value: +{ + "items": { + "$ref": "#/$defs/ParticipantInfo" + }, + "type": "array" +} - added
Output schema / properties / restarts_by_nameAdded value: +{ + "additionalProperties": { + "$ref": "#/$defs/NameLifecycle" + }, + "description": "Names seen with several GUIDs or lost at least once (absent: ran once and stayed). A restart is a new GUID with the same name.", + "type": "object" +} - added
Output schema / properties / restarts_noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Set when unnamed participants were left out of `restarts_by_name`." +} - removed
Output schema / properties / resultRemoved value: -{ - "items": { - "$ref": "#/$defs/ParticipantInfo" - }, - "title": "Result", - "type": "array" -} - added
Output schema / properties / returnedAdded value: +{ + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / totalAdded value: +{ + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / truncatedAdded value: +{ + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "result" -]New value: +[ + "participants", + "returned", + "total", + "truncated", + "domain_id", + "mode_effective" +] - changed
Output schema / titlePrevious value: -"list_participantsOutput"New value: +"ParticipantListing"
- Changed
list_topics15 fields changed- removed
Input schema / titleRemoved value: -"list_topicsArguments" - removed
Output schema / $defs / QosProfileRemoved value: -{ - "additionalProperties": false, - "description": "QoS profile of one DDS endpoint (reader or writer).\n\nCovers the policies behind most \"subscriber receives nothing\" cases.\nVendor-specific extensions are ignored.", - "properties": { - "data_representation": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Data representations the endpoint accepts or offers, from `XCDR1` and `XCDR2`. `None` when not announced.", - "title": "Data Representation" - }, - "deadline_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Deadline QoS in nanoseconds. `None` means no deadline (infinite, the DDS default). A reader deadline tighter (smaller) than a writer deadline is incompatible: the writer cannot guarantee the reader's promise.", - "title": "Deadline Ns" - }, - "destination_order": { - "anyOf": [ - { - "enum": [ - "BY_RECEPTION_TIMESTAMP", - "BY_SOURCE_TIMESTAMP" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "DestinationOrder QoS. `None` when not announced.", - "title": "Destination Order" - }, - "durability": { - "description": "DDS Durability QoS. `VOLATILE` writers do not retain samples for late joiners; `TRANSIENT_LOCAL` writers do. A `TRANSIENT_LOCAL` reader cannot match a `VOLATILE` writer.", - "enum": [ - "VOLATILE", - "TRANSIENT_LOCAL", - "TRANSIENT", - "PERSISTENT" - ], - "title": "Durability", - "type": "string" - }, - "history": { - "anyOf": [ - { - "enum": [ - "KEEP_LAST", - "KEEP_ALL" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "DDS History QoS. `KEEP_LAST` keeps a bounded ring buffer of size `history_depth`; `KEEP_ALL` keeps every sample (memory permitting). Mixed `KEEP_ALL` reader with `KEEP_LAST` writer is risky but not strictly incompatible. `None` when it is not known: DDS discovery does not carry History, so it is reported only for TopicForge's own endpoints and for Cyclone DDS peers that set something other than the default (see `history_note`).", - "title": "History" - }, - "history_depth": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Depth for `KEEP_LAST`. `None` for `KEEP_ALL` or when `history` is `None`.", - "title": "History Depth" - }, - "history_note": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Set when `history` was left out because discovery did not announce it.", - "title": "History Note" - }, - "latency_budget_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "LatencyBudget QoS in nanoseconds (a delivery-delay hint). `0` is the DDS default. `None` means infinite or not announced.", - "title": "Latency Budget Ns" - }, - "liveliness_kind": { - "anyOf": [ - { - "enum": [ - "AUTOMATIC", - "MANUAL_BY_PARTICIPANT", - "MANUAL_BY_TOPIC" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Liveliness QoS kind. `AUTOMATIC` is asserted by the middleware; the two `MANUAL_*` kinds need the application to write or assert liveliness, and the endpoint is declared not alive when it does not within `liveliness_lease_ns`. `None` when not announced.", - "title": "Liveliness Kind" - }, - "liveliness_lease_ns": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Liveliness lease duration in nanoseconds. `None` means infinite (the DDS default) or not announced.", - "title": "Liveliness Lease Ns" - }, - "ownership_kind": { - "anyOf": [ - { - "enum": [ - "SHARED", - "EXCLUSIVE" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Ownership QoS kind. With `EXCLUSIVE`, only the writer with the highest `ownership_strength` delivers samples to a reader. `None` when not announced.", - "title": "Ownership Kind" - }, - "ownership_strength": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Ownership strength of a writer (`EXCLUSIVE` ownership). `None` when not announced.", - "title": "Ownership Strength" - }, - "partitions": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Partition names the endpoint's Publisher or Subscriber belongs to. No Partition policy is the default partition, reported as `[\"\"]` (never an empty list). Endpoints only match when their partitions intersect. `None` only when the backend cannot read partitions at all (unknown, not the default).", - "title": "Partitions" - }, - "reliability": { - "description": "DDS Reliability QoS. `RELIABLE` retries lost samples; `BEST_EFFORT` does not. A `RELIABLE` reader cannot match a `BEST_EFFORT` writer.", - "enum": [ - "RELIABLE", - "BEST_EFFORT" - ], - "title": "Reliability", - "type": "string" - }, - "unknown_policies": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Policies the endpoint announced but whose value could not be read (for example `Deadline`). Their duration fields are `None` here, which does NOT mean infinite for these: the scan does not compare them.", - "title": "Unknown Policies" - } - }, - "required": [ - "reliability", - "durability" - ], - "title": "QosProfile", - "type": "object" -} - removed
Output schema / $defs / TopicInfoRemoved value: -{ - "additionalProperties": false, - "description": "Description of a single ROS2 topic.\n\nCarries optional DDS-side enrichment fields when the active middleware\nbackend can resolve them (CycloneDDS / Fast DDS). The ROS2 CLI adapter and\nthe mock ROS2 path leave them `None`.", - "properties": { - "message_type": { - "description": "ROS2 message type, e.g. `geometry_msgs/msg/Twist`.", - "title": "Message Type", - "type": "string" - }, - "mode_effective": { - "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.", - "enum": [ - "mock", - "live" - ], - "title": "Mode Effective", - "type": "string" - }, - "name": { - "description": "Fully qualified topic name, e.g. `/cmd_vel`.", - "title": "Name", - "type": "string" - }, - "publisher_count": { - "description": "Publishers known to the graph.", - "minimum": 0, - "title": "Publisher Count", - "type": "integer" - }, - "qos_durability": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Durability announced by the topic's publishers: `volatile`, `transient_local` (late subscribers receive the last samples; typical of latched topics such as `/tf_static`), or `mixed` when publishers disagree. `null` when unknown, with the same rules as `qos_reliability`.", - "title": "Qos Durability" - }, - "qos_profile": { - "anyOf": [ - { - "$ref": "#/$defs/QosProfile" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Effective DDS QoS profile for this topic when resolvable. `None` from the ROS2 CLI adapter or when the DDS module is inactive. The DDS module populates this on a best-effort basis (picks one representative endpoint if reader/writer QoS differ)." - }, - "qos_reliability": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Reliability announced by the topic's publishers: `reliable`, `best_effort`, or `mixed` when publishers disagree. `null` when unknown: the topic has no publisher, or the value was not read (`list_topics` does not read QoS; `get_topic_info` does).", - "title": "Qos Reliability" - }, - "reader_count": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "DDS reader-endpoint count when the active backend can resolve endpoint-level info (Cyclone / Fast DDS). `None` from the ROS2 CLI adapter or when the DDS module is inactive.", - "title": "Reader Count" - }, - "subscriber_count": { - "description": "Subscribers known to the graph.", - "minimum": 0, - "title": "Subscriber Count", - "type": "integer" - }, - "writer_count": { - "anyOf": [ - { - "minimum": 0, - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "description": "DDS writer-endpoint count when the active backend can resolve endpoint-level info. `None` from the ROS2 CLI adapter or when the DDS module is inactive.", - "title": "Writer Count" - } - }, - "required": [ - "name", - "message_type", - "publisher_count", - "subscriber_count", - "mode_effective" - ], - "title": "TopicInfo", - "type": "object" -} - added
Output schema / $defs / TopicListItemAdded value: +{ + "additionalProperties": false, + "properties": { + "message_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher_count": { + "minimum": 0, + "type": "integer" + }, + "subscriber_count": { + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "name", + "message_type", + "publisher_count", + "subscriber_count" + ], + "type": "object" +} - added
Output schema / additionalPropertiesAdded value: +false - added
Output schema / descriptionAdded value: +"Envelope returned by `list_topics`." - added
Output schema / properties / mode_effectiveAdded value: +{ + "description": "`live` or `mock` (fixtures).", + "enum": [ + "mock", + "live" + ], + "type": "string" +} - added
Output schema / properties / noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence of context, or `None`." +} - removed
Output schema / properties / resultRemoved value: -{ - "items": { - "$ref": "#/$defs/TopicInfo" - }, - "title": "Result", - "type": "array" -} - added
Output schema / properties / returnedAdded value: +{ + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / topicsAdded value: +{ + "items": { + "$ref": "#/$defs/TopicListItem" + }, + "type": "array" +} - added
Output schema / properties / totalAdded value: +{ + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / truncatedAdded value: +{ + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "result" -]New value: +[ + "topics", + "returned", + "total", + "truncated", + "mode_effective" +] - changed
Output schema / titlePrevious value: -"list_topicsOutput"New value: +"TopicListing"
- Changed
participant_events43 fields changed- changed
Input schema / properties / domain_id / descriptionPrevious value: -"Accepted for compatibility (0..232). TopicForge observes the domain it joined at startup (TOPICFORGE_DDS_DOMAIN_ID); this argument does not switch domains, and the response `domain_id` says which one was observed."New value: +"Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value." - removed
Input schema / properties / domain_id / titleRemoved value: -"Domain Id" - added
Input schema / properties / lookback_sAdded value: +{ + "default": 300, + "description": "Window in seconds, 1..86400, default 300. A larger window may hit the 200-event cap.", + "maximum": 86400, + "minimum": 1, + "type": "integer" +} - removed
Input schema / properties / lookback_secondsRemoved value: -{ - "default": 300, - "description": "Window (in seconds) over which to return events. Defaults to 300 (5 minutes). Range: 1..86400 (1 second to 24 hours). Larger windows may hit the 200-event cap: narrow the window or filter on `domain_id` when that happens.", - "maximum": 86400, - "minimum": 1, - "title": "Lookback Seconds", - "type": "integer" -} - removed
Input schema / titleRemoved value: -"participant_eventsArguments" - added
Output schema / $defs / NameLifecycleAdded value: +{ + "additionalProperties": false, + "properties": { + "departures": { + "description": "Times a participant with this name was lost.", + "minimum": 0, + "type": "integer" + }, + "lives": { + "description": "Distinct GUIDs that carried this name; restarts = `lives` - 1.", + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "lives", + "departures" + ], + "type": "object" +} - removed
Output schema / $defs / ParticipantEvent / descriptionRemoved value: -"A point-in-time lifecycle event (discovered or lost) for a DDS participant.\n\n`ParticipantInfo` is the current state; this is one event in its history.\nReturned by `participant_events`." - added
Output schema / $defs / ParticipantEvent / properties / age_sAdded value: +{ + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Seconds between `timestamp_ns` and this response; `None` in mock mode." +} - removed
Output schema / $defs / ParticipantEvent / properties / domain_id / descriptionRemoved value: -"DDS domain id the event occurred on." - removed
Output schema / $defs / ParticipantEvent / properties / domain_id / titleRemoved value: -"Domain Id" - changed
Output schema / $defs / ParticipantEvent / properties / event_type / descriptionPrevious value: -"`discovered` when the participant first joined the bus (or rejoined after leaving). `lost` when TopicForge observed a `REMOVED` callback (Fast DDS) or a polling delta where the GUID no longer appears in the DCPSParticipant builtin reader snapshot (Cyclone). The transition is reported once per state change."New value: +"`discovered` (joined or rejoined) or `lost` (removed, or gone from discovery); reported once per change." - removed
Output schema / $defs / ParticipantEvent / properties / event_type / titleRemoved value: -"Event Type" - removed
Output schema / $defs / ParticipantEvent / properties / guid / descriptionRemoved value: -"DDS Global Unique Identifier of the participant involved." - removed
Output schema / $defs / ParticipantEvent / properties / guid / titleRemoved value: -"Guid" - removed
Output schema / $defs / ParticipantEvent / properties / hostname / descriptionRemoved value: -"Hostname announced by the participant when known, else `None`." - removed
Output schema / $defs / ParticipantEvent / properties / hostname / titleRemoved value: -"Hostname" - removed
Output schema / $defs / ParticipantEvent / properties / mode_effectiveRemoved value: -{ - "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.", - "enum": [ - "mock", - "live" - ], - "title": "Mode Effective", - "type": "string" -} - removed
Output schema / $defs / ParticipantEvent / properties / name / descriptionRemoved value: -"Participant name (EntityName QoS) when known, else `None`." - removed
Output schema / $defs / ParticipantEvent / properties / name / titleRemoved value: -"Name" - changed
Output schema / $defs / ParticipantEvent / properties / observed_ns / descriptionPrevious value: -"Local wall-clock time (ns since epoch) at which TopicForge noticed the event. Always at or after `timestamp_ns` when the latter is DDS-derived. `None` when not tracked."New value: +"Local ns when TopicForge noticed the event, never before a DDS-derived `timestamp_ns`; `None` when untracked." - removed
Output schema / $defs / ParticipantEvent / properties / observed_ns / titleRemoved value: -"Observed Ns" - changed
Output schema / $defs / ParticipantEvent / properties / time_source / descriptionPrevious value: -"Where `timestamp_ns` comes from. `dds_source_timestamp`: the DDS timestamp of the discovery announcement or dispose. For a `lost` event it is an upper bound: exact after a clean shutdown, the lease expiry after a crash (the process died up to one lease earlier: 10 s Cyclone default, 20 s Fast, 100 s RTI), and the two cannot be told apart. `observed_local`: the moment TopicForge noticed (weakest)."New value: +"`dds_source_timestamp` (DDS timestamp of the announcement or dispose; for `lost` an upper bound of the death) or `observed_local` (when TopicForge noticed, weakest)." - removed
Output schema / $defs / ParticipantEvent / properties / time_source / titleRemoved value: -"Time Source" - changed
Output schema / $defs / ParticipantEvent / properties / timestamp_ns / descriptionPrevious value: -"Wall-clock timestamp (nanoseconds since epoch) when TopicForge captured the event. For Fast DDS this is when the listener callback ran; for Cyclone this is when the polling delta was computed; for mock fixtures this is a deterministic anchor."New value: +"Wall-clock ns of the event; its origin is `time_source`." - removed
Output schema / $defs / ParticipantEvent / properties / timestamp_ns / titleRemoved value: -"Timestamp Ns" - removed
Output schema / $defs / ParticipantEvent / properties / vendor / descriptionRemoved value: -"DDS implementation tag for the participant involved, decoded the same way as `ParticipantInfo.vendor`." - removed
Output schema / $defs / ParticipantEvent / properties / vendor / titleRemoved value: -"Vendor" - changed
Output schema / $defs / ParticipantEvent / requiredPrevious value: -[ - "guid", - "event_type", - "vendor", - "timestamp_ns", - "domain_id", - "mode_effective" -]New value: +[ + "guid", + "event_type", + "vendor", + "timestamp_ns", + "domain_id" +] - removed
Output schema / $defs / ParticipantEvent / titleRemoved value: -"ParticipantEvent" - added
Output schema / additionalPropertiesAdded value: +false - added
Output schema / descriptionAdded value: +"Envelope returned by `participant_events`." - added
Output schema / properties / domain_idAdded value: +{ + "description": "DDS domain observed (joined at startup).", + "maximum": 232, + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / eventsAdded value: +{ + "items": { + "$ref": "#/$defs/ParticipantEvent" + }, + "type": "array" +} - added
Output schema / properties / mode_effectiveAdded value: +{ + "description": "`live` or `mock` (fixtures).", + "enum": [ + "mock", + "live" + ], + "type": "string" +} - added
Output schema / properties / noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence of context, or `None`." +} - added
Output schema / properties / restarts_by_nameAdded value: +{ + "additionalProperties": { + "$ref": "#/$defs/NameLifecycle" + }, + "description": "Names seen with several GUIDs or lost at least once (absent: ran once and stayed). A restart is a new GUID with the same name. Window only.", + "type": "object" +} - added
Output schema / properties / restarts_noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Set when unnamed participants were left out of `restarts_by_name`." +} - removed
Output schema / properties / resultRemoved value: -{ - "items": { - "$ref": "#/$defs/ParticipantEvent" - }, - "title": "Result", - "type": "array" -} - added
Output schema / properties / returnedAdded value: +{ + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / totalAdded value: +{ + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when cut to the 200-event cap.", + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "result" -]New value: +[ + "events", + "returned", + "total", + "truncated", + "domain_id", + "mode_effective" +] - changed
Output schema / titlePrevious value: -"participant_eventsOutput"New value: +"ParticipantEventListing"
- Changed
peek_bag_samples51 fields changed- changed
Input schema / properties / count / descriptionPrevious value: -"Maximum number of recent messages to return. Defaults to 5; silently clamped to 50 (the hard cap that keeps tool output bounded; read it from `health_check.max_sample_count`). Negative values raise an error. The returned `SampleResult.count` reflects the actual number of samples produced: it can be lower than the request (empty topic, timeout, mock fixture shorter than requested)."New value: +"Messages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower." - removed
Input schema / properties / count / titleRemoved value: -"Count" - changed
Input schema / properties / path / descriptionPrevious value: -"Path to a bag: a file ending in `.mcap` or `.db3`, or a `rosbag2_*` directory. `peek_bag_samples` also reads ROS 1 `.bag` files; `analyze_bag` does not (`ros2 bag info` cannot open them). Leading/trailing whitespace is stripped. Null bytes and otherwise malformed filesystem paths are rejected. Existence and bag format are validated by the live adapter (mock mode accepts any well-formed path)."New value: +"Bag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed path." - removed
Input schema / properties / path / titleRemoved value: -"Path" - changed
Input schema / properties / topic / descriptionPrevious value: -"Fully qualified ROS2 topic name starting with `/`, e.g. `/cmd_vel` or `/camera/image_raw`. Each `/`-separated segment must start with a letter or underscore and contain only letters, digits, and underscores; everything else (whitespace, quotes, shell metacharacters, `//`, trailing `/`) is rejected before reaching the `ros2` CLI."New value: +"Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI." - removed
Input schema / properties / topic / titleRemoved value: -"Topic" - removed
Input schema / titleRemoved value: -"peek_bag_samplesArguments" - added
Output schema / $defs / ImageSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "data_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Pixel buffer size in bytes (see `data_length_basis`)." + }, + "data_length_basis": { + "anyOf": [ + { + "enum": [ + "payload", + "step_x_height" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`payload` (counted) or `step_x_height` (array cut, so step times height)." + }, + "encoding": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Pixel encoding, e.g. `rgb8`." + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` of the camera." + }, + "height": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Pixels." + }, + "is_bigendian": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "Big-endian pixels." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when partial; else `null`." + }, + "step": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Row length, bytes." + }, + "summary_type": { + "const": "image", + "type": "string" + }, + "width": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Pixels." + } + }, + "required": [ + "summary_type", + "frame_id", + "width", + "height", + "encoding", + "step", + "is_bigendian", + "data_length", + "data_length_basis" + ], + "type": "object" +} - added
Output schema / $defs / ImuSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "angular_velocity_norm": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Norm of `angular_velocity`, rad/s." + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` of the IMU." + }, + "linear_acceleration_norm": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Norm of `linear_acceleration`, m/s^2." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when a value is unusable; else `null`." + }, + "orientation_valid": { + "description": "False when the IMU gives no orientation: `roll`, `pitch`, `yaw` are `null`.", + "type": "boolean" + }, + "pitch": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Rotation about y, radians." + }, + "roll": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Rotation about x, radians." + }, + "summary_type": { + "const": "imu", + "type": "string" + }, + "yaw": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Rotation about z, radians." + } + }, + "required": [ + "summary_type", + "frame_id", + "orientation_valid", + "roll", + "pitch", + "yaw", + "angular_velocity_norm", + "linear_acceleration_norm" + ], + "type": "object" +} - added
Output schema / $defs / LaserScanSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "angle_increment": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Angle between beams, rad." + }, + "angle_max": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Bearing of the last beam, rad." + }, + "angle_min": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Bearing of beam 0, rad." + }, + "beam_count": { + "description": "Beams in `ranges`.", + "minimum": 0, + "type": "integer" + }, + "closest_obstacle": { + "anyOf": [ + { + "$ref": "#/$defs/ScanReturn" + }, + { + "type": "null" + } + ], + "description": "Closest valid return of the scan; `null` with `note`." + }, + "finite_count": { + "description": "Beams with a finite range.", + "minimum": 0, + "type": "integer" + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id`; every angle is in this sensor frame, not the base frame." + }, + "inf_count": { + "description": "Beams at `+inf`.", + "minimum": 0, + "type": "integer" + }, + "nan_count": { + "description": "Beams at `nan`.", + "minimum": 0, + "type": "integer" + }, + "neg_inf_count": { + "description": "Beams at `-inf`.", + "minimum": 0, + "type": "integer" + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when the summary is partial; else `null`." + }, + "out_of_range_count": { + "description": "Finite ranges outside `range_min`..`range_max` (not valid).", + "minimum": 0, + "type": "integer" + }, + "range_max": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Largest valid range, m." + }, + "range_min": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Smallest valid range, m." + }, + "sectors": { + "$ref": "#/$defs/ScanSectors", + "description": "Closest valid return per sector." + }, + "summary_type": { + "const": "laser_scan", + "type": "string" + } + }, + "required": [ + "summary_type", + "frame_id", + "beam_count", + "angle_min", + "angle_max", + "angle_increment", + "range_min", + "range_max", + "finite_count", + "inf_count", + "neg_inf_count", + "nan_count", + "out_of_range_count", + "closest_obstacle", + "sectors" + ], + "type": "object" +} - removed
Output schema / $defs / MessageSample / descriptionRemoved value: -"A single sampled message on a topic." - removed
Output schema / $defs / MessageSample / properties / message_type / descriptionRemoved value: -"ROS2 message type of this sample, e.g. `geometry_msgs/msg/Twist`." - removed
Output schema / $defs / MessageSample / properties / message_type / titleRemoved value: -"Message Type" - changed
Output schema / $defs / MessageSample / properties / payload / descriptionPrevious value: -"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded."New value: +"Message fields as nested values; `nan`/`inf` are strings. Live `sample_messages` adds `_truncated_fields` (cut paths) and `_raw_text` (unparsable output)." - removed
Output schema / $defs / MessageSample / properties / payload / titleRemoved value: -"Payload" - changed
Output schema / $defs / MessageSample / properties / received_ns / descriptionPrevious value: -"Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise."New value: +"Wall-clock ns when the `ros2` CLI printed the message (live `sample_messages` only)." - removed
Output schema / $defs / MessageSample / properties / received_ns / titleRemoved value: -"Received Ns" - changed
Output schema / $defs / MessageSample / properties / recorded_ns / descriptionPrevious value: -"Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role."New value: +"Bag record time in ns (`peek_bag_samples` only)." - removed
Output schema / $defs / MessageSample / properties / recorded_ns / titleRemoved value: -"Recorded Ns" - removed
Output schema / $defs / MessageSample / properties / stamp_source / anyOfRemoved value: -[ - { - "enum": [ - "header", - "none", - "recorded" - ], - "type": "string" - }, - { - "type": "null" - } -] - removed
Output schema / $defs / MessageSample / properties / stamp_source / defaultRemoved value: -null - changed
Output schema / $defs / MessageSample / properties / stamp_source / descriptionPrevious value: -"Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say."New value: +"Origin of `timestamp_ns`: `header`, `payload`, `recorded` (bag record time), `dds_source` or `none` (0)." - added
Output schema / $defs / MessageSample / properties / stamp_source / enumAdded value: +[ + "header", + "payload", + "recorded", + "dds_source", + "none" +] - removed
Output schema / $defs / MessageSample / properties / stamp_source / titleRemoved value: -"Stamp Source" - added
Output schema / $defs / MessageSample / properties / stamp_source / typeAdded value: +"string" - added
Output schema / $defs / MessageSample / properties / summaryAdded value: +{ + "anyOf": [ + { + "discriminator": { + "mapping": { + "image": "#/$defs/ImageSummary", + "imu": "#/$defs/ImuSummary", + "laser_scan": "#/$defs/LaserScanSummary", + "odometry": "#/$defs/OdometrySummary", + "point_cloud2": "#/$defs/PointCloud2Summary" + }, + "propertyName": "summary_type" + }, + "oneOf": [ + { + "$ref": "#/$defs/LaserScanSummary" + }, + { + "$ref": "#/$defs/OdometrySummary" + }, + { + "$ref": "#/$defs/ImuSummary" + }, + { + "$ref": "#/$defs/ImageSummary" + }, + { + "$ref": "#/$defs/PointCloud2Summary" + } + ] + }, + { + "type": "null" + } + ], + "default": null, + "description": "Condensed reading of the whole message (see `summary_type`); `null` for other types and DDS peeks." +} - changed
Output schema / $defs / MessageSample / properties / timestamp_ns / descriptionPrevious value: -"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."New value: +"Publisher-clock ns: `header.stamp`, else the time in the body of `Clock`, `TFMessage` or `Log`, else 0 (see `stamp_source`); not the arrival time." - removed
Output schema / $defs / MessageSample / properties / timestamp_ns / titleRemoved value: -"Timestamp Ns" - removed
Output schema / $defs / MessageSample / properties / topic / descriptionRemoved value: -"Fully qualified topic name the sample was taken from, e.g. `/cmd_vel`." - removed
Output schema / $defs / MessageSample / properties / topic / titleRemoved value: -"Topic" - changed
Output schema / $defs / MessageSample / requiredPrevious value: -[ - "topic", - "message_type", - "timestamp_ns" -]New value: +[ + "topic", + "message_type", + "timestamp_ns", + "stamp_source" +] - removed
Output schema / $defs / MessageSample / titleRemoved value: -"MessageSample" - added
Output schema / $defs / OdometrySummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "angular_z": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`twist.twist.angular.z`, rad/s." + }, + "child_frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`child_frame_id` (frame of the twist)." + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` (frame of the pose)." + }, + "linear_speed": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Norm of `twist.twist.linear`, m/s." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when a value is missing; else `null`." + }, + "position_x": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`pose.pose.position.x`, m." + }, + "position_y": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`pose.pose.position.y`, m." + }, + "position_z": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`pose.pose.position.z`, m." + }, + "summary_type": { + "const": "odometry", + "type": "string" + }, + "yaw": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Heading from the pose quaternion, radians." + } + }, + "required": [ + "summary_type", + "frame_id", + "child_frame_id", + "linear_speed", + "angular_z", + "position_x", + "position_y", + "position_z", + "yaw" + ], + "type": "object" +} - added
Output schema / $defs / PointCloud2SummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "fields": { + "description": "Point layout.", + "items": { + "$ref": "#/$defs/PointFieldSummary" + }, + "type": "array" + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` of the cloud." + }, + "height": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Rows (1: unorganized)." + }, + "is_dense": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "True when no point is invalid." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when partial; else `null`." + }, + "point_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "`width` times `height`." + }, + "point_step": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Bytes per point." + }, + "row_step": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Bytes per row." + }, + "summary_type": { + "const": "point_cloud2", + "type": "string" + }, + "width": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Points per row." + } + }, + "required": [ + "summary_type", + "frame_id", + "width", + "height", + "point_count", + "point_step", + "row_step", + "is_dense", + "fields" + ], + "type": "object" +} - added
Output schema / $defs / PointFieldSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Elements per point." + }, + "datatype": { + "description": "Element type, e.g. `FLOAT32`.", + "type": "string" + }, + "name": { + "description": "Field name, e.g. `x`.", + "type": "string" + }, + "offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Byte offset in a point." + } + }, + "required": [ + "name", + "datatype", + "offset", + "count" + ], + "type": "object" +} - added
Output schema / $defs / ScanReturnAdded value: +{ + "additionalProperties": false, + "properties": { + "beam_index": { + "description": "Index in `ranges`.", + "minimum": 0, + "type": "integer" + }, + "bearing": { + "description": "Beam direction in radians, sensor frame: 0 is +x, counter-clockwise positive.", + "type": "number" + }, + "range": { + "description": "Distance, meters.", + "type": "number" + } + }, + "required": [ + "range", + "bearing", + "beam_index" + ], + "type": "object" +} - added
Output schema / $defs / ScanSectorAdded value: +{ + "additionalProperties": false, + "properties": { + "beam_count": { + "description": "Beams inside the sector (0: not covered).", + "minimum": 0, + "type": "integer" + }, + "closest": { + "anyOf": [ + { + "$ref": "#/$defs/ScanReturn" + }, + { + "type": "null" + } + ], + "description": "Closest valid return in the sector; `null` with `note`." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Why `closest` is `null`; `null` when it is set." + } + }, + "required": [ + "beam_count", + "closest" + ], + "type": "object" +} - added
Output schema / $defs / ScanSectorsAdded value: +{ + "additionalProperties": false, + "properties": { + "front": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings within +-45 degrees of +x." + }, + "left": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings from 45 to 135 degrees." + }, + "rear": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings beyond +-135 degrees." + }, + "right": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings from -135 to -45 degrees." + } + }, + "required": [ + "front", + "left", + "right", + "rear" + ], + "type": "object" +} - added
Output schema / $defs / TopicRateAdded value: +{ + "additionalProperties": false, + "properties": { + "basis": { + "description": "Clock the rate is measured on: `received_ns` (live) or `recorded_ns` (bag).", + "enum": [ + "received_ns", + "recorded_ns" + ], + "type": "string" + }, + "interval_cv": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Interval standard deviation over mean; `null` under 2 messages." + }, + "interval_median_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Median interval; `null` under 2 messages." + }, + "max_gap_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Longest interval; `null` under 2 messages." + }, + "mean_interval_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Mean interval; `null` under 2 messages." + }, + "message_count": { + "description": "Messages the rate is computed from.", + "minimum": 0, + "type": "integer" + }, + "observed_frequency_hz": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Messages per second on `basis`, drain excluded; the rate the caller received. `null` under 2 messages." + }, + "sim_frequency_hz": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Same rate on the messages' own stamps (publisher clock); `null` without advancing stamps." + }, + "startup_burst_count": { + "description": "Leading messages set aside as a start-up drain (live only); still in `message_count`.", + "minimum": 0, + "type": "integer" + }, + "trailing_gap_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Time from the last message to the end of a live observation; else `null`." + }, + "verdict": { + "description": "First match: `silent` (none), `insufficient` (under 5), `intermittent` (an interval over 3x the median), `stable` (cv < 0.2), `jittery` (< 0.5), `erratic`. Describes this window, not a diagnosis.", + "enum": [ + "silent", + "insufficient", + "intermittent", + "stable", + "jittery", + "erratic" + ], + "type": "string" + }, + "verdict_note": { + "description": "The verdict in plain words.", + "type": "string" + }, + "window_s": { + "description": "Observation length, seconds.", + "minimum": 0, + "type": "number" + } + }, + "required": [ + "basis", + "message_count", + "startup_burst_count", + "window_s", + "mean_interval_s", + "interval_median_s", + "max_gap_s", + "interval_cv", + "observed_frequency_hz", + "sim_frequency_hz", + "trailing_gap_s", + "verdict", + "verdict_note" + ], + "type": "object" +} - changed
Output schema / descriptionPrevious value: -"Envelope returned by the `sample_messages` tool."New value: +"Samples returned by `sample_messages`, `peek_dds_samples` and `peek_bag_samples`." - changed
Output schema / properties / count / descriptionPrevious value: -"Number of samples actually returned. May be 0 (no publisher active in live mode, or empty mock fixture), less than the requested count (topic yielded fewer messages within the timeout), or capped by the the silent maximum of 50: request `count > 50` and you will receive at most 50 without warning."New value: +"Number of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50)." - removed
Output schema / properties / count / titleRemoved value: -"Count" - changed
Output schema / properties / mode_effective / descriptionPrevious value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)." - removed
Output schema / properties / mode_effective / titleRemoved value: -"Mode Effective" - changed
Output schema / properties / note / descriptionPrevious value: -"Why `samples` is empty or limited, when the cause is not obvious (for example payload decoding is disabled for DDS user topics). `None` when there is nothing to add."New value: +"Why `samples` is empty or limited, when not obvious; else `None`." - removed
Output schema / properties / note / titleRemoved value: -"Note" - added
Output schema / properties / rateAdded value: +{ + "anyOf": [ + { + "$ref": "#/$defs/TopicRate" + }, + { + "type": "null" + } + ], + "default": null, + "description": "How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10." +} - changed
Output schema / properties / samples / descriptionPrevious value: -"The sampled messages, ordered as received from the backend."New value: +"The samples, in backend order." - removed
Output schema / properties / samples / titleRemoved value: -"Samples" - removed
Output schema / properties / topic / descriptionRemoved value: -"Topic the samples were taken from, echoed from the request." - removed
Output schema / properties / topic / titleRemoved value: -"Topic"
- Changed
peek_dds_samples49 fields changed- changed
Input schema / properties / count / descriptionPrevious value: -"Maximum number of recent messages to return. Defaults to 5; silently clamped to 50 (the hard cap that keeps tool output bounded; read it from `health_check.max_sample_count`). Negative values raise an error. The returned `SampleResult.count` reflects the actual number of samples produced: it can be lower than the request (empty topic, timeout, mock fixture shorter than requested)."New value: +"Messages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower." - removed
Input schema / properties / count / titleRemoved value: -"Count" - changed
Input schema / properties / topic / descriptionPrevious value: -"DDS topic name. Bare DDS names such as `scan` are valid, as are ROS 2 mangled names such as `rt/scan` and the builtin discovery topics `DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`. Letters, digits, `_`, `/` and `::` are allowed; anything else is rejected."New value: +"DDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only." - removed
Input schema / properties / topic / titleRemoved value: -"Topic" - removed
Input schema / titleRemoved value: -"peek_dds_samplesArguments" - added
Output schema / $defs / ImageSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "data_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Pixel buffer size in bytes (see `data_length_basis`)." + }, + "data_length_basis": { + "anyOf": [ + { + "enum": [ + "payload", + "step_x_height" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`payload` (counted) or `step_x_height` (array cut, so step times height)." + }, + "encoding": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Pixel encoding, e.g. `rgb8`." + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` of the camera." + }, + "height": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Pixels." + }, + "is_bigendian": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "Big-endian pixels." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when partial; else `null`." + }, + "step": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Row length, bytes." + }, + "summary_type": { + "const": "image", + "type": "string" + }, + "width": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Pixels." + } + }, + "required": [ + "summary_type", + "frame_id", + "width", + "height", + "encoding", + "step", + "is_bigendian", + "data_length", + "data_length_basis" + ], + "type": "object" +} - added
Output schema / $defs / ImuSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "angular_velocity_norm": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Norm of `angular_velocity`, rad/s." + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` of the IMU." + }, + "linear_acceleration_norm": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Norm of `linear_acceleration`, m/s^2." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when a value is unusable; else `null`." + }, + "orientation_valid": { + "description": "False when the IMU gives no orientation: `roll`, `pitch`, `yaw` are `null`.", + "type": "boolean" + }, + "pitch": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Rotation about y, radians." + }, + "roll": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Rotation about x, radians." + }, + "summary_type": { + "const": "imu", + "type": "string" + }, + "yaw": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Rotation about z, radians." + } + }, + "required": [ + "summary_type", + "frame_id", + "orientation_valid", + "roll", + "pitch", + "yaw", + "angular_velocity_norm", + "linear_acceleration_norm" + ], + "type": "object" +} - added
Output schema / $defs / LaserScanSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "angle_increment": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Angle between beams, rad." + }, + "angle_max": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Bearing of the last beam, rad." + }, + "angle_min": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Bearing of beam 0, rad." + }, + "beam_count": { + "description": "Beams in `ranges`.", + "minimum": 0, + "type": "integer" + }, + "closest_obstacle": { + "anyOf": [ + { + "$ref": "#/$defs/ScanReturn" + }, + { + "type": "null" + } + ], + "description": "Closest valid return of the scan; `null` with `note`." + }, + "finite_count": { + "description": "Beams with a finite range.", + "minimum": 0, + "type": "integer" + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id`; every angle is in this sensor frame, not the base frame." + }, + "inf_count": { + "description": "Beams at `+inf`.", + "minimum": 0, + "type": "integer" + }, + "nan_count": { + "description": "Beams at `nan`.", + "minimum": 0, + "type": "integer" + }, + "neg_inf_count": { + "description": "Beams at `-inf`.", + "minimum": 0, + "type": "integer" + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when the summary is partial; else `null`." + }, + "out_of_range_count": { + "description": "Finite ranges outside `range_min`..`range_max` (not valid).", + "minimum": 0, + "type": "integer" + }, + "range_max": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Largest valid range, m." + }, + "range_min": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Smallest valid range, m." + }, + "sectors": { + "$ref": "#/$defs/ScanSectors", + "description": "Closest valid return per sector." + }, + "summary_type": { + "const": "laser_scan", + "type": "string" + } + }, + "required": [ + "summary_type", + "frame_id", + "beam_count", + "angle_min", + "angle_max", + "angle_increment", + "range_min", + "range_max", + "finite_count", + "inf_count", + "neg_inf_count", + "nan_count", + "out_of_range_count", + "closest_obstacle", + "sectors" + ], + "type": "object" +} - removed
Output schema / $defs / MessageSample / descriptionRemoved value: -"A single sampled message on a topic." - removed
Output schema / $defs / MessageSample / properties / message_type / descriptionRemoved value: -"ROS2 message type of this sample, e.g. `geometry_msgs/msg/Twist`." - removed
Output schema / $defs / MessageSample / properties / message_type / titleRemoved value: -"Message Type" - changed
Output schema / $defs / MessageSample / properties / payload / descriptionPrevious value: -"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded."New value: +"Message fields as nested values; `nan`/`inf` are strings. Live `sample_messages` adds `_truncated_fields` (cut paths) and `_raw_text` (unparsable output)." - removed
Output schema / $defs / MessageSample / properties / payload / titleRemoved value: -"Payload" - changed
Output schema / $defs / MessageSample / properties / received_ns / descriptionPrevious value: -"Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise."New value: +"Wall-clock ns when the `ros2` CLI printed the message (live `sample_messages` only)." - removed
Output schema / $defs / MessageSample / properties / received_ns / titleRemoved value: -"Received Ns" - changed
Output schema / $defs / MessageSample / properties / recorded_ns / descriptionPrevious value: -"Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role."New value: +"Bag record time in ns (`peek_bag_samples` only)." - removed
Output schema / $defs / MessageSample / properties / recorded_ns / titleRemoved value: -"Recorded Ns" - removed
Output schema / $defs / MessageSample / properties / stamp_source / anyOfRemoved value: -[ - { - "enum": [ - "header", - "none", - "recorded" - ], - "type": "string" - }, - { - "type": "null" - } -] - removed
Output schema / $defs / MessageSample / properties / stamp_source / defaultRemoved value: -null - changed
Output schema / $defs / MessageSample / properties / stamp_source / descriptionPrevious value: -"Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say."New value: +"Origin of `timestamp_ns`: `header`, `payload`, `recorded` (bag record time), `dds_source` or `none` (0)." - added
Output schema / $defs / MessageSample / properties / stamp_source / enumAdded value: +[ + "header", + "payload", + "recorded", + "dds_source", + "none" +] - removed
Output schema / $defs / MessageSample / properties / stamp_source / titleRemoved value: -"Stamp Source" - added
Output schema / $defs / MessageSample / properties / stamp_source / typeAdded value: +"string" - added
Output schema / $defs / MessageSample / properties / summaryAdded value: +{ + "anyOf": [ + { + "discriminator": { + "mapping": { + "image": "#/$defs/ImageSummary", + "imu": "#/$defs/ImuSummary", + "laser_scan": "#/$defs/LaserScanSummary", + "odometry": "#/$defs/OdometrySummary", + "point_cloud2": "#/$defs/PointCloud2Summary" + }, + "propertyName": "summary_type" + }, + "oneOf": [ + { + "$ref": "#/$defs/LaserScanSummary" + }, + { + "$ref": "#/$defs/OdometrySummary" + }, + { + "$ref": "#/$defs/ImuSummary" + }, + { + "$ref": "#/$defs/ImageSummary" + }, + { + "$ref": "#/$defs/PointCloud2Summary" + } + ] + }, + { + "type": "null" + } + ], + "default": null, + "description": "Condensed reading of the whole message (see `summary_type`); `null` for other types and DDS peeks." +} - changed
Output schema / $defs / MessageSample / properties / timestamp_ns / descriptionPrevious value: -"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."New value: +"Publisher-clock ns: `header.stamp`, else the time in the body of `Clock`, `TFMessage` or `Log`, else 0 (see `stamp_source`); not the arrival time." - removed
Output schema / $defs / MessageSample / properties / timestamp_ns / titleRemoved value: -"Timestamp Ns" - removed
Output schema / $defs / MessageSample / properties / topic / descriptionRemoved value: -"Fully qualified topic name the sample was taken from, e.g. `/cmd_vel`." - removed
Output schema / $defs / MessageSample / properties / topic / titleRemoved value: -"Topic" - changed
Output schema / $defs / MessageSample / requiredPrevious value: -[ - "topic", - "message_type", - "timestamp_ns" -]New value: +[ + "topic", + "message_type", + "timestamp_ns", + "stamp_source" +] - removed
Output schema / $defs / MessageSample / titleRemoved value: -"MessageSample" - added
Output schema / $defs / OdometrySummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "angular_z": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`twist.twist.angular.z`, rad/s." + }, + "child_frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`child_frame_id` (frame of the twist)." + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` (frame of the pose)." + }, + "linear_speed": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Norm of `twist.twist.linear`, m/s." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when a value is missing; else `null`." + }, + "position_x": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`pose.pose.position.x`, m." + }, + "position_y": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`pose.pose.position.y`, m." + }, + "position_z": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`pose.pose.position.z`, m." + }, + "summary_type": { + "const": "odometry", + "type": "string" + }, + "yaw": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Heading from the pose quaternion, radians." + } + }, + "required": [ + "summary_type", + "frame_id", + "child_frame_id", + "linear_speed", + "angular_z", + "position_x", + "position_y", + "position_z", + "yaw" + ], + "type": "object" +} - added
Output schema / $defs / PointCloud2SummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "fields": { + "description": "Point layout.", + "items": { + "$ref": "#/$defs/PointFieldSummary" + }, + "type": "array" + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` of the cloud." + }, + "height": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Rows (1: unorganized)." + }, + "is_dense": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "True when no point is invalid." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when partial; else `null`." + }, + "point_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "`width` times `height`." + }, + "point_step": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Bytes per point." + }, + "row_step": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Bytes per row." + }, + "summary_type": { + "const": "point_cloud2", + "type": "string" + }, + "width": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Points per row." + } + }, + "required": [ + "summary_type", + "frame_id", + "width", + "height", + "point_count", + "point_step", + "row_step", + "is_dense", + "fields" + ], + "type": "object" +} - added
Output schema / $defs / PointFieldSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Elements per point." + }, + "datatype": { + "description": "Element type, e.g. `FLOAT32`.", + "type": "string" + }, + "name": { + "description": "Field name, e.g. `x`.", + "type": "string" + }, + "offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Byte offset in a point." + } + }, + "required": [ + "name", + "datatype", + "offset", + "count" + ], + "type": "object" +} - added
Output schema / $defs / ScanReturnAdded value: +{ + "additionalProperties": false, + "properties": { + "beam_index": { + "description": "Index in `ranges`.", + "minimum": 0, + "type": "integer" + }, + "bearing": { + "description": "Beam direction in radians, sensor frame: 0 is +x, counter-clockwise positive.", + "type": "number" + }, + "range": { + "description": "Distance, meters.", + "type": "number" + } + }, + "required": [ + "range", + "bearing", + "beam_index" + ], + "type": "object" +} - added
Output schema / $defs / ScanSectorAdded value: +{ + "additionalProperties": false, + "properties": { + "beam_count": { + "description": "Beams inside the sector (0: not covered).", + "minimum": 0, + "type": "integer" + }, + "closest": { + "anyOf": [ + { + "$ref": "#/$defs/ScanReturn" + }, + { + "type": "null" + } + ], + "description": "Closest valid return in the sector; `null` with `note`." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Why `closest` is `null`; `null` when it is set." + } + }, + "required": [ + "beam_count", + "closest" + ], + "type": "object" +} - added
Output schema / $defs / ScanSectorsAdded value: +{ + "additionalProperties": false, + "properties": { + "front": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings within +-45 degrees of +x." + }, + "left": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings from 45 to 135 degrees." + }, + "rear": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings beyond +-135 degrees." + }, + "right": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings from -135 to -45 degrees." + } + }, + "required": [ + "front", + "left", + "right", + "rear" + ], + "type": "object" +} - added
Output schema / $defs / TopicRateAdded value: +{ + "additionalProperties": false, + "properties": { + "basis": { + "description": "Clock the rate is measured on: `received_ns` (live) or `recorded_ns` (bag).", + "enum": [ + "received_ns", + "recorded_ns" + ], + "type": "string" + }, + "interval_cv": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Interval standard deviation over mean; `null` under 2 messages." + }, + "interval_median_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Median interval; `null` under 2 messages." + }, + "max_gap_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Longest interval; `null` under 2 messages." + }, + "mean_interval_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Mean interval; `null` under 2 messages." + }, + "message_count": { + "description": "Messages the rate is computed from.", + "minimum": 0, + "type": "integer" + }, + "observed_frequency_hz": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Messages per second on `basis`, drain excluded; the rate the caller received. `null` under 2 messages." + }, + "sim_frequency_hz": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Same rate on the messages' own stamps (publisher clock); `null` without advancing stamps." + }, + "startup_burst_count": { + "description": "Leading messages set aside as a start-up drain (live only); still in `message_count`.", + "minimum": 0, + "type": "integer" + }, + "trailing_gap_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Time from the last message to the end of a live observation; else `null`." + }, + "verdict": { + "description": "First match: `silent` (none), `insufficient` (under 5), `intermittent` (an interval over 3x the median), `stable` (cv < 0.2), `jittery` (< 0.5), `erratic`. Describes this window, not a diagnosis.", + "enum": [ + "silent", + "insufficient", + "intermittent", + "stable", + "jittery", + "erratic" + ], + "type": "string" + }, + "verdict_note": { + "description": "The verdict in plain words.", + "type": "string" + }, + "window_s": { + "description": "Observation length, seconds.", + "minimum": 0, + "type": "number" + } + }, + "required": [ + "basis", + "message_count", + "startup_burst_count", + "window_s", + "mean_interval_s", + "interval_median_s", + "max_gap_s", + "interval_cv", + "observed_frequency_hz", + "sim_frequency_hz", + "trailing_gap_s", + "verdict", + "verdict_note" + ], + "type": "object" +} - changed
Output schema / descriptionPrevious value: -"Envelope returned by the `sample_messages` tool."New value: +"Samples returned by `sample_messages`, `peek_dds_samples` and `peek_bag_samples`." - changed
Output schema / properties / count / descriptionPrevious value: -"Number of samples actually returned. May be 0 (no publisher active in live mode, or empty mock fixture), less than the requested count (topic yielded fewer messages within the timeout), or capped by the the silent maximum of 50: request `count > 50` and you will receive at most 50 without warning."New value: +"Number of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50)." - removed
Output schema / properties / count / titleRemoved value: -"Count" - changed
Output schema / properties / mode_effective / descriptionPrevious value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)." - removed
Output schema / properties / mode_effective / titleRemoved value: -"Mode Effective" - changed
Output schema / properties / note / descriptionPrevious value: -"Why `samples` is empty or limited, when the cause is not obvious (for example payload decoding is disabled for DDS user topics). `None` when there is nothing to add."New value: +"Why `samples` is empty or limited, when not obvious; else `None`." - removed
Output schema / properties / note / titleRemoved value: -"Note" - added
Output schema / properties / rateAdded value: +{ + "anyOf": [ + { + "$ref": "#/$defs/TopicRate" + }, + { + "type": "null" + } + ], + "default": null, + "description": "How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10." +} - changed
Output schema / properties / samples / descriptionPrevious value: -"The sampled messages, ordered as received from the backend."New value: +"The samples, in backend order." - removed
Output schema / properties / samples / titleRemoved value: -"Samples" - removed
Output schema / properties / topic / descriptionRemoved value: -"Topic the samples were taken from, echoed from the request." - removed
Output schema / properties / topic / titleRemoved value: -"Topic"
- Changed
sample_messages56 fields changed- changed
Input schema / properties / arrays_summary_only / descriptionPrevious value: -"When true, array fields are replaced by a short type and length summary instead of their elements. Use it to inspect the non-array fields of large messages (images, scans, point clouds). Defaults to false."New value: +"When true, arrays become a type-and-length summary: use it to read the non-array fields of large messages. Default false." - removed
Input schema / properties / arrays_summary_only / titleRemoved value: -"Arrays Summary Only" - changed
Input schema / properties / count / descriptionPrevious value: -"Number of messages to wait for. Defaults to 5; at most 50 (a larger request is capped to 50 and `note` says so). 0 returns nothing. `SampleResult.count` is how many arrived: fewer than requested when the topic publishes slowly or the deadline (`timeout_s`) ran out."New value: +"Messages to wait for, default 5, at most 50 (more is capped and `note` says so); 0 returns nothing. Fewer arrive when the topic is slow or `timeout_s` runs out." - removed
Input schema / properties / count / titleRemoved value: -"Count" - changed
Input schema / properties / max_array_length / descriptionPrevious value: -"Longest array, string or bytes value to return in full, 1..65536; longer ones are cut after their first N elements or characters and the field's dotted path is listed in the sample's `_truncated_fields`. Defaults to 128, the `ros2 topic echo` default, which cuts a 541-beam `LaserScan` after 128 ranges. Pass null to return everything in full (large for images and point clouds; a message over the server's size cap, 1 MiB by default, is dropped with a note, and a very large message may not print before the deadline)."New value: +"Longest array, string or bytes value returned in full, 1..65536, default 128 (a 541-beam `LaserScan` is cut to 128 ranges; its `summary` still covers all). Longer values are cut and listed in `_truncated_fields`. Null returns everything (large for images and clouds; a message over 1 MiB is dropped with a note)." - removed
Input schema / properties / max_array_length / titleRemoved value: -"Max Array Length" - changed
Input schema / properties / timeout_s / descriptionPrevious value: -"Seconds to wait for the messages, 1..45, default 10. Bounds the whole call, topic lookup and the `ros2` CLI start-up (a few seconds on a slow machine) included: the call returns within about `timeout_s` + 2 s. Whatever arrived by then is returned with a `note`. Raise it for a topic that publishes slower than 1 Hz. The server handles one call at a time, so a long wait delays other tool calls."New value: +"Seconds to wait, 1..40, default 10, covering topic lookup and CLI start-up. The call returns within `timeout_s` plus a few seconds, with what arrived and a `note`. Raise it for a topic slower than 1 Hz." - changed
Input schema / properties / timeout_s / maximumPrevious value: -45New value: +40 - removed
Input schema / properties / timeout_s / titleRemoved value: -"Timeout S" - changed
Input schema / properties / topic / descriptionPrevious value: -"Fully qualified ROS2 topic name starting with `/`, e.g. `/cmd_vel` or `/camera/image_raw`. Each `/`-separated segment must start with a letter or underscore and contain only letters, digits, and underscores; everything else (whitespace, quotes, shell metacharacters, `//`, trailing `/`) is rejected before reaching the `ros2` CLI."New value: +"Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI." - removed
Input schema / properties / topic / titleRemoved value: -"Topic" - removed
Input schema / titleRemoved value: -"sample_messagesArguments" - added
Output schema / $defs / ImageSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "data_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Pixel buffer size in bytes (see `data_length_basis`)." + }, + "data_length_basis": { + "anyOf": [ + { + "enum": [ + "payload", + "step_x_height" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`payload` (counted) or `step_x_height` (array cut, so step times height)." + }, + "encoding": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Pixel encoding, e.g. `rgb8`." + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` of the camera." + }, + "height": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Pixels." + }, + "is_bigendian": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "Big-endian pixels." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when partial; else `null`." + }, + "step": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Row length, bytes." + }, + "summary_type": { + "const": "image", + "type": "string" + }, + "width": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Pixels." + } + }, + "required": [ + "summary_type", + "frame_id", + "width", + "height", + "encoding", + "step", + "is_bigendian", + "data_length", + "data_length_basis" + ], + "type": "object" +} - added
Output schema / $defs / ImuSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "angular_velocity_norm": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Norm of `angular_velocity`, rad/s." + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` of the IMU." + }, + "linear_acceleration_norm": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Norm of `linear_acceleration`, m/s^2." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when a value is unusable; else `null`." + }, + "orientation_valid": { + "description": "False when the IMU gives no orientation: `roll`, `pitch`, `yaw` are `null`.", + "type": "boolean" + }, + "pitch": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Rotation about y, radians." + }, + "roll": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Rotation about x, radians." + }, + "summary_type": { + "const": "imu", + "type": "string" + }, + "yaw": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Rotation about z, radians." + } + }, + "required": [ + "summary_type", + "frame_id", + "orientation_valid", + "roll", + "pitch", + "yaw", + "angular_velocity_norm", + "linear_acceleration_norm" + ], + "type": "object" +} - added
Output schema / $defs / LaserScanSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "angle_increment": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Angle between beams, rad." + }, + "angle_max": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Bearing of the last beam, rad." + }, + "angle_min": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Bearing of beam 0, rad." + }, + "beam_count": { + "description": "Beams in `ranges`.", + "minimum": 0, + "type": "integer" + }, + "closest_obstacle": { + "anyOf": [ + { + "$ref": "#/$defs/ScanReturn" + }, + { + "type": "null" + } + ], + "description": "Closest valid return of the scan; `null` with `note`." + }, + "finite_count": { + "description": "Beams with a finite range.", + "minimum": 0, + "type": "integer" + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id`; every angle is in this sensor frame, not the base frame." + }, + "inf_count": { + "description": "Beams at `+inf`.", + "minimum": 0, + "type": "integer" + }, + "nan_count": { + "description": "Beams at `nan`.", + "minimum": 0, + "type": "integer" + }, + "neg_inf_count": { + "description": "Beams at `-inf`.", + "minimum": 0, + "type": "integer" + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when the summary is partial; else `null`." + }, + "out_of_range_count": { + "description": "Finite ranges outside `range_min`..`range_max` (not valid).", + "minimum": 0, + "type": "integer" + }, + "range_max": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Largest valid range, m." + }, + "range_min": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Smallest valid range, m." + }, + "sectors": { + "$ref": "#/$defs/ScanSectors", + "description": "Closest valid return per sector." + }, + "summary_type": { + "const": "laser_scan", + "type": "string" + } + }, + "required": [ + "summary_type", + "frame_id", + "beam_count", + "angle_min", + "angle_max", + "angle_increment", + "range_min", + "range_max", + "finite_count", + "inf_count", + "neg_inf_count", + "nan_count", + "out_of_range_count", + "closest_obstacle", + "sectors" + ], + "type": "object" +} - removed
Output schema / $defs / MessageSample / descriptionRemoved value: -"A single sampled message on a topic." - removed
Output schema / $defs / MessageSample / properties / message_type / descriptionRemoved value: -"ROS2 message type of this sample, e.g. `geometry_msgs/msg/Twist`." - removed
Output schema / $defs / MessageSample / properties / message_type / titleRemoved value: -"Message Type" - changed
Output schema / $defs / MessageSample / properties / payload / descriptionPrevious value: -"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded."New value: +"Message fields as nested values; `nan`/`inf` are strings. Live `sample_messages` adds `_truncated_fields` (cut paths) and `_raw_text` (unparsable output)." - removed
Output schema / $defs / MessageSample / properties / payload / titleRemoved value: -"Payload" - changed
Output schema / $defs / MessageSample / properties / received_ns / descriptionPrevious value: -"Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise."New value: +"Wall-clock ns when the `ros2` CLI printed the message (live `sample_messages` only)." - removed
Output schema / $defs / MessageSample / properties / received_ns / titleRemoved value: -"Received Ns" - changed
Output schema / $defs / MessageSample / properties / recorded_ns / descriptionPrevious value: -"Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role."New value: +"Bag record time in ns (`peek_bag_samples` only)." - removed
Output schema / $defs / MessageSample / properties / recorded_ns / titleRemoved value: -"Recorded Ns" - removed
Output schema / $defs / MessageSample / properties / stamp_source / anyOfRemoved value: -[ - { - "enum": [ - "header", - "none", - "recorded" - ], - "type": "string" - }, - { - "type": "null" - } -] - removed
Output schema / $defs / MessageSample / properties / stamp_source / defaultRemoved value: -null - changed
Output schema / $defs / MessageSample / properties / stamp_source / descriptionPrevious value: -"Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say."New value: +"Origin of `timestamp_ns`: `header`, `payload`, `recorded` (bag record time), `dds_source` or `none` (0)." - added
Output schema / $defs / MessageSample / properties / stamp_source / enumAdded value: +[ + "header", + "payload", + "recorded", + "dds_source", + "none" +] - removed
Output schema / $defs / MessageSample / properties / stamp_source / titleRemoved value: -"Stamp Source" - added
Output schema / $defs / MessageSample / properties / stamp_source / typeAdded value: +"string" - added
Output schema / $defs / MessageSample / properties / summaryAdded value: +{ + "anyOf": [ + { + "discriminator": { + "mapping": { + "image": "#/$defs/ImageSummary", + "imu": "#/$defs/ImuSummary", + "laser_scan": "#/$defs/LaserScanSummary", + "odometry": "#/$defs/OdometrySummary", + "point_cloud2": "#/$defs/PointCloud2Summary" + }, + "propertyName": "summary_type" + }, + "oneOf": [ + { + "$ref": "#/$defs/LaserScanSummary" + }, + { + "$ref": "#/$defs/OdometrySummary" + }, + { + "$ref": "#/$defs/ImuSummary" + }, + { + "$ref": "#/$defs/ImageSummary" + }, + { + "$ref": "#/$defs/PointCloud2Summary" + } + ] + }, + { + "type": "null" + } + ], + "default": null, + "description": "Condensed reading of the whole message (see `summary_type`); `null` for other types and DDS peeks." +} - changed
Output schema / $defs / MessageSample / properties / timestamp_ns / descriptionPrevious value: -"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."New value: +"Publisher-clock ns: `header.stamp`, else the time in the body of `Clock`, `TFMessage` or `Log`, else 0 (see `stamp_source`); not the arrival time." - removed
Output schema / $defs / MessageSample / properties / timestamp_ns / titleRemoved value: -"Timestamp Ns" - removed
Output schema / $defs / MessageSample / properties / topic / descriptionRemoved value: -"Fully qualified topic name the sample was taken from, e.g. `/cmd_vel`." - removed
Output schema / $defs / MessageSample / properties / topic / titleRemoved value: -"Topic" - changed
Output schema / $defs / MessageSample / requiredPrevious value: -[ - "topic", - "message_type", - "timestamp_ns" -]New value: +[ + "topic", + "message_type", + "timestamp_ns", + "stamp_source" +] - removed
Output schema / $defs / MessageSample / titleRemoved value: -"MessageSample" - added
Output schema / $defs / OdometrySummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "angular_z": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`twist.twist.angular.z`, rad/s." + }, + "child_frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`child_frame_id` (frame of the twist)." + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` (frame of the pose)." + }, + "linear_speed": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Norm of `twist.twist.linear`, m/s." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when a value is missing; else `null`." + }, + "position_x": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`pose.pose.position.x`, m." + }, + "position_y": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`pose.pose.position.y`, m." + }, + "position_z": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "`pose.pose.position.z`, m." + }, + "summary_type": { + "const": "odometry", + "type": "string" + }, + "yaw": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Heading from the pose quaternion, radians." + } + }, + "required": [ + "summary_type", + "frame_id", + "child_frame_id", + "linear_speed", + "angular_z", + "position_x", + "position_y", + "position_z", + "yaw" + ], + "type": "object" +} - added
Output schema / $defs / PointCloud2SummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "fields": { + "description": "Point layout.", + "items": { + "$ref": "#/$defs/PointFieldSummary" + }, + "type": "array" + }, + "frame_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "`header.frame_id` of the cloud." + }, + "height": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Rows (1: unorganized)." + }, + "is_dense": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "True when no point is invalid." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence when partial; else `null`." + }, + "point_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "`width` times `height`." + }, + "point_step": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Bytes per point." + }, + "row_step": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Bytes per row." + }, + "summary_type": { + "const": "point_cloud2", + "type": "string" + }, + "width": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Points per row." + } + }, + "required": [ + "summary_type", + "frame_id", + "width", + "height", + "point_count", + "point_step", + "row_step", + "is_dense", + "fields" + ], + "type": "object" +} - added
Output schema / $defs / PointFieldSummaryAdded value: +{ + "additionalProperties": false, + "properties": { + "count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Elements per point." + }, + "datatype": { + "description": "Element type, e.g. `FLOAT32`.", + "type": "string" + }, + "name": { + "description": "Field name, e.g. `x`.", + "type": "string" + }, + "offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Byte offset in a point." + } + }, + "required": [ + "name", + "datatype", + "offset", + "count" + ], + "type": "object" +} - added
Output schema / $defs / ScanReturnAdded value: +{ + "additionalProperties": false, + "properties": { + "beam_index": { + "description": "Index in `ranges`.", + "minimum": 0, + "type": "integer" + }, + "bearing": { + "description": "Beam direction in radians, sensor frame: 0 is +x, counter-clockwise positive.", + "type": "number" + }, + "range": { + "description": "Distance, meters.", + "type": "number" + } + }, + "required": [ + "range", + "bearing", + "beam_index" + ], + "type": "object" +} - added
Output schema / $defs / ScanSectorAdded value: +{ + "additionalProperties": false, + "properties": { + "beam_count": { + "description": "Beams inside the sector (0: not covered).", + "minimum": 0, + "type": "integer" + }, + "closest": { + "anyOf": [ + { + "$ref": "#/$defs/ScanReturn" + }, + { + "type": "null" + } + ], + "description": "Closest valid return in the sector; `null` with `note`." + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Why `closest` is `null`; `null` when it is set." + } + }, + "required": [ + "beam_count", + "closest" + ], + "type": "object" +} - added
Output schema / $defs / ScanSectorsAdded value: +{ + "additionalProperties": false, + "properties": { + "front": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings within +-45 degrees of +x." + }, + "left": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings from 45 to 135 degrees." + }, + "rear": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings beyond +-135 degrees." + }, + "right": { + "$ref": "#/$defs/ScanSector", + "description": "Bearings from -135 to -45 degrees." + } + }, + "required": [ + "front", + "left", + "right", + "rear" + ], + "type": "object" +} - added
Output schema / $defs / TopicRateAdded value: +{ + "additionalProperties": false, + "properties": { + "basis": { + "description": "Clock the rate is measured on: `received_ns` (live) or `recorded_ns` (bag).", + "enum": [ + "received_ns", + "recorded_ns" + ], + "type": "string" + }, + "interval_cv": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Interval standard deviation over mean; `null` under 2 messages." + }, + "interval_median_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Median interval; `null` under 2 messages." + }, + "max_gap_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Longest interval; `null` under 2 messages." + }, + "mean_interval_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Mean interval; `null` under 2 messages." + }, + "message_count": { + "description": "Messages the rate is computed from.", + "minimum": 0, + "type": "integer" + }, + "observed_frequency_hz": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Messages per second on `basis`, drain excluded; the rate the caller received. `null` under 2 messages." + }, + "sim_frequency_hz": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Same rate on the messages' own stamps (publisher clock); `null` without advancing stamps." + }, + "startup_burst_count": { + "description": "Leading messages set aside as a start-up drain (live only); still in `message_count`.", + "minimum": 0, + "type": "integer" + }, + "trailing_gap_s": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Time from the last message to the end of a live observation; else `null`." + }, + "verdict": { + "description": "First match: `silent` (none), `insufficient` (under 5), `intermittent` (an interval over 3x the median), `stable` (cv < 0.2), `jittery` (< 0.5), `erratic`. Describes this window, not a diagnosis.", + "enum": [ + "silent", + "insufficient", + "intermittent", + "stable", + "jittery", + "erratic" + ], + "type": "string" + }, + "verdict_note": { + "description": "The verdict in plain words.", + "type": "string" + }, + "window_s": { + "description": "Observation length, seconds.", + "minimum": 0, + "type": "number" + } + }, + "required": [ + "basis", + "message_count", + "startup_burst_count", + "window_s", + "mean_interval_s", + "interval_median_s", + "max_gap_s", + "interval_cv", + "observed_frequency_hz", + "sim_frequency_hz", + "trailing_gap_s", + "verdict", + "verdict_note" + ], + "type": "object" +} - changed
Output schema / descriptionPrevious value: -"Envelope returned by the `sample_messages` tool."New value: +"Samples returned by `sample_messages`, `peek_dds_samples` and `peek_bag_samples`." - changed
Output schema / properties / count / descriptionPrevious value: -"Number of samples actually returned. May be 0 (no publisher active in live mode, or empty mock fixture), less than the requested count (topic yielded fewer messages within the timeout), or capped by the the silent maximum of 50: request `count > 50` and you will receive at most 50 without warning."New value: +"Number of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50)." - removed
Output schema / properties / count / titleRemoved value: -"Count" - changed
Output schema / properties / mode_effective / descriptionPrevious value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)." - removed
Output schema / properties / mode_effective / titleRemoved value: -"Mode Effective" - changed
Output schema / properties / note / descriptionPrevious value: -"Why `samples` is empty or limited, when the cause is not obvious (for example payload decoding is disabled for DDS user topics). `None` when there is nothing to add."New value: +"Why `samples` is empty or limited, when not obvious; else `None`." - removed
Output schema / properties / note / titleRemoved value: -"Note" - added
Output schema / properties / rateAdded value: +{ + "anyOf": [ + { + "$ref": "#/$defs/TopicRate" + }, + { + "type": "null" + } + ], + "default": null, + "description": "How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10." +} - changed
Output schema / properties / samples / descriptionPrevious value: -"The sampled messages, ordered as received from the backend."New value: +"The samples, in backend order." - removed
Output schema / properties / samples / titleRemoved value: -"Samples" - removed
Output schema / properties / topic / descriptionRemoved value: -"Topic the samples were taken from, echoed from the request." - removed
Output schema / properties / topic / titleRemoved value: -"Topic"
- Changed
topic_metrics36 fields changed- changed
Input schema / properties / domain_id / descriptionPrevious value: -"Accepted for compatibility (0..232). TopicForge observes the domain it joined at startup (TOPICFORGE_DDS_DOMAIN_ID); this argument does not switch domains, and the response `domain_id` says which one was observed."New value: +"Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value." - removed
Input schema / properties / domain_id / titleRemoved value: -"Domain Id" - changed
Input schema / properties / topic / descriptionPrevious value: -"DDS topic name. Bare DDS names such as `scan` are valid, as are ROS 2 mangled names such as `rt/scan` and the builtin discovery topics `DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`. Letters, digits, `_`, `/` and `::` are allowed; anything else is rejected."New value: +"DDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only." - removed
Input schema / properties / topic / titleRemoved value: -"Topic" - added
Input schema / properties / window_sAdded value: +{ + "default": 60, + "description": "Window in seconds, 1..3600, default 60.", + "maximum": 3600, + "minimum": 1, + "type": "integer" +} - removed
Input schema / properties / window_secondsRemoved value: -{ - "default": 60, - "description": "Window in seconds over which to compute metrics (1..3600). Defaults to 60 seconds. Smaller windows reflect more recent state; larger windows smooth transient anomalies.", - "maximum": 3600, - "minimum": 1, - "title": "Window Seconds", - "type": "integer" -} - removed
Input schema / titleRemoved value: -"topic_metricsArguments" - changed
Output schema / descriptionPrevious value: -"Temporal metrics for a single DDS topic over a recent window.\n\nBuilt from samples that pass through `peek_dds_samples`. The buffer is\nfilled on demand because neither the `cyclonedds` nor the `fastdds`\nPython binding has a reliable per-sample callback, so a burst between two\ncalls is not seen.\n\nNumeric fields are `None` (`0` for `sequence_gaps_count`) when the data is\nmissing: no samples, no source timestamps, no sequence number in the\npayload. `samples_observed=0` is a valid result, not an error."New value: +"Temporal metrics for a single DDS topic over a recent window." - added
Output schema / properties / declared_frequency_hzAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "description": "`1 / deadline` of the shortest writer Deadline announced: the promised rate, not the observed one; `None` when none announced." +} - removed
Output schema / properties / frequency_hz_declaredRemoved value: -{ - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Declared, not measured: `1 / deadline` for the shortest QoS Deadline period announced by a writer on this topic in discovery. `None` when no writer announced a finite Deadline or the topic is not announced. It is the rate the application promised, not the rate observed.", - "title": "Frequency Hz Declared" -} - removed
Output schema / properties / frequency_hz_observedRemoved value: -{ - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "default": null, - "description": "`samples_observed / window_seconds_actual`. `None` when fewer than 2 samples were observed (a single sample does not define a frequency).", - "title": "Frequency Hz Observed" -} - changed
Output schema / properties / latency_available / descriptionPrevious value: -"True when at least one sample in the window carried both a publish timestamp and a receive timestamp. The percentile fields are `None` when this is False."New value: +"True when a sample carried both a publish and a receive timestamp." - removed
Output schema / properties / latency_available / titleRemoved value: -"Latency Available" - changed
Output schema / properties / latency_ns_p50 / descriptionPrevious value: -"Median publish-to-receive latency in nanoseconds, computed only when the sample type exposes a publish timestamp (typically via `header.stamp` on `Header`-stamped messages). `None` when `latency_available=False`."New value: +"Median publish-to-receive latency in ns; `None` unless `latency_available`." - removed
Output schema / properties / latency_ns_p50 / titleRemoved value: -"Latency Ns P50" - removed
Output schema / properties / latency_ns_p95 / titleRemoved value: -"Latency Ns P95" - removed
Output schema / properties / latency_ns_p99 / titleRemoved value: -"Latency Ns P99" - changed
Output schema / properties / mode_effective / descriptionPrevious value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)." - removed
Output schema / properties / mode_effective / titleRemoved value: -"Mode Effective" - added
Output schema / properties / noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "One sentence of context, or `None`." +} - added
Output schema / properties / observed_frequency_hzAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "description": "`samples_observed / window_actual_s`; `None` under 2 samples." +} - changed
Output schema / properties / samples_observed / descriptionPrevious value: -"Number of samples in the buffer matching `topic` within the window. `0` means TopicForge has not seen any sample on this topic recently: it does NOT mean the topic has no publisher, only that no `peek_dds_samples` call captured one in the window."New value: +"Samples buffered in the window. `0` does NOT mean no publisher, only that no `peek_dds_samples` call captured one." - removed
Output schema / properties / samples_observed / titleRemoved value: -"Samples Observed" - changed
Output schema / properties / sequence_gaps_count / descriptionPrevious value: -"Number of missing sequence numbers detected in the buffered samples. `0` either means no gaps observed OR the sample type did not expose a sequence number (check `sequence_numbers_available` to disambiguate)."New value: +"Missing sequence numbers in the buffered samples; `0` also when none exist (see `sequence_numbers_available`)." - removed
Output schema / properties / sequence_gaps_count / titleRemoved value: -"Sequence Gaps Count" - changed
Output schema / properties / sequence_numbers_available / descriptionPrevious value: -"True when the adapter successfully extracted sequence numbers from at least one sample. Sequence number support depends on the message type: `Header`-stamped messages with a `seq` field expose it; primitives like `std_msgs/String` do not."New value: +"True when a sequence number could be read from a sample." - removed
Output schema / properties / sequence_numbers_available / titleRemoved value: -"Sequence Numbers Available" - changed
Output schema / properties / status / descriptionPrevious value: -"How to read the numbers. `unsupported_user_topic`: the topic is a user topic, whose payload is not decoded, so no metric exists and the null fields are not a measurement. `no_samples_yet`: a supported topic with nothing buffered in the window. `ok`: metrics computed from buffered samples."New value: +"`ok`; `no_samples_yet` (empty buffer); `unsupported_user_topic` (payload not decoded: null fields are not measurements)." - removed
Output schema / properties / status / titleRemoved value: -"Status" - removed
Output schema / properties / topic / descriptionRemoved value: -"Topic the metrics were computed for." - removed
Output schema / properties / topic / titleRemoved value: -"Topic" - added
Output schema / properties / window_actual_sAdded value: +{ + "description": "Seconds actually covered; shorter than `window_s` after a recent start; `0.0` without samples.", + "minimum": 0, + "type": "number" +} - added
Output schema / properties / window_sAdded value: +{ + "description": "Requested window in seconds (echoed).", + "maximum": 3600, + "minimum": 1, + "type": "integer" +} - removed
Output schema / properties / window_secondsRemoved value: -{ - "description": "Requested window in seconds (1..3600). Echoed back from the tool call so the LLM can correlate the request.", - "maximum": 3600, - "minimum": 1, - "title": "Window Seconds", - "type": "integer" -} - removed
Output schema / properties / window_seconds_actualRemoved value: -{ - "description": "Actual elapsed seconds within the window. May be smaller than `window_seconds` when the adapter buffered samples for less time than the requested window (e.g., the server just started). `0.0` when `samples_observed=0`.", - "minimum": 0, - "title": "Window Seconds Actual", - "type": "number" -} - changed
Output schema / requiredPrevious value: -[ - "topic", - "window_seconds", - "window_seconds_actual", - "samples_observed", - "mode_effective" -]New value: +[ + "topic", + "window_s", + "window_actual_s", + "samples_observed", + "mode_effective" +]
4 tool updates
v0.6.3- Changed
health_check1 field changed- added
Output schema / properties / sim_clock_publishedAdded value: +{ + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Whether `/clock` has at least one publisher on the ROS 2 graph. True suggests nodes may run on simulated time (`use_sim_time`), so `header.stamp` values are sim time, not wall time (compare with `received_ns`). It is a hint: a publisher on `/clock` does not prove a given node follows it. `None` in mock mode or when the `ros2` CLI could not answer.", + "title": "Sim Clock Published" +}
- Changed
peek_bag_samples5 fields changed- changed
Output schema / $defs / MessageSample / properties / payload / descriptionPrevious value: -"Structured message payload. **In live mode the parser exposes the message fields as positional CSV columns** keyed as `col_0`, `col_1`, ... (`header.stamp.sec`/`nanosec` are stripped out into `timestamp_ns` when detected). The raw CSV row is preserved verbatim under the reserved `_raw_text` key so clients can re-parse against the message's IDL when needed. In mock mode the payload is fully structured and `_raw_text` is absent. Large messages (e.g. images) may be summarized to keep tool output bounded."New value: +"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded." - added
Output schema / $defs / MessageSample / properties / received_nsAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise.", + "title": "Received Ns" +} - added
Output schema / $defs / MessageSample / properties / recorded_nsAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role.", + "title": "Recorded Ns" +} - added
Output schema / $defs / MessageSample / properties / stamp_sourceAdded value: +{ + "anyOf": [ + { + "enum": [ + "header", + "none", + "recorded" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say.", + "title": "Stamp Source" +} - changed
Output schema / $defs / MessageSample / properties / timestamp_ns / descriptionPrevious value: -"Timestamp in nanoseconds since epoch. In live mode this is the `header.stamp` of the sampled message when present: the live adapter invokes `ros2 topic echo --csv --once`, whose flattened CSV exposes `header.stamp.sec`/`nanosec` as the first two columns for any `Header`-stamped message. **Headerless message types** (e.g. `std_msgs/String`, `geometry_msgs/Twist`) carry no embedded timestamp, and `timestamp_ns` falls back to 0; an rclpy-backed adapter will eventually expose rmw receive times for those. Mock mode emits monotonically increasing values for deterministic ordering."New value: +"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."
- Changed
peek_dds_samples5 fields changed- changed
Output schema / $defs / MessageSample / properties / payload / descriptionPrevious value: -"Structured message payload. **In live mode the parser exposes the message fields as positional CSV columns** keyed as `col_0`, `col_1`, ... (`header.stamp.sec`/`nanosec` are stripped out into `timestamp_ns` when detected). The raw CSV row is preserved verbatim under the reserved `_raw_text` key so clients can re-parse against the message's IDL when needed. In mock mode the payload is fully structured and `_raw_text` is absent. Large messages (e.g. images) may be summarized to keep tool output bounded."New value: +"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded." - added
Output schema / $defs / MessageSample / properties / received_nsAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise.", + "title": "Received Ns" +} - added
Output schema / $defs / MessageSample / properties / recorded_nsAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role.", + "title": "Recorded Ns" +} - added
Output schema / $defs / MessageSample / properties / stamp_sourceAdded value: +{ + "anyOf": [ + { + "enum": [ + "header", + "none", + "recorded" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say.", + "title": "Stamp Source" +} - changed
Output schema / $defs / MessageSample / properties / timestamp_ns / descriptionPrevious value: -"Timestamp in nanoseconds since epoch. In live mode this is the `header.stamp` of the sampled message when present: the live adapter invokes `ros2 topic echo --csv --once`, whose flattened CSV exposes `header.stamp.sec`/`nanosec` as the first two columns for any `Header`-stamped message. **Headerless message types** (e.g. `std_msgs/String`, `geometry_msgs/Twist`) carry no embedded timestamp, and `timestamp_ns` falls back to 0; an rclpy-backed adapter will eventually expose rmw receive times for those. Mock mode emits monotonically increasing values for deterministic ordering."New value: +"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."
- Changed
sample_messages9 fields changed- changed
Input schema / properties / count / descriptionPrevious value: -"Maximum number of recent messages to return. Defaults to 5; silently clamped to 50 (the hard cap that keeps tool output bounded; read it from `health_check.max_sample_count`). Negative values raise an error. The returned `SampleResult.count` reflects the actual number of samples produced: it can be lower than the request (empty topic, timeout, mock fixture shorter than requested)."New value: +"Number of messages to wait for. Defaults to 5; at most 50 (a larger request is capped to 50 and `note` says so). 0 returns nothing. `SampleResult.count` is how many arrived: fewer than requested when the topic publishes slowly or the deadline (`timeout_s`) ran out." - added
Input schema / properties / count / maximumAdded value: +50 - changed
Input schema / properties / max_array_length / descriptionPrevious value: -"Longest array, string or bytes value to return in full, 1..65536; longer ones are cut. A cut array is listed in the sample's `_truncated_after_columns` (index of the last kept column); a cut string or bytes value is kept as its first N characters plus `...` and listed in `_truncated_columns`. Defaults to 128, the `ros2 topic echo` default, which cuts a 541-beam `LaserScan` after 128 ranges. Pass null to return everything in full (large for images and point clouds; a message over the server's size cap, 1 MiB by default, is dropped with a note, and a very large message may not print within the echo timeout)."New value: +"Longest array, string or bytes value to return in full, 1..65536; longer ones are cut after their first N elements or characters and the field's dotted path is listed in the sample's `_truncated_fields`. Defaults to 128, the `ros2 topic echo` default, which cuts a 541-beam `LaserScan` after 128 ranges. Pass null to return everything in full (large for images and point clouds; a message over the server's size cap, 1 MiB by default, is dropped with a note, and a very large message may not print before the deadline)." - added
Input schema / properties / timeout_sAdded value: +{ + "default": 10, + "description": "Seconds to wait for the messages, 1..45, default 10. Bounds the whole call, topic lookup and the `ros2` CLI start-up (a few seconds on a slow machine) included: the call returns within about `timeout_s` + 2 s. Whatever arrived by then is returned with a `note`. Raise it for a topic that publishes slower than 1 Hz. The server handles one call at a time, so a long wait delays other tool calls.", + "maximum": 45, + "minimum": 1, + "title": "Timeout S", + "type": "number" +} - changed
Output schema / $defs / MessageSample / properties / payload / descriptionPrevious value: -"Structured message payload. **In live mode the parser exposes the message fields as positional CSV columns** keyed as `col_0`, `col_1`, ... (`header.stamp.sec`/`nanosec` are stripped out into `timestamp_ns` when detected). The raw CSV row is preserved verbatim under the reserved `_raw_text` key so clients can re-parse against the message's IDL when needed. In mock mode the payload is fully structured and `_raw_text` is absent. Large messages (e.g. images) may be summarized to keep tool output bounded."New value: +"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded." - added
Output schema / $defs / MessageSample / properties / received_nsAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise.", + "title": "Received Ns" +} - added
Output schema / $defs / MessageSample / properties / recorded_nsAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role.", + "title": "Recorded Ns" +} - added
Output schema / $defs / MessageSample / properties / stamp_sourceAdded value: +{ + "anyOf": [ + { + "enum": [ + "header", + "none", + "recorded" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say.", + "title": "Stamp Source" +} - changed
Output schema / $defs / MessageSample / properties / timestamp_ns / descriptionPrevious value: -"Timestamp in nanoseconds since epoch. In live mode this is the `header.stamp` of the sampled message when present: the live adapter invokes `ros2 topic echo --csv --once`, whose flattened CSV exposes `header.stamp.sec`/`nanosec` as the first two columns for any `Header`-stamped message. **Headerless message types** (e.g. `std_msgs/String`, `geometry_msgs/Twist`) carry no embedded timestamp, and `timestamp_ns` falls back to 0; an rclpy-backed adapter will eventually expose rmw receive times for those. Mock mode emits monotonically increasing values for deterministic ordering."New value: +"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."
12 tool updates
v0.5.6- First observed
analyze_bag - First observed
detect_qos_mismatches - First observed
get_topic_info - First observed
health_check - First observed
list_endpoints - First observed
list_participants - First observed
list_topics - First observed
participant_events - First observed
peek_bag_samples - First observed
peek_dds_samples - First observed
sample_messages - First observed
topic_metrics
TDQS
Scored across 14 tools
Most tools have clearly distinct layer-and-action purposes: ROS 2 graph vs DDS bus, current state vs lifecycle events, summary vs samples, live vs bag. However, the sample/peek trio (sample_messages, peek_dds_samples, peek_bag_samples) and list-vs-get pairs share enough surface concepts that an agent may briefly hesitate, even though the descriptions cross-reference the correct choice.
All names use snake_case and most follow a verb_noun pattern (list_*, get_*, peek_*, sample_messages, analyze_bag, detect_*). A few established noun forms (health_check, participant_events, topic_metrics) deviate from the verb_noun convention but remain readable and predictable.
14 tools is well within the expected range for a rich ROS 2/DDS diagnostic server. Each tool covers a distinct layer, lifecycle stage, or data source, so the set does not feel padded or thin.
Coverage is broad: environment health, ROS nodes/topics/info/samples, DDS participants/endpoints/events/samples/metrics/QoS mismatch detection, and bag analysis/peek. Minor gaps remain, such as no direct global list_services or list_actions, and DDS user-topic payloads are undecodable by design, which limits some diagnostic depth.
Maintenance
Related MCP Connectors
Read-only MCP access to a documented IT fleet: state, changes, posture. 15 tools.
Read-only MCP server: let AI agents read your ORANO saved-video library, tasks, and memory.
Read-only MCP tools for AI agent discovery, structured resources, and NIULAI information.
Read-only MCP server for the OPERANT AI operating-agent calibration benchmark.
Related MCP Servers
- AlicenseAqualityDmaintenanceRead-only Modbus TCP monitoring server that exposes safe MCP tools for AI agents to read holding/input registers, coils, and device identity from industrial devices without write access.4Apache 2.0
- AlicenseNot gradedqualityCmaintenanceMCP server for AI agents to inspect and control ROS2 graphs, enabling topic listing, node info, service calls, parameter management, and pub/echo without manual ROS2 CLI usage.12MIT
- AlicenseNot gradedqualityCmaintenanceRead-only MCP server that exposes Autopsy digital forensics case data as tools, enabling LLM clients like Cline to browse filesystems, query artifacts, and search keywords without modifying the case.1MIT
- AlicenseNot gradedqualityCmaintenanceA read-only MCP server for bounded semantic inspection of ROS 2 perception systems, enabling discovery and metadata extraction from sensors such as cameras, depth sensors, and LiDARs without device configuration or actuation.Apache 2.0