Skip to content

About

Typed capability routing and governed execution for AI agents across APIs, tools, and data systems.

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

SchemaRouter

When tools speak different schemas, put a typed capability boundary in between.

English · 한국어 · Docs · Examples · Contributing · Latest release

CI Docs CodeQL Security Audit PyPI Python MIT

Stable release: 0.17.0 · Beta / pre-1.0

SchemaRouter is a typed capability routing and governed execution layer for AI agents across APIs, tools, and data systems.

It sits between an orchestrator and external tools/data sources, compiles heterogeneous schemas into one capability model, retrieves a bounded candidate set, and validates the selected call again at execution time. It is not an agent framework, identity provider, database proxy, or LLM gateway.

pip install schemarouter

Why SchemaRouter

A normal tool router mainly answers which tool should I call? SchemaRouter also keeps track of which endpoint, parameters, output fields, schema version, access path, policy, and execution binding make that call valid.

user query
  -> required semantic fields
  -> bounded capability candidates
  -> endpoint + parameters + output fields
  -> policy / availability / schema validation
  -> trusted execution
  -> projected typed result

This is useful when a catalog mixes APIs, MCP servers, SDKs, databases, and framework tools whose names overlap but whose schemas and execution constraints differ.

Concepts → · Capability retrieval → · Execution boundary →

Quickstart

Provider-first onboarding lets an application start from the service it wants rather than knowing every underlying protocol first.

import asyncio

from schemarouter import PlanRequest, SchemaRouter


async def main():
    router = SchemaRouter()

    async with router:
        registration = await router.add_provider("apis-guru")
        tool = router.registry.get(registration.registered_tool_keys[0])

        plan = router.plan(
            PlanRequest(
                query="API directory metrics total number of APIs",
                preferred_tools=[tool.key],
                max_calls=1,
            )
        )

        result = (await router.execute(plan))[0]
        print(result.tool, result.endpoint, result.data["numAPIs"])


asyncio.run(main())

The flow is:

provider identity → schema/adaptor resolution → typed capability registration → bounded selection → validated execution → typed result

Quickstart → · Runnable examples →

Where it fits

flowchart LR
    Q["User"] --> A["Agent / RAG / application"]
    A --> SR["SchemaRouter"]
    SR --> S["APIs / MCP / SDKs / databases"]
    S --> SR
    SR --> A
Loading

The surrounding application owns conversation, decomposition, memory, checkpoints, and final generation. SchemaRouter owns the registered capability and execution boundary.

Agent-neutral tool catalog: the router first supplies a bounded, optionally principal-authorized index of available capabilities (route ID, endpoint, parameter/output-field contract). The agent/application interprets the task, chooses whether and which tool to use, constructs its arguments and decides whether to continue. SchemaRouter does not autonomously decide the next agent step, call tools merely because they appeared in retrieval, or write a final answer. Its optional plan/execute APIs are explicitly host-invoked validation and execution boundaries, not an autonomous agent loop. The research-only scoped index-to-schema experiment lives under scripts/; it is not a new product-default model or a production catalog_index API.

Retrieval is side-effect free:

candidates = router.retrieve("current Young's modulus for MAT-7", k=5)

for candidate in candidates.candidates:
    print(candidate.route_id, candidate.output_fields)

Use retrieve_executable(...) when the shortlist must also have a currently valid execution binding.

