Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
AI_R_EXTRASNoOptional extras to install: 'tokens', 'semantic', or 'http'. Used during installation, not at runtime.
AI_R_MCP_HOSTNoBind host for the HTTP transport.127.0.0.1
AI_R_MCP_PORTNoPort for the HTTP transport.8756
AI_R_HTTP_TOKENNoBearer token for HTTP transport. Required if AI_R_MCP_ALLOW_REMOTE=1.
AI_R_MCP_IDLE_SECNoIdle self-exit threshold for the HTTP server.
AI_R_MCP_TRANSPORTNoTransport for the MCP server: 'stdio' (default) or 'http'.stdio
AI_R_MCP_ALLOW_REMOTENoSet to '1' to allow non-loopback bind. Requires AI_R_HTTP_TOKEN.0
AI_R_SEMANTIC_THREADSNoMaximum CPU threads for onnxruntime semantic re-ranking.2
AI_R_SEMANTIC_IDLE_SECNoIdle seconds before unloading the semantic model (frees ~118 MB RAM).300
AI_R_HAYSTACK_CACHE_MAXNoSearch cache ceiling by entry count.
AI_R_HAYSTACK_CACHE_CHARS_MAXNoSearch cache ceiling by total size.

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
list_sessionsA

List discoverable sessions, optionally filtered by agent.

Results are sorted by date (newest first) and paginated with limit/offset so the payload stays small. The default limit guards against dumping an unbounded number of sessions.

