Production-grade, event-driven AI orchestration for Go
๐ English ยท Portuguรชs
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.
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.
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 |
๐ค 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 a researcher, which delegates to a writer โ with configurable depth and parallel execution.
๐ 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.
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.
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.
go get github.com/wmulabs/eywaSub-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 processingNo 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.
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}.
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 |
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.
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"
}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.
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.
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.
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.
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.
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.
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{
SpiritID: "assistant",
MonthlyTokenLimit: 100_000,
OnExceed: "downgrade", // "block" | "downgrade" | "alert"
DowngradeModel: eywa.SpiritModel{Provider: "openai", Model: "gpt-4o-mini"},
AlertThreshold: 0.8,
})
// 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()Eywa supports 8+ LLM providers out of the box. Mix them freely โ different models for reasoning, routing, and archiving.
| 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) |
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"))
// 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",
}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.
Mount a full management API in two lines:
import eywafiber "github.com/wmulabs/eywa/fiber"
app := fiber.New()
eywafiber.RegisterManagementRoutes(app, weave, eywafiber.ManagementDeps{
APIKeys: map[string]string{"my-api-key": "admin"},
OperatorAuth: eywa.NewOperatorAuth(operatorRepo, []byte(jwtSecret)),
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),
PubSub: eywaredis.NewPubSub(redisClient), // enables SSE + real-time event fanout
})
app.Listen(":8080")Routes registered (all under /api/v1, auth required except /auth/token):
| 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 |
| ๐ก SSE | GET /sse/rites ยท GET /sse/vigil ยท GET /sse/echoes/:memoryKey |
| ๐ Auth | POST /auth/token (public) |
Security note: The fiber package also exports
RegisterRoutes, a lighter alternative that mounts only the event-ingestion and Spirit CRUD endpoints โ without authentication middleware. Do not exposeRegisterRoutesto the public internet without adding your own auth layer or placing the server behind a network boundary. Spirit system prompts are a critical attack surface: an unauthenticated write to/api/v1/spiritsis prompt injection at the infrastructure level. For production deployments, preferRegisterManagementRoutes, which enforces authentication on all management endpoints.
When PubSub is set in ManagementDeps, 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.
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).
All 13 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 decides |
09_long_term_memory |
Imprint: remember_fact, auto-extraction, cross-session facts |
10_cost_tracking |
Ledger: TokenBudget, ModelRoutingRule, usage stats |
11_mcp_client |
Conduit: connect to MCP server, auto-discover tools |
12_management_api |
Full Fiber management API with operator auth |
13_multi_agent |
Orchestrator Spirit: summon_spirit, OrchestratorConfig |
SPIRIT_NOT_FOUND โ Spirit was not returned by the Pathfinder
A Pulse's event type was matched by a Link, but the Pathfinder selected a Spirit name
that was not registered. Check that weave.RegisterSpirit() was called for the Spirit
name returned by your Pathfinder, and that the Spirit's Activate() succeeded.
ErrSessionHeld / ErrMemoryBusy โ Concurrent message for same session
A second Pulse arrived for a MemoryKey while the first was still being processed.
The Bond (distributed lock) is working as intended: the second Pulse is held in the
Inbox (if configured) or rejected. The caller should retry after a short delay.
If this happens too frequently, tune LockTTL and ReasoningTimeout.
Oracle unavailable (503 / rate limit)
The ErrReasoningFailed error is retriable โ the pipeline returns it as a retriable
OrchestrationError. If using Cloud Tasks as Keeper, the task will be retried with
exponential backoff automatically. For synchronous calls, check IsRetriable(err) and
implement your own retry logic.
Lock expired before reasoning completed
Symptom: duplicate responses or interleaved Chronicle entries for the same session.
Cause: LockTTL โค ReasoningTimeout. Fix: set LockTTL to at least ReasoningTimeout + 30s.
WeaveConfig.Validate() enforces this โ run it explicitly if you construct config manually.
Redis connection error on startup
If using NoOpBond for local development, remove the Redis dependency entirely.
If using redis.NewBondManager(), ensure the Redis URL is correct and the instance is reachable.
Check bond.Ping() in your health check.
Eywa ships with conservative defaults for most settings, but one requires explicit action in production:
Prompt injection detection is enabled by default. InputGuard.PromptInjectionDetection defaults
to true in DefaultWeaveConfig(). If you find false positives for your use case, you can disable it:
weave, err := eywa.NewWeaveBuilder(ctx).
// ... other options ...
WithInputGuard(eywa.GuardConfig{
MaxLineCount: 200, // reject messages with more than N lines (0 = disabled)
PromptInjectionDetection: false, // disable only if you trust all input sources
}).
Build()See WithInputGuard for tuning options.
What to never log: Avoid logging raw Pulse.UserMessage in production โ it may contain PII or
credentials. Eywa's structured logging (Zap) logs metadata but not message content by default.
Oracle API keys: Pass via environment variables, never hard-coded. Eywa reads them through the provider constructors; the values are never stored or logged.
Eywa is built on hexagonal architecture (ports & adapters). The domain has zero infrastructure dependencies โ you swap MongoDB for Postgres, Redis for Valkey, OpenAI for Bedrock, without touching the engine.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your Application โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Fiber routes โ WhatsApp receptor โ Cloud Tasks callback โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Weave (engine core) โ
โ Pipeline ยท Memory ยท Archivist ยท Pathfinder ยท Actions โ
โโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโค
โ MongoDB โ Redis โ OpenAI โ Bedrock โ Any MCP server โ
โ adapter โ adapter โ Oracle โ Oracle โ (via Conduit) โ
โโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโ
Every external system is behind a port (interface). The _examples/ directory shows how to compose these adapters for real workloads.
Guides:
Every adapter you write extends the Weave. Since everything in Eywa is wired through interfaces, contributing means implementing one port and publishing it as a standalone sub-module โ no engine internals required.
What the community can build:
| Type | Interface | Examples |
|---|---|---|
| ๐ฎ LLM Oracle | eywa.Oracle |
Cohere, DeepSeek, Fireworks AI, Azure OpenAI, llama.cpp |
| ๐ Vector Store | eywa.LoreStore |
Chroma, Milvus, OpenSearch, Redis Vector Sets |
| ๐ฃ Channel | eywa.Voice + eywa.Receptor |
Telegram, Slack, Discord, SMS, Email, WeChat |
| ๐๏ธ Repository | eywa.*Repository |
PostgreSQL, DynamoDB, Firestore, Valkey |
| โ๏ธ Cloud | eywa.Vault + eywa.Lens |
S3, Azure Blob, AWS Transcribe |
See CONTRIBUTING.md for structure, requirements, and how to publish.
# All tests
make test
# Tests + coverage summary
make coverage
# Interactive HTML coverage report
make coverage-htmlEvery PR must include tests. See CONTRIBUTING.md โ Testing for the conventions used across this codebase.
If Eywa saved you time โ or just made you think differently about AI infrastructure โ consider buying a coffee. It helps keep the Weave growing.
Apache 2.0 โ see LICENSE.
๐ฟ Inspired by the neural network of Pandora. Built for production AI systems in Go.
