Skip to content

Repository files navigation

multi-bot-agentic

multi-bot-agentic is a standalone AI-agent engineering showcase: a deterministic agent coordinator with explicit Observe -> Decide -> Act loops, durable event logs, rationale traces, provider adapters, and bounded safety controls.

It is built as a portfolio-quality recreation of the multi-bot product idea without depending on private infrastructure. The default path runs fully offline with a deterministic fake provider. Real adapters are included for GPT-5.5/OpenAI-compatible models, Claude Sonnet 4.6 via Claude Code CLI, Gemini 3.x, and Kimi K2/Moonshot.

multi-bot-agentic animated demo

DeadLetterToolQueue

BotHandoffReceiptStore

ToolCallQuotaGuard BotTurnFairnessScheduler HitlEscalationCooldownGate BotRoleConflictDetector PlanStepDependencyResolver BlackboardEntryTtlEvictor BotHeartbeatLivenessWatchdog SharedBlackboardWriteLease FanInBarrierGate ToolResultFingerprintDeduper BotSkillTagRouter SessionTokenBudgetLedger BotVoteConsensusAggregator SessionTtlExpirer ToolCallLatencyTracker CriticBotVerdictGate ConversationTurnBudgetGuard ToolPermissionAllowlist StickyBotAffinityStore RunDeadlineWatchdog ToolCallIdempotencyCache

Why It Exists

Most agent demos let the LLM decide everything. This repo takes the production-minded path:

  1. The LLM is an input source, not the control plane.
  2. A deterministic decision engine chooses actions.
  3. Every decision has a rationale trace.
  4. Every lifecycle transition is persisted.
  5. Every external integration goes through an adapter.
  6. Safety controls bound scope, runtime, tools, and cancellation.

Use Cases: Issues This Solves

1. "My agent did something, but I cannot explain why."

LLM-first agents often skip straight from prompt to action. When something goes wrong, the transcript may show what the model said, but not which control rule allowed the action.

multi-bot-agentic writes every decision as a durable event with a RationaleTrace: rule id, observations used, rejected actions, and explanation. You can replay the run later and inspect exactly why the engine chose call_llm, call_tool, finish, or cancel.

multi-bot-agentic replay --event-log data/runs.sqlite --event-type decision --format text

2. "I want to use AI agents, but I do not want the model directly executing tools."

Many agent frameworks let the model choose and invoke tools directly. That is convenient, but risky for production workflows where tool access should be explicit, bounded, and auditable.

This repo treats model output as an observation. The deterministic decision engine interprets constrained text like TOOL:checklist:<payload>, checks the safety policy, and only then executes an allowlisted tool adapter.

3. "I need the same agent flow to work with GPT-5.5, Claude Sonnet 4.6, Gemini 3.x, and Kimi K2."

Provider-specific SDKs and response shapes make agent code hard to port. A prototype built around one model often leaks provider details into the orchestration layer.

multi-bot-agentic normalizes providers behind one adapter interface:

  • OpenAIAdapter for GPT-5.5/OpenAI-compatible chat completions.
  • ClaudeCodeCLIAdapter for local Claude Code CLI workflows with Claude Sonnet 4.6.
  • GeminiAdapter for Gemini 3.x generateContent.
  • KimiAdapter for Moonshot/Kimi K2 chat completions.
  • FakeLLMAdapter for deterministic CI and demos.

The runner consumes all provider responses as ModelOutput, so orchestration logic stays provider-neutral.

4. "I need a safe demo path that does not require API keys."

Portfolio and CI demos should not depend on live model credentials, model availability, or network behavior.

The fake provider produces deterministic model-like outputs that the real runtime consumes. It still exercises Observe -> Decide -> Act, tool routing, safety checks, event logging, replay, and reports.

multi-bot-agentic run --goal "Create a launch checklist for an AI agent platform" --provider fake

5. "Agent runs fail silently or leave no durable audit trail."

Long-running agent tasks need post-run inspection. Without durable state, crashes and restarts turn into guesswork.

The sqlite event log records lifecycle transitions, observations, decisions, action requests, action results, failures, cancellations, and completion. Replay does not call any provider or tool, so postmortems are safe and deterministic.

multi-bot-agentic report --event-log data/runs.sqlite

6. "The agent keeps looping or spending tokens without finishing."

Unbounded agent loops are a common failure mode. They waste time, cost money, and make incident response harder.

SafetyPolicy bounds run scope with max_steps, provider/tool timeouts, prompt size limits, cancellation files, and tool allowlists. If the run reaches its budget, the decision engine finishes or fails through explicit lifecycle events.

7. "I need to compare AI provider behavior without rewriting my orchestration."

Teams often want to test GPT-5.5 vs Gemini 3.x vs Kimi K2 vs Claude Sonnet 4.6, but provider-specific code makes comparisons noisy.

With provider adapters, you can keep the same runner, same decision engine, same event log, and same replay/report UX while swapping the provider:

multi-bot-agentic run --goal "Draft a migration plan" --provider openai
multi-bot-agentic run --goal "Draft a migration plan" --provider gemini
multi-bot-agentic run --goal "Draft a migration plan" --provider kimi
multi-bot-agentic run --goal "Draft a migration plan" --provider claude_code

8. "I want agents to produce useful artifacts, not just chat text."

Agent demos often end with prose. Real workflows need structured, repeatable artifacts.

The built-in checklist tool turns a goal into a deterministic launch checklist and records the tool result in the event log. It is intentionally simple, but it demonstrates the production pattern: model suggests, policy validates, adapter executes, event log records.

9. "I need a clean teaching or interview example for agent architecture."

Agent systems can become hard to explain when planning, tool use, model calls, retries, and state are mixed together.

This repo keeps the boundaries visible:

  • runner.py: owns Observe -> Decide -> Act.
  • decision.py: deterministic rules and rationale traces.
  • lifecycle.py: state-machine transitions.
  • event_log.py: durable sqlite events.
  • llm/: provider adapters.
  • tools/: allowlisted tool adapters.
  • safety.py: bounds and cancellation.

