Skip to content

Competitor Analysis

Last updated: 2026-03-28

tama occupies a real gap: named reasoning patterns as a first-class declaration, in plain Markdown files, executed by a local Rust binary. No single competitor does all three.

tamaDocker AgentJulepLangGraphCrewAIAgnoWSO2 AFM
Agent definitionMarkdown files (dir tree)YAML file (flat, one file)Python SDKPython codeYAML + PythonPython codeMarkdown files
Named patternsYes (12)NoNoNoNoNoNo
FSM supportNative (deterministic)DAG + LLM-driven traversalswitch/if-elseConditional edgesFlows + @routerRouter primitiveNo
ReflexionNativeManualManualManualNot nativeManual LoopNo
DebateNativeNot nativeNot nativeManualNot nativebroadcast TeamNo
ConstitutionalNativeNot nativeNot nativeManualNot nativeManualNo
Best-of-NNativeNot nativeVoting approx.ManualNot nativeManual ParallelNo
Chain-of-verificationNativeNot nativeNot nativeManualNot nativeManualNo
RuntimeRust binaryGo binaryMicroservicesPythonPythonPythonNone (spec only)
Zero app codeYesYesNoNoPartialNoYes (no runtime)
Local-firstYesYesNo (hosted)YesYesYesN/A
Stars2.7k6.6k27.7k47.4k39k20

GitHub: github.com/docker/docker-agent · 2,721 stars · Go · Built by Docker Engineering

A local Go binary (also ships as a Docker Desktop CLI plugin) that reads a single YAML file defining one or more agents. Zero cloud dependency. Agents run locally. Backed by Docker Inc — actively developed, growing fast.

Everything lives in one YAML file per project:

version: "8"
agents:
root:
model: anthropic/claude-sonnet-4-5
instruction: |
You are a research assistant.
sub_agents: [researcher, writer]
toolsets:
- type: filesystem
- type: mcp
ref: docker:duckduckgo

Multi-agent composition — it’s a DAG, not FSM

Section titled “Multi-agent composition — it’s a DAG, not FSM”

Docker Agent’s composition model is fundamentally a DAG with LLM-driven edge traversal, not agent composition in the tama sense.

sub_agents: — hierarchical delegation. The parent agent gets a transfer_task(agent, task) tool injected. The parent LLM decides at runtime which child to call and what task to give it. The child is isolated (no parent history). This is tool-calling with agents as tools — not a declared topology.

handoffs: — each agent declares which other agents it can hand off to (an adjacency list). But which edge to traverse is decided by the LLM at runtime, not by a routing table. There are no named states, no routing words, no deterministic dispatch.

Critical limitation — no pattern nesting: In Docker Agent, all agents live in a single flat YAML file. There is no concept of “this sub-agent runs the reflexion pattern” or “this worker uses a critic loop.” An agent is just a system prompt + tools. You cannot declare a topology inside a sub-agent. The composition is flat delegation, not recursive pattern composition.

tama’s agent composition is fundamentally different:

  • Each agent lives in its own directory (agents/name/AGENT.md) with its own declared pattern:
  • A sub-agent in tama can itself be pattern: reflexion — it runs the full act→reflect→loop internally
  • The full graph is parsed and validated statically before any LLM call (AgentGraph::build())
  • Routing is deterministic: the LLM returns a routing word (key in finish(key, value)), the FSM table maps that word to the next state — no LLM decides “which agent to call next”

background_agents toolset — true parallelism. run_background_agent(agent, task) is non-blocking; coordinator fans out and polls for results.

No — it has a DAG with LLM-driven traversal. The handoffs: config defines which edges exist (possible transitions), but which edge to take is decided by the LLM based on instructions — not by a routing table keyed on output words. There is no state:, no on_enter:, no routing words, no deterministic dispatch. Even the cycles that handoffs: supports are traversed by LLM decision, not by declared transition rules.

tama’s pattern: fsm works differently: the LLM outputs a routing word (key), and the FSM table in the AGENT.md frontmatter maps that word to the next state deterministically. The LLM never “chooses an agent” — it produces output, and the runtime routes based on a declared rule.

None are named or declared. Everything is assembled from three primitives: delegation, handoffs, and background parallelism. Reflexion, debate, critic, constitutional AI, best-of-N, chain-of-verification — all require hand-coding via instruction engineering.

