crewai-go

August 22, 2026 · View on GitHub

Languages: English (current) · Português

Go Reference CI codecov Release Go Version Last Commit Zero Dependencies License: MIT Mentioned in Awesome Go

crewai-go cover

Orchestration of autonomous, collaborative AI agents in Go.

crewai-go is an idiomatic Go port of the CrewAI framework. It lets you assemble teams (crews) of agents with distinct roles that collaborate — sequentially or hierarchically — to complete complex tasks using large language models (LLMs).

Built with zero external dependencies — only the Go standard library. Easy to install, audit, and integrate.


Table of Contents


Why crewai-go

  • 🧩 Simple, composable APIAgent, Task, Crew, Tool, LLM.
  • No dependencies — stdlib only; small, fast builds.
  • 🔌 Any LLM — OpenAI (and compatible: Ollama, Groq, Azure…), Anthropic (Claude), or your own implementation of the LLM interface.
  • 🛠️ Tools via ReAct — agents reason and call tools in plain text.
  • 📋 Structured output — tasks can require JSON validated against a JSON Schema, with a bounded repair loop.
  • 🛡️ Guardrails — code-enforced post-output validation that blocks publication of outputs violating business invariants.
  • 📌 Facts & provenance — first-class Fact type populated only by deterministic connector tools, never by the LLM, with full provenance metadata.
  • 🔧 Native tool calling — use provider-native function calling (OpenAI, Anthropic, Ollama) instead of text-based ReAct, with automatic fallback and full trace observability.
  • 🔎 Web search — agent-driven search via the WebSearcher interface (Ollama, OpenAI, Anthropic, xAI) or model-driven search via WebSearchTool with 7 providers (Wikipedia, LangSearch, Serpstack, DuckDuckGo, Google, Brave). SSRF-protected.
  • 📝 Structured logging via log/slog — inject a custom *slog.Logger on Crew and Agent, with backward-compatible Verbose fallback.
  • Async waves (v0.6) — independent Task.Async tasks overlap under Sequential/Hierarchical; Task.Context is the DAG; declaration-order fold after each wave barrier (never completion order). NewCrew defaults AsyncMaxWorkers = 8.
  • 🧱 Semantic-race safe parallelism — Staged/Async groups are independent mid-flight; barrier + slot-by-index merge; Memory AutoSave commits at the barrier in declaration order (D-M7). -race is necessary, not sufficient — see docs/concurrency.md.
  • 🧠 Memory between tasks and chainable context — pluggable MemoryStore, durable FileStore (JSONL), optional embeddings + cosine recall. Prefer WithContext to merge parallel siblings; Memory inject sees the committed snapshot only.
  • 👔 Hierarchical process with a manager that delegates dynamically.
  • 🪜 Staged process — stages in sequence, tasks within a stage in parallel; same barrier/fold contract as Async waves.
  • 🔁 Agentic loop — optional Plan-Execute-Evaluate-Refine cycle with self-evaluation and iterative refinement.
  • 🔌 MCP — connect to Model Context Protocol servers and expose their tools as crewai.Tool (schema-preserving).
  • 📡 Progress & warnings — real-time WithProgress callbacks and per-task non-fatal warnings.
  • 🌊 Streaming — optional StreamingLLM + Crew.WithStream for final-answer token deltas (ReAct/native no-tools); Task/Agent demux on Async waves.
  • 📊 Lifecycle eventsWithEvents / CrewEvent metadata telemetry (llm_call, react turns, repairs) alongside Progress.
  • Testable — mock LLM included; ~90% core coverage.

Concepts

