Skip to main content
Amazon SQS

Amazon SQS → the Naive queue primitive

Amazon SQS is a trademark of its respective owner, used here for identification and migration comparison only. No endorsement, partnership, or affiliation is implied.
Amazon SQS is the default durable message queue: create a queue, SendMessage, long-poll with ReceiveMessage, DeleteMessage when you’re done, and get at-least-once delivery with retries and dead-letter queues. It’s battle-tested and the SDK is mature. But SQS is also a raw slice of an AWS account that sits apart from everything else your agent touches:
  • Every queue lives behind IAM credentials (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY or a role), an AWS account, and CloudWatch — disconnected from wherever the agent’s email, cards, secrets, and KYC live.
  • The credential is coarse: a key with sqs:* (or even a scoped policy) is an account-level grant. There is no per-end-user queue — you build tenant isolation yourself with naming conventions and hand-written IAM policies.
  • “Which customer’s agent enqueued that job, and what else can this agent touch?” is answered in IAM + CloudWatch for queues, and in unrelated systems for everything else. The queue has no shared accountability with the rest of the agent’s footprint.
Naive’s queue primitive gives the agent the same capability — a durable, at-least-once work queue (it’s Amazon SQS underneath) — but the queue is rooted in one governed identity:
  • The tenant user that owns the queue is the same user that owns its email inboxes, its cards, its vault secrets, and its KYC.
  • The agent never holds a cloud key. Naive owns the underlying cloud credentials and scopes every queue to your tenant (one queue per resource, namespaced + tagged, with a queue policy locked to your tenant’s task role) — the same model as Compute.
  • Whether an agent may use the queue at all is decided by that user’s Account Kit, and every request is metered against the tenant’s plan — so a leaked agent key can’t quietly run up a bill on your raw AWS account.
