Chapter DWorkflow automation in codePage 3 of 8

Workflow automation in code

Implement the happy path

Workflow automation in code is production work only when one frozen failure can be reproduced, one measurable gate can stop a release, and one operator can safely reverse it.

~14 minHappy path

Before you start

Why this matters

Read this incident aloud: a ticket webhook is delivered three times and the model marks an ambiguous message as urgent. In two minutes, write the earliest deterministic check that should fail, the telemetry signal you would inspect, and the action that must not happen automatically. Compare your answer with this chapter's boundary: verify webhook signatures before parsing; the model proposes actions but cannot send externally.

1Learn the idea

Read

Implement the controlled happy path

Code begins at the boundary, not at the provider SDK. Parse WebhookEvent with event_id, signature, occurred_at, ticket_id, text, and dry_run before any model or tool call, and return TriageDecision with category, urgency, confidence, evidence, proposed_action, and approval_required even when the provider times out. Typed records make malformed data a normal error path instead of an exception discovered after a side effect. Inject the model client, clock, action adapter, and telemetry sink so tests can replace each one. That design also prevents a local test from accidentally reaching production.

The important implementation choice is ordering. Authenticate and normalize first; make the controlled call second; validate the response third; commit any state or action last. For this system, verify webhook signatures before parsing; the model proposes actions but cannot send externally. A convenient function that mixes parsing, generation, action, and logging cannot prove that ordering. Keep each stage named so a trace can show which boundary rejected the request.

Use deterministic identifiers derived from stable business inputs when retries are possible. Record the release, fixture version, and policy version with the result. Never derive authorization from generated text. The code path should make the safe behavior easier than bypassing it: callers receive a decision object, not an unrestricted provider response.

For the happy path, run one valid fixture through the real orchestration with fake adapters. Keep the path small: accept, decide, validate, record. A successful response must carry evidence and a trace ID, not merely polished text.

Read

Focused implementation artifact

def run_case(case, *, release, trace_id):
    # Adapters are injected in production; the orchestration owns policy.
    decision = workflow.handle(event, actions=RecordingActions(), clock=frozen_clock)
    reasons = evaluate(answer if "answer" in locals() else locals().get("result", locals().get("decision", locals().get("job", locals().get("choice")))))
    return {"passed": not reasons, "reasons": reasons,
            "release": release, "trace_id": trace_id}

result = run_case({"event_id":"evt_884","signature":"test-valid","occurred_at":"2026-07-18T05:10:00Z","ticket_id":"T-91","text":"Maybe locked out; payroll closes today","dry_run":true}, release="candidate", trace_id="trace-42")
assert result["trace_id"] == "trace-42"

Read

Walk the successful transaction

Trace one accepted fixture through parse, policy lookup, controlled dependency call, output validation, and result recording. At each stage, name the input and output type. The successful transaction must produce evidence for correct actions per unique event, a correlation ID for logs and spans, and a release identifier. It must not grant the model authority to choose identity, credentials, network destinations, or approval.

Run the path with recording fakes and inspect their call order. The assertion is not merely “returned 200”: exactly the expected adapter was called, its arguments were normalized, no forbidden adapter ran, and workflow_action_total recorded a bounded outcome. Repeat with the same idempotency key or request identifier where retries are relevant; the observable decision should remain stable.

Finally, swap in the known failure the retry handler posts three Slack alerts because idempotency is recorded after the side effect while leaving orchestration unchanged. The happy path must stop at the correct boundary instead of manufacturing a success. The operator-facing consequence is to record the proposal transactionally, request human approval, and execute once with an idempotency key.

Checking tutor…

Continue learning · glossary & guides