PatterntamaDocker Agent
ReActpattern: reactDefault — every agent is a ReAct loop
Scatter/parallelpattern: scatterbackground_agents toolset
HierarchicalNot implementedsub_agents:
Reflexionpattern: reflexionManual — two sub_agents calling each other in a loop
Debatepattern: debateNot native
FSMpattern: fsmNot native (LLM-driven handoff graph only)
Criticpattern: criticNot native
Constitutionalpattern: constitutionalNot native
Best-of-Npattern: best-of-nNot native
Chain-of-verificationpattern: chain-of-verificationNot native
  • Rich tooling: 13 built-in toolset types (shell, filesystem, lsp, think, todo, memory, tasks, fetch, api, openapi, a2a, rag, background_agents)
  • Full MCP ecosystem: Docker MCP catalog + local stdio + remote SSE
  • OCI packaging: docker agent share push — publish/pull agents from any registry
  • A2A protocol: serve and consume agents across frameworks
  • Hooks: pre/post tool, session start/end, stop, notification — full shell script control over every tool call
  • RAG built-in: BM25 + embeddings + hybrid + reranking via rag: config
  • Model routing: per-turn semantic routing between models based on examples
  • Thinking budgets: thinking_budget: high/low per model config
  • Docker Model Runner: local offline inference, no API key needed

Three fundamental differences:

  1. DAG vs FSM. Docker Agent’s routing is LLM-driven over a declared adjacency list (DAG). tama’s FSM routing is deterministic: the LLM outputs a key, the runtime maps it to the next state via a table. No LLM in tama ever “chooses which agent to call next.”

  2. Flat vs recursive composition. All Docker Agent agents live in one flat YAML file. Sub-agents are system prompts with tools — they have no internal pattern structure. In tama, every agent in the graph declares its own pattern:, so a scatter worker can internally be a reflexion loop, which itself calls critic agents.

  3. LLM decides routing vs runtime decides routing. In Docker Agent, if you want “go back to step A if quality is low,” you rely on the LLM following instructions. In tama’s FSM, the routing table is declared in YAML — the LLM returns "retry" or "done", and the runtime deterministically routes. No prompt engineering required for the control flow.


GitHub: github.com/julep-ai/julep · 6,603 stars · Python · Hosted service shut down Dec 31, 2025. Now self-host only. Team has moved on.

A microservices platform (Temporal + Postgres + 8 services) for durable, long-running agent tasks. Tasks are defined as YAML programs passed via Python SDK. Self-hosting requires a full Docker Compose stack.

YAML is passed as a dict via Python SDK — not a file on disk:

client.tasks.create(agent_id=agent_id, **{
"name": "Research Pipeline",
"main": [
{"prompt": [{"role": "user", "content": "$ _.topic"}]},
{"tool": "brave_search", "arguments": {"query": "$ _.result"}},
{"if": "$ len(_.results) > 0",
"then": [{"return": "$ _.results"}],
"else": [{"error": "No results"}]}
]
})

Julep documents these explicitly:

PatternImplementation
Prompt chainingSequential main: steps
Routingswitch: / if-else: on LLM output
Parallelization (sectioning)over/map with parallelism: N
Voting (best-of-N approx.)over + custom aggregation
Evaluator-optimizer (reflexion approx.)Recursive workflow: calls with if-else scoring
Orchestrator-workersforeach: calling sub-workflows

No native support for: debate, constitutional AI, chain-of-verification, FSM, scatter as named concepts.

No. switch: and if-else: provide conditional branching, but there are no named states, no on_enter/on_exit, no state-file-per-state pattern. Steps are indexed, not named states.

The hosted backend shut down. Self-hosting requires running ~10 Docker services. The team has moved to a different product (memory.store). For new projects, Julep is effectively abandoned despite the star count.


GitHub: langchain-ai/langgraph · 27,760 stars · Python

LangGraph is a graph execution engine for stateful, cyclic agent workflows. LangChain is the broader ecosystem of model wrappers and integrations. LangGraph is the orchestration layer.

No YAML, no Markdown, no config files. Everything is Python:

