In Na'vi belief, Eywa is the consciousness of Pandora โ€” a vast living network that connects every creature, carries the memory of ancestors, and orchestrates the balance of the world. When a being sends a signal into the Weave, Eywa finds the Spirit with the wisdom to answer.

That's exactly what this library does for your AI systems.


The problem with "AI in production"

You added an LLM call to a webhook handler. It works. Then your users start sending concurrent messages and you get duplicate responses. You add Redis locking. Then conversations grow beyond the context window. You add a summarizer. Then you need the AI to take real actions. You add tool use. Then you need human oversight. You addโ€ฆ

You're not building a product anymore. You're building infrastructure.

Eywa is that infrastructure โ€” a battle-tested Go framework that handles the full lifecycle of an AI interaction: receiving events from any channel, enriching them with context, routing them to the right Spirit, executing tool calls, maintaining memory, observing everything, and delivering responses. All the hard parts, done once, done right.

Stop bolting LLM calls onto webhook handlers. Start orchestrating.


๐ŸŒ The Mythology

Every name in Eywa carries meaning from the world of Pandora. This isn't cosmetic โ€” the names encode the architecture:

๐Ÿ”ฎ Term What it is
Weave The living network โ€” the runtime engine that connects everything
Spirit An AI agent: named, with a system prompt, model config, and allowed actions
Pulse A signal entering the Weave โ€” a message, a webhook, a trigger
Oracle An LLM provider โ€” the source of wisdom (Anthropic, OpenAI, Gemini, and more)
Action A tool the Oracle can invoke โ€” a real-world capability the Spirit wields
Scout Enriches a Pulse with context before the Spirit sees it
Pathfinder Routes a Pulse to the right Spirit when multiple are available
Voice The channel through which a Spirit's response reaches the world
Memory Ephemeral conversation state โ€” the Spirit's working memory per user
Echo Persisted message history โ€” the permanent record
Chronicle Audit log of every interaction โ€” for observability
Bond Distributed lock โ€” prevents race conditions across concurrent Pulses
Keeper Scheduler backend (e.g. Cloud Tasks) โ€” watches over future events
Ritual A scheduled or recurring event โ€” a ceremony the Keeper performs
Archivist Summarizes long conversations so the Oracle never loses context
Receptor Converts raw webhook payloads into Pulses
Link Wires an event type to Scouts, a Pathfinder, and allowed Spirits
Vault Object storage for media files (e.g. GCS)
Lens Media processor โ€” transcribes audio, analyzes images, extracts documents
Lore The knowledge base โ€” documents a Spirit can search at runtime (RAG)
Imprint Long-term user memory โ€” facts that persist across all conversations
Ledger Token usage and cost tracking โ€” with budgets and smart routing
Vigil Human takeover โ€” an operator acquiring a seat on a live conversation
Rite Async approval workflow โ€” Spirit waits for human decision before acting
Conduit MCP client โ€” connects to external tool servers via Model Context Protocol

โœจ What you build with Eywa

๐Ÿค– Conversational AI agents that handle thousands of concurrent users, maintain memory between sessions, call external APIs, and never produce duplicate responses.

๐Ÿงญ Multi-agent pipelines where Pulses route between specialized Spirits โ€” an orchestrator delegates to specialists (summon, call-and-return), or a Spirit hands off control to a peer that owns the conversation from then on (triage โ†’ specialist). See Multi-agent.

๐Ÿ“š RAG-powered assistants that search a private knowledge base (Lore) at query time, retrieving the right chunks and injecting them as context before the Oracle reasons.

๐Ÿง  Personalized experiences where a Spirit remembers user preferences, past goals, and facts (Imprint) โ€” not just from this session, but from every conversation the user ever had.

๐Ÿ™‹ Human-in-the-loop workflows where critical actions pause for operator approval (Rite) and operators can take over live conversations directly (Vigil) โ€” then hand back to the AI.

๐Ÿ”ง MCP-native tool use where Spirits call tools from any Model Context Protocol server (Conduit), discovering capabilities at runtime and calling them like native Actions.

โšก Async event processing where webhooks return in milliseconds and processing happens reliably in the background via Cloud Tasks, with automatic retry and deduplication.


๐ŸŒฟ Philosophy & The Eywa Mythology

Eywa borrows its name from the neural network connecting all living beings on Pandora โ€” an invisible, intelligent fabric through which information, memory, and intent flow across the entire world.

This framework is built on the same idea: a living orchestration layer that connects AI agents (Spirits) to the world through events (Pulses), equips them with knowledge (Lore, Imprint), tools (Actions), and senses (Scouts) โ€” and delivers their responses through channels (Voices).