10. "I need CI to prove the agent works without real provider credentials."

Live provider tests are useful, but they should not be required for every pull request.

CI runs lint, format, typecheck, tests, and a fake-provider smoke demo across Python 3.10, 3.11, and 3.12. Live provider calls remain operator-triggered because they require credentials and external systems.

Architecture At A Glance

Goal
  |
  v
Observe  -> durable observation event
  |
  v
Decide   -> deterministic rule + rationale trace
  |
  v
Act      -> LLM adapter or allowlisted tool
  |
  v
Event log + replay

Quick Demo

python -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
ruff check .
ruff format --check .
mypy src tests
pytest
multi-bot-agentic run --goal "Create a launch checklist for an AI agent platform" --provider fake

Replay the durable event log:

multi-bot-agentic replay --event-log data/runs.sqlite
multi-bot-agentic replay --event-log data/runs.sqlite --format text
multi-bot-agentic report --event-log data/runs.sqlite

What This Showcases

  • Explicit Observe -> Decide -> Act runtime loop.
  • Deterministic decision engine with rationale traces.
  • State-machine lifecycle: created, observing, deciding, acting, succeeded, failed, cancelled.
  • Durable sqlite event log with replay.
  • Typed multi-bot handoffs with per-bot tool allowlists.
  • HITL approval gate for sensitive tools via durable JSON request files.
  • Parallel fan-out helpers for capped, order-preserving multi-bot task batches.
  • LLM adapters for GPT-5.5/OpenAI-compatible models, Claude Sonnet 4.6 via Claude Code CLI, Gemini 3.x, and Kimi K2/Moonshot.
  • Tool adapters with allowlisted execution, including deterministic checklist generation.
  • Safety controls for max steps, prompt bounds, cancellation, and timeouts.
  • Human-readable replay and run reports for inspecting durable rationale traces.
  • Production-minded layout: src/, tests/, scripts/, migrations/, .github/workflows/, env config, docs.

Providers

Provider Adapter Live credential
Fake deterministic local provider none
GPT-5.5 / OpenAI-compatible OpenAIAdapter OPENAI_API_KEY
Claude Sonnet 4.6 / Claude Code ClaudeCodeCLIAdapter local claude command
Gemini 3.x GeminiAdapter GEMINI_API_KEY
Kimi K2 / Moonshot KimiAdapter KIMI_API_KEY

All adapters normalize output into ModelOutput. The runner consumes that output as an observation before the decision engine selects the next action.