For the original paper's field-only routing (which schema fields and typed dependencies a caller would need, without fetching provider records), register your source schemas and opt into router.prepare_field_routing() without a concept catalog to begin owner review. Call session.inspect_visible_fields(principal=reader, limit=32) first. After the owner explicitly approves NativeConceptCatalog, call session = session.revise_catalog(catalog=reviewed_concepts); this activates field routing without rebuilding the registered-source index. Alternatively start with router.prepare_field_routing(catalog=reviewed_concepts) if your application already has an approved catalog. Then call session.discover(query, principal=reader) for field-complete alternatives or session.compile(query, principal=reader) for typed, non-executing call shapes and source/unit/license field metadata. Each compiled route also provides parameters_meta (safe input names, required flags, locations, JSON type labels), without exposing raw schema annotations or supplying argument values. For registered sources needing an explicit host-supplied scalar (for example, an owner-reviewed lookup identifier), first inspect that native input shape, then approve it alongside the concept mapping: session = session.revise_catalog(catalog=reviewed_concepts, approved_host_roots=frozenset({"directory.lookup"})). Omit the argument to retain existing root approvals, or pass frozenset() to revoke all of them. These are exact registered route IDs, not model-suggested values. Revision reprojects only original tools affected by added/revoked roots through the existing canonical graph updater; no source-wide index rebuild, provider call or implicit execution grant occurs. An approved root can yield a typed plan with requires_arguments; only separately supplied valid host values may produce a ready non-executing receipt. Each bounded page contains only currently authorized and visible scalar field names, semantic IDs, type labels, units, safe source input parameter shapes (parameters_meta: canonical name, required flag, location and basic JSON types), and (when not scope-restricted) source/license labels. The input projection uses the same live principal/data-scope rules as the guarded typed plan; it excludes descriptions, wire aliases, raw JSON schemas, defaults and argument values. At most 512 parameters per route and 2,048 per page share the conservative 256 KiB review budget with output field metadata; oversized routes fail explicitly. Follow the fixed-size opaque next_after_route token by passing it as after_route until it is None. That token is tied to the source snapshot and works even when registered route names are unusually long or contain control characters. The prepared native index now materializes its route→semantic reverse inventory, sorted review routes and original-format sr1: cursor anchors once per verified source revision. Review pages use an indexed cursor or binary-search seek instead of rebuilding and sorting the entire posting catalog on each page. This does not replace live source fingerprint, principal permission, field-scope or availability checks; pages may still scan forward past denied or unavailable sources. An opaque cursor is also rechecked against the current principal's live visibility before it can be used as an anchor: unknown, hidden and revoked anchors are rejected indistinguishably. The reverse index stays private on the trusted host and must never be served as an authorized route listing. Measure synthetic 500/5,000-route page CPU/latency and serialized metadata bytes with python -m scripts.benchmark_native_review_pagination_b1 --routes 5000 --limit 64 from the repository root. This is not an independently measured ranking or E2 improvement. This does not auto-author a NativeConceptCatalog, guess aliases or fetch records: the host still reviews and explicitly declares concept meanings. On a review-only session, discover, compile, compile_understood, acompile_query and catalog_revision_id fail closed until catalog approval. Recheck current authorization when using this inventory; a cursor is not permission and different pages are not a policy-stable snapshot. The complete operator-oriented Schema Explorer intentionally remains a separate interface and is not a principal-safe substitute for this review API. Keep the prepared session object on the trusted host: properties such as indexed_route_count intentionally describe the full registered index, not one principal's visibility. Expose only the principal-scoped page receipts to consumer UIs. Pages have strict route (64), field (512 per route / 2,048 per page), and conservative byte budgets; unusually wide or huge registered schemas fail explicitly rather than silently omitting reviewable fields. Those schemas require host-side splitting or a future field-granular paging interface. Live, internally stateful availability callbacks are not an atomic permission snapshot: owners must supply stable/versioned availability state when they need transaction-like consistency and reauthorize later actions. If your application already has structured Understand(q) hints, use session.compile_understood(PaperUnderstanding(...), principal=reader, intent_registry=reviewed_intents) to reuse the same prepared index and the original source-proven direct/one-/two-hop graph compiler. The host must review intent→concept contracts; model-proposed parameter_hints are never used as call arguments. compile_understood does not call an LLM or any provider. For natural-language paraphrases, the separate opt-in async method await session.acompile_query(query, principal=reader, model=host_callback, intent_registry=reviewed_intents, model_visible_intent_ids=(...), model_visible_concept_ids=(...), model_visible_tool_keys=(...)) invokes one host-owned model callback with only explicitly disclosed, currently field-proven intent/concept IDs and scoped tool keys. It strictly validates the returned IDs, and delegates to that same source-proven direct/one-/two-hop compiler. The caller is responsible for model privacy and deciding what to disclose; the router never fetches provider rows, promotes model suggestions to source authority, or binds model-proposed parameter values. An independent model-understanding benchmark is still required.

For original-paper Algorithm 1 Rank(Tcand, C), a trusted host can optionally declare PaperNativeRankPolicy(snapshot_id=session.snapshot_id, route_priorities={"already.reviewed.route": 2}) and pass it as rank_policy=... to compile_understood or acompile_query. Annotated route IDs must be currently registered and the source snapshot must match; the policy also supports owner-assigned domain tags (PaperIntentDeclaration domains) and web-route demotion. The index first checks the entire eligible native FieldGroup cohort against live principal authorization, field scope, source contract, health and fingerprints, then locally ranks only those already-qualified routes to Top-K5 (including >32 and 5,000-route catalogs). Direct plans report ranked_unverified=True and always executable_authority=False; no model/provider is invoked by Rank. Named tool overrides continue through their original scoped source proof without implicit ranking. Without an explicit rank policy, the original over_k_unranked refusal at >K remains unchanged. Dependency-path G5 ranking, quality evidence, independently reviewed E2 and default promotion are not implemented by this opt-in direct route feature. Synthetic CPU/byte evidence: python -m scripts.benchmark_paper_native_rank_b1 --routes 5000 --repeats 3; this is not an E2 accuracy benchmark.

