What you get for free
What a Telo application gets without writing it, compared with TypeScript, Go or Java and their usual libraries. Telo itself is what every application gets, whatever it imports. Standard library is what its modules add. Within each group, the features people ask about most come first. The page grows with every release.
Telo itself
Running
- Request cancellation. When a caller goes away — a client disconnecting, a timeout, a shutdown — cancellation flows down everything the call started, to every call that honours it, retry backoffs included. No cancellation token passed through every function.
- Graceful shutdown.
SIGINT/SIGTERMcancel in-flight work, refuse what has not started, close resources in reverse dependency order, wait briefly for background work, flush logs last, and exit with a defined code — the same in a packaged binary. A cleanup that fails does not stop the rest. - Timeouts that cancel. A per-attempt
timeout:on any call cancels it rather than abandoning it with its side effect still running. - Safe retries.
retry:on any call, with exponential backoff and full jitter. Cancellations and contract violations are never retried,nonRetryable:stops on named codes, a retry that would resend a consumed stream is refused before the app runs, and a cancelled backoff keeps the original failure. - Starts and stops itself. Startup order comes from the references between resources, and a dependency loop is reported with its path before anything starts. The process stays up while something serves or schedules and exits when the work is done; one stuck waiting on something that can never happen exits 1 with the reason.
- Validation on every call. A resource's
inputType/outputTypeare checked at runtime on every call between resources, not only at the HTTP edge. - No context leaking between requests. Inbound work starts with no inherited transaction or trace.
Configuration
- See what an app needs.
--helplists every argument and every environment-only input with its type and default;telo needs reportlists the variables its dependencies read;telo module manifest --jsonreports all of it;telo env templatewrites the.env/.env.locallines. Nothing is booted for any of them. - Validated before start. Inputs are declared once with
env:andtype:, and every missing or invalid one is reported together before anything starts. A declared variable reads as unset through the process environment, so code reading it directly gets nothing. - Asked for at the terminal.
telo runat a terminal asks for each input nothing supplies — a secret without echo, each answer checked as the environment variable would be and asked again with the reason when refused — then offers to save the answers to.env/.env.local. A watch session asks once; nothing is asked under--no-input, in a runner or without a terminal. - Dependency settings without inventing names. A library's settings are read from derived names (
TELO_NEED_ORDERS__DATABASE_ACCESS__URL); a missing one names every variable to set; two settings reading one variable are refused; a statedversion:orfeatures:is checked against what the dependency finds at start. - Libraries can't read your environment. Only the application reads environment variables and arguments; a library declaring one is refused, and receives values from its importer instead.
- Command-line arguments.
arg:turns an input into a flag or positional, with one precedence (argument, then environment, thendefault:) and unknown arguments refused. A secret can never be an argument. .envfiles..envand.env.localare collected up to the workspace marker bytelo runand Studio alike..envmay be committed;.env.localholds secrets.- Public URLs without hard-coding.
Telo.PublicUrlis derived from host templates, so no domain is written in a manifest; two addresses no host could route apart are refused at start. - Paths that don't depend on the working directory. A relative path in configuration is refused before the app runs, and files a module ships are found wherever it is unpacked and checked to exist.
- Ports known before boot.
ports:are typed TCP or UDP and known to a runner before the app starts.
Checked before it runs
- Static checking.
telo checkresolves every import, reference, schema and expression with no database, network or process — the configuration, wiring and query mistakes a compiler cannot see. Malformed URLs, regexes, durations and cron expressions are refused too. It exits non-zero on errors only, and-o jsongives each finding with its location, code and fix. - The runtime agrees with the checker.
telo runruns the same analysis and refuses to start, with the same message. - Error coverage. Each error code a handler declares must be rendered by a
catches:entry around it; a catch for a code nothing throws and a missing or misplaced catch-all are refused. Wrappers pass their body's codes through (throws: { inherit: true }), and errors are coded and typed rather than matched on message text. - Wiring checked. Every reference must resolve to a resource of the kind its slot accepts.
- Texts checked for translation. Text shown to a person is typed
Textand written in the module'slang:, with{$name}placeholders and plural cases chosen by the language's CLDR rules. A text that does not parse, a plural case its language lacks, a value a text reads that the call does not give, a placeholder the screen or controller that shows the text will not fill, an expression where a text must be written at the call, and a translation reading a placeholder its source lacks are refused before the app runs, and the same mistakes in a computed text when it is computed, at start or per call; a translation nothing shows is warned about. Each controller receives the text with the language it is written in. - Security settings that would do nothing are refused. A sensitive mark no contract reaches, a redaction path that does not parse, a version requirement nothing compares.
- Failures point at the cause. A failed start reports the root cause first, with the resources it blocked collapsed under it and the manifest location named.
Observability and security
- Tracing. Every call is a span and an incoming W3C
traceparentis continued. Tracing turns on by listing one sink. - Structured logging. Every record carries its resource and
scope, and inside a call itstrace_id/span_id. Levels are set per import and cascade. A log call never throws, sinks are bounded with dropped records counted,fatalflushes, and only sinks the application lists receive anything. - Secret redaction.
secrets:values, URL passwords, sensitive dependency settings and contract fields markedx-telo-sensitiveare redacted in logs, errors and traces. Spans carry no payloads unless a contract field opts in (x-telo-span-attribute). - Secrets stay where they belong. Never packaged or indexed; a published contract shows only a secret's type. A secret read from an env file not marked sensitive is warned about at
telo runand in Studio's Variables tab, and sensitive env files are never synced to Telo Cloud or into an agent's session. - Verified downloads. Import pins, layer digests, revalidated mutable tags, pinned
https-only staged files and checksummed binaries; an archive path escaping its module is refused. - Egress guard.
TELO_EGRESS=public-onlyrefuses module fetches to private and loopback hosts.
Delivery
- No install step.
telo runfetches each import on first use, verified against its pin, and only the parts this machine needs — under--watchtoo, so an import added while editing is fetched on the next reload. An app has no package manifest, dependency directory or lockfile of its own. - One executable.
telo packageproduces a file that runs with no telo and no network, and refuses to write one that would be incomplete. - Run a published app by reference.
telo run oci://…@<version>#sha256-<pin>, verified before anything runs. - No image per change. Runners run a manifest without building an image, and watch sessions reload on edit.
- Offline boot.
telo installwarms.telo/, so the tree boots with no network. - Upgrades your runtime can read.
telo upgradeand the editor's import CodeLenses move pins only to versions your telo can run, and report what they held back. - One version of each module. A module imported at incompatible versions is refused before the app runs.
- Downloads only what's used. Module layers and assets are fetched lazily, for the kinds actually used and this platform only.
Editing and extending
- Write your own modules. When the standard library lacks something, write a module beside your app — controllers in TypeScript or Rust — and import it by relative path. No build step: TypeScript is bundled from source when the app loads and Rust is built from its crate (with
cargoinstalled). Its kinds' schemas, references and contracts are checked like the standard library's, and publishing it later changes only the import'ssource:. Seeexamples/local-modulesfor a TypeScript and a Rust module racing on the same job. - Visual editing. Studio shows Topology, Inventory, Source and Deployment views over the YAML on disk, with edits going both ways.
- Editor and runtime on one version. The editor runs the exact engine of the telo version the module's
requires: telo:selects, and the local runner runs that same release. - Coding agents know the modules. The hub's MCP server gives agents
search_resources,get_kindswith exact pins and schemas, and authoring guides.
Standard library
Secure by default. The standard library's modules ship with their protective settings on: request and read limits enforced, guards and locks that fail closed, SQL parameters always bound, commands run without a shell, secrets and tokens kept out of logs. Each module is a pinned, integrity-checked artifact.
HTTP server
- Request validation.
request.schemachecks body, query, path and headers before the handler runs, converting query and path values to their declared types, with a standard 400 envelope. - Error rendering. Thrown codes are rendered through
catches:on the route, thenHttp.Api, thenHttp.Server, then a built-in 500, witherror.datatyped per code. - Streaming responses.
mode: streamwith an encoder (NDJSON, SSE, plain text): backpressure, a client disconnect cancels the upstream call, and a mid-stream failure is written in-band. - OpenAPI. Built from the mounted routes;
Http.Referenceserves a browsable page offline, listing only the routes the requesting host can reach. - Body limits.
maxBodyBytes(1 MiB default, always on) and multipart limits are enforced while reading. - Auth guards. A mount
guard:runs after CORS and before the body is parsed, and never fails open. - Host routing.
unknownHosts: rejectanswers foreign hosts with 421; forwarded headers and client IP are trusted only when declared. - No silent route shadowing. Duplicate routes and MCP tool names are refused.
- Content negotiation.
Acceptnegotiated per RFC 9110, with406listing what is available.
HTTP client
- Resilient by default. Retries on 408/429/5xx and network failures,
Retry-Afterhonoured, a 10 s default timeout, network errors normalized to one set of codes. - Credential refresh. A 401 refreshes the credential and retries once.
- No empty resends. A streamed body is never re-sent as an empty one.
SQL
- Ambient transactions. Every statement reached through a
Sql.Transactionjoins it at any depth; nested transactions join the outer one; a throw rolls back. - Transactions checked on every path. A statement that requires a transaction is checked on every path the analyzer can see before the app runs, the runtime refusing the rest; a durable wait inside a transaction or lease is refused.
- Declarative schema.
Postgres.Schema/SQLite.Schemabring the database to the declared tables at boot, under an advisory lock, one transaction per batch, touching only objects they declare. - Safe schema changes. Removals wait for
reclaim:, renames go expand-contract (renamedFrom:), and changes it detects as unsafe — narrowing a type,NOT NULLover existing NULLs — are refused before any DDL. - Parameterized by construction.
!sqlbinds every hole as a driver-native parameter. - Typed rows. A declared table projects its row, so seed rows, filters and keys are checked against its columns.
- Portable enums. One declaration becomes a native type on PostgreSQL and a
CHECKon SQLite. - Self-healing connections. Postgres pools probe and replace dead connections;
LISTENre-subscribes after reconnecting.
CRUD
- Models instead of DTOs.
Crud.Modelgives four shapes — read, list, create, update — checked for consistency. - Safe list queries. Only declared filters and sorts are accepted,
limit/sort/cursorare validated, and seek cursors return every row once even while rows are inserted.
User interfaces
- Screens that follow their data, with no frontend code. A
Ui.Datareads a record when its page is drawn — a loading state first, a failure state for a refused read, again on window focus or when a request inside it answers 401 — and a node'sshowncondition decides in the browser what is drawn from the answer, the page's query parameters or a form's values; hidden content keeps what was typed and requests nothing. A sign-in gate is a reader and two conditions. A condition that can never hold for the value it reads, or that reads a value its place does not hold, is refused before the app runs. - Forms with lookups, date ranges and remembered values. A form's fields are kinds: a choice or a picker over options read from your API — searched as the viewer types, paged as the list scrolls, chosen values labelled by one lookup, narrowed by other fields of the form — a date range with presets such as this month or last quarter in the viewer's time zone, and sliders, switches and tags. Fields are grouped into labelled sections, checked against the model as values are typed with the server's refusals shown on the field they name, and kept in the page address or the browser's storage across visits (
state:). A field whose control or options do not fit its property — fromuior from any other module — is refused before the app runs. - Buttons that answer in place, with no double submits. A
Ui.Actiondraws a success as itsresult:tree and any failure — a refusal, a 401, no answer at all — as itsfailure:tree, then reloads every reader and table over the pathsonSuccess.refreshesnames and moves the page whereonSuccess.navigatesays. A form or a row takes no second press while its first is pending,concurrency: serialruns presses one after another, and a table's or a list's row draws the answer of its action — a download link, a status — in place of its button. A row's inputs that cannot make a valid request are refused before the app runs. - Screens in the viewer's language. Give
UiReact.Appatranslator:and alang:read fromAccept-Languageor a cookie, and every page title, label, model title, enumeration label and built-in word is served translated, with plurals and numbers formatted in the browser by the language's rules and a heading over data composed from the values it names ({ text, values }). Each language is one cached document withVarynaming exactly the headers read.
Durable execution
- Crash recovery. Durable runs record each step and decision; a restart resumes where the work stopped.
- Durability that can't break silently. Swallowed suspensions are detected, a replay reaching a different step is refused, and
now()/uuidv4()inside a replayed region are refused before the app runs. - Waiting for days.
Durable.Sleep/Durable.Awaithold no process, connection or timer; a delivery is checked against its type. - Timing that survives restarts. Long backoffs park with their attempt count, deadlines are fixed on the first pass, and missed schedules are found due.
- Exactly-once with the database. With the journal on the business connection, journal writes commit in the same transaction, and each region logs which regime it got (
durable.zone.mode).
Coordination
- Idempotency.
Idempotency.Onceclaims atomically, replays the stored result on repeats and releases on failure; a repeated workflowrunIdattaches to the existing run. - Distributed locks.
Lease.Criticalacquires, runs and releases, expires onttl, fails closed on an empty key and guards release by holder. - Race-free key/value.
putIfAbsent/compareAndSetare atomic, and a lost race isnull, not an exception. - Rate limiting.
RateLimit.GuardandRateLimit.Budgetreturn{ allowed, remaining, retryAfter }over any cache store.
Caching
- Stale-while-revalidate.
Cache.Viewrevalidates in the background with single-flight, or synchronously, and serves stale on error. - Degraded mode. A cache falls back to another store and reports
cache.degraded/cache.recovered.
Scheduling
- Schedules.
Schedule.IntervalandSchedule.Cronwith a timezone and a per-tickwhen:; a failing tick re-arms, ticks never overlap, and shutdown waits for the tick in flight. - Long polling.
Watch.Wait/Watch.Publishwith capped timeouts.
Translation
- Translated texts.
I18n.Catalogholds one language's translations andSelf.t(text, lang, values)formats a text in the language asked for — falling back frompl-PLtoplto the source, with numbers and plural cases by the language's CLDR rules and no ambient locale; a text translated twice in one language across a translator's catalogs is refused before the app runs. - Language negotiation.
I18n.Negotiatoranswers the language to reply in from anAccept-Languagevalue, by quality and RFC 4647 lookup over the catalog languages and a default, skipping malformed ranges instead of failing the request.
Files and streams
- Atomic writes.
Fs.FileWriterenames into place andBlob.Putreplaces whole; an over-size write stores nothing. - Streams without buffering.
Stream.Chunk,Stream.Tapand lazyRun.Iteration; multipart bodies arrive as a stream.
Testing
- Tests run the real app.
App.Instanceruns the application file itself, and tests are manifests run by the kernel you deploy. - Containers on demand. Tests start their own containers — readiness-waited, loopback-only, always removed — unchanged locally, in CI or in a runner session.
- Assert on what happened.
Assert.Eventsmatches kernel events as a subsequence or counts them (times: 0for "never"). - No store mocks. In-memory stores and
:memory:SQLite run the production code path. - Benchmarks in CI.
Benchmark.Suitefails the build onp99thresholds. - Interactive apps.
Channel.SendLine/Channel.ReadUntildrive one step by step, with bounded reads.
Auth and untrusted input
- OAuth.
OAuthClient: PKCE on,isschecked, single-use sign-ins, one refresh per grant at a time, tokens never logged. - Shell commands.
Shell.Commandargs:run with no shell;env: {X: null}unsets an inherited secret. - Untrusted content. Allowlist HTML sanitizing,
Image.Fitsizing before decoding and stripping metadata,Blob.Keyrefusing../x.
AI
- Human approval.
Ai.Agentapproval:gates tool calls. - Bounded agents.
maxStepscaps model calls andmaxToolResultBytescaps each tool result, with an explicit truncation marker. - Concurrent tool calls. Run concurrently, results returned in call order.
- One error set per provider family. Every model provider — and every search, OCR, graph and MCP client — reports one closed set of codes, with
retryAfterSeconds. - Vector dimensions. Enforced at insert and query.
For module authors
- Rules for your kinds. State cross-field and referrer rules as data, checked before the app runs.
- Templated definitions. Checked like any manifest; an error in a forwarded value is reported on the consumer's line.
- Typed node trees. A kind whose schema is a recursive node tree (
$refto a named shape) gets every expression in it typed and evaluated at every depth, a nestedx-telo-contextadding its names to the enclosing one; a condition's schema that can never hold for the value it names (x-telo-match-for) is refused before the app runs. - No bundler config. The kernel bundles TypeScript controllers and
exports.browser:entries;telo publishuses the same builder. - Native controllers. Rust controllers build on load, the kernels that can host a kind are derived from its
controllers:, andnative:files are declared once. - Runtime requirements.
requires: telo:gives a consumer on an older runtime one clear "runtime too old" error instead of errors blamed on their manifest, and ranges are verified at their edges on publish. - Publishing.
telo publishpushes an OCI artifact atmetadata.version; an unpinned import or changed contents at a published version are refused. - Releases.
telo releasedecides bumps from payload digests, propagates them with reasons, writes every version file andCargo.lock, andtelo release addwrites changelog fragments. - Diagnostics tests.
Assert.Manifestasserts the exact diagnostics a manifest produces, and can run it to prove both verdicts agree. - Test selection.
telo changedlists the tests a diff reaches. - Reproducible artifacts. Layer archives are a pure function of their files; the module's
LICENSEand upstream notices ship automatically.