# AX: Google's Open Agentic Orchestrator Explained — Building Production AI Agent Workflows

> Source: <https://dev.to/rawas_aditya/ax-googles-open-agentic-orchestrator-explained-building-production-ai-agent-workflows-4710>
> Published: 2026-09-21 08:15:06+00:00

*Originally published at [adityarawas.in](https://adityarawas.in/blog/ax-googles-open-agentic-orchestrator-explained-building-production-ai-agent-workflows)*

Every framework promising to "orchestrate agents" eventually collapses into the same three problems: state management across long-running tasks, tool-call reliability, and observability when something silently breaks at 3am. AX — Google's open agentic orchestrator — is the first framework in this space that treats those three problems as first-class citizens instead of afterthoughts bolted onto a prompt-chaining library. If you've shipped anything with LangChain or a homegrown agent loop, you already know why that matters.

This is a practical breakdown of what AX actually does, how it's architected, and how to wire it into a real Node.js/TypeScript backend with Docker for deployment.

AX is an open-source orchestration layer for AI agents that sits between your LLM calls and your application logic. Unlike prompt-chaining libraries that treat agents as a sequence of function calls, AX models agents as **stateful graphs** with explicit transitions, retries, and checkpoints.

The core primitives:

This is closer to a workflow engine (think Temporal or AWS Step Functions) than a chatbot SDK — which is exactly the gap it fills. Most agent frameworks are great for demos and terrible for anything that needs to survive a network blip or a rate-limited API three steps into a 20-step task.

If you're building agentic features into a product — not a research notebook — you care about things AX explicitly designs for:

That last point is the sleeper feature. You don't need to rewrite your Go microservices in Python to make them agent-callable — AX treats any HTTP/gRPC endpoint as a valid executor.

```
npm install @google/ax-orchestrator
npm install @google/ax-store-redis
```

A minimal agent graph in TypeScript:

``` js
import { Graph, Executor, RetryPolicy } from '@google/ax-orchestrator';
import { RedisStateStore } from '@google/ax-store-redis';

const store = new RedisStateStore({
  url: process.env.REDIS_URL!,
});

const fetchData: Executor = {
  id: 'fetch-user-data',
  type: 'tool',
  run: async (ctx) => {
    const res = await fetch(`https://api.internal/users/${ctx.input.userId}`);
    if (!res.ok) throw new Error(`Upstream failed: ${res.status}`);
    return res.json();
  },
  policy: RetryPolicy.exponentialBackoff({ maxAttempts: 3, baseMs: 500 }),
};

const summarize: Executor = {
  id: 'summarize-with-llm',
  type: 'llm',
  model: 'gemini-2.5-pro',
  prompt: (ctx) => `Summarize this user profile in 2 sentences:\n${JSON.stringify(ctx.state.fetchData)}`,
};

const graph = new Graph({ store })
  .addNode(fetchData)
  .addNode(summarize)
  .connect('fetch-user-data', 'summarize-with-llm');

export async function runAgent(userId: string) {
  const run = await graph.start({ input: { userId } });
  return run.result;
}
```

Notice there's no manual `try/catch` retry loop, no ad-hoc `setTimeout` backoff — that's the whole point. The policy is declared once, attached to the node, and the orchestrator enforces it.

| Feature | AX | LangChain | CrewAI | 
|---|---|---|---|
| State persistence | Durable, pluggable store | In-memory by default | In-memory by default | 
| Crash recovery | Resumes from checkpoint | Restarts from scratch | Restarts from scratch | 
| Retry policies | Per-node, declarative | Manual wrapping | Limited | 
| Language interop | Any HTTP/gRPC service | Python/JS SDKs only | Python only | 
| Observability | Built-in trace export (OpenTelemetry) | Requires LangSmith | Minimal | 
| Deployment model | Stateless orchestrator + external store | Embedded in app process | Embedded in app process | 
| Best fit | Production multi-step workflows | Prototyping, RAG pipelines | Role-based agent teams | 

The architectural bet AX makes is that **agents are workflows, not chat sessions**. That reframing is why it plugs so cleanly into existing DevOps tooling — checkpoints are just rows in Postgres, traces are just OTel spans, and scaling is just adding more orchestrator replicas behind a load balancer.

Real-world use cases rarely involve a single LLM call. Here's a research-and-report pipeline with conditional branching:

``` js
import { Graph, Executor, Condition } from '@google/ax-orchestrator';