The mythology is not decoration. Each name encodes an architectural concept so that the codebase reads as a coherent system, not a pile of abstractions:

  • Spirits carry intent and behavior, not infrastructure. They are named, opinionated agents.
  • Pulses are the heartbeat of the system โ€” every event that enters the Weave is a Pulse.
  • Oracles provide wisdom on demand: LLM inference, isolated behind a clean port.
  • Chronicles preserve the memory of what passed โ€” immutably, for observability and compliance.
  • Bonds ensure only one thread of thought per session, preventing the chaos of concurrent writes.
  • Keepers watch over the future, delivering Rituals at the appointed time.

The names are chosen to be memorable, conceptually accurate, and distinct from every other Go framework you have used. Once you internalize the glossary, reading the source code feels like reading documentation.


๐Ÿ”„ The Pipeline

Every Pulse flows through the same pipeline:

Pulse โ†’ [Guard] โ†’ [Lock] โ†’ [Scouts] โ†’ [Pathfinder] โ†’ Spirit โ†’ Oracle โ†’ Actions โ†’ Voice
Step Description
๐Ÿ›ก๏ธ Guard Blocks or allows the Pulse based on allow/block rules
๐Ÿ”’ Lock Acquires a Bond โ€” only one Pulse per user at a time
๐Ÿ”ญ Scouts Run sequentially, enriching the Pulse with knowledge
๐Ÿงญ Pathfinder Selects the right Spirit from the allowed set
๐Ÿ‘ป Spirit Provides system prompt, model config, and allowed Actions
๐Ÿ”ฎ Oracle Reasons over memory + Lore, calls Actions in a loop until done
โšก Actions Execute tool calls (fetch data, send messages, update records)
๐Ÿ“ข Voice Delivers the final response through the appropriate channel

The pipeline also manages memory setup, message coalescing, conversation archiving, human takeover checks, and full persistence โ€” all transparent to your application code.


๐Ÿ“ฆ Installation

go get github.com/wmulabs/eywa

Sub-modules are opt-in โ€” only include what you need:

# Infrastructure adapters
go get github.com/wmulabs/eywa/mongo              # MongoDB: Spirits, Echoes, Chronicles, Rituals, Lore, Rites
go get github.com/wmulabs/eywa/redis              # Redis: Memory, Bond, Vigil, rate limiter

# LLM providers
go get github.com/wmulabs/eywa/providers/anthropic # Anthropic Claude (Sonnet, Haiku, Opus)
go get github.com/wmulabs/eywa/providers/openai   # OpenAI GPT โ€” and any OpenAI-compatible API
go get github.com/wmulabs/eywa/providers/gemini   # Google Gemini
go get github.com/wmulabs/eywa/providers/bedrock  # AWS Bedrock (Converse API โ€” any Bedrock model)
go get github.com/wmulabs/eywa/providers/vertexai # Google Vertex AI (ADC auth, no API key)

# REST management API
go get github.com/wmulabs/eywa/fiber              # Fiber: full management REST API

# External tool servers
go get github.com/wmulabs/eywa/mcp               # MCP Conduit: connect to any MCP server

# Channels
go get github.com/wmulabs/eywa/channels/whatsapp  # WhatsApp via 360Dialog / Twilio

# GCP integrations
go get github.com/wmulabs/eywa/gcp/cloudtasks     # Cloud Tasks: async dispatch + Rituals
go get github.com/wmulabs/eywa/gcp/gcs            # GCS Vault for media storage
go get github.com/wmulabs/eywa/gcp/gemini         # Gemini: image/audio/document processing

๐Ÿš€ Quick Start

Fastest start: clone the eywa-starter template โ€” a ready-to-run hexagonal project (Gemini Spirit, actions, scouts, REST server, docker compose for Mongo+Redis). git clone, set your key, and you have a working agent in minutes.

No infrastructure? Use eywa.NewNoOpBond() for local development and single-instance prototyping โ€” no Redis required. For production multi-instance deployments, use redis.NewBondManager() and MongoDB repositories.

eywa.NewNoOpBond() is an in-process mutex-backed Bond โ€” safe for single Weave instance, not suitable for horizontal scaling.

package main

import (
    "context"
    "fmt"
    "os"

    eywa "github.com/wmulabs/eywa"
    eywamongo "github.com/wmulabs/eywa/mongo"
    eywaredis "github.com/wmulabs/eywa/redis"
    eywaopenai "github.com/wmulabs/eywa/providers/openai"
)