ConceptWhat it is
AgentA worker with a role, a goal, a backstory, an LLM, and tools.
TaskA unit of work with a description, expected output, and an assignee.
CrewThe team: groups agents and tasks and orchestrates them.
ProcessExecution strategy: Sequential, Hierarchical, or Staged.
ToolA capability an agent can invoke (calculation, search, API…).
LLMAbstraction over the language model. Several providers ready to use.
MemoryShort-term bag + pluggable MemoryStore (FileStore, embeddings).
StructuredOutputConfigures a task to require JSON validated against a JSON Schema.
GuardrailPost-output validation hook that blocks publication of invalid outputs.
FactData from a deterministic connector tool with provenance (source, hash).
FactSourceOptional interface for tools that produce Facts.
ToolModeTool execution strategy: "react" (default) or "native".
ToolCallingLLMOptional LLM interface for native function calling.
StreamingLLMOptional LLM interface for token/delta streaming (CallStream).
StreamChunkOne streaming unit: Delta, Task, Agent, Done, Err.
CrewEventMetadata lifecycle record for WithEvents telemetry.
ToolTraceRecords each native tool invocation (name, args, output, duration).

Installation

Requires Go 1.24+.

go get github.com/rhgs/crewai-go@latest

In your code:

import "github.com/rhgs/crewai-go"

Quick Start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/rhgs/crewai-go"
	"github.com/rhgs/crewai-go/llm/openai"
)