Each summary carries kind ("agent" for a top-level session, "subagent" for a spawned subagent/sidechain) and parent_uuid (the parent session's uuid for subagents, else None). Subagent detection covers Claude, OpenCode, Codex and Pi; Antigravity's format has no parent signal, so it always reports kind="agent".

Each summary also carries the F1.4 origin fields, None when the source format has no signal (never fabricated):

  • project_dir — the project directory the session ran in (Claude: transcript cwd / Desktop metadata / verified slug decode; Codex: session_meta.cwd; OpenCode: session.directory; Pi: header cwd; Antigravity: no signal).

  • launch_surface — where the session was driven from (Claude: "claude-cli" | "claude-desktop"; Codex: the raw originator, e.g. "codex_vscode"; Antigravity: "antigravity-ide" | "antigravity-cli"; OpenCode/Pi: no signal).

Each summary also carries models — the unique model ids observed in the session, in order of first appearance (Claude: assistant message.model; Codex: turn_context.model; OpenCode: message.data.modelID; Pi: assistant message.model; Antigravity records no model signal). [] when the format carries no signal — honest absence, never fabricated.

Each summary also carries the A3 recency signal, measured against a single wall-clock now sampled once for the whole call:

  • last_activity — the last-activity timestamp as an explicit ISO string (same instant as date; date is kept for backward compatibility);

  • age_sec — whole seconds since last_activity (clamped at 0 when a future timestamp implies writer/reader clock skew);

  • activity"fresh" if age_sec is at or under the AI_R_STALL_SEC threshold (default 600 s = 10 min), "stale" if past it.

Honest contract (F1.1): activity describes only the recency of the last written record. It is not a claim about process liveness — a session file cannot show whether its producer is still running. "Running but silent" vs. "crashed" is a consumer-side inference (correlate activity == "stale" with an OS pid-alive check); ai-r does not fabricate it.

Args: agent: One of claude, codex, opencode, antigravity, pi. When omitted, every supported agent is queried. limit: Max sessions in this page. 0 means no cap (use with care: may return a very large payload). Defaults to 100. offset: Zero-based index of the first session to return. Use with limit to page through total. kind: Optional filter. "agent" returns only top-level sessions, "subagent" returns only subagent sessions. When omitted (default), both kinds are returned. noise: Noise filter — a session is noise when it is a spawned subagent (kind == "subagent" or parent_uuid set). "include" (default) returns everything, "exclude" drops noise sessions, "only" returns only noise sessions. kind and noise compose (AND). project_dir: Keep only sessions whose project_dir equals this path or is a descendant of it (path-boundary aware: /a/b matches /a/b and /a/b/sub, never /a/bc); trailing slashes ignored. Sessions without a project_dir signal never match. Composes with the other filters (AND). redact: When True (default) secrets in emitted title / extra values are masked as [REDACTED_<TYPE>] and the response carries a redactions type→count dict when any replacement happened; False returns raw titles.

Returns: {"sessions": [...], "total": int, "offset": int, "limit": int, "truncated": bool}. total is the full count matching the agent (and kind/noise) filter; truncated is True when more sessions remain beyond this page. When total == 0 the dict additionally carries diagnostics (scanned agents + session counts, source-dir presence, cause hints) so an empty inventory is explainable.

read_sessionA

Read a single session by uuid; agent is an optional hint.

Args: uuid: Session identifier. agent: One of claude, codex, opencode, antigravity, pi. Optional: when omitted, the uuid is looked up across every parser (session ids are unique across agents in practice). If — rarely — the same id exists under several agents, a candidates list is returned (not an error) so the caller can re-ask with an explicit agent. offset: Zero-based index of the first message to return (applied to the projected {role, content} list). limit: Maximum number of messages to return. Defaults to :data:_MESSAGES_CAP (100). A non-positive value means "no upper bound". redact: When True (default) secrets in the emitted title, message content/intent/qa are masked as [REDACTED_<TYPE>] and the response carries a redactions type→count dict when any replacement happened (see ai_r.redact); False returns the raw content. with_tokens: When True (F3.3) attach token usage read at request time (nothing runs in the background):

    * ``summary["tokens"]`` — the session's flat
      :func:`ai_r.tokens.session_tokens` block (exact where the agent
      records usage, a labeled estimate otherwise, honest
      ``source=None`` without any signal);
    * ``summary["component_tokens"]`` — the
      :func:`ai_r.tokens.component_tokens` breakdown: the transcript's
      estimated token volume split across ai-r's event taxonomy
      (``user_turn`` / ``assistant_turn`` / ``thinking`` / ``plan``
      and a ``tool_call`` per-``tool_kind`` sub-dict), always
      ``source="estimate"`` (never merged with the exact tier),
      ``None`` on an empty transcript;
    * per-message ``tokens`` on projected entries that carry exact
      usage — Claude (deduplicated per streamed API call), OpenCode
      and Pi.  Codex / Antigravity / user turns carry **no**
      ``tokens`` key at all (absent, not ``null``).  The dedup/attach
      is decided on absolute message positions BEFORE pagination, so
      page boundaries never shift which record is "first" for a call.

    Default ``False``: output is byte-identical to the historical
    shape (no ``tokens`` / ``component_tokens`` key on the summary or
    any message).  The token blocks carry only integers and
    ai-r-authored labels (never raw session text), so they stay
    outside the F2.1 redaction pass by construction.
include_subagents: When ``True`` attach
    ``summary["subagent_rollup"]`` — the parent session's
    ``component_tokens`` block plus one per spawned subagent child
    (resolved via :func:`ai_r.session_stats.children_of` on
    ``parent_uuid``) and a ``total`` folding parent + children through
    the ``aggregate`` ``component_tokens`` metric.  Each child entry
    carries ``uuid`` / ``agent`` / ``component_tokens`` (the estimate)
    plus **what it actually cost**: ``tokens`` read from the child's own
    transcript on the honest three-tier ``source`` ladder — billed
    ``"exact"`` where the child's transcript records usage, a labeled
    ``"estimate"`` where it does not (a truncated / reference-only run),
    ``source=None`` without any signal (never a fabricated zero) —
    ``models`` (the model(s) it ran on — a persona pinned to a cheaper
    tier shows up here), ``subagent_type`` (its persona, from the
    child's own spawn metadata, falling back to the spawning call's
    sidecar) and ``status`` where the spawn recorded one
    (``"async_launched"`` for a background spawn).  A childless parent
    (or an agent like Antigravity that never records ``parent_uuid``)
    yields an empty ``children`` list and a ``total`` equal to the
    parent's own block — honest, not an error.  Independent of
    ``with_tokens``.  Default ``False``.
include_thinking: When ``True`` attach the model's reasoning
    (``message.thinking``) as a separate ``thinking`` string field on
    each projected message that carried it — kept OUT of ``content``
    so the historical text projection is unchanged.  Default
    ``False``: output is byte-identical to the historical shape (no
    ``thinking`` key on any message).  Reasoning is opt-in to save the
    caller's budget; ``Event.has_thinking`` / the presence of the key
    signals which messages have it.  Fail-soft: a message without
    reasoning never carries the key.

Returns: A dict with session metadata plus:

* ``messages`` — the projected ``{role, content}`` list, sliced
  to ``[offset:offset+limit]``.
* ``total`` — the full uncapped projected message count (the
  length the slice was taken from).
* ``offset`` / ``limit`` — the pagination echo values actually
  used.
* ``messages_truncated`` — True when the MCP hard cap stopped
  extraction before every projected message could be returned.
* ``outcome`` — session outcome classification (F2.3):
  ``{status: success|failure|mixed|unknown, signals, user_verdict,
  markers, tool_results, tool_errors, error_rate,
  error_rate_reliable}``.  ``status`` combines the tool-call
  error rate (real flag only for Claude/OpenCode —
  ``None`` elsewhere, never guessed) with a calibrated bilingual
  success/failure dictionary over the tail user turns;
  ``"unknown"`` when neither signal exists (SSOT
  :mod:`ai_r.outcome`).

On an id collision (agent omitted, several agents own the id):
``{"ambiguous": True, "uuid": ..., "candidates": [...],
"count": N, "message": ...}`` where each candidate is a session
summary carrying its ``agent``.

On a missing session, returns an ``error`` dict instead of
raising (``agents_scanned`` lists the parsers probed when the
agent was omitted).
find_file_editsA

Find every file edit across sessions, cross-agent by default.

redact=True (default) masks secrets in emitted record fields as [REDACTED_<TYPE>] and adds a redactions type→count dict when any replacement happened; redact=False returns raw content.

Reference-by-default: to keep an audit listing small, each record does not inline the full edit body. Instead it carries a light-weight reference — input_sha256 (hash of the body) and input_chars (its length) — so you can see a body exists and how big it is. Fetch the body on demand with get_body / read_session (keyed by session_uuid

  • message_index). Pass include_input=True to inline the full body under input instead.

Size-bounded output: over-long intent / assistant fields are cut with a …[truncated] marker (named in the per-record truncated_fields) and emission stops at a total byte budget (output_truncated — distinct from the count-based truncated).

Default time window: a call with NO narrowing filter at all (no agent / since / until) is scoped to the last 7 days instead of the whole corpus; the response then carries default_since (the applied bound) plus a note saying so. Any explicit scope disables the default — pass e.g. since="1970-01-01" to deliberately scan the full history.

Thin wrapper over :func:ai_r.find_file_edits.find_file_edits that translates the core ValueError contract into the {"error": "invalid_argument", "message": str(exc)} shape the MCP client expects.

find_tool_callsA

Find every tool call across sessions, cross-agent by default.

redact=True (default) masks secrets in emitted record fields as [REDACTED_<TYPE>] and adds a redactions type→count dict when any replacement happened; redact=False returns raw content. Filters always match the RAW, pre-redaction text.

session scopes the scan to a single session uuid (or a list of uuids) — same semantics as the query facet. None = every session; a wide since/until with NO session therefore surfaces calls from unrelated sessions, so pin it when auditing one conversation.

Exactly one of tool_name (exact, case-insensitive) or tool_name_pattern (substring, case-insensitive) must be set.

Optional filters combine with AND: input_contains / output_contains (case-insensitive substring on the full, pre-cap input/output), output_excludes (drop records whose output contains it) and is_error (tri-state: None all, True failures only, False successes only). output_mode selects output truncation — "head"/"tail"/"smart"; None is adaptive ("smart" on errors, "head" otherwise). Each record also carries is_error_reliable (True only for Claude/OpenCode) plus the wrapper-aware classification: tool_kind (edit/write/read/bash/task/skill/mcp/ web/other) and tool_resolved — the real name under a Skill/Task/MCP wrapper (subagent type, skill name, or "<server>:<tool>"); None when there is no wrapper or the input carries no name signal.

A record whose call has a correlated result also carries tool_use_id — the join key back to a spawned subagent's own session (the child stores it as extra.spawn_tool_use_id). On a spawn (tool_kind="task") it additionally carries subagent: what the child COST — model (the model it actually resolved to, which may be a cheaper pinned tier than the parent's), agent_type (persona), tokens (EXACT billed usage, source="exact", full token-block shape), status, duration_ms, tool_uses. Honest gaps: a background spawn (status="async_launched", sidecar written before the run exists) reports its model with no tokens key — never a fabricated zero; its real cost and persona come from read_session(include_subagents=True)subagent_rollup.children. A record carrying several tool results drops the sidecar rather than billing it to the wrong subagent.

