An agent that answers a user and an agent that spends minutes consuming tasks do not have the same execution contract. Putting both behind an HTTP route can work in a prototype and create an operational problem as soon as the loop must survive the end of the request.

This article compares the three Cloud Run resources for that decision: Service, Job, and worker pool. The question is not which name sounds newer. It is how work arrives, when it ends, where state lives, and what evidence lets you recover an interrupted execution.

Diagram compares an AI agent using an HTTP service, a Job, and a worker pool with a queue.

Decision result

  • Use a Service when the agent needs an endpoint, streaming, or a response to a request.
  • Use a Job when each execution has a clear beginning, end, and input set.
  • Use a worker pool when the container should continuously consume a queue without serving HTTP.
  • In every option, persist state and evidence outside the agent process.

The first question is whether the work has an endpoint

The What is Cloud Run documentation separates Service, Job, and worker pool by execution model. The first decision is straightforward: if a client must call the agent and receive a response, start with Service; if work is pulled from a queue without an endpoint, evaluate a worker pool.

This distinction avoids a common mistake in agentic applications. The agent starts inside a request, calls tools, waits for an external response, and continues reasoning. If progress depends on local memory or an open HTTP connection, a restart can leave the task without an owner.

The agent should return only what belongs to the interaction. The rest should become a task with an identifier, state, and evidence. The blog's multi-agent orchestration with TypeScript applies the same separation when work no longer fits inside a conversation. The Cloud Run introduction covers the platform model before this decision.

What changes between Service, Job, and worker pool?

The Google Cloud document describes Service as a resource for HTTPS endpoints and events, Job as a task execution that ends, and worker pool as continuous pull-based processing such as Pub/Sub, Kafka, or RabbitMQ consumers (Google Cloud, "What is Cloud Run"). The table turns that distinction into an operational decision.

Resource Main input Expected end Operational state Agent choice
Service HTTP request or event Response or event delivery State outside the instance Chat, API, streaming, and task dispatch
Job Manual run, schedule, or workflow Task completion Result persisted per execution Evals, batches, migrations, and finite work
Worker pool Queue consumed by the container Continuous work Task state and checkpoint Long-running agentic consumer

A worker pool is not a slower version of a Service. It has no load-balanced endpoint and does not autoscale by itself. The team must decide how many instances remain active and, if needed, build an autoscaler from queue metrics, as described in the worker pool deployment documentation.

That operational cost can be correct when the queue is the boundary you actually need. A Service can scale with requests, a Job can wait for another execution, while a worker pool still needs an explicit policy when the queue is empty. The choice should make that behavior visible.

Citation capsule: On Cloud Run, a Service handles HTTP and events, a Job runs tasks to completion, and a worker pool processes continuous work without an endpoint. The choice defines how the agent receives work, how the system ends an execution, and who controls scaling, state, retry, and recovery.

When should an agent stay in a Cloud Run Service?

The Host AI agents on Cloud Run documentation lists HTTP streaming and WebSockets among the useful features for agent interactions. Service is the best fit when the client needs to follow a response, query task state, or start an execution through an authenticated API.

The safer pattern is to separate the request from the execution. The route validates input, creates durable state, and publishes a message. It can return the task identifier without keeping the entire agent loop inside the user connection.

type CreateTask = {
  taskId: string;
  prompt: string;
  repository: string;
};

async function createTask(input: CreateTask) {
  await taskStore.create({
    id: input.taskId,
    state: "queued",
    repository: input.repository,
    prompt: input.prompt,
  });

  await queue.publish({ taskId: input.taskId });

  return { taskId: input.taskId, state: "queued" };
}

This snippet does not turn the agent into a worker pool. It defines a clean Service boundary: receive, validate, persist, and forward. A separate consumer owns the loop and updates the task. If the agent needs streaming, the Service can follow persisted events without trusting instance memory.

Use Service when the agent acts as a tool API or remote MCP server. Confirm the transport and authentication first. The MCP on Cloud Run documentation supports Streamable HTTP, but not stdio for a server running in the cloud.

When is a Cloud Run Job the cleaner option?

A Job fits the model described in Execute jobs: an execution can receive arguments, environment variables, task count, and a task timeout. Use it when the agent receives a known batch, performs the work, and exits without listening to a queue.

An agent eval is a clear example. The system receives a review, a set of cases, and a prompt or code version. The Job runs the cases, stores the results, and ends. A new execution can use a different input set without leaving a permanent process waiting for work.

gcloud run jobs execute agent-eval \
  --region=REGION \
  --update-env-vars=EVAL_RUN_ID=RUN_ID,INPUT_URI=INPUT_URI \
  --wait

The command starts a Job execution and waits for completion. Replace the example names with values from your environment and grant only the identity that needs to execute the resource. When duration or inputs vary by run, use the documented execution overrides and store the configuration with the result.

Do not use a Job to pretend that a continuous queue exists. A scheduler or another service must start every execution, and the agent must know whether a task already finished. For a queue that receives work continuously, that extra layer is a signal to evaluate a worker pool.

When does a worker pool justify its operational cost?

The Cloud Run worker pool deployment documentation describes worker pools as an option for continuous background work without a load-balanced endpoint or automatic autoscaling. Choose one when the container should pull from a queue and keep processing while tasks are available.

Diagram shows a queue, durable state, logs, and retry as proof of an agentic loop.

The worker loop should not depend on "continuing to think" inside a single request. Each message must represent a unit that can be claimed, processed, acknowledged, or returned for retry.

