Uai \ wai \. Mineiro Portuguese, all-purpose interjection.
Low-level primitives for building AI agents with Effect.
effect-uai is not a framework. There's no runtime to learn, no orchestrator to override, no graph to fight. You get typed streaming primitives (one turn, one tool call) and compose the loop yourself.
OpenAI Responses, Anthropic, and Gemini wire formats normalize to one
TurnEvent union. State is yours. The loop is yours.
Status
While we're in 0.x, minor releases may include breaking changes.
Each one ships with a migration guide
and the effect-uai-migrate skill
encodes the rewrites for Claude Code, so upgrades are mechanical.
Why effect-uai
Most agent libraries decide how your loop works: state shape, retry policy, tool dispatch, cancellation. When you need something they didn't plan for (approval gates, mid-stream cancel, fallback, auto-compaction), you fight the framework.
effect-uai owns the wire (HTTP, SSE, event normalization, validation).
You own the policy. They meet at a Stream<TurnEvent> and a plain
state record.
Features
- Explicit control. No black-box magic. You stay in full control of your agent loop.
- Built on Effect. Retries, streams, concurrency, errors: handled by Effect, not reinvented.
- Composable primitives. Small building blocks you assemble into your own agentic loops.
- Recipes for the hard parts. Copy-paste solutions for model council, auto-compaction, pause and resume, and more.
- Streaming first. Everything's a stream you can transform, filter, and collect when ready.
- Typed errors. Match
RateLimited,Unavailable, orTimeoutdirectly. No string parsing. - Carry your own state. History, budget, scratchpad. Track whatever your agent needs. It's just a value.
Quick taste
The canonical agent loop: stream a turn, run any tools the model asks for, append the outputs, continue until it stops.
export const conversation = loop(initial, (state) =>
Effect.gen(function* () {
const oai = yield* Responses // swap for Anthropic / Gemini any turn
return oai
.streamTurn({ history: state.history, model, tools: toolkit }) // stream text, reasoning, tool events
.pipe(
onTurnComplete((turn) =>
Effect.sync(() => {
const calls = Turn.getToolCalls(turn) // approve, deny, audit, batch (it's your code)
if (calls.length === 0) return stop() // stop on a final answer, a budget, your call
return Toolkit.run(toolkit, calls).pipe(
// run typed Effect tools
Toolkit.continueWithResults(
Toolkit.appendToolResults(state, turn), // fold results back into your state
),
)
}),
),
)
}),
)For tools, approvals, multi-turn loops, sandboxes, and cross-provider fallback, see the docs or the recipes.
Packages
| Package | What it is |
|---|---|
@effect-uai/core |
The primitives: Loop, LanguageModel, Tool, Toolkit, Items, Turn, Transcriber, SpeechSynthesizer, EmbeddingModel, MusicGenerator, WebSearch, Sandbox. No provider deps. |
@effect-uai/responses |
OpenAI Responses provider. Implements LanguageModel over OpenAI's /v1/responses endpoint. |
@effect-uai/anthropic |
Anthropic Messages provider, including extended thinking. |
@effect-uai/google |
Google Gemini: language model, embeddings, speech (sync STT + TTS), and Lyria music generation. |
@effect-uai/mistral |
Mistral: LanguageModel (chat) plus Voxtral speech: realtime + batch STT and TTS. One brand for a full STT to LLM to TTS pipeline. |
@effect-uai/openai |
OpenAI speech: Transcriber (sync + realtime WS) and Synthesizer (sync + chunked HTTP). |
@effect-uai/elevenlabs |
ElevenLabs speech: Scribe v2 Realtime STT and Flash v2.5 TTS with incremental-text-in WS. |
@effect-uai/inworld |
Inworld speech: first-party STT/TTS plus router-style passthroughs (AssemblyAI / Soniox / Groq Whisper). |
@effect-uai/jina |
Jina embeddings: dense, sparse (ELSER), and multivector (ColBERT-style) variants. |
@effect-uai/perplexity |
Perplexity web search: fast, current-events snippets for grounding an LLM. |
@effect-uai/exa |
Exa web search: neural / semantic retrieval ranked by relevance score. |
@effect-uai/tavily |
Tavily web search: snippets and scores with search-depth control. |
@effect-uai/microsandbox |
Local Firecracker microVM sandboxes via microsandbox. Run untrusted code in isolation. |
@effect-uai/deno |
Hosted Firecracker microVM sandboxes on Deno Deploy. No local infra to run. |
Each provider is its own package - edge / browser builds only pull in what you actually use.
Repo layout
.
├── packages/
│ ├── core/ # @effect-uai/core - primitives, no provider deps
│ └── providers/
│ ├── responses/ # @effect-uai/responses - OpenAI Responses
│ ├── anthropic/ # @effect-uai/anthropic
│ ├── google/ # @effect-uai/google - Gemini + speech + Lyria
│ ├── mistral/ # @effect-uai/mistral - LLM + Voxtral speech (STT/TTS)
│ ├── openai/ # @effect-uai/openai - speech (STT/TTS)
│ ├── elevenlabs/ # @effect-uai/elevenlabs - speech
│ ├── inworld/ # @effect-uai/inworld - speech
│ ├── jina/ # @effect-uai/jina - embeddings
│ ├── perplexity/ # @effect-uai/perplexity - web search
│ ├── exa/ # @effect-uai/exa - web search
│ ├── tavily/ # @effect-uai/tavily - web search
│ ├── microsandbox/ # @effect-uai/microsandbox - local sandboxes
│ └── deno/ # @effect-uai/deno - hosted sandboxes
├── recipes/ # 27 worked recipes (type-checked, tested) covering
│ # tools, approvals, fallback, voice, sandboxes, …
├── recipes-extras/ # Recipes that need extra infra to run (e.g. sandbox-code-interpreter)
├── docs/ # Source for the docs site (concepts, recipes, providers)
├── webpage/ # Astro/Starlight site that renders docs/
└── integration-tests/ # Live-system smoke tests; run manually, not part of CI
A recipe folder typically contains:
index.ts- the building blocks (tools, state, body), reusable in testsrun.ts- a runnable demo that wires real providersindex.test.ts- vitest tests againstMockProviderREADME.md- the page that's mirrored in the docs site
Docs / learn
Full docs: https://effect-uai.betalyra.com
Recommended reading order:
- One turn is a stream - the smallest provider-agnostic primitive.
- Basic usage - the core agent harness: state, stream, tools, continuation.
- The loop primitive - what
loopis, its shape, andstreamUntilComplete. - Items and turns - the conversation as a flat list, the assembled turn, the event stream.
- Tools and toolkits -
Tool.make(with progress viaemit),Toolkit.make, approval planners,ToolEvent.
Then dip into recipes for whatever pattern you need.
Local development
pnpm install
pnpm test # vitest run across all workspaces
pnpm typecheck # tsc --noEmitTo run a recipe end-to-end against real providers:
OPENAI_API_KEY=sk-... pnpm tsx recipes/basic-usage/run.tsNix dev shell (optional)
This repo ships a flake.nix that provides a dev shell with the exact
toolchain CI uses - Node 24, the pinned pnpm version (via corepack), and
Deno for the integration tests. It is 100% optional: if you already
have Node and pnpm installed, ignore this entirely and use the commands
above.
If you do use Nix with flakes enabled:
nix develop # drops you into a shell with node, pnpm and denoThe repo also ships an .envrc, so with direnv
installed the shell loads automatically when you cd in - just run
direnv allow once. Without direnv the file is inert and ignored.
License
MIT - see LICENSE.