Skip to main content
Glama
littlebigbrains

@littlebigbrain/mcp

lbb_query

Read-only

Ask questions in plain words or run SPARQL and structured queries against Little Big Brain knowledge graphs to retrieve, filter, search by meaning, and analyze data.

Instructions

Analytical and expert reads. Modes: question (a question in plain words), structured (SPARQL-subset JSON body), sparql (SPARQL text), search (instances by meaning over every searchable class; filter narrows by class and relationship; every hit checked against the graph), analyze. Use mode=question when you have a question in plain words and no SPARQL query: the server selects the kind of query (route), writes the SPARQL query from a description of the graph, checks it, runs it, and returns the route, the rationale, the query and its rows. Continue or correct that query with mode=sparql. Each question uses model tokens and counts toward a daily limit of the stack. SPARQL is the query language; search finds what the words describe. SPARQL text can also search by meaning inside the query with ?x https://littlebigbrain.com/search#similarTo "words", so the other patterns filter and join the hits in one query; the result's search field reports the plan. To plan a search: lbb_embeddings action=list, then SPARQL for a class's relationships on a sample, then mode=search with explain=true to check the resolved filter before the real search (lbb_inspect action=guide has the queries). Relations are https://littlebigbrain.com/r/NAME and types https://littlebigbrain.com/class/NAME (both lowercased); entities are content-addressed, so anchor a named one by its rdfs:label rather than building its IRI. Structured and text queries pin one published watermark for the request.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
runNoquestion: run the query and return its rows. Defaults to true; false returns the query only.
bodyNoStructured SPARQL-subset request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: "x" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { "compare": { "op": <op>, "left": <term>, "right": <term> } } (or { "and": [<filter>…] }, { "or": [<filter>…] }, { "not": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { "var": "x" }, { "property": { "var": "x", "field": "amount" } } (a typed scalar attribute), or { "value": <typed> } — and <typed> is exactly one wrapper: { "str": "…" }, { "i64": 5 }, { "f64": 0.9 }, { "bool": true }, { "date_time": "2026-01-01" } (RFC3339), or { "entity": { "entity_type": "T", "name": "N" } }. Complete runnable example — deals whose amount ≥ 1000000: { "patterns": [{ "subject": { "var": "d" }, "predicate": "for_client", "object": { "var": "c" } }], "filters": [{ "compare": { "op": "ge", "left": { "property": { "var": "d", "field": "amount" } }, "right": { "value": { "f64": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: ["s"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { "patterns": [{ "subject": { "var": "c" }, "predicate": "committed_to", "object": { "var": "repo" } }], "group_keys": [{ "date_bucket": { "var": "c", "field": "committed_at", "granularity": "month", "as": "m" } }, { "property": { "var": "c", "field": "area", "as": "area" } }], "aggregates": [{ "func": "count", "as": "n" }], "order_by": [{ "var": "m" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { "compare": { "op": "gt", "left": { "var": "n" }, "right": { "value": { "i64": 10 } } } }). A `combinators` key (UNION/OPTIONAL/MINUS/EXISTS) is rejected here; express those with SPARQL text under mode=sparql. Cheap aggregate count: pair an equality having (e.g. { "compare": { "op": "eq", "left": { "var": "n" }, "right": { "value": { "i64": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning use the top-level `as_of_commit_seq` argument or the same body field. Valid-time `as_of` and `as_of_valid_time` selectors are unsupported and rejected before HTTP.
modeYesSelects the variant (one of: structured, sparql, question, search, analyze).
textNoThe query text (required); embedded with the graph's model.
as_ofNoUnsupported in structured and SPARQL text modes; use as_of_commit_seq for a retained commit snapshot.
chartNo
fieldNo
graphNoGraph to target; defaults to the connection's graph
limitNoquestion: rows the run returns, 1 to 1,000. Defaults by detail: compact=20, standard=100, full=1000.
probeNoClusters to read (default 4·√clusters, at least 8). More reads cost latency and find more.
queryNoSPARQL 1.1 query text (SELECT or ASK). Valid-time as_of is unsupported; use as_of_commit_seq for a retained commit snapshot. IRI scheme: relations are <https://littlebigbrain.com/r/NAME> (NAME lowercased, e.g. writes_to; reverse a relation with the ^ path operator, no stored inverse triple). Types are <https://littlebigbrain.com/class/NAME> (lowercased), matched as `?x a <…/class/NAME>` with explicit entailment=subclass, rdfs, or owl for inference (default none). Property fields are <https://littlebigbrain.com/p/NAME> (lowercased). The local name is ALWAYS lowercase — an uppercase one (e.g. <…/r/FOR_CLIENT>) is a different, non-existent IRI that silently matches nothing; this tool auto-lowercases the local name of /r/, /class/, and /p/ IRIs for you and adds a `notes` entry when it does, so a stray uppercase still resolves. (Structured mode's `predicate` is case-insensitive on its own.) Entities are content-addressed <https://littlebigbrain.com/e/HASH> — never build an entity IRI from a name; anchor a named entity by its label instead: `?e <http://www.w3.org/2000/01/rdf-schema#label> "Acme"`. Discover the exact relation and type names with lbb_inspect action=ontology. SELECT and ASK only (CONSTRUCT/DESCRIBE are rejected). Example: SELECT ?service ?db WHERE { ?service <https://littlebigbrain.com/r/writes_to> ?db } LIMIT 10. Search by meaning inside the query (the graph needs an embedding; see lbb_embeddings action=list): PREFIX search: <https://littlebigbrain.com/search#> SELECT ?x ?score WHERE { ?x search:similarTo "card payments" ; search:score ?score . ?x <https://littlebigbrain.com/r/calls> ?y } ORDER BY DESC(?score) LIMIT 5. The other patterns filter and join the hits. One search:similarTo per query, with a variable subject, in the main group (not inside OPTIONAL, UNION, MINUS or a subquery). The object is text, an entity IRI (records like that one) or a vector literal. search:top N sets the hits (1 to 1000; default the query LIMIT, then 10) and search:embedding "name" searches one embedding. FILTER(?score > 0.8) keeps close hits. The result's search field reports the plan (nearest, filter_first or search_first), the hits and complete. For a question in words, add search:rerank true ; search:relevance ?r and ORDER BY DESC(?r): the rerank model (Jev) reads the best hits with the text, keeps the top that answer it and binds its answer (0 to 1); search.rerank reports the status. Only a text query reranks.
routeNoquestion: the kind of query, when you know it. Omit it and the router model selects it.
top_kNo
anchorNoquestion: entity IRIs the question is about, at most 10 (for example the record the user has open). The server reads each one, and the query uses these IRIs directly instead of matching their names.
cursorNoOpaque cursor from a previous lbb_query row page; reruns the original query at the next offset.
detailNoResponse detail level. Defaults to compact.
filterNoConditions every hit must meet: {"class": …} or {"via": …, "to": …}. The response's filter shows how each condition resolved; an unknown relationship or name answers with the options.
metricNo
rerankNotrue: the managed rerank model (Jev) orders the best hits by how well each answers the text, and each hit gets its relevance (0 to 1); adds about 0.3 s. false: the similarity order. Omit it to follow the graph's search setting.
sparqlNo
contextNoquestion: notes for the model, at most 8,000 characters: what the data means, units, names to prefer.
explainNoPlan without running: the scope, the resolved filter, and the allowed count. No model call.
includeNotext: return the embedded text of each hit.
requestNoThe user's own words behind this query (managed evals). When present the server records an eval trace and the result carries its trace_id; label the rows valid or not with lbb_evals action=label. Omit on continuation pages.
questionNoquestion: the question in plain words (required), 1 to 4,000 characters. The server writes the SPARQL query from a description of this graph, checks it, and runs it.
timelineNoquestion: dated points that stand for the graph's commits, at most 200. A question about a date reads the commit of the latest point on or before it. Use it when the commits stand for other dates than the days they were written (a demo's milestones, an import of old records); without it the server reads the last commit written by the end of that day.
embeddingNoOne embedding by name (see lbb_embeddings action=list). Omit it to search every searchable class of the graph; each hit names its class.
row_limitNoMaximum query rows to return in this page. Defaults by detail: compact=20, standard=100, full=1000.
entailmentNoReasoning over the pinned RDF generation. Defaults to none. owl includes RDFS, inverse relationships and the supported OWL profile.
consistencyNoRead consistency. strong requires publication through head; a pending response is retryable.
min_indexed_seqNoRead-after-write publication floor. Preserved across cursor pages.
as_of_commit_seqNoSnapshot pin: evaluate the body as of this commit_seq, hiding later commits. Errors if past head. Top-level alias for the body's `as_of_commit_seq` (either works for this one).

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed11 schema fields changedv0.9.0
    • addedInput schema / properties / anchor
      Added value: +{
      +  "description": "question: entity IRIs the question is about, at most 10 (for example the record the user has open). The server reads each one, and the query uses these IRIs directly instead of matching their names.",
      +  "items": {
      +    "maxLength": 2000,
      +    "minLength": 1,
      +    "type": "string"
      +  },
      +  "maxItems": 10,
      +  "type": "array"
      +}
    • addedInput schema / properties / context
      Added value: +{
      +  "description": "question: notes for the model, at most 8,000 characters: what the data means, units, names to prefer.",
      +  "type": "string"
      +}
    • addedInput schema / properties / limit
      Added value: +{
      +  "description": "question: rows the run returns, 1 to 1,000. Defaults by detail: compact=20, standard=100, full=1000.",
      +  "maximum": 1000,
      +  "minimum": 1,
      +  "type": "integer"
      +}
    • changedInput schema / properties / mode / description
      Previous value: -"Selects the variant (one of: structured, sparql, search, analyze)."New value: +"Selects the variant (one of: structured, sparql, question, search, analyze)."
    • changedInput schema / properties / mode / enum
      Previous value: -[
      -  "structured",
      -  "sparql",
      -  "search",
      -  "analyze"
      -]New value: +[
      +  "structured",
      +  "sparql",
      +  "question",
      +  "search",
      +  "analyze"
      +]
    • changedInput schema / properties / query / description
      Previous value: -"SPARQL 1.1 query text (SELECT or ASK). Valid-time as_of is unsupported; use as_of_commit_seq for a retained commit snapshot. IRI scheme: relations are <https://littlebigbrain.com/r/NAME> (NAME lowercased, e.g. writes_to; reverse a relation with the ^ path operator, no stored inverse triple). Types are <https://littlebigbrain.com/class/NAME> (lowercased), matched as `?x a <…/class/NAME>` with explicit entailment=subclass, rdfs, or owl for inference (default none). Property fields are <https://littlebigbrain.com/p/NAME> (lowercased). The local name is ALWAYS lowercase — an uppercase one (e.g. <…/r/FOR_CLIENT>) is a different, non-existent IRI that silently matches nothing; this tool auto-lowercases the local name of /r/, /class/, and /p/ IRIs for you and adds a `notes` entry when it does, so a stray uppercase still resolves. (Structured mode's `predicate` is case-insensitive on its own.) Entities are content-addressed <https://littlebigbrain.com/e/HASH> — never build an entity IRI from a name; anchor a named entity by its label instead: `?e <http://www.w3.org/2000/01/rdf-schema#label> \"Acme\"`. Discover the exact relation and type names with lbb_inspect action=ontology. SELECT and ASK only (CONSTRUCT/DESCRIBE are rejected). Example: SELECT ?service ?db WHERE { ?service <https://littlebigbrain.com/r/writes_to> ?db } LIMIT 10"New value: +"SPARQL 1.1 query text (SELECT or ASK). Valid-time as_of is unsupported; use as_of_commit_seq for a retained commit snapshot. IRI scheme: relations are <https://littlebigbrain.com/r/NAME> (NAME lowercased, e.g. writes_to; reverse a relation with the ^ path operator, no stored inverse triple). Types are <https://littlebigbrain.com/class/NAME> (lowercased), matched as `?x a <…/class/NAME>` with explicit entailment=subclass, rdfs, or owl for inference (default none). Property fields are <https://littlebigbrain.com/p/NAME> (lowercased). The local name is ALWAYS lowercase — an uppercase one (e.g. <…/r/FOR_CLIENT>) is a different, non-existent IRI that silently matches nothing; this tool auto-lowercases the local name of /r/, /class/, and /p/ IRIs for you and adds a `notes` entry when it does, so a stray uppercase still resolves. (Structured mode's `predicate` is case-insensitive on its own.) Entities are content-addressed <https://littlebigbrain.com/e/HASH> — never build an entity IRI from a name; anchor a named entity by its label instead: `?e <http://www.w3.org/2000/01/rdf-schema#label> \"Acme\"`. Discover the exact relation and type names with lbb_inspect action=ontology. SELECT and ASK only (CONSTRUCT/DESCRIBE are rejected). Example: SELECT ?service ?db WHERE { ?service <https://littlebigbrain.com/r/writes_to> ?db } LIMIT 10. Search by meaning inside the query (the graph needs an embedding; see lbb_embeddings action=list): PREFIX search: <https://littlebigbrain.com/search#> SELECT ?x ?score WHERE { ?x search:similarTo \"card payments\" ; search:score ?score . ?x <https://littlebigbrain.com/r/calls> ?y } ORDER BY DESC(?score) LIMIT 5. The other patterns filter and join the hits. One search:similarTo per query, with a variable subject, in the main group (not inside OPTIONAL, UNION, MINUS or a subquery). The object is text, an entity IRI (records like that one) or a vector literal. search:top N sets the hits (1 to 1000; default the query LIMIT, then 10) and search:embedding \"name\" searches one embedding. FILTER(?score > 0.8) keeps close hits. The result's search field reports the plan (nearest, filter_first or search_first), the hits and complete. For a question in words, add search:rerank true ; search:relevance ?r and ORDER BY DESC(?r): the rerank model (Jev) reads the best hits with the text, keeps the top that answer it and binds its answer (0 to 1); search.rerank reports the status. Only a text query reranks."
    • addedInput schema / properties / question
      Added value: +{
      +  "description": "question: the question in plain words (required), 1 to 4,000 characters. The server writes the SPARQL query from a description of this graph, checks it, and runs it.",
      +  "minLength": 1,
      +  "type": "string"
      +}
    • addedInput schema / properties / rerank
      Added value: +{
      +  "description": "true: the managed rerank model (Jev) orders the best hits by how well each answers the text, and each hit gets its relevance (0 to 1); adds about 0.3 s. false: the similarity order. Omit it to follow the graph's search setting.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / route
      Added value: +{
      +  "description": "question: the kind of query, when you know it. Omit it and the router model selects it.",
      +  "enum": [
      +    "lookup",
      +    "aggregate",
      +    "search",
      +    "history",
      +    "schema",
      +    "unanswerable"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / run
      Added value: +{
      +  "description": "question: run the query and return its rows. Defaults to true; false returns the query only.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / timeline
      Added value: +{
      +  "description": "question: dated points that stand for the graph's commits, at most 200. A question about a date reads the commit of the latest point on or before it. Use it when the commits stand for other dates than the days they were written (a demo's milestones, an import of old records); without it the server reads the last commit written by the end of that day.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "as_of_commit_seq": {
      +        "description": "The commit that holds the graph as it was on that date.",
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "date": {
      +        "description": "YYYY-MM-DD.",
      +        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      +        "type": "string"
      +      },
      +      "label": {
      +        "description": "A name for the point, such as a milestone.",
      +        "maxLength": 200,
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "date",
      +      "as_of_commit_seq"
      +    ],
      +    "type": "object"
      +  },
      +  "maxItems": 200,
      +  "type": "array"
      +}
  2. Changed11 schema fields changedv0.5.2
    • removedInput schema / properties / branch
      Removed value: -{
      -  "description": "Branch to target; defaults to the connection's branch",
      -  "type": "string"
      -}
    • addedInput schema / properties / embedding
      Added value: +{
      +  "description": "One embedding by name (see lbb_embeddings action=list). Omit it to search every searchable class of the graph; each hit names its class.",
      +  "type": "string"
      +}
    • addedInput schema / properties / explain
      Added value: +{
      +  "description": "Plan without running: the scope, the resolved filter, and the allowed count. No model call.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / filter
      Added value: +{
      +  "description": "Conditions every hit must meet: {\"class\": …} or {\"via\": …, \"to\": …}. The response's filter shows how each condition resolved; an unknown relationship or name answers with the options.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "class": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          }
      +        ],
      +        "description": "A class condition: one class IRI or a list (any of); subclasses too. One class condition per search."
      +      },
      +      "direction": {
      +        "description": "With via: out (default), the hit links to the entity; in, the entity links to the hit.",
      +        "enum": [
      +          "out",
      +          "in"
      +        ],
      +        "type": "string"
      +      },
      +      "to": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          }
      +        ],
      +        "description": "With via: the entity, an IRI or a name (its label; a spelling slip resolves when one entity is clearly closest), or a list (any of)."
      +      },
      +      "via": {
      +        "description": "A relationship condition: the relationship, a local name (calls), prefix:name, or <iri>.",
      +        "type": "string"
      +      }
      +    },
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • addedInput schema / properties / include
      Added value: +{
      +  "description": "text: return the embedded text of each hit.",
      +  "items": {
      +    "enum": [
      +      "text"
      +    ],
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • changedInput schema / properties / mode / description
      Previous value: -"Selects the variant (one of: structured, sparql, analyze)."New value: +"Selects the variant (one of: structured, sparql, search, analyze)."
    • changedInput schema / properties / mode / enum
      Previous value: -[
      -  "structured",
      -  "sparql",
      -  "analyze"
      -]New value: +[
      +  "structured",
      +  "sparql",
      +  "search",
      +  "analyze"
      +]
    • addedInput schema / properties / probe
      Added value: +{
      +  "description": "Clusters to read (default 4·√clusters, at least 8). More reads cost latency and find more.",
      +  "exclusiveMinimum": 0,
      +  "type": "integer"
      +}
    • addedInput schema / properties / request
      Added value: +{
      +  "description": "The user's own words behind this query (managed evals). When present the server records an eval trace and the result carries its trace_id; label the rows valid or not with lbb_evals action=label. Omit on continuation pages.",
      +  "type": "string"
      +}
    • addedInput schema / properties / text
      Added value: +{
      +  "description": "The query text (required); embedded with the graph's model.",
      +  "type": "string"
      +}
    • addedInput schema / properties / top_k / maximum
      Added value: +200
  3. Changed6 schema fields changedv0.5.1
    • changedInput schema / properties / as_of / description
      Previous value: -"Snapshot pin (valid-time, RFC3339): evaluate the body as of this instant. Folded into the request's `as_of_valid_time`. Top-level here is the supported spelling — a bare `as_of` inside the body is rejected, since the server silently ignores it."New value: +"Unsupported in structured and SPARQL text modes; use as_of_commit_seq for a retained commit snapshot."
    • changedInput schema / properties / body / description
      Previous value: -"Structured SPARQL-subset request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: \"x\" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { \"compare\": { \"op\": <op>, \"left\": <term>, \"right\": <term> } } (or { \"and\": [<filter>…] }, { \"or\": [<filter>…] }, { \"not\": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { \"var\": \"x\" }, { \"property\": { \"var\": \"x\", \"field\": \"amount\" } } (a typed scalar attribute), or { \"value\": <typed> } — and <typed> is exactly one wrapper: { \"str\": \"…\" }, { \"i64\": 5 }, { \"f64\": 0.9 }, { \"bool\": true }, { \"date_time\": \"2026-01-01\" } (RFC3339), or { \"entity\": { \"entity_type\": \"T\", \"name\": \"N\" } }. Complete runnable example — deals whose amount ≥ 1000000: { \"patterns\": [{ \"subject\": { \"var\": \"d\" }, \"predicate\": \"for_client\", \"object\": { \"var\": \"c\" } }], \"filters\": [{ \"compare\": { \"op\": \"ge\", \"left\": { \"property\": { \"var\": \"d\", \"field\": \"amount\" } }, \"right\": { \"value\": { \"f64\": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: [\"s\"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { \"patterns\": [{ \"subject\": { \"var\": \"c\" }, \"predicate\": \"committed_to\", \"object\": { \"var\": \"repo\" } }], \"group_keys\": [{ \"date_bucket\": { \"var\": \"c\", \"field\": \"committed_at\", \"granularity\": \"month\", \"as\": \"m\" } }, { \"property\": { \"var\": \"c\", \"field\": \"area\", \"as\": \"area\" } }], \"aggregates\": [{ \"func\": \"count\", \"as\": \"n\" }], \"order_by\": [{ \"var\": \"m\" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { \"compare\": { \"op\": \"gt\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 10 } } } }). A `combinators` key (UNION/OPTIONAL/MINUS/EXISTS) is rejected here; express those with SPARQL text under mode=sparql. Cheap aggregate count: pair an equality having (e.g. { \"compare\": { \"op\": \"eq\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning prefer the top-level `as_of` / `as_of_commit_seq` arguments below; a bare `as_of` key inside the body is rejected (the body's valid-time field is `as_of_valid_time`)."New value: +"Structured SPARQL-subset request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: \"x\" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { \"compare\": { \"op\": <op>, \"left\": <term>, \"right\": <term> } } (or { \"and\": [<filter>…] }, { \"or\": [<filter>…] }, { \"not\": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { \"var\": \"x\" }, { \"property\": { \"var\": \"x\", \"field\": \"amount\" } } (a typed scalar attribute), or { \"value\": <typed> } — and <typed> is exactly one wrapper: { \"str\": \"…\" }, { \"i64\": 5 }, { \"f64\": 0.9 }, { \"bool\": true }, { \"date_time\": \"2026-01-01\" } (RFC3339), or { \"entity\": { \"entity_type\": \"T\", \"name\": \"N\" } }. Complete runnable example — deals whose amount ≥ 1000000: { \"patterns\": [{ \"subject\": { \"var\": \"d\" }, \"predicate\": \"for_client\", \"object\": { \"var\": \"c\" } }], \"filters\": [{ \"compare\": { \"op\": \"ge\", \"left\": { \"property\": { \"var\": \"d\", \"field\": \"amount\" } }, \"right\": { \"value\": { \"f64\": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: [\"s\"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { \"patterns\": [{ \"subject\": { \"var\": \"c\" }, \"predicate\": \"committed_to\", \"object\": { \"var\": \"repo\" } }], \"group_keys\": [{ \"date_bucket\": { \"var\": \"c\", \"field\": \"committed_at\", \"granularity\": \"month\", \"as\": \"m\" } }, { \"property\": { \"var\": \"c\", \"field\": \"area\", \"as\": \"area\" } }], \"aggregates\": [{ \"func\": \"count\", \"as\": \"n\" }], \"order_by\": [{ \"var\": \"m\" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { \"compare\": { \"op\": \"gt\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 10 } } } }). A `combinators` key (UNION/OPTIONAL/MINUS/EXISTS) is rejected here; express those with SPARQL text under mode=sparql. Cheap aggregate count: pair an equality having (e.g. { \"compare\": { \"op\": \"eq\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning use the top-level `as_of_commit_seq` argument or the same body field. Valid-time `as_of` and `as_of_valid_time` selectors are unsupported and rejected before HTTP."
    • addedInput schema / properties / consistency
      Added value: +{
      +  "description": "Read consistency. strong requires publication through head; a pending response is retryable.",
      +  "enum": [
      +    "eventual",
      +    "strong"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / entailment
      Added value: +{
      +  "description": "Reasoning over the pinned RDF generation. Defaults to none. owl includes RDFS, inverse relationships and the supported OWL profile.",
      +  "enum": [
      +    "none",
      +    "subclass",
      +    "rdfs",
      +    "owl"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / min_indexed_seq
      Added value: +{
      +  "description": "Read-after-write publication floor. Preserved across cursor pages.",
      +  "maximum": 9007199254740991,
      +  "minimum": -9007199254740991,
      +  "type": "integer"
      +}
    • changedInput schema / properties / query / description
      Previous value: -"SPARQL 1.1 query text (SELECT or ASK). IRI scheme: relations are <https://littlebigbrain.com/r/NAME> (NAME lowercased, e.g. writes_to; reverse a relation with the ^ path operator, no stored inverse triple). Types are <https://littlebigbrain.com/class/NAME> (lowercased), matched as `?x a <…/class/NAME>` with rdfs:subClassOf closure on by default. Property fields are <https://littlebigbrain.com/p/NAME> (lowercased). The local name is ALWAYS lowercase — an uppercase one (e.g. <…/r/FOR_CLIENT>) is a different, non-existent IRI that silently matches nothing; this tool auto-lowercases the local name of /r/, /class/, and /p/ IRIs for you and adds a `notes` entry when it does, so a stray uppercase still resolves. (Structured mode's `predicate` is case-insensitive on its own.) Entities are content-addressed <https://littlebigbrain.com/e/HASH> — never build an entity IRI from a name; anchor a named entity by its label instead: `?e <http://www.w3.org/2000/01/rdf-schema#label> \"Acme\"`. Discover the exact relation and type names with lbb_inspect action=ontology. SELECT and ASK only (CONSTRUCT/DESCRIBE are rejected). Example: SELECT ?service ?db WHERE { ?service <https://littlebigbrain.com/r/writes_to> ?db } LIMIT 10"New value: +"SPARQL 1.1 query text (SELECT or ASK). Valid-time as_of is unsupported; use as_of_commit_seq for a retained commit snapshot. IRI scheme: relations are <https://littlebigbrain.com/r/NAME> (NAME lowercased, e.g. writes_to; reverse a relation with the ^ path operator, no stored inverse triple). Types are <https://littlebigbrain.com/class/NAME> (lowercased), matched as `?x a <…/class/NAME>` with explicit entailment=subclass, rdfs, or owl for inference (default none). Property fields are <https://littlebigbrain.com/p/NAME> (lowercased). The local name is ALWAYS lowercase — an uppercase one (e.g. <…/r/FOR_CLIENT>) is a different, non-existent IRI that silently matches nothing; this tool auto-lowercases the local name of /r/, /class/, and /p/ IRIs for you and adds a `notes` entry when it does, so a stray uppercase still resolves. (Structured mode's `predicate` is case-insensitive on its own.) Entities are content-addressed <https://littlebigbrain.com/e/HASH> — never build an entity IRI from a name; anchor a named entity by its label instead: `?e <http://www.w3.org/2000/01/rdf-schema#label> \"Acme\"`. Discover the exact relation and type names with lbb_inspect action=ontology. SELECT and ASK only (CONSTRUCT/DESCRIBE are rejected). Example: SELECT ?service ?db WHERE { ?service <https://littlebigbrain.com/r/writes_to> ?db } LIMIT 10"
  4. Changed2 schema fields changedv0.4.2
    • changedInput schema / properties / body / description
      Previous value: -"Structured SPARQL-subset or analytics request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: \"x\" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { \"compare\": { \"op\": <op>, \"left\": <term>, \"right\": <term> } } (or { \"and\": [<filter>…] }, { \"or\": [<filter>…] }, { \"not\": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { \"var\": \"x\" }, { \"property\": { \"var\": \"x\", \"field\": \"amount\" } } (a typed scalar attribute), or { \"value\": <typed> } — and <typed> is exactly one wrapper: { \"str\": \"…\" }, { \"i64\": 5 }, { \"f64\": 0.9 }, { \"bool\": true }, { \"date_time\": \"2026-01-01\" } (RFC3339), or { \"entity\": { \"entity_type\": \"T\", \"name\": \"N\" } }. Complete runnable example — deals whose amount ≥ 1000000: { \"patterns\": [{ \"subject\": { \"var\": \"d\" }, \"predicate\": \"for_client\", \"object\": { \"var\": \"c\" } }], \"filters\": [{ \"compare\": { \"op\": \"ge\", \"left\": { \"property\": { \"var\": \"d\", \"field\": \"amount\" } }, \"right\": { \"value\": { \"f64\": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: [\"s\"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { \"patterns\": [{ \"subject\": { \"var\": \"c\" }, \"predicate\": \"committed_to\", \"object\": { \"var\": \"repo\" } }], \"group_keys\": [{ \"date_bucket\": { \"var\": \"c\", \"field\": \"committed_at\", \"granularity\": \"month\", \"as\": \"m\" } }, { \"property\": { \"var\": \"c\", \"field\": \"area\", \"as\": \"area\" } }], \"aggregates\": [{ \"func\": \"count\", \"as\": \"n\" }], \"order_by\": [{ \"var\": \"m\" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { \"compare\": { \"op\": \"gt\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 10 } } } }); it is evaluated only on this grouped path, NOT alongside `combinators` (UNION/OPTIONAL/MINUS), which route to the analytics engine. Cheap aggregate count: pair an equality having (e.g. { \"compare\": { \"op\": \"eq\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning prefer the top-level `as_of` / `as_of_commit_seq` arguments below; a bare `as_of` key inside the body is rejected (the body's valid-time field is `as_of_valid_time`)."New value: +"Structured SPARQL-subset request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: \"x\" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { \"compare\": { \"op\": <op>, \"left\": <term>, \"right\": <term> } } (or { \"and\": [<filter>…] }, { \"or\": [<filter>…] }, { \"not\": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { \"var\": \"x\" }, { \"property\": { \"var\": \"x\", \"field\": \"amount\" } } (a typed scalar attribute), or { \"value\": <typed> } — and <typed> is exactly one wrapper: { \"str\": \"…\" }, { \"i64\": 5 }, { \"f64\": 0.9 }, { \"bool\": true }, { \"date_time\": \"2026-01-01\" } (RFC3339), or { \"entity\": { \"entity_type\": \"T\", \"name\": \"N\" } }. Complete runnable example — deals whose amount ≥ 1000000: { \"patterns\": [{ \"subject\": { \"var\": \"d\" }, \"predicate\": \"for_client\", \"object\": { \"var\": \"c\" } }], \"filters\": [{ \"compare\": { \"op\": \"ge\", \"left\": { \"property\": { \"var\": \"d\", \"field\": \"amount\" } }, \"right\": { \"value\": { \"f64\": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: [\"s\"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { \"patterns\": [{ \"subject\": { \"var\": \"c\" }, \"predicate\": \"committed_to\", \"object\": { \"var\": \"repo\" } }], \"group_keys\": [{ \"date_bucket\": { \"var\": \"c\", \"field\": \"committed_at\", \"granularity\": \"month\", \"as\": \"m\" } }, { \"property\": { \"var\": \"c\", \"field\": \"area\", \"as\": \"area\" } }], \"aggregates\": [{ \"func\": \"count\", \"as\": \"n\" }], \"order_by\": [{ \"var\": \"m\" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { \"compare\": { \"op\": \"gt\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 10 } } } }). A `combinators` key (UNION/OPTIONAL/MINUS/EXISTS) is rejected here; express those with SPARQL text under mode=sparql. Cheap aggregate count: pair an equality having (e.g. { \"compare\": { \"op\": \"eq\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning prefer the top-level `as_of` / `as_of_commit_seq` arguments below; a bare `as_of` key inside the body is rejected (the body's valid-time field is `as_of_valid_time`)."
    • changedInput schema / properties / metric / enum
      Previous value: -[
      -  "entity_types",
      -  "relations",
      -  "overview",
      -  "facets",
      -  "sparql"
      -]New value: +[
      +  "entity_types",
      +  "relations",
      +  "overview",
      +  "sparql"
      +]
  5. Changed16 schema fields changedv0.2.7
    • removedInput schema / properties / anchor_name
      Removed value: -{
      -  "type": "string"
      -}
    • removedInput schema / properties / anchor_type
      Removed value: -{
      -  "type": "string"
      -}
    • removedInput schema / properties / explain
      Removed value: -{
      -  "type": "boolean"
      -}
    • removedInput schema / properties / include_derived
      Removed value: -{
      -  "type": "boolean"
      -}
    • removedInput schema / properties / max_derived
      Removed value: -{
      -  "exclusiveMinimum": 0,
      -  "type": "integer"
      -}
    • removedInput schema / properties / max_premises
      Removed value: -{
      -  "exclusiveMinimum": 0,
      -  "type": "integer"
      -}
    • removedInput schema / properties / max_rounds
      Removed value: -{
      -  "exclusiveMinimum": 0,
      -  "type": "integer"
      -}
    • removedInput schema / properties / max_solutions
      Removed value: -{
      -  "exclusiveMinimum": 0,
      -  "type": "integer"
      -}
    • changedInput schema / properties / mode / description
      Previous value: -"Selects the variant (one of: structured, sparql, shacl, infer, retrieval_premises, analyze)."New value: +"Selects the variant (one of: structured, sparql, analyze)."
    • changedInput schema / properties / mode / enum
      Previous value: -[
      -  "structured",
      -  "sparql",
      -  "shacl",
      -  "infer",
      -  "retrieval_premises",
      -  "analyze"
      -]New value: +[
      +  "structured",
      +  "sparql",
      +  "analyze"
      +]
    • removedInput schema / properties / query_top_k
      Removed value: -{
      -  "exclusiveMinimum": 0,
      -  "type": "integer"
      -}
    • removedInput schema / properties / relation
      Removed value: -{
      -  "type": "string"
      -}
    • removedInput schema / properties / rules
      Removed value: -{
      -  "items": {
      -    "additionalProperties": true,
      -    "properties": {
      -      "body": {
      -        "description": "The condition: a basic graph pattern over current edges (asserted + already-derived), joined on shared variables. Terms may be variables or fixed entities.",
      -        "items": {
      -          "additionalProperties": true,
      -          "properties": {
      -            "object": {
      -              "$ref": "#/properties/rules/items/properties/body/items/properties/subject",
      -              "description": "A rule term: { \"var\": \"x\" } (a variable) or { \"entity\": { \"entity_type\": \"DeliveryStatus\", \"name\": \"Complete\" } } (a fixed entity used as a constant in the body or head)"
      -            },
      -            "predicate": {
      -              "description": "Relation name. In a rule body, the reserved \"rdf:type\" makes a type-membership constraint: the object names a class ({ entity: { entity_type: \"Contact\" } }, no name) and matches every entity of that class and its subtypes (rdfs:subClassOf closure), so one rule keyed on a supertype fires for all subtypes. Not allowed in a head or an exists/not_exists filter.",
      -              "type": "string"
      -            },
      -            "subject": {
      -              "anyOf": [
      -                {
      -                  "additionalProperties": true,
      -                  "properties": {
      -                    "var": {
      -                      "description": "A variable, joined across patterns by name",
      -                      "type": "string"
      -                    }
      -                  },
      -                  "required": [
      -                    "var"
      -                  ],
      -                  "type": "object"
      -                },
      -                {
      -                  "additionalProperties": true,
      -                  "properties": {
      -                    "entity": {
      -                      "additionalProperties": true,
      -                      "properties": {
      -                        "entity_id": {
      -                          "description": "Entity id (hex), as an alternative to type+name",
      -                          "type": "string"
      -                        },
      -                        "entity_type": {
      -                          "description": "Entity type name (with `name`, names a fixed entity)",
      -                          "type": "string"
      -                        },
      -                        "name": {
      -                          "description": "Entity name (paired with `entity_type`)",
      -                          "type": "string"
      -                        }
      -                      },
      -                      "type": "object"
      -                    }
      -                  },
      -                  "required": [
      -                    "entity"
      -                  ],
      -                  "type": "object"
      -                }
      -              ],
      -              "description": "A rule term: { \"var\": \"x\" } (a variable) or { \"entity\": { \"entity_type\": \"DeliveryStatus\", \"name\": \"Complete\" } } (a fixed entity used as a constant in the body or head)"
      -            }
      -          },
      -          "type": "object"
      -        },
      -        "type": "array"
      -      },
      -      "combinators": {
      -        "description": "exists/not_exists filters folded over the body. not_exists is stratified negation — it lets a rule express a universal condition, e.g. derive \"phase complete\" only when not_exists an incomplete deliverable. A negation cycle is rejected.",
      -        "items": {
      -          "anyOf": [
      -            {
      -              "additionalProperties": true,
      -              "properties": {
      -                "exists": {
      -                  "items": {
      -                    "$ref": "#/properties/rules/items/properties/body/items"
      -                  },
      -                  "type": "array"
      -                }
      -              },
      -              "required": [
      -                "exists"
      -              ],
      -              "type": "object"
      -            },
      -            {
      -              "additionalProperties": true,
      -              "properties": {
      -                "not_exists": {
      -                  "items": {
      -                    "$ref": "#/properties/rules/items/properties/body/items"
      -                  },
      -                  "type": "array"
      -                }
      -              },
      -              "required": [
      -                "not_exists"
      -              ],
      -              "type": "object"
      -            }
      -          ],
      -          "description": "An existence filter over the body solutions: { exists: [...] } (semijoin — keep rows with a compatible match) or { not_exists: [...] } (negation/antijoin — keep rows with none)"
      -        },
      -        "type": "array"
      -      },
      -      "head": {
      -        "$ref": "#/properties/rules/items/properties/body/items",
      -        "description": "The triple derived once per body solution. Every head variable must be bound by the body; a fixed-entity object derives a constant (e.g. set the rolled-up status to the Complete entity)."
      -      },
      -      "name": {
      -        "type": "string"
      -      },
      -      "order": {
      -        "description": "Run order, low to high (a determinism hint)",
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  "type": "array"
      -}
    • removedInput schema / properties / shacl_mode
      Removed value: -{
      -  "description": "select returns focus nodes; validate returns a report",
      -  "enum": [
      -    "select",
      -    "validate"
      -  ],
      -  "type": "string"
      -}
    • removedInput schema / properties / shapes
      Removed value: -{
      -  "items": {
      -    "$ref": "#/properties/body"
      -  },
      -  "type": "array"
      -}
    • removedInput schema / properties / threshold
      Removed value: -{
      -  "maximum": 1,
      -  "minimum": 0,
      -  "type": "number"
      -}
  6. First observedv0.2.1

TDQS

A4.1/5.0
Behavior5/5

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

With only readOnlyHint available in annotations, the description carries real behavioral burden and delivers: questions consume model tokens and count toward a daily limit, structured/text queries pin one watermark, strong consistency requires publication through head and a pending response is retryable, errors if past head, CONSTRUCT/DESCRIBE and combinators are rejected, uppercase IRIs are auto-lowercased with a notes entry, and rerank adds ~0.3s. 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.

Conciseness4/5

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

Front-loads the mode list and the highest-value routing rule (mode=question) before descending into detail, so the critical information is not buried. It is dense and long with some tangential depth (multiple full examples, rerank mechanics), but most sentences earn their place.

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

Completeness4/5

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

For a 32-parameter, nested-object tool with no output schema, the description is close to complete: it explains return shape of question mode (route, rationale, query, rows) and points to the result's search field for the plan. Minor gaps remain around pagination/cursor behavior, but overall it equips an agent to call it correctly.

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

Parameters3/5

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

Schema description coverage is 84%, so the schema already documents most parameters, including the elaborate body shape. The description adds mode-selection meaning and a couple of worked patterns, but for individual parameters it largely restates what the schema provides, so baseline 3 is appropriate.

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

Purpose4/5

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

States a specific verb (reads) and resource (the graph) and enumerates the five modes with a one-line gloss of each, so an agent knows this is the analytical/expert query surface. It names siblings (lbb_inspect, lbb_embeddings, lbb_evals) as supporting steps, though it does not sharply draw the boundary against them for tool selection.

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

Usage Guidelines4/5

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

Gives explicit when-to-use routing: 'Use mode=question when you have a question in plain words and no SPARQL query,' 'Continue or correct that query with mode=sparql,' and the SPARQL-vs-search distinction ('SPARQL is the query language; search finds what the words describe'). It also lays out a planning workflow (explain=true, lbb_embeddings action=list). It stops short of explicit when-not-to-use guidance against siblings.

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