func main() {
	// 1. Pick an LLM (uses OPENAI_API_KEY from the environment).
	llm := openai.New("gpt-4o-mini")

	// 2. Create an agent.
	poet := crewai.NewAgent(
		"Poet",
		"Write short, memorable poems",
		"You are an award-winning poet, master of brevity.",
		llm,
	)

	// 3. Define a task.
	task := crewai.NewTask(
		"Write a haiku about the Go programming language.",
		"A haiku (3 lines) in English.",
		poet,
	)

	// 4. Assemble the crew and run it.
	crew := crewai.NewCrew([]*crewai.Agent{poet}, []*crewai.Task{task})
	out, err := crew.Kickoff(context.Background(), nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(out.Final)
}
export OPENAI_API_KEY=sk-...
go run .

LLM Providers

Any type implementing the interface below works:

type LLM interface {
	Call(ctx context.Context, messages []Message) (string, error)
	Model() string
}

Ready to use:

import (
	"github.com/rhgs/crewai-go/llm/openai"
	"github.com/rhgs/crewai-go/llm/anthropic"
	"github.com/rhgs/crewai-go/llm/ollama"
	"github.com/rhgs/crewai-go/llm/xai"
	"github.com/rhgs/crewai-go/llm/mock" // for tests
)

// OpenAI (and compatible: Groq, Azure, Together…)
llm := openai.New("gpt-4o-mini")

// Anthropic (Claude)
llm := anthropic.New("claude-sonnet-5")

// Ollama local (no key)
llm := ollama.New("llama3.2")

// Ollama Cloud (uses OLLAMA_API_KEY)
llm := ollama.NewCloud("gpt-oss:120b")

// xAI (Grok) via API key
llm := xai.New("grok-4")

// xAI (Grok) via subscription OAuth (SuperGrok / X Premium) — no per-token key
df := xai.NewDeviceFlow(clientID)
ts, _ := xai.LoadTokenSource("~/.crewai-xai-token.json", df)
llm := xai.NewWithOAuth("grok-4", ts)
ProviderPackageAuthentication
OpenAI (and compatible)llm/openaiOPENAI_API_KEY / WithTokenSource
Anthropic (Claude)llm/anthropicANTHROPIC_API_KEY
Ollama localllm/ollama (New)none
Ollama Cloudllm/ollama (NewCloud)OLLAMA_API_KEY
xAI (Grok) — API keyllm/xai (New)XAI_API_KEY
xAI (Grok) — subscriptionllm/xai (NewWithOAuth)OAuth Device Flow
Mock (tests)llm/mocknone

See docs/llms.md for all options.

Tools

Create a tool from any Go function:

search := crewai.NewTool(
	"web_search",
	"Searches the web for a term. Input: the search term.",
	func(ctx context.Context, term string) (string, error) {
		// ... your logic ...
		return result, nil
	},
)

agent.WithTools(search)

Built-in tools in the tools package:

import "github.com/rhgs/crewai-go/tools"

agent.WithTools(
	tools.Calculator(),        // evaluates arithmetic expressions
	tools.CurrentTime(""),      // current date/time
	tools.WordCount(),         // counts words/characters
)

The agent uses tools via the ReAct protocol (Thought → Action → Action Input → Observation → Final Answer). Details in docs/tools.md.

Native tool calling

For providers that support native function calling (OpenAI, Anthropic, Ollama), set Agent.ToolMode to use the provider's built-in tool calling instead of text-based ReAct:

llm := ollama.New("llama3.2")

agent := crewai.NewAgent("Researcher", "Find answers", "You are a researcher.", llm)
agent.ToolMode = crewai.ToolModeNative
agent.WithTools(
    tools.Calculator(),
    tools.CurrentTime(""),
)

task := crewai.NewTask("What is 15% of 200?", "A short answer.", agent)
crew := crewai.NewCrew([]*crewai.Agent{agent}, []*crewai.Task{task})
out, _ := crew.Kickoff(context.Background(), nil)

// ToolTraces record each native tool call for observability.
for _, trace := range out.TasksOutput[0].ToolTraces {
    fmt.Printf("%s(%s) -> %s\n", trace.Tool, string(trace.Args), trace.Output)
}

When ToolMode is "native" but the LLM does not implement ToolCallingLLM, the executor returns ErrNativeToolsUnsupported. The default ("" or "react") uses the existing ReAct loop — no changes to existing code. Details in docs/tools.md.

Web search is available in two patterns — agent-driven (Go code controls queries) and model-driven (the LLM decides when to search via the ReAct loop).

Agent-driven: WebSearcher interface

For LLM providers that have a native web search API (Ollama Cloud, OpenAI, Anthropic, xAI), call SearchWeb directly from Go code:

import "github.com/rhgs/crewai-go"

// OpenAI requires a search-capable model (e.g. gpt-4o-search-preview).
llm := openai.New("gpt-4o-search-preview")

hits, err := crewai.SearchWeb(ctx, llm, "Go programming language", 5)
if err != nil {
    // Returns ErrWebSearchUnsupported if the LLM doesn't implement WebSearcher.
    log.Fatal(err)
}
for _, hit := range hits {
    fmt.Printf("%s -- %s\n%s\n\n", hit.Title, hit.URL, hit.Content)
}
ProviderHow it works
Ollama CloudPOST /api/web_search — pure search endpoint, no model invocation, no tokens consumed
OpenAIweb_search_options in Chat Completions (NOT tools); requires search models (gpt-4o-search-preview, gpt-5-search-api); results as nested url_citation annotations
Anthropicweb_search_20250305 server tool; results as web_search_tool_result blocks with encrypted_content
xAI (Grok)Delegates to the OpenAI-compatible client

Model-driven: WebSearchTool

For the ReAct loop, use WebSearchTool so the LLM decides when to search. It implements both Tool and FactSource, so results are collected as Facts with provenance.

import "github.com/rhgs/crewai-go/tools"

// Wikipedia (default, free, no API key) — searches Wikipedia articles only.
search := tools.NewWebSearch(nil)

// LangSearch (100% free, semantic summaries) — general web search.
// Get a free key at https://langsearch.com
search := tools.NewWebSearch(tools.NewLangSearch("LANGSEARCH_API_KEY"))

// Serpstack (1000 free searches/month) — Google SERP data.
search := tools.NewWebSearch(tools.NewSerpstack("SERPSTACK_API_KEY"))

// Google Custom Search (requires API key + CSE ID).
search := tools.NewWebSearch(tools.NewGoogleSearch("GOOGLE_API_KEY", "GOOGLE_CSE_ID"))

// Brave Search (requires API key).
search := tools.NewWebSearch(tools.NewBraveSearch("BRAVE_API_KEY"))

// DuckDuckGo (no key, but may be blocked by captcha).
search := tools.NewWebSearch(tools.NewDuckDuckGoSearch())

agent.WithTools(search)

All search results are SSRF-protected: URLs pointing to localhost, private/CGNAT/multicast IPs, 0.0.0.0, link-local addresses (169.254.x), metadata aliases, userinfo, and unspecified addresses are filtered out. Domain names are resolved via DNS to prevent DNS rebinding attacks (fail-closed).

Details in docs/llms.md (WebSearcher) and docs/tools.md (WebSearchTool).

Logging

crewai-go uses log/slog (structured logging, Go 1.21+) from the standard library. Every log call is structured (key-value pairs), not format strings.

Inject a custom *slog.Logger via WithLogger on both Crew and Agent:

log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))