Built-In Safe Tools

  • checklist: deterministic launch checklist generator used by the fake-provider demo.
  • content_type_sniff: sniffs likely content type from a bounded text or base64 byte prefix (json, xml, html, csv, tsv, markdown, plain) and returns a confidence score without network access. Empty or oversized input returns a structured failure. A model requests it with TOOL:content_type_sniff:<payload> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need a parser hint before the next step.
  • cron_next: returns the next N UTC fire times for a classic 5-field cron expression (count default 5, max 20; optional from_iso). Invalid fields return a structured failure. Safe for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 scheduling agents.
  • echo: safe text echo for adapter tests.
  • readonly_file: root-contained read-only file reader.
  • calculator: sandboxed arithmetic evaluator. It parses expressions into an AST and walks an allowlist of numeric literals and + - * / // % ** operators — never eval — so names, calls, and attribute access are rejected and exponents are bounded against CPU/memory exhaustion. Results that are not real numbers (for example a fractional power of a negative base) or not finite (overflow to inf, or nan) are refused rather than returned as opaque values. A model requests it with TOOL:calculator:2 + 3 * 4, matching the same model-suggests / policy-validates / adapter-executes pattern as every other tool.
  • json_format: validates a JSON document and returns it canonicalized (sorted keys, 2-space indent). Invalid input yields a structured failure with the parser's message instead of raising, and the non-standard NaN/Infinity/ -Infinity tokens (which RFC 8259 forbids and strict parsers reject) are rejected rather than round-tripped into invalid output. A model requests it with TOOL:json_format:{"b":1,"a":2}, giving agents a safe way to verify and normalize JSON produced by earlier steps.
  • uuid_nil: returns the RFC 4122 nil UUID 00000000-0000-0000-0000-000000000000 (default) or the max UUID when mode=max. Useful as a placeholder id in crewAI / LangGraph-style agent pipelines. Unsupported modes return a structured failure. A model requests it with TOOL:uuid_nil: for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need a stable sentinel identifier.
  • yaml_format: validates a constrained safe YAML subset and returns it canonicalized (sorted mapping keys, 2-space indentation). It supports block mappings/sequences, JSON-style flow collections, and finite scalar values using the Python stdlib only; unsupported full-YAML features such as anchors, tags, document markers, and constructors return structured failures. A model requests it with TOOL:yaml_format:enabled: true, giving agents a safe way to normalize YAML handoff snippets.
  • toml_format: validates TOML (via tomllib on Python 3.11+ or tomli when available) and returns a deterministic serialization with sorted keys for tables/arrays/strings/ints/floats/bools. Dates/times, non-finite floats, empty or oversized input, and a missing parser return structured failures. A model requests it with TOOL:toml_format:enabled = true, giving agents a safe way to normalize TOML configuration snippets.
  • toml_json: converts between TOML and JSON text for agent handoffs (direction: to_json default or to_toml). Parsing uses tomllib/tomli or strict json.loads; output is canonical JSON (sorted keys, 2-space indent) or deterministic TOML (same dumper as toml_format). Dates/times, JSON null, non-finite numbers, empty or oversized input, and missing parsers return structured failures. A model requests it with TOOL:toml_json:enabled = true, giving agents a safe way to bridge TOML configuration and JSON payloads.
  • tsv_format: validates tab-separated spreadsheet text via stdlib csv (excel-tab dialect) and returns canonical TSV with consistent newlines. Empty or oversized input, uneven column counts (header defines width), and malformed tables return structured failures; trailing blank rows are stripped. A model requests it with TOOL:tsv_format:model\tscore, giving agents a safe way to normalize TSV handoff snippets.
  • json_merge_patch: applies RFC 7396 JSON Merge Patch (base+patch, or text with <<<PATCH>>>) via stdlib json. Empty, oversized, malformed, or over-deep requests return a structured failure. A model requests it with TOOL:json_merge_patch:<json> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic partial JSON updates.
  • line_number: prefixes each text line with a 1-based line number (optional start / separator). Empty or oversized input and invalid start/separator values return a structured failure. A model requests it with TOOL:line_number:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need stable line citations.
  • csv_filter: filters CSV rows where a named column equals or contains a value (mode: equals default or contains; case_insensitive: true default) via stdlib csv. Empty, oversized, malformed, unknown-column, or over-bounds requests return a structured failure. A model requests it with TOOL:csv_filter:<csv><<<CSV_FILTER>>>column<<<=>>>value (or column<<<~>>>value) for deterministic tabular predicates before the next turn.
  • csv_groupby: groups CSV rows by key columns and aggregates numeric value columns (agg: sum default, count, min, max, mean) via stdlib csv. Empty, oversized, malformed, unknown-column, or non-numeric requests return a structured failure. A model requests it with TOOL:csv_groupby:<csv> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic tabular aggregation before the next turn.
  • csv_join: joins two CSV tables on a key column (how: inner default or left; on or left_on+right_on) via stdlib csv. Supply sides as left+right, or text+right. Empty, oversized, malformed, or unknown-column requests return a structured failure. A model requests it with TOOL:csv_join:<csv> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic tabular lookup joins before the next turn.
  • csv_pivot: pivots long CSV to wide (index/columns/values) or unpivots wide columns (id_vars/value_vars) via stdlib csv. Empty, oversized, malformed, or unknown-column requests return a structured failure. A model requests it with TOOL:csv_pivot:<csv> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic tabular reshape before the next turn.
  • csv_stack: vertically concatenates CSV documents that share an identical header, accepting a csvs list or text split by <<<CSV_STACK>>>. It rejects malformed input, mismatched headers, uneven rows, and bounded-size violations for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.
  • csv_tsv: converts between CSV and TSV text for agent handoffs (direction: csv_to_tsv default or tsv_to_csv). Parsing and serialization use stdlib csv only; an optional single-character delimiter overrides the input separator. Empty or oversized input, invalid direction/delimiter, uneven column counts, and malformed tables return structured failures. A model requests it with TOOL:csv_tsv:model,score, giving agents a safe way to bridge CSV and TSV handoff snippets across GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.
  • xml_escape: escapes or unescapes XML special characters (&, <, >) via stdlib xml.sax.saxutils.escape/unescape (mode: escape|unescape; default escape). Empty, oversized, or unsupported-mode requests return a structured failure. A model requests it with TOOL:xml_escape:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic XML entity escaping.
  • xml_parse: parses XML via stdlib xml.etree.ElementTree into a compact indented text tree (tag names, @attr=value pairs, direct text nodes). Empty or oversized input, DOCTYPE/ENTITY declarations (XXE hardening), and malformed XML return structured failures; rendering is depth- and element-capped. A model requests it with TOOL:xml_parse:<root>...</root>, giving agents a safe way to summarize XML handoff snippets.
  • json_path:
  • json_pointer: extracts one value from a JSON document using RFC 6901 JSON Pointer (/foo/0/bar, ~0/~1 escapes); agents may split document and pointer on <<<JSON_POINTER>>> for TOOL:json_pointer:... directives. Distinct from json_path. PLACEHOLDER
  • jwt_decode: base64url-decodes a JWT header and payload into JSON claims without verifying the signature. Empty, oversized, or malformed tokens return a structured failure. Output is never trusted as authenticated. A model requests it with TOOL:jwt_decode:<jwt> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need opaque claim inspection.
  • json_query: filters JSON object arrays (where field equals value) or plucks a field across objects (pluck) via stdlib json. Empty, oversized, malformed, or unsupported-mode requests return a structured failure. A model requests it with TOOL:json_query:<json><<<JSON_QUERY>>>{...} for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic array select beyond json_path.
  • json_path_KEEP: extracts one value from a JSON document using a small deterministic path dialect (.foo.bar, items[0].name, or $/empty for the whole document). Supply text+path, or a single payload split on <<<JSON_PATH>>> for TOOL:json_path:... directives. Recursive descent, filters, scripts, pipes, oversized input, and oversized serialized results return structured failures; the tool uses json.loads only and never executes code.
  • spreadsheet_slice: parses CSV text and returns a deterministic row/column subset as JSON (header + rows). Row ranges use zero-based, end-exclusive body-row slices via rows=1:3 or row_start/row_end; columns may be selected by exact header name and/or zero-based index. A single TOOL:spreadsheet_slice payload can embed options after <<<SPREADSHEET_SLICE>>>. Empty input, oversized tables, blank headers, invalid ranges, missing names, ambiguous names, and out-of-bounds indexes return structured failures; the tool uses stdlib csv only and never executes code.
  • redact: scrubs common PII (email addresses, phone numbers, US Social Security numbers, IPv4 addresses) from text, replacing each match with a typed placeholder such as [EMAIL] and reporting per-category counts in the tool metadata. A model requests it with TOOL:redact:<text>, giving agents a safe way to sanitize content before it is persisted to the durable event log.
  • hash: computes a hex digest of text with a small allowlist of well-known algorithms (md5, sha1, sha256, sha512; default sha256). Empty, oversized, or unsupported-algorithm requests return a structured failure. A model requests it with TOOL:hash:<text>, giving agents a deterministic fingerprint for deduplication, cache keys, or integrity checks between steps.
  • base58: encodes text to Bitcoin-alphabet Base58 or decodes Base58 back to text (mode: encode|decode; default encode; accepts text or data). Decoding requires valid Base58 that yields UTF-8; empty, oversized, unsupported-mode, or invalid payloads return a structured failure. A model requests it with TOOL:base58:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need lookalike-safe opaque handoffs.
  • pluralize: pluralize/singularize a single English word (mode: pluralize|singularize; accepts text or word; common irregulars; max 2000 chars). Safe for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.
  • punycode: encode/decode domain text via Punycode/IDNA (mode: encode|decode; accepts text or domain; max 2000 chars). Safe for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.
  • base85: encodes text to Adobe ASCII85/Base85 or decodes back (mode: encode|decode; default encode; accepts text or data). Safe for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.
  • base32_encode: encodes text to standard Base32 or decodes Base32 back to text (mode: encode|decode; default encode) via stdlib base64.b32encode/b32decode. Decoding requires valid Base32 that yields UTF-8; empty, oversized, unsupported-mode, or invalid payloads return a structured failure. A model requests it with TOOL:base32_encode:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic Base32 handoffs.
  • base64: encodes text to standard Base64 or decodes Base64 back to text (operation: encode|decode; default encode). Decoding validates the payload strictly and requires the decoded bytes to be valid UTF-8, so invalid Base64 or non-text payloads return a structured failure. A model requests it with TOOL:base64:<text>, giving agents a safe way to move opaque payloads between steps.
  • url_encode: percent-encodes text via stdlib urllib.parse.quote (optional safe default /, plus for space-as-+). Empty, oversized, or invalid option requests return a structured failure. A model requests it with TOOL:url_encode:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic URL encoding.
  • url_parse: splits an absolute URL into its components (scheme, host, port, path, query, grouped query parameters, fragment) using the standard library — never a network request. Relative URLs, empty input, and invalid ports return a structured failure. A model requests it with TOOL:url_parse:<url>, giving agents a safe way to route on a host or inspect a query parameter relayed by an earlier step.
  • uuid4: generates random version-4 UUID identifier(s) (optional count requests it with TOOL:yaml_to_json:enabled: true, giving GPT-5.5 /
  • yaml_to_json: converts a constrained safe YAML subset to canonical JSON 1..16, default 1). Output is one UUID string or newline-joined UUIDs when count > 1. These are opaque identifiers for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workflows — not cryptographic secrets or keying material. Out-of-range or non-integer count returns a structured failure. A model requests it with TOOL:uuid4:.
  • uuid5: computes a deterministic version-5 UUID from a name and a namespace (dns, url, oid, x500, or a custom UUID string; default dns). Because a v5 UUID is a hash of (namespace, name), the same inputs always yield the same id — keeping the runtime deterministic, unlike a random v4 UUID. Empty, oversized, or unusable-namespace requests return a structured failure. A model requests it with TOOL:uuid5:<name>, giving agents stable primary keys, idempotency keys, or correlation ids shared across steps.
  • slugify: converts free-form text into a URL- and filesystem-safe ASCII slug. It strips diacritics, lowercases, collapses every run of non-alphanumeric characters into a single separator (default -, overridable), trims the ends, and can cap the length on a word boundary via max_length. Empty, oversized, unusable-separator, invalid-max_length, or slug-empty requests return a structured failure. A model requests it with TOOL:slugify:<text>, giving agents deterministic branch names, path segments, cache-file names, and anchor ids from arbitrary text.
  • datetime: normalizes an ISO-8601 timestamp to a canonical UTC form (YYYY-MM-DDTHH:MM:SS+00:00) and reports its Unix epoch and weekday. A trailing Z (Zulu) designator and numeric offsets are both accepted; a naive timestamp fails unless assume_utc=true is passed. It reads no wall-clock now, so it stays fully deterministic. Empty, oversized, unparseable, or naive-without-assume_utc requests return a structured failure. A model requests it with TOOL:datetime:<timestamp>, giving agents one canonical instant to compare, sort, and log timestamps that arrive in mixed shapes.
  • duration: parses an ISO-8601 duration (PT1H30M, P1DT2H, P2W, with an optional leading - and a fractional smallest component) into its total length in seconds plus a normalized component breakdown. Only fixed-length components (weeks, days, hours, minutes, seconds) are supported; calendar components (years/months) are refused because they have no fixed second length. It reads no wall-clock now, so it stays fully deterministic. Empty, oversized, calendar, componentless, or unparseable requests return a structured failure. A model requests it with TOOL:duration:<duration>, giving agents one exact scalar for retry backoffs, TTLs, and time budgets.
  • diff: produces a deterministic unified diff between two texts via difflib. Supply sides as text+other, or as a single text split on the <<<DIFF>>> sentinel (so TOOL:diff:... still works with one payload). Optional context controls hunk size (default 3). Empty/oversized sides, ambiguous splits, and invalid context return a structured failure. Gives agents a trustworthy before/after comparison for observations and tool outputs — matching the gap popular agent frameworks fill with a dedicated diff/patch tool.
  • regex_replace: applies a bounded regex find/replace (text / pattern / repl, optional count) via stdlib re. Empty or oversized input, patterns over 200 chars, nested-quantifier ReDoS shapes, and match counts over the cap return a structured failure. A model requests it with TOOL:regex_replace:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic substitutions before the next turn.
  • text_case: converts text to lower, upper, title, snake, kebab, or camel (default lower; max 20_000 chars). Supply text+case, or a single payload split on <<<TEXT_CASE>>>. Empty, oversized, or unsupported case values return a structured failure. A model requests it with TOOL:text_case:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic case transforms.
  • text_indent: indents every non-empty line by N spaces (default 2, max 32; optional skip_first). Supply text+spaces/skip_first, or a single payload split on <<<TEXT_INDENT>>>. Empty, oversized, or invalid option requests return a structured failure. A model requests it with TOOL:text_indent:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic indentation.
  • csv_select_columns: selects and reorders CSV columns by name via stdlib csv (max 500 rows, 64 columns). Supply text+columns, or a single payload split on <<<CSV_SELECT>>>. Empty, oversized, malformed, unknown-column, or over-bounds requests return a structured failure. A model requests it with TOOL:csv_select_columns:<csv><<<CSV_SELECT>>>col1,col2 for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic tabular projection.
  • csv_unique: deduplicates CSV rows by named column(s) via stdlib csv (keep first occurrence; max 500 rows, 64 columns). Supply text+columns, or a single payload split on <<<CSV_UNIQUE>>>. Empty, oversized, malformed, unknown-column, or over-bounds requests return a structured failure. A model requests it with TOOL:csv_unique:<csv><<<CSV_UNIQUE>>>col1,col2 for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic tabular deduplication.
  • csv_window: emits sliding windows of CSV data rows with the header preserved once per window (window_size required; step default 1; optional start_row/index). It rejects malformed input and bounded-size violations for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.
  • text_sort_lines: sorts multi-line text ascending or descending (order default asc) with optional unique dedupe after sort. Empty, oversized, or unsupported-order requests return a structured failure. A model requests it with TOOL:text_sort_lines:<text>, giving agents a stable line order for checklists, tags, and other line-oriented observations.
  • unicode_normalize: normalizes Unicode text via stdlib unicodedata to NFC (default), NFD, NFKC, or NFKD. Empty, oversized, or unsupported-form requests return a structured failure. A model requests it with TOOL:unicode_normalize:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need canonical text before comparison or hashing.
  • text_wrap: wraps or fills text via stdlib textwrap (mode wrap default or fill, width default 80). Empty, oversized, invalid-width, or unsupported-mode requests return a structured failure. A model requests it with TOOL:text_wrap:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need bounded line reflow for logs and previews.
  • html_attr_extract: extracts HTML attribute values via stdlib html.parser (required attr; optional tag filter and max_results). Empty, oversized, or invalid-bound requests return a structured failure. A model requests it with TOOL:html_attr_extract:<html> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic attribute extraction from markup handoffs.
  • html_entities: encodes or decodes HTML entities via stdlib html (mode encode default or decode; encode optionally escapes quotes). Empty, oversized, unsupported-mode, or invalid-quote requests return a structured failure. A model requests it with TOOL:html_entities:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic entity escaping before render or compare.
  • html_strip: strips HTML markup to plain text via the stdlib HTML parser. Documents containing <script> or <style> are rejected; empty or oversized input returns a structured failure. A model requests it with TOOL:html_strip:<html>, giving agents a deterministic way to turn scraped snippets into readable text without inventing or leaking markup.
  • html_markdown: converts safe HTML fragments to Markdown (headings, links, lists, bold/italic, code, paragraphs) via stdlib html.parser. Documents containing <script> or <style> are rejected; empty or oversized input returns a structured failure. A model requests it with TOOL:html_markdown:<html> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic HTML→Markdown handoffs.
  • html_table: extracts the first HTML table, or a 1-based table_index, and renders it as GitHub-flavored markdown or CSV. It uses stdlib html.parser only, rejects <script>/<style>, caps document/output chars plus rows and columns, and returns structured metadata for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need safe tabular observations from HTML.
  • html_table_csv: converts the first HTML table (default) or every table (all=true) to CSV text via stdlib html.parser. It rejects <script>/ <style>, caps document and output chars, and returns structured failures for empty, oversized, or table-less input. A model requests it with TOOL:html_table_csv:<html> for deterministic CSV handoffs across GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.
  • mime_attachment_names, text_outdent: parses bounded raw MIME with stdlib email and returns only a JSON list of decoded attachment filename/name parameters. Empty, oversized, or defective input returns a structured failure; payloads are never returned or written. A model requests it with TOOL:mime_attachment_names:<raw> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need safe attachment routing metadata.
  • mime_attachment_sizes: parses bounded raw MIME with stdlib email and returns only a JSON list of attachment filename/size objects. Sizes use Content-Length when present, otherwise decoded payload byte length. Payloads are never returned or written. A model requests it with TOOL:mime_attachment_sizes:<raw> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need safe attachment size metadata.
  • mime_attachment_cid_map: parses bounded raw MIME and returns a JSON map of Content-ID tokens to attachment filename/content_type objects without payloads. A model requests it with TOOL:mime_attachment_cid_map:<raw> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.
  • mime_attachment_disposition: parses bounded raw MIME with stdlib email and returns only Content-Disposition filename/disposition objects for attachment and inline parts, including unnamed disposition records. Payloads are never returned or written. Empty, oversized, or defective input returns a structured failure. A model requests it with TOOL:mime_attachment_disposition:<raw> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need safe routing metadata without attachment content.
  • mime_attachment_encoding: parses bounded raw MIME and returns only named
  • mime_attachment_filenames_unique: maps duplicate MIME attachment filenames to unique names (file-2.pdf); no payloads. A model requests it with TOOL:mime_attachment_filenames_unique:<raw> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers. attachment filename/encoding objects. Content-Transfer-Encoding tokens are normalized, missing values default to 7bit, and payloads are never decoded or returned. A model requests it with TOOL:mime_attachment_encoding:<raw> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.
  • mime_multipart: parses a raw MIME message via stdlib email and returns JSON summaries of each part (content_type, charset, size, payload preview). Empty or oversized input returns a structured failure. A model requests it with TOOL:mime_multipart:<raw> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need part metadata before parsing attachments.
  • template_render: fills simple {var} or Jinja-like {{ var }} placeholders from scalar JSON variables. It HTML-escapes every substituted value, rejects expressions/filters/attribute access instead of evaluating them, caps template/variable/output sizes, and supports a single directive payload split on <<<TEMPLATE_VARS>>>. A model requests it with TOOL:template_render:Hello {name}<<<TEMPLATE_VARS>>>{"name":"Ada"} for safe, repeatable snippets without raw string surgery.
  • zip_list: lists ZIP archive member metadata (name, size, compress_size, date) from base64-encoded bytes via stdlib zipfile. It never extracts or executes archive members; invalid base64, non-ZIP payloads, and empty or oversized input return structured failures. A model requests it with TOOL:zip_list:<base64> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers inspecting small attachment bundles.

