Skip to main content
Glama

analyze_bag

Read-onlyIdempotent

Summarize a ROS 2 bag as structured data: storage format, duration, message count, per-topic frequency and latched status. Use it to inspect recorded bag files before replay or debugging.

Instructions

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: ros2 CLI behind the ROS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

Input Schema

NameRequiredDescriptionDefault
pathYesBag 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

NameRequiredDescriptionDefault
noteNoWhy the result is less detailed than usual (e.g. rates over the whole bag duration); else `None`.
pathYesBag path as supplied.
topicsYesPer-topic statistics for every topic present in the bag.
start_nsNoRecord time in ns of the bag's first message (rosbag2 `starting_time`): the zero of `peek_bag_samples` `start_s` / `end_s`. `null` when unreadable.
anomaliesNoNotes on gaps or clock jumps; mock mode only.
bag_formatNoDetected container: `mcap`, `db3`, `bag` or `unknown`; `None` for `ros2 bag info` text.
duration_sYesBag duration in seconds.
message_countYesTotal number of messages across all recorded topics.
mode_effectiveYes`live` or `mock` (fixtures).
storage_formatNo`mcap`, `sqlite3`, or other storage identifier when known.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed31 schema fields changedv0.8.0
    • removedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / anyOf
      Removed value: -[
      -  {
      -    "minimum": 0,
      -    "type": "integer"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • removedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / default
      Removed value: -null
    • addedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / minimum
      Added value: +0
    • addedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / type
      Added value: +[
      +  "integer",
      +  "null"
      +]
    • removedOutput schema / $defs / BagTopicStats / properties / frequency_basis / anyOf
      Removed value: -[
      -  {
      -    "enum": [
      -      "topic_span",
      -      "bag_duration"
      -    ],
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • removedOutput schema / $defs / BagTopicStats / properties / frequency_basis / default
      Removed value: -null
    • addedOutput schema / $defs / BagTopicStats / properties / frequency_basis / enum
      Added value: +[
      +  "topic_span",
      +  "bag_duration",
      +  null
      +]
    • addedOutput schema / $defs / BagTopicStats / properties / frequency_basis / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / $defs / BagTopicStats / properties / frequency_hz / anyOf
      Removed value: -[
      -  {
      -    "minimum": 0,
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • removedOutput schema / $defs / BagTopicStats / properties / frequency_hz / default
      Removed value: -null
    • addedOutput schema / $defs / BagTopicStats / properties / frequency_hz / minimum
      Added value: +0
    • addedOutput schema / $defs / BagTopicStats / properties / frequency_hz / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / $defs / BagTopicStats / properties / kind / default
      Removed value: -"user"
    • removedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / anyOf
      Removed value: -[
      -  {
      -    "minimum": 0,
      -    "type": "integer"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • removedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / default
      Removed value: -null
    • addedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / minimum
      Added value: +0
    • addedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / type
      Added value: +[
      +  "integer",
      +  "null"
      +]
    • removedOutput schema / $defs / BagTopicStats / properties / latched / anyOf
      Removed value: -[
      -  {
      -    "type": "boolean"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • removedOutput schema / $defs / BagTopicStats / properties / latched / default
      Removed value: -null
    • addedOutput schema / $defs / BagTopicStats / properties / latched / type
      Added value: +[
      +  "boolean",
      +  "null"
      +]
    • removedOutput schema / properties / bag_format / anyOf
      Removed value: -[
      -  {
      -    "enum": [
      -      "mcap",
      -      "db3",
      -      "bag",
      -      "unknown"
      -    ],
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • removedOutput schema / properties / bag_format / default
      Removed value: -null
    • addedOutput schema / properties / bag_format / enum
      Added value: +[
      +  "mcap",
      +  "db3",
      +  "bag",
      +  "unknown",
      +  null
      +]
    • addedOutput schema / properties / bag_format / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / note / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • removedOutput schema / properties / note / default
      Removed value: -null
    • addedOutput schema / properties / note / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • addedOutput schema / properties / start_ns
      Added value: +{
      +  "description": "Record time in ns of the bag's first message (rosbag2 `starting_time`): the zero of `peek_bag_samples` `start_s` / `end_s`. `null` when unreadable.",
      +  "type": [
      +    "integer",
      +    "null"
      +  ]
      +}
    • removedOutput schema / properties / storage_format / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • removedOutput schema / properties / storage_format / default
      Removed value: -null
    • addedOutput schema / properties / storage_format / type
      Added value: +[
      +  "string",
      +  "null"
      +]
  2. Changed42 schema fields changedv0.7.1
    • changedInput schema / properties / path / description
      Previous 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."
    • removedInput schema / properties / path / title
      Removed value: -"Path"
    • removedInput schema / title
      Removed value: -"analyze_bagArguments"
    • removedOutput schema / $defs / BagTopicStats / description
      Removed value: -"Per-topic statistics inside a bag analysis result."
    • changedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / description
      Previous 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."
    • removedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / title
      Removed value: -"First Timestamp Ns"
    • changedOutput schema / $defs / BagTopicStats / properties / frequency_basis / description
      Previous 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."
    • removedOutput schema / $defs / BagTopicStats / properties / frequency_basis / title
      Removed value: -"Frequency Basis"
    • changedOutput schema / $defs / BagTopicStats / properties / frequency_hz / description
      Previous 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)."
    • removedOutput schema / $defs / BagTopicStats / properties / frequency_hz / title
      Removed value: -"Frequency Hz"
    • addedOutput schema / $defs / BagTopicStats / properties / kind
      Added 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"
      +}
    • changedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / description
      Previous 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."
    • removedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / title
      Removed value: -"Last Timestamp Ns"
    • changedOutput schema / $defs / BagTopicStats / properties / latched / description
      Previous 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."
    • removedOutput schema / $defs / BagTopicStats / properties / latched / title
      Removed value: -"Latched"
    • removedOutput schema / $defs / BagTopicStats / properties / message_count / title
      Removed value: -"Message Count"
    • removedOutput schema / $defs / BagTopicStats / properties / message_type / description
      Removed value: -"ROS2 message type recorded for this topic."
    • removedOutput schema / $defs / BagTopicStats / properties / message_type / title
      Removed value: -"Message Type"
    • removedOutput schema / $defs / BagTopicStats / properties / name / description
      Removed value: -"Fully qualified topic name as recorded in the bag."
    • removedOutput schema / $defs / BagTopicStats / properties / name / title
      Removed value: -"Name"
    • removedOutput schema / $defs / BagTopicStats / title
      Removed value: -"BagTopicStats"
    • removedOutput schema / $defs / ParticipantInfo
      Removed 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"
      -}
    • changedOutput schema / description
      Previous 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."
    • changedOutput schema / properties / anomalies / description
      Previous 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."
    • removedOutput schema / properties / anomalies / title
      Removed value: -"Anomalies"
    • changedOutput schema / properties / bag_format / description
      Previous 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."
    • removedOutput schema / properties / bag_format / title
      Removed value: -"Bag Format"
    • addedOutput schema / properties / duration_s
      Added value: +{
      +  "description": "Bag duration in seconds.",
      +  "minimum": 0,
      +  "type": "number"
      +}
    • removedOutput schema / properties / duration_seconds
      Removed value: -{
      -  "description": "Total bag duration, in seconds (wall clock between first and last message).",
      -  "minimum": 0,
      -  "title": "Duration Seconds",
      -  "type": "number"
      -}
    • removedOutput schema / properties / message_count / title
      Removed value: -"Message Count"
    • changedOutput schema / properties / mode_effective / description
      Previous 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)."
    • removedOutput schema / properties / mode_effective / title
      Removed value: -"Mode Effective"
    • changedOutput schema / properties / note / description
      Previous 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`."
    • removedOutput schema / properties / note / title
      Removed value: -"Note"
    • removedOutput schema / properties / participants_recorded
      Removed 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"
      -}
    • changedOutput schema / properties / path / description
      Previous 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."
    • removedOutput schema / properties / path / title
      Removed value: -"Path"
    • removedOutput schema / properties / recording_duration_ns
      Removed 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"
      -}
    • removedOutput schema / properties / samples_decoded_count
      Removed 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"
      -}
    • removedOutput schema / properties / storage_format / title
      Removed value: -"Storage Format"
    • removedOutput schema / properties / topics / title
      Removed value: -"Topics"
    • changedOutput schema / required
      Previous value: -[
      -  "path",
      -  "duration_seconds",
      -  "message_count",
      -  "topics",
      -  "mode_effective"
      -]New value: +[
      +  "path",
      +  "duration_s",
      +  "message_count",
      +  "topics",
      +  "mode_effective"
      +]
  3. First observedv0.5.6

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent/no-destructive, and the description still adds substantial behavior: the 200 MiB `.mcap` fallback rule, the `bag_duration` basis and `note`, which error conditions raise MCP errors, the 45 s ceiling, and single-call lock serialization. The only small gap is that the `latched`/1 s rule is described operationally without saying how the caller should interpret a null `frequency_hz`.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the return shape, then falls back to computation semantics, modes, error cases and timing — a sensible information hierarchy with no filler sentences. It is dense to the point of being a wall of bolded clauses, which costs a point on scannability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter tool with an output schema, the description still covers modes, format gate, fallback math, error surface, lock contention and timeout — everything needed to call it correctly and to interpret a degraded result. Nothing material is left for the agent to guess.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the parameter is already self-documented, so the baseline is 3; the description nonetheless adds that existence and format are validated live while mock accepts any well-formed path, which is meaning beyond the schema. It largely restates the accepted extension list, which caps it below 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource ('Summarize a ROS 2 bag at `path`') and names the concrete return type (`BagAnalysis`) plus its fields, so the agent knows exactly what it gets back. It also routes ROS 1 `.bag` users to `peek_bag_samples`, distinguishing it from the closest sibling in the same sentence.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly splits live mode (`.mcap`, `.db3`, `rosbag2_*`) from mock mode (any bag-like path, anomaly detection only) and states the alternative (`peek_bag_samples`) for the format this tool rejects. It even gives retry guidance for the `busy` lock error.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.