crew := crewai.NewCrew(agents, tasks).WithLogger(log)
agent := crewai.NewAgent("a", "g", "b", llm).WithLogger(log)

If no logger is injected, Kickoff creates a text-format logger on stderr. The default level depends on Crew.Verbose:

VerboseDefault levelEffect
falseLevelErrorOnly warnings and errors (matches legacy nop behavior).
trueLevelDebugEverything: debug, info, warn, error.

When WithLogger is used, the injected logger is used as-is — the caller controls the level and handler.

To mask likely secrets in log attributes and messages (best-effort, opt-in):

log := slog.New(crewai.RedactHandler(
    slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}),
))
crew := crewai.NewCrew(agents, tasks).WithLogger(log)

See examples/logging/ for a full wiring sample. Prefer LevelInfo (or higher) in production; LevelDebug can include full LLM output and tool args.

Example log line (JSON handler):

{"time":"...","level":"INFO","msg":"agent thought","agent":"Poet","output":"Final Answer: ..."}

Subpackages (llm/*, tools/*) do not log internally — they return errors that the executor logs at the appropriate level.

Logging safety

Debug-level logs include full LLM output (agent thought → the entire model response) and tool inputs/arguments. If your log destination is shared (e.g. remote log aggregator), the output may contain PII or proprietary model responses. Provider errors (logged at WARN on delegation failures, for example) may include API keys in their message.

Mitigations:

  • Pick your destination accordingly (sink to local files, not a shared stream, when handling user data).
  • Use a level filter (e.g. LevelError only) to keep secrets out of logs by default.
  • Wrap your handler with a redactor. See examples/logging/ for a drop-in redaction handler that masks likely-secrets (API keys, bearer tokens, long alphanumeric tokens).

Thread-safety

WithLogger is not concurrent-safe. Set the logger before calling Kickoff/Execute and do not mutate it concurrently. Multiple sequential calls to WithLogger are idempotent — the last call wins.

log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))

crew := crewai.NewCrew(agents, tasks).WithLogger(log)
agent := crewai.NewAgent("a", "g", "b", llm).WithLogger(log)

If no logger is injected, Kickoff creates a text-format logger on stderr. The default level depends on Crew.Verbose:

VerboseDefault levelEffect
falseLevelErrorOnly warnings and errors (matches legacy nop behavior).
trueLevelDebugEverything: debug, info, warn, error.

When WithLogger is used, the injected logger is used as-is — the caller controls the level and handler. Agent.Execute (standalone, without a crew) falls back to slog.Default() unless Agent.WithLogger is set.

Example log line (JSON handler):

{"time":"...","level":"INFO","msg":"agent thought","agent":"Poet","output":"Final Answer: ..."}

Subpackages (llm/*, tools/*) do not log internally — they return errors that the executor logs at the appropriate level.

Processes: Sequential, Hierarchical, and Staged

Sequential — tasks in order, each output becomes context for the next:

crew := crewai.NewCrew(agents, tasks)
crew.Process = crewai.Sequential

Hierarchical — a manager delegates each task to the most suitable agent:

crew.Process = crewai.Hierarchical
crew.ManagerLLM = llm // or crew.ManagerAgent = myManager

Staged — stages run in sequence, but the tasks within a single stage run concurrently. The output of each stage is available as context to the tasks of the following stages. A stage marked Optional does not abort the crew when one of its tasks fails; otherwise the first failure aborts Kickoff.

crew := crewai.NewCrew(agents, nil)
crew.Process = crewai.Staged
crew.Stages = []crewai.Stage{
    {Name: "collect", Tasks: []*crewai.Task{researchA, researchB}},
    {Name: "synthesize", Tasks: []*crewai.Task{write}},
}

Chain context explicitly with WithContext:

analysis := crewai.NewTask("Analyze the data", "insights", analyst).
	WithContext(collection) // receives the output of the 'collection' task

Async waves (Sequential / Hierarchical) — mark independent tasks with WithAsync() so they overlap without switching to Staged. Task.Context is the DAG; each wave folds by declaration order after a barrier. NewCrew defaults to AsyncMaxWorkers = 8 (0 = unlimited). Ignored under Staged (one-shot Warn). See docs/crews.md and examples/async_tasks.

research := crewai.NewTask("research topic", "notes", agent).WithAsync()
outline  := crewai.NewTask("draft outline", "bullets", agent).WithAsync()
write    := crewai.NewTask("write article", "markdown", agent).
	WithContext(research, outline)

Agentic loop

By default an agent uses a single-pass ReAct executor. For tasks that benefit from self-assessment and iterative refinement, set Agent.Loop (or Task.Loop) to an AgenticLoop:

agent.Loop = crewai.NewAgenticLoop(
    crewai.WithMaxRefinements(3),
    crewai.WithPassThreshold(80),
    crewai.WithEvaluator(evaluatorAgent), // optional independent evaluator
)

The loop follows a Plan → Execute → Evaluate → Refine cycle. If the output never passes evaluation, Kickoff returns ErrEvaluationFailed. See docs/agents.md and examples/agentic_loop.

Structured output

When a task needs typed, trustworthy data (e.g. for persisting into a database), set the Structured field with a JSON Schema. The executor instructs the model to reply with JSON only, validates the output in Go, and retries up to RepairMax times if validation fails.

schema := map[string]any{
    "type": "object",
    "properties": map[string]any{
        "name":  map[string]any{"type": "string"},
        "count": map[string]any{"type": "integer"},
    },
    "required": []string{"name", "count"},
}

structured, _ := crewai.NewStructuredOutput(schema, crewai.WithRepairMax(3))

task := crewai.NewTask("Extract the product name and count.", "JSON", agent)
task.Structured = structured

On success, task.Output() returns the canonicalized JSON string. If the model never produces valid JSON within the repair budget, the task fails with crewai.ErrRepairBudgetExceeded. The executor never returns invalid JSON or invents data.

The built-in validator is a stdlib-only JSON Schema subset: type, properties, required, enum, const, items, additionalProperties, minLength/maxLength (bytes), numeric/array bounds, pattern, format (date-time, date, time, email, uri, uri-reference, uuid, ipv4, ipv6), oneOf/anyOf/allOf, not, if/then/else, property counts, uniqueItems, and local $ref. Use WithStrictSchema() to reject unsupported keywords at construction (unevaluated* still fails). Optional WithAllowTools() runs a tool gather phase before JSON capture. Details in docs/tasks.md.

Guardrails

Guardrails are code-enforced post-output validation hooks. They run after a crew (or task) produces output and block publication if a business invariant is violated. Unlike prompt-level instructions, guardrails are a hard code guarantee.

crew.Guardrails = []crewai.Guardrail{
    func(_ context.Context, out *crewai.CrewOutput) error {
        if !strings.Contains(out.Final, "http") {
            return fmt.Errorf("missing source URL")
        }
        return nil
    },
}

Task-level guardrails can also be set via task.WithGuardrail(...). On failure, Kickoff returns crewai.ErrBlockedByGuardrail — the output is never partially returned. Details in docs/crews.md.

Facts & provenance

A Fact is a piece of data produced by a deterministic connector tool, not by the LLM. It carries provenance (source, URL, timestamp, payload hash) so a wrong value can never be presented as a "fact the model remembered".

tool := crewai.NewFactSourceTool(
    "cnpj_lookup",
    "Looks up CNPJ status. Input: the CNPJ number.",
    func(_ context.Context, cnpj string) (string, error) { /* ... */ },
    func(_ context.Context, output string) []crewai.Fact {
        return []crewai.Fact{
            crewai.NewFact(output, "Receita Federal", "https://...", []byte(rawPayload)),
        }
    },
)

