Planned features
Everything here is Planned. Nothing is built. If time runs short, features are cut and shipped later rather than delaying the rest.
Queue and workers 16 planned
- Many tickets at once on local or remote workers, with the same rules in both modes.
- Retries with exponential backoff and jitter. Defaults: 3 attempts, 2 s base, 60 s cap. All configurable.
- Four timeouts for hung calls: per step, no-progress, whole-ticket maximum, and worker lease.
- Cancel that stops at the next step boundary and keeps partial results, or a hard kill if you choose.
- Duplicate protection: submitting the same key twice returns the first ticket.
- Poison protection: a ticket that keeps killing its worker is stopped and flagged as suspected. A server restart or a stalled writer is not counted as a lost lease.
- Bulk submit: one call with a list of tickets, with a result per item (created, duplicate or refused with the reason) or all-or-nothing if you choose, and a cost preview for the batch (planned, M1 and M2).
- Guarded bulk retry after an outage: list first, a maximum per call, one linked retry per ticket (planned, M1).
- Dashboard users are made from the command line, with a one-time first password and a login attempt limit (planned, M5).
- Priority: a whole number, higher runs first, default 0, with an allowed range you configure. When tickets are eligible together, the highest priority goes first, then the oldest, then the lowest id, so the order is the same every time (planned, M1).
- Named queues: a ticket is in exactly one queue (default: default). A worker lists the queues it serves and serves only those (planned, M1).
- Queue pause and drain: pausing stops new claims while running tickets finish; draining also refuses new submits with a reason. Both are operator actions and leave an audit record (planned, M1).
- An optional limit on queued tickets, per queue and per user. Default: none. Over the limit a submit gets a 429 with a retry hint (planned, M1).
- Worker started, stopped and lost events.
- A correlation id on every ticket, event, log line and webhook to follow one piece of work.
- Approvals: a step or a policy can pause a ticket until a person approves or rejects it.
Saved steps 7 planned
- Each step is saved when it finishes. Retries and crashes resume after the last saved step.
- A step with the same name and different input fails loudly instead of reusing a wrong result.
- Optional maximum age for saved results, so stale data is not reused.
- A code-version warning when saved steps came from different code.
- Workflow versions are pinned per ticket: if a ticket with a saved side effect meets a different version, it pauses with a reason instead of continuing.
- Optional encryption of marked fields with your own key.
- Content is purged after 30 days by default; small metadata is kept longer. Both configurable.
Spend caps and providers 20 planned
- Caps in tokens, dollars or counts, per ticket, user, plan, app and queue. Dollars use a small bundled, dated price table that you can override; qlane warns when it is stale.
- A cap that cannot be checked refuses the call (fail-closed, configurable). The cap is per ticket or step with partial results kept, not per API key, and works alongside a gateway such as LiteLLM or Portkey.
- A call that had started when its worker died stays charged against the cap until its step settles or a person releases it, so caps can count a little too much after a crash.
- Observe mode: a cap that never blocks and records what it would have blocked. One alert when a cap passes a set percent of its limit.
- A per-provider circuit breaker: when too many retryable calls to one provider fail, new calls wait or use the fallback list, then a few trial calls test it. One alert on trip and one on recovery. Out of quota stays a separate class.
- Per-step cost, tokens and saved-or-paid status in every step list, avoided cost on reused steps, a stats command, a JSON run report and usage events per ticket as CSV.
- A ticket that hits a cap stops, keeps partial results and ends in its own state.
- Submit-time rejection when a ticket would exceed its cap.
- Ordered provider fallback that does not use up a retry attempt.
- Escalation: try a cheaper model first and move to a stronger one when the step's output check fails. It does not use up an attempt, every try's cost counts against the cap, and the most escalation steps per step is a setting (planned, M2).
- Every model call records a hash of the exact prompt (system prompt, messages and parameters, without secrets) and an optional prompt version you set. Both show in the step history, the terminal view and the dashboard. They only record; they never block a ticket (planned, M2).
- Support planned for Anthropic, OpenAI-compatible services, Google Gemini and local models.
- One layer owns retries for model calls (SDK retries off in qlane adapters), and every try is recorded and counted.
- The provider request id is saved with each try, to match invoices, and shown in the step history.
- A dropped stream costs money: the tokens that arrived count as spent against the cap before the retry runs, so a retry is never free.
- Out of quota is its own error class, not a rate limit. It is never retried, so there is no retry storm. Each provider gets a table of quota codes you can configure.
- With no fallback left, a ticket stopped by a provider out of quota ends failed by default, or paused if you set that per provider or project. One alert per provider until its next successful call; every ticket still records its own stop.
- Optional result cache across tickets, off by default and set per step. The key is the prompt, the parameters and the exact model version you pin in the step. A step that uses a model alias with the cache on is refused unless a version is pinned, so a moved alias cannot serve an old answer. An answer is saved only if the provider returns a version string equal to the pinned one. Scope is per user unless you change it, and a maximum age is required when it is on.
- A cache hit is marked "cache hit", costs nothing against the cap and shows the saved cost. It is never used for effect steps, truncated, failed-check, dropped-stream, error or quota answers, or answers past the maximum age.
- Honest limit of the cache: above temperature 0 a hit returns an earlier answer, so behavior can differ from a fresh call. It is qlane's own cache, not the provider's prompt-caching discount.
Async provider jobs 4 planned
- A step type for long provider jobs such as video or audio: submit, then poll by the saved job id.
- The job id is saved before polling, so a dropped connection never submits the job again.
- Waiting between polls does not hold a worker or count as running time.
- A maximum wait and a maximum poll count end a stuck job with a clear reason.
Tickets that start tickets 11 planned
- A workflow step can create another ticket (ctx.submit) and get its id back at once, with a "caused by" link to the parent. Planned for M1.
- A retried parent never creates a second child: the default key is the origin ticket id (the first ticket of a retry chain), step name and call index, so a retry finds the existing children.
- Children inherit the parent's user, plan, app and queue scopes, count against the same caps, and have their own per-ticket cap.
- Plain children are independent: cancelling the parent never cascades to them.
- A loop guard refuses chains that are too deep or repeat the same work, and every ticket carries a correlation id. Planned for M1.
- Optional wait-for-all dependencies ({dependency: true}): the parent is paused with the reason waiting for children, holds no worker or slot, and resumes with the children's results. Planned for M3, and the first thing cut if M3 slips.
- A list of children for fan-out and fan-in. In M3 the wait is for all children; wait-for-any is a separate, later milestone (M3b, below).
- If a dependency child fails, the parent waits for all children to be final, then fails with the first failed child's error. A continue option resumes with each child's final state instead.
- Cancelling a parent, or a parent ending dead at its maximum wait, cancels its unfinished dependency children.
- A configurable maximum number of children per parent step and per parent ticket; a submit over the limit is refused.
- Generic links between tickets (retry of, related, caused by) for navigation, with no effect on behavior.
Parallel steps, wait-for-any and dependent tickets (milestone M3b) 8 planned
- Run named branches at the same time inside one ticket (ctx.parallel). Each branch is a saved step with its own memo and cost record, and the result is a list in branch order. Planned for M3b.
- If a branch fails, you choose per call. The default lets the other branches finish, then fails the step with the first error; finished branches stay saved, so a retry does not redo them.
- Effect steps may run inside branches, each with its own ledger record. One ticket can then list several unknown outcomes, and each is resolved on its own.
- A limit on the number of branches and a cap on how many run at once (values not set yet). Each branch counts against the same spend caps and reserves budget like any step; the ticket still holds one lease.
- A branch may wait for a provider job, wait for child tickets, or run a nested parallel step (up to a configurable depth, default 2, a proposed default). A parked branch holds no slot and no lease; the ticket keeps its lease while any branch is active and parks only when every unfinished branch is parked, with a list of reasons when they wait for different things. A branch waiting out a retry backoff counts as parked. A parked branch that reaches a maximum wait fails on its own and the failure option you chose decides about the other branches; its reserved budget is released.
- Wait-for-any: a parent resumes when the first child finishes successfully. By default the other children are cancelled and keep their partial results; you can let them finish instead. If the first child fails, the parent keeps waiting by default.
- A client can submit a ticket that depends on other tickets in the same project. A missing id or a cycle is refused and nothing is created. While it waits it holds no worker slot; by default it fails if a dependency fails, or you can let it run anyway.
- The whole milestone is the first thing cut if time runs out; wait-for-all in M3 does not depend on it.
Dependency graph view and editor (milestone M5) 4 planned
- A graph in the web dashboard of parents, children and dependencies, with the state of each ticket. Planned for M5.
- An editor to add or remove dependencies on scheduled tickets and on tickets waiting for dependencies (operator role only, cycles refused, every change recorded). Queued, running and finished tickets are read-only.
- Start a new ticket graph from a saved workflow template. Templates are written in your code and registered at startup and stored by the server in the project database; the dashboard lists them with their old versions, only starts runs, shows a form for the template's input and records which template version each run used. A changed declaration under the same version keeps the stored one, and an admin deletes an unused old version on request.
- This is the first thing cut from milestone M5 if M5 slips, and it is not a no-code builder.
Scheduling 13 planned
- Delayed tickets, recurring (cron) tickets, expiry deadlines and off-peak start windows.
- Cron in UTC by default, with an optional time zone per schedule. Standard 5-field cron, or an every-N-minutes or every-N-hours shorthand; no seconds field.
- Schedules are defined in your code and registered by name at startup. The dashboard lists them and can disable one, and a disabled schedule stays disabled after a restart. A run that falls while a schedule is disabled is skipped and raises one missing-run alert for it, with the case disabled.
- If a schedule fires while its previous run is still going, the new run is skipped and an event is written; you can allow overlap instead. A skip because of overlap is not a missing run.
- Daylight saving: a run in a skipped hour happens once at the next valid time; a run in a repeated hour happens once, at the first occurrence. Never twice.
- A schedule fires once even if two instances or a duplicate tick race for the same time.
- Several start windows per queue for off-peak batch work, each with its own concurrency limit (planned, M4). Overlapping windows are refused when saved, unless you choose to allow overlap with the higher or the lower limit.
- The scheduler promotes due tickets on a tick, never early, and late by at most a tick interval you configure. The dashboard shows the lag.
- Missed runs are skipped unless you ask for one catch-up run.
- External schedules: for jobs started by cron or launchd, qlane expects a ticket or receipt with a given name by a deadline and raises the missing-run alert if none comes.
- Missing-run alert: if an expected run never happened, failed, or finished with no receipt, you get one alert.
- Receipts: a short note of what a step produced, such as "sent 12 emails".
- Expected-output check on every step by default (produced something), with a custom check or an off switch, and a receipt of what the step produced.
Fair scheduler 4 planned
- Weighted fair share by default (it picks the group, and priority orders tickets inside the group), with priority lanes and first-in-first-out with aging also planned.
- Counts tokens (or dollars or steps) against provider rate limits and local model slots.
- Learns limits from provider headers and rate-limit responses.
- Optional cap on how many tickets one user or queue may run at once, separate from spend caps. Default: none.
Side effects 6 planned
- An effect ledger: intent before, outcome after, for emails, charges and writes.
- Unknown outcome state when a crash lands in between, resolved by a check or a person. A probe is retried on a timer first, and if it still cannot tell, the ticket stays in unknown outcome with an alert. One alert is raised if the ticket is still unresolved after an hour (a proposed default).
- Helpers for HTTP calls with idempotency keys, email, database writes and agent tool calls.
- The default key uses the origin ticket id, so a retried ticket sends the same key for the same effect and cannot silently repeat it.
- Compensation on request: a step can declare a hook that undoes one completed effect, run only when a person or an API call asks. It is itself an effect with its own record, and it can be wrong.
- The code can save a short progress note while a call runs, and the check for an unknown outcome receives the last one.
Testing 7 planned
- A seeded crash-test harness that kills processes, breaks providers, delays and duplicates calls and injects clock and database faults.
- A fake model-provider server with seeded rate limits, slow replies, dropped streams and truncation.
- Failing seeds become regression tests.
- Two tiers: a fast in-memory simulator for every push, and a smaller suite on the real SQLite file with real kill -9, including a full disk.
- A one-command crash demo (planned right after M1 and again after M3): it kills a worker mid-ticket, restarts it and prints what was reused and what was sent once, with no API key.
- The fake model provider is planned as its own small package with a recipe for running it against your own code.
- Harness tests for the cost rules: a dropped stream is counted as spent, a quota answer gives one alert per provider and no retries, the cache never serves an effect step, and one user's cached answer is never returned to another.
Open source and no telemetry (milestone M6) 2 planned
- Planned under the Apache-2.0 license, with DCO sign-off and no CLA. Planned for M6.
- No telemetry: the software sends nothing anywhere. Planned for M6.
Operating it 8 planned
- Embedded mode (a library over one SQLite file) and server mode (HTTP API, workers, API keys with roles).
- A command-line tool, including ticket history per step, workers, backup and the harness; a doctor command that checks a setup; starter templates for three common job shapes; and a view of the active limits and current usage.
- Safer upgrades: each project migrates on its own, the backup can go to another disk and is refused if there is no room, and a full disk stops writes cleanly and recovers by itself.
- A web dashboard: tickets, spend, scheduler, unknown-outcome inbox, workers and crash-test runs.
- Logs with redaction, a metrics endpoint and tracing.
- Webhooks signed with HMAC, at-least-once with event ids to dedupe.
- Importer for BullMQ queue settings and jobs. Flows are reported as unsupported.
- Docker image and single binary for Linux and macOS.
What qlane does not do: guardrails and prompt-injection defense, access control for your tools, retrieval, evals and prompt testing, a chat UI, and a model gateway or proxy. Use your own or a third-party tool for those. qlane limits what its own tickets spend and records what its own effects did; it does not stop a model from being tricked into asking for a tool call. See the plain list.