Skip to main content

Lightweight Python framework for building multi-step AI agents powered by Claude

Project description

๐Ÿฆฆ OtterFlow

OtterFlow logo

Build production-ready AI agents in 10 lines of Python.

PyPI version Python 3.10+ License: MIT Tests

OtterFlow is a minimal, composable framework for building multi-step AI agents powered by Claude. No bloat. No magic. Just agents that work.

from otterflow import Agent
from otterflow.tools import web_search, calculator

agent = Agent(
    name="FinanceBot",
    role="You are a financial research assistant.",
    tools=[web_search, calculator],
)

print(agent.run("What's NVIDIA's latest revenue? Calculate 15% YoY growth."))

Why OtterFlow?

Most agent frameworks are either toy demos or enterprise nightmares. otterflow is neither.

OtterFlow LangChain AutoGen
Lines to a working agent ~10 ~50 ~40
Multi-agent orchestration โœ… โœ… โœ…
Built-in business agents โœ… โŒ โŒ
Zero magic / full control โœ… โŒ โŒ
Persistent memory โœ… โœ… โœ…
Custom tools in 3 lines โœ… โŒ โŒ

Install

pip install otterflow

Then set your Anthropic API key. The easiest way is a .env file in your project root:

cp .env.example .env
# then open .env and paste your key

Or export it directly in your shell:

export ANTHROPIC_API_KEY="your-key-here"

Get a key at console.anthropic.com.


New to AI agents?

โ†’ Read LEARN.md โ€” a beginner's guide that explains agents, tools, memory, and orchestration in plain English. No jargon. No experience required. Goes from zero to your first working agent in about 20 minutes.


Core Concepts

1. Agent โ€” the building block

from otterflow import Agent

agent = Agent(
    name="Analyst",
    role="You are a senior business analyst. Be concise and data-driven.",
    verbose=True,   # streams tool call logs to stdout
)

result = agent.run("Summarize the current state of the AI chip market.")
print(result)

2. Streaming โ€” real-time output

agent = Agent("Writer", "You are a sharp business writer.")

for chunk in agent.stream("Write a 3-paragraph brief on the AI chip market."):
    print(chunk, end="", flush=True)

print()  # newline after stream ends

Works with tools too โ€” tool-call steps run silently between turns, and only the final text response is streamed to the caller.

3. Async โ€” non-blocking, production-ready

import asyncio
from otterflow import Agent

agent = Agent("Bot", "You are a research assistant.", tools=[web_search])

# Single async call
result = await agent.arun("What are the top AI infrastructure startups?")

# Run multiple agents concurrently โ€” both fire at the same time
results = await asyncio.gather(
    agent1.arun("Research the US EV market."),
    agent2.arun("Research the EU EV market."),
)

4. Pipe operator โ€” chain agents

from otterflow import Agent
from otterflow.agents import ResearchAgent

researcher = ResearchAgent()
writer = Agent("Writer", "Turn research into a sharp, structured report.")
editor = Agent("Editor", "Tighten copy. Cut filler. Keep it punchy.")

# Build a pipeline with |
pipeline = researcher | writer | editor

# Each agent's output becomes the next agent's input
result = pipeline.run("What's the state of the B2B AI market in 2025?")
print(result)

Async pipelines work the same way:

result = await pipeline.arun("What's the state of the B2B AI market in 2025?")

5. Token tracking โ€” know your costs

agent = Agent("Researcher", "You research topics.", tools=[web_search])
agent.run("Summarize the latest AI chip news.")

print(agent.usage)
# Usage(input=3,241, output=892, ~$0.0231)

print(agent.usage.estimated_cost_usd)   # 0.0231
print(agent.usage.total_tokens)         # 4133

Usage accumulates across all .run() / .arun() / .stream() calls on the same agent instance.

6. Tools โ€” give your agent superpowers

otterflow ships with 7 built-in tools:

from otterflow.tools import (
    web_search,     # searches the web for current info
    read_file,      # reads local files
    write_file,     # writes output to disk (system paths are blocked)
    run_python,     # executes Python snippets in an isolated subprocess
    calculator,     # evaluates math expressions safely
    memory_store,   # stores key-value pairs
    memory_recall,  # recalls stored values
)

agent = Agent(
    name="ResearchBot",
    role="You research topics and save reports.",
    tools=[web_search, write_file],
)

agent.run("Research the top 3 AI coding assistants and save a report to report.md")

7. Custom tools โ€” 3 lines

from otterflow.tools import tool

@tool(description="Fetch the current price of a stock ticker.")
def get_stock_price(ticker: str) -> str:
    import yfinance as yf
    return str(yf.Ticker(ticker).fast_info.last_price)

agent = Agent("Trader", "You track stocks.", tools=[get_stock_price])
agent.run("What's Apple trading at?")

8. Memory โ€” agents that remember

from otterflow import Agent, Memory

memory = Memory(max_turns=20)
memory.remember("client_name", "Acme Corp")
memory.remember("deal_value", "$120k")

agent = Agent("SalesBot", "You are a sales assistant.", memory=memory)

agent.run("Help me prep for tomorrow's renewal call.")
agent.run("What objections should I prepare for given the deal size?")
# โ†‘ Agent remembers the deal value from the previous turn

9. Multi-agent orchestration

Spawn sub-agents and let a parent orchestrator delegate tasks:

from otterflow import Agent
from otterflow.tools import web_search, write_file

# Specialist agents
researcher = Agent("Researcher", "Deep research specialist.", tools=[web_search])
writer = Agent("Writer", "Turn research into polished reports.", tools=[write_file])

