The TypeScript framework for agentic conversation — bots that maintain a stateful relationship with a human user AND take real actions in external systems with the isolation, observability, and audit that side effects demand.
Not for: pure FAQ chatbots (use Vercel AI SDK), pure coding agents (use Flue directly). Built on: Flue → pi-agent-core → pi-ai. We hide Flue's vocabulary from users; we use its primitives heavily under the hood.
Floe positions in the intersection — stateful dialogue + action-taking. Examples: internal IT/HR ops bots, sales SDR bots, external CX bots that issue refunds, voice agents that take appointments.
Full positioning + the "what we don't build" filter: docs/adr/0001-floe-as-agentic-conversation-framework.md.
Locked v1 contract: docs/BLUEPRINT.md.
pnpm add @floe/runtime @floe/adapter-webFlue (
@flue/runtime) is the runtime engine underneath. It ships as a transitive dep of@floe/runtime— you never install or import it directly.
// assistant.ts
import { Assistant } from '@floe/runtime';
import { workspaceBm25 } from '@floe/runtime/knowledge/workspace-bm25';
import { confidence } from '@floe/runtime/validators';
import { localSandbox } from '@floe/runtime/sandbox/local';
export const ops = new Assistant({
name: 'ops',
mode: 'coordinate', // see § Modes
systemPrompt: `You are the IT operations bot. Use delegate() to ask a
specialist role when relevant. File a ticket for any access change.`,
roles: {
'access-approver': {
name: 'access-approver',
description: 'Approves access requests against policy.',
instructions: 'You evaluate access requests...',
thinkingLevel: 'high',
},
},
knowledge: [workspaceBm25({ name: 'policies', paths: ['knowledge/**/*.md'] })],
validators: [confidence({ disambiguateBelow: 0.6 })],
sandbox: localSandbox(),
model: 'anthropic/claude-sonnet-4-6',
});// server.ts
import { serve } from '@hono/node-server';
import { webAdapter } from '@floe/adapter-web';
import { ops } from './assistant.ts';
const app = webAdapter({ assistant: ops });
serve({ fetch: app.fetch, port: 3000 });That's it. Send a POST to http://localhost:3000/agents/web/<sessionId> with {message: "..."} and you have an action-taking, role-delegating, stateful conversation.
The configured handler that turns user messages into actions + replies. Construct with new Assistant({...}). Long-lived: configure once at module scope, reuse across requests.
interface AssistantConfig {
name: string;
systemPrompt: string;
mode?: AssistantMode; // default 'direct'
roles?: Record<string, Role>; // specialist role registry
tools?: FloeTool[]; // shared with all roles via delegate
flows?: Flow[]; // multi-step state machines
procedures?: Procedure[]; // passive policy injection
knowledge?: KnowledgeSource[];
validators?: Validator[];
memory?: MemoryConfig | false;
persona?: PersonaConfig;
model?: string; // 'provider/model-id'
thinkingLevel?: 'off' | 'low' | 'medium' | 'high' | 'xhigh';
sandbox: SandboxFactory | false; // required; opt-out with false
configDir?: string;
resolveUserId?: (input) => string | undefined;
}A Role is Flue's Role (we re-export). Markdown-defined instructions + optional model/thinkingLevel overrides. No defineAgent, no Floe-level Agent class.
import type { Role } from '@floe/runtime';
const billing: Role = {
name: 'billing',
description: 'Billing specialist — pricing, refunds, plan changes',
instructions: 'You are a billing expert. ...',
thinkingLevel: 'medium',
// model?: 'openai/gpt-4.1-mini', // optional override
};Delegation happens via Floe's delegate({role, prompt}) tool, automatically injected when mode='coordinate' and roles is non-empty.
All preserved unchanged from v0. See:
docs/SUPPORT-BOT-BLUEPRINT.md— start here if you're building — layered Layer 0 → Layer 9 walkthrough from minimum-viable to production, cross-referenced to every primitivedocs/FLOWS.md— the 4 node kinds (Extraction / Capture / Compute / Reply) with worked examplesdocs/LATENCY.md— performance budgets, the streaming architecture, the dials you can turndocs/RAG.md— knowledge + retrievaldocs/OBSERVABILITY.md— turn metrics + sinksdocs/PROCEDURE-VS-SKILL.md— Procedure (passive) vs Flue Skill (active)
Floe expresses the coordination spectrum via four explicit modes (Agno-inspired). The mode is an Assistant-level default; adapters can override per call.
| Mode | LLM calls/turn | TTFT | Best for |
|---|---|---|---|
direct (default) |
1 | ~1.5s | FAQ, single domain, voice |
route |
2 (triage + specialist) | ~2s | Clear domain boundaries, cheap triage model |
coordinate |
2+N (host + tasks + synthesis) | ~1-1.5s* | Complex turns, host stitches specialist outputs |
broadcast |
2+N (parallel) | ~1.5s | Compound research, perspective gathering |
(*coordinate's TTFT can beat end-to-end because the host streams "Let me check..." while the task runs.)
Default = direct. The runtime never burns LLM cost on routing unless you opt in. Multi-role Assistants explicitly set mode: 'coordinate' (or 'route' / 'broadcast').
Adapters can override the mode per run() call — e.g., a voice adapter strips delegation regardless of the Assistant default:
await assistant.run(transcript, {
sessionId: callSid,
overlay: { mode: 'direct', maxResponseTokens: 100 },
});Returns a TurnHandle that's awaitable + iterable + cancellable + pipeable to an HTTP response.
const handle = ops.run('Hello', { sessionId: 'user-1' });
// (a) Await for the final result
const result = await handle; // { content, runId, sessionId, mode, ... }
// (b) Iterate events as they happen
for await (const event of handle.events) { /* ... */ }
// (c) Pipe to HTTP — web widget, AI SDK useChat compatibility
return new Response(handle.toResponseStream('sse'));
// (d) Cancel mid-turn (or wire to a client AbortSignal)
handle.cancel('user navigated away');Mount the Assistant via an adapter once at module scope — e.g. webAdapter({ assistant: ops }) from @floe/adapter-web. Programmatic assistant.run(...) works after that without further wiring.
Core ships exactly one channel adapter: @floe/adapter-web — webAdapter({ assistant }) returns a Hono-mountable {fetch, route}. JSON POST + SSE response. Vercel AI SDK useChat compatible.
For Slack / voice / Twilio / WhatsApp / custom platforms: write your own adapter. An adapter is a function that:
- Parses the inbound platform webhook
- Calls
assistant.run(message, {sessionId, userId, overlay}) - Renders the response however the platform wants
Floe doesn't ship defineChannel for non-web. The slack/voice adapters that previously lived in core have been deleted — they're better handled as separate packages (@floe/adapter-slack, @floe/adapter-elevenlabs, etc.) on the user's roadmap or built in 30 LOC inline.
Tier 1 (positioning):
- Visual flow builder
- Managed hosting product
- Vertical CX UI components
- CrewAI-style swarm orchestration beyond what Flue's
taskgives
Tier 2 (DX surface):
- JSX card rendering (Block Kit, Adaptive Cards) — out of scope; use vercel/chat if you need that
- Modal / Button / Select / RadioSelect UI primitives
- Cross-platform mdast format converters
- Unified event model beyond "user turn arrived"
floe.*runtime namespace — subpath imports do the job
The floe.* runtime namespace was rejected — too many indirection, broken tree-shaking. Use subpath imports (Hono-style):
import { Assistant, defineTool } from '@floe/runtime';
import { webAdapter } from '@floe/adapter-web';
import { workspaceBm25, hybridKnowledge } from '@floe/runtime/knowledge';
import { confidence, safety, piiRedaction } from '@floe/runtime/validators';
import { localSandbox, cfBashSandbox, noneSandbox } from '@floe/runtime/sandbox';
import { openaiEmbedder } from '@floe/runtime/embedders/openai';
import { InMemoryVectorStore } from '@floe/runtime/vectorstores';Full subpath list in packages/runtime/package.json#exports.
docs/BLUEPRINT.md— locked v1 contract (this is the source of truth)docs/adr/0001-floe-as-agentic-conversation-framework.md— positioningdocs/PROCEDURE-VS-SKILL.md— Procedure (passive) vs Flue Skill (active) — kept separate by designdocs/use-cases/— concrete walkthroughs (IT ops, B2C subscription, B2C clinic)docs/RAG.md,docs/OBSERVABILITY.md,docs/EVAL.md,docs/OPENAI-COMPAT.md
examples/role-spike/— minimal mode='coordinate' canary (live-verified)examples/streaming-bot/— single-Assistant streamingexamples/mcp-bot/— MCP tool wiring + stub MCP serverexamples/memory-bot/— cross-session memory keyed by userIdexamples/hybrid-rag-bot/— BM25 + embeddings + rerankerexamples/flow-bot/— multi-step flowexamples/support-bot/— multi-role (mode='coordinate' with service + sales)examples/ecommerce-bot/— full production-shaped bot with Turso state
MIT.