Execution model
Follow a message from signed ingress through serialized work, model steps, checkpoints, and durable delivery.
Noir separates accepting work from executing work. A channel webhook should be verified, normalized, and enqueued quickly; model execution and external effects happen after acceptance.
Turn lifecycle
- A channel adapter verifies the provider request and returns normalized events.
agent.fetch()routes each event by the adapter'sdefaultPath.- The runtime deduplicates the event and derives a conversation key.
- A worker claims the event with a renewable lease.
- Work for the same conversation is serialized.
- Memory and prompt modules assemble context when the model loop is active.
- The model produces content or tool calls within configured limits.
- Tools validate input, apply policy, execute, validate output, and project model-visible results.
- Final output is queued before channel delivery.
- Delivery is claimed, retried, and settled independently.
Stable identifiers
The runtime assigns a stable execution ID to a turn. ToolContext.idempotencyKey is stable for a particular tool call. Handler code can derive keys from noir.executionId and a stable step name.
const result = await noir.run('create-invoice', () =>
billing.createInvoice({
customerId,
idempotencyKey: `${noir.executionId}:create-invoice`,
}),
)noir.run stores JSON output after success and reuses it on replay. It cannot make an arbitrary external API idempotent: the API must also receive the stable key.
Leases and retries
Inbound and outbound claims include a worker ID, current time, and lease duration. A worker renews long-running claims. On a retryable error, the record becomes available at a later timestamp; on completion, the owning worker settles it.
Tools declare retry semantics independently:
safe: repeating the call has no externally visible mutation.idempotent: repeating is safe only because the call uses a stable provider key.never: Noir must not replay the effect automatically.
Bounds
limits.maxSteps bounds the assistant loop. runTimeoutMs bounds the turn, deliveryTimeoutMs bounds channel output, historyMessages bounds context, and toolResultBytes prevents a large provider response from taking over the prompt. Tool-level maxOutputBytes can be stricter.
Shutdown
agent.close() stops polling and closes channels, connectors, capabilities, the store, and the model adapter when they expose lifecycle hooks. Construction does not perform I/O, which keeps imports safe in build and inspection environments.