Building dynamic workflows with Claude: a pattern for adaptive automation
· claude, ai, serverless, platform-engineering, workflows
A pattern I’ve been exploring: instead of hardcoding every branch and condition in a workflow, let Claude decide what happens next based on the context at runtime. The workflow becomes a loop — observe, reason, act — where Claude’s job is to pick the next action and the orchestrator’s job is to execute it safely.
This isn’t about replacing Step Functions or Temporal with an LLM. It’s about using Claude as a decision layer in workflows where the right path isn’t knowable until you see the data.
The core idea
A traditional workflow looks like this:
Start → Check condition A → if true: Task 1 → Check condition B → ...
Every branch is pre-defined. That works beautifully when the logic is stable, but breaks down when you’re dealing with:
- Unstructured input that needs interpretation
- Contexts where the “right” next step depends on nuance
- Workflows that need to adapt to new scenarios without a redeploy
A dynamic workflow flips it:
Start → Observe state → Ask Claude: "what should happen next?" → Execute that → Loop
Claude gets the current state as context, a list of available actions, and returns a structured decision. The orchestrator validates it, runs it, and feeds the result back in.
A concrete example: incident triage
Say you’re building an on-call helper. An alert fires, and you want to:
- Classify severity
- Decide whether to page immediately, gather more data, or auto-remediate
- If gathering data: pick which diagnostic commands to run
- Synthesize the findings and decide next steps
You could write nested if statements for every alert type and every metric pattern. Or you could give Claude:
- The alert payload
- Recent logs and metrics
- A menu of available actions:
page_oncall,run_diagnostic,auto_restart,escalate,resolve
Claude reasons through it and returns something like:
{
"action": "run_diagnostic",
"parameters": {
"command": "check_disk_usage",
"target": "prod-db-01"
},
"reasoning": "High memory alert but disk at 95% — check if logs are filling disk before paging."
}
The orchestrator runs that command, appends the output to the context, and asks Claude again. Maybe this time it pages. Maybe it auto-remediates. The workflow is static — it’s a loop — but the path through it is dynamic.
The shape I’ve landed on
Here’s the loop:
- Observe — gather the current state (alert data, previous actions taken, tool outputs)
- Reason — call Claude’s Messages API with:
- System prompt defining the role and available actions
- The state as a message
response_formatset to JSON mode (or tool use, depending on the SDK version)
- Validate — parse Claude’s response; ensure the action exists and parameters are sane
- Execute — run the action via a Lambda, an API call, a script — whatever the action maps to
- Record — append the action and its result to the state
- Decide — is this terminal (resolved, escalated, max-loops hit)? If not, loop back to Observe
The orchestrator itself can be a Step Function, a simple Lambda loop with a DynamoDB table for state, or even a long-running container. The key is that Claude never executes anything directly — it only returns a decision, and the orchestrator is responsible for safety, retries, and observability.
What goes in the system prompt
The system prompt is where you define the contract:
You are an incident triage assistant. Your job is to analyze the current state
and decide the next action.
Available actions:
- page_oncall: page the on-call engineer (use sparingly)
- run_diagnostic: run a diagnostic command
- auto_restart: restart a service
- escalate: escalate to a human
- resolve: mark the incident as resolved
Respond with valid JSON:
{
"action": "<action_name>",
"parameters": { ... },
"reasoning": "<why you chose this>"
}
If you're unsure, prefer gathering more data over paging.
Be explicit about the output format, the available actions, and any guardrails. Claude is good at following structured instructions — the clearer you are, the fewer validation errors you’ll see.
Guardrails
A few things that matter:
- Action allowlist — validate that the returned action is in your known set. Reject anything else.
- Parameter schemas — use a JSON schema or a simple type check to ensure parameters are safe. Don’t pass unsanitized strings to a shell.
- Max loops — cap the number of iterations so a misbehaving loop doesn’t run forever.
- Human-in-the-loop for high-risk actions — if Claude suggests
delete_production_database, maybe ask a human first. - Audit log — record every decision Claude makes, the reasoning, and the outcome. This is both for debugging and for building trust.
Why I like this shape
- The workflow is simple. It’s just a loop. No combinatorial explosion of branches.
- It adapts without a redeploy. New alert types, new edge cases — Claude handles them if they’re within its reasoning capability.
- The reasoning is legible. Every decision comes with a
reasoningfield. When something goes wrong, you can see why Claude chose that path. - It’s testable. You can unit-test the orchestrator logic separately from Claude’s decisions, and you can regression-test by replaying state snapshots.
What this isn’t
This is not:
- A replacement for deterministic workflows where the logic is well-understood
- Appropriate for latency-critical paths (Claude’s API adds hundreds of milliseconds per call)
- A way to avoid writing code (you still write the actions, the validation, the orchestrator)
- Magically correct (Claude can make mistakes — that’s why you validate and log)
Things I’d still like to add
A few extensions I’m thinking about:
- Confidence scores — ask Claude to return a confidence level with each decision, and auto-escalate on low confidence
- Feedback loop — record outcomes (was the incident resolved? did it escalate?) and periodically fine-tune or adjust the prompt
- Multi-agent patterns — one Claude instance for triage, another for remediation, a third for summarization
- Cost tracking — log token usage per workflow run so you can see if a particular incident type is expensive to reason through
If you’re building automation that deals with ambiguity — support tickets, incident triage, data pipelines with messy input — giving Claude a structured menu of actions and letting it reason through the context is a shape worth trying.