Babelagent: The Whole Story¶
A long-form explainer of what Babelagent is, why it exists, how it is built, how anything plugs into it, where it honestly sits in a crowded field, and the tradeoffs behind every choice. Read it once and you should be able to stand at a whiteboard, explain the skeleton, explain how any agent clicks onto it, and answer the hard question a skeptic will ask: "how is this different from everything else?"
1. The premise, in one breath¶
Babelagent is the Babel that lets AI agents understand each other. The story it is named for is the Tower of Babel, where one people speaking one language were scattered into many tongues and could no longer cooperate. That is exactly the state of AI agents today. Every framework and every provider speaks its own dialect, and the moment you want two of them to work together you are stuck translating by hand. Babelagent gives them one shared tongue. You bring your own agents, whatever dialect they were born in, and it wraps each one in a single common interface so they can pass messages to each other, collaborate on a task, and be checked at every step along the way.
That is the whole pitch. Everything else in this document is about why that pitch is worth building, how the machine underneath makes it true, and how honest we can be about which parts of it are genuinely new.
2. The problem it answers¶
If you have ever tried to wire two agent frameworks together, you already know the pain. Every framework has its own idea of what an agent is. LangChain has Runnables with .invoke. CrewAI has Crews with .kickoff. AutoGen has conversational agents with .generate_reply. An LLM provider has a client and a messages.create. A microservice has an HTTP route and a JSON body. None of these agree on a shape, so the moment you want a summarizer from one world to feed a classifier from another world, you are writing glue. You unwrap a response object here, reshape a dict there, remember which call is async and which one blocks, catch four different kinds of errors, and hope the whole thing runs in the right order.
The frameworks themselves do not fix this, because each one wants to be your whole world. Adopt our agent model, our memory, our tool protocol, our orchestration. That is a fair bet for a framework. It also means two of them in one process fight rather than cooperate. What has been missing is a neutral layer. Not another framework. A thin layer in the middle that speaks each agent's dialect, gives them all one shared interface, and lets a result pass from any agent to any other while checking the work as it goes.
Babelagent is that neutral layer. It deliberately owns very little. It does not want to be your agent framework. It wants to be the shared tongue between them, the wiring that carries messages from one to the next, and the inspector that grades each hop before it continues. The value is not in any single adapter. It is that once something is adapted, it composes with everything else that has been adapted, and it does so under one execution model with one set of rules.
3. Why the Babel metaphor is load-bearing¶
Metaphors in software are usually decoration. This one is structural. The Babel framing tells you, before you read a line of the API, what the library is for and what it refuses to do. It is for turning many incompatible agent dialects into one working conversation. It refuses to become the thing everyone must rewrite their agents in. A newcomer who hears "Babel for agents" already has the right mental model: bring what you have, and the layer translates.
The framing also sets the emotional register. Babel is about miscommunication and the cost of it. So the library's job is measured by one question: did these agents actually understand each other and produce something correct. That is why a quality check on every hop is a first-class idea in Babelagent rather than an afterthought. The name commits us to caring whether the translation worked, not just whether a message was passed.
4. The mental model you will teach from¶
Here is the smallest complete example. Every noun in it maps to a concept you will explain to others.
from babelagent import Graph
graph = (
Graph()
.node("clean", str.strip)
.node("shout", str.upper)
.node("exclaim", lambda s: s + "!")
)
result = await graph.run(" hello ") # result.output == "HELLO!"
Three things are happening. First, Graph() starts an empty graph of agents. Second, each .node(name, agent) adds a participant and, because no dependencies were named, wires it after the previous one, so the first agent hands its message to the second. Third, run sends a Message carrying " hello " into the graph, each agent transforms the payload and passes it along, and the value that comes out the end becomes the Result.
Now the same machine, with agents working in parallel and then rejoining. This is the agent-to-agent case: several different agents contribute, and their outputs are combined.
g = Graph()
g.node("src", lambda text: text)
g.node("summary", adapt(langchain_agent), after=["src"]) # a LangChain agent
g.node("labels", adapt("https://api.example.com/classify"), after=["src"]) # an HTTP service
g.join("merge", after=["summary", "labels"], agent=combine, barrier="all")
result = await g.run(document)
The moment you pass after=[...], you have stopped drawing a straight line and started drawing a graph. summary and labels both depend on src, so they run at the same time. merge depends on both, so it waits for them and then receives their outputs joined into a dictionary keyed by node name. The important teaching point is that linear is not a different system from the graph. It is a convenience over it. You never graduate from one API to another. You just start naming dependencies when you need to.
The last concept in the model is the quality gate. Any node can carry a check that inspects the message after the agent runs and returns a Grade of pass, warn, or fail. A gate mode decides what that grade does: advisory (record it, never block), block on fail, or block on warn or worse. This is the inspector on every hop. It is optional, it is native to Babelagent, and as section 9 explains, it is the part of the design that is genuinely hard to find anywhere else.
5. The skeleton: three layers¶
Structurally, Babelagent is three concentric layers, and keeping them separate is one of the most important decisions in the whole project.
The innermost layer is the core engine. It knows about messages, agents, nodes, the topology of the graph, the scheduler that runs it, and the native grade types. It has no idea that HTTP exists, or that LangChain exists, or that there is a command line. It depends only on Pydantic for its data types. If you deleted every adapter and every interface, the core would still compile and run any agent that already speaks the Agent shape.
The middle layer is the adapters. This is the "bring your own" machinery: the code that takes something from the outside world and makes it a node in the graph. Callables and HTTP live here in the light base install. MCP, LLM providers, and the big frameworks live here too but behind optional extras, so they are only imported if you actually use them. The crown of this layer is adapt(), the function that looks at whatever you hand it and decides which adapter applies. Adapters depend on the core, never the other way around.
The outermost layer is the interfaces: the ways a human or another program drives the graph. Today that is the command line (babelagent demo, babelagent doctor, babelagent inspect) plus a key-free demonstration. Planned for this layer are a REST service that serves a graph over HTTP and an MCP server that exposes a graph as a tool. Interfaces depend on the core and the adapters. Nothing depends on interfaces.
interfaces (CLI, demo, doctor; planned: REST, MCP server)
|
adapters (adapt(), callable, http/openapi, mcp, llm, frameworks, registry)
|
core engine (Message/Result, Agent, Node, Topology, Scheduler, Verdict/Grade)
|
pydantic (the only hard dependency of the core)
The reason this layering is worth guarding is that it keeps the base install light and the core reusable. Dependencies flow strictly inward. A change to how HTTP works cannot ripple into the scheduler. A new interface cannot force a new core concept. And because the core has no knowledge of any specific agent technology, it will not age the way a framework-coupled core would when the framework of the month changes.
6. The core engine, part by part¶
The Message and the Result¶
The thing that travels between agents is a Message. It carries three things: a payload (the actual data, which can be anything), a meta dictionary (provenance and annotations that accumulate as it moves), and an optional content_type hint. A message is a small envelope rather than just the raw payload because a conversation between agents needs to carry more than the words. It needs the record: which agent touched this, what the HTTP status was, which model produced this text. Every time an agent produces output, Babelagent stamps the node's name into that record. When the run finishes you get a Result: the final output, a boolean ok, an overall verdict, and a trace that is the full list of what happened at each node. The result is deliberately more than "the answer." It is the answer plus the record of how it was reached, which is exactly what you want when something goes wrong and you need to see where.
The Agent protocol: the one shape everything becomes¶
At the very center of the design is a single, tiny contract.
@runtime_checkable
class Agent(Protocol):
name: str
async def run(self, message: Message, ctx: Context) -> Message: ...
This is the narrow waist of the whole system, and it is the shared tongue the name promises. Everything a user brings is eventually turned into something with a name and an async run that takes a message and returns a message. Making this a Protocol rather than a base class you must inherit from is deliberate and important. It means an object can be an agent without ever importing Babelagent. Duck typing is the point. If your object already has a name and an async run of the right shape, Babelagent treats it as an agent and never wraps it. This keeps coupling as loose as it can be, and it is why the library can honestly call itself neutral.
Making run async is also deliberate. The advantage of a graph is that independent agents can work at the same time, and async is the cheapest way to get that concurrency for the kind of work agents do, which is overwhelmingly waiting on I/O: an HTTP response, an LLM, a subprocess. Synchronous agents are not shut out. The callable adapter runs blocking functions on a worker thread so they never freeze the event loop. But the native language of the graph is async, because that is what lets a fan-out actually fan out.
The Context¶
Alongside the message, every run receives a Context. It carries a run_id, a shared state dictionary that acts as a blackboard for the whole run, and an optional deadline. The deadline is a monotonic clock value, so the scheduler can compute at any moment how much time the run has left and refuse to start work that cannot finish in time. The context is how run-wide concerns travel without being threaded through every payload by hand.
The Node¶
A Node is a small record: a name, the agent, an optional check, a gate mode, an optional timeout, and its wiring (the after list of upstream nodes and the fan-in barrier). It is intentionally dumb. It does not run anything itself. It is just the description of one participant and how it is connected. Keeping the node as data rather than behavior is what lets the graph be inspected and reasoned about without executing anything.
The Topology and why validation happens before running¶
When you call Graph.compile(), the nodes you have added become a Topology, and the topology is validated before it is ever run. Validation checks two things: that no node depends on a name that does not exist, and that the graph is acyclic. The cycle check uses Kahn's algorithm, repeatedly removing nodes with no remaining upstream dependencies. If any nodes are left over, they form a cycle and compilation fails with an error that names them. This "fail at build time, not at run time" stance pays off constantly. A typo in a dependency name, or an accidental loop, is caught the instant you compile, with a clear message, rather than showing up later as a hang or a confusing partial run. The topology also describes itself as plain JSON (topo_spec), which powers babelagent inspect. The honest limitation: the shape serializes, but the live agents do not, because an arbitrary Python object or network client cannot be rebuilt from JSON. So a graph's shape is portable and inspectable. Rebuilding a runnable graph from a saved spec is a future feature that will lean on the registry.
The Scheduler: the actual machine¶
The scheduler is where the conversation happens. It is a reactive loop rather than a fixed march through stages, and the difference matters. On each pass it does three things. It marks any node whose barrier can never be satisfied as skipped. It launches every node whose barrier is satisfied. Then it waits for at least one running node to finish and absorbs the result. It repeats until nothing is running and nothing new can start.
Reactive means a node starts the moment its dependencies are ready, not when some global stage boundary is reached. Two independent branches run genuinely at the same time, and a slow branch never holds up a fast one that does not depend on it. Concurrency is bounded by a semaphore (default of eight) so a very wide fan-out cannot spawn an unbounded number of tasks. This is both a performance choice and a safety one. Every node run is wrapped so that a raised exception, a timeout, or a blocking gate all resolve to a recorded outcome rather than crashing the whole run. Timeouts are the tighter of the node's own timeout and whatever remains of the run deadline, so neither can be exceeded.
The join semantics are worth stating precisely, because people will ask. When a node has one contributing upstream, it receives that upstream's message directly, unwrapped, which is the ergonomic thing: a normal chain just passes values along. When a node has several contributing upstreams, their payloads are merged into a dictionary keyed by upstream node name, and their meta is merged too. That is why the merge node in the parallel example received {"summary": ..., "labels": ...}, while a node with a single parent just receives the value.
The Verdict, the Grade, and the Gate¶
Babelagent has its own three-value verdict: pass, warn, fail. It has a Grade (a verdict plus a human reason), a Check (a function, sync or async, that looks at a message and returns a grade), and a GateMode that decides what a grade does to the flow. Off means the check runs for its signal but never blocks. Warn means only a fail blocks. Strict means a warn or a fail blocks. When a gate blocks, that node is marked failed, and the failure then propagates through the barrier logic downstream exactly as any other failure would. These types are native to Babelagent and borrowed from nowhere, which keeps the base install dependency-free and keeps the project honest about being a neutral layer. The overall result verdict is the worst grade any node produced, unless the graph failed to produce all its outputs, in which case it is fail.
7. How things plug in: the adapter story¶
This is the part people most want to understand, because it is the "bring your own" promise made concrete.
adapt(): the connector maker¶
The centerpiece is a single function, adapt(obj), whose job is to look at whatever you hand it and return an Agent. It tries possibilities in a deliberate order, and the order is the design. First, if the object already satisfies the agent shape, it is returned untouched. Bring something that already speaks the protocol and Babelagent gets out of the way. Second, any custom or third-party adapters that have been registered get first crack, because the people extending the system should be able to override or specialize the built-in behavior. Third come the specific recognizers: an MCP reference, then a remote A2A reference, then framework agents (LangChain, CrewAI, AutoGen), then HTTP and OpenAPI endpoints. Last comes the universal fallback: if the object is merely callable, wrap it as a callable agent.
The ordering is not arbitrary. The most specific and least ambiguous cases are checked first, and the broadest catch-all (callable) is checked last, because almost everything in Python is callable in some sense and you do not want the fallback to shadow a more meaningful interpretation. A LangChain Runnable is technically callable, but you want it recognized as a LangChain agent, so its recognizer runs before the callable fallback.
Recognizing frameworks without importing them¶
There is a subtle but important trick in how frameworks are detected. Babelagent must not import LangChain just to check whether your object is a LangChain object, because that would drag a heavy dependency into everyone's process. So detection is done by inspecting the object's type's module name and the presence of tell-tale methods. If the type's module starts with langchain and the object has .invoke, it is treated as a LangChain agent. If the module starts with crewai and it has .kickoff, it is a Crew. This answers "is this a LangChain thing?" without ever importing LangChain. The framework code is only touched when the adapter runs, and only if you brought such an object.
The callable adapter and its binding¶
The most-used adapter is the humblest: wrap a plain function. Two decisions inside it matter. First, sync functions run on a worker thread through asyncio.to_thread, so a blocking function does not stall the event loop and starve the concurrent branches. Second, a small binding rule decides how a message's payload becomes the function's arguments. If the function takes zero or one parameter, the payload is passed as a single argument. If the payload is a dictionary and the function's parameter names line up, it is spread as keyword arguments. If the payload is a list or tuple and the function takes several parameters, it is spread positionally. Otherwise it is passed as one value. This is what lets str.upper, a one-argument summarizer, and a two-argument combine(first, second) fed by a join all work without you writing binding code. It is a rule with edges, but the edges are deterministic and documented, so the behavior is predictable rather than magical.
HTTP, OpenAPI, and reading a schema versus running code¶
The HTTP adapter posts a message's payload as JSON to an endpoint and returns the response. The OpenAPI path is more interesting: given a spec (a URL or a parsed document), it reads the schema to learn how to call the service and builds the request. The line that matters for trust is this: the OpenAPI path only ever reads the schema. It never fetches or executes remote code. Reading a description of an API is safe. Running whatever an API hands back is not, and Babelagent does not do the latter. There is also a basic SSRF guard that validates the URL scheme and, unless told otherwise, refuses to call internal, loopback, or link-local addresses. This guard is conservative and will be hardened in the dedicated security pass, but even now it closes the most obvious "make the server call its own metadata endpoint" attack.
LLMs and MCP behind extras¶
LLM providers (Anthropic, OpenAI, Ollama) and MCP tools are real adapters, but their heavy client libraries are optional. They are imported lazily, at call time, and if the matching extra is not installed you get a clear "install babelagent[cloud]" style error rather than an obscure import failure. API keys are only ever read from the environment or the user's config directory, never hard-coded and never written into a serialized graph. The LLM adapter defaults to the latest Claude model, and it is a plain prompt-in, text-out agent.
The registry and entry points: extension without forking¶
The adapter layer is open. Anyone can teach adapt() a new trick in two ways. In-process, they call register_adapter(name, matches, build) with a predicate that recognizes their object and a builder that wraps it. Later registrations take precedence, so extensions can specialize built-ins. Across packages, a third-party library declares an entry point in the babelagent.adapters group, and Babelagent discovers it automatically the first time adapt() runs. A new kind of agent can be supported by publishing a small package, with no change to Babelagent itself.
8. The interfaces, and the demo that proves the idea¶
The command line is the first way to drive the graph. babelagent doctor reports which optional adapter families are installed. babelagent inspect prints a graph's topology as JSON. And babelagent demo is the one to show someone first, because it proves the whole idea in seconds with no API key and no network.
The demo builds two versions of the same one-node graph. The broken version has a summarizer with a bug: it returns an empty string. A quality check on the node requires non-empty output, so the gate blocks and the result comes back with a fail verdict and a recorded reason. The fixed version has a working summarizer, so the same graph passes the gate and produces a real result. In one command you see an agent, a quality gate refusing bad output, and then the same machine producing good output. The REST service and the MCP server, which will let other programs and other agents drive a graph remotely, are the next interfaces to build, and they are exactly where the dedicated security work will concentrate, because that is where untrusted input meets the machine.
9. Where Babelagent honestly sits in the field¶
A crowded field deserves an honest map, and being able to say what is new and what is not is the difference between a defensible product and "yet another orchestrator." A prior-art review against primary sources put every claim into one of two buckets.
Three of the four things Babelagent does are, on their own, commodity. A single interface across several agent frameworks already exists in the wild, most directly in mozilla.ai's any-agent, which wraps seven frameworks behind one interface. On-the-fly adapter generation by introspecting a callable or an OpenAPI schema is table stakes: LangChain, Pydantic AI, smolagents, FastMCP, Semantic Kernel, and Google's ADK all do it, and auto-converted OpenAPI wrappers are known to be lossy compared with hand-curated ones. A directed-acyclic-graph executor with fan-out, fan-in, and barrier policies ships in roughly ten of fifteen surveyed frameworks, several with more mature join semantics than a first version here would have. None of these three should be marketed as an invention. adapt() and the graph are convenience and plumbing, offered honestly, with the OpenAPI caveat stated out loud.
The one thing that did not turn up anywhere, as a shipping product, is the wedge. It has two parts. First, a per-node, tri-state quality gate as a first-class element of the graph: a node that emits pass, warn, or fail with a human reason and can stop bad output from flowing downstream mid-run. Every gate found in other tools is either binary (a tripwire that only fires on the first or last agent, a hook that raises to block) or positional (only at the very end). A graded, three-value, any-hop gate was a genuine gap. Second, the combination: a neutral cross-framework adapter layer and a gated graph in one library. That union was a null result in the survey. So the honest claim is narrow and specific. Babelagent is not the first to wrap frameworks, not the first to introspect tools, not the first to run a DAG. It appears to be the first to put a graded, halt-capable quality gate on every hop of a framework-agnostic agent graph.
The closest neighbor is any-agent, and the one-line difference is worth memorizing: any-agent gives you one interface across frameworks, Babelagent adds a gated graph so heterogeneous agents collaborate and get graded at each hop. any-agent has no graph executor, its adapters are hand-written rather than introspective, and its evaluation is post-hoc scoring of a finished trace rather than an inline gate that halts bad output mid-run. A sensible future move is to interoperate rather than compete: wrap an any-agent object as one more adapter and inherit its seven framework integrations, instead of reimplementing them.
The agent-to-agent protocols are not rivals at all. Google's A2A, the Model Context Protocol, IBM's ACP, and the AGNTCY effort are wire protocols, usually for remote agents and tools to talk over HTTP. They live at a different layer from an in-process Python adapter-and-graph library, and none of them ships a graph executor or a quality gate. The right relationship is to consume them. A remote A2A agent or an MCP server should become just another adaptable Agent, so Babelagent sits on top of and between the standards rather than against them. That framing, being the graded interoperability layer that speaks every protocol as an input, is the position no competitor currently occupies, and it is the one to build toward.
10. The decisions and their tradeoffs¶
Every interesting system is a pile of tradeoffs, and being able to explain why not the other thing is what separates understanding from memorization.
Neutral layer, not a framework. The biggest decision is what Babelagent refuses to be. It is not trying to own your memory, your tool protocol, or your agent model. The benefit is that it composes with everything and ages slowly, because it has almost no surface that a changing ecosystem can break. The cost is that it does less for you out of the box than an all-in-one framework. It will not manage your conversation history or your vector store, because that is deliberately not its job. This is the right trade for a piece meant to sit between tools rather than replace them.
Lead with the gate, not the graph. Given the honest map in section 9, the product is positioned around the one differentiated pillar. The graded per-hop gate is the wedge, the graph and the adapters are supporting cast. The benefit is a defensible story. The cost is discipline: it would be easy and dishonest to sell the DAG or adapt() as novel, and we do not.
Fully independent, with its own grade types. Babelagent could have depended on an external "report and verdict" contract so its gates spoke a shared language. The decision was to stay fully independent and define native Verdict, Grade, Message, and Result types. The benefit is a base install that depends on essentially nothing but Pydantic, and a project that is honestly standalone and adoptable by people outside any particular ecosystem. The cost is that Babelagent does not, today, emit any external report format, so a future optional bridge (behind an extra) is the planned way to interoperate for those who want it. Independence in the core, interoperability as an opt-in.
A graph with a linear face. Babelagent could have been a simple linear pipeline, which is less code, or a graph-only API, which is more powerful but heavier to learn. The choice was to build the graph underneath and offer linear chaining as sugar on top of it. Beginners get the one-liner and never see the graph until they need it, while power users get fan-out, parallelism, and joins from the same object with no migration. The cost is a scheduler that is genuinely more complex than a for loop, with barrier logic and failure propagation to get right. That complexity is paid once, in one file, and everyone benefits.
Barrier policies instead of all-or-nothing. When a node has several upstreams, the naive rule is "wait for all, and if any failed, give up." Babelagent supports that (all), but also k_of_n (proceed when at least k succeed) and optional (proceed once everyone has settled, pass or fail). This is what lets a graph be resilient: run three redundant agents and continue as long as one succeeds, or attach a best-effort enrichment whose failure should not sink the run. The cost is conceptual surface and a subtler definition of what "the run succeeded" means, which leads to the next decision.
Succeeded means the outputs were produced, not that nothing ever failed. Early on, the result was marked not-ok if any node anywhere failed. That was wrong in the presence of tolerant barriers: if a k_of_n join was explicitly told that one failing branch is fine, that failure should not fail the whole run. So the rule became: the run is ok if every terminal node produced its output. A failure a downstream barrier tolerated is recorded in the trace for honesty, but it does not, by itself, sink the result. A small semantic decision with a large effect on how resilient graphs behave.
Async as the native tongue, threads for the blockers. Making run async buys real concurrency for I/O-bound work, which is what agents mostly do. The price is that the whole API is async and callers must be in an event loop, a small friction for simple scripts. Blocking functions are shunted to threads. CPU-bound work that holds the GIL is the genuine weak spot, and the honest answer is that such work belongs in a process or a service that a node then calls, not inside the event loop.
A light base with heavy things behind extras. The base install carries only what callables and HTTP need. Everything large (LLM clients, MCP, the frameworks, the web server) is an optional extra, imported lazily and only when used. The benefit is a small, fast install and an import that never drags in a dependency you did not ask for. The cost is a little ceremony and the ongoing discipline of never letting a heavy import sneak into the core. It is worth keeping, because it is the difference between a tool people reach for and one they hesitate to add.
Duck-typed protocol over a mandatory base class. Requiring everyone to subclass a base class would make detection trivial but coupling maximal. A runtime-checkable protocol means an object can satisfy the contract by shape, without importing Babelagent at all. The cost is that structural typing only checks that methods exist, not that signatures are perfect, so a malformed agent fails at call time rather than registration time. For a bring-anything tool, that looseness is a feature.
Fail at compile, not at run. Validating the graph when you compile it costs a little up-front work and the occasional "why won't this compile" moment. It buys clear, early, actionable errors instead of hangs and half-finished runs. For anything that orchestrates other systems, early failure with a good message is almost always the better trade.
11. What is deliberately not there yet¶
An honest architecture document says what is shipped and what is still open. Babelagent now has a tested core, the adapter layer with adapt(), the CLI with the demo, a hardened REST service and an MCP server that let other programs and agents drive a graph remotely, and the A2A adapter that turns a remote agent into a node. It has been through its dedicated security pass: a three-surface audit and four adversarial red-team rounds (request-size caps, connection and handler limits, constant-time authentication, a fail-closed posture on any non-loopback bind, SSRF hardening, a deadline guillotine), each fix regression-pinned. The package is published on PyPI, the repository is public, the documentation is hosted, and the first release carries a DOI. What remains open is smaller and deliberately so: richer topology serialization (rebuilding a runnable graph from a saved spec), optional interop bridges, and the long tail of adapters. The residual risks that no audit removes are named plainly in the security page.
12. How to explain Babelagent in sixty seconds¶
If someone stops you in a hallway: Babelagent is the Babel for AI agents. Every framework speaks its own dialect, so getting two of them to cooperate means writing glue. Babelagent is a neutral layer that wraps any agent, a function, an HTTP or OpenAPI endpoint, an MCP tool, a LangChain or CrewAI agent, an LLM, in one shared interface, so they can pass messages to each other and collaborate on a task. You wire them into a graph that can run in a line or branch and rejoin, and here is the part nobody else ships: every hop carries an optional quality gate that grades the output pass, warn, or fail and stops bad work from continuing. It does not try to be your framework, and it treats the agent-to-agent protocols like A2A and MCP as inputs to consume rather than rivals to beat. One interface across frameworks is not new. A graded, halt-capable gate on every hop of a framework-agnostic agent graph appears to be.
13. Glossary¶
Babelagent. The neutral layer that gives heterogeneous agents one shared tongue so they can collaborate and be graded at each hop.
Graph. Builds the network of agents; offers both linear chaining and an explicit dependency (DAG) API.
CompiledGraph. A compiled, validated graph, ready to run messages between agents.
Node. One participant: an agent, its wiring (after + barrier), and an optional quality check and gate.
Agent. The uniform interface everything becomes; anything with a name and an async run(message, ctx) -> message.
adapt(). Turns any brought object into an Agent, inferring the adapter; extensible via register_adapter and entry points.
Message. The envelope agents exchange: a payload plus accumulating metadata.
Result. What a run returns: the final output, an ok flag, an overall verdict, and a full trace.
Barrier. The fan-in policy for a node with several upstreams: all, k_of_n, or optional.
Verdict / Grade / Check / GateMode. The native quality types: the judgement (pass/warn/fail), the judgement with a reason, the function that judges, and the rule for what a judgement does to the flow.
Registry / entry points. How new adapters are added, in-process or from another package, without modifying Babelagent.
This document is the narrative companion to the code. When it and the code disagree, the code wins, and this file should be corrected. — amitpatole