Facts flow only from tools, never from the model. They are deduplicated by PayloadHash and appear in CrewOutput.Facts and TaskOutput.Facts. Use crewai.AllFactsProvenanced in a guardrail to enforce provenance. Details in docs/tools.md.

MCP (Model Context Protocol)

Connect to external MCP servers over Streamable HTTP and expose their tools as crewai.Tool values. The original inputSchema is preserved (SchemaProvider) so native tool calling receives the real schema.

import "github.com/rhgs/crewai-go/mcp"

clients, err := mcp.LoadConfig(ctx, "/etc/mcp.json", "my-app", "1.0.0")
// or: client := mcp.New(endpoint, mcp.WithHTTPTimeout(30*time.Second),
//     mcp.WithHeader("Authorization", "Bearer "+tok))
var tools []crewai.Tool
for _, c := range clients {
    ts, _ := c.ListTools(ctx)
    // Optional least-privilege filter (deny-by-default for unlisted names):
    // ts = mcp.FilterTools(ts, map[string]struct{}{"search_docs": {}})
    for _, t := range ts {
        tools = append(tools, mcp.NewToolAdapter(c, t, mcp.WithDescriptionLimit(500)))
    }
}
agent.WithTools(tools...)

Default HTTP timeout is 30s (DefaultHTTPTimeout); JSON config accepts per-server "timeout". See docs/en/mcp.md for configuration, threat model, catalog guards, and the programmatic API.