with_subagent_cost=True (opt-in) recovers exactly that for the spawn records here: each subagent sidecar is JOINED to the spawned child's own files, adding the persona (agent_type) from the child's agent-*.meta.json, the models it ran on, its EXACT billed tokens (source="exact", an estimate is never merged into the billing field) and child_uuid. So a background spawn — anonymous and price-less in the launch-time sidecar — becomes a named, priced row. The child is preferred over the sidecar, which stays the fallback for a child that cannot be joined (not yet on disk, meta corrupt): its tokens are then left absent, never zeroed. Default False reads no per-spawn child file (a cross-corpus scan does not pay the join).

Thin wrapper over :func:ai_r.find_tool_calls.find_tool_calls that translates the core ValueError contract into the {"error": "invalid_argument", "message": str(exc)} shape the MCP client expects.

session_statsA

Summarise sessions, grouped and ranked — the bird's-eye audit view.

Where find_file_edits / find_tool_calls return flat record streams, this rolls the sessions themselves up by one dimension so you can see how the work is distributed in a single call.

group_by is one of:

  • "agent" (default) — claude vs codex vs opencode vs ...

  • "dir" — by working directory / project (the normalized project_dir first — one real directory = one bucket across agents — then cwd for codex/pi / project slug for claude; "(unknown)" for agents without any signal).

  • "date" — by calendar day (YYYY-MM-DD).

  • "kind" — top-level agent sessions vs spawned subagent sessions.

  • "model" — by the model that produced the session. A session that mixed models buckets as "(mixed)" rather than being attributed to one of them; one whose transcript records no model is "(unknown)". Neither is guessed. Pair with with_tokens=True to see what each model actually cost.

