Neural Mastery
← Practice
Plan-and-Execute With Dependencieshard

Plan-and-Execute With Dependencies

hard Build an Agent 25–30 min

Standard library only · runs in your browser

ReAct decides one step at a time, and pays a model call for every step. Plan-and-execute has the model write the whole plan once, then your code runs it: in dependency order, piping outputs into later steps, and skipping work that depends on a failure.

Task

Implement execute_plan(plan). Each step is {"id", "tool", "arguments", "depends_on": [ids]}.

  1. Validate first, run nothing on failure: raise ValueError for duplicate step ids, a depends_on that names an unknown step, or a dependency cycle.
  2. Order the steps topologically (Kahn's algorithm). When several steps are ready, take the one that comes first in plan.
  3. Run each step in that order:
    • If any dependency's status isn't "ok", the step is {"status": "skipped", "output": None}.
    • Otherwise substitute arguments. A string argument that is exactly "$<step id>" becomes that step's output, parsed with json.loads when possible (so "5" becomes 5), or the raw string otherwise.
    • Run it with dispatch. If the output starts with error:, the status is "error", otherwise "ok".
  4. Return {"order": [ids in run order], "results": {id: {"status", "output"}}}. Skipped steps appear in order too.

Function Signature

execute_plan(plan: list[dict]) -> dict

Examples

Example 1: Output of one step feeds the next
Input: plan = [{'id': 's1', 'tool': 'add', 'arguments': {'a': 2, 'b': 3}, 'depends_on': []}, {'id': 's2', 'tool': 'add', 'arguments': {'a': '$s1', 'b': 10}, 'depends_on': ['s1']}]
Output: {'order': ['s1', 's2'], 'results': {'s1': {'status': 'ok', 'output': '5'}, 's2': {'status': 'ok', 'output': '15'}}}
Example 2: Plan order isn't run order
Input: plan = [{'id': 'total', 'tool': 'add', 'arguments': {'a': '$x', 'b': 1}, 'depends_on': ['x']}, {'id': 'x', 'tool': 'divide', 'arguments': {'a': 9, 'b': 3}, 'depends_on': []}, {'id': 'w', 'tool': 'get_weather', 'arguments': {'city': 'Lima'}, 'depends_on': []}]
Output: {'order': ['x', 'total', 'w'], 'results': {'x': {'status': 'ok', 'output': '3.0'}, 'total': {'status': 'ok', 'output': '4.0'}, 'w': {'status': 'ok', 'output': '16C, overcast'}}}
Example 3: Dependents of a failed step are skipped
Input: plan = [{'id': 's1', 'tool': 'divide', 'arguments': {'a': 1, 'b': 0}, 'depends_on': []}, {'id': 's2', 'tool': 'add', 'arguments': {'a': '$s1', 'b': 1}, 'depends_on': ['s1']}, {'id': 's3', 'tool': 'get_weather', 'arguments': {'city': 'Tokyo'}, 'depends_on': []}]
Output: {'order': ['s1', 's2', 's3'], 'results': {'s1': {'status': 'error', 'output': 'error: divide raised ZeroDivisionError: division by zero'}, 's2': {'status': 'skipped', 'output': None}, 's3': {'status': 'ok', 'output': '24C, clear'}}}
Example 4: Cycles are rejected before anything runs
Input: plan = [{'id': 'a', 'tool': 'add', 'arguments': {'a': 1, 'b': 1}, 'depends_on': ['b']}, {'id': 'b', 'tool': 'add', 'arguments': {'a': 1, 'b': 1}, 'depends_on': ['a']}]
Output: Raises ValueError

Constraints

  • Detect a cycle when no remaining step is ready, before executing anything.
  • Skipping is transitive: a step that depends on a skipped step is also skipped.
  • `$`-strings that don't name a step are passed through unchanged.
Python3Saved ✓
import json
import math
import re


def execute_plan(plan):
"""Validate, topologically order and run a plan of tool steps."""
# Your code here
pass


# =============================================================================
# PROVIDED BELOW -- the sandbox and your earlier stages. Read it, don't edit it.
# =============================================================================
# ---------------------------------------------------------------------------
# The sandbox world your agent lives in (provided -- you don't edit this).
# Four deterministic tools, each with a JSON-Schema-style argument spec,
# exactly the shape you'd hand a real model's tool-calling API.
# ---------------------------------------------------------------------------
WEATHER = {"paris": "18C, light rain", "tokyo": "24C, clear", "lima": "16C, overcast"}
DOCS = {
"refund-policy": "Refunds are accepted within 30 days with a receipt.",
"shipping": "Standard shipping takes 3-5 business days.",
}


def add(a, b):
return a + b


def divide(a, b):
return a / b


def get_weather(city):
key = city.strip().lower()
Case 1: Output of one step feeds the next
Input: plan = [{'id': 's1', 'tool': 'add', 'arguments': {'a': 2, 'b': 3}, 'depends_on': []}, {'id': 's2', 'tool': 'add', 'arguments': {'a': '$s1', 'b': 10}, 'depends_on': ['s1']}]
Expected: {'order': ['s1', 's2'], 'results': {'s1': {'status': 'ok', 'output': '5'}, 's2': {'status': 'ok', 'output': '15'}}}
Case 2: Plan order isn't run order
Input: plan = [{'id': 'total', 'tool': 'add', 'arguments': {'a': '$x', 'b': 1}, 'depends_on': ['x']}, {'id': 'x', 'tool': 'divide', 'arguments': {'a': 9, 'b': 3}, 'depends_on': []}, {'id': 'w', 'tool': 'get_weather', 'arguments': {'city': 'Lima'}, 'depends_on': []}]
Expected: {'order': ['x', 'total', 'w'], 'results': {'x': {'status': 'ok', 'output': '3.0'}, 'total': {'status': 'ok', 'output': '4.0'}, 'w': {'status': 'ok', 'output': '16C, overcast'}}}
Case 3: Dependents of a failed step are skipped
Input: plan = [{'id': 's1', 'tool': 'divide', 'arguments': {'a': 1, 'b': 0}, 'depends_on': []}, {'id': 's2', 'tool': 'add', 'arguments': {'a': '$s1', 'b': 1}, 'depends_on': ['s1']}, {'id': 's3', 'tool': 'get_weather', 'arguments': {'city': 'Tokyo'}, 'depends_on': []}]
Expected: {'order': ['s1', 's2', 's3'], 'results': {'s1': {'status': 'error', 'output': 'error: divide raised ZeroDivisionError: division by zero'}, 's2': {'status': 'skipped', 'output': None}, 's3': {'status': 'ok', 'output': '24C, clear'}}}
Case 4: Cycles are rejected before anything runs
Input: plan = [{'id': 'a', 'tool': 'add', 'arguments': {'a': 1, 'b': 1}, 'depends_on': ['b']}, {'id': 'b', 'tool': 'add', 'arguments': {'a': 1, 'b': 1}, 'depends_on': ['a']}]
Expected: Raises ValueError