---
date: 2026-05-01
topic: routines-step-functions-rebuild
---

# Routines: Step Functions Rebuild and Automations Integration

## Summary

Routines become first-class AWS Step Functions state machines, authored via natural-language chat by end users, operators, and agents. Steps are composed from a curated recipe library plus a `python()` escape hatch running in the AgentCore code sandbox, with first-class human-in-the-loop approval and a transparent Step Functions execution graph for the run experience. Routines fold into a real Automations module alongside Schedules and Webhooks, and a tenant-level `python()` usage dashboard drives the recipe-curation loop.

---

## Problem Frame

Today's Routines surface is in a half-built state. The marketing positioning treats routines as the cornerstone of "agentic → robotic" — the way ThinkWork tames non-deterministic agent loops into deterministic, fast, cheap, auditable workflows. The existing implementation does not back that claim:

- The public docs describe Step Functions, ASL, branching, Map fan-out, HITL via task tokens, and a per-step retry/observability story.
- The actual code is a Python `code` field generated by a mobile chat builder, executed inside a generic agent thread (`thread_turns`) — no Step Functions, no per-step structure, no native retry/HITL substrate.
- The previously-existing `routine_runs` and `routine_steps` tables were removed in favor of a unified `thread_turns` model, leaving the GraphQL `RoutineRun.steps` field with no real backing data.
- There is no operator-grade run UI, no markdown summary surface, and Routines is not even in the admin sidebar — only Scheduled Jobs is, under a misleading "Automations" label.

The cost is twofold. Customers see chat-built Python that the agent generated and reasonably ask whether that's really more deterministic than the agent loop it replaced. Eric's go-to-market story leans on a feature whose runtime can't yet honor the pitch. Both pains compound as enterprise scale arrives — at four customers × 100+ agents × five templates, every minute of agent-loop runtime that could have been a sub-second Step Functions transition is wasted token spend and observability debt.

---

## Actors

- A1. End user (mobile): describes a workflow in natural language, triggers routines, reads the markdown summary and run graph for routines they care about.
- A2. Tenant operator (admin): authors routines via the same chat surface, monitors all tenant routines, approves or rejects HITL steps via the Inbox, watches the `python()` usage dashboard.
- A3. Tenant agent (programmatic): self-stamps a routine via an MCP tool when it observes itself doing the same multi-step thing repeatedly; later calls the routine as a tool.
- A4. ThinkWork engineer: curates the v0 recipe library; promotes high-frequency `python()` patterns into named recipes in subsequent releases.

---

## Key Flows

- F1. **End-user authors a routine via chat.**
  - **Trigger:** End user opens "New Routine" in mobile, describes the workflow.
  - **Actors:** A1.
  - **Steps:** Chat builder loads the routine-builder system prompt → user iterates with the builder agent on intent, recipes, HITL points → user clicks Build → builder agent emits ASL + markdown summary via a tool call → ASL is validated and a Step Functions state machine is provisioned → routine appears in the user's list.
  - **Outcome:** A published routine ready to be triggered, with markdown summary visible.
  - **Covered by:** R1, R2, R3, R6, R10.

- F2. **Operator authors via admin chat.**
  - **Trigger:** Operator opens Automations → Routines → New in admin.
  - **Actors:** A2.
  - **Steps:** Same builder flow as F1, but inside admin chrome with tenant-wide context (operator can pick any tenant agent / connector / prior routine as building blocks). Operator can preview the execution graph before publishing.
  - **Outcome:** Same as F1, plus operator may set tenant-default approval routing for HITL steps.
  - **Covered by:** R1, R2, R7, R10, R12.

- F3. **Agent self-stamps a routine.**
  - **Trigger:** Agent calls `create_routine` MCP tool with a high-level spec (intent + suggested steps, or natural-language description).
  - **Actors:** A3.
  - **Steps:** MCP tool routes through the same ASL generator the chat builders use → produces ASL + markdown → publishes routine → returns routine id to the calling agent → agent can then call `routine_invoke` on subsequent turns.
  - **Outcome:** A new routine in the tenant, owned by the calling agent, ready to be reused.
  - **Covered by:** R4, R10, R20.

- F4. **Routine execution with HITL approval.**
  - **Trigger:** Routine is invoked (manual / scheduled / webhook / event / agent-tool-call).
  - **Actors:** A2 (approver), plus the trigger source.
  - **Steps:** Step Functions execution starts → runs deterministic and agent steps → reaches an `inbox_approval` step → execution pauses, task token is persisted on a new Inbox item → operator sees the Inbox item with markdown context summarizing the pending decision → operator approves or rejects → token releases, execution resumes on the chosen branch → final state recorded.
  - **Outcome:** Routine completes (success / failed / cancelled) with full per-step audit trail; approval decisions are recorded.
  - **Covered by:** R11, R12, R13, R14, R15.

