What does Ethos look like in 90 seconds?
Ethos has one core abstraction and a handful of interfaces around it. This page is the 90-second tour. Every term linked below has an entry in the glossary.
The one core abstraction
AgentLoop is an AsyncGenerator<AgentEvent>. You give it a user message; it streams typed events back — text, tool calls, usage, errors, completion — until the turn is done.
Every dependency AgentLoop needs (LLM provider, session store, memory provider, personality registry, tool registry, hook registry) is an interface defined in @ethosagent/types and injected at construction. Core never imports concrete implementations.
The turn cycle
~/.ethos/config.yaml
│
▼
wiring.ts assembles all components from config
├── LLMProvider AnthropicProvider | OpenAICompatProvider
├── SessionStore SQLiteSessionStore (WAL + FTS5)
├── MemoryProvider MarkdownFileMemoryProvider
└── PersonalityRegistry FilePersonalityRegistry (mtime hot-reload)
│
▼
AgentLoop.run(text) AsyncGenerator<AgentEvent>
├── session_start hooks
├── MemoryProvider.prefetch() → system context
├── ContextInjector[] → system prompt assembly
├── before_prompt_build hooks
├── LLMProvider.complete() → stream chunks
│ ├── text_delta events
│ ├── tool_use_start / delta / end
│ └── usage event
├── ToolRegistry.executeParallel()
│ ├── before_tool_call hooks (arg override / rejection)
│ ├── parallel execution with budget splitting
│ └── after_tool_call hooks
├── MemoryProvider.sync()
└── agent_done hooks
Three things worth noticing in this diagram:
- Streams, not batched responses. Every step that emits output yields to the generator. The CLI prints text as it arrives; channel adapters update messages mid-flight.
- Hooks fire at every boundary.
session_start,before_prompt_build,before_tool_call,after_tool_call,agent_done— each is a registration point for cross-cutting concerns (auth, audit, rate limiting). - Tools execute in parallel within a budget. When the model returns multiple
tool_useblocks in one turn,ToolRegistry.executeParallelruns them concurrently and splits an 80k-character result budget across them.
The surface architecture
AgentLoop is a library. It does not know what is consuming its events. Nine surfaces ship today — all of them thin wrappers that feed user input into the loop and render whichever AgentEvent subset matters for their medium.
┌─────────────────────────────────────────────────────┐
│ AgentLoop │
│ AsyncGenerator<AgentEvent> │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────┴──────────────────────────────┐
│ web-api (Hono + oRPC) │
│ HTTP + SSE for browser clients │
└──────┬────────────┬────────────────────────────────┘
│ │
┌─────────────┴──┐ ┌─────┴──────────┐
│ Web (React) │ │ Desktop (Electron)│
│ ethos serve │ │ native tray, │
│ --web │ │ global shortcuts │
└────────────────┘ └─────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ Direct consumers │
├──────────┬────────────┬────────────────┬─────────────────────────┤
│ CLI │ TUI (Ink) │ VS Code ext. │ MCP server │ ACP server│
│ readline │ React-Ink │ sidebar panel │ serves │ Agent │
│ REPL │ terminal │ in editor │ Ethos as │ Comm. │
│ │ dashboard │ │ MCP tools │ Protocol │
└──────────┴────────────┴────────────────┴────────────┴───────────┘
┌──────────────────────────────────────────────────────────────────┐
│ Channel adapters (via gateway) │
├────────────┬───────────┬──────────┬────────────┬────────────────┤
│ Telegram │ Discord │ Slack │ WhatsApp │ Email │
└────────────┴───────────┴──────────┴────────────┴────────────────┘
The web and desktop apps connect through the same web-api layer — a Hono HTTP server with oRPC-typed endpoints that streams AgentEvent over SSE. This is the only API surface; there is no separate REST API. Both apps talk to the same AgentLoop instance the CLI uses.
Direct consumers embed the loop in-process. The CLI, TUI, and VS Code extension each construct an AgentLoop and consume its generator directly. The MCP server and ACP server wrap the loop behind their respective protocols, letting external agents call Ethos tools or delegate tasks to Ethos personalities.
Channel adapters are the thinnest layer. Each adapter translates platform-specific webhook payloads into InboundMessage and renders AgentEvent back into platform-specific API calls. The gateway handles dedup, routing, and per-bot loop isolation.
AgentEvent — the streaming contract
Everything the agent does is one of these eight event types:
type AgentEvent =
| { type: 'text_delta'; text: string }
| { type: 'thinking_delta'; thinking: string }
| { type: 'tool_start'; toolCallId: string; toolName: string; args: unknown }
| { type: 'tool_progress'; toolName: string; message: string; percent?: number }
| { type: 'tool_end'; toolCallId: string; toolName: string; ok: boolean; durationMs: number }
| { type: 'usage'; inputTokens: number; outputTokens: number; estimatedCostUsd: number }
| { type: 'error'; error: string; code: string }
| { type: 'done'; text: string; turnCount: number }
Consuming the generator:
for await (const event of agentLoop.run('explain this codebase')) {
if (event.type === 'text_delta') process.stdout.write(event.text)
if (event.type === 'tool_start') console.log(`\n[${event.toolName}]`)
if (event.type === 'done') console.log(`\nTurns: ${event.turnCount}`)
}
A surface (CLI, channel adapter, web UI) renders whichever subset of events it cares about. The contract is the same everywhere — same event types, same fields, same semantics.
Injection at construction
AgentLoop receives every component via AgentLoopConfig. Nothing is global. The wiring.ts in the CLI reads ~/.ethos/config.yaml and assembles the loop:
const loop = new AgentLoop({
llm: new AnthropicProvider({ apiKey, model }),
session: new SQLiteSessionStore({ path: '~/.ethos/sessions.db' }),
memory: new MarkdownFileMemoryProvider({ dir: '~/.ethos' }),
personalities: new FilePersonalityRegistry({ dir: '~/.ethos/personalities' }),
tools: new DefaultToolRegistry(),
hooks: new DefaultHookRegistry(),
})
To use a different LLM, session store, or memory backend — implement the interface and inject it. Nothing else changes.
Extension points
Every interface below is in @ethosagent/types (zero dependencies; safe to depend on from anywhere).
| Interface | Default implementation | Swap to |
|---|---|---|
LLMProvider | AnthropicProvider, OpenAICompatProvider | Any HTTP-based LLM |
SessionStore | SQLiteSessionStore | Redis, Postgres, in-memory |
MemoryProvider | MarkdownFileMemoryProvider | Vector store, database |
PersonalityRegistry | FilePersonalityRegistry | Remote registry |
ToolRegistry | DefaultToolRegistry | Custom filtering / routing |
HookRegistry | DefaultHookRegistry | Custom hook execution |
PlatformAdapter | CLI readline | Telegram, Discord, Slack, WhatsApp, Email |
PluginLoader | DefaultPluginLoader | Plugins register SQLite data sources, widget templates (widgets.yaml), and slash commands |
SkillEvolver | @ethosagent/skill-evolver | Analyzes eval output and proposes skill improvements |
What a personality changes
A personality lives at ~/.ethos/personalities/<id>/ — three files (SOUL.md, config.yaml, toolset.yaml). Switching personalities atomically changes:
- System prompt (from
SOUL.md) - Tool access (from
toolset.yaml) - Memory scope (from
memoryScopeinconfig.yaml) - Model (from
modelinconfig.yaml)
The mental model is: a personality is a role-bound configuration of the agent, not a prompt string. The researcher and the engineer are not the same agent in different costumes — they have different tools, different memories, different models. The next page explains why that matters.
Recommended reading order
Newcomers usually go from here in this order:
- Why is personality the unit? — the headline thesis
- Why Ethos? — honest comparison to LangChain, CrewAI, OpenClaw, Hermes
- Use Ethos: quickstart — install, talk to the agent, switch personalities
- Use the web dashboard — personalities, memory, cron jobs, and MCP from the browser
See also
- What is Ethos? — start here if this page assumed too much
- Why Ethos? — comparison to LangChain, CrewAI, OpenClaw, Hermes
- AgentEvent reference — every variant in detail
- Plugin development — data sources, widgets, slash commands
- MCP server — serving Ethos as an MCP server