# Orchestrator spawns both as tools it can call
orchestrator = Agent("Boss", "Delegate and synthesize.")
orchestrator.spawn(researcher)
orchestrator.spawn(writer)

# Single call routes through both agents automatically
orchestrator.run(
    "Research the EV market and write a 1-page executive brief to ev_brief.md"
)

Pre-built Business Agents

Drop these into your app immediately:

from otterflow.agents import (
    ResearchAgent,          # Deep web research + structured reports
    EmailAgent,             # Draft, rewrite, and improve emails
    DataAnalystAgent,       # Reads files, runs Python, surfaces insights
    CompetitiveIntelAgent,  # Competitor battle cards
    ContentCreatorAgent,    # Platform-specific content coaching + rewrites
    BusinessIntelPipeline,  # All of the above, orchestrated
)

ResearchAgent

researcher = ResearchAgent(verbose=True)
report = researcher.run(
    "What are the top 5 B2B SaaS trends for 2025? Focus on AI and pricing."
)

EmailAgent

email_bot = EmailAgent(tone="assertive")
draft = email_bot.run(
    "Write a follow-up to a prospect who went cold after last week's demo."
)

ContentCreatorAgent

coach = ContentCreatorAgent(platform="LinkedIn")

feedback = coach.run("""
    Analyze this post:

    Excited to share that I've been thinking a lot about leadership lately.
    Great leaders listen to their teams. What do you think? Like and follow!
""")

# In the next turn, the agent remembers what it already critiqued
rewrite = coach.run("Now rewrite it using what you just taught me.")

Supports "LinkedIn", "Twitter/X", and "Newsletter" โ€” each with platform-specific rules for hook quality, formatting, engagement triggers, and CTAs.

BusinessIntelPipeline

pipeline = BusinessIntelPipeline(verbose=True)
analysis = pipeline.run(
    "Should I build a B2B AI sales coaching product in 2025? "
    "Analyze market size, competitors, and give me a go/no-go."
)

Real-world example: content coach with memory

OtterFlow agents remember context across turns โ€” so feedback from turn one informs the rewrite in turn two, just like working with a real editor.

from otterflow import Memory
from otterflow.agents import ContentCreatorAgent

# Load user context into memory
memory = Memory()
memory.remember("author", "Jordan")
memory.remember("niche", "B2B SaaS founders")
memory.remember("goal", "grow LinkedIn to 10k followers in 90 days")

coach = ContentCreatorAgent(platform="LinkedIn", memory=memory)

# First turn: critique
with open("draft_post.txt") as f:
    draft = f.read()

feedback = coach.run(f"Analyze this post for Jordan:\n{draft}")
print(feedback)

# Second turn: agent remembers the draft, the critique, and Jordan's goal
rewrite = coach.run("Now rewrite it.")
print(rewrite)

Architecture

otterflow/
โ”œโ”€โ”€ agent.py          # Core Agent class โ€” tool loop, sub-agent spawning
โ”œโ”€โ”€ memory.py         # Memory โ€” sliding window, persistent facts
โ”œโ”€โ”€ tools.py          # Tool class, @tool decorator, built-in tools
โ””โ”€โ”€ agents/
    โ””โ”€โ”€ __init__.py   # Pre-built business agents

The execution loop is deliberately transparent:

run(prompt)
  โ”‚
  โ”œโ”€ inject memory + history into messages
  โ”œโ”€ call Claude with tool specs
  โ”‚
  โ””โ”€ loop:
      โ”œโ”€ if stop_reason == "end_turn" โ†’ return text
      โ””โ”€ else โ†’ execute tool calls โ†’ append results โ†’ repeat

No hidden chains. No mysterious abstractions. You can read the entire core in < 200 lines.


Contributing

PRs welcome. High-value contributions:

  • New built-in tools (Slack, email, calendar, Postgres)
  • Async support (agent.arun())
  • Streaming output
  • Token usage tracking
  • More pre-built agents
git clone https://github.com/tabato/otterflow
cd otterflow
pip install -e ".[dev]"
pytest tests/

License

MIT. Use it, fork it, build your startup on it.


Built with Claude by Anthropic.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

otterflow-0.2.2.tar.gz (68.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

otterflow-0.2.2-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

Details for the file otterflow-0.2.2.tar.gz.

File metadata

  • Download URL: otterflow-0.2.2.tar.gz
  • Upload date:
  • Size: 68.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for otterflow-0.2.2.tar.gz
Algorithm Hash digest
SHA256 2cc246da739053ff8189d4ba79bef95a3e8809861b430fdcb099a7a2dcfc8339
MD5 b52589af16a6f6d11b55f9876c31dfe0
BLAKE2b-256 2ea3ec1bbd3bfd24206778ca62ff2b68b3be3934fb677b340b49018aaa146143

See more details on using hashes here.

Provenance

The following attestation bundles were made for otterflow-0.2.2.tar.gz:

Publisher: publish.yml on tabato/otterflow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file otterflow-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: otterflow-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 20.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for otterflow-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 425daa232cc5b91d8f606c6e43a752c6ffbf52617960c06f3f1d9ea475a2e908
MD5 6959efc17863569609f44efae9149e20
BLAKE2b-256 6dbad13607a89c646d994447cd7ddfe5396fdffd1f6126ab29e8689834d2ffb7

See more details on using hashes here.

Provenance

The following attestation bundles were made for otterflow-0.2.2-py3-none-any.whl:

Publisher: publish.yml on tabato/otterflow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page