from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict, Annotated
from operator import add
class State(TypedDict):
messages: Annotated[list, add]
def llm_call(state: State):
return {"messages": [model.invoke(state["messages"])]}
def tool_node(state: State):
# execute tool calls from last message
...
def should_continue(state: State):
if state["messages"][-1].tool_calls:
return "tools"
return END
builder = StateGraph(State)
builder.add_node("llm", llm_call)
builder.add_node("tools", tool_node)
builder.add_edge(START, "llm")
builder.add_conditional_edges("llm", should_continue, ["tools", END])
builder.add_edge("tools", "llm")
agent = builder.compile()

That’s a minimal ReAct loop: ~35 lines. With create_react_agent(model, tools) prebuilt: 1 line — but it’s opaque.

LangGraph has zero named patterns. Every pattern is assembled by the developer:

  • Reflexion: two nodes (actor, reflector) + conditional edge checking “DONE”
  • Critic/refine: three sequential nodes chained with edges
  • Debate: parallel nodes + judge node + merge edge
  • FSM: conditional edges returning string node names — the graph IS a state machine
  • Scatter: Send API returns a list of Send("node", item) objects from a conditional edge
  • Plan-execute: planner node + loop with executor + verifier

Prebuilt helpers: create_react_agent, create_supervisor (langgraph-supervisor package), ToolNode. Everything else is DIY.

State is a TypedDict with Annotated reducers. Checkpointing (thread-scoped persistence, time-travel debugging, human-in-the-loop via interrupt()) is available with InMemorySaver or PostgresSaver. This is LangGraph’s strongest feature — no other framework in this comparison has equivalent state durability.

Boilerplate is significant. The mental model requires: graph theory, TypedDict typing, reducer functions, understanding of the node/edge/state separation. Frequent breaking changes in 2023–2024. LangSmith (paid) is almost required for production observability.

LangGraph routing requires Python. Every branch, every cycle, every conditional is a def should_continue(state) function. The developer writes the control flow in code — nodes and edges, not states and transitions.

tama’s FSM inverts this: the developer declares a state table in YAML, and the LLM produces routing words that the runtime maps to transitions. No Python, no graph construction, no reducer functions. The topology is readable without executing anything.

The tradeoff is real: LangGraph can implement any topology — tama’s FSM is bounded by what the state table can express. For anything outside the built-in patterns, tama requires modifying Rust code. LangGraph has no such ceiling.


GitHub: crewAIInc/crewAI · 47,420 stars · Python

A framework for autonomous, role-based multi-agent collaboration. The core abstraction is “a crew of agents working through tasks.” Two separate systems: Crews (LLM-orchestrated) and Flows (code-orchestrated).

Recommended approach: YAML config + Python decorators:

config/agents.yaml
researcher:
role: "Senior Research Analyst"
goal: "Find accurate information about {topic}"
backstory: "Experienced researcher with 10 years in the field"
llm: openai/gpt-4o
# config/tasks.yaml
research_task:
description: "Research the latest developments in {topic}"
expected_output: "A comprehensive 3-paragraph summary"
agent: researcher
output_file: output/research.md
@CrewBase
class ResearchCrew:
@agent
def researcher(self) -> Agent:
return Agent(config=self.agents_config['researcher'], tools=[SerperDevTool()])
@task
def research_task(self) -> Task:
return Task(config=self.tasks_config['research_task'])
@crew
def crew(self) -> Crew:
return Crew(agents=self.agents, tasks=self.tasks, process=Process.sequential)

The YAML defines agent identity (role, goal, backstory, model) and task descriptions. The orchestration (process type, task ordering, wiring) still lives in Python.

  • Process.sequential — tasks run in order; each task’s output available as context to later tasks
  • Process.hierarchical — a manager LLM delegates tasks to worker agents; LLM-driven routing

No parallel process at the crew level. Async execution (async_execution=True on tasks) enables concurrency for independent tasks.

Flows provide event-driven routing:

class ReviewFlow(Flow[ReviewState]):
@start()
def write_draft(self): ...
@router(write_draft)
def review(self):
return "approved" if quality_check() else "rejected"
@listen("approved")
def publish(self): ...
@listen("rejected")
def revise(self): ...

This is FSM-like: @router returns string labels, @listen triggers on labels. But it is not a full FSM — cycles require manually re-triggering methods, there are no on_enter/on_exit events, no formal state object beyond a Pydantic model.