- F5. **Recipe promotion from `python()` usage.**
  - **Trigger:** ThinkWork engineer reviews the tenant `python()` usage dashboard at any cadence.
  - **Actors:** A4, A2 (observer).
  - **Steps:** Engineer sees a recurring pattern (e.g., "12 customers wrote a `python()` step that hits Stripe") → adds a curated recipe in the next release → operators are surfaced a "this routine could use the new recipe" suggestion in admin.
  - **Outcome:** The recipe library grows demand-driven; previously-custom code becomes auditable, typed, retried-by-default behavior.
  - **Covered by:** R8, R26, R27.

---

## Requirements

**Authoring**

- R1. The same natural-language chat builder is used by end users (mobile) and operators (admin), backed by one system prompt and one ASL generator. The visual difference is the chrome (mobile vs. admin), not the builder.
- R2. The builder produces three artifacts on Build: a validated ASL state-machine definition, a markdown summary written for human readers, and a structured step manifest (recipe types + node ids + step titles) used by the run UI.
- R3. The builder recognizes HITL signals in the user's prompt (phrases like "require approval before X", "pause for review", "needs sign-off") and inserts `inbox_approval` nodes accordingly; the markdown summary names every approval point.
- R4. Agents author routines via an MCP tool that accepts a high-level spec and returns the published routine id; the tool uses the same ASL generator and validation as the chat surfaces.
- R5. The visual ASL graph is read-only in v0. Customers do not see or edit raw ASL JSON; operators do not get a drag-and-drop canvas.

**Step model**

- R6. The v0 recipe library covers control flow (`sequence`, `choice`, `map`, `wait`, `fail`), invocation (`agent_invoke`, `tool_invoke`, `routine_invoke`), IO (`http_request`, `aurora_query`, `transform_json`, `set_variable`), notification (`slack_send`, `email_send`), HITL (`inbox_approval`), and the escape hatch (`python`).
- R7. `agent_invoke`, `tool_invoke`, and `routine_invoke` expose existing tenant inventory as composable steps without per-item recipe authoring: any tenant agent, any registered tool / connector / MCP method, and any prior routine becomes callable from a routine.
- R8. The `python` recipe runs agent- or operator-authored Python in the AgentCore code sandbox (not raw Lambda) with time bounds, isolation, and explicit network-grant semantics. `python` step output is captured as a step event for the run UI.
- R9. Recipe definitions are owned by ThinkWork engineering. Customers cannot author new recipes in v0; they compose existing recipes, fall back to `python()`, and rely on the promotion loop (R26, R27) for new curated blocks.
- R10. Routine publish performs ASL validation, recipe-arg type checking, and a dry-run graph trace. A routine that fails any check is rejected with actionable errors surfaced into the chat builder for the agent to repair.

**Human-in-the-loop**

- R11. The `inbox_approval` step pauses Step Functions execution via task token, creates an Inbox item with the routine name, the step's markdown summary, and the input payload preview, and resumes when the operator decides.
- R12. Each `inbox_approval` step declares (at routine-author time) its approval routing (which operator role(s) can decide), its timeout behavior (auto-approve, auto-reject, or escalate), and its decision payload shape (yes/no, choice from N options, free-text annotation).
- R13. Approval decisions are recorded as run events with the deciding operator, decision value, and timestamp; the markdown summary on the run detail page reflects which branch the routine took.

**Run experience**

- R14. The run detail surface shows the Step Functions execution graph (states + transitions + per-state status) sourced primarily from Step Functions native APIs, with a per-node panel showing input, output, duration, retries, and recipe type.
- R15. The markdown summary renders alongside the graph and is regenerated whenever the routine is edited (not auto-derived from ASL — agent-authored, persisted as a first-class field).
- R16. ThinkWork persists step-level metadata that Step Functions does not natively carry: per-step LLM cost (for `agent_invoke` and `python` steps that call models), `python()` step stdout/stderr, markdown step titles, and HITL decision metadata.
- R17. The run list is paginated, filterable by status (running / succeeded / failed / cancelled / awaiting approval) and by triggering source, and updates in near-real-time during a live execution.

**Triggers and integration**

- R18. Routines plug into the existing `scheduled_jobs` infrastructure as another trigger target. Manual, scheduled (rate / cron / one-time), webhook, event, and agent-tool-call triggers are all supported in v0.
- R19. An agent calls a routine via the same `tool_invoke`-style surface it uses for any other tool: `routine_invoke(routineId, args)`.

**Compounding library**