Progress & warnings

Progress. Surface real-time execution events to a frontend via Crew.WithProgress. Events: stage_started, stage_completed, task_started, task_completed, tool_invoked. Payloads carry metadata only (never prompts, outputs, or tool inputs). The callback must be goroutine-safe; panics are recovered.

crew.WithProgress(func(p crewai.Progress) {
    fmt.Printf("%s %s/%s tool=%s\n", p.Event, p.Stage, p.Task, p.Tool)
})

Warnings. A task that succeeds can still record non-fatal diagnostics (Task.AddWarning / crewai.AddWarningFromCtx). They aggregate into TaskOutput.Warnings and CrewOutput.Warnings — distinct from Stage.Optional, which swallows task failures.

crewai.AddWarningFromCtx(ctx, "secondary source timeout")

Details in docs/crews.md and docs/tasks.md.

Inter-agent delegation

researcher.AllowDelegation = true // eligible target
crew.EnableDelegationTool = true  // auto-attach delegate_to_coworker
// or: writer.WithTools(crewai.NewDelegationTool(crew))

Depth/cycle/self guards apply (DefaultMaxDelegationDepth = 2). See docs/agents.md and examples/delegation.

Memory

crew.Memory = true // permanent v0.x alias → ensures InMemory store
// ...
crew.Kickoff(ctx, nil)

for _, r := range crew.MemorySnapshot().Records() {
	fmt.Printf("[%s] %s\n", r.Agent, r.Content)
}

*Memory also implements the pluggable crewai.MemoryStore contract (Put/Query/Delete/Close with entry/query caps). Wire long-term backends with Crew.MemoryStore + Crew.MemoryPolicy (NewMemoryPolicy() for defaults). During parallel waves/stages, AutoSave commits at the barrier in declaration order (D-M7) — next-wave inject never sees in-flight siblings.

store, _ := crewai.OpenFileStore("/var/lib/myapp/crew-memory") // app owns Close
defer store.Close()
crew.MemoryStore = store
crew.MemoryPolicy = crewai.NewMemoryPolicy()
// Optional semantic recall (app-provided embedder; serial at barrier):
// crew.Embed = myEmbedder; crew.MemoryPolicy.AutoEmbed = true

See docs/memory.md, examples/memory_file, and examples/memory_embed.

Examples

Run the included examples:

go run ./examples/custom_llm     # offline, no API key
go run ./examples/ollama         # local Ollama (or OLLAMA_CLOUD=1)
go run ./examples/streaming     # WithStream deltas (offline mock)
go run ./examples/async_tasks    # Task.Async waves (offline mock)
go run ./examples/memory_file    # FileStore JSONL across Kickoffs
go run ./examples/memory_embed   # AutoEmbed + cosine Query (mock)
go run ./examples/agentic_loop   # offline, mock LLM
go run ./examples/logging        # RedactHandler demo
go run ./examples/mcp            # MCP wiring (live with MCP_ENDPOINT)

export OPENAI_API_KEY=sk-...
go run ./examples/basic
go run ./examples/sequential
go run ./examples/hierarchical
go run ./examples/staged
go run ./examples/tools
go run ./examples/delegation

export XAI_API_KEY=xai-...        # or XAI_OAUTH=1 + XAI_CLIENT_ID
go run ./examples/xai_oauth

Documentation

📖 All docs are available in English (default) and Português (*.pt-BR.md / docs/pt-BR/). Each file has a language switch link at the top.

Core guides

GuideEnglishPortuguês
Getting StartedENPT
AgentsENPT
TasksENPT
CrewsENPT
ToolsENPT
LLMsENPT
MemoryENPT
MCPENPT
Decision logENPT
Plan / RoadmapENPT
Security policyEN

What's new in v0.8.0

All features are backward compatible — additive APIs only.

FeatureDescriptionDocs (EN)Docs (PT)
WithEvents / CrewEventMetadata-only lifecycle telemetry: kickoff, task/wave, llm_call_*, react_iteration, structured_repair, loop_phase, guardrail_blocked; KickoffID; dual-emit with Progress.docs/crews.mddocs/pt-BR/crews.md
JSON Schema remainderLocal $ref, format allowlist, const/not/if/then/else, property counts, uniqueItems; unevaluated* still Strict-fail.docs/tasks.mddocs/pt-BR/tasks.md
Concurrency guideData races vs semantic races; barrier + declaration-order fold; Memory D-M7.docs/concurrency.mddocs/pt-BR/concurrency.md
Decision logLiving catalog of design decisions with options.Plan/DECISIONS.mdPlan/DECISIONS.pt-BR.md
ExampleOffline examples/events.examples/eventsexamples/

Also in v0.7.0: StreamingLLM + WithStream, provider CallStream, CollectStream.

Also in v0.6.0: Task.Async waves, MemoryStore / MemoryPolicy / D-M7, FileStore, embeddings + cosine Query.

Also in v0.5.0: MCP hardening, OutputFile jail, RedactHandler, Kickoff single-flight, schema keywords, delegate_to_coworker.

Also in v0.4.x: staged process, agentic loop, MCP client, progress callbacks, per-task warnings, structured emit_result.

Also in v0.3.0: native tool calling, web search, structured logging via log/slog, secret redaction.

See the CHANGELOG for the full list of changes and the v0.8.0 release for details.

Tests

go test ./...            # all tests
go test ./... -cover     # with coverage
go vet ./...             # static analysis

Tests are hermetic: they use the mock LLM and httptest, with no real network calls.

Comparison with CrewAI (Python)

API mapping

CrewAI (Python)crewai-go
Agent(role=...)crewai.NewAgent(role, ...)
Task(description=...)crewai.NewTask(desc, ...)
Crew(agents, tasks)crewai.NewCrew(agents, tasks)
crew.kickoff(inputs)crew.Kickoff(ctx, inputs)
Process.sequentialcrewai.Sequential
Process.hierarchicalcrewai.Hierarchical
Process.stagedcrewai.Staged
@tool / BaseToolcrewai.NewTool / crewai.Tool
litellmLLM interface (openai/anthropic)

Features in crewai-go that the original CrewAI does NOT have