func main() {
    ctx := context.Background()

    mongoConn, err := eywamongo.NewMongoConnection(ctx, os.Getenv("MONGO_URL"), "mydb", "myapp")
    if err != nil {
        log.Fatalf("failed to connect to MongoDB: %v", err)
    }
    defer mongoConn.DisconnectMongoDB(ctx)

    redisConn, err := eywaredis.NewRedisConnection(ctx, os.Getenv("REDIS_URL"), "myapp")
    if err != nil {
        log.Fatalf("failed to connect to Redis: %v", err)
    }
    defer redisConn.DisconnectRedisDB(ctx)

    db := mongoConn.GetDatabase()

    weave, err := eywa.NewWeaveBuilder(ctx).
        WithRepositories(
            eywamongo.NewSpiritRepository(db),
            eywaredis.NewMemoryRepository(redisConn.GetClient(), "myapp", "prod", 3600, nil),
            eywamongo.NewEchoRepository(db),
            eywamongo.NewChronicleRepository(db),
        ).
        WithBond(eywaredis.NewBondManager(redisConn.GetClient())).
        WithActionRegistry(eywa.NewActionRegistry()).
        WithScoutRegistry(eywa.NewScoutRegistry()).
        AddOracle(eywaopenai.NewOracle(os.Getenv("OPENAI_API_KEY"))).
        WithConfig(eywa.DefaultWeaveConfig()).
        Build()
    if err != nil {
        panic(err)
    }

    weave.RegisterEventConfiguration(
        eywa.NewLink("user_message").
            WithDefaultSpirit("assistant").
            Build(),
    )

    pulse := eywa.NewPulse(eywa.MemoryKey{Channel: "api", User: "user_123"}).
        WithUserMessage("What is the status of my order #4821?").
        Build()

    result, _ := weave.ProcessEventByKey(ctx, "user_message", pulse)
    fmt.Println(result.Message)
}

โš ๏ธ Bond TTL invariant: LockTTL must be at least ReasoningTimeout + 30 seconds. With the default ReasoningTimeout of 2 minutes, the minimum safe LockTTL is 2m30s. WeaveConfig.Validate() enforces this at startup โ€” a misconfigured TTL returns an error from Build(), not a silent data integrity issue at runtime.


๐Ÿ‘ป Defining Spirits

Spirits are the agents in your system. Each has a name, a personality (system prompt), a model configuration, and a list of Actions it is allowed to call.

spirit := &eywa.Spirit{
    Name:         "support_agent",
    Description:  "Customer support specialist",
    SystemPrompt: `You are a helpful support agent for Acme Corp.
You have access to order tracking and refund tools.
Always be concise and professional.`,
    AllowedActions: []eywa.AllowedAction{
        {Name: "track_order"},
        {Name: "request_refund"},
    },
    ModelConfig: eywa.SpiritModel{
        Provider:    "openai",
        Model:       "gpt-4o-mini",
        Temperature: 0.5,
        MaxTokens:   1000,
    },
    IsActive:  true,
    CreatedAt: time.Now(),
}
spiritRepo.Create(ctx, spirit)

Tip: Spirits are versioned. Every Update call creates a new version. Roll back with POST /api/v1/spirits/:name/activate + {"version": N}.


โšก Custom Actions (Tool Use)

Give Spirits real-world capabilities by implementing the Action interface:

type TrackOrderAction struct {
    orderService *OrderService
}

func (a *TrackOrderAction) GetName() string        { return "track_order" }
func (a *TrackOrderAction) GetDescription() string { return "Track a customer order by ID." }
func (a *TrackOrderAction) IsCritical() bool       { return false }

func (a *TrackOrderAction) GetParameters() map[string]interface{} {
    return map[string]interface{}{
        "type": "object",
        "properties": map[string]interface{}{
            "order_id": map[string]interface{}{
                "type":        "string",
                "description": "The order ID to track",
            },
        },
        "required": []string{"order_id"},
    }
}

func (a *TrackOrderAction) Validate(args map[string]interface{}) error {
    if id, _ := args["order_id"].(string); id == "" {
        return eywa.NewBusinessError("order_id is required")
    }
    return nil
}

func (a *TrackOrderAction) Execute(ctx context.Context, args map[string]interface{}) (string, error) {
    status, err := a.orderService.GetStatus(ctx, args["order_id"].(string))
    if err != nil {
        return "", eywa.NewInfrastructureError("failed to fetch order", err)
    }
    return fmt.Sprintf("Order status: %s", status), nil
}
registry := eywa.NewActionRegistry()
registry.Register(&TrackOrderAction{orderService: svc})

weave, _ := eywa.NewWeaveBuilder(ctx).
    WithActionRegistry(registry).
    // ...
    Build()

Built-in Actions:

Action constructor Tool name Description
eywa.NewScheduleRitualAction() schedule_ritual Schedule a future Pulse via the Keeper
eywa.NewListRitualsAction() list_rituals List pending Rituals for the current user
eywa.NewCancelRitualAction() cancel_ritual Cancel a pending Ritual
eywa.NewUpdateSubjectAction() update_subject Track a subject key and accumulate facts in Memory
eywa.NewRememberFactAction() remember_fact Store a persistent user fact in Imprint
eywa.NewForgetFactAction() forget_fact Remove a fact from Imprint
eywa.NewSearchLoreAction() search_lore Search the knowledge base (RAG)
eywa.NewRequestRiteAction() request_rite Request human approval before proceeding

