composable AI agents

Weaveflow architecture

An LLM is a brain. An agent is the body. The better the body, with its sensors, memory, actuators, and nervous system, the better the output of the brain. Weaveflow defines the standard anatomy of that body so any LLM can inhabit any agent, and any agent can plug into any other. USB for AI agents.

open ports

Typed input & output

Every agent exposes schema-defined ports as its public interface. Internals stay private.

swappable brain

Any LLM backend

Pick a brain with a "provider:model" string. No vendor API leaks into the core.

auto-connect

Protocol + transforms

Compatible-but-different types are bridged automatically between agents.

01 Agent anatomy

Each layer of an agent maps to a part of a body. The skin (ports) is what the outside world touches; the brain is the swappable LLM; the nervous system validates everything around it.

Skin: interface ports (PortSchema) Nervous system: guardrails · validation · errors Sense organs input Payload Brain LLMAdapter Memory short · long-term Action organs output Payload validate · pre coerce · validate · post
Skin (ports) Brain (LLM) Nervous system Organs

02 Module map

Each anatomical layer is a Python package. Files stay small (≤300 LOC) and single-purpose.

types/

The 8 primitive DataTypes, the compatibility graph, and the immutable Payload envelope.

schema/

PortSchema contracts, the fail-fast validate(), and a reference registry.

llm/

LLMAdapter ABC, a provider factory, and 6 adapters with lazy SDK imports.

memory/

Memory ABC, a bounded short-term buffer, and a long-term vector store.

guardrails/

The pre / post / on_error hook bundle (the nervous system).

agent/

BaseAgent lifecycle, the injected AgentContext, and the @agent decorator.

connection/

The protocol engine, the Router/matcher, and the transform registry.

interop/

Bridges that wrap a LangChain/CrewAI/any callable agent as a BaseAgent: duck-typed, no rewrite.

runtime/

Pipeline (series) + Parallel (fan-out/in) composition and the in-process LocalRunner with tracing.

cli/

scaffold / validate / package, dispatched via a lookup map.

errors.py · logger.py

Centralized typed errors (code + message + docs link) and an env-aware structured logger.

03 Dependency direction

Outer layers depend inward on abstractions, never outward on concretions (Dependency Inversion). No vendor SDK API crosses an adapter boundary.

cli ─┐
     ├─> runtime ─> connection ─> agent ─> guardrails
     │                              │   ├─> llm  (LLMAdapter ABC) ─> adapters
     │                              │   └─> memory (Memory ABC)
     └──────────────────────────────────> schema ─> types ─> errors
                                                            └─> logger
The core knows only the LLMAdapter and Memory interfaces. Concrete providers and stores are injected, so they swap without code changes.

04 Agent execution lifecycle

BaseAgent.run(payload) runs the nervous system around your handler. It is fail-fast and never swallows exceptions.

validateinput guardrailspre handleyour logic coerce validateoutput guardrailspost on error → on_error hooks · log · re-raise (typed)

05 Connection protocol

When agent A's output feeds agent B's input, a four-step handshake runs. Incompatible links are rejected before any LLM call.

1 · type matchidentical / compatible 2 · shape validatestructured_json 3 · capability checkRouter (tag affinity) 4 · transform injectif compatible ≠ identical → B.input

Deterministic transforms

code↔text, structured_json→text, document→text, stream→text, all without an LLM.

Semantic transforms

text→structured_json uses the brain (extraction prompt). Fails fast if no brain is supplied.

06 Pipelines & the local runner

A Pipeline chains agents and validates every link at construction. The LocalRunner runs a chain in-process and records a per-hop trace.

cleaner extractor summarizer textprotocol.handoff (validate+transform) structured_jsonprotocol.handoff (validate+transform)
trace = await LocalRunner().simulate([cleaner, extractor], "raw input")
for hop in trace.hops:        # Hop(agent, output, elapsed_ms)
    print(hop.agent, hop.output.value, hop.elapsed_ms)

07 Standard data types

Interoperability depends on every port speaking these 8 primitives.

TypePython valueCommon use
textstrprompts, summaries, reports
structured_jsondictextraction, structured output
imagestr | bytesvision, document parsing
codestrcode gen / review / execution
audiostr | bytesvoice input, transcription
documentstr | bytesdocument analysis, drafting
embeddinglist[float]semantic search, RAG
streamiteratorlive, long-form output

08 Type compatibility graph

A source type feeds a target directly (identical) or via a transform. Anything not on an edge is incompatible and rejected at connect time.

text structured_json code stream document text→json · LLM json→text · dumps text ↔ code · retag stream→text document→text
LLM-backed transform deterministic retag / serialize

09 Design principles

No if/elif dispatch

Providers, transforms, type checks, and CLI commands all resolve through lookup maps, so adding a case never edits existing code (Open/Closed).

Immutability by default

Payload and PortSchema are frozen. Agents and transforms return new objects instead of mutating state.

Dependency injection

Brain, memory, and logger are injected into the context: no globals, fully testable, swappable per environment.

Fail fast, never swallow

Inputs are validated at the boundary; failures raise typed errors with a code, plain-language message, and docs link.

Zero-dependency core

Provider SDKs are optional extras, imported lazily. The base install carries no runtime dependencies.

Small & SOLID

Single-responsibility modules ≤300 LOC, guard clauses over nesting, abstractions over concretions.