After registered schemas change, the original prepared session becomes stale. Call new_session = session.refresh() to create a new immutable field-routing snapshot using the built-in in-memory/SQLite registry's verified change journal. For a custom registry, pass changed_tool_keys=(...) to the same method; the full native manifest is checked before accepting those hints. A missing, expired or inconsistent delta fails closed (reprepare explicitly if needed). Refreshing does not fetch provider records, invoke tool executors, or grant a model permission to supply query arguments. The old session stays stale. If only the host-reviewed concept/field catalog changes while registered schemas stay at the same revision, call new_session = session.revise_catalog(catalog=updated_reviewed_concepts). This revalidates and detaches the owner's bounded catalog while reusing the same pinned posting/G5 graph, including host-approved roots; there is no schema rescan or provider execution. Use (new_session.snapshot_id, new_session.catalog_revision_id) to partition host schema/semantic caches: the source-only snapshot_id intentionally stays unchanged across a catalog revision. With nonempty explicit root approvals, catalog_revision_id also commits to the sorted root list; changing only that list changes the semantic cache key. Do not use this pair alone to cache or replay authorized discover()/compile() results. Those results depend on the live principal, authorization policy, allowed scopes and availability, and require current source and authorization rechecks at use time. The application must atomically replace and retire its old handle after approving new meanings: independently retained old sessions are not automatically revoked by this API. Source changes still require refresh(); a stale source refuses catalog-only revision. The caller supplies an explicit authorization policy; a reviewed semantic concept catalog is required to route, not to inspect source fields. Undeclared or conflicting concept aliases raise ValueError (no guessed mapping). Changes require a fresh session, either through verified refresh(), catalog-only revise_catalog() or an explicit new prepare_field_routing build; none grants execution permission. See the runnable provider-neutral field-routing example.

Connect tools and data

SchemaRouter supports several ingress families behind the same capability model.

Family Typical entry points
Provider identity await router.add_provider(...)
Typed Python / ToolSpec add_callable(...), add_tool(...), add_bound_tool(...)
API protocols OpenAPI, MCP, OPTIMADE, GraphQL, OData, OpenRPC
Relational databases SQLite, caller-owned SQLAlchemy Engine
Vector databases Qdrant, Milvus, Pinecone, Weaviate, Chroma, PostgreSQL/pgvector
Graph / RDF Neo4j, Neptune, ArangoDB, FalkorDB, SPARQL
Document / search / KV / time-series MongoDB, Elasticsearch/OpenSearch, DynamoDB, Cosmos DB, Couchbase, ClickHouse, InfluxDB
Framework bridges LangChain, LangGraph, LlamaIndex

Credentials, connection pools, database clients, and transport state remain caller-owned. Vendor query languages are not exposed as model authority.

Choose an ingestion path → · Full ingestion matrix → · Provider-first registration → · Enterprise data onboarding →

Authorization and enterprise data

SchemaRouter consumes a verified PrincipalContext from the host application and can apply deny-by-default RBAC/ABAC before retrieval and again at execution.

The same policy model can narrow:

  • visible tools/endpoints;
  • database tables and fields;
  • trusted row/tenant filters;
  • vector metadata filters;
  • document/search fields;
  • graph relationships and hop depth.

SchemaRouter does not authenticate users and does not replace database-native roles, grants, RLS, ACLs, or network controls.

Authorization → · Database onboarding →

Execution boundary

A retrieved candidate or model choice never grants authority by itself. Before a result crosses the runtime boundary, SchemaRouter can revalidate:

  • tool/endpoint/schema fingerprints;
  • arguments and JSON Schema contracts;
  • principal policy and data scope;
  • current execution binding and availability;
  • raw output schema;
  • final field projection.

Remote mutation/destructive operations fail closed unless explicitly authorized by local policy.

Security model → · Trust and release evidence →

Stability

Qualification status: 19-item evidence checklist tracks engineering/DEV evidence, independent E2, blockers and the separate v0.18 release HOLD. Green CI does not imply task-level qualification.

SchemaRouter 0.17.0 is Beta / pre-1.0. Public APIs may still evolve, but breaking changes are documented and release-gated.

Python 3.10–3.14 are release-blocking targets. Python 3.15 is a preview target.

Research benchmarks are kept separate from stable product guarantees. Experimental retrieval profiles or ranking methods are not promoted solely because one benchmark improves.

Versioning policy → · Research status → · Changelog →

Documentation

Topic Guide
Installation and first use Getting started
Architecture and concepts What SchemaRouter is
Provider/API ingestion Connect guides
Enterprise databases and access scope Enterprise data onboarding
Runtime policy and authorization Authorization
Operational inspection Inspection
Public API Reference
Research evidence Research index

Full documentation: https://jdeun.github.io/SchemaRouter/

Scope

SchemaRouter deliberately does not own agent loops, final answer generation, identity authentication, credential storage, or arbitrary database/query execution. Those remain outside the capability boundary.

License

MIT. See LICENSE.

About

Typed capability routing and governed execution for AI agents across APIs, tools, and data systems.

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages