Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

asyncdotengineering/floe

Repository files navigation

Floe

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.


Install

pnpm add @floe/runtime @floe/adapter-web

Flue (@flue/runtime) is the runtime engine underneath. It ships as a transitive dep of @floe/runtime — you never install or import it directly.

Minimum end-to-end app — two files, ~50 LOC

// 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.


Primitives

Assistant — the one Floe-level primitive

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;
}

Role — the only specialist primitive

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.

Procedures, Flows, Knowledge, Validators, Memory

All preserved unchanged from v0. See:

  • docs/SUPPORT-BOT-BLUEPRINT.mdstart here if you're building — layered Layer 0 → Layer 9 walkthrough from minimum-viable to production, cross-referenced to every primitive
  • docs/FLOWS.md — the 4 node kinds (Extraction / Capture / Compute / Reply) with worked examples
  • docs/LATENCY.md — performance budgets, the streaming architecture, the dials you can turn
  • docs/RAG.md — knowledge + retrieval
  • docs/OBSERVABILITY.md — turn metrics + sinks
  • docs/PROCEDURE-VS-SKILL.md — Procedure (passive) vs Flue Skill (active)

Modes

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').

Per-call override

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 },
});

The Assistant.run() API

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.


Channels — BYO + one in-core

Core ships exactly one channel adapter: @floe/adapter-webwebAdapter({ 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:

  1. Parses the inbound platform webhook
  2. Calls assistant.run(message, {sessionId, userId, overlay})
  3. 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.


What we do not build

Tier 1 (positioning):

  • Visual flow builder
  • Managed hosting product
  • Vertical CX UI components
  • CrewAI-style swarm orchestration beyond what Flue's task gives

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

Subpath import map

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

Examples

  • examples/role-spike/ — minimal mode='coordinate' canary (live-verified)
  • examples/streaming-bot/ — single-Assistant streaming
  • examples/mcp-bot/ — MCP tool wiring + stub MCP server
  • examples/memory-bot/ — cross-session memory keyed by userId
  • examples/hybrid-rag-bot/ — BM25 + embeddings + reranker
  • examples/flow-bot/ — multi-step flow
  • examples/support-bot/ — multi-role (mode='coordinate' with service + sales)
  • examples/ecommerce-bot/ — full production-shaped bot with Turso state

License

MIT.

About

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.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /