Floret
English |
简体中文 |
繁體中文 |
日本語 |
한국어 |
Deutsch |
Français |
Español |
Português do Brasil |
Русский
The agent runtime that leaves your product in your hands.
Durable conversations, tool execution, context lifecycle, and observable runtime facts for Go applications.
Why Floret ·
At a glance ·
What You Keep ·
Quick Start ·
Production Shape ·
Integration Surface

✨ Why Floret
Most agent libraries help a model call a tool. Shipping a serious agent product
requires much more: a resilient model loop, a durable conversation record,
approval-aware tool execution, long-context management, recoverable work, and
runtime facts your UI can trust.
Floret provides that runtime without taking over the product around it. It is
not an agent UI, a workflow graph, or a multi-agent framework. It is the Go
layer behind the experience you are building.
That distinction matters when your product needs to be genuinely its own. You
can keep your interface, identity system, permission model, model routing,
secrets, data model, and domain tools while delegating the difficult agent
execution lifecycle to Floret.
Built for products that cannot accept a preset
- Bring your model path. Use built-in configuration or provide a
runtime.ModelGateway; Floret drives the request and continuation lifecycle
while your product retains transport and credential control.
- Give every agent a business-native role. Set
config.AgentProfile.SystemPrompt or config.Config.SystemPrompt to define
the role, voice, business scenario, and operating rules your product needs,
rather than shipping a generic assistant.
- Keep tools and instructions in step with the work. Register strict domain
tools with
tools.Registry, then use runtime.ToolSurfaceProvider to
refresh tools, hosted capabilities, instructions, and host context at safe
points during a run.
- Keep conversations durable. The Floret runtime manages threads, turns,
retries, forks, parent-managed child threads, and provider-safe history.
- Put approval policy where it belongs. Floret understands generic effects,
resources, and approval state. Your product decides who may do what, where,
and why.
- Make runtime behavior visible. Stream sanitized events, context pressure,
compaction facts, and neutral activity timelines into any UI without exposing
prompts, secrets, or internal storage records.
- Test the product, not a model. The fake provider makes agent flows
deterministic in local and CI tests.
🧭 At a glance
| You need to... |
Use... |
| Configure an agent and a provider |
config.Config or config.Load |
| Run durable conversations |
runtime.NewTurnExecutionHostBinder |
| Compact an idle thread |
runtime.NewThreadCompactionHostBinder with runtime.CompactThreadRequest |
| Manage interactive child threads |
runtime.NewSubAgentHostBinder |
| Recover one exact interrupted turn |
runtime.NewInterruptedTurnRecoveryHostBinder |
| Reload canonical context state |
runtime.NewThreadReadHostBinder |
| Keep Floret runtime data in memory or SQLite |
runtime.NewMemoryStore or runtime.StartSQLiteStore |
| Keep model transport under product control |
runtime.ModelGateway |
| Define an agent's role and business instructions |
config.AgentProfile.SystemPrompt or config.Config.SystemPrompt |
| Change tools and instructions during a run |
runtime.ToolSurfaceProvider |
| Register domain actions |
tools.Registry |
| Render neutral runtime facts |
runtime.EventSink and observation DTOs |
What You Keep
Floret has a deliberately narrow boundary. It owns engine mechanics; your
application owns every product decision.
| Floret runs |
Your application decides |
| Provider loop, retries, tool continuation, and finish state |
When users can start, retry, interrupt, or cancel work |
| Durable thread journal, prompt scope, provider ledger, opaque continuation, and runtime artifacts |
Users, workspaces, billing, product metadata, and retention policy |
| Tool schema validation, generic effect metadata, approval lifecycle, and result projection |
Authorization, approval UX, domain actions, and user-facing copy |
| Context pressure, compaction lifecycle, and provider-visible history |
What product data is safe to supply and how it appears in the interface |
| Sanitized events and neutral activity facts |
Layout, workflows, controls, routing, and diagnostics policy |
This is what lets an operations console, a coding environment, a support tool,
or an industry-specific assistant share a dependable runtime without becoming
the same product.
Quick Start
Install the downstream packages:
go get github.com/floegence/floret@v1.0.0
Generate the memory composition into your application. The command is a dry
run by default, so the first invocation only shows the proposed source:
go run github.com/floegence/floret/cmd/floret-host-init@v1.0.0 \
--profile memory --package main --dir .
# Review the diff, then repeat with --write.
The generated files own Store lifetime and binder wiring. Application code
keeps only explicit identities and the three narrow local capabilities.
Under the hood it calls runtime.ConfigureHostCapabilities once and retains
only the selected binders at this package-private composition root.
Advanced hosts can use the resulting thread-bound runtime.TurnExecutionHost
directly without adopting the generated local interface.
Generated compositions build Turn and SubAgent options independently from the
same host-owned inputs. Manual integrations must do the same through
NewTurnExecutionHostOptions, NewThreadCompactionHostOptions, and
NewSubAgentHostOptions; the returned values are opaque and cannot be copied
across capability families. Each Factory.NewHost revalidates the option value
and current Store authority. WithTurnIDGenerator,
WithThreadCompactionIDGenerator, and WithSubAgentIDGenerator exist for
deterministic tests or host correlation only and never derive ThreadID,
TurnID, RunID, or PromptScopeID.
The complete business path is 19 nonblank lines:
composition, err := openFloretComposition(ctx, cfg)
if err != nil { return err }
defer composition.close()
threadID := runtime.ThreadID("thread-1")
intentID := runtime.CreateIntentID("create-thread-1")
creator, err := composition.bindThreadCreator(threadID, intentID)
if err != nil { return err }
thread, err := creator.CreateThread(ctx, runtime.CreateThreadRequest{
ThreadID: threadID, CreateIntentID: intentID,
})
if err != nil { return err }
turns, err := composition.bindTurnHost(ctx, thread.ID)
if err != nil { return err }
result, runErr := turns.RunTurn(ctx, runtime.RunTurnRequest{
ThreadID: thread.ID, TurnID: "turn-1", RunID: "run-1",
Input: runtime.TurnInput{Text: "Welcome a new customer."},
})
if err := validateFloretTurnOutcome(result, runErr); err != nil { return err }
fmt.Println(result.Output)
Here cfg is an ordinary config.Config; use the deterministic fake provider
in tests and choose your real provider configuration at the composition root.
The complete runnable minimal durable host
shows imports, shutdown error handling, and durable startup.
Replace the fake configuration with an OpenAI-compatible gateway or a
host-supplied runtime.ModelGateway when your product owns model transport.
When Floret should persist its own runtime data, use StartSQLiteStore. Its
zero-value migration policy refuses an upgrade without writing. An application
that deliberately preserves compatible automatic upgrades must choose
SQLiteMigrationApplyCompatible; Floret derives a stable migration correlation
ID only when an upgrade is actually needed. Advanced hosts may override that ID
and observe typed inspecting, migrating, verifying, and opening progress. The
result uses optional inspection, migration, and verification facts to show
exactly which phases completed even when startup fails. The correlation ID is
not an idempotency key; migration safety comes from the inspected schema,
exclusive writer admission, and transaction boundary. Your product data stays
in your own store, keyed by runtime.ThreadID.
Operator-facing maintenance code may still compose the lower-level
runtime.InspectSQLiteStore, runtime.VerifySQLiteStore,
runtime.MigrateSQLiteStore, and runtime.OpenSQLiteStore contracts directly.
The caller owns the runtime Store, may share it across runtime facades, and
closes it once after all active work has stopped. Runtime facades never close an
injected Store.
Profiles are memory, durable-basic, approval, subagent, and
production-recovery. Generated composition, fake-provider smoke tests, and
profile-specific approval/recovery behavior tests are host-owned,
deterministic, and gofmt-formatted. Existing files are never
overwritten. durable-basic
blocks startup on interrupted work and does not claim automatic recovery;
production-recovery recursively discovers the full SubAgent descendant tree,
recovers exact root or immediate parent-child authority, and requires the host
to reconcile every pending external effect before the startup barrier opens.
The generator creates no backup, go.work, replace, or sibling wiring.
For a production-shaped integration, start with the runnable
minimal durable host, then use the focused
examples for a custom model gateway, effect approval, message
references, SubAgents, startup recovery, and Store maintenance. Integration
tests can use the public deterministic helpers in
florettest. Operators can inspect, verify, plan, and explicitly
apply SQLite maintenance through the
Store maintenance example or the
floret-store command.
Floret's journal is the only durable source for admitted user input, assistant
output, turn/run lifecycle, control signals, approvals, projections, and Agent
todos. Use ListThreadTurns for ordered bootstrap and pagination,
ReadThreadTurn when a canonical TurnID is already known, ReadThread
for transcript-free thread lifecycle metadata, and the typed Agent todo CAS
methods for write_todos state. A host may keep product metadata and commands
that Floret has not admitted yet, but it must not copy these Agent facts into a
second conversation or run database.
Turn-page cursors are opaque, thread- and direction-bound tokens. Hosts return
them unchanged and never interpret journal positions. A composition owner may
retain ThreadInventoryHost to discover canonical root threads for startup
reconciliation; product visibility, routing, pinning, and ordering remain in the
host. Parent-bound SubAgentReadHost.ListThreadTurns and ReadThreadTurn use
the same typed turn contracts as root reads after descendant authority is
proved. Each admitted user entry carries a typed
ThreadUserMessageOrigin, so hosts can distinguish a user message, delegated
SubAgent mission, follow-up SubAgent input, or pending-tool completion without
parsing detail metadata or internal input identities. Detail events remain a
separate diagnostic surface. Reads of pre-v0.30 SubAgent turns recover the same
typed origin from Floret's durable input authority without rewriting journal
entries or exposing compatibility logic to hosts. Inherited legacy entries are
resolved through immutable full-path fork lineage back to the original admitted
input, rather than by matching message text.
Production Shape
Let prompts carry product intent
Floret does not prescribe a generic persona. Give an agent its initial role,
voice, business scenario, and operating rules through
config.AgentProfile.SystemPrompt or config.Config.SystemPrompt. The prompt
is host-owned product configuration, so different agents can behave like a
support specialist, an operations analyst, a coding assistant, or a domain
expert without changing the runtime.
For context-dependent behavior, return a runtime.ToolSurface from a
ToolSurfaceProvider. It can replace the current system prompt alongside the
tool surface, hosted capabilities, and host context. This is useful when a
conversation moves between product modes, workspaces, permissions, or business
stages. Floret refreshes that surface before model requests and local dispatch,
so an old model decision cannot silently run under newer instructions or
product policy.
Give the runtime only the authority it needs
Define domain actions through tools.Registry. Each tool has a strict JSON
schema and can describe its effects and resources. Floret validates the call,
records its generic approval lifecycle, and routes effectful dispatch through
the host's runtime.EffectAuthorizationGate before invoking the handler. The
handler must still enforce your authorization rules. The gate invokes the
one-shot AuthorizedEffect with the execution context selected by the host;
Floret also binds that context to the active turn lifetime, so neither a longer
host context nor a retained callback can outlive canonical turn cancellation.
| Tool concern |
Floret handles |
Host handles |
| Schema |
strict provider-visible JSON shape |
domain argument meaning |
| Permission |
effect metadata and durable approval lifecycle |
product authorization and approval policy through EffectAuthorizationGate |
| Execution |
scheduling, panic recovery, and result projection |
the domain action itself |
| Output |
model projection, neutral activity, and artifact references |
product-specific display choices |
Design around explicit identity
Keep Floret's runtime identities in product work records:
ThreadID identifies the durable conversation.
TurnID identifies one user-facing turn.
RunID identifies one concrete provider execution.
PromptScopeID identifies prompt-cache and provider-ledger reuse.
ForkOperationID identifies one replayable durable thread-fork operation.
They are intentionally separate. For example, a host-owned process that later
settles pending tool work must use the recorded Floret ThreadID, TurnID, and
RunID, rather than a UI, audit, or display identifier.
When a host cancels its exact RunTurn context, an exact terminal TurnResult
means Floret has committed the canonical terminal outcome and released active
turn authority. That valid terminal result is the handoff point for settling
remaining host-owned pending work through the bound PendingToolRecoveryHost.
If execution returns without a valid exact terminal result, the host must first
confirm the exact terminal turn through the public canonical read API. Hosts
must not poll or retry around ErrThreadBusy before either proof exists.
Thread forks require a host-supplied ForkOperationID. Floret saves the source
leaf, destination identities, terminal child-thread plan, and turn/run mappings
before creating any target. Repeating the same operation resumes missing plan
nodes or returns the stored result; reusing it for a different request, finding
an unrelated destination, or losing a completed target fails explicitly.
Render facts, not engine internals
Send a runtime.EventSink to receive sanitized lifecycle events and use the
observation DTOs for context pressure, compaction, and activity timelines.
Events are designed for host rendering and diagnostics; they are not a secret
store or a replacement for your product database.
Public lifecycle fields use finite types such as observation.EventType,
ContextPhase, ContextDisplayStatus, CompactionPhase, and
CompactionStatus. Normalized finish, completion, and continuation reasons are
also finite public fields. Raw provider finish text remains separate in
RawFinishReason, and FinishInferred records whether normalization required
inference. Hosts should call runtime.Event.Validate at their integration
boundary. It validates the event plus nested stream observations, activity
timelines, context/compaction observations, committed detail identity, and turn
projections so unknown values cannot acquire a normal display state or lifecycle
semantics from Metadata.
Thread titles are always persisted by Floret. Set
TurnExecutionHostOptions.ThreadTitleMode = runtime.ThreadTitleModeProvider
when Floret
should issue the dedicated provider request automatically. Products that choose
titles themselves keep the default runtime.ThreadTitleModeHostOwned behavior
and call SetThreadTitle; they must not store a second title copy.
Hosts should validate read models with ThreadSnapshot.Validate or
ThreadSummary.Validate before rendering or caching them. The public
ThreadTitleStatus and ThreadTitleSource types define the accepted finite
title vocabulary; unknown or contradictory title state fails validation rather
than acquiring a normal UI meaning.
RunTurnRequest.Input is a structured runtime.TurnInput. A user turn may
contain text, opaque MessageAttachment resource references, or both. Attachment
resources remain host-owned and are resolved only by a host-supplied
ModelGateway; Floret persists the message-to-resource association without
reading the resource or storing file bytes. An attachment may include optional
host-attested TextStats with Unicode code-point and logical-line counts;
Floret validates and preserves that immutable display snapshot but does not
derive it from MIME type or content. Public hard limits bound attachment count,
field size, referenced byte totals, and descriptor JSON before admission.
A gateway that expands attachments into provider-native content declares
ModelGatewayAttachmentPayloadExpanded and implements the optional
ModelGatewayRequestPreparer. Preparation receives the complete ModelRequest
and returns one immutable, single-use PreparedModelRequest, a stable rendered
payload fingerprint, and a complete exact or conservative token estimate.
Floret uses that estimate for context pressure and input-limit checks, streams
the same prepared instance, and closes every prepared handle on compaction,
request rejection, cancellation, startup failure, normal completion, or Store
shutdown; standalone manual compaction closes its validated handle without
streaming it. The handle is never journaled, cached, or persisted. Existing
descriptor-only gateways keep the original direct ModelGateway contract and
the legacy estimate for requests without attachments. When attachment
descriptors are present, Floret conservatively counts at least one input token
per UTF-8 byte of the complete serialized ModelRequest; this complete-request
upper bound is partitioned into additive prefix, message, and tool components so
native-usage anchors retain attachment deltas. It participates in context
pressure but is not an exact token count.
When a host needs a durable display projection, use ThreadTurnProjection and
the public detail APIs. Do not read Floret's storage tables or rebuild
provider-visible history in the host.
Every turn projection carries ThroughOrdinal, the greatest durable detail
event ordinal included in that projection. Compare it only within the explicit
thread, turn, and run identities to reject duplicate or stale projections.
ProjectedAt is observation time only and is not an ordering key. Live
projections report Status=running from the committed turn-start marker until a
completed, waiting, failed, or cancelled marker becomes durable.
Turn execution and display projection availability are independent outcomes.
TurnResult.ProjectionAvailability is ready or unavailable; an unavailable
projection keeps terminal status, output, metrics, signal, and the ordinary
engine error unchanged. Opaque provider continuation is owned and persisted by
Floret's Store and is never exposed on TurnResult. ProjectionError is
diagnostic, while ReadTurnProjection is the explicit durable reload operation.
Runtime event sinks receive only public observation event types; harness
lifecycle events stay on the separate internal harness sink.
Runtime flow
Host UI/API
| | |
| CreateThread | RunTurn / RetryTurn | CompactThread
v v v
ThreadCreateHost TurnExecutionHost ThreadCompactionHost
| | |
| canonical journal| provider/tool loop | context lifecycle
+------------------+-----------+-----------+
v
Floret runtime
Run it with confidence
Floret is deterministic with the fake provider, so tool behavior, approval
flows, retries, context pressure, and host UI projections can be tested without
real model calls.
Provider-backed execution uses separate thread-bound TurnExecutionHost,
ThreadCompactionHost, and parent-bound SubAgentHost capabilities. Creation,
read, title, fork, delete, parent-bound SubAgent read/maintenance, and bound
pending-tool settlement are also separate concrete capabilities. Create and
retain only the required narrow binders inside the one-time
ConfigureHostCapabilities callback. The callback bootstrap is sealed before
configuration returns, and retained binders become active only after the
callback succeeds. Afterward, each binder can issue only its named capability.
Provider binders fix one thread or parent before provider options are supplied;
provider-free binders return either the create-only coordinator handle or an
exact existing-authority handle. Pass only that selected factory or handle to
its owner; binders stay at the composition root.
Requests retain explicit identities and fail when they do not match the handle
authority.
go test ./...
go run ./cmd/floret-test-ui
The local test console is for contributors to inspect fake-provider sessions,
sanitized events, tool scenarios, and hosted child threads. It is not a
downstream integration surface.
📦 Downstream integration surface
Downstream applications should import only these public 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 implementation detail. The package reference at
pkg.go.dev is the API
source of truth; the OKF knowledge bundle explains the runtime's
architecture and vocabulary for contributors.
Every published release is checked from a blank temporary Go module with
workspace discovery disabled, no local replacement, and a fresh module cache.
The gate verifies the exact tag, module zip, and checksums before compiling and
running all generated profile smoke tests plus the durable host, custom gateway,
tool approval, startup recovery, and Store maintenance examples. Maintainers
can run the same post-release check with
scripts/check_published_release_adoption.sh <exact-tag>. Redeven pins and
verifies that published version in its own dedicated downstream worktree; this
blank-consumer gate does not mutate a sibling repository.
License
Floret is licensed under the MIT License.