This guide maps SQS’s queue model to Naive’s queue primitive, shows the smallest working swap, and is explicit about the SQS features that don’t map yet.
Tested against: the AWS SDK for JavaScript v3 @aws-sdk/client-sqs v3.1080.x (SQSClient, CreateQueueCommand, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand, GetQueueAttributesCommand, PurgeQueueCommand, DeleteQueueCommand; Query/JSON protocol against https://sqs.<region>.amazonaws.com) and the Naive Node SDK @usenaive-sdk/server against the Naive API (base https://api.usenaive.ai/v1, docs snapshot July 2026).Version notes:
  • SQS has two queue types: standard (best-effort ordering, at-least-once) and FIFO (queue name ends .fifo, exactly-once, ordered per MessageGroupId). Naive maps both: create({ type: "standard" }) and create({ type: "fifo" }).
  • SQS dead-letter queues are configured with a RedrivePolicy attribute pointing at a second queue’s ARN and a maxReceiveCount. Naive maps the DLQ with a single flag: create({ dlq: true }) — but maxReceiveCount and redrive config are not exposed. See what doesn’t map yet.
  • SQS identifies a queue by its QueueUrl (per region/account). Naive identifies a queue by its id and resolves the region/account for you.
  • On Naive the queue is per-tenant: naive.queue.* acts as the account’s default agent profile, naive.forUser(id).queue.* is a specific end-user. There is no SQS equivalent — tenant isolation in SQS is naming + IAM you write yourself.

Concept map

Before / after: the core path

The path that matters for almost every queue-backed agent is create a queue, enqueue work, long-poll for it, process, then delete the message. Here it is on both platforms.
The shape is largely the same — create, send, receive, delete — because Naive’s queue is Amazon SQS underneath. The real differences to plan for:
  • No key, no region wrangling. SQS needs IAM credentials and a region; the queue runs on your AWS trust. Naive runs the queue on cloud credentials it owns — the agent holds no cloud key and never sees a QueueUrl.
  • The queue is per-user, not per-account. SQS gives you one account; you hand-build tenant isolation with naming + IAM. On Naive naive.forUser(id).queue is the isolation boundary.
  • ReceiptHandlereceipt_handle, Bodybody. Field casing changes; the semantics (long-poll, visibility timeout, at-least-once, ack-to-delete) are the same SQS semantics.
  • The id is your identity, not a separate account. In SQS the key scopes SQS in one AWS account. In Naive the same forUser(id) handle also owns the agent’s email, cards, vault, and KYC.

FIFO, delay, and dead-letter queues

The other SQS features teams rely on — FIFO ordering, per-message delay, and a dead-letter queue — map directly, with less setup.
  • DLQ is a flag, not a two-queue dance. SQS makes you create the DLQ, read its ARN, and attach a RedrivePolicy. Naive provisions the DLQ when you pass dlq: true and tears it down with the parent on destroy.
  • maxReceiveCount is not exposed on Naive — the redrive threshold is managed. If you tune it in SQS, note that as a gap.

Minimal viable migration

The smallest swap that keeps a working queue-backed agent alive is create → send → receive → ack.
1

Install the SDK and set your key

Set NAIVE_API_KEY (a server-side key from the dashboard). You can drop AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY and the region config for this path.
2

Pick the identity that owns the queue

Use naive.queue for the account’s default agent profile, or naive.forUser(id).queue to scope the queue to a specific end-user. There is no QueueUrl and no region — Naive resolves them.
3

Swap queue creation

Replace CreateQueueCommand({ QueueName }) with naive.queue.create({ name }). For FIFO pass type: "fifo"; for a dead-letter queue pass dlq: true (no separate DLQ + RedrivePolicy). Keep the returned id in place of the QueueUrl.
4

Swap send

Map SendMessageCommand({ QueueUrl, MessageBody })naive.queue.send(id, body). Move MessageGroupId / MessageDeduplicationId / DelaySeconds to the { group_id, dedup_id, delay_seconds } options object.
5

Swap receive + ack

Map ReceiveMessageCommand({ MaxNumberOfMessages, WaitTimeSeconds })naive.queue.receive(id, { max, wait }), then read m.body / m.receipt_handle (lower-case). Map DeleteMessageCommand({ ReceiptHandle })naive.queue.ack(id, receipt_handle).
6

Ship it

At this point you are off SQS for the core create → send → receive → ack path. Everything below is upside, not a requirement.

Consolidate further once you’re on Naive

This is where the migration pays for itself. In SQS, the IAM credential scopes queues in one AWS account and nothing else, and you build per-tenant isolation with naming conventions and policies. On Naive, the unit of isolation is a tenant user, and the queue is one of many primitives that identity owns.

Gain #1 — one identity across primitives

  • With SQS, the agent’s queues are a slice of one AWS account, and per-tenant isolation is a naming convention plus IAM policy you write and audit yourself. With Naive, naive.forUser(acme.id) is a single handle to queue and email and cards and vault and KYC.
  • Each queue is namespaced + tagged and locked to that tenant’s task role automatically — sibling tenants can never read each other’s queues, secrets, cards, or logs. Tear the customer down from one place.

Gain #2 — execution-time governance

  • Whether an agent may use the queue at all is policy on the identity — not an IAM policy you maintain in a second console. Toggle the queue primitive per user in the Account Kit, and every request is metered against the tenant’s plan quota.
  • The agent’s code path stays the same for every tier — client.queue.send(...), client.queue.receive(...). What changes at execution time:
    • Disabled primitive → the call is refused. If the Account Kit doesn’t grant queue, the request never reaches AWS.
    • Quota exceeded → 429. Once usage crosses the plan’s queue quota for the period, the call is rejected with a rate_limited error — no surprise AWS bill from a runaway agent.
    • The agent holds no cloud key. Naive owns the AWS credentials; a leaked agent key can’t enumerate or drain queues on your raw AWS account.
Unlike money-moving primitives (cards, trading), enqueuing a message is not approval-gated — there’s no human-in-the-loop pause on send. Queue governance is enable/disable + plan quota + unified logs, enforced at execution time. If you need per-action human approval, that lives on the sensitive primitives, not the queue.

Gain #3 — unified accountability

  • Every queue create, send, receive, and purge for a customer lands in one per-user activity log — alongside their email, card, vault, and KYC events, not in a separate CloudWatch namespace:
  • That is the question that is hard to answer when queues live in SQS/CloudWatch, secrets live in your own manager, cards live in Stripe, and email lives somewhere else. Under Naive it is a single query.

What does not map yet

A migration guide that hides gaps is worse than none. The core path (create → send → receive → ack), FIFO, per-message delay, dead-letter queues, purge, attributes, and long-polling map cleanly — the queue is Amazon SQS underneath. But the following SQS capabilities have no direct equivalent on Naive’s queue primitive today. Check this list against your app before you commit.
If your agent depends on SQS MessageAttributes, batch send/delete, per-message ChangeMessageVisibility, or fine-grained queue attribute / redrive tuning, those are the gaps most likely to need a workaround — Naive’s queue is a curated, per-tenant SQS surface (create / send / receive / ack / purge / attributes), not the full SQS control plane. The core producer/consumer path, FIFO, delay, and DLQ map directly. The flip side is the gain: the queue, its credentials, and its isolation are governed by the same identity and Account Kit as the rest of the agent — with per-plan quota and a unified audit trail — not a slice of an AWS account behind an all-or-nothing IAM key.

Where to go next

  • queue primitive — standard / FIFO, DLQ, send / receive / ack, worker pattern
  • queue SDK sub-client — typed method signatures
  • queue CLI — create / send / receive / ack / attributes
  • Compute — pair a service worker that long-polls the queue for a cheap autoscaling consumer
  • Customer billing — the plan + quota that meters queue usage at execution time
  • Account Kits — the policy model that enables/disables the queue per user
  • Tenant users — the identity that owns the queue, compute, email, cards, and vault