async function consumeForever() {
  for await (const message of queue.pull()) {
    const lease = await taskStore.claim(message.taskId);

    if (!lease) continue;

    try {
      const result = await runAgent({
        taskId: message.taskId,
        checkpoint: lease.checkpoint,
      });

      await taskStore.complete(message.taskId, {
        result: result.summary,
        evidence: result.evidence,
      });
      await queue.ack(message);
    } catch (error) {
      await taskStore.failAttempt(message.taskId, serializeError(error));
      await queue.nack(message);
    }
  }
}

The code is an execution contract, not a queue SDK. claim must prevent simultaneous reservations, checkpoint must survive a restart, and complete must be idempotent. Without those properties, an agent retry can duplicate a change or mark a task complete after producing only text.

The resource does not scale on its own. Google Cloud says worker pools need active instances and that a team can build its own autoscaler to adjust capacity with demand. Queue depth therefore becomes part of the system, not a detail hidden in the container.

What must the deployment protect?

The deployment documentation requires separate permissions to develop the worker pool, use its service account, and read the image from Artifact Registry (Deploy worker pools to Cloud Run). The first barrier is to give the agent no broader identity than the work requires.

Diagram shows an agent passing through IAM before accessing a queue, tools, and logs.

Configure a dedicated service account for the worker. If the agent only reads a queue, grant the consumption permission and the required data reads. If it writes to a repository, database, or deployment system, put that action behind a narrow tool with separate authorization. Do not give the agent administrative access for convenience.

Keep secrets and context separate. Environment variables can configure the process, but the prompt should not receive credentials. The task should load only the context needed for the next step and store logs, checkpoints, and evidence in an appropriate system.

When I need continuity between Codex and Claude Code sessions without sending the same context again, I use RemoteCode to take agentic workflows further with less repeated token context. It is the author's own tool, mentioned here because the context budget is part of loop architecture, not because it replaces IAM, state, or verification.

The blog's agent observability in CI helps define the events that must leave the worker: task received, claim, tool call, checkpoint, error, retry, and completion. Logs without a task identifier are difficult to use when several instances work in parallel.

How do you verify that the choice works?

The official worker pool quickstart checks the result through container logs, while the Cloud Run documentation points to Cloud Logging and Error Reporting for operations. Verification must prove more than deployment: it should show that the message was processed and state can be recovered.

Run a small test with a disposable task and check this sequence:

  1. The Service or producer creates a task with a unique identifier.
  2. The queue delivers the message to the worker.
  3. Storage records the claim and checkpoint.
  4. The agent produces short evidence; a summary alone is not enough.
  5. State changes to complete and the message is acknowledged.
  6. A failure between checkpoint and acknowledgment allows a retry without duplicating the effect.

Then interrupt an execution at a known point. The worker should restart, find the incomplete task, and continue from explicit state. If the only proof is a log from an instance that died, the architecture is not ready.

Use gcloud run worker-pools list to verify that the resource exists and query logs by task identifier. For Jobs, store the execution identifier and its results. For Services, test the endpoint and the asynchronous path separately. The blog's TypeScript tool-call validation covers the same discipline at the boundary between agent and code.

Citation capsule: A long-running agent is operationally verified only when a task has an identifier, the worker records a checkpoint, the output contains evidence, and a retry can continue without duplicating the effect. A successful deployment proves that the container started. It does not prove that the loop survives failure, redelivery, or restart.

Common mistakes and limits of the comparison

The first mistake is choosing a worker pool because the agent seems autonomous. Model autonomy does not define the execution contract. If there is a clear HTTP request and a short response, Service is simpler. If there is finite input, Job reduces the operational surface.

The second is assuming that a worker pool includes automatic autoscaling. The current documentation says otherwise. You must define instances, metrics, scale-up and scale-down policy, and behavior when the queue is empty. If you do not want to maintain that mechanism, use Service, Pub/Sub push, Cloud Tasks, or Jobs in a flow that matches the work.

The third is storing state on local disk or in memory. Instances are disposable, and a new revision can replace an old one. Save progress in a database, queue, object store, or another system that recovery can read.

There is also a product limit. Worker pools are a newer capability, and the documentation can change while commands and integrations mature. Pin the image revision, test the configuration in a disposable project, and confirm region, permissions, and availability terms before connecting the resource to a critical operation.

Frequently asked questions about AI agents on Cloud Run

Does a long-running AI agent need a worker pool?

No. A Service can start a task and return an identifier, while a Job can run a batch to completion. A worker pool fits when the container should continuously consume a queue without an endpoint. The decision depends on how work arrives and how recovery works, not on prompt duration alone.

Does a Cloud Run worker pool have a public URL?

No. The worker pool documentation describes the resource without a load-balanced endpoint. That reduces the HTTP surface, but requires another way to deliver work, such as a pull queue. The agent still needs service identity, narrow permissions, logs, and a way to update task state.

When is a Cloud Run Job better than a worker pool?

A Job is better when input can be defined at the start and execution should end. Evals, batches, and migrations are examples. A worker pool is better when the process should keep consuming a queue. If a scheduler must launch Jobs continuously just to imitate a consumer, reassess the design.

How can you stop an agent retry from duplicating effects?

Use an idempotent task identifier, claim the message with a lease, and write a checkpoint before repeating a call that creates an effect. Acknowledgment should follow durable state. For write tools, include an idempotency key or an approval step before an irreversible operation.

Sources consulted