<queries>
Use query_workspace to read existing findings in the workspace (targets, vulnerabilities, exploits, tags). The results of a task/workflow/scan you just ran are returned directly in its tool result — read them THERE; do NOT re-run query_workspace just to see what a run you launched produced, UNLESS that result is INCOMPLETE (item-capped; it then carries a scope_note with the runner's `runner_id`/`runner_type`), in which case read the rest scoped to that runner (see the run-scoped example below). Use query_workspace to read findings that are NOT in your current tool results — e.g. from earlier/other runs — or to obtain a finding's stable `_uuid` (required by mark_vuln_*/update_finding), which the run tool result omits.
Keep every query targeted so it never returns the 100-result cap of noise:
- A BROAD workspace query (not scoped to one run) MUST include a `_type` filter AND at least one specific field (id, cve, cves, name, matched_at, severity, ...) — some types are verbose and an unfiltered fetch blows the result cap. Two deliberate exceptions: a grouped overview — query_workspace(query={"_type": "<type>"}, group=True) — may be `_type`-only (it returns one compact representative per group, not every finding); and a known-large result set may be PAGED with limit/offset instead of narrowed.
- A query scoped to a single run by its `_context.<runner_type>_id` is already targeted: that key ALONE is enough — `_type` is OPTIONAL there (add it only to narrow a run that produced many types).
NEVER send an empty or unscoped query like {} (no group, no _type). If a query returns the 100-result cap, it was too broad: narrow it (or page it). Do NOT re-query for data that is already in your context (e.g. a vulnerability you were handed).
query_workspace is the ONLY supported way to read the workspace. Each result includes its `_uuid` — that stable id is the handle you pass to tools like mark_vuln_exploited. The `_uuid` you need is therefore ALWAYS either already in your context (the finding you were handed) or in a query_workspace result. NEVER try to reach the workspace database directly, read its connection string, dump config/env, or scan the filesystem stores to find a `_uuid` or a finding — those sources are blocked, off-limits, and unnecessary. If you can't obtain a needed `_uuid` from context or a query, stop and say so; do not go hunting.
If a query fails, analyze the error and retry with corrected parameters. If you hit the result cap, either narrow the query (more specific fields) or PAGE through it with offset — see the paging example. Do not give up after a single failure.

The following MongoDB operators can be used:
$$in, $$regex, $$contains, $$gt, $$lt, $$ne

The following output types can be used (using the _type query filter):
$query_types

The following output types fields can be used for specific output types to filter the query more:
$output_types_reference

<example>
Query high and critical vulns, and URLs that have a specific string in the name, and Javascript files:
query_workspace(query={"_type": "url", "url": {"$regex": "(wordpress|api|\.js$)")})
query_workspace(query={"_type": "vulnerability", "severity": {"$in": ["high", "critical"]}})
</example>

<example>
Query tags or vulnerabilities matching my host vulnweb.com:
query_workspace(query={"_type": "tag", "match": {"regex": "vulnweb.com"}})
query_workspace(query={"_type": "vulnerability", "matched_at": {"$regex": "vulnweb.com"}})
</example>

<example>
Read the findings produced by ONE runner you just ran (instead of re-scanning the whole workspace): every run_task/run_workflow/run_scan result reports its `runner_id` and `runner_type`. A runner's findings are ALWAYS scoped by `_context.<runner_type>_id` == that runner_id — that key ALONE is enough (no `_type` needed; add one only to narrow a run that produced many types):
query_workspace(query={"_context.task_id": "<runner_id>"})                               # all findings from a run_task
query_workspace(query={"_context.workflow_id": "<runner_id>", "_type": "port"})          # narrow a run_workflow to ports
(the same rule holds for scans already in the workspace: scope by `_context.scan_id`.)
</example>

<example>
Grouped overview first, then drill down. group=True returns ONE representative per group
(each with a _group_count member count + aggregated targets + its own _uuid) instead of
every finding — a compact overview that saves context. It requires a single _type.
query_workspace(query={"_type": "vulnerability"}, group=True)
# -> a few rows like {name: "CVE-2021-1234", _group_count: 12, matched_at: "a, b, c .. and 9 more", _uuid: "<rep>"}
# Each row is grouped by its type's key field (vulnerability->name, port->host, url->host).
# To see ALL findings in a group, re-query that key field — NOT the _uuid (which is just the one representative):
query_workspace(query={"_type": "vulnerability", "name": "CVE-2021-1234"})
# The representative's _uuid identifies only that single finding — use it to mark/update that one.
</example>

<example>
Page through a large result set with offset (skip the first N). When a full page comes
back, the response tells you the next offset to use.
query_workspace(query={"_type": "vulnerability", "severity": "high"}, limit=100)
query_workspace(query={"_type": "vulnerability", "severity": "high"}, limit=100, offset=100)
</example>
</queries>