๐Ÿ”ญ Context Enrichment with Scouts

Scouts run before Spirit selection and inject knowledge into the Pulse. They are the right place to load user data, feature flags, or external context.

type UserProfileScout struct{ repo *UserRepository }

func (s *UserProfileScout) GetName() string { return "user_profile" }

func (s *UserProfileScout) IsApplicable(pulse *eywa.Pulse) bool {
    return pulse.ContactPhone != ""
}

func (s *UserProfileScout) Harvest(ctx context.Context, pulse *eywa.Pulse) error {
    user, err := s.repo.FindByPhone(ctx, pulse.ContactPhone)
    if err != nil {
        return nil // non-fatal โ€” Pulse continues without this data
    }
    pulse.Knowledge["user_name"]   = user.Name
    pulse.Knowledge["user_tier"]   = user.Tier
    pulse.Knowledge["open_orders"] = user.OpenOrderCount
    return nil
}
weave.RegisterEventConfiguration(
    eywa.NewLink("customer_message").
        WithScouts("user_profile", "order_context").
        WithDefaultSpirit("support_agent").
        Build(),
)

Scouts run sequentially within ScoutTimeout (default 15s). A Scout error is logged but never aborts the pipeline.


๐Ÿงญ Multi-Agent Routing with Pathfinders

Route Pulses to the right Spirit automatically based on message content:

// LLM-based routing โ€” a cheap model classifies intent
weave, _ := eywa.NewWeaveBuilder(ctx).
    WithDefaultLLMPathfinder("openai", "gpt-4o-mini", 0.1).
    Build()

weave.RegisterEventConfiguration(
    eywa.NewLink("customer_message").
        WithSpirits("support_agent", "sales_agent", "billing_agent").
        WithDefaultSpirit("support_agent").
        Build(),
)

Or implement rule-based routing:

type TierPathfinder struct{}

func (p *TierPathfinder) GetName() string { return "tier_router" }

func (p *TierPathfinder) SelectSpirit(_ context.Context, pulse *eywa.Pulse, available []string) string {
    if tier, _ := pulse.Knowledge["user_tier"].(string); tier == "VIP" {
        return "premium_agent"
    }
    return "standard_agent"
}

๐Ÿค Multi-Agent Orchestration

Build pipelines of specialized Spirits. An Orchestrator dispatches subtasks to Sub-Spirits via the built-in summon_spirit tool.

coordinator := &eywa.Spirit{
    Name:        "coordinator",
    Description: "Orchestrates research and writing",
    SystemPrompt: `You coordinate specialist agents.
For each request: summon the researcher, then pass results to the writer.`,
    Type: eywa.SpiritTypeOrchestrator,
    OrchestratorConfig: eywa.OrchestratorConfig{
        SubSpirits:     []string{"researcher", "writer"},
        MaxDepth:       2,
        ParallelSummon: false, // set true for independent parallel tasks
    },
    ModelConfig: eywa.SpiritModel{Provider: "openai", Model: "gpt-4o-mini"},
    IsActive: true,
}

The orchestrator calls summon_spirit("researcher", task) like any other tool. Eywa routes the call, returns the result, and the orchestrator continues reasoning.


๐Ÿง  Reasoning that thinks about its thinking

Most frameworks give you a fixed ReAct loop. Eywa's loop layers meta-cognition on top โ€” all opt-in, off by default, so a Spirit that enables none behaves like classic ReAct:

  • Stall detection โ€” notices it's spinning on the same tool and forces a final answer
  • Context compression โ€” summarizes old iterations into an evidence ledger to stay within budget
  • Reflection โ€” self-critiques a draft before delivery, with bounded retries
  • Grounding โ€” RAG answers must cite the chunks they used
  • Plan / scratchpad โ€” a persistent plan across iterations; no premature stop
  • Model tiering โ€” cheap model for tool steps, strong model for the final answer
  • Confidence โ†’ handoff โ€” low-confidence turns escalate to a human instead of guessing
  • Tool-result shaping + arg-aware ban โ€” a giant result can't blow the window; a failed call isn't blindly retried
eywa.NewWeaveBuilder(ctx).
    WithProgressPolicy(eywa.ProgressPolicy{Enabled: true, StallWindow: 3}).
    WithReflectionPolicy(eywa.ReflectionPolicy{Enabled: true, MaxRounds: 1}).
    WithPlanPolicy(eywa.PlanPolicy{Enabled: true, MaxItems: 8})

Each is independent, and any Spirit can override any subset via ReasoningOverrides. Full reference: docs/reasoning.md.


๐Ÿ“š RAG with Lore

Give Spirits a searchable knowledge base. Ingest documents and let the Oracle retrieve relevant chunks at query time.