Repository Layout

src/multi_bot_agentic/   runtime, lifecycle, decision engine, event log, adapters
tests/                   deterministic unit and integration tests
scripts/                 demo and verification scripts
migrations/              sqlite schema scaffold
docs/                    architecture, safety, config, quickstart, demo
.github/workflows/       CI for lint, format, typecheck, tests, demo smoke

Documentation

Verification

scripts/check.sh

scripts/check.sh runs ruff, format check, mypy, pytest, and a fake-provider smoke run with replay/report. CI runs the same script on Python 3.10, 3.11, and 3.12.

For a richer local demo:

scripts/run_demo.sh

Visual Asset

The README GIF is reproducible:

python scripts/render_demo_gif.py

The repo also keeps docs/demo.svg as a static architecture card.

License

MIT — see LICENSE.

Production use cases

Real issues this agent solves — deterministic ODA loop, rationale traces, durable event log, GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 adapters, and safety controls (timeouts, bounded scope, cancellation).

Issue Problem Solution doc
#001 Non-deterministic agent loops hard to debug doc
#002 Long-running tasks need cancellation doc
#003 Tool failures should not crash the run doc
#005 Unreadable files should not crash the run doc
#007 OpenAI-compatible gateways may return structured content doc
#011 PII redaction missed parenthesized area-code phone numbers doc
#012 PII redaction over-redacted non-address dotted numbers doc

