Skip to content
/ services / workflows

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.

Heuristic. If the answer to "what happens if this crashes halfway?" is anything other than "it picks up exactly where it left off," you don’t have a workflow yet. You have a fragile script and a lot of hope.
trigger webhook · cron · queue extract LLM + schema decide deterministic resolver human review on low confidence write idempotent audit replay log
durability

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.

idempotency

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.

HITL

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.