Models propose.Code decides.Events confirm.
Turnframe is a Rust library for LLM conversational workflows that write to real records. Small checked tasks, sized for mini and flash models, read what a message means and stream each step as it is decided. Deterministic reducers decide what happens. The reply claims only what the ledger confirmed. Randomness lives in the reading, never in the effects.
cargo add turnframeeffortstepstoneoverlayfault- Received
- Interpreted
- Reduced
- Executing
- Committed
- Composed
- Delivered
Let the model understand language. Let your code control reality.
Five steps, one direction.
Turnframe is built around the Flow Map: every turn runs the same five steps, and each hands the next only what it may decide. A model’s reading reaches a record only through a reducer, a policy and, for anything consequential, a card the user clicks.
- Projector · purePersisted state determines the viewThe stored record becomes one phase, its open obligations and at most one blocking card. The transcript is history, never state.
- Model · checkedSmall tasks propose what a message meansNarrow questions, each with a strict schema and a check in code, read the message into proposed acts that point at the user’s words.
- Code · deterministicReducers decide effectsThe whole turn is reduced at once into typed commands, each with an expected revision and an idempotency key, under policy.
- User · a clickInteractions authorize consequencesA consequential command waits on a card bound to the exact revision. A click on a card drawn before the record changed is refused.
- Ledger · eventsCommitted events decide claimsThe reply says only what the ledger recorded, so a failed or pending write is never described as done.
Nine small questions, not one large prompt.
A message goes through a fixed chain of narrow tasks, each with a strict schema and a check in code. Dates and amounts are computed by code from what the model points at. No task needs more than a few hundred tokens, so a small, cheap model can run a real workflow.
Models propose
ProposedCode decides
Events confirm
CommittedNo RNG where it matters. Honest RNG where it can’t hurt.
Reliability is not one accuracy number. Side effects, claims and workflow state are correct by construction: a violation is a defect a test can find, never model variance. Understanding is probabilistic, and it is only allowed to fail safe.
| § | Property | Enforced by | Target |
|---|---|---|---|
| 2.1 | Side-effect integrityNo write on the wrong case, over a newer revision, twice for one key, or from an ambiguous target. | Reducer, command policy, ledger commit path | By constructioneffectively 100% |
| 2.2 | Claim integrityNo "created", "changed" or "sent" without the event or receipt that proves it. Unknown outcomes stay unknown. | Ledger, receipt composition | By constructioneffectively 100% |
| 2.3 | Workflow-state consistencyOne lifecycle phase, projected purely from persisted state and the workflow version. The projector reads neither the clock nor the transcript. | Pure projector, state exploration | By constructioneffectively 100% |
| 2.4 | Semantic turn completionMini models read one narrow question at a time, so they are sometimes wrong. This is the only place RNG lives. | Checked tasks, votes, mandatory safe degradation | RNG · fails safe |
| 2.5 | Conversational qualityTone varies with the model. The reply is reviewed before it is shown and cannot contradict a receipt. | Reply tasks, offline judges | Product-dependent |
Mini models. Frame‑perfect effects.
No task needs more than a few hundred tokens, so the reading runs on mini and flash models: on this release's corpus, gpt-5.4-mini passed 228 of 228 samples at medium. Accuracy is bought with checked calls on the same small model, and effects are identical at every setting. How each run was measured, and what the figures cannot show, is in the benchmarks.
The default. How a message is split and routed is read three times, and a verdict that finds fault is voted on again.
Code reads no level. A low turn that misreads a message still cannot run a command its policy forbids, claim what no event backs, or leave a record in a state its workflow cannot express.
Not an agent framework. The model never holds the controller.
Turnframe is for conversational applications that write to real records. The difference is where authority sits.
| Question | Model-calls-tools agents | Turnframe |
|---|---|---|
| Who performs a write | The model calls a write tool | A reducer compiles a typed command; policy decides |
| What the reply may claim | Whatever the model writes | Only what a committed event or receipt backs |
| Two records match | Usually the most recent one | A selection card. Never a recency guess |
| A double click, a retry, a crash | Depends on each tool | One idempotency key, one effect |
| A misread message | A wrong tool call, found later | Checked against the user’s words; a risky act still waits for its card |
| The model it needs | Large, with a long context | Small: no task needs more than a few hundred tokens |
Plain Rust. No macros, no DSL.
- The projector turns the stored record into a view: one phase, its open obligations, at most one blocking card.
- Understanding splits the message, routes each request to an offered operation, and points at the value in the user’s own words.
- The reducer resolves the record and compiles a typed command with an expected revision and an idempotency key.
- The receipt is rendered from the committed event, never from a model’s prose. A timed-out call stays unknown until it is reconciled.
use std::sync::Arc; use turnframe::flow::WorkflowRegistry;use turnframe::runtime::config::OrchestratorConfig;use turnframe::runtime::orchestrator::Orchestrator; // 1. Your domains. Each is a pure projector plus an executor you write.let workflows = Arc::new( WorkflowRegistry::builder() .register(TripWorkflow::default(), Arc::clone(&trips)) .register(TravelerWorkflow::default(), Arc::clone(&travelers)) .build()?,); // 2. Models, persistence, and the records this user may address.// The model never sees a record id: it sees a label.let orchestrator = Orchestrator::builder() .workflows(workflows) .providers(providers) .stores(stores) .case_directory(Arc::new(directory)) .config(OrchestratorConfig::conservative()) .build()?; // 3. One turn: "Register Marta Bianchi and put her on this trip"let answer = orchestrator.handle_turn(input).await?; // The reply says "added" only because a committed event backs it.assert!(answer.receipts().all(|receipt| receipt.is_event_backed()));Install the facade. Pick features.
One dependency, the family behind feature flags. The core crate is pure types and the projector: it runs no async runtime and opens no connection.
| Part | Crate | Role | Feature |
|---|---|---|---|
| Core | |||
| 01 | turnframe | Facade re-exporting the family behind feature flags | included |
| 02 | turnframe-core | Pure types and the deterministic Flow Map projector; no async runtime, HTTP or database | included |
| 03 | turnframe-tasks | Small verified model tasks: repairs, in-place retries, votes, escalation, budgets, records | included |
| 04 | turnframe-understand | The understanding pipeline over those tasks | included |
| 05 | turnframe-runtime | Turn orchestration: reduction, interactions, commands, events, the reply, tracing, replay | included |
| Providers | |||
| 06 | turnframe-provider | Provider-neutral model interfaces, capability routing, fallback policy, conformance suite | included |
| 07 | turnframe-provider-openai | OpenAI, Azure OpenAI and OpenAI-compatible endpoints (profiles) | openai |
| 08 | turnframe-provider-anthropic | Anthropic Messages API | anthropic |
| 09 | turnframe-provider-gemini | Google Gemini and Vertex AI | gemini |
| 10 | turnframe-provider-bedrock | AWS Bedrock Converse | bedrock |
| 11 | turnframe-provider-ollama | Ollama | ollama |
| Persistence and prompts | |||
| 12 | turnframe-store | Object-safe persistence traits and the deterministic in-memory store | included |
| 13 | turnframe-store-postgres | PostgreSQL reference store with migrations and expected-revision transactions | postgres |
| 14 | turnframe-prompt | Prompt sources: prompts compiled in from your own repository, a bounded cache, an optional Langfuse v4 adapter | prompts, langfuse |
| Proof | |||
| 15 | turnframe-test | Test kit: scripted providers and tasks, fake stores, sample workflows, workflow exploration | test-kit |
| 16 | turnframe-eval | Evaluation harness, scored per turn and per understanding task | eval |
| 17 | turnframe-telemetry | Tracing spans, turnframe.* metrics, optional OpenTelemetry bridge | telemetry, otel |
all-providersfull· turnframe-macros is reserved; no macros ship in 0.1Run a turn. Frame by frame.
Read the architecture guide, then run the travel desk: four turns, four guarantees, and no key needed. When you want to see what a real model does with the contract, the console runs the same desk against whichever provider you have a key for. A mini one is enough.
cargo run -p travel-desk