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 composefor 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, useredis.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:
LockTTLmust be at leastReasoningTimeout + 30 seconds. With the defaultReasoningTimeoutof 2 minutes, the minimum safeLockTTLis 2m30s.WeaveConfig.Validate()enforces this at startup โ a misconfigured TTL returns an error fromBuild(), 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
Updatecall creates a new version. Roll back withPOST /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:
LoreEmbedderandLoreStoreare 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 byEventAuth(app tokens) /EventVerifiers(HMAC + channel signatures). Spirit CRUD mounts only withSpiritRepoand 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 |
No comments yet
Be the first to share your take.