// Ingest documents
loreRepo := eywamongo.NewLoreRepository(db)
lore := &eywa.Lore{
    Name:        "product_docs",
    Description: "Product documentation and FAQs",
    Chunks: []eywa.LoreChunk{
        {Content: "The return policy allows 30-day returns for all items..."},
        {Content: "To track your order, visit mysite.com/track with your order ID..."},
    },
}
loreRepo.Create(ctx, lore)

// Wire to Weave
weave, _ := eywa.NewWeaveBuilder(ctx).
    WithLoreRepository(loreRepo).
    WithLoreEmbedder(myEmbedder).  // implements LoreEmbedder port
    // ...
    Build()

// Spirit can now call search_lore
spirit.AllowedActions = []eywa.AllowedAction{{Name: "search_lore"}}

Architecture note: LoreEmbedder and LoreStore are ports. The MongoDB adapter uses full-text search out of the box. Swap for pgvector, Qdrant, Pinecone, or Weaviate adapters for semantic vector search.

Lore as a queryable store โ€” matching, dedup, recommendation

Lore is not only RAG the Oracle pulls from mid-turn. You can drive it directly, out of any turn, as a scored vector store โ€” for matching, deduplication, or recommendation:

// Index a structured record: IngestObject verbalizes it via an Oracle (better embeddings than raw
// JSON) and keeps every field as filterable metadata. Re-ingesting the same DocumentID upserts.
weave.IngestObject(ctx, "catalog", record, eywa.IngestObjectOptions{
    DocumentID: "svc-1", Provider: "openai", Model: "gpt-4o-mini",
})

// Direct, scored, metadata-filtered search โ€” no Spirit, no reasoning loop.
maxPrice := 100.0
matches, _ := weave.SearchLore(ctx, "catalog", "affordable analytics platform",
    eywa.LoreSearchOptions{
        TopK:            5,
        Filter:          &eywa.LoreFilter{
            Equals: map[string]any{"category": "analytics"},
            Ranges: map[string]eywa.LoreRange{"monthly_price": {Max: &maxPrice}},
        },
        GroupByDocument: true, // return distinct objects, not chunks of the same one
    })

Metadata filtering needs a FilterableLoreStore (pgvector, Qdrant, Pinecone). See example 14.


๐Ÿง  Long-Term User Memory with Imprint

Spirits remember user preferences and facts across sessions โ€” not just within a conversation.

weave, _ := eywa.NewWeaveBuilder(ctx).
    WithImprintRepository(eywamongo.NewImprintRepository(db)).
    WithImprintExtraction(eywa.ImprintExtractionConfig{
        Enabled:    true,
        MaxFacts:   50,
        Categories: []string{"preference", "personal", "goal"},
    }).
    // ...
    Build()

When ImprintExtractionConfig.Enabled is true, the engine automatically extracts and stores facts from user messages. Spirits also have explicit remember_fact and forget_fact Actions.

A user who says "I prefer formal tone" in one conversation will have that preference injected as context in the next โ€” without the user repeating it.


๐Ÿ™‹ Human-in-the-Loop

Vigil โ€” Operator Takeover

An operator can acquire an exclusive seat on any live conversation. While the seat is held, the AI is blocked and the operator handles messages directly. The seat has a TTL โ€” it auto-expires if the operator goes silent.

vigilRepo := eywaredis.NewVigilRepository(client, "myapp", "prod")

weave, _ := eywa.NewWeaveBuilder(ctx).
    WithVigilRepository(vigilRepo).
    WithVigilConfig(eywa.VigilConfig{InactivityTimeout: 30 * time.Minute}).
    // ...
    Build()

When a Vigil seat is active, ProcessEventByKey returns ErrSessionHeld. The management API exposes:

GET    /api/v1/vigil                       # list all active seats across all sessions
POST   /api/v1/vigil/:memoryKey            # operator takes the seat
POST   /api/v1/vigil/:memoryKey/echoes     # operator sends a message directly
DELETE /api/v1/vigil/:memoryKey            # release โ€” AI resumes
GET    /api/v1/vigil/:memoryKey            # seat status

Subscribe to GET /api/v1/sse/vigil for real-time vigil_acquired / vigil_released events across all sessions.

Rite โ€” Approval Workflow

A Spirit can pause and request human approval before executing a critical action. The Rite is stored in MongoDB and the operator approves or rejects via the API.

// Spirit calls the built-in request_rite action
spirit.AllowedActions = []eywa.AllowedAction{{Name: "request_rite"}}

// Wire the Rite repository
weave, _ := eywa.NewWeaveBuilder(ctx).
    WithRiteRepository(eywamongo.NewRiteRepository(db)).
    // ...
    Build()
GET  /api/v1/rites              # list pending rites
POST /api/v1/rites/:id/approve  # approve โ€” Spirit resumes execution
POST /api/v1/rites/:id/reject   # reject โ€” Spirit receives the decision and responds

Subscribe to GET /api/v1/sse/rites for real-time rite_created / rite_decided / rite_expired events.


๐Ÿ”Œ MCP Integration (Conduit)

Connect Spirits to any Model Context Protocol server. Tools are auto-discovered at startup and registered as Eywa Actions with the prefix <conduit_name>__<tool_name>.

import eywamcp "github.com/wmulabs/eywa/mcp"

conduit := eywamcp.NewConduit(eywamcp.ConduitConfig{
    Name:      "my_tools",
    Transport: "http",
    URL:       "http://localhost:3001",
    Timeout:   15 * time.Second,
    // Headers: map[string]string{"Authorization": "Bearer " + key},
})

weave, _ := eywa.NewWeaveBuilder(ctx).
    WithConduit(conduit). // tools auto-registered on Build()
    // ...
    Build()

// Spirit references MCP tools by prefixed name
spirit.AllowedActions = []eywa.AllowedAction{
    {Name: "my_tools__search"},
    {Name: "my_tools__create_task"},
}

Multiple Conduits can be attached to the same Weave โ€” each with its own namespace.


๐Ÿ’ฐ Cost Tracking with Ledger

Track token usage per Spirit with monthly budgets and automatic model routing.

ledgerRepo := eywamongo.NewLedgerRepository(db)

// Set a monthly budget with downgrade on exceed
ledgerRepo.SetBudget(ctx, eywa.TokenBudget{
    SpiritName:           "assistant",
    MonthlyTokenLimit:    100_000,
    OnExceed:             "downgrade", // "block" | "downgrade" | "alert" โ€” at the hard limit
    DowngradeModel:       eywa.SpiritModel{Provider: "openai", Model: "gpt-4o-mini"},
    AlertThreshold:       0.8,
    DowngradeAtThreshold: true, // proactively switch to DowngradeModel at 80%, before the limit
})

// Auto-route to cheaper models based on request characteristics
weave, _ := eywa.NewWeaveBuilder(ctx).
    WithLedgerRepository(ledgerRepo).
    WithModelRoutingRules([]eywa.ModelRoutingRule{
        {
            Name:      "long_input",
            Condition: eywa.ModelRoutingCondition{InputLengthGte: 2000},
            Model:     eywa.SpiritModel{Provider: "openai", Model: "gpt-4o-mini"},
        },
    }).
    // ...
    Build()

๐Ÿ”ฎ LLM Providers

Eywa supports 8+ LLM providers out of the box. Mix them freely โ€” different models for reasoning, routing, and archiving.

Native providers

Provider Package Constructor
๐ŸŸฃ Anthropic Claude providers/anthropic anthropic.NewOracle(apiKey)
๐ŸŸข OpenAI GPT providers/openai openai.NewOracle(apiKey)
๐Ÿ”ต Google Gemini providers/gemini gemini.NewOracle(ctx, apiKey)
๐ŸŸ  AWS Bedrock providers/bedrock bedrock.NewOracle(ctx, region)
๐Ÿ”ด Google Vertex AI providers/vertexai vertexai.NewOracle(ctx, project, location)

OpenAI-compatible providers (via providers/openai)

Any service that speaks the OpenAI API format works as a drop-in Oracle:

import "github.com/wmulabs/eywa/providers/openai"

// Local models
ollama   := openai.NewOllamaOracle("http://localhost:11434")

// Cloud providers
groq     := openai.NewGroqOracle(os.Getenv("GROQ_API_KEY"))
mistral  := openai.NewMistralOracle(os.Getenv("MISTRAL_API_KEY"))
together := openai.NewTogetherOracle(os.Getenv("TOGETHER_API_KEY"))
router   := openai.NewOpenRouterOracle(os.Getenv("OPENROUTER_API_KEY"))
xai      := openai.NewXAIOracle(os.Getenv("XAI_API_KEY"))

// Azure OpenAI (api-key + deployment-based URLs handled for you; Model = your deployment name)
azure    := openai.NewAzureOracle(os.Getenv("AZURE_OPENAI_ENDPOINT"), os.Getenv("AZURE_OPENAI_KEY"), "2024-10-21")

// Mix them all
weave, _ := eywa.NewWeaveBuilder(ctx).
    AddOracle(groq).
    AddOracle(mistral).
    AddOracle(ollama).
    Build()

Each Spirit's ModelConfig.Provider selects which Oracle handles it:

spirit.ModelConfig = eywa.SpiritModel{
    Provider: "groq",
    Model:    "llama-3.3-70b-versatile",
}

๐Ÿ—“๏ธ Conversation Memory & Archiving

Memory is automatic. For long conversations, wire the Archivist to prevent context overflow:

weave, _ := eywa.NewWeaveBuilder(ctx).
    WithDefaultLLMArchivist("anthropic", "claude-haiku-4-5-20251001", 20).
    WithArchivistConfig(0.1, 512).
    Build()

When the conversation reaches 20 messages, the Archivist summarizes the oldest half and stores it in Memory. The Oracle receives the summary โ€” the thread of conversation never breaks.


