Identifiers
Public IDs, keys, names, and what each one is for.
Runlane's public identity values are opaque strings. Application code writes them as plain strings at authoring boundaries; Runlane validates and brands them before durable records, leases, schedules, and delivery messages are written.
Shared rules:
- Values must be non-empty strings.
- Values must not contain
:. - Treat values as whole identifiers, not parseable prefix paths.
- Use queues to route work; do not use worker IDs as routing targets.
: is reserved so storage adapters can compose their own backend-internal keys without colliding with public Runlane ids. Use dots, underscores, or a hashed segment for application structure:
| Need | Good | Rejected |
|---|---|---|
| Task id | emails.welcome | emails:welcome |
| Idempotency key | emails.welcome.user_123 | emails:welcome:user_123 |
| Singleton key from external id | quickbooks_invoices_user_123 | quickbooks:invoices:user_123 |
| Dynamic unsafe external id | provider_account_${hash(accountId)} | Raw value that may contain : or unbounded text |
Public Identity Values
Ideal owner names who should supply or manage the value in normal application code. Durable records and operator APIs may still expose values owned by Runlane.
| Value | Ideal owner | What it names | What it is for |
|---|---|---|---|
| Task ID | User/app author | A stable task definition, such as emails.welcome | Selects the registered handler and schema. Do not put tenant, user, or resource IDs here. |
| Run ID | Runlane by default; user/operator only for external correlation | One durable execution record | Names a specific run for operator reads or external correlation. |
| Schedule ID | User/app author | A registered schedule definition | Deduplicates and audits schedule materialization. Use one stable ID per logical schedule. |
| Queue name | User/app deployment config | A logical routing lane such as default, emails, or media | Serializes a provider-neutral queue definition into durable records and transport envelopes. |
| Idempotency key | User/app producer or task definition | One logical trigger request for one task/environment | Returns the original active or retained terminal run for duplicate producer calls. |
| Singleton key | User/app producer or task definition | One active resource that must not overlap | Prevents concurrent active runs for the same resource. |
| Concurrency key | User/app producer or task definition | One bounded-queue capacity partition | Limits execution concurrency per tenant, account, or resource without deduplicating trigger requests. |
| Worker ID | Runlane by default; user config only for diagnostics | The process or invocation that claimed work | Records diagnostic ownership for leases, schedule claims, and outbox claims. It does not route work. |
Common authoring locations include:
- Task and schedule identity:
task({ id })andtask({ schedule: { id } }). - Queue routing:
queue({ name }),task({ queue }), trigger/runNow queue options, and worker queue filters. - Run creation keys: static
task({ idempotencyKey, singletonKey, concurrencyKey })values, task key resolvers, and explicittrigger()orrunNow()options. - Run and worker identity:
trigger(..., { runId }),runNow(..., { runId, workerId }),createRunlane({ workerId }),worker({ workerId }),executeNext({ workerId }), andexecuteDelivery(..., { workerId }).
environment.name is also public, but it is a namespace rather than a Runlane ID. It scopes durable records so development, staging, production, tenants, or test suites do not see each other's runs.
Choosing The Right Value
When deciding where a value belongs:
- If it names the kind of work, use the task ID.
- If it chooses which worker pool should process the work, use a queue definition.
- If it deduplicates a producer retry or returns a retained terminal owner, use the idempotency key. Repeat triggers with the same retained idempotency key return the original run.
- If it prevents overlap for one resource, use the singleton key.
- If every request should become a run but execution must be limited by partition, use the concurrency key on a bounded queue.
- If it identifies one execution record, use the run ID.
- If it identifies recurring materialization, use the schedule ID.
- If it identifies who claimed work, use the worker ID and keep it out of producer routing logic.
Runlane idempotency keys with a finite idempotencyKeyTTL are retained after successful or cancelled terminal completion, released immediately after failure, and released at any terminal state when the TTL is 'active'. If a public API must reject payload mismatches or replay response bodies, keep that request ledger in the application boundary and trigger Runlane from the accepted request record.
Authoring And Validation
Public authoring APIs accept strings in place:
const emailQueue = queue({ name: 'emails', default: true })
const sendWelcomeEmail = task({
id: 'emails.welcome',
queue: emailQueue,
schema,
idempotencyKey: (payload) => `emails.welcome.${payload.userId}`,
async run(payload) {
await emailProvider.send(payload.userId)
},
})
const runlane = createRunlane({
lane,
queues: [emailQueue],
tasks: { sendWelcomeEmail },
})
await runlane.trigger(runlane.tasks.sendWelcomeEmail, { userId: 'user_123' })
await runlane.worker({ queues: [emailQueue] }).closedLiteral task IDs, static task keys, and task-colocated schedule IDs get TypeScript checks where possible. Queue names, explicit run IDs, worker IDs, and dynamic key resolver results still go through runtime validation because TypeScript cannot prove values from payloads, environment variables, HTTP requests, or databases are safe.