Skip to content

Process hundreds of records in one routine ​

Find a list at run time, perform the same bounded workflow for every item, preserve input order, and finish with one aggregate report.

  • What you'll build β€” A read-oriented client-health audit that pulls quiet clients from the Sales Platform, analyzes two aspects of each client in parallel, recommends a next action, and summarizes the entire batch.
  • Workflow features β€” foreach, nested parallel and sequence behavior, item aliases, {previous}, bounded concurrency, ordered results, partial failures, per-step Agents, and an optional batch effect boundary.
  • Scale β€” Designed for hundreds of records. The runtime accepts at most 10,000 resolved items and at most 100 simultaneously active Agent leaves.

Build this through Claude Code using the account-scoped Agents MCP. Start with Build and test a routine with Claude if the server is not connected yet.

Why foreach is the important part ​

A normal parallel block has a fixed number of branches written into YAML. foreach gets its list at run time and starts one copy of its nested workflow for each item. The source Agent can obtain that list with any ordinary tool it already has; the YAML does not need a typed client source or a special batch worker.

Each item receives:

  • {client} β€” the current item, because this example uses as: client;
  • {item} β€” the same item under the standard name;
  • {index} β€” its zero-based position;
  • {previous} β€” the previous nested step's result for that same client;
  • {input} β€” the original routine run message.

Separate data paging from Agent work ​

The source step must collect its input through bounded connector calls. For an exhaustive client sweep, scan_clients returns {clients, count, limit, next_after_id}: request up to 100, pass next_after_id back as after_id, and stop when count < limit. Apply mutable client conditions in the skill. list_clients retains filters and offset for interactive lookup, where a live result is acceptable; it is not a batch snapshot. Do not ask for limit: 0 as a shortcut to "all".

Conversation extraction uses an immutable id cursor because a live chat's activity time can move while you scan it. export_chats returns at most 100 chats with a bounded first or last message window. Feed next_before_id back as before_id on the next call; never calculate an offset or page by the mutable activity timestamp. See the exact contract in Sales tools reference: export_chats. Apply assistant/client/page/channel, activity-window, and operator-state conditions in the skill to each returned row. Those mutable fields intentionally do not define server-side membership across calls.

Use foreach only when each record genuinely needs an Agent decision. A request such as β€œcopy the first ten messages of 7,000 chats to CSV” is deterministic data work: page export_chats, write/checkpoint each page in the caller's skill, then upload the finished file. Creating 7,000 Agent leaves for that copy would be slower and far more expensive without improving the result.

1. Ask Claude to inspect the account ​

Use a prompt like this:

text
Build a read-only routine that audits clients with no activity in a window I
provide. Expect 200–500 clients. Use the existing Sales Platform-capable Agent
to return a minimal JSON array with stable client ids, then foreach over it.
For each client, analyze relationship health and data completeness in parallel,
then produce one recommended next action. Do not send messages or update client
records. Summarize successes and failures after the batch.

Inspect my account and the routine documentation first. Validate and show me the
complete canonical YAML before saving. Preview a three-item literal version,
then restore the dynamic source and run readiness. Do not create or enable a
schedule yet.

Claude should call get_routine_authoring_context, read its Markdown reference, then call get_account_authoring_inventory. It must confirm the actual agent slugs and relevant Sales Platform connector before drafting. The example below uses the standard platform-assistant, personal-assistant, and bulk-text-worker slugs; Claude should adapt them if your effective inventory differs.

2. Review the final live YAML ​

This is the complete routine shape Claude should produce:

yaml
name: quiet-client-health-audit
display_name: Quiet client health audit
description: Audit quiet clients in parallel and return an ordered action list without changing records.
archetype: personal-assistant
run_message: Find clients with no activity in the last 30 days.
steps:
  - title: Find quiet clients
    archetype: platform-assistant
    prompt: |
      Use the connected Sales Platform tools to find clients matching this
      window: {input}

      Page scan_clients with limit 100. Pass next_after_id back as after_id
      until a page count is smaller than its limit. Apply the requested quiet
      window to each returned row. Never request an unbounded result.

      Return only a JSON array. Keep it compact. Every item must contain a
      stable client id, display name, last activity time, current status, and
      the minimum recent context needed for an audit. Return [] when none match.

  - type: foreach
    title: Audit every quiet client
    items: "{previous}"
    as: client
    max_concurrency: 8
    steps:
      - type: parallel
        title: Check health and data quality
        steps:
          - title: Assess relationship health
            archetype: personal-assistant
            prompt: |
              Assess this client without changing anything: {client}
              Return risk level, evidence, and the best next conversation goal.
          - title: Check record completeness
            archetype: platform-assistant
            prompt: |
              Inspect the supplied client data only: {client}
              List missing or inconsistent fields and do not update the record.

      - title: Recommend one next action
        archetype: bulk-text-worker
        prompt: |
          Combine the parallel findings below into compact JSON with client_id,
          risk, recommended_action, and reasons:
          {previous}

  - title: Summarize the batch
    archetype: personal-assistant
    prompt: |
      Summarize this foreach aggregate: {previous}
      Report total, succeeded, failed, counts by risk, the ten highest-priority
      clients, and the ids of failed items that should be retried. Do not claim
      that any client record was changed.

The two branches inside each item run at the same time. With max_concurrency: 8, the widest point is 8 items Γ— 2 Agent leaves = 16 active Agent runs, below the routine-wide ceiling of 100. The recommendation for an item waits for both branches and reads their combined output through {previous}.

3. Preview a literal sample ​

The final items: "{previous}" source is dynamic, so its worst-case call count cannot be known before the source Agent runs. That final routine is valid for live execution but is not eligible for the MCP's bounded preview.

Before saving the final form, have Claude temporarily replace items with a small representative list:

yaml
    items:
      - id: sample-001
        display_name: Sample retailer
        last_activity_at: "2026-07-01T10:00:00Z"
        status: qualified
        recent_context: Asked for wholesale pricing; no reply for 35 days.
      - id: sample-002
        display_name: Sample studio
        last_activity_at: "2026-06-28T15:30:00Z"
        status: new
        recent_context: Imported record with no conversation history.
      - id: sample-003
        display_name: Sample clinic
        last_activity_at: "2026-06-20T08:15:00Z"
        status: negotiating
        recent_context: Proposal sent; decision date is missing.

Validate and save that version, approve the billed tool-free preview, and inspect the item and aggregate outputs. Three items produce a statically bounded 11 Agent calls in this graph: one source step, nine item steps, and one summary.

The preview does not query the Sales Platform. Its source step and all connector actions are simulated because AgentOS removes tools. After the graph behaves correctly, ask Claude to restore items: "{previous}", validate the full YAML, update the saved routine, and call get_routine_readiness again.

4. Run a narrow live read ​

The Agents MCP cannot start a live run. In MyChatBot, run the final routine with a deliberately narrow window such as β€œfind at most five clients with no activity in 90 days.” Confirm that:

  • the source result is a JSON array rather than prose or a Markdown fence;
  • every item contains a stable client id;
  • no branch attempts a write;
  • successes and failures remain aligned with the original item order;
  • the final summary names failed ids without inventing results.

Increase the source limit and concurrency gradually. Provider rate limits and model cost usually matter well before the YAML ceiling does.

5. Understand failure and retry behavior ​

One item failure does not cancel its siblings. The aggregate records total, succeeded, failed, and one ordered row per input item. The batch itself is not automatically retried.

For safe reruns:

  1. keep stable external record ids in every item;
  2. have the summary emit the failed ids;
  3. rerun a list containing only those failures;
  4. make any eventual write idempotent when the connector supports it.

foreach is not a transaction or an exactly-once job queue. A process can fail after an external write succeeds but before the result is recorded.

Optional: turn recommendations into record updates ​

Once the read-only audit is proven, Claude can change the final nested step to perform an explicit update through the same Agent's existing tools. Put one approval on the whole foreach block to describe the proposed batch, not one pause inside each item:

yaml
  - type: foreach
    title: Update every reviewed client
    items: "{previous}"
    as: client
    max_concurrency: 4
    approval:
      message: Review the client list and approve the described CRM updates.
    steps:
      - title: Apply one idempotent update
        archetype: platform-assistant
        prompt: |
          Update only the record identified by the stable id in {client}.
          Apply the explicitly reviewed fields and return the id plus the tool
          result. Do not send a message or change any other record.

Do not enable this version on the strength of approval today

The runtime-wide review switch currently leaves approval pauses off by default. In that mode, the owner-authored update instruction runs unattended. Keep the scheduled routine read-only, or enable writes only after the deployment visibly shows the expected approval card and you have separately tested idempotency and partial-failure recovery.

Nested foreach, child approvals, Loop iteration review, and Router user/output review are intentionally rejected inside an item. Flatten the list and review the batch as a whole.

Put it on a schedule ​

After the live read is sound, ask Claude to call create_routine_schedule with the desired cron and IANA timezone. The MCP creates it disabled. Review normal batch size, calls per item, expected spend, and external rate limits before a separate set_routine_schedule_enabled approval.

See also ​