PatternCrewAI
Sequential pipelineProcess.sequential
Hierarchical / supervisorProcess.hierarchical
Conditional routingFlows @router
Human-in-the-loophuman_input=True on task
Guardrails / retryguardrail=fn on task with guardrail_max_retries
ReflexionNot native
DebateNot native
ConstitutionalNot native (sequential tasks can approximate)
Best-of-NNot native
Scatter/parallel fan-outNot native

CrewAI’s YAML defines agent identity — role, goal, backstory. tama’s Markdown defines agent behavior — pattern, model, prompt. In CrewAI, the orchestration topology is Python code. In tama, the pattern name in the frontmatter IS the orchestration declaration.

The stock analysis example: ~250 lines of Python in CrewAI vs 5 Markdown files in tama.


GitHub: agno-agi/agno · 39,000 stars · Python · Apache-2.0 · Very active

A full-stack Python framework covering three layers: agent/team framework, FastAPI production runtime (AgentOS), and cloud control plane (os.agno.com). Positioned as a production platform, not a prototyping tool.

No YAML, no Markdown. Pure Python with ~70-parameter constructor:

agent = Agent(
name="Researcher",
model=Claude(id="claude-sonnet-4-6"),
instructions=["Research the topic thoroughly."],
tools=[WebSearchTools(), WikipediaTools()],
reasoning=True,
reasoning_max_steps=10,
memory_manager=AgentMemory(),
output_schema=ResearchReport, # Pydantic model
pre_hooks=[PIIDetectionGuardrail()],
retries=3,
db=SqliteDb(db_file="agent.db"),
)

Three team modes:

TeamMode.route — leader selects ONE member, delegates entirely, returns that member’s response.

TeamMode.broadcast — leader sends SAME task to ALL members in parallel, synthesizes all responses. (This is tama’s debate and parallel in one.)

TeamMode.coordinate (default) — leader analyzes request, crafts individual subtasks per member, synthesizes a unified answer.

Teams are fully nestable: teams as members of teams.

The deterministic execution layer. Typed primitives:

workflow = Workflow(
name="Research Workflow",
steps=[
Router(selector=classify_fn, choices=[web_step, db_step]),
Loop(steps=[refine_step], end_condition=quality_ok, max_iterations=3),
Condition(evaluator=needs_check, steps=[fact_check_step]),
Parallel([analysis_step, summary_step]),
write_step,
],
)
PrimitiveEquivalent tama pattern
Loopreflexion (but code-defined end condition)
Routerfsm (but Python fn, not LLM routing words)
Conditionfsm conditional branch
Parallelparallel
Stepany pattern step

None. Agno has the primitives to build every tama pattern, but none are pre-wired with names. pattern: reflexion in tama is a one-line declaration; the equivalent in Agno is a Workflow with a Loop wrapping two agents with an end_condition function.

  • Guardrails: pre_hooks/post_hooks with PII detection, prompt injection blocking, custom validators
  • Memory: agentic memory (agent decides what to remember), session summaries, user-scoped memory, RAG knowledge base
  • Structured output: Pydantic/JSON Schema enforcement on any agent output
  • Production serving: built-in FastAPI runtime with per-user session isolation, streaming, horizontal scaling
  • Human-in-the-loop: @approval decorator on tools, requires_confirmation=True pause-and-resume
  • Tool ecosystem: 100+ pre-built toolkits

Agno is a full production platform (serving, memory, guardrails, monitoring). tama is a pattern-first local runtime. Agno forces Python for everything. tama forces nothing except Markdown.


GitHub: wso2/agent-flavored-markdown · 20 stars · Spec, no runtime · IUI 2026 research paper

A portability specification for AI agents. Not a framework. Not a runtime. A file format standard: YAML frontmatter + Markdown body = one agent definition.

---
spec_version: "0.3.0"
name: "Support Agent"
model:
name: "claude-sonnet-4-6"
provider: "anthropic"
authentication:
type: "api-key"
api_key: "${env:ANTHROPIC_API_KEY}"
interfaces:
- type: consolechat
tools:
mcp:
- name: "github"
transport:
type: http
url: "https://mcp.github.com"
---
# Role
You are a customer support agent.
# Instructions
Help users resolve their issues clearly and concisely.

