{"slug": "acai-chapter-7-workflow-orchestration-and-agent-execution", "title": "ACAI — Chapter 7: Workflow Orchestration and Agent Execution", "summary": "A developer introduced Chapter 7 of the ACAI project, adding a workflow orchestration engine that decomposes complex AI requests into a task graph. The new system replaces the linear planner-router-model-verifier pipeline with a workflow builder that manages task dependencies, states, and retries, enabling multi-step operations like research, analysis, and report generation.", "body_md": "ACAI can now:\n\n```\nChapter 1 → Core API\nChapter 2 → Planner\nChapter 3 → Retrieval\nChapter 4 → Memory\nChapter 5 → Model Router\nChapter 6 → Verification\n```\n\nThe next limitation is that a complex request may require **multiple dependent operations**.\n\nFor example:\n\n```\n\"Research a topic, summarize the evidence,\ncompare the findings, and produce a report.\"\n```\n\nThis is not one simple task.\n\nIt can be represented as:\n\n```\nResearch\n   ↓\nCollect Evidence\n   ↓\nAnalyze\n   ↓\nCompare\n   ↓\nWrite Report\n   ↓\nVerify\n```\n\nChapter 7 introduces a workflow engine that represents these operations as a **task graph**.\n\nThe previous system was approximately:\n\n```\nUser\n ↓\nPlanner\n ↓\nRouter\n ↓\nModel\n ↓\nVerifier\n ↓\nResponse\n```\n\nThe new system becomes:\n\n```\nUser Goal\n    ↓\nPlanner\n    ↓\nWorkflow\n    ↓\nTask Graph\n    ↓\nExecutor\n    ↓\nVerification\n    ↓\nFinal Result\n```\n\nThe key idea is:\n\nA complex AI task should be decomposed into smaller executable steps.\n\nA workflow can be represented as a directed graph.\n\nExample:\n\n```\n             ┌───────────────┐\n             │    Research  │\n             └───────┬───────┘\n                     │\n             ┌───────▼───────┐\n             │  Extract Data │\n             └───────┬───────┘\n                     │\n             ┌───────▼───────┐\n             │    Analyze    │\n             └───────┬───────┘\n                     │\n              ┌──────┴──────┐\n              ▼             ▼\n        ┌──────────┐   ┌──────────┐\n        │ Compare  │   │ Validate │\n        └────┬─────┘   └────┬─────┘\n             │              │\n             └──────┬───────┘\n                    ▼\n              ┌───────────┐\n              │  Report   │\n              └─────┬─────┘\n                    ▼\n               Verification\n```\n\nSome tasks depend on previous tasks.\n\nOthers can execute independently.\n\nCreate:\n\n```\napp/services/workflow.py\n```\n\nStart with:\n\n``` python\nfrom dataclasses import dataclass, field\n\n@dataclass\nclass Task:\n\n    task_id: str\n\n    name: str\n\n    task_type: str\n\n    dependencies: list[str] = field(\n        default_factory=list\n    )\n\n    status: str = \"pending\"\n\n    result: str | None = None\n\n    error: str | None = None\n```\n\nEach task contains:\n\n```\ntask_id\nname\ntask_type\ndependencies\nstatus\nresult\nerror\n```\n\nA task should have explicit states.\n\n```\npending\n   ↓\nrunning\n   ↓\ncompleted\n```\n\nIf something fails:\n\n```\nrunning\n   ↓\nfailed\n```\n\nA retry can produce:\n\n```\nfailed\n   ↓\nretrying\n   ↓\nrunning\n```\n\nThe state machine is:\n\n```\n             ┌──────────┐\n             │ pending  │\n             └────┬─────┘\n                  ▼\n             ┌──────────┐\n             │ running  │\n             └────┬─────┘\n              ┌───┴───┐\n              ▼       ▼\n        ┌─────────┐ ┌────────┐\n        │complete │ │ failed │\n        └─────────┘ └───┬────┘\n                        │\n                        ▼\n                     retry\n```\n\nAdd:\n\n``` python\nfrom dataclasses import dataclass, field\n\n@dataclass\nclass Workflow:\n\n    workflow_id: str\n\n    tasks: dict[str, Task] = field(\n        default_factory=dict\n    )\n\n    status: str = \"pending\"\n\n    result: str | None = None\n```\n\nNow ACAI can represent:\n\n```\nWorkflow\n   ├── Task A\n   ├── Task B\n   ├── Task C\n   └── Task D\n```\n\nAdd:\n\n``` python\nclass WorkflowBuilder:\n\n    def __init__(\n        self,\n        workflow_id: str,\n    ) -> None:\n\n        self.workflow = Workflow(\n            workflow_id=workflow_id\n        )\n\n    def add_task(\n        self,\n        task_id: str,\n        name: str,\n        task_type: str,\n        dependencies: list[str] | None = None,\n    ) -> None:\n\n        if task_id in self.workflow.tasks:\n\n            raise ValueError(\n                f\"Task already exists: \"\n                f\"{task_id}\"\n            )\n\n        self.workflow.tasks[task_id] = Task(\n            task_id=task_id,\n            name=name,\n            task_type=task_type,\n            dependencies=(\n                dependencies or []\n            ),\n        )\n\n    def build(self) -> Workflow:\n\n        return self.workflow\n```\n\nCreate:\n\n``` python\nfrom uuid import uuid4\n\nbuilder = WorkflowBuilder(\n    workflow_id=str(uuid4())\n)\n\nbuilder.add_task(\n    task_id=\"research\",\n    name=\"Research topic\",\n    task_type=\"research\",\n)\n\nbuilder.add_task(\n    task_id=\"analysis\",\n    name=\"Analyze evidence\",\n    task_type=\"analysis\",\n    dependencies=[\n        \"research\"\n    ],\n)\n\nbuilder.add_task(\n    task_id=\"report\",\n    name=\"Write report\",\n    task_type=\"writing\",\n    dependencies=[\n        \"analysis\"\n    ],\n)\n\nworkflow = builder.build()\n```\n\nThe dependency graph is:\n\n```\nresearch\n   ↓\nanalysis\n   ↓\nreport\n```\n\nA workflow should reject invalid dependencies.\n\nAdd:\n\n``` php\ndef validate_workflow(\n    workflow: Workflow,\n) -> None:\n\n    task_ids = set(\n        workflow.tasks.keys()\n    )\n\n    for task in workflow.tasks.values():\n\n        for dependency in task.dependencies:\n\n            if dependency not in task_ids:\n\n                raise ValueError(\n                    f\"Unknown dependency \"\n                    f\"{dependency} for task \"\n                    f\"{task.task_id}\"\n                )\n```\n\nThis prevents:\n\n```\nTask A\n ↓\nMissing Task X\n```\n\nfrom reaching execution.\n\nA more dangerous problem is:\n\n```\nTask A\n ↓\nTask B\n ↓\nTask A\n```\n\nThis creates a cycle.\n\nAdd:\n\n``` php\ndef detect_cycle(\n    workflow: Workflow,\n) -> bool:\n\n    visiting = set()\n    visited = set()\n\n    def visit(\n        task_id: str,\n    ) -> bool:\n\n        if task_id in visiting:\n            return True\n\n        if task_id in visited:\n            return False\n\n        visiting.add(task_id)\n\n        task = workflow.tasks[task_id]\n\n        for dependency in task.dependencies:\n\n            if visit(dependency):\n                return True\n\n        visiting.remove(task_id)\n\n        visited.add(task_id)\n\n        return False\n\n    for task_id in workflow.tasks:\n\n        if visit(task_id):\n            return True\n\n    return False\n```\n\nThen:\n\n``` php\ndef validate_workflow(\n    workflow: Workflow,\n) -> None:\n\n    task_ids = set(\n        workflow.tasks.keys()\n    )\n\n    for task in workflow.tasks.values():\n\n        for dependency in task.dependencies:\n\n            if dependency not in task_ids:\n\n                raise ValueError(\n                    f\"Unknown dependency \"\n                    f\"{dependency}\"\n                )\n\n    if detect_cycle(workflow):\n\n        raise ValueError(\n            \"Workflow contains a cycle.\"\n        )\n```\n\nThe executor needs to determine which tasks can run.\n\nA task is ready when:\n\n```\nstatus = pending\n```\n\nand every dependency is:\n\n```\ncompleted\n```\n\nAdd:\n\n``` php\ndef get_ready_tasks(\n    workflow: Workflow,\n) -> list[Task]:\n\n    ready = []\n\n    for task in workflow.tasks.values():\n\n        if task.status != \"pending\":\n            continue\n\n        dependencies_completed = all(\n            workflow.tasks[\n                dependency\n            ].status == \"completed\"\n            for dependency\n            in task.dependencies\n        )\n\n        if dependencies_completed:\n\n            ready.append(task)\n\n    return ready\n```\n\nCreate:\n\n``` python\nclass WorkflowExecutor:\n\n    async def execute(\n        self,\n        workflow: Workflow,\n    ) -> Workflow:\n\n        validate_workflow(workflow)\n\n        workflow.status = \"running\"\n\n        while True:\n\n            ready_tasks = get_ready_tasks(\n                workflow\n            )\n\n            if not ready_tasks:\n\n                unfinished = [\n                    task\n                    for task\n                    in workflow.tasks.values()\n                    if task.status\n                    not in {\n                        \"completed\",\n                        \"failed\",\n                    }\n                ]\n\n                if unfinished:\n\n                    raise RuntimeError(\n                        \"Workflow cannot make \"\n                        \"further progress.\"\n                    )\n\n                break\n\n            for task in ready_tasks:\n\n                await self.execute_task(\n                    task,\n                    workflow,\n                )\n\n        workflow.status = \"completed\"\n\n        return workflow\n```\n\nAdd:\n\n``` python\n    async def execute_task(\n        self,\n        task: Task,\n        workflow: Workflow,\n    ) -> None:\n\n        task.status = \"running\"\n\n        try:\n\n            result = await self.run_task(\n                task,\n                workflow,\n            )\n\n            task.result = result\n\n            task.status = \"completed\"\n\n        except Exception as exc:\n\n            task.error = str(exc)\n\n            task.status = \"failed\"\n\n            workflow.status = \"failed\"\n\n            raise\n```\n\nFor the first prototype:\n\n``` python\n    async def run_task(\n        self,\n        task: Task,\n        workflow: Workflow,\n    ) -> str:\n\n        if task.task_type == \"research\":\n\n            return (\n                \"Research task completed.\"\n            )\n\n        if task.task_type == \"analysis\":\n\n            return (\n                \"Analysis task completed.\"\n            )\n\n        if task.task_type == \"writing\":\n\n            return (\n                \"Writing task completed.\"\n            )\n\n        return (\n            f\"Task {task.name} completed.\"\n        )\n```\n\nThis is intentionally a mock implementation.\n\nLater it will call:\n\n```\nResearch\n→ Retrieval Service\n\nAnalysis\n→ Model Router + Model\n\nWriting\n→ Model Router + Model\n\nVerification\n→ Verification Service\n```\n\nThe prototype can therefore be:\n\n``` python\nclass WorkflowExecutor:\n\n    async def execute(\n        self,\n        workflow: Workflow,\n    ) -> Workflow:\n\n        validate_workflow(workflow)\n\n        workflow.status = \"running\"\n\n        while True:\n\n            ready_tasks = get_ready_tasks(\n                workflow\n            )\n\n            if not ready_tasks:\n\n                unfinished = [\n                    task\n                    for task\n                    in workflow.tasks.values()\n                    if task.status\n                    not in {\n                        \"completed\",\n                        \"failed\",\n                    }\n                ]\n\n                if unfinished:\n\n                    raise RuntimeError(\n                        \"Workflow cannot make \"\n                        \"further progress.\"\n                    )\n\n                break\n\n            for task in ready_tasks:\n\n                await self.execute_task(\n                    task,\n                    workflow,\n                )\n\n        workflow.status = \"completed\"\n\n        return workflow\n\n    async def execute_task(\n        self,\n        task: Task,\n        workflow: Workflow,\n    ) -> None:\n\n        task.status = \"running\"\n\n        try:\n\n            result = await self.run_task(\n                task,\n                workflow,\n            )\n\n            task.result = result\n\n            task.status = \"completed\"\n\n        except Exception as exc:\n\n            task.error = str(exc)\n\n            task.status = \"failed\"\n\n            workflow.status = \"failed\"\n\n            raise\n\n    async def run_task(\n        self,\n        task: Task,\n        workflow: Workflow,\n    ) -> str:\n\n        if task.task_type == \"research\":\n\n            return (\n                \"Research task completed.\"\n            )\n\n        if task.task_type == \"analysis\":\n\n            return (\n                \"Analysis task completed.\"\n            )\n\n        if task.task_type == \"writing\":\n\n            return (\n                \"Writing task completed.\"\n            )\n\n        return (\n            f\"Task {task.name} completed.\"\n        )\n```\n\nFor:\n\n```\nResearch\n ↓\nAnalysis\n ↓\nReport\n```\n\nexecution becomes:\n\n```\nResearch\n   ↓\nCOMPLETED\n   ↓\nAnalysis\n   ↓\nCOMPLETED\n   ↓\nReport\n   ↓\nCOMPLETED\n```\n\nConsider:\n\n```\n           Research\n          /        \\\n         ▼          ▼\n      Source A    Source B\n         │          │\n         └────┬─────┘\n              ▼\n           Analysis\n```\n\nSource A and Source B do not depend on each other.\n\nThey can therefore run in parallel.\n\nThe architecture becomes:\n\n```\n             Research\n                 │\n          ┌──────┴──────┐\n          ▼             ▼\n       Source A       Source B\n          │             │\n          └──────┬──────┘\n                 ▼\n              Analysis\n```\n\nPython's `asyncio`\n\ncan execute independent asynchronous tasks concurrently.\n\nAdd:\n\n``` python\nimport asyncio\n```\n\nThen replace the sequential loop:\n\n```\nfor task in ready_tasks:\n\n    await self.execute_task(\n        task,\n        workflow,\n    )\n```\n\nwith:\n\n```\nawait asyncio.gather(\n    *[\n        self.execute_task(\n            task,\n            workflow,\n        )\n        for task in ready_tasks\n    ]\n)\n```\n\nNow independent tasks can execute concurrently.\n\nSuppose:\n\n```\nTask A = 5 seconds\nTask B = 5 seconds\n```\n\nSequential execution can take approximately:\n\n```\n5 + 5 = 10 seconds\n```\n\nIf they are independent and safely executed concurrently, idealized execution can approach:\n\n```\nmax(5, 5) = 5 seconds\n```\n\nReal systems have overhead, rate limits, network latency, and resource constraints, so actual performance must be measured.\n\nReal workflows fail.\n\nPossible causes:\n\n```\nNetwork error\nProvider timeout\nTemporary API failure\nRate limit\nInvalid response\nDependency failure\n```\n\nA task should therefore support bounded retries.\n\nAdd:\n\n```\n@dataclass\nclass Task:\n\n    task_id: str\n\n    name: str\n\n    task_type: str\n\n    dependencies: list[str] = field(\n        default_factory=list\n    )\n\n    status: str = \"pending\"\n\n    result: str | None = None\n\n    error: str | None = None\n\n    attempts: int = 0\n\n    max_attempts: int = 3\nphp\nasync def execute_task(\n    self,\n    task: Task,\n    workflow: Workflow,\n) -> None:\n\n    while task.attempts < task.max_attempts:\n\n        task.attempts += 1\n\n        task.status = \"running\"\n\n        try:\n\n            result = await self.run_task(\n                task,\n                workflow,\n            )\n\n            task.result = result\n\n            task.status = \"completed\"\n\n            return\n\n        except Exception as exc:\n\n            task.error = str(exc)\n\n            if (\n                task.attempts\n                >= task.max_attempts\n            ):\n\n                task.status = \"failed\"\n\n                raise\n\n            task.status = \"retrying\"\n```\n\nThis gives:\n\n```\nAttempt 1\n   ↓\nFail\n   ↓\nAttempt 2\n   ↓\nFail\n   ↓\nAttempt 3\n   ↓\nSuccess / Failure\n```\n\nRetries should not be automatic for every error.\n\nFor example:\n\n```\nInvalid input\n```\n\nmay not become valid by repeating the same request.\n\nBut:\n\n```\nTemporary network failure\n```\n\nmight succeed on retry.\n\nTherefore future versions should classify errors:\n\n```\nTransient\nPermanent\nUnknown\n```\n\nThen retry only appropriate failures.\n\nA task that never completes can block an entire workflow.\n\nUse:\n\n``` python\nimport asyncio\n```\n\nand:\n\n```\nresult = await asyncio.wait_for(\n    self.run_task(\n        task,\n        workflow,\n    ),\n    timeout=60,\n)\n```\n\nThis creates a maximum execution window.\n\nSuppose:\n\n```\nTask A → completed\nTask B → failed\nTask C → depends on B\n```\n\nTask C cannot safely execute.\n\nTherefore:\n\n```\nA → COMPLETE\n\nB → FAILED\n\nC → BLOCKED\n```\n\nThe system should distinguish:\n\n```\nfailed\n```\n\nfrom:\n\n```\nblocked\n```\n\nAdd:\n\n```\npending\nrunning\nretrying\ncompleted\nfailed\nblocked\n```\n\nThe workflow can produce a final result from completed tasks.\n\nExample:\n\n``` php\ndef collect_results(\n    workflow: Workflow,\n) -> dict[str, str]:\n\n    return {\n        task.task_id: task.result\n        for task in workflow.tasks.values()\n        if task.result is not None\n    }\n```\n\nThen:\n\n```\nWorkflow\n   ↓\nTask Results\n   ↓\nResult Aggregation\n   ↓\nFinal Answer\n```\n\nWorkflow execution should not end immediately after generation.\n\nA final verification task should be added.\n\nExample:\n\n```\nResearch\n   ↓\nAnalysis\n   ↓\nDraft\n   ↓\nVerification\n   ↓\nFinal\n```\n\nThe verification task can inspect:\n\n```\nDraft\n+\nEvidence\n+\nOriginal User Goal\n```\n\nThen return:\n\n```\nPASS\n```\n\nor:\n\n```\nREVISION_REQUIRED\n```\n\nArchitecture:\n\n```\n                 USER GOAL\n                     │\n                     ▼\n                  PLANNER\n                     │\n                     ▼\n                TASK GRAPH\n                     │\n          ┌──────────┼──────────┐\n          ▼          ▼          ▼\n        Task A      Task B     Task C\n          │          │          │\n          └──────────┼──────────┘\n                     ▼\n                  Draft\n                     │\n                     ▼\n                Verification\n                     │\n                ┌────┴────┐\n                ▼         ▼\n              PASS      REVISE\n                │         │\n                ▼         ▼\n             Final      Retry\n```\n\nAt this point ACAI begins to resemble an agentic workflow system.\n\nBut an important distinction should be maintained:\n\n```\nAgent\n≠\nUncontrolled autonomous process\n```\n\nA practical agent should have:\n\n```\nGoal\n+\nTools\n+\nState\n+\nConstraints\n+\nTermination Conditions\n```\n\nCreate:\n\n```\n@dataclass\nclass AgentState:\n\n    goal: str\n\n    current_task: str | None = None\n\n    completed_tasks: list[str] = field(\n        default_factory=list\n    )\n\n    failed_tasks: list[str] = field(\n        default_factory=list\n    )\n\n    observations: list[str] = field(\n        default_factory=list\n    )\n```\n\nThe agent can now maintain execution state.\n\nThe conceptual loop is:\n\n```\nGoal\n ↓\nObserve\n ↓\nPlan\n ↓\nAct\n ↓\nObserve Result\n ↓\nVerify\n ↓\nContinue / Stop\n```\n\nImplementation:\n\n``` php\nasync def run_agent(\n    goal: str,\n) -> AgentState:\n\n    state = AgentState(\n        goal=goal\n    )\n\n    while True:\n\n        # Observe\n        observation = (\n            \"Current workflow state\"\n        )\n\n        state.observations.append(\n            observation\n        )\n\n        # Plan\n        task = choose_next_task(\n            state\n        )\n\n        if task is None:\n            break\n\n        # Act\n        state.current_task = task\n\n        result = await execute_agent_task(\n            task\n        )\n\n        # Record\n        state.completed_tasks.append(\n            task\n        )\n\n    return state\n```\n\nThis is a simplified demonstration.\n\nAn agent must have clear stopping conditions.\n\nFor example:\n\n```\nGoal achieved\nOR\nMaximum steps reached\nOR\nMaximum time reached\nOR\nNo valid action available\nOR\nCritical failure\n```\n\nWithout termination conditions:\n\n```\nAgent\n ↓\nAction\n ↓\nAction\n ↓\nAction\n ↓\n...\n```\n\ncould continue indefinitely.\n\nAdd:\n\n```\nMAX_AGENT_STEPS = 10\n```\n\nThen:\n\n```\nfor step in range(\n    MAX_AGENT_STEPS\n):\n\n    ...\n```\n\nThis gives the system a hard upper bound.\n\nA future ACAI agent can use controlled tools:\n\n```\nRetrieval\nFile Search\nCalculator\nCode Executor\nDatabase\nExternal API\nModel\n```\n\nThe architecture should be:\n\n```\nAgent\n  │\n  ▼\nTool Selection\n  │\n  ▼\nPermission Check\n  │\n  ▼\nTool Execution\n  │\n  ▼\nResult Validation\n```\n\nThe permission layer is important.\n\nAn agent should not automatically receive unrestricted access to arbitrary systems.\n\nCreate:\n\n``` php\nclass ToolRegistry:\n\n    def __init__(self) -> None:\n\n        self.tools = {}\n\n    def register(\n        self,\n        name: str,\n        function,\n    ) -> None:\n\n        self.tools[name] = function\n\n    def get(\n        self,\n        name: str,\n    ):\n\n        return self.tools.get(name)\n\n    def list_tools(self) -> list[str]:\n\n        return list(\n            self.tools.keys()\n        )\n```\n\nExample:\n\n```\nregistry = ToolRegistry()\n\nregistry.register(\n    \"retrieval\",\n    retrieval_service,\n)\n```\n\nNow the agent can discover available tools through a controlled registry.\n\nBefore tool execution:\n\n```\nAgent\n ↓\nRequested Tool\n ↓\nPermission Policy\n ↓\nAllowed?\n ┌──┴──┐\nYES    NO\n │      │\n ▼      ▼\nRun   Reject\n```\n\nExample:\n\n``` python\nclass ToolPolicy:\n\n    def __init__(\n        self,\n        allowed_tools: set[str],\n    ) -> None:\n\n        self.allowed_tools = (\n            allowed_tools\n        )\n\n    def allowed(\n        self,\n        tool_name: str,\n    ) -> bool:\n\n        return (\n            tool_name\n            in self.allowed_tools\n        )\n```\n\nThis makes tool access explicit.\n\nWorkflow execution needs detailed logs.\n\nFor each task:\n\n```\nworkflow_id\ntask_id\ntask_type\nstart_time\nend_time\nduration\nstatus\nattempt\nerror\n```\n\nExample:\n\n```\nevent = {\n    \"workflow_id\":\n        workflow.workflow_id,\n\n    \"task_id\":\n        task.task_id,\n\n    \"status\":\n        task.status,\n\n    \"attempt\":\n        task.attempts,\n}\n```\n\nThese events can later be sent to a logging system.\n\nCreate:\n\n```\ntests/test_workflow.py\n```\n\nAdd:\n\n``` python\nimport pytest\n\nfrom app.services.workflow import (\n    Task,\n    Workflow,\n    WorkflowBuilder,\n    get_ready_tasks,\n    validate_workflow,\n)\n```\n\nTest task dependencies:\n\n``` python\ndef test_ready_tasks():\n\n    workflow = Workflow(\n        workflow_id=\"test\"\n    )\n\n    workflow.tasks[\"a\"] = Task(\n        task_id=\"a\",\n        name=\"A\",\n        task_type=\"general\",\n    )\n\n    workflow.tasks[\"b\"] = Task(\n        task_id=\"b\",\n        name=\"B\",\n        task_type=\"general\",\n        dependencies=[\"a\"],\n    )\n\n    ready = get_ready_tasks(\n        workflow\n    )\n\n    assert len(ready) == 1\n\n    assert ready[0].task_id == \"a\"\npython\ndef test_invalid_dependency():\n\n    workflow = Workflow(\n        workflow_id=\"test\"\n    )\n\n    workflow.tasks[\"a\"] = Task(\n        task_id=\"a\",\n        name=\"A\",\n        task_type=\"general\",\n        dependencies=[\"missing\"],\n    )\n\n    with pytest.raises(ValueError):\n\n        validate_workflow(\n            workflow\n        )\npython\ndef test_cycle_detection():\n\n    workflow = Workflow(\n        workflow_id=\"test\"\n    )\n\n    workflow.tasks[\"a\"] = Task(\n        task_id=\"a\",\n        name=\"A\",\n        task_type=\"general\",\n        dependencies=[\"b\"],\n    )\n\n    workflow.tasks[\"b\"] = Task(\n        task_id=\"b\",\n        name=\"B\",\n        task_type=\"general\",\n        dependencies=[\"a\"],\n    )\n\n    with pytest.raises(ValueError):\n\n        validate_workflow(\n            workflow\n        )\npython\nimport pytest\n\nfrom app.services.workflow import (\n    WorkflowBuilder,\n    WorkflowExecutor,\n)\n\n@pytest.mark.asyncio\nasync def test_workflow_execution():\n\n    builder = WorkflowBuilder(\n        workflow_id=\"demo\"\n    )\n\n    builder.add_task(\n        task_id=\"research\",\n        name=\"Research\",\n        task_type=\"research\",\n    )\n\n    builder.add_task(\n        task_id=\"analysis\",\n        name=\"Analysis\",\n        task_type=\"analysis\",\n        dependencies=[\n            \"research\"\n        ],\n    )\n\n    workflow = builder.build()\n\n    executor = WorkflowExecutor()\n\n    result = await executor.execute(\n        workflow\n    )\n\n    assert result.status == \"completed\"\n\n    assert (\n        result.tasks[\"research\"]\n        .status\n        == \"completed\"\n    )\n\n    assert (\n        result.tasks[\"analysis\"]\n        .status\n        == \"completed\"\n    )\n```\n\nRun:\n\n```\npytest\n```\n\nYou should now have coverage for:\n\n```\nAPI\nPlanner\nRetrieval\nMemory\nRouter\nVerification\nWorkflow\nUSER\n                                │\n                                ▼\n                           FastAPI API\n                                │\n                                ▼\n                         ORCHESTRATOR\n                                │\n                                ▼\n                            PLANNER\n                                │\n                                ▼\n                         WORKFLOW GRAPH\n                                │\n               ┌────────────────┼────────────────┐\n               ▼                ▼                ▼\n             TASK A           TASK B           TASK C\n               │                │                │\n               └────────────────┼────────────────┘\n                                │\n                                ▼\n                         MODEL ROUTER\n                                │\n                                ▼\n                          MODEL SERVICE\n                                │\n                                ▼\n                           GENERATION\n                                │\n                                ▼\n                         VERIFICATION\n                                │\n                         ┌──────┴──────┐\n                         ▼             ▼\n                       PASS          REVISE\n                         │             │\n                         ▼             ▼\n                       RESULT        RETRY\n```\n\nAfter Chapter 7, the architecture can conceptually:\n\n```\n[✓] Receive a request\n[✓] Analyze the task\n[✓] Retrieve information\n[✓] Use memory\n[✓] Select a model\n[✓] Create multiple tasks\n[✓] Handle dependencies\n[✓] Execute independent tasks concurrently\n[✓] Retry bounded failures\n[✓] Apply timeouts\n[✓] Verify outputs\n[✓] Produce a final result\n```\n\nThis is substantially more capable than a simple:\n\n```\nPrompt → Model → Answer\n```\n\npipeline.\n\nThe architecture should **not** yet be described as:\n\n```\nAGI\nHuman-level intelligence\nFully autonomous intelligence\nGuaranteed factual AI\nSelf-improving superintelligence\n```\n\nThose claims would require evidence far beyond the architecture described here.\n\nA technically defensible description is:\n\nACAI is a modular AI orchestration architecture that combines planning, retrieval, memory, model routing, workflow execution, and output verification.\n\nThe architecture now has execution capabilities.\n\nThe next major requirement is **persistent data and production infrastructure**.\n\nCurrently:\n\n```\nMemory\n→ In-memory Python objects\n\nWorkflow\n→ Runtime objects\n\nLogs\n→ Basic application logging\n```\n\nThese disappear when the process stops unless persistent storage is added.\n\nTherefore the next chapter will introduce:\n\nThe architecture will move toward:\n\n```\n                    ACAI\n                     │\n          ┌──────────┼──────────┐\n          ▼          ▼          ▼\n       Compute     Storage    Observability\n          │          │          │\n          ▼          ▼          ▼\n       Workers    Database    Metrics\n                     │\n               ┌─────┴─────┐\n               ▼           ▼\n            Memory       Workflows\n```\n\nThe next stage will cover:\n\n```\nDatabase schema\nPersistent memory\nWorkflow persistence\nRequest IDs\nError handling\nRate limiting\nCaching\nBackground jobs\nHealth checks\nProduction configuration\n```\n\n**End of Chapter 7**", "url": "https://wpnews.pro/news/acai-chapter-7-workflow-orchestration-and-agent-execution", "canonical_source": "https://dev.to/black_shadow_team/acai-chapter-7-workflow-orchestration-and-agent-execution-15im", "published_at": "2026-08-30 16:35:21+00:00", "updated_at": "2026-08-30 16:53:03.426977+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["ACAI"], "alternates": {"html": "https://wpnews.pro/news/acai-chapter-7-workflow-orchestration-and-agent-execution", "markdown": "https://wpnews.pro/news/acai-chapter-7-workflow-orchestration-and-agent-execution.md", "text": "https://wpnews.pro/news/acai-chapter-7-workflow-orchestration-and-agent-execution.txt", "jsonld": "https://wpnews.pro/news/acai-chapter-7-workflow-orchestration-and-agent-execution.jsonld"}}