Full index: docs/use-cases/README.md

Agentic design

  • Decision engine — deterministic step selection with logged rationale

  • State machinecreated → observing → deciding → acting → succeeded | failed | cancelled

  • Event log — SQLite/JSON audit trail for replay

  • Tool adapters — pluggable HTTP/LLM/retrieval integrations

  • Safety — timeouts, cancellation tokens, bounded run scope

  • json_diff_paths: compares two bounded JSON documents and returns only the sorted paths whose values differ, using dotted object keys and bracketed array indexes. Supply text+other, or one directive payload split on <<<JSON_DIFF_PATHS>>>. Empty, malformed, non-finite, oversized, or over-expanded input returns a structured failure. A model requests it with TOOL:json_diff_paths:<before><<<JSON_DIFF_PATHS>>><after> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need compact change routing without echoing both documents.

  • json_patch_apply: applies bounded RFC 6902 JSON Patch arrays with add/remove/replace/move/copy/test operations via stdlib only. Supply text+patch, or split one directive payload on <<<JSON_PATCH>>>; documents are capped at 20,000 characters and patches at 200 operations for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.

  • text_justify_lines: formats bounded non-empty lines with left, right, center, or full justification at widths up to 500 while preserving line endings and never truncating content. It supports text options or the <<<TEXT_JUSTIFY_LINES>>> sentinel for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.

  • text_slug_lines: slugifies every bounded document line independently while preserving original line endings. It supports configurable separators, casing, empty-line handling, and the <<<TEXT_SLUG_LINES>>> sentinel for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.

  • text_title_lines: title-cases every bounded document line independently while preserving original line endings. It supports skip_empty, lowercase_first, and the <<<TEXT_TITLE_LINES>>> sentinel for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.

  • text_margin_lines: adds left/right ASCII margins to non-empty lines for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers.

  • mime_multipart_flatten: recursively flattens nested multipart MIME via stdlib email into a JSON array of leaf-part metadata (content_type, filename, content_id, size, depth), max 20_000 chars and 200 leaf parts. Payload bytes are never returned. Empty, oversized, or malformed input returns a structured failure. A model requests it with TOOL:mime_multipart_flatten:<raw> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need a safe inventory of nested email parts, inspired by mail pipelines in popular agent frameworks.

  • html_links_extract: extracts HTML anchor href+text pairs as compact

  • semver_compare: compares two semantic versions (major.minor.patch with optional pre-release) and returns -1/0/1 plus a human relation. Supply version_a+version_b, or a payload split on <<<SEMVER_COMPARE>>>. Empty or invalid versions return a structured failure. A model requests it with TOOL:semver_compare:<a><<<SEMVER_COMPARE>>><b> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic version gating.

  • csv_fillna: fills empty CSV cells with a constant via stdlib csv (fill_value default empty string; optional columns subset). Supply text+fill_value, or a payload split on <<<CSV_FILLNA>>> (optional <<<COLUMNS>>>col1,col2). Empty, oversized, malformed, unknown-column, or over-bounds requests return a structured failure. A model requests it with TOOL:csv_fillna:<csv> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic tabular fillna. JSON via stdlib html.parser (max_links default 100, range 1..500). Documents containing script/style are rejected. Empty, link-less, oversized, or invalid max_links requests return a structured failure. A model requests it with TOOL:html_links_extract:<html> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 research workers.

  • ics_parse: parses iCalendar (.ics) text and extracts VEVENT SUMMARY/DTSTART/DTEND/UID/LOCATION as JSON Lines via stdlib only (max 20_000 chars, 100 events). Empty, oversized, or VEVENT-less requests return a structured failure. A model requests it with TOOL:ics_parse:<ics> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need deterministic calendar field extraction.

  • markdown_toc: builds a nested Markdown table of contents from ATX headings (#..######) up to max_level (default 3, range 1..6) with GitHub-like slug anchors. Supply text+max_level, or a payload split on <<<MARKDOWN_TOC>>>. Empty, heading-less, oversized, or invalid max_level requests return a structured failure. A model requests it with TOOL:markdown_toc:<markdown> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 documentation workers.

  • text_unique_lines: deduplicates lines in first-seen order (optional strip, default true) while preserving original line endings. Supply text+strip, or a single payload split on <<<TEXT_UNIQUE_LINES>>>. Empty, oversized, or invalid strip requests return a structured failure. A model requests it with TOOL:text_unique_lines:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need stable dedupe without sorting.

  • text_collapse_blank: collapses runs of consecutive blank or whitespace-only lines to at most max_blank lines (default 1, range 0..100) while preserving non-blank line endings. Supply text+max_blank, or a single payload split on <<<TEXT_COLLAPSE_BLANK>>>. Empty, oversized, or invalid max_blank requests return a structured failure. A model requests it with TOOL:text_collapse_blank:<text> for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 workers that need to tidy noisy blank-line runs before the next turn.

HITL approval gate

HitlApprovalGate persists pending/approved/rejected tool approvals as JSON under an approval directory for operator review.

Parallel fan-out

ParallelFanOut runs capped task batches via ThreadPoolExecutor, preserves input order, and merges successful answers without requiring AgentRunner LLM calls.

Checkpoint resume

Durable checkpoint resume for ODA runs is available via resume.

  • ToolCallQuotaGuard: Per-tool call quota per session advisory/hard deny — see docs/guides/TOOL_CALL_QUOTA_GUARD_GUIDE.md
  • HitlEscalationCooldownGate: Cooldown after HITL deny/reject to block immediate re-escalation (advisory/hard; never network I/O) — see docs/guides/HITL_ESCALATION_COOLDOWN_GATE_GUIDE.md
  • BotRoleConflictDetector: Exclusive role-tag collision detector (advisory/hard; never network I/O) — see docs/guides/BOT_ROLE_CONFLICT_DETECTOR_GUIDE.md
  • PlanStepDependencyResolver: Topological plan-step ordering with cycle detection — see docs/guides/PLAN_STEP_DEPENDENCY_RESOLVER_GUIDE.md
  • BlackboardEntryTtlEvictor: Per-key TTL eviction for shared blackboard entries — see docs/guides/BLACKBOARD_ENTRY_TTL_EVICTOR_GUIDE.md
  • BotHeartbeatLivenessWatchdog: Per-bot heartbeat liveness (alive/stale/unknown) — see docs/guides/BOT_HEARTBEAT_LIVENESS_WATCHDOG_GUIDE.md
  • SharedBlackboardWriteLease: Exclusive time-bounded blackboard write leases — see docs/guides/SHARED_BLACKBOARD_WRITE_LEASE_GUIDE.md
  • FanInBarrierGate: Fan-in barrier releasing after N distinct bot arrivals — see docs/guides/FAN_IN_BARRIER_GATE_GUIDE.md
  • ToolResultFingerprintDeduper: Fingerprint-hash tool-result dedupe per session to avoid re-injecting identical observations (never network I/O) — see docs/guides/TOOL_RESULT_FINGERPRINT_DEDUPER_GUIDE.md
  • BotSkillTagRouter: Route tasks to bots by skill-tag overlap ranking — see docs/guides/BOT_SKILL_TAG_ROUTER_GUIDE.md
  • SessionTokenBudgetLedger: Soft/hard cumulative token budget per session — see docs/guides/SESSION_TOKEN_BUDGET_LEDGER_GUIDE.md
  • BotVoteConsensusAggregator: Majority/plurality vote across bot answers — see docs/guides/BOT_VOTE_CONSENSUS_AGGREGATOR_GUIDE.md
  • SessionTtlExpirer: Per-session idle TTL with advisory/hard expiry — see docs/guides/SESSION_TTL_EXPIRER_GUIDE.md
  • ToolCallLatencyTracker: Per-tool latency samples with advisory p50/p95 — see docs/guides/TOOL_CALL_LATENCY_TRACKER_GUIDE.md
  • CriticBotVerdictGate: Critic accept/revise/reject verdict gate — see docs/guides/CRITIC_BOT_VERDICT_GUIDE.md
  • ConversationTurnBudgetGuard: Max turns per session advisory/hard gate — see docs/guides/CONVERSATION_TURN_BUDGET_GUIDE.md
  • ToolPermissionAllowlist: Per-bot tool ACL allow/deny gate — see docs/guides/TOOL_PERMISSION_ALLOWLIST_GUIDE.md
  • StickyBotAffinityStore: session→bot sticky affinity for multi-bot continuity — see docs/guides/STICKY_BOT_AFFINITY_GUIDE.md
  • RunDeadlineWatchdog: advisory wall-clock run deadline (remaining/expired) — see docs/guides/RUN_DEADLINE_WATCHDOG_GUIDE.md
  • ToolCallIdempotencyCache: hash-keyed tool result replay for retries — see docs/guides/TOOL_CALL_IDEMPOTENCY_CACHE_GUIDE.md
  • ToolResultTruncator: soft mid-string cap for oversized tool results before LLM context — see docs/guides/TOOL_RESULT_TRUNCATOR_GUIDE.md
  • SharedBlackboard: typed cross-bot scratchpad with revision caps — see docs/guides/SHARED_BLACKBOARD_GUIDE.md
  • RateLimitedToolRunner: per-tool sliding-window call budget — see docs/guides/RATE_LIMITED_TOOL_RUNNER_GUIDE.md
  • AdaptiveConcurrencyLimiter: global in-flight tool cap with adaptive shrink — see docs/guides/ADAPTIVE_CONCURRENCY_LIMITER_GUIDE.md
  • ToolCircuitBreaker: per-tool failure isolation (closed/open/half-open) — see docs/guides/TOOL_CIRCUIT_BREAKER_GUIDE.md
  • ObservationRedactor: PII/token scrub before event logs — see docs/guides/OBSERVATION_REDACTOR_GUIDE.md
  • ToolArgumentSanitizer: scrub secrets from tool args before execute — see docs/guides/TOOL_ARGUMENT_SANITIZER_GUIDE.md
  • ConversationSummarizer: extractive rolling summary (head/tail + keywords) — see docs/guides/CONVERSATION_SUMMARIZER_GUIDE.md
  • ToolResultSchemaValidator: validate ToolResult before observe — see docs/guides/TOOL_RESULT_SCHEMA_VALIDATOR_GUIDE.md
  • BudgetedStepPlanner: token/cost-aware step caps before LLM calls — see docs/guides/BUDGETED_STEP_PLANNER_GUIDE.md
  • ToolRetryBackoffPolicy: jittered exponential tool retries — see docs/guides/TOOL_RETRY_BACKOFF_GUIDE.md
  • EventLogCompactor: bounded head/tail event-log compaction — see docs/guides/EVENT_LOG_COMPACTOR_GUIDE.md
  • RunReplayDiff: run-to-run event-log drift diff (ignores timestamps) — see docs/guides/RUN_REPLAY_DIFF_GUIDE.md
  • SpeculativeToolPrefetch: speculative next-tool ranking (never executes) — see docs/guides/SPECULATIVE_TOOL_PREFETCH_GUIDE.md

| Gap filled by ToolCallQuotaGuard | Missing per-session per-tool call quota | ToolCallQuotaGuard — see docs/guides/TOOL_CALL_QUOTA_GUARD_GUIDE.md | | Gap filled by BotTurnFairnessScheduler | Missing turn fairness across bots | BotTurnFairnessScheduler — see docs/guides/BOT_TURN_FAIRNESS_SCHEDULER_GUIDE.md | | Gap filled by BotRoleConflictDetector | Missing exclusive role-collision detection | BotRoleConflictDetector — see docs/guides/BOT_ROLE_CONFLICT_DETECTOR_GUIDE.md | | Gap filled by PlanStepDependencyResolver | Missing explicit plan-step dependency resolver | PlanStepDependencyResolver — see docs/guides/PLAN_STEP_DEPENDENCY_RESOLVER_GUIDE.md | | Gap filled by BlackboardEntryTtlEvictor | Missing per-entry blackboard TTL eviction | BlackboardEntryTtlEvictor — see docs/guides/BLACKBOARD_ENTRY_TTL_EVICTOR_GUIDE.md | | Gap filled by BotHeartbeatLivenessWatchdog | Missing per-bot heartbeat liveness | BotHeartbeatLivenessWatchdog — see docs/guides/BOT_HEARTBEAT_LIVENESS_WATCHDOG_GUIDE.md | | Gap filled by SharedBlackboardWriteLease | Missing exclusive blackboard write leases | SharedBlackboardWriteLease — see docs/guides/SHARED_BLACKBOARD_WRITE_LEASE_GUIDE.md | | Gap filled by FanInBarrierGate | Missing explicit fan-in barrier gate | FanInBarrierGate — see docs/guides/FAN_IN_BARRIER_GATE_GUIDE.md | | Gap filled by ToolResultFingerprintDeduper | Missing tool-result fingerprint dedupe | ToolResultFingerprintDeduper — see docs/guides/TOOL_RESULT_FINGERPRINT_DEDUPER_GUIDE.md | | Gap filled by HitlEscalationCooldownGate | Missing HitlEscalationCooldownGate capability | HitlEscalationCooldownGate — see docs/guides/HITL_ESCALATION_COOLDOWN_GATE_GUIDE.md | | Gap filled by BotSkillTagRouter | Missing skill-tag overlap bot router | BotSkillTagRouter — see docs/guides/BOT_SKILL_TAG_ROUTER_GUIDE.md | | Gap filled by SessionTokenBudgetLedger | Missing per-session soft/hard cumulative token ledger | SessionTokenBudgetLedger — see docs/guides/SESSION_TOKEN_BUDGET_LEDGER_GUIDE.md | | Gap filled by BotVoteConsensusAggregator | Missing majority/plurality bot consensus | BotVoteConsensusAggregator — see docs/guides/BOT_VOTE_CONSENSUS_AGGREGATOR_GUIDE.md | | Gap filled by SessionTtlExpirer | Missing per-session idle TTL | SessionTtlExpirer — see docs/guides/SESSION_TTL_EXPIRER_GUIDE.md | | Gap filled by ToolCallLatencyTracker | Missing local per-tool p50/p95 latency | ToolCallLatencyTracker — see docs/guides/TOOL_CALL_LATENCY_TRACKER_GUIDE.md | | Gap filled by CriticBotVerdictGate | Missing critic accept/revise/reject gate | CriticBotVerdictGate — see docs/guides/CRITIC_BOT_VERDICT_GUIDE.md | | Gap filled by ConversationTurnBudgetGuard | Missing per-session turn budget | ConversationTurnBudgetGuard — see docs/guides/CONVERSATION_TURN_BUDGET_GUIDE.md | | Gap filled by ToolPermissionAllowlist | Missing per-bot tool ACL | ToolPermissionAllowlist — see docs/guides/TOOL_PERMISSION_ALLOWLIST_GUIDE.md | | Gap filled by StickyBotAffinityStore | Missing sticky session→bot pin | StickyBotAffinityStore — see docs/guides/STICKY_BOT_AFFINITY_GUIDE.md | | Gap filled by RunDeadlineWatchdog | Missing wall-clock advisory deadline | RunDeadlineWatchdog — see docs/guides/RUN_DEADLINE_WATCHDOG_GUIDE.md | | Gap filled by ToolCallIdempotencyCache | Missing local tool idempotency | ToolCallIdempotencyCache — see docs/guides/TOOL_CALL_IDEMPOTENCY_CACHE_GUIDE.md | | Gap vs popular stacks for DeadLetterToolQueue | Missing local HITL control | DeadLetterToolQueue adds offline HITL-safe behavior for GPT-5.5 / Claude Sonnet 4.6 / Gemini 3.x / Kimi K2 | | Gap filled by BotHandoffReceiptStore | Missing structured bot handoff audit | BotHandoffReceiptStore — see docs/guides/BOT_HANDOFF_RECEIPT_GUIDE.md |

About

Deterministic multi-provider AI-agent orchestrator — ODA loops, GPT/Claude/Gemini/Kimi adapters, safety controls, event log

Topics

Resources

Stars

12 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages