AI workflows & automation
Back-office pipelines, agentic ops, business processes. Durable across restarts, idempotent across retries, observable end to end. LLMs where they earn their keep, deterministic code where they must.
When you need a workflow, and when a script will do
A workflow engine earns its keep when steps are long-running, externally triggered, can fail in the middle, or need a human in the loop. If the whole job is a single API call that either succeeds or fails immediately, you have a function, not a workflow. We’ll tell you which one you have, and we won’t over-engineer the second case into the first.
Workflow-as-code on a real engine
Temporal, or equivalent. Every step is a versioned activity with retry semantics, calling Claude, OpenAI, or Gemini under a thin provider abstraction. Restarts pick up where they left off, automatically.
Idempotent writes, always
Every external write carries a content-hash idempotency key. Re-runs are cheap. Double-writes are impossible by construction. We’d rather over-key than under-key.
Humans in the loop, as a real step
Confidence below threshold sends the row to a review queue with the full context: original input, model rationale, suggested action. Reviews are a workflow step, not a side channel.
Where n8n-style tools stop being enough
Visual workflow builders (n8n, Zapier, Make) are excellent for the first 80% of a process. The other 20% is where they stop being enough: versioned deploys, real observability, deterministic retry semantics, branch-merge logic, someone on call. We start where the visual tool stops, and let your team keep using what already works.
- Code-reviewable workflow definitions. Git history, PR review, version pinning. Diffable.
- Typed inputs and outputs at every step. Pydantic / Zod / Protobuf — pick one and enforce it.
- Replay any production run from input bytes. Incident response in minutes, not days.
- Branch on confidence, not on hope. Every model call returns calibrated confidence; the resolver branches deterministically.
# Temporal workflow — durable across restarts, deterministic on replay.
@workflow.defn
class InvoiceIntake:
@workflow.run
async def run(self, payload: InboundInvoice) -> PostedInvoice:
canonical = await activity.execute(canonicalize, payload,
retry_policy=RetryPolicy(maximum_attempts=3))
extracted = await activity.execute(extract_fields, canonical,
retry_policy=RetryPolicy(maximum_attempts=5))
if extracted.confidence < 0.92:
# HITL is a workflow step, not a side channel
extracted = await workflow.wait_condition(
lambda: human_review_complete(canonical.id),
timeout=timedelta(hours=24))
decision = await activity.execute(resolve_routing, extracted)
return await activity.execute(write_to_erp, decision,
# content-hash key — re-runs are no-ops
idempotency_key=canonical.hash)
- Activities are pure functions of their inputs. No hidden state, no time-of-day branching, no global mutables.
- Retries are policy, not loops. Set the policy on the activity; trust the engine to enforce it.
- HITL waits don’t hold workers hostage. The workflow is suspended; resources are freed; resumes on signal.
- Idempotency keys are part of the data model. The ERP layer can de-dup independently of the workflow.
Process you’d like to automate?
If your team is keying things into systems, copy-pasting between tools, or running a script-and-pray cron job for a process that has to be right, this is what we do.