const searchWeb: Executor = {
  id: 'search-web',
  type: 'tool',
  run: async (ctx) => searchAPI(ctx.input.query),
};

const validateResults: Executor = {
  id: 'validate-results',
  type: 'llm',
  model: 'gemini-2.5-flash',
  prompt: (ctx) => `Are these search results relevant to "${ctx.input.query}"? Answer YES or NO.\n${JSON.stringify(ctx.state.searchWeb)}`,
};

const draftReport: Executor = {
  id: 'draft-report',
  type: 'llm',
  model: 'gemini-2.5-pro',
  prompt: (ctx) => `Write a report using:\n${JSON.stringify(ctx.state.searchWeb)}`,
};

const refineQuery: Executor = {
  id: 'refine-query',
  type: 'llm',
  model: 'gemini-2.5-flash',
  prompt: (ctx) => `Rewrite this search query to be more specific: "${ctx.input.query}"`,
};

const graph = new Graph()
  .addNode(searchWeb)
  .addNode(validateResults)
  .addNode(draftReport)
  .addNode(refineQuery)
  .connect('search-web', 'validate-results')
  .connectConditional('validate-results', {
    onTrue: 'draft-report',
    onFalse: 'refine-query',
  })
  .connect('refine-query', 'search-web'); // loop back
```

This loop-back edge is where most naive agent implementations fall apart — infinite loops without a cap. AX solves it with built-in **cycle guards**:

``` js
const graph = new Graph({
  maxCycles: 5,
  onCycleLimitExceeded: 'fail-with-partial-result',
});
```

AX ships OpenTelemetry instrumentation out of the box. Every node execution emits a span with input/output/token-usage metadata:

``` js
import { AXTracer } from '@google/ax-orchestrator';

const tracer = new AXTracer({
  exporter: 'otlp',
  endpoint: process.env.OTEL_COLLECTOR_URL,
});

graph.attachTracer(tracer);
```

Pipe that into Grafana/Tempo and you get a flame graph of your agent's decision tree — which node retried, how many tokens each LLM call burned, and where latency actually lives. This is the difference between debugging an agent by re-reading chat transcripts versus debugging it like you'd debug a distributed system, because that's what it is.

Because the orchestrator is stateless, containerizing it is straightforward:

```
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
```

`docker-compose.yml` for local development with Redis as the state store:

```
version: "3.9"
services:
  ax-orchestrator:
    build: .
    ports:
      - "3000:3000"
    environment:
      - REDIS_URL=redis://redis:6379
      - OTEL_COLLECTOR_URL=http://otel-collector:4318
    depends_on:
      - redis
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
  otel-collector:
    image: otel/opentelemetry-collector:latest
    ports:
      - "4318:4318"
```

Scale horizontally by just increasing replicas — since state lives in Redis/Postgres, any orchestrator instance can pick up a checkpointed run.

```
docker compose up --scale ax-orchestrator=3
```

The pattern that separates toy agents from production agents is explicit failure handling per node:

``` js
const paymentExecutor: Executor = {
  id: 'process-payment',
  type: 'tool',
  run: async (ctx) => chargeCard(ctx.state.amount, ctx.state.cardToken),
  policy: {
    retry: { maxAttempts: 2, baseMs: 1000 },
    onFailure: 'compensate',
    compensate: async (ctx) => {
      await refundIfCharged(ctx.state.transactionId);
    },
  },
};
```

That `compensate` hook is essentially the Saga pattern from distributed transactions, applied to agent workflows. If you've built payment or booking systems, this should feel immediately familiar — because it's solving the same problem.

AX is overkill for:

It earns its complexity when you have:

| Store | Best for | Tradeoff | 
|---|---|---|
| Redis | Low-latency, ephemeral workflows | No long-term durability guarantees without persistence config | 
| Postgres | Audit trails, long-running workflows (days) | Higher write latency per checkpoint | 
| GCS/S3 | Very large state payloads (large documents, embeddings) | Higher latency, not ideal for high-frequency checkpoints | 

Pick based on workflow duration and payload size — don't default to Postgres for a workflow that completes in under 10 seconds; Redis will save you real latency.

`compensate` hook makes AX suitable for workflows with real side effects like payments or bookings.