No runtime in this repo. WSO2’s Agent Manager (closed-source, Choreo platform) is the reference implementation. No local execution story.

No named patterns. No multi-agent composition in v0.3.0 (listed as future work). No FSM, no reflexion, no debate. The only execution control knob is max_iterations.

WSO2 AFM independently arrived at nearly the same file format as tama’s AGENT.md: YAML frontmatter + Markdown body = one agent. This validates the approach. Key divergences:

tama AGENT.mdWSO2 AFM
Pattern declarationpattern: reflexionNot in spec
Multi-agentagents/ directory treeNot in spec (future)
Runtimetamar Rust binaryNone (closed WSO2)
InterfacesCLI onlyconsolechat, webchat, webhook
Structured I/ONot definedJSON Schema signature
SkillsSKILL.md conventionagentskills.io spec
Stars20

The convergence path between tama’s AGENT.md and AFM is short. Adding spec_version, interfaces, and signature to AGENT.md’s frontmatter would make tama files AFM-compatible — worth considering as a positioning move.


1. A real FSM — the LLM produces content, the runtime owns control flow.

This is tama’s primary differentiator. Every other framework in this comparison routes via one of:

FrameworkRouting mechanismProblem
LangGraphPython conditional edge functionsRequires code for every branch
Docker AgentLLM decides which agent to hand off toNon-deterministic — prompt-engineer your control flow
CrewAI FlowsPython @router decorator functionRequires code
Agno WorkflowPython Router(selector=fn)Requires code
CrewAI CrewsManager LLM delegates tasksNon-deterministic

tama does neither. The LLM returns a routing word (the key in finish(key, value)). The FSM state table in AGENT.md frontmatter maps that word to the next state deterministically. No code. No LLM making routing decisions. The developer declares the topology; the runtime enforces it.

# agents/pipeline/AGENT.md
pattern: fsm
initial: triage
states:
triage:
- billing: billing-agent
- technical: tech-agent
- general: general-agent
billing-agent:
- done: ~
- escalate: triage # cycle back — declared, not hoped for
tech-agent: ~
general-agent: ~

The triage agent calls finish(key="billing", value="..."). The runtime routes to billing-agent. No Python function. No LLM choosing an agent. The triage agent never knows the routing table exists.

This makes complex routing — conditional branching, escalation paths, retry loops, cycles — a YAML authoring problem, not a programming problem.

2. The Markdown body IS the system prompt.

In LangGraph, CrewAI, and Agno, system prompts are Python string literals embedded in code. In tama, open AGENT.md and read the agent. One file, no translation layer between what you read and what runs.

3. Step prompts are first-class versioned files.

act.md, reflect.md, draft.md — each step’s prompt is a separate file in git. You can PR-review a change to the reflexion prompt’s reflection step independently of the act step. In every Python framework, step prompts are strings in a Python file — the diff is noise.

4. Zero code for a complete multi-agent FSM system.

Docker Agent comes closest but has no FSM — just LLM-driven DAG traversal. Every Python framework requires Python. tama requires only Markdown and YAML.

  1. No production serving. CLI only. Agno and Julep have HTTP APIs. Docker Agent can expose agents via A2A.

  2. No memory or persistence. Agno has a full memory system. LangGraph has checkpointers with time-travel. tama has DuckDB traces (observability only).

  3. No guardrails. Agno has PII detection, prompt injection blocking, task-level retry validators. tama has none.

  4. No MCP ecosystem. Docker Agent has the full Docker MCP catalog. LangGraph and CrewAI have LangChain tool integrations. tama has custom SKILL.md files per project.

  5. No orchestrator pattern. The enum variant exists in the code but no orchestrator.rs is implemented.

  6. Small tool surface. 100+ toolkits in Agno. tama requires authoring SKILL.md files.

Docker Agent is the most direct competitive threat. Backed by Docker Inc, actively growing, same “no Python required” philosophy. If they add a pattern library (reflexion, debate, constitutional, etc.), they eliminate tama’s primary differentiator while bringing a far richer tooling ecosystem. Worth monitoring every release.

Agno is the most feature-complete framework in this list. If they add a YAML/Markdown agent definition layer — even partial — they close the gap significantly given their 39k stars and active community.


This document covers: Docker Agent, Julep, LangGraph/LangChain, CrewAI, Agno, WSO2 AFM.