Metadata-Version: 2.4
Name: fastaiagent
Version: 1.4.2
Summary: Build, debug, evaluate, and operate AI agents. The only SDK with fork-and-rerun Agent Replay.
Project-URL: Homepage, https://github.com/fastaifoundry/fastaiagent-sdk
Project-URL: Documentation, https://github.com/fastaifoundry/fastaiagent-sdk/tree/main/docs
Project-URL: Repository, https://github.com/fastaifoundry/fastaiagent-sdk
Project-URL: Issues, https://github.com/fastaifoundry/fastaiagent-sdk/issues
Project-URL: Changelog, https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/CHANGELOG.md
Author-email: FastAIFoundry <support@fastaifoundry.com>
License: Apache-2.0
License-File: LICENSE
Keywords: agent-debugging,agent-observability,agent-replay,ai-agents,llm-evaluation,opentelemetry
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Requires-Dist: httpx>=0.25
Requires-Dist: opentelemetry-api>=1.20
Requires-Dist: opentelemetry-sdk>=1.20
Requires-Dist: pillow>=10.0
Requires-Dist: pydantic>=2.0
Requires-Dist: pymupdf>=1.24
Requires-Dist: rich>=13.0
Requires-Dist: typer>=0.9
Provides-Extra: all
Requires-Dist: aiosqlite>=0.19; extra == 'all'
Requires-Dist: anthropic>=0.20; extra == 'all'
Requires-Dist: bcrypt>=4.0; extra == 'all'
Requires-Dist: chromadb>=0.5; extra == 'all'
Requires-Dist: crewai>=1.0; extra == 'all'
Requires-Dist: faiss-cpu>=1.7; extra == 'all'
Requires-Dist: fastapi>=0.110; extra == 'all'
Requires-Dist: fastembed>=0.3; extra == 'all'
Requires-Dist: itsdangerous>=2.1; extra == 'all'
Requires-Dist: langchain-core>=0.2; extra == 'all'
Requires-Dist: mcp>=1.2; extra == 'all'
Requires-Dist: openai>=1.0; extra == 'all'
Requires-Dist: opentelemetry-exporter-otlp>=1.20; extra == 'all'
Requires-Dist: psycopg[binary,pool]>=3.2; extra == 'all'
Requires-Dist: pymupdf>=1.23; extra == 'all'
Requires-Dist: python-multipart>=0.0.9; extra == 'all'
Requires-Dist: qdrant-client>=1.8; extra == 'all'
Requires-Dist: uvicorn[standard]>=0.27; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.20; extra == 'anthropic'
Provides-Extra: chroma
Requires-Dist: chromadb>=0.5; extra == 'chroma'
Provides-Extra: crewai
Requires-Dist: crewai>=1.0; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.3; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs-section-index>=0.3; extra == 'docs'
Requires-Dist: mkdocs>=1.6; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
Provides-Extra: kb
Requires-Dist: faiss-cpu>=1.7; extra == 'kb'
Requires-Dist: fastembed>=0.3; extra == 'kb'
Requires-Dist: pymupdf>=1.23; extra == 'kb'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2; extra == 'langchain'
Provides-Extra: mcp-server
Requires-Dist: mcp>=1.2; extra == 'mcp-server'
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == 'openai'
Provides-Extra: otel-export
Requires-Dist: opentelemetry-exporter-otlp>=1.20; extra == 'otel-export'
Provides-Extra: postgres
Requires-Dist: psycopg[binary,pool]>=3.2; extra == 'postgres'
Provides-Extra: qdrant
Requires-Dist: qdrant-client>=1.8; extra == 'qdrant'
Provides-Extra: ui
Requires-Dist: aiosqlite>=0.19; extra == 'ui'
Requires-Dist: bcrypt>=4.0; extra == 'ui'
Requires-Dist: fastapi>=0.110; extra == 'ui'
Requires-Dist: itsdangerous>=2.1; extra == 'ui'
Requires-Dist: python-multipart>=0.0.9; extra == 'ui'
Requires-Dist: uvicorn[standard]>=0.27; extra == 'ui'
Description-Content-Type: text/markdown

