The rototo Model
When an application asks for configuration at runtime, what exactly is rototo evaluating?
That question matters because the application is not reading a constant from its own code. It is asking a reviewed configuration workspace for an answer that can depend on the environment and the current request.
The complete lifecycle has two moving parts: the configuration workspace and the application that consumes it. They are developed, tested, and deployed separately. The workspace is released to a source such as a Git ref, and the application is deployed with a URI for that source.
Phase 1: release the configuration workspace
config author workspace source
| |
| create workspace |
| define variables, |
| qualifiers, schemas |
| |
| pre-push validation |
| lint and tests |
| |
| review and merge |
|-------------------------->|
| | controlled Git ref
| | exposes workspace URI
Phase 2: build and deploy the application
app developer workspace source app CI deployed app
| | | |
| use workspace URI | | |
| in SDK tests | | |
|-------------------------->| | |
| | returns workspace | |
|<--------------------------| | |
| | | |
| pre-push validation | | |
| app tests with workspace | | |
| | | |
| push app change | | |
|------------------------------------------------->| |
| | | load workspace |
| |<---------------------| for integration test |
| | returns workspace | |
| |--------------------->| |
| | | app CI passes |
| | |--------------------->|
| | | | deployed with
| | | | workspace URI
Phase 3: resolve configuration at runtime
deployed app workspace source
| |
| load workspace at startup |
|-------------------------->|
| | returns workspace version
|<--------------------------|
|
| request arrives with runtime context
| resolve variable for environment + context
| use selected value
Phase 4: refresh after a config release
config author workspace source deployed app
| | |
| update workspace | |
| pre-push validation | |
| lint, test, review | |
|-------------------------->| updated Git ref |
| | |
| | refresh workspace |
| |<--------------------------|
| | returns new workspace |
| |-------------------------->|
| | | future resolutions
| | | use refreshed config
Inside the deployed application, each resolution is smaller than that whole lifecycle. The application asks for one variable in one environment with one runtime context. rototo evaluates that request against the currently loaded workspace version:
workspace version + variable id + environment + runtime context
-> validate context
-> evaluate qualifiers
-> select value key
-> validate selected value
-> return value and selection metadata
The rest of this page defines the pieces in those two flows and how they fit together.
Workspace
A workspace is the control-plane boundary for rototo configuration. It is a
directory tree with a rototo-workspace.toml manifest and the files that define
runtime decisions.
The workspace is the unit that gets reviewed, linted, tested, loaded by the CLI, loaded by the SDK, and shipped through Git. Keeping that boundary explicit prevents configuration from becoming a mix of application constants, deployment variables, dashboard rules, and undocumented overrides.
The common shape is:
config/
rototo-workspace.toml
qualifiers/
enterprise-accounts.toml
variables/
llm-agent-config.toml
schemas/
context.schema.json
llm-config.schema.json
The manifest declares workspace-level facts such as the valid environments and,
when needed, the context schema. Qualifier and variable files are discovered
from conventional directories. The file stem is the id: llm-agent-config.toml
defines the variable id llm-agent-config.
Workspace Source
An application or tool does not have to load a workspace only from a local
directory. A workspace source can point at a local path, a file:// URI, a Git
repository, or an HTTPS archive source.
Git sources make the deployment model explicit:
git+https://github.com/acme/runtime-config.git#prod:config
That source means: load the workspace from the config directory at ref prod.
The application can be deployed with this source URI instead of embedding the
configuration files into the application binary.
rototo does not require a specific release convention. A small team might use
main as the reviewed production source. A team that wants explicit promotion
can use a mutable production branch such as prod or release/prod, and move
that ref only after workspace checks pass. Tags and commit refs are useful for
reproducible tests or pinned deployments, but immutable refs do not produce new
refresh results because the source does not move.
Environment
An environment is the runtime or deployment lane in which a variable is
resolved. Common environments are dev, stage, and prod.
The environment is not just a string tag. It is part of the selection input. The same application-facing variable can resolve differently in different environments while application code keeps asking for the same id.
max-output-tokens in dev -> small
max-output-tokens in stage -> standard
max-output-tokens in prod -> large
Declaring environments in the workspace gives rototo a validation boundary. A
request for prod can resolve. A misspelled environment such as prd is an
error instead of an accidental fallback.
Runtime Context
Runtime context is the JSON object the application passes when it asks for configuration. It contains request-time facts that are not known when the workspace is authored: account plan, tenant region, user attributes, request country, operation type, rollout bucket, or other application facts.
{
"account": {
"plan": "enterprise",
"seats": 250
},
"request": {
"country": "DE"
}
}
Context keeps request-specific inputs visible. Instead of hiding those facts in application branches, deployment state, or a feature flag service, the application supplies them directly to resolution and the workspace declares how they are used.
Context Schema
A context schema is the input contract between the application and the workspace. It is a JSON Schema that describes the runtime context shape the workspace expects.
The schema exists because runtime decisions often fail at the boundary between
application code and configuration. If config authors write predicates against
account.seats, but the application stops sending that field or sends it as a
string, the rule may no longer mean what the author intended.
With a context schema, rototo can reject invalid context before evaluating qualifiers or variables. That turns a hidden mismatch into a validation failure with a diagnostic, instead of silently falling through to a default branch or selecting a value for the wrong reason.
Qualifier
A qualifier is a named condition over runtime context.
The name is the important part. Production decisions are usually about a
business or operational condition, not about a raw JSON path. A qualifier such
as enterprise-accounts can mean "the account is on the enterprise plan and
has at least 100 seats." Variables, tests, diagnostics, and future config can
refer to that name instead of repeating the predicate everywhere.
schema_version = 1
[qualifier]
description = "Accounts on the enterprise plan with at least 100 seats"
[[qualifier.predicate]]
attribute = "account.plan"
op = "eq"
value = "enterprise"
[[qualifier.predicate]]
attribute = "account.seats"
op = "gte"
value = 100
Qualifiers are reusable. A workspace can resolve a qualifier directly for debugging, or use it inside variable rules to select different values.
Variable
A variable is the application-facing configuration contract. Application code
asks for a variable id such as max-output-tokens or llm-agent-config; it
does not need to know which qualifiers, branches, or rules are active.
A variable defines possible values and the logic for selecting one of them in an environment. It also declares either a primitive type or a JSON Schema for the returned value.
schema_version = 1
[variable]
description = "Maximum number of tokens the summarizer can emit"
type = "int"
[variable.values]
small = 500
standard = 1000
large = 2000
[variable.env._]
value = "standard"
[variable.env.dev]
value = "small"
[variable.env.prod]
value = "large"
The variable id is stable from the application's point of view. The workspace can change which value key is selected in each environment without requiring application code to change.
External Value Files
Values can live inline in the variable file, or in a sibling directory when the values are large enough to deserve their own files.
Inline values keep small decisions close to the variable:
[variable.values]
small = 500
standard = 1000
large = 2000
External value files keep richer values readable and reviewable. For a variable
file named variables/llm-agent-config.toml, rototo also loads value files
from variables/llm-agent-config-values/*.toml. Each file stem becomes the
value key.
variables/
llm-agent-config.toml
llm-agent-config-values/
standard.toml
enterprise.toml
An external value file can hold a scalar:
value = "Welcome back, premium member."
Or it can hold an object under [value]:
[value]
model = "gpt-5"
gateway = "openai"
prompt = "Summarize the incident for an enterprise support workflow."
max_output_tokens = 5000
temperature = 0.2
Resolution does not care whether the selected value came from the variable file or a separate value file. The workspace loader expands both forms into the same variable model before linting and resolution.
Value Key and Value
A value key is the name of a configured branch inside a variable. The value is the actual JSON-compatible data returned to the application.
For max-output-tokens, the value key might be large and the value might be
2000. For llm-agent-config, the value key might be enterprise and the
value might be an object containing the model, gateway, prompt, token limit, and
temperature.
Keeping both pieces matters. The application needs the value. Humans, tests, logs, and agents often need the value key because it explains which configured branch produced the value.
In CLI and JSON output, the selected branch name appears as value_key.
Rules
Rules let a variable choose a value based on qualifiers.
The environment block provides the default value for that environment. Rules can override that default when their qualifier matches the runtime context.
[variable.env.prod]
value = "standard"
[[variable.env.prod.rule]]
description = "Enterprise accounts get the larger agent configuration"
qualifier = "enterprise-accounts"
value = "enterprise"
This means prod uses standard unless enterprise-accounts matches. When
the qualifier matches, rototo selects the enterprise value key instead.
Rules keep application code out of the decision. The application still asks for
llm-agent-config with environment and context; the workspace owns the
selection logic.
Resolution
Resolution is the process that turns a variable id, environment, context, and workspace version into a selected value.
For a variable resolution, rototo follows this shape:
1. Load the workspace source.
2. Validate the workspace structure.
3. Validate the requested environment.
4. Validate runtime context against the context schema, if one is declared.
5. Find the requested variable.
6. Evaluate qualifiers referenced by matching rules.
7. Select the value key for the environment.
8. Validate the selected value against the variable type or schema.
9. Return the selected value key and value.
The exact result is not just "the value for this key." It is the answer to a more precise question:
Given this workspace version, environment, and runtime context,
which reviewed branch applies, and what value should the application receive?
That distinction is the core of rototo. It is a runtime configuration resolver, not only a storage format.
Refreshing Workspaces
Configuration does not have to be fixed at application deployment time.
In the production model, the configuration workspace is deployed separately from the application binary. The application is deployed with a workspace source URI. At startup, the SDK loads the workspace from that source. A long-running service can then refresh the workspace periodically from the same source.
configuration repo
|
| review, lint, test, merge
v
controlled Git ref
|
| workspace source URI
v
application instances
|
+-- load workspace at startup
+-- periodically refresh workspace
+-- resolve variables from current workspace
A successful refresh replaces the active workspace for future resolutions. If a refresh fails because Git is unavailable, authentication breaks, or the new workspace is invalid, the application continues resolving from the last successfully loaded workspace.
That behavior gives teams a way to release reviewed configuration changes without rebuilding the application while avoiding a hard dependency on every refresh attempt succeeding.
Custom Lint
Built-in validation checks the rototo model: manifests, environments, qualifier references, value keys, schemas, and value types. Some teams also need workspace-specific policy that rototo cannot know in advance.
Custom lint is declared on a variable. The variable points at a Lua file owned by the workspace:
[variable.lint]
path = "../lint/llm-agent-config.lua"
The Lua file can define hooks at two levels:
lint(variable)validates the expanded variable as a whole.lint_value(value)validates each value after inline and external value files have been loaded.
function lint_value(value)
if value.value.max_output_tokens > 5000 then
return {
{
message = "value " .. value.name .. " exceeds the token budget",
help = "Use 5000 or fewer output tokens."
}
}
end
return {}
end
Custom lint is for local policy: naming conventions, budget limits, required metadata, allowed model families, rollout rules, or organization-specific constraints. It runs with workspace lint, so policy failures can block local pre-push checks, CI, and release promotion before an application loads the workspace.
The current custom lint declaration is variable-scoped. Workspace-level and qualifier-level custom lint are not separate extension points today.
Validation and Diagnostics
rototo validates configuration at multiple points because different mistakes appear at different times.
Workspace lint checks the files before release: parse errors, invalid manifests, unknown environments, missing values, unknown qualifiers, invalid schemas, external value files, and custom lint failures. Context validation checks the runtime input the application supplies. Value validation checks the selected output before the application consumes it.
Diagnostics use stable codes so humans and agents can recognize failure classes and link them to reference documentation. That makes validation usable in local development, CI, release automation, and application startup.
Observability
Once runtime decisions move out of application code, production systems need a record of what rototo decided.
An evaluation record should capture:
- the workspace source and version that made the decision;
- the variable id and environment;
- the selected value key;
- the matched qualifier or rule;
- the relevant runtime context attributes, with redaction where needed;
- request trace information from the application.
That record lets operators answer the questions that matter during debugging: which value did this request receive, which configuration version produced it, and why did that branch match?
Boundaries
rototo is for runtime configuration decisions that need review, validation, testing, release discipline, and explainability.
It is not intended to replace every configuration mechanism. Process-level settings such as listening ports, database connection strings, and secrets may still belong in deployment or secret-management systems. Application code still owns business logic. rototo owns the reviewed runtime decision: which configured value should this application receive for this environment and context?
That boundary keeps the model small enough to reason about while still covering the decisions that become risky when they are scattered across code, deployment state, dashboards, and operational memory.
What to Read Next
Read quickstart if you want to create a small local workspace and resolve one
variable from the CLI.
Read production-workflow if you want to see the same model with a separate Git
workspace repository, context schema, qualifier rules, tests, SDK loading,
refresh, and observability.