NOIR
Core concepts

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

  1. A channel adapter verifies the provider request and returns normalized events.
  2. agent.fetch() routes each event by the adapter's defaultPath.
  3. The runtime deduplicates the event and derives a conversation key.
  4. A worker claims the event with a renewable lease.
  5. Work for the same conversation is serialized.
  6. Memory and prompt modules assemble context when the model loop is active.
  7. The model produces content or tool calls within configured limits.
  8. Tools validate input, apply policy, execute, validate output, and project model-visible results.
  9. Final output is queued before channel delivery.
  10. 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.

On this page