# FastAIAgent SDK

**Build, debug, evaluate, and operate AI agents.**
The only SDK with **Agent Replay** — fork-and-rerun debugging — and a
**zero-ceremony Local UI** that ships inside the Python wheel.

Works standalone or connected to the [FastAIAgent Platform](https://fastaiagent.net) for visual editing, production monitoring, and team collaboration.

[![PyPI](https://img.shields.io/pypi/v/fastaiagent?v=1.4.2)](https://pypi.org/project/fastaiagent/)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
[![Tests](https://github.com/fastaifoundry/fastaiagent-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/fastaifoundry/fastaiagent-sdk/actions)
[![Python](https://img.shields.io/pypi/pyversions/fastaiagent)](https://pypi.org/project/fastaiagent/)

---

## Quickstart

```python
from fastaiagent import Agent, LLMClient

# Create an LLM client
llm = LLMClient(provider="openai", model="gpt-4o")

# Create an agent
agent = Agent(
    name="my-agent",
    system_prompt="You are a helpful assistant.",
    llm=llm,
)

# Run it
result = agent.run("What is the capital of France?")
print(result.output)
print(result.trace_id)  # every run is traced — use this ID for replay/debugging
```

## Multimodal — images and PDFs as first-class inputs

```python
from fastaiagent import Agent, LLMClient, Image, PDF

agent = Agent(name="claims", llm=LLMClient(provider="anthropic", model="claude-sonnet-4-20250514"))

result = agent.run([
    "Compare the photo to the policy and assess the claim.",
    Image.from_file("damage.jpg"),
    PDF.from_file("policy.pdf"),
])
print(result.output)
```

The same code works against OpenAI, Azure, Anthropic, Bedrock, and Ollama —
provider-specific wire formatting (and OpenAI's tool-message workaround) is
handled inside `LLMClient`. See [docs/multimodal/](docs/multimodal/index.md).

## Debug a failing agent in 30 seconds

```python
from fastaiagent.trace import Replay

# Load a trace from a production failure
replay = Replay.load("trace_abc123")

# Step through to find the problem
replay.step_through()
# Step 3: LLM hallucinated the refund policy ← found it

# Fork at the failing step, fix, rerun
forked = replay.fork_at(step=3)
forked.modify_prompt("Always cite the exact policy section...")
result = forked.rerun()
```

**No other SDK can do this.**

## Pause for human approval. For days.

```python
from fastaiagent import Chain, FunctionTool, Resume, SQLiteCheckpointer, interrupt
from fastaiagent.chain.node import NodeType


def approve(amount: str):
    if int(amount) > 10_000:
        decision = interrupt(reason="manager_approval", context={"amount": int(amount)})
        return {"approved": decision.approved}
    return {"approved": True}


chain = Chain("refund-flow", checkpointer=SQLiteCheckpointer())
chain.add_node(
    "approve",
    tool=FunctionTool(name="approve_tool", fn=approve),
    type=NodeType.tool,
    input_mapping={"amount": "{{state.amount}}"},
)

from fastaiagent._internal.async_utils import run_sync

# First run — suspends and the process can exit cleanly.
result = chain.execute({"amount": 50_000}, execution_id="refund-abc")
assert result.status == "paused"

# Hours, days, or a server restart later, in any process:
result = run_sync(chain.aresume(
    "refund-abc",
    resume_value=Resume(approved=True, metadata={"approver": "alice"}),
))
assert result.status == "completed"
```

Crash-proof agents (real `SIGKILL` resumes at the last checkpoint),
SQLite locally / Postgres in production (same Protocol surface), the
`@idempotent` decorator that makes `charge_customer` safe to call
inside a paused node, and a built-in `/approvals` UI to drive the
resume from a browser. See [docs/durability/](docs/durability/index.md).

## See every trace, eval, and prompt in your browser — no Docker, no signup

```bash
pip install 'fastaiagent[ui]'
fastaiagent ui
```

Opens a polished web UI at `http://127.0.0.1:7842`. Every agent run you
execute lands here — span tree with Gantt-style timing, JSON-viewer
inspector, Agent Replay fork-and-rerun in the browser, eval runs with
pass-rate trend charts, prompt editor with version lineage, guardrail
events, agent scorecards, and a **read-only browser + search playground
for every `LocalKB`** you've built. Everything stored in one SQLite file at
`./.fastaiagent/local.db`. Bcrypt-hashed local auth. Nothing phones home.

![FastAIAgent Local UI — trace detail](https://raw.githubusercontent.com/fastaifoundry/fastaiagent-sdk/main/docs/ui/screenshots/03-trace-detail.png)

### See your Chain / Swarm / Supervisor topology rendered as a graph

Pass your runners to `build_app(runners=[...])` to enable the **interactive
React Flow topology view** at `/workflows/{type}/{name}` — agent / HITL /
function nodes, conditional edges, swarm handoffs, supervisor delegation
arrows, all with click-to-inspect node detail panels:

```python
import uvicorn
from fastaiagent import Agent, Chain
from fastaiagent.ui.server import build_app

researcher = Agent(name="researcher", llm=llm)
writer     = Agent(name="writer",     llm=llm)

chain = Chain("research-then-summarise")
chain.add_node("research",  agent=researcher)
chain.add_node("summarize", agent=writer)
chain.connect("research", "summarize")

# Register the chain so the topology endpoint can render it.
app = build_app(runners=[chain])
uvicorn.run(app, host="127.0.0.1", port=7843)
# → open http://127.0.0.1:7843/workflows/chain/research-then-summarise
```

Without `runners=[...]` the trace list, agent stats, and analytics still
populate from runtime spans — but `/workflows/chain/<name>` shows a
"No topology available" callout with the registration recipe above.
Same pattern works for `Swarm` and `Supervisor`. See
[examples/47_workflow_topology.py](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/examples/47_workflow_topology.py)
and [docs/ui/workflow-visualization.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/workflow-visualization.md)
for the full reference.

### Iterate on prompts in the browser — Prompt Playground

The **Prompt Playground** at `/playground` is the inner-loop iteration
surface for prompts: pick one from the registry, fill in its
`{{variables}}`, choose a provider/model, click **Run**, watch the
response stream back token-by-token. Edit the template inline for
one-off experiments, attach an image for vision models, then click
**Save as eval case** to append the input/output pair to a JSONL
dataset that loads directly via `Dataset.from_jsonl()`. Every run emits
a trace tagged `fastaiagent.source = "playground"` so playground
experiments share the same observability surface as production runs.

![FastAIAgent Local UI — Prompt Playground](https://raw.githubusercontent.com/fastaifoundry/fastaiagent-sdk/main/docs/ui/screenshots/sprint2-3-playground-streamed-response.png)

See [docs/ui/playground.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/playground.md)
and [examples/49_prompt_playground.py](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/examples/49_prompt_playground.py)
for the walkthrough.

### See what your agent is made of — Agent Dependency Graph

The **Dependencies** tab on any `/agents/{name}` page renders a
structural graph of the agent: every tool, knowledge base, prompt,
guardrail, and model appears as a node radiating out from the agent
centre. Tools that the LLM has called but weren't registered show up in
amber so hallucinated tool names are visible at a glance. For
**Supervisors** every Worker appears as a sub-agent with its own
subtree; for **Swarms** peers appear as siblings with handoff edges.
Click any node to inspect its details and jump to its own page.

![FastAIAgent Local UI — Agent Dependency Graph](https://raw.githubusercontent.com/fastaifoundry/fastaiagent-sdk/main/docs/ui/screenshots/sprint2-4-agent-dependency-graph.png)

See [docs/ui/agent-dependencies.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/agent-dependencies.md)
and [examples/50_agent_dependencies.py](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/examples/50_agent_dependencies.py)
for the walkthrough.

### Debug what your guardrails did — Guardrail Event Detail

Every guardrail firing already shows up on `/guardrails`. Click any row
to open its **detail page** with three panels — *what triggered it*,
*which rule matched*, *what happened next* — plus an execution-context
timeline of the surrounding spans and the other guardrails that ran on
the same content. For `filtered` events the third panel renders a
before/after diff of the rewritten content; for `llm_judge` rules it
shows the judge prompt + response inline. A **Mark as false positive**
button flips a flag stored on the event row so you can curate signal
vs. noise without ever editing the DB — and a new `FP: yes / FP: no`
filter on the list page hides the noise once you've marked it.

![FastAIAgent Local UI — Guardrail Event Detail](https://raw.githubusercontent.com/fastaifoundry/fastaiagent-sdk/main/docs/ui/screenshots/sprint2-7-guardrail-detail-blocked.png)

See [docs/ui/guardrail-events.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/guardrail-events.md)
and [examples/51_guardrail_events.py](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/examples/51_guardrail_events.py)
for the walkthrough.

### Compare any two traces — Trace Comparison

Generalises Replay's "original vs forked" diff to *any* two traces, so
you can answer "why did Monday's run differ from Friday's?", A/B-test
two prompts on the same input, or spot a regression after a model
change. Multi-select two rows on `/traces` → **Compare** in the action
bar; or use **Compare with…** on any trace detail page. The view
shows summary delta cards (duration, tokens, cost, span count) over a
span-aligned table — server-side alignment matches by name first then
position, classifying each row as `same` / `slower` / `faster` /
`different output` / `new in A` / `new in B`. Click any row to expand
side-by-side input / output / attributes diffs powered by
`react-diff-viewer-continued`. URL is bookmarkable:
`/traces/compare?a=<id>&b=<id>`.

![FastAIAgent Local UI — Trace Comparison](https://raw.githubusercontent.com/fastaifoundry/fastaiagent-sdk/main/docs/ui/screenshots/sprint3-2-trace-compare-summary.png)

See [docs/ui/trace-comparison.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/trace-comparison.md)
and [examples/52_trace_compare.py](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/examples/52_trace_compare.py)
for the walkthrough.

### Curate eval cases inline — Eval Dataset Editor

Datasets stay JSONL on disk (same files `Dataset.from_jsonl()` already
loads) — but the editor at `/datasets` replaces the script-edit-rerun
loop with point-and-click CRUD. Add, edit, duplicate, delete cases;
upload images for multimodal cases (the typed-parts shape is preserved
on disk so cases stay framework-runnable); import / export JSONL with
line-numbered errors on bad input; and **Run eval** kicks off the
existing eval framework against the dataset and surfaces the resulting
`run_id` in `/evals`. The Playground's *Save as eval case* button now
combos over existing datasets with a `+ New` escape hatch so the
inner-loop iteration feeds outer-loop curation without copy-paste.

![FastAIAgent Local UI — Eval Dataset Editor](https://raw.githubusercontent.com/fastaifoundry/fastaiagent-sdk/main/docs/ui/screenshots/sprint3-6-dataset-detail.png)

See [docs/ui/datasets.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/datasets.md)
and [examples/53_dataset_editor.py](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/examples/53_dataset_editor.py)
for the walkthrough.

### Find any trace — Richer Trace Filtering

The Traces filter bar is now production-grade. **FTS5-backed
full-text search** across LLM prompts and responses
(`gen_ai.prompt`, `gen_ai.response.text`, with `fastaiagent.*`
namespaced fallbacks) — sub-second on 1k spans, regression-tested,
with LIKE fallback for legacy DBs. **Custom date-range picker**
(`react-day-picker`) alongside the quick ranges (15m, 1h, 24h, 7d,
**30d**, All). **More filters** disclosure with duration and cost
ranges. **Saved filter presets** (project-scoped) — capture every
active filter, name it, one-click reapply. **300 ms debounced**
search. And **URL state**: every active filter mirrors into
`?key=value` query params, so refresh, bookmark, share, and
back/forward all preserve filter state.

![FastAIAgent Local UI — Richer Trace Filtering](https://raw.githubusercontent.com/fastaifoundry/fastaiagent-sdk/main/docs/ui/screenshots/sprint3-9-filters-expanded.png)

See [docs/ui/trace-filters.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/trace-filters.md)
and [examples/54_trace_filters.py](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/examples/54_trace_filters.py)
for the walkthrough.

### Other Local UI surfaces

- **Multimodal trace rendering** — image thumbnails and PDF cards
  render inline in the trace input/output tabs, no raw base64.
  ([docs/ui/multimodal.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/multimodal.md))
- **Checkpoint inspector** at `/executions/{id}` — vertical timeline of
  every checkpoint with status, expandable state snapshots, automatic
  state diff between adjacent rows, and an idempotency-cache panel.
  ([docs/ui/checkpoint-inspector.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/checkpoint-inspector.md))
- **Cost tracking** at the bottom of `/analytics` — three tabs (by
  model / by agent / by chain node) backed by
  `GET /api/analytics/costs`. Reuses the same pricing table the
  per-trace cost column uses, so the numbers always agree.
  ([docs/ui/cost-tracking.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/cost-tracking.md))
- **Export trace as JSON** — Export button on every trace detail page
  opens a dialog with `Include image / PDF data` and
  `Include checkpoint state` toggles. Same payload from the CLI:

  ```bash
  fastaiagent export-trace --trace-id <id> --output trace.json
  ```

  ([docs/ui/export-trace.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/export-trace.md))
- **Project scoping** — every record the SDK writes carries a
  `project_id` resolved from `./.fastaiagent/config.toml` (created
  lazily on the first `agent.run()` from a fresh directory). Multiple
  projects can share one Postgres without cross-contamination; the
  header breadcrumb reads `Local UI // <project-id> // …`.
  ([docs/ui/projects.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/projects.md))

See [docs/ui/](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/index.md) for the full tour; the KB browser is documented at [docs/ui/kb.md](https://github.com/fastaifoundry/fastaiagent-sdk/blob/main/docs/ui/kb.md).

## Evaluate agents systematically

```python
from fastaiagent.eval import evaluate

results = evaluate(
    agent_fn=my_agent.run,
    dataset="test_cases.jsonl",
    scorers=["correctness", "relevance"]
)
print(results.summary())
# correctness: 92% | relevance: 88%
```

## Trace any agent — yours or LangChain/CrewAI

```python
import fastaiagent
fastaiagent.integrations.langchain.enable()

# Your existing LangChain agent, now with full tracing
result = langchain_agent.invoke({"input": "..."})
# → Traces stored locally or pushed to FastAIAgent Platform
```

## Build agents with guardrails and cyclic workflows

```python
from fastaiagent import Agent, Chain, LLMClient, Guardrail
from fastaiagent.guardrail import no_pii, json_valid

agent = Agent(
    name="support-bot",
    system_prompt="You are a helpful support agent...",
    llm=LLMClient(provider="openai", model="gpt-4o"),
    tools=[search_tool, refund_tool],
    guardrails=[no_pii(), json_valid()]
)

# Chains with loops (retry until quality is good enough)
chain = Chain("support-pipeline", state_schema=SupportState)
chain.add_node("research", agent=researcher)
chain.add_node("evaluate", agent=evaluator)
chain.add_node("respond", agent=responder)
chain.connect("research", "evaluate")
chain.connect("evaluate", "research", max_iterations=3, exit_condition="quality >= 0.8")
chain.connect("evaluate", "respond", condition="quality >= 0.8")

result = chain.execute({"message": "My order is late"}, trace=True)
```

## Deploying

A fastaiagent agent is a plain Python object — wrap it in any web framework and ship it anywhere Python runs. [docs/deployment](docs/deployment/index.md) has copy-paste recipes for:

- **[FastAPI + Uvicorn](docs/deployment/fastapi.md)** — the baseline. Works on a laptop or any VM / container.
- **[Docker → Cloud Run / Fly / Render / Railway](docs/deployment/docker.md)** — one Dockerfile, four managed container platforms.
- **[Modal](docs/deployment/modal.md)** — serverless Python with no container work.
- **[Replicate (Cog)](docs/deployment/replicate.md)** — public inference endpoint.

Every recipe exposes the same `POST /run` + `POST /run/stream` contract so callers don't care where the agent lives. See the runnable starter: [examples/33_deploy_fastapi.py](examples/33_deploy_fastapi.py).

## Expose agents as MCP servers (Claude Desktop / Cursor / Continue / Zed)

Any `Agent` or `Chain` becomes an MCP server with one line:

```python
from fastaiagent import Agent, LLMClient

agent = Agent(name="research_assistant", llm=LLMClient(provider="openai", model="gpt-4o"))

if __name__ == "__main__":
    import asyncio
    asyncio.run(agent.as_mcp_server(transport="stdio").run())
```

Register it in `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "research-assistant": {
      "command": "python",
      "args": ["/absolute/path/to/my_agent.py"]
    }
  }
}
```

Claude Desktop now treats your fastaiagent as a callable tool. Same config shape for Cursor / Continue / Zed. Or use the CLI: `fastaiagent mcp serve my_agent.py:agent`. See [docs/tools/mcp-server.md](docs/tools/mcp-server.md).

Install: `pip install 'fastaiagent[mcp-server]'`.

## Peer-to-peer swarms with handoffs

Beyond the central-coordinator Supervisor/Worker pattern, agents can hand off to each other directly:

```python
from fastaiagent import Agent, LLMClient, Swarm

llm = LLMClient(provider="openai", model="gpt-4o-mini")

triage = Agent(name="triage", llm=llm, system_prompt="Hand off to the right specialist.")
coder = Agent(name="coder", llm=llm, system_prompt="Answer Python questions.")
writer = Agent(name="writer", llm=llm, system_prompt="Help with prose.")

swarm = Swarm(
    name="help_desk",
    agents=[triage, coder, writer],
    entrypoint="triage",
    handoffs={"triage": ["coder", "writer"], "coder": [], "writer": []},
)
result = swarm.run("How do I reverse a list in Python?")
```

The currently active agent decides when to transfer control — no central LLM. See [docs/agents/swarm.md](docs/agents/swarm.md) for the full guide, and [Swarm vs Supervisor](docs/agents/swarm.md#swarm-vs-supervisor--when-to-use-which) for when to pick which.

## Long-term memory with composable blocks

Beyond a sliding window, layer static facts, a rolling summary, semantic recall, and fact extraction into one memory object:

```python
from fastaiagent import Agent, LLMClient, ComposableMemory, AgentMemory
from fastaiagent import StaticBlock, SummaryBlock, VectorBlock, FactExtractionBlock
from fastaiagent.kb.backends.faiss import FaissVectorStore

llm = LLMClient(provider="openai", model="gpt-4o-mini")

agent = Agent(
    name="assistant",
    llm=llm,
    memory=ComposableMemory(
        blocks=[
            StaticBlock("User is Upendra. Prefers terse answers."),
            SummaryBlock(llm=llm, keep_last=10, summarize_every=5),
            VectorBlock(store=FaissVectorStore(dimension=384)),
            FactExtractionBlock(llm=llm, max_facts=100),
        ],
        primary=AgentMemory(max_messages=20),
    ),
)
```

`VectorBlock` works with any `VectorStore` (Qdrant / Chroma / custom). Write your own block by subclassing `MemoryBlock` with two methods. See [docs/agents/memory.md](docs/agents/memory.md).

## Swap the KB storage layer

Default `LocalKB` ships with FAISS + BM25 + SQLite — zero setup. Point at Qdrant, Chroma, or your own backend with one kwarg:

```python
from fastaiagent.kb import LocalKB
from fastaiagent.kb.backends.qdrant import QdrantVectorStore

kb = LocalKB(
    name="product-docs",
    search_type="vector",
    vector_store=QdrantVectorStore(
        url="http://localhost:6333",
        collection="product-docs",
        dimension=1536,
    ),
)
kb.add("docs/")
results = kb.search("refund policy", top_k=5)
```

Adapters shipped: **FAISS**, **BM25**, **SQLite** (defaults), **Qdrant** (`fastaiagent[qdrant]`), **Chroma** (`fastaiagent[chroma]`). Write your own against the `VectorStore` / `KeywordStore` / `MetadataStore` protocols — see [docs/knowledge-base/backends.md](docs/knowledge-base/backends.md).

**Platform-hosted KBs.** For KBs uploaded and managed on the FastAIAgent platform, use `fa.PlatformKB(kb_id=...)` — same `.search()` / `.as_tool()` surface, retrieval (hybrid + rerank + relevance gate) runs on the platform. See [docs/knowledge-base/platform-kb.md](docs/knowledge-base/platform-kb.md).

## Shape agent behavior with middleware

Compose pre/post model hooks and tool wrappers without subclassing `Agent`:

```python
from fastaiagent import Agent, LLMClient, TrimLongMessages, RedactPII, ToolBudget

agent = Agent(
    name="controlled",
    llm=LLMClient(provider="openai", model="gpt-4o"),
    tools=[search_tool],
    middleware=[
        TrimLongMessages(keep_last=30),   # cap history size
        RedactPII(),                      # scrub emails/phones/SSNs both directions
        ToolBudget(max_calls=5),          # cooperatively stop after 5 tool calls
    ],
)
```

Write your own by subclassing `AgentMiddleware` and overriding `before_model`, `after_model`, or `wrap_tool`. See [docs/agents/middleware.md](docs/agents/middleware.md) for ordering, hook reference, and custom patterns.

## Multi-agent teams with context

```python
from fastaiagent import Agent, LLMClient, RunContext, Supervisor, Worker, tool

@tool(name="get_tickets")
def get_tickets(ctx: RunContext[AppState], status: str) -> str:
    """Get support tickets for the current user."""
    return ctx.state.db.query("tickets", user_id=ctx.state.user_id, status=status)

support = Agent(name="support", llm=llm, tools=[get_tickets], system_prompt="Handle tickets.")
billing = Agent(name="billing", llm=llm, tools=[get_billing], system_prompt="Handle billing.")

supervisor = Supervisor(
    name="customer-service",
    llm=LLMClient(provider="openai", model="gpt-4o"),
    workers=[
        Worker(agent=support, role="support", description="Manages tickets"),
        Worker(agent=billing, role="billing", description="Handles billing"),
    ],
    system_prompt=lambda ctx: f"You lead support for {ctx.state.company}. Be helpful.",
)

# Context flows to all workers and their tools
ctx = RunContext(state=AppState(db=db, user_id="u-1", company="Acme"))
result = supervisor.run("Show my open tickets and billing", context=ctx)

# Stream the supervisor's response
async for event in supervisor.astream("Help me", context=ctx):
    if isinstance(event, TextDelta):
        print(event.text, end="")
```

## Connect to FastAIAgent Platform (optional)

```python
import fastaiagent as fa

fa.connect(api_key="fa-...", project="my-project")

# Traces automatically sent to platform dashboard
result = agent.run("Help me")

# Pull versioned prompts from platform
prompt = PromptRegistry().get("support-prompt")

# Publish eval results to platform
results = evaluate(agent, dataset=dataset)
results.publish()
```

**SDK works standalone. Platform adds: production observability, prompt management,
evaluation dashboards, team collaboration, HITL approval workflows.**

[Free tier available →](https://app.fastaiagent.net)

---

## Install

```bash
pip install fastaiagent
```

With optional integrations:
```bash
pip install "fastaiagent[openai]"       # OpenAI auto-tracing
pip install "fastaiagent[langchain]"    # LangChain auto-tracing
pip install "fastaiagent[kb]"           # Local knowledge base
pip install "fastaiagent[all]"          # Everything
```

## Documentation

- [Getting Started](https://github.com/fastaifoundry/fastaiagent-sdk/tree/main/docs/getting-started)
- [Agent Replay Guide](https://github.com/fastaifoundry/fastaiagent-sdk/tree/main/docs/replay)
- [Building Chains with Cycles](https://github.com/fastaifoundry/fastaiagent-sdk/tree/main/docs/chains)
- [Guardrails](https://github.com/fastaifoundry/fastaiagent-sdk/tree/main/docs/guardrails)
- [Evaluation](https://github.com/fastaifoundry/fastaiagent-sdk/tree/main/docs/evaluation)
- [API Reference](https://github.com/fastaifoundry/fastaiagent-sdk/tree/main/docs/api-reference)

## Contributing

We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License

Apache 2.0 — see [LICENSE](LICENSE).