Each group carries its session count plus enrichment from the shared find_file_edits core: edits (file edits attributed to the group's sessions), intents (distinct requests behind those edits), the distinct agents in the group, and total messages.

RISK-4 note: subagent detection is currently Claude-only. When no subagent sessions are in scope, a group_by="kind" result shows a single agent bucket — so the result always carries kind_split_available (False here) plus a note making clear that this is NOT a verified "no subagents", just an absent split.

with_tokens=True (F3.3) additionally reads every matched session's token usage at request time (nothing runs in the background) and adds a folded tokens block to each group and to totals: {input, output, reasoning, cache_read, cache_write, total, exact, estimated, unknown}. Per session the numbers are exact where the agent's own files record usage (Claude message.usage, Codex token_count, OpenCode message.data.tokens, Pi usage); a session without a recorded signal (e.g. Antigravity) gets a transcript-volume estimate — tokenized with the optional tiktoken dependency (pip install "ai-r[tokens]") when installed, else a rough chars/4 heuristic — and counts under estimated, never silently mixed in as exact; no signal at all counts under unknown. Sums that no session carried stay null (never a fabricated 0). The block contains only ai-r-computed integers and labels — no raw session text — so it is outside the redaction surface by construction. Default False: byte-identical historical output, no extra reads.

Scan guard (token_scan_limit): because with_tokens reads every matched session's files at request time, an unscoped run over a huge corpus is a multi-hour I/O storm. When with_tokens is set with no narrowing filter (agent/since/until) and more than token_scan_limit sessions match, the call returns {"error": "scope_required", ...} (naming the count and the limit) INSTEAD of scanning — the check runs on the cheap inventory count before any file is read. Narrow the scope, or raise token_scan_limit (0 disables the cap) to force the full scan. A permitted-but-large scan runs but carries a warning.

Thin wrapper over :func:ai_r.session_stats.session_stats that translates the core ValueError contract into the {"error": "invalid_argument", "message": str(exc)} shape the MCP client expects.

incidentsA

Dangerous shell commands + regret reactions — the incidents preset.

One call answers "where did an agent run something destructive — and did it then apologise?". A preset over the existing core, not a second engine: ONE query scan (type="tool_call", tool_kind="bash") supplies the candidates, a deterministic danger dictionary (harvested from public agent-guardrail rule sets, calibrated on real history) selects the dangerous commands, and a bilingual (ru + en) regret dictionary scans the next reaction_window messages (default 6) for an apology/rollback reaction — the two-step check behind the confirmed flag. Zero LLM, zero guessing: no dictionary hit → no incident; no reaction → confirmed: false, never inferred.

Filters (all parameters): agent, session (uuid or list of uuids), since/until (ISO bounds on the call ts), category (fs/git/db/net — unknown values fail loud), confirmed (include default | only | exclude), noise and project_dir (session-level, same semantics as query).

Each incident record carries the query event id (walk its context via query(relative_to=...) / read_session), the matched patterns + categories, a char-capped command fragment centred on the hit (token budget — full context stays on-demand), is_error (null when the agent's format has no correlated outcome signal — honest, cross-agent), confirmed and reaction (message_index/offset/role/marker labels/capped preview; null when unconfirmed). count/confirmed_count/ by_pattern always reflect the FULL match set; limit (default 50, 0 = no cap) bounds only the emitted records (truncated).

Dictionary caveat (documented, not hidden): patterns are a deterministic dictionary, not a shell interpreter — a command that merely mentions a dangerous string (e.g. echo "rm -rf /") can still match. Matching runs on the extracted command field (a Bash description alone never fires) and always on the RAW stored text; redact=true (default) masks secrets only in the emitted session_title/command/reaction.preview fields (redactions type→count dict when anything was masked). When count == 0 the response carries diagnostics so an empty result is explainable (missing source dir vs all-excluding filter vs a genuinely clean history).

Thin wrapper over :func:ai_r.incidents.incidents that translates the core ValueError contract into the {"error": "invalid_argument", "message": str(exc)} shape the MCP client expects.

networkA

Network-egress audit — the network preset (F4.3).

One call answers "where did an agent reach out to the network — and how risky did those requests look?". A preset over the existing core, not a second engine: ONE query scan (type="tool_call", tool_kind="web") supplies the candidates — Claude WebFetch/WebSearch, OpenCode webfetch, Codex web_search (surfaced from web_search_call rollout records), Gemini/Antigravity web_fetch/google_web_search; Pi records no web tool (honest absence). The request target (url/query) is extracted from each call's own input and assessed with a deterministic risk dictionary: plain_http, credentials_in_url, secret_in_url / secret_in_query (the redaction patterns double as the detector), ip_literal_host, private_or_local_host, punycode_host. Zero LLM, zero guessing: no extractable target → honest null fields; a risk fires only on parse/regex evidence.

Filters (all parameters): agent, session (uuid or list of uuids), since/until (ISO bounds on the call ts), kind (fetch|search — derived from the extracted fields, unknown values fail loud), risk (include default | only | exclude), domain (host equals-or-subdomain match), noise and project_dir (session-level, same semantics as query).

Each request record carries the query event id (walk its context via query(relative_to=...) / read_session), the derived kind, char-capped url/query (token budget — full context stays on-demand), domain, the risks labels and tri-state is_error (null when the agent's format has no correlated outcome signal — honest, cross-agent). count/risky_count/ by_domain/by_risk always reflect the FULL match set; limit (default 50, 0 = no cap) bounds only the emitted records (truncated).

Honesty caveats (documented, not hidden): risk labels are a deterministic dictionary, not a threat oracle; MCP-mediated network access (browser-automation servers etc.) stays under tool_kind="mcp" — a name alone cannot prove an MCP server touches the network, so it is never guessed into this audit. Risk assessment runs on the RAW stored strings; redact=true (default) masks secrets only in the emitted url/query/session_title fields (redactions type→count dict when anything was masked). When count == 0 the response carries diagnostics so an empty result is explainable.

Thin wrapper over :func:ai_r.network.network that translates the core ValueError contract into the {"error": "invalid_argument", "message": str(exc)} shape the MCP client expects.

quotesA

User quotes of prior in-session content — the quotes preset.

One call answers "where did the user quote something the agent said, and what did they say about it?". When a user selects a chunk of a prior message and comments on it (the "attach selection as context" flow), the quoted text is embedded VERBATIM in their turn — no agent records it as a structured field — so it is recovered by matching the user turn against the text before it. A preset over the existing core, not a second engine: query scans supply user turns + assistant turns, the reviewed text is normalized (_normalize_rendered_text, reused from the plan-feedback anchorer) and :func:difflib.SequenceMatcher finds the longest verbatim run; a run below the minimum is not a quote (honest null), and text pasted from OUTSIDE the session matches nothing (never fabricated).

This is the cross-agent, chat-wide generalization of plan(feedback) (which surfaces «plan quote → user comment» only for Claude's plan-approval flow): quotes surfaces «any prior message quote → user comment» for every agent, operating on the normalized event stream, not client markup.

Filters (all parameters): agent, session (uuid or list), since/until (ISO bounds on the user turn's ts), source_kind (currently assistant — a user quoting the agent's prose; unknown values fail loud), noise and project_dir (session-level, same as query).

Each record carries the user_turn event id (context on-demand via query(relative_to=...)), source_id (the quoted assistant turn), source_kind, quote_chars, and the char-capped quote + comment (the user's turn with the quote elided). count/by_source_kind reflect the FULL matched set; limit (default 50, 0 = no cap) bounds only the emitted records (truncated).

Caveat (documented): v1 sources are assistant prose (the common case — quoting a tool's raw output is a future extension); the emitted quote/comment come from the NORMALIZED text (markdown stripped), so they are readable but not byte-identical to the raw turn (raw bodies stay reachable via the event ids). redact=true (default) masks secrets only in the emitted session_title/quote/comment. When count == 0 the response carries diagnostics so an empty result is explainable.

Thin wrapper over :func:ai_r.quotes.quotes that translates the core ValueError contract into the {"error": "invalid_argument", "message": str(exc)} shape the MCP client expects.

audit_briefA

Token-lean, budgeted session digest for auditors — the audit_brief preset.

One call answers "what happened in this session, verbatim where it matters", inside a hard character budget. A preset over the existing core, not a second engine (project preset rule): ONE query scan over the session's events supplies the user turns (VERBATIM — the auditor's ground truth) and the tool/file footprint (folded by aggregate(group_by="tool_kind") + the edit/write rows' existing file refs); the plan/plan_feedback projections supply the decision trail; :mod:ai_r.tokens (the same SSOT behind session_stats(with_tokens) / read_session(with_tokens)) supplies the token breakdown.

Deterministic budget algorithm: build the full digest, then tighten in a FIXED ladder until the serialized JSON fits budget_chars (default 15000; 0 = unlimited) — (1) drop tool-call error details, (2) drop the per-file edit list, (3) drop plan bodies + feedback quote/comment texts (counts/references always stay; bodies on-demand via get_body). User turns are NEVER truncated: if they alone exceed the budget the response carries budget.over_budget: true + a note naming the full projections — never a silently clipped ground truth.

session accepts the full uuid or a unique id prefix (e.g. the 8-hex head), resolved through the SAME id-prefix matching locate uses — the digest's session.uuid echoes the full resolved id; an ambiguous prefix is invalid_argument naming the candidates (capped), zero matches is not_found with closest-title suggestions. agent is an optional hint (None = the id resolves across every parser, like read_session). redact=true (default) masks secrets in the emitted title / user texts / plan bodies / feedback pairs. The response is section-structured (session / user_turns / plans / tools / files / tokens / component_tokens / budget); the CLI mirror is ai-r audit-brief <uuid> (markdown, --json for this dict).

Thin wrapper over :func:ai_r.audit_brief.audit_brief: a ValueError becomes {"error": "invalid_argument"}, an unknown session id {"error": "not_found"}.

locateA

Find a session across all agents by uuid / id-prefix / title — locate.

One call answers "I remember a session — where does it live and how do I read it?". needle is a full uuid, an id prefix (e.g. the 8-hex head), or a case-insensitive title substring. A thin preset over the existing per-parser inventory (the same list_sessions walk — zero new scanning code) with a deterministic algorithm inside: prefix-match on uuid/path-stem OR substring-match on title, ranked by last activity (mtime) descending. Each match carries where it lives (path / agent / project_dir / date / size_bytes), the honest local-content claim readable (false for a reference-only stub whose transcript is not on this machine), and the ready-to-run commands: read_command (ai-r read <uuid> --agent <agent>) + resume_command (F2.2 — text only, never executed).

limit bounds the emitted list (0 = no cap; count keeps the full total, truncated flags the cut). Zero matches → an honest empty with closest-title suggestions + diagnostics — never a fabricated match.

web=true (v1, honest scope) adds web sessions KNOWN LOCALLY only: materialized hook-export files ($SW_HOME/web-sessions, default ~/.session-watch/web-sessions) and ~/.claude.json → projects[*].lastSessionId teleport stubs (id known, transcript NOT local — content_local: false). The fuller per-repo teleport-picker sweep needs a PTY and is a documented follow-up, not guessed here.

Thin wrapper over :func:ai_r.locate.locate (ValueError{"error": "invalid_argument"}).

session_diffA

Reconstruct what the agent changed in one session — without git.

Stitches the session's own edit records (Edit/MultiEdit old_stringnew_string, Write content, codex shell-exec redirections) into a per-file, chronological diff. Returns {"files": [...], "count": N, "caveats": [...]}; each file carries its ordered edits (timestamp + intent + hunks) and a stitched, readable diff.

caveats always carries two honest blind spots: (1) this is a diff of the agent's actions, not the git outcome — manual edits / partial commits / merges are invisible; (2) RISK-3 — inherits the find_file_edits shell-redirect gap (tee / sed -i / cp / mv / heredoc writes are not detected and are silently skipped).

redact=True (default) masks secrets in the emitted diff/hunks/ intents as [REDACTED_<TYPE>] and adds a redactions type→count dict when any replacement happened; redact=False returns raw.

Size-bounded output (mirrors find_file_edits): over-long intent / hunk bodies / per-file diff text are cut with a …[truncated] marker and named in the per-file truncated_fields (indexed paths), and whole-file emission stops at a total byte budget (output_truncated; count keeps the true total). The full edit body stays reachable on demand via get_body / read_session.

Thin wrapper over :func:ai_r.session_diff.session_diff that translates the core ValueError contract into the {"error": "invalid_argument", "message": str(exc)} shape the MCP client expects.

search_sessionsA

Case-insensitive search across sessions.

Args: query: Search string. Supports: * Bare words: pwa manifest (AND default) * Quoted phrases: "exact phrase" * Negative prefix: -claude (Google-style, always excluded) agent: Optional agent filter (claude/codex/opencode/antigravity/pi). scope: Where to look. * "title" — only session.title (default, backward-compat) * "body" — message text + tool_use[*].input + tool_result[*].content * "all" — title OR body operator: How to combine positive terms. * "AND" — all positive terms must appear (default) * "OR" — at least one positive term must appear * "NOT" — no term (positive or negative) may appear Negative -term prefixes are always excluded regardless of operator. limit: Maximum number of results. 0 or negative = no limit. Applied after sorting, so it keeps the top-ranked matches. sort: Result ordering. * "relevance" — BM25 relevance over the matched text (default). Pure-stdlib scoring; ties keep newest-first. * "date" — newest-first by session date (the historical pre-ranking order). * "semantic" — F5.1 (optional ai-r[semantic]): the BM25 top-50 candidates re-ranked by meaning with a local multilingual embedding model (cross-lingual ru↔en, synonyms); the response carries a semantic dict — either the active ranking (active: true, model, candidate count, blend weight) or the honest degradation notice (active: false + plain-words reason + fallback: "bm25", order stays BM25 — never a crash). noise: Noise filter — a session is noise when it is a spawned subagent (kind == "subagent" or parent_uuid set). * "include" — no filtering (default). * "exclude" — search only top-level agent sessions. * "only" — search only subagent sessions. Applied before matching, so excluded sessions never pay the body-scan cost. redact: When True (default) secrets in the emitted title / snippet / extra fields are masked as [REDACTED_<TYPE>] and the response carries a redactions type→count dict when any replacement happened; False returns raw content. Matching always runs on the RAW stored text, so searching for a literal secret still finds its session — only the displayed snippet is masked. include_thinking: When True fold model reasoning (message.thinking) into the body/all search haystack so a search matches text that lives only in the model's thoughts. Default False: reasoning is excluded from matching to save the caller's budget (turn it on only when a query must reach into reasoning). No effect on scope="title". The two modes are cached under separate keys, so toggling never serves a stale haystack of the other mode.

Returns: A dict {"results": [...], "count": N} where results is the list of session summaries and count is their total. When scope is "body" or "all" and a match is found, each summary includes a "snippet" field with the first matching message excerpt (up to 200 chars) and may carry body_truncated. When a scan matches nothing (count == 0), the dict additionally carries diagnostics (scanned agents + session counts, corpus date bounds, cause hints) so an empty result is explainable. With sort="semantic" the dict also carries a semantic report (active ranking vs BM25 fallback + reason).

Errors are returned as a top-level {"error": ..., "message": ...} dict (matches the existing convention).

queryA

Filter/search the unified session event stream — the workhorse verb.

Every parser's messages + tool calls are normalized into one flat, agent-neutral event stream (user_turn / assistant_turn / tool_call(<sub>) / plan_event); this tool filters that stream by facets — all behaviour is parameters, never hard-wired variants.

Facets:

  • typeuser_turn | assistant_turn | tool_call | tool_call(edit|write|read|bash|other) | plan_event. Bare tool_call matches every subtype.

  • agent — one of claude/codex/opencode/antigravity/pi (all if omitted).

  • session — restrict to a single session uuid, OR a list of uuids (the union of those sessions' events in one call — e.g. the ids picked from a search_sessions / list_sessions result). Duplicates collapse; an unknown uuid contributes nothing. An empty list or a non-string item is a fail-loud invalid_argument — never a silent unfiltered scan.

  • since / until — ISO-8601 bounds (inclusive) on the event ts.

  • file — substring matched against an event's referenced file path.

  • tool — substring (pattern) matched against the referenced tool name OR the resolved name under a wrapper (tool_resolved) — so tool="commit" also finds the Skill call that ran the commit skill.

  • tool_kind — exact match against the wrapper-aware classification of a tool call: edit / write / read / bash / task (subagent spawn) / skill / mcp / web / other. Every tool_call event carries tool_kind (in refs and as a top-level field); wrappers whose input names the real actor also carry tool_resolved — the subagent type under Task/Agent/ spawn_agent, the skill name under Skill/SlashCommand, or "<server>:<tool>" for a Claude-style mcp__<server>__<tool> call. No signal → no tool_resolved (never guessed). An unknown tool_kind value is a fail-loud invalid_argument.

  • model — exact, case-insensitive match against the model that produced the event's message: an assistant_turn / tool_call / plan_event inherits the model of the assistant message behind it and carries it as a top-level model field (absent without a signal — user turns, Antigravity — so aggregate(group_by="model") buckets those under "(unknown)"). Model ids are agent-defined strings (no fixed vocabulary); events without a signal never match; an empty string is a fail-loud invalid_argument.

  • user_ref — filter user_turn events by the entity the user referenced. A string with special values: "any" matches every user turn that referenced some target; a bare kind (e.g. "file" / "session") matches turns referencing that kind of target; any other string is a substring matched against the referenced target. Non-user events never match, so combining user_ref with a non-user_turn type yields an honest empty result.

  • has_thinking — filter by whether the event's message carried model reasoning (Event.has_thinking). True keeps only events with reasoning, False only those without; unset (None, default) does not filter. Note the reasoning text itself is never inlined — this only gates on its presence (fetch it with get_body(id, include_thinking=True)).

  • text — substring matched against event text. With sort="relevance" survivors are BM25-ranked using the same scorer as search_sessions; sort="semantic" (F5.1, optional ai-r[semantic]) re-ranks the BM25 top-50 candidates by meaning with a local multilingual embedding model (cross-lingual ru↔en, synonyms) — the response carries a semantic dict reporting either the active ranking (active: true, model, candidate count, blend weight) or the honest degradation (active: false + plain-words reason + fallback: "bm25" — the order is then plain BM25, never a crash); sort="date" (default) orders by timestamp ascending.

  • relative_to (event id) + direction (prev|next) + n (a positive integer, default 1, or "all") — the neighbouring-turn walk. A numeric string ("3") is deprecated and will be rejected in 0.6.0 — pass an int or "all". Generalises the previous_user_intent used by find_file_edits to both directions and any count. step_type chooses which event type to collect (default user_turn). When relative_to is set, other filter facets are ignored.

with_intent=True attaches a top-level intent (the request behind the event, via the same previous_user_intent walk-back the legacy tools use) to every returned event. Default False keeps the base event shape unchanged.

noise filters at the session level before events are read — a session is noise when it is a spawned subagent (kind == "subagent" or parent_uuid set): "include" (default, no filtering), "exclude" (top-level sessions only), "only" (subagent sessions only). Ignored on the relative_to walk, like every other filter facet.

project_dir also filters at the session level: keep only events of sessions whose project_dir equals this path or is a descendant of it (path-boundary aware, trailing slashes ignored) — "events of this project". Sessions without a project_dir signal never match. Ignored on the relative_to walk, like every other filter facet.

parent also filters at the session level: keep only events of sessions that are a descendant (transitively, any depth) of this session uuid in the subagent parent_uuid tree — the whole spawned subtree below parent (direct children plus nested). parent itself is excluded (its own events are reachable via session=<parent>). An unknown uuid matches nothing (honest empty result). Ignored on the relative_to walk, like every other filter facet.

group filters at the event level, plan_events only: keep only the plan_events whose task_id (the plan-task grouping key — plan-file slug or normalized title) equals this value. Non-plan events never match when group is set, so combining group with a non-plan type yields an honest empty result.

redact=True (default) masks secrets in the emitted text / intent fields as [REDACTED_<TYPE>] and adds a top-level redactions type→count dict when any replacement happened; redact=False returns raw content. Redaction is emission-time only: the text facet (and every other filter) matches the RAW stored text.

Events are reference-by-default: each emitted event's text is a preview cut to ~160 chars (applied after redaction). A real cut is marked with a trailing and text_truncated: true (absent when nothing was cut). id/refs/sha256 are untouched — fetch the full body on demand with get_body(id).

kind was removed — it duplicated noise (noise="only" for subagents, noise="exclude" for top-level). It is kept in the signature only as a fail-loud tombstone: passing any value returns an invalid_argument error pointing at noise rather than silently ignoring it (the MCP transport would otherwise drop an unknown argument and return an unfiltered result — a silent wrong answer).

Returns {"events": [...], "count": N} or the standard {"error": ..., "message": ...} dict on invalid arguments. When count == 0 the dict additionally carries diagnostics (scanned agents + session counts, corpus date bounds, cause hints) so an empty result is explainable.

planA

Normalized plan atoms for a session — final vs drafts, grouped by task.

Wraps query(type="plan_event", …) and normalizes every agent's plan signal (Claude ExitPlanMode / Write plans/*.md, Codex update_plan, Antigravity implementation_plan.md) into a single :class:~ai_r.events.Plan shape — the per-agent signal is an internal detail, never surfaced.

Plans are grouped by task keyed on each plan's task_key — the plan-file slug when the agent has one (Claude plans/<slug>.md, Antigravity implementation_plan.md path), falling back to the normalized title only when no plan file exists (Codex update_plan). Within a task the latest plan is final and earlier revisions are draft; plans of earlier completed tasks are completed_major.

F3.4 default schema (measured ≈×3.7 cheaper than "everything inlined"): the final plan's full text is inlined (body + body_source"approval_edited_by_user" when the user's approval carried an edited plan, which is the AUTHORITATIVE text and overrides the signal/file body, else "plan_signal"); drafts stay references (bodies via get_body); every «plan quote → user comment» pair extracted from the user's plan responses is returned under feedback, each with a ref ("<session>:pf<N>") that get_body resolves to the FULL raw response. Only agents with an interactive plan-approval flow have the feedback signal (today: Claude — an ExitPlanMode verdict or a rejected plan-file Write); others honestly contribute nothing. Technical failures and bare no-comment rejections are filtered out.

F3.4 v2 additions: every plan atom carries version — its 1-based revision number within the task group, chronological (drafts are v1…vN-1, the final is vN); every feedback pair carries plan_version (the answered revision's number), round (1-based feedback-round number within the session — one round per user response that produced pairs) and section — the heading of the plan section the quote anchors to. Quotes are selected from the RENDERED plan, so the anchor match strips markdown markup from both sides; a quote that matches no section — or more than one — gets an honest null anchor, never a nearest guess.

Args: session: Restrict to one session uuid (recommended). kind: Optional filter — draft | final | completed_major. group: Grouping strategy; only "task" is supported. agent: Optional agent filter (claude/codex/opencode/antigravity/pi). redact: When True (default) secrets in the emitted plan/feedback fields (title/steps/body/quote/comment…) are masked as [REDACTED_<TYPE>] and the response carries a redactions type→count dict when any replacement happened; False returns raw content. bodies: "final" (default) inlines the final plan's full text; "none" returns reference-only atoms. feedback: True (default) adds the feedback pair list + feedback_count; False omits both (historical shape). rounds: "all" (default) returns every feedback round; "last" keeps only each session's final round (v2). Any other value fails loud.

Returns: {"plans": [...], "count": N, "feedback": [...], "feedback_count": M} — each plan carries id/session_id/agent/title/task_id/kind/version/path/steps/status/ refs/sha256 (+ body/body_source on the final when bodies="final"); each feedback pair carries session_id/agent/plan_id/plan_version/verdict/round/quote/comment/ section/ref/ts (verdictrejected | stay_in_plan_mode; quote is null for a free-text comment; plan_version/ section are null without a signal). Draft bodies and raw responses stay on-demand via :func:get_body. Standard {"error": ..., "message": ...} dict on invalid arguments.

get_bodyA

Return the on-demand body for an event / plan id.

For a plan_event id: the full plan text and/or Codex steps (bodies are deliberately kept off the event stream so callers pay for them only when needed). For a user_turn / assistant_turn id: the turn text.

shallow=True (plans only) returns just the final plan of the id's task, dropping the bodies of superseded draft revisions — the S6 case where a subagent receives one plan without the draft noise (dropped_drafts lists the ids that were elided).

max_chars bounds the returned body/text (default 500_000, generous enough that ordinary bodies are never cut; pass 0 to disable). When it trips, the field is sliced with a …[truncated] marker and body_truncated: true is set.

redact=True (default) masks secrets in the emitted text/body/title/steps as [REDACTED_<TYPE>] and adds a redactions type→count dict when any replacement happened; redact=False returns the raw content.

include_thinking=True (turn ids only): attach the model's reasoning (message.thinking) as a separate thinking field on the returned body, re-read from the hosting message. Default False leaves the body byte-identical to the historical shape (no thinking key). Fail-soft: a turn without reasoning never carries the key.

Returns the body dict, or {"error": ..., "message": ...} on a bad id.

aggregateA

Roll a list of row dicts up by group_by — the generic stats verb.

Reproduces session_stats (group_byagent/dir/date/ kind over a session inventory) and file_frequency (group_by="file" over a find_file_edits record stream) as a pure fold over already-materialized rows — no re-parsing. session_stats is now a thin preset over this verb (rank_by="stats" + kind_split).

Args: rows: The row dicts to fold (query output, find_file_edits records, or a session inventory). group_by: The bucket key — a row field name (agent / dir / date / kind / file / model — query rows carry the producing model where the format records one / …). Missing/empty values bucket under "(unknown)". metrics: Which numbers each bucket carries. One or more of count / sessions / edits / intents / agents / messages / files / tokens / component_tokens. Defaults to ["count"]. tokens (F3.3) folds per-row tokens blocks (the shape session_stats(with_tokens=True) rows carry, or a bare int total) into {input, output, reasoning, cache_read, cache_write, total, exact, estimated, unknown} — sums over rows that carry each field (null when none does) plus honest provenance counters (exact + estimated + unknown == len(rows)). component_tokens (F3.3) folds per-row component_tokens blocks (the shape :func:ai_r.tokens.component_tokens produces, as read_session(with_tokens=True) attaches) into summed event-taxonomy components (user_turn / assistant_turn / thinking / plan and a tool_call per-kind sub-dict) + total + provenance counters (estimated / unknown; never exact — always an estimate). A component/kind no row carried stays absent (never a fabricated 0). rank_by: Group ordering — "default" (edits→sessions→count→label, the file_frequency order) or "stats" (sessions→edits→label, the session_stats order). kind_split: When True, add the session_stats RISK-4 fields (kind_split_available + a degenerate-split note).

Returns: {"group_by", "groups": [...], "totals": {...}} (plus kind_split_available/note when kind_split) or the standard {"error": ..., "message": ...} dict on an unknown metric/rank_by.

diffA

Stitch edit rows into a per-file chronological diff — the diff verb.

Reproduces the synthesis of session_diff: given the edit events of a session (query(type="tool_call(edit)", session=…) — plus write / shell-redirect events), group them per file in chronological order and render a stitched, readable diff. Bodies are fetched on demand (via each event's stored message_index), never inlined on the row.

Args: rows: Edit-event dicts (query output). Each must carry an id and a refs list with a file entry; unresolvable rows skip. per_file: Group by file (the only mode today). format: "unified" (the only rendering today). redact: When True (default) secrets in the stitched output are masked as [REDACTED_<TYPE>] and the result carries a redactions type→count dict when any replacement happened; False returns raw content.

Returns: {"files": [{"file", "edits", "diff", "hunks"}], "count", "caveats"} (same shape + caveats as session_diff, size-bounded the same way: capped fields named in the per-file truncated_fields, byte budget → output_truncated) or the standard {"error": ..., "message": ...} dict on an unsupported format.

detect_currentA

Return the current runtime identity (session + agent) from env/fs.

NOT a session-query — this reads the runtime environment (env vars + per-session flag files), reusing the exact cascade behind the ai-r detect-agent / ai-r detect-session CLI subcommands.

Args: agent: Optional hint (accepted for symmetry with the CLI's deprecated --agent flag); the cascade scans all agents.

Returns: {"session_id", "agent", "model", "resume_command", "candidates": [...], "verified", "self"} where session_id / agent describe the highest-priority candidate, model is the current session's model — the LAST assistant model recorded in its transcript (null when identity is incomplete or the format records no model signal — never guessed) — resume_command is the ready-to-run shell one-liner that reopens the detected session in its agent's CLI (F2.2, text only, never executed; null when no real command exists) — and candidates is the full cascade for disambiguation. Returns {"error": ..., "message": ...} on an unknown agent hint.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pro-target/ai-r'

If you have feedback or need assistance with the MCP directory API, please join our Discord server