Featurecrewai-goCrewAI (Python)
Zero dependencies✅ stdlib only — no external packages❌ 50+ PyPI packages (litellm, langchain, pydantic, chromadb, etc.)
Staged process✅ stages in sequence, tasks within a stage concurrent; optional stages continue on failure❌ no staged/parallel-within-stage process
Streaming LLM deltas✅ optional StreamingLLM + WithStream; final-text paths; Task/Agent demux⚠️ provider SDKs stream; orchestration-level sink is app-built
Async waves under Sequential/HierarchicalTask.Async + DAG via Task.Context; declaration-order fold; worker cap default 8⚠️ async exists via asyncio / event loops, not as a first-class wave scheduler with barrier fold
Pluggable long-term memory (stdlib)MemoryStore + JSONL FileStore + optional cosine embeddings; zero extra deps⚠️ typically needs Chroma/external vector stores
Agentic loop✅ opt-in Plan-Execute-Evaluate-Refine with independent evaluator and bounded refinements⚠️ agentic workflows exist, but not as a first-class Plan-Execute-Evaluate-Refine loop with score threshold
Native tool calling with fallbackAgent.ToolMode auto-falls back to ReAct if provider doesn't support ToolCallingLLM❌ no automatic fallback; requires compatible provider
Facts & provenance✅ first-class Fact type with source_org, source_url, payload_hash, collection_time — populated only by deterministic tools, never by the LLM❌ no provenance tracking; LLM can hallucinate "facts"
Guardrails✅ crew-level (Crew.Guardrails) + task-level (Task.Guardrail) post-output validation that blocks publication of invalid outputs❌ no built-in post-output validation hooks
Structured output with repair loop✅ JSON Schema validation with bounded repair loop (RepairMax, default 2) and ErrRepairBudgetExceeded⚠️ partial — uses Pydantic, no repair loop
Web search (agent-driven)WebSearcher interface + SearchWeb(ctx, llm, query, max) — direct search from Go code via Ollama, OpenAI, Anthropic, xAI❌ no direct search API; requires tools
Web search (model-driven)WebSearchTool with 7 pluggable providers (Wikipedia, LangSearch, Serpstack, DuckDuckGo, Google, Brave)⚠️ requires SerperDev or similar external tool integration
SSRF protection✅ blocks non-http(s), userinfo, loopback, private/CGNAT/multicast IPs, link-local, unspecified, metadata aliases; DNS rebinding prevention via net.LookupIP (fail-closed)❌ no URL filtering on search results
Secret redaction in logsredactError/redactString + opt-in RedactHandler for slog messages/attrs❌ no log redaction
Structured logginglog/slog — inject any *slog.Logger with custom handler, level, and output❌ Python logging module, less flexible handler injection
Tool call security limits✅ max args size, output size, response size, JSON depth, arg validation — all configurable❌ no size/depth limits on tool calls
Tool tracesToolTrace in TaskOutput — full observability of every tool call (name, args, result, duration)❌ no per-call trace type
Context propagationcontext.Context throughout — cancellation, deadlines, tracing propagated to all LLM and tool calls❌ no native context/cancellation; async requires asyncio
Thread-safe execution✅ all providers safe for concurrent use; -race tested❌ Python GIL limits true concurrency
Compile-time interface checksvar _ crewai.WebSearcher = (*Client)(nil) — catches missing methods at build time❌ runtime duck-typing, no compile-time checks
Single binary deployment✅ compile to a single static binary — no runtime, no VM, no interpreter❌ requires Python runtime + virtualenv + dependencies
Cold start✅ milliseconds (native binary)❌ seconds (Python import + model loading)
Memory footprint✅ ~10-20 MB typical❌ ~100-300 MB typical (Python + deps)
Cross-compilationGOOS=linux GOARCH=arm64 go build — any target from any host❌ requires target-platform Python or container

This port covers the CrewAI core (agents, tasks, crews, processes, tools, memory) plus several original features not found in the Python version. Advanced features of the original project (event-driven Flows, training, telemetry) are not part of this version.

Contributing

Contributions are welcome. See CONTRIBUTING.md (EN) / CONTRIBUTING.pt-BR.md (PT) for setup, conventions, and the PR checklist. Please also read our Code of Conduct (PT).

License

MIT.

Changelog

See CHANGELOG.md (EN) / CHANGELOG.pt-BR.md (PT).