- R20. `routine_invoke` is a v0 recipe. Any routine in the tenant can be called as a step from any other routine. Cycles are detected at publish time and rejected.
- R21. Routine ownership and visibility metadata distinguish three classes: agent-stamped routines are **private to the owning agent by default** until explicitly promoted by an operator; user-published routines are visible tenant-wide; operator-published routines are visible tenant-wide and governance-tagged. Promotion of an agent-stamped routine to tenant-wide is an explicit operator action that surfaces in the admin Automations module.

**Automations module integration**

- R22. The admin sidebar exposes Automations as a parent section with three children: Routines, Schedules, Webhooks. The current single "/scheduled-jobs" entry under the "Automations" label is restructured. Inbox stays separate as the HITL surface.
- R23. The mobile Routines tab gains parity with the run-detail experience (graph + markdown summary + step panel), scoped to routines the end user can see.

**Migration and deprecation**

- R24. Existing Python-script routines (rows with non-null `code`) are archived and excluded from the new Routines list. They are not auto-converted to ASL.
- R25. The mobile builder's Python-generation prompt and `update_routine` Python-targeting tool are deprecated and replaced with the ASL-targeting equivalents in v0; the legacy paths are removed once the new surface is live.

**Observability and recipe curation**

- R26. A `python()` usage dashboard, operator-only in admin, surfaces frequency and signature clusters of `python()` blocks across the tenant's routines, with drill-down to which routines use which patterns.
- R27. The dashboard also surfaces `python()` patterns that have been promoted to curated recipes since the routine was last edited, so operators can see candidates for refactor.

---

## Acceptance Examples

- AE1. **Covers R3, R11, R12.** Given an end user describes a routine as "fetch overnight Stripe disputes, draft a refund response with the agent, **require approval before sending**, then post to the customer's email," when they click Build, the resulting routine includes an `inbox_approval` step before the email-send step, the markdown summary names the approval point explicitly, and the step's approval routing defaults to the routine creator's tenant-operator role.

- AE2. **Covers R6, R7, R8.** Given an operator describes a routine that needs to call a third-party API ThinkWork has not curated as a recipe, when the chat builder cannot find a matching recipe it generates a `python()` step containing the request code with explicit network grants, and the markdown summary describes what the `python()` step does in plain language.

- AE3. **Covers R10.** Given the agent emits ASL containing an `agent_invoke` step that references an agent id that does not exist in the tenant, when publish-time validation runs, the routine is rejected, the chat returns the validation error to the builder agent, and the builder agent attempts a repair (e.g., picks a valid agent or asks the user) without the user having to restart.

- AE4. **Covers R14, R16, R17.** Given a routine is mid-execution with one branch awaiting an `inbox_approval`, when an operator opens the run-detail surface, the execution graph shows that node in `awaiting_approval` state, the run-list filter "awaiting approval" includes this run, and clicking the awaiting node shows the input payload, the markdown summary for that step, and a deep link to the Inbox item.

- AE5. **Covers R20.** Given an existing routine `A` that calls routine `B` via `routine_invoke`, when an operator edits routine `B` to call routine `A`, publish is rejected with a cycle-detection error naming both routines.

- AE6. **Covers R26.** Given five routines across a tenant each contain a `python()` step that performs the same Stripe-API call signature, when the operator opens the `python()` usage dashboard, those five steps are clustered together with a shared signature label and a drill-down to all five routines.

---

## Success Criteria

- A customer-facing demo of "agentic → robotic" lands without caveats: the demo routine has at least one `agent_invoke` step (showing agentic-when-needed), at least one deterministic recipe step (showing robotic optimization), and at least one `inbox_approval` step (showing HITL governance), all visible in the execution graph and named in the markdown summary.
- End users can build a useful routine from a single chat session without touching JSON, code, or the AWS console.
- Operators can answer "what is this routine doing right now?" from the run-detail surface alone, without opening CloudWatch or the Step Functions console.
- The recipe-promotion loop is observable: at any time, an operator (and ThinkWork eng) can answer "what `python()` patterns are most common in my tenant" from the dashboard.
- ce-plan can produce a phased implementation plan from this document without re-litigating product behavior, scope, or audience.

---

## Scope Boundaries

- Notebook-style run UI (linear cells, click-to-reveal code) — replaced by the Step Functions execution graph.
- Visual drag-and-drop ASL canvas authoring for operators in v0.
- Raw ASL JSON editor exposed to operators or end users.
- Auto-conversion of legacy Python-script routines into ASL.
- End-user-authored custom recipe definitions; only ThinkWork-eng-authored recipes plus the `python()` escape are available in v0.
- Cross-tenant routine sharing, publishing, or marketplace.
- Pause-and-edit of in-flight Step Functions executions (Step Functions limitation; documented as a known limit).
- Versioning UI for diffing routine versions or rolling back; v0 ships native Step Functions versioning behavior only.
- Multi-stage approval chains, parallel approval-from-N-of-M, delegation of approvals to other operators; v0 is single-approver, single-decision per HITL step.
- Mobile push-notification UX for approvals beyond what the existing Inbox already provides.
- Per-step retry / timeout overrides authored at the routine level; v0 inherits Step Functions defaults (retry on Throttling/ServiceUnavailable, no retry on AccessDenied, 15-minute per-state timeout). HITL is exempt — it is a v0 capability, not a default-only behavior.

