Floret
A Go runtime for interactive, tool-using AI agents.
Floret owns the agent loop, durable thread runtime, context pressure, tool dispatch, and sanitized observation. Your product owns the UI, users, permissions, secrets, and domain tools.
Why Floret Β·
At a glance Β·
Quick Start Β·
Projected Turns Β·
Boundaries Β·
Runtime Flow Β·
Quality Gate Β·
License
Floret is a reusable Go runtime for applications that need interactive agent
conversations without rebuilding the same provider loop, durable thread state,
tool execution, context management, compaction, and event projection in every
host. It is not a graph workflow framework and not a multi-agent orchestration
framework. The intended integration path is a small host facade: configure a
host, register tools, start a thread, run turns, and render snapshots or
observations.
β¨ Why Floret
Most agent products end up with the same hard plumbing: provider request shaping,
stream parsing, tool-call validation, approval hooks, durable conversation state,
long-context pressure, retries, usage metrics, and UI-friendly runtime events.
Floret packages those concerns behind a compact public API so product code can
stay focused on product behavior.
- Agent loop: continue after tool calls, enforce loop limits, track finish
reasons, and return clear turn results.
- Durable threads: start, read, retry, and delete hosted conversations through
runtime.Host.
- Tools: register strict schemas with
tools.Registry, declare effects, ask
for approval, and dispatch domain handlers.
- Storage: choose
runtime.NewMemoryStore for tests or
runtime.OpenSQLiteStore for Floret-managed durable runtime storage.
- Observation: stream sanitized
runtime.EventSink records and use
observation DTOs for context and compaction UI.
- Deterministic tests: use the fake provider path to test host flows without
real model calls.
π§ At a glance
| You need to... |
Use... |
| Configure a provider and agent persona |
config.Config or config.Load |
| Build a durable conversation host |
runtime.NewHost |
| Run one turn from a host-owned transcript projection |
runtime.RunProjectedTurn |
| Supply product-owned model transport |
runtime.ModelGateway |
| Keep Floret runtime data in memory |
runtime.NewMemoryStore |
| Keep Floret runtime data in SQLite |
runtime.OpenSQLiteStore |
| Expose product-specific actions |
tools.Registry and typed tool handlers |
| Render progress and diagnostics |
runtime.EventSink plus observation DTOs |
π¦ Stable downstream API
Production downstream projects should import only these packages:
github.com/floegence/floret/config
github.com/floegence/floret/runtime
github.com/floegence/floret/tools
github.com/floegence/floret/observation
Everything under internal/ is Floret implementation. Downstream applications
should not bypass the runtime facade to build turn requests, call Floret
implementation contracts, manage Floret journal tables, or parse prompt-cache
and provider-ledger records. If the product owns model transport, implement
runtime.ModelGateway and let Floret construct the turn request, own the loop,
dispatch tools, and record runtime facts. Product data such as owners,
workspaces, pinned state, read watermarks, and billing metadata belongs in the
host database keyed by runtime.ThreadID. Any package outside the stable list
above is contributor or runtime implementation, not a downstream contract.
π Quick Start
Install Floret:
go get github.com/floegence/floret/config github.com/floegence/floret/runtime github.com/floegence/floret/tools github.com/floegence/floret/observation
Create a host with the fake provider:
package main
import (
"context"
"fmt"
"log"
"github.com/floegence/floret/config"
"github.com/floegence/floret/runtime"
"github.com/floegence/floret/tools"
)
type echoArgs struct {
Text string `json:"text"`
}
func main() {
ctx := context.Background()
cfg := config.Config{
Provider: config.ProviderFake,
Model: "fake-model",
FakeResponse: "hello from floret",
AgentProfile: config.AgentProfile{
ID: "example-agent",
Name: "Example Agent",
SystemPrompt: "You are a concise example assistant.",
},
}
registry := tools.NewRegistry()
err := registry.Register(tools.Define[echoArgs](
tools.Definition{
Name: "echo",
Title: "Echo",
Description: "Return the supplied text.",
InputSchema: tools.StrictObject(map[string]any{
"text": tools.String("Text to echo."),
}, []string{"text"}),
ReadOnly: true,
},
nil,
nil,
func(ctx context.Context, inv tools.Invocation[echoArgs]) (tools.Result, error) {
return tools.Result{Text: inv.Args.Text}, nil
},
))
if err != nil {
log.Fatal(err)
}
store := runtime.NewMemoryStore()
host, err := runtime.NewHost(runtime.HostOptions{
Config: cfg,
Store: store,
Tools: registry,
})
if err != nil {
log.Fatal(err)
}
defer host.Close()
thread, err := host.StartThread(ctx, runtime.StartThreadRequest{ThreadID: "thread-1"})
if err != nil {
log.Fatal(err)
}
result, err := host.RunTurn(ctx, runtime.RunTurnRequest{
ThreadID: thread.ID,
TurnID: "turn-1",
Input: "Say hello in one short sentence.",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Output)
}
Use runtime.OpenSQLiteStore(path) when the host wants Floret-managed durable
runtime storage. Treat runtime.Store as an opaque handle; do not reach into its
tables or implementation details from downstream code.
π Projected Turns
Use runtime.RunProjectedTurn only when the product already owns conversation
rows and needs Floret to execute one turn from a host-built transcript
projection. Durable Floret-managed conversations should use runtime.Host.
Projected turn requests must carry explicit RunID, ThreadID, TurnID,
TraceID, and PromptScopeID. History accepts only user, assistant, and
tool messages; system instructions belong in config.Config. The returned
Transcript is the provider-visible projection for the next turn, not a
sanitized UI display row. When ProjectedTurnOptions.ModelGateway is set,
Floret passes a runtime.ModelRequest to the host-owned model transport and
continues to own tool dispatch, loop control, ledgers, and events.
βοΈ Configuration
config.Load reads .env.local and environment variables. A host may also build
config.Config directly in code.
FLORET_PROVIDER=fake
FLORET_MODEL=fake-model
FLORET_FAKE_RESPONSE=ok
FLORET_CONTEXT_WINDOW_TOKENS=256000
FLORET_RESERVED_OUTPUT_TOKENS=64000
FLORET_RECENT_TAIL_TOKENS=12000
For a custom OpenAI-compatible gateway:
FLORET_PROVIDER=openai-compatible
FLORET_MODEL=your-model
FLORET_BASE_URL=https://api.example.com/v1
FLORET_API_KEY=your-api-key
Provider secrets should be resolved by the host configuration path and passed to
Floret configuration. Events, snapshots, and observation DTOs must not be used as
secret stores.
Hosts register domain tools with tools.Registry. Floret validates JSON
arguments, extracts generic resource and effect information, asks the configured
approver when required, dispatches the handler, shapes output, and records
runtime facts. Tool handlers still enforce product-specific permissions such as
user, tenant, workspace, environment, and target ownership.
| Tool concern |
Floret handles |
Host handles |
| Schema |
strict provider-visible JSON shape |
domain argument meaning |
| Permission |
generic approval hook and effect metadata |
product authorization policy |
| Execution |
scheduling, panic recovery, result projection |
the actual domain action |
| Output |
model/UI projection and artifact references |
product-specific display choices |
Important tool rules:
- Read-only tools may run in parallel only when
ParallelSafe is explicitly
valid.
- Mutating, shell, network, destructive, or open-world tools must declare
permission behavior.
- Provider-native hosted capabilities are not local tools and are not dispatched
by
tools.Registry.
- Large outputs should be represented by artifact references when the model or UI
does not need full inline content.
π§± Responsibility boundary
| Area |
Floret owns |
Host application owns |
| Agent execution |
provider loop, tool continuation, loop limits, finish reasons |
choosing when a user can start, retry, or cancel work |
| Provider access |
adapters, request shape, stream parsing, usage, continuation state |
user-level provider profile, secret source, allowed model policy |
| Storage |
thread journal, prompt material, provider ledger, artifacts, runtime metadata |
product metadata keyed by runtime.ThreadID |
| Tools |
schema validation, generic effects, approval hook, dispatch, result projection |
domain handlers and final product permission checks |
| UI |
sanitized events, snapshots, observation DTOs |
layout, workflows, interaction states, recovery actions |
ποΈ Observation
Use runtime.EventSink to receive sanitized runtime events from a host. Use
observation DTOs for context pressure and compaction state when building UI
surfaces. Observation records are not raw provider payloads and should not
contain prompt text, tool arguments, tool results, local paths, or secrets.
π Runtime Flow
Host UI/API
|
| StartThread / RunTurn / RetryTurn / DeleteThread
v
runtime.Host
|
| owns loop control, journal projection, tool dispatch, context pressure
v
Floret runtime implementation
|
+--> tools.Registry for local domain tools
+--> runtime.Store for Floret-owned runtime data
+--> runtime.EventSink and observation DTOs for host rendering
A normal hosted conversation uses runtime.ThreadID as the durable journal
identity. runtime.TurnID identifies one user-visible turn. runtime.RunID
identifies one provider execution. runtime.PromptScopeID is the prompt-cache
and provider-ledger reuse boundary. Code must not rely on those identities being
equal.
π§ͺ Contributor Test Console
Floret includes a local test console for contributor inspection:
go run ./cmd/floret-test-ui
The console can run fake-provider sessions, inspect sanitized events, run
provider smoke checks, and exercise tool scenarios. It is not the downstream
integration contract.
β
Quality Gate
go test ./...
The test suite covers host facade behavior, Floret-owned provider stream
contracts, tool validation and permissions, context pressure, compaction,
prompt-scope ownership, storage cleanup, and architecture boundaries that keep
Floret internals out of downstream APIs.
π License
Floret is licensed under the MIT License.