๐ŸŒ REST Management API (Fiber)

A single registrar mounts the whole HTTP surface โ€” open event ingestion + the authenticated management API:

import eywafiber "github.com/wmulabs/eywa/fiber"

app := fiber.New()
eywafiber.RegisterRoutes(app, weave, eywafiber.RouteDeps{
    // Management auth (required to expose any management route):
    APIKeys:            map[string]string{"my-api-key": "admin"},
    OperatorAuth:       eywa.NewOperatorAuth(operatorRepo, []byte(jwtSecret)),
    // Management groups (each mounts only when its repo is set):
    SpiritRepo:         spiritRepo, // authenticated Spirit CRUD
    EchoRepo:           echoRepo,
    EchoQueryRepo:      echoRepo,
    ChronicleQueryRepo: chronicleRepo,
    WeaveConfigRepo:    eywamongo.NewWeaveConfigRepository(db),
    ConfigCache:        eywa.NewConfigCache(linkRepo, nil, nil),
    HTTPToolRepo:       eywamongo.NewHTTPToolRepository(db),
    VigilRepo:          vigilRepo,
    VigilConfig:        eywa.VigilConfig{InactivityTimeout: 30 * time.Minute},
    RiteRepo:           eywamongo.NewRiteRepository(db),
    ImprintRepo:        eywamongo.NewImprintRepository(db),
    AppTokenRepo:       eywamongo.NewAppTokenRepository(db), // manage event app-tokens
    PubSub:             eywaredis.NewPubSub(redisClient),    // enables SSE + real-time event fanout
})
app.Listen(":8080")

Routes registered (all under /api/v1; management routes behind auth, events open by default):

Resource Routes
๐Ÿ” Discovery GET /discovery โ€” all registered actions, scouts, classifiers, channels, routers
๐Ÿ‘ป Spirits GET/POST /spirits ยท GET/PUT/DELETE /spirits/:name ยท GET /spirits/:name/versions
๐Ÿ“œ Chronicle GET /chronicle ยท GET /chronicle/:id
๐Ÿ“Š Analytics GET /analytics/tokens ยท /analytics/actions ยท /analytics/spirits
๐Ÿ’ฌ Conversations GET /echoes/sessions ยท GET /echoes/sessions/:key ยท POST /echoes/sessions/:key/messages
๐Ÿง  Imprints GET /imprints (filter by user/spirit/category) ยท DELETE /imprints/:id
โš™๏ธ Config GET/PUT /event-configurations/:eventType ยท GET/PUT /admin/engine-config
๐Ÿ”ง HTTP Tools GET/POST /http-tools ยท GET/PUT/DELETE /http-tools/:id ยท POST /http-tools/:id/test
๐Ÿ™‹ Vigil GET /vigil (all active) ยท POST/DELETE/GET /vigil/:memoryKey ยท POST /vigil/:memoryKey/echoes
โœ… Rites GET /rites ยท GET /rites/:id ยท POST /rites/:id/approve ยท POST /rites/:id/reject
๐Ÿ‘ค Operators GET/POST /operators ยท GET/PUT/DELETE /operators/:id
๐Ÿ”‘ App Tokens POST /app-tokens ยท GET /app-tokens ยท DELETE /app-tokens/:id (revocable event credentials)
๐Ÿ“ก SSE GET /sse/rites ยท GET /sse/vigil ยท GET /sse/echoes/:memoryKey
๐Ÿ”‘ Auth POST /auth/token (public)

Two auth axes. Management routes (above) are always behind auth โ€” API key, operator JWT, or external JWT/JWKS (all via Authorization: Bearer). Event ingestion (POST /api/v1/events/:key) is a separate axis: open by default (webhook-style), or gated by EventAuth (app tokens) / EventVerifiers (HMAC + channel signatures). Spirit CRUD mounts only with SpiritRepo and is always authenticated. Full reference: docs/authentication.md.


๐Ÿ“ก Real-Time SSE

When PubSub is set in RouteDeps, the management API gains three Server-Sent Events endpoints. The cockpit and any custom dashboard can subscribe to lifecycle events without polling.

// Browser โ€” subscribe to Rite lifecycle events
const es = new EventSource('/api/v1/sse/rites', { withCredentials: true })
es.onmessage = (e) => {
    const { event, rite } = JSON.parse(e.data)
    if (event === 'rite_created') showApprovalToast(rite)
}
Endpoint Events
GET /api/v1/sse/rites rite_created ยท rite_decided ยท rite_expired
GET /api/v1/sse/vigil vigil_acquired ยท vigil_released
GET /api/v1/sse/echoes/:memoryKey message_added ยท vigil_acquired ยท vigil_released ยท rite_created

Backed by Redis PubSub โ€” all events fan out across every running instance. Connection keeps alive with a 30-second heartbeat ping. Nginx buffering is disabled automatically.


๐ŸŒŠ Response Streaming

Stream a turn token-by-token end to end โ€” Oracle โ†’ reasoning loop โ†’ your transport. The loop logic is identical to the buffered path; only the LLM call streams and tool-status events are emitted.

ch, _ := weave.ProcessEventByKeyStream(ctx, "chat", pulse)
for ev := range ch {
    switch ev.Type {
    case eywa.AgentStreamDelta:
        fmt.Print(ev.Delta)             // partial text
    case eywa.AgentStreamToolStatus:
        log.Printf("calling %s", ev.ToolName)
    case eywa.AgentStreamDone:
        _ = ev.Response                  // full assembled answer
    }
}

Any Oracle that implements the optional StreamingOracle capability streams; others fall back to buffered automatically. The Fiber API exposes it over SSE at POST /api/v1/events/:event_key/stream.


๐Ÿ“ค Structured Output

Make a Spirit's final answer a JSON object validated against a schema โ€” cross-provider, using the provider's native structured mode when available and a validated repair pass otherwise.

spirit.ResponseFormat = &eywa.ResponseFormat{
    Name:   "ticket",
    Strict: true,
    Schema: map[string]any{
        "type": "object",
        "properties": map[string]any{
            "priority": map[string]any{"type": "string", "enum": []string{"low", "high"}},
            "summary":  map[string]any{"type": "string"},
        },
        "required": []string{"priority", "summary"},
    },
}
// the validated JSON object is returned in response.FinalResponse.

๐Ÿ”ญ Observability (OpenTelemetry + Langfuse)

Eywa emits spans following the OpenTelemetry GenAI semantic conventions (gen_ai.*) for every turn and LLM call. Point them at any OTLP backend โ€” or ship to Langfuse with the bundled exporter:

import eywalangfuse "github.com/wmulabs/eywa/observability/langfuse"

tp, shutdown, _ := eywalangfuse.NewTracerProvider(ctx, eywalangfuse.Config{
    PublicKey: os.Getenv("LANGFUSE_PUBLIC_KEY"),
    SecretKey: os.Getenv("LANGFUSE_SECRET_KEY"),
})
defer shutdown(ctx)
otel.SetTracerProvider(tp) // set before Build(); no engine change needed

โธ๏ธ Durable Execution

Let a turn survive a crash, deploy, or timeout and resume where it stopped instead of restarting โ€” re-paying tokens and re-running tools. Opt-in via a CheckpointStore; off by default.

import eywaredis "github.com/wmulabs/eywa/redis" // or eywamongo for Mongo

weave, _ := eywa.NewWeaveBuilder(ctx).
    WithCheckpointStore(eywaredis.NewCheckpointStore(redisClient)).
    // ...
    Build()

The loop checkpoints after each iteration and resumes on retry (same idempotency key); successful tool results are memoized so side effects aren't duplicated. Full reference: docs/durable-execution.md.


๐ŸŒฟ Roadmap โ€” eywa-cockpit

Deep within the Weave, something stirs. The roots of a new tree are taking hold โ€” one that lets you see every Spirit, every Pulse, every Rite and Vigil seat through a single luminous interface.

eywa-cockpit is a full management UI for the Eywa engine. The management API it connects to is already shipped and production-ready (see Management API).

Feature Status
Hometree โ€” token usage charts, Spirit health, pending Rites ๐Ÿšง In progress
Spirit Grove โ€” create and version Spirits with a configuration editor ๐Ÿ“‹ Planned
Echo Chamber โ€” live conversation inspector with Vigil takeover ๐Ÿ“‹ Planned
Vigil Watch โ€” active operator seats, real-time via SSE ๐Ÿ“‹ Planned
Rite Chamber โ€” approval queue with one-click approve/reject ๐Ÿ“‹ Planned
Chronicle โ€” full audit log with cost breakdown ๐Ÿ“‹ Planned
Pulse Flows โ€” visual event routing configuration ๐Ÿ“‹ Planned
Conduit Gateway โ€” HTTP tool builder with live test runner ๐Ÿ“‹ Planned

Follow progress or contribute at github.com/wmulabs/eywa-cockpit (coming soon).


๐Ÿงช Examples

All examples are runnable with just MongoDB, Redis, and an LLM API key:

Example Concepts
01_basic_setup Minimal Weave โ€” Spirit, Pulse, ProcessEventByKey
02_custom_actions Implementing and registering custom Actions (tool use)
03_advanced_routing Scouts + Pathfinders + multi-Spirit routing
04_async_concept Sync vs async processing comparison
05_multi_provider Multiple Oracle providers โ€” Spirits on different models
06_rag_with_lore RAG: ingest documents, search_lore action, LoreEmbedder port
07_human_takeover Vigil: operator acquires/releases seat, ErrSessionHeld
08_approval_workflow Rites: Spirit requests approval, operator decid