---

## Key Decisions

- Routine = AWS Step Functions ASL state machine: matches existing public docs, gives free retry / HITL / observability substrate, and produces a stronger procurement story than a homegrown notebook engine.
- Hybrid step model (recipe library + `python()` escape) over recipes-only or code-first: preserves the determinism / auditability pitch for the common case while giving a credible answer to "but what about our weird API?" at the sales demo.
- One chat builder for end users and operators, plus an MCP tool for agents, all calling one ASL generator: keeps prompt surface, validation, and recipe inventory in one place.
- Run UI sources execution data from Step Functions native APIs, augmented with ThinkWork-persisted markers (cost, `python()` output, markdown titles, HITL metadata): leans into "transparent AWS" instead of building a parallel event store.
- `routine_invoke` ships in v0: small implementation cost, large narrative payoff (compounding tenant library).
- Single-approver HITL in v0: covers the common case; multi-stage / N-of-M deferred until customer pull is observed.
- Legacy Python `code` routines are archived rather than auto-converted: conversion would be lossy and risk breaking trusted production flows on customer behalf.
- The R6 v0 recipe set is the locked floor for v0: planning may add recipes but cannot remove. Prevents scope drift during implementation phasing.
- Agent-stamped routines default to private (owning agent only) until explicitly promoted by an operator: lets agents experiment without polluting the tenant library or imposing tenant-wide review burden on every agent stamp.

---

## Dependencies / Assumptions

- AWS Step Functions is available in every stage / region ThinkWork deploys to (already true for the Terraform foundation module).
- AgentCore code sandbox is the execution substrate for `python()` steps; assumed available and capable of accepting tenant-bounded invocations from a Step Functions Task state.
- The existing `scheduled_jobs` + `thread_turns` infrastructure is reusable as the trigger and execution-record surface; routine runs become a kind of `thread_turn` with a Step Functions execution id attached, OR a parallel `routine_executions` table — to be decided in planning.
- The existing Inbox surface is the resumption point for HITL approvals; assumed already capable of carrying task-token metadata (or extensible without breaking other consumers).
- The MCP tool surface already supports streaming-HTTP MCP servers reachable from the Strands runtime; `create_routine` and `routine_invoke` follow the same shape as existing tenant MCP tools.
- Recipe argument type checking is feasible with the v0 recipe set; complex recipe arg shapes (nested branches, computed defaults) are out of v0.

---

## Outstanding Questions

### Deferred to Planning

- [Affects R8][Technical] Exact integration shape for `python()` Task states: Lambda Task wrapping a sandbox invocation, direct sandbox HTTP API as an `arn:aws:states:::http:invoke` Task, or a new dedicated Task type. Affects per-step latency and how step events are captured.
- [Affects R14, R16][Technical] Where does ThinkWork-persisted step metadata live: a new `routine_step_events` table, an extension of `thread_turn_events`, or a new schema entirely. Affects query performance and the GraphQL surface.
- [Affects R2][Technical] Storage shape for ASL: a new column on `routines` (jsonb), a separate `routine_versions` table, or S3-backed with a pointer column. Versioning, audit history, and Step Functions reconciliation depend on the choice.
- [Affects R11, R12][Technical] How task tokens are stored on Inbox items and how the resumption Lambda is wired. Existing Inbox schema may already accommodate task-token metadata or may need extension.
- [Affects R7][Needs research] Does `agent_invoke` block the Step Functions state until the agent's full loop completes (sync), or does it spawn a `thread_turn` and resume via callback (async)? Sync is simpler but bottlenecks long agent runs; async is more scalable but adds task-token plumbing for every agent step.
- [Affects R26][Technical] `python()` clustering algorithm — exact-match by AST hash, fuzzy by import set, or LLM-summarized signatures. Determines how useful the dashboard is at v0.
- [Affects R10][Technical] Validation pipeline shape: client-side in the chat builder, server-side at publish, or both with progressive feedback during chat. Affects builder UX and round-trip latency.
- [Affects R22][Technical] Concrete admin nav restructuring: rename existing routes, introduce a parent `/automations` route with redirects, or build a tabbed shell. Affects existing operator muscle memory and any deep-linked URLs.
