525 lines
19 KiB
Markdown
525 lines
19 KiB
Markdown
|
|
# Graph Runtime Audit
|
||
|
|
|
||
|
|
Date: 2026-08-15
|
||
|
|
|
||
|
|
Baseline inspected: `6a73372665715950aee8416a9d2b3ec79955f816`
|
||
|
|
|
||
|
|
Scope: audit current Artifex implementation against the intended graph-runtime architecture. This document does not propose or include a refactor.
|
||
|
|
|
||
|
|
## Executive Summary
|
||
|
|
|
||
|
|
`GraphRuntime` is currently an interface boundary, not the production execution engine.
|
||
|
|
|
||
|
|
`LangGraphRuntime` is a deterministic placeholder adapter. It is not executing project, milestone, feature, task, Coder, Reviewer, Judge, retry, or commit workflows.
|
||
|
|
|
||
|
|
Real production task execution is driven by `runtime_loop.autonomous_task_loop.AutonomousTaskLoop`, `graph.scheduler.TaskScheduler`, Django ORM state, direct Python service calls, and persisted Event Bus events.
|
||
|
|
|
||
|
|
The current implementation has two separate concepts that are easy to conflate:
|
||
|
|
|
||
|
|
- Project DAG: persisted project work decomposition and task dependencies.
|
||
|
|
- Execution Graph: the operational workflow for executing one task attempt through Coder, tools, tests, Reviewer, Judge, retry, and commit.
|
||
|
|
|
||
|
|
The Project DAG exists today in Django models and scheduler logic. The Execution Graph exists only as imperative Python control flow inside `AutonomousTaskLoop`; it is not represented as a graph runtime yet.
|
||
|
|
|
||
|
|
Recommendation: keep Django/Postgres as canonical state and migrate execution to graph semantics incrementally. Start by extracting an explicit `TaskExecutionGraph` description around the existing task loop without changing behavior, then add graph telemetry/checkpoint metadata. LangGraph should remain behind `GraphRuntime` and should provide value only when conditional routing, resumable execution, human gates, subgraphs, and agent-team workflows become difficult to manage in plain Python.
|
||
|
|
|
||
|
|
## Direct Answers
|
||
|
|
|
||
|
|
### 1. Is `GraphRuntime` used in production task execution?
|
||
|
|
|
||
|
|
No.
|
||
|
|
|
||
|
|
`graph/runtime.py` defines an abstract `GraphRuntime` with `start`, `pause`, `resume`, `cancel`, and `signal`, but no production task execution path depends on it.
|
||
|
|
|
||
|
|
Observed production path:
|
||
|
|
|
||
|
|
- `runtime_loop/autonomous_task_loop.py` imports `TaskScheduler`, `Coder`, `Reviewer`, `Judge`, `WorktreeTools`, and `DeterministicTestRunner`.
|
||
|
|
- It does not import or call `GraphRuntime` or `LangGraphRuntime`.
|
||
|
|
|
||
|
|
Current role of `GraphRuntime`: architectural boundary and future abstraction.
|
||
|
|
|
||
|
|
### 2. Is `LangGraphRuntime` actually executing task workflows?
|
||
|
|
|
||
|
|
No.
|
||
|
|
|
||
|
|
`graph/langgraph_runtime.py` returns a deterministic string from `start` and no-ops `pause`, `resume`, `cancel`, and `signal`.
|
||
|
|
|
||
|
|
It does not import LangGraph, build a state graph, execute graph nodes, route edges, persist graph checkpoints, or call Coder/Reviewer/Judge.
|
||
|
|
|
||
|
|
Its only observed test coverage is `tests/test_interfaces.py::test_langgraph_runtime_is_hidden_behind_runtime_interface`, which checks that `start()` returns a run id prefix.
|
||
|
|
|
||
|
|
## Real Execution Path
|
||
|
|
|
||
|
|
Current task execution path for a single task:
|
||
|
|
|
||
|
|
1. Scheduler
|
||
|
|
|
||
|
|
`AutonomousTaskLoop.run_once()` calls `TaskScheduler.claim_next_ready_task()`.
|
||
|
|
|
||
|
|
`TaskScheduler` queries `Task(status=READY)` ordered by priority and creation time, skips tasks with incomplete dependencies, marks the selected task `RUNNING`, persists it, and publishes `TASK_STARTED`.
|
||
|
|
|
||
|
|
2. Investigation/context building
|
||
|
|
|
||
|
|
`AutonomousTaskLoop._execute_task()` creates or reuses a worktree and builds context with `WorkerContextBuilder.build_for_task()`.
|
||
|
|
|
||
|
|
This context builder reads selected Python files and tests directly from the worktree, excludes migrations and secrets, and attaches the current diff.
|
||
|
|
|
||
|
|
Coder also has an inspect-before-edit phase inside `Coder.execute()` for sensitive tasks. It asks the model for read-only inspection operations, then executes `list_directory`, `read_file`, `search_code`, `find_symbol`, `git_status`, or `git_diff` through `WorktreeTools`.
|
||
|
|
|
||
|
|
3. Coder
|
||
|
|
|
||
|
|
`AutonomousTaskLoop` calls `Coder.execute(context, tools, project, agent_version)`.
|
||
|
|
|
||
|
|
`Coder.execute()` may perform inspection, then creates a `CoderToolLoop` and calls `CoderToolLoop.run()`.
|
||
|
|
|
||
|
|
4. Mutation
|
||
|
|
|
||
|
|
`CoderToolLoop` asks the model for mutation operations and applies them through `WorktreeTools`.
|
||
|
|
|
||
|
|
Supported mutation operations include:
|
||
|
|
|
||
|
|
- `write_file`
|
||
|
|
- `apply_patch`
|
||
|
|
- `delete_file`
|
||
|
|
- `move_file`
|
||
|
|
- `create_directory`
|
||
|
|
- `run_command`
|
||
|
|
|
||
|
|
Patch mismatch recovery is internal to `CoderToolLoop`: it records structured failure evidence, re-reads the live target file excerpt, and asks the model once more for a corrected patch. It does not default to full-file replacement.
|
||
|
|
|
||
|
|
5. Tests
|
||
|
|
|
||
|
|
`AutonomousTaskLoop` calls `DeterministicTestRunner.run()`.
|
||
|
|
|
||
|
|
`DeterministicTestRunner` shells out via `WorktreeTools.run()`, persists an `Artifact` with stdout/stderr/returncode, and creates a `TestRun` with `PASS` or `FAIL`.
|
||
|
|
|
||
|
|
On failure, `_attach_test_failure_evidence()` stores stdout/stderr excerpts into the attempt's `coder_result.metadata.test_failure_evidence`, so later retry context can include the actual traceback.
|
||
|
|
|
||
|
|
6. Reviewer
|
||
|
|
|
||
|
|
`AutonomousTaskLoop` stages intent-to-add with `git add -N`, gets `git diff`, then calls `Reviewer.review(task, reviewer_version, diff, test_status)`.
|
||
|
|
|
||
|
|
`Reviewer` is currently a deterministic Python policy. It creates a `Review` row with `PASS`, `REWORK_REQUIRED`, or `REJECTED` based on test status, empty diff, and health-route-specific heuristics.
|
||
|
|
|
||
|
|
7. Judge
|
||
|
|
|
||
|
|
If Reviewer passes, `AutonomousTaskLoop` calls `Judge.judge(project, task, judge_version, diff, test_status)`.
|
||
|
|
|
||
|
|
`Judge` is currently deterministic Python. It creates a `Verification` row with task-level acceptance evidence and `PASS` or `FAIL`.
|
||
|
|
|
||
|
|
8. Retry
|
||
|
|
|
||
|
|
If Coder, Reviewer, or Judge fails, `_retry_or_fail()` updates `TaskAttempt.status`, increments `Task.retry_count`, publishes `TASK_FAILED`, and either loops for another attempt or marks the task `FAILED` and publishes `TASK_RETRY_EXHAUSTED` plus final `TASK_FAILED`.
|
||
|
|
|
||
|
|
The effective retry budget is capped at two retries, giving three total attempts.
|
||
|
|
|
||
|
|
9. Commit
|
||
|
|
|
||
|
|
If tests, Reviewer, and Judge all pass, `WorktreeTools.commit_all()` stages and commits all changes. `CommitRecord` is persisted with coder, reviewer, judge, test run, review, verification, SHA, branch, and message.
|
||
|
|
|
||
|
|
The task is marked `COMPLETE`, `COMMIT_CREATED` and `TASK_COMPLETED` are published with telemetry, the worktree is validated clean, and the task worktree is cleaned up.
|
||
|
|
|
||
|
|
## Transition Classification
|
||
|
|
|
||
|
|
### Explicit graph nodes/edges
|
||
|
|
|
||
|
|
Currently only the Project DAG has explicit persisted edges:
|
||
|
|
|
||
|
|
- `TaskDependency(task, depends_on)` models task-to-task dependencies.
|
||
|
|
- `Milestone.dependencies` models milestone-level dependencies but is not currently central in execution scheduling.
|
||
|
|
- `ProjectPlanBuilder` validates task dependency acyclicity before materializing tasks.
|
||
|
|
- `TaskScheduler.claim_next_ready_task()` treats incomplete `TaskDependency` rows as blocking edges.
|
||
|
|
|
||
|
|
No explicit graph nodes/edges currently model the execution workflow inside a task attempt.
|
||
|
|
|
||
|
|
### Direct Python service calls
|
||
|
|
|
||
|
|
Most real execution transitions are direct service calls:
|
||
|
|
|
||
|
|
- `AutonomousTaskLoop.run_once()` -> `TaskScheduler.claim_next_ready_task()`
|
||
|
|
- `_execute_task()` -> `WorkerContextBuilder.build_for_task()`
|
||
|
|
- `_execute_task()` -> `Coder.execute()`
|
||
|
|
- `Coder.execute()` -> `CoderToolLoop.run()`
|
||
|
|
- `CoderToolLoop` -> `ModelRouter.complete()`
|
||
|
|
- `CoderToolLoop` -> `WorktreeTools` mutation/read/test helpers
|
||
|
|
- `_execute_task()` -> `DeterministicTestRunner.run()`
|
||
|
|
- `_execute_task()` -> `Reviewer.review()`
|
||
|
|
- `_execute_task()` -> `Judge.judge()`
|
||
|
|
- `_execute_task()` -> `WorktreeTools.commit_all()`
|
||
|
|
- `_retry_or_fail()` direct retry/fail transition
|
||
|
|
|
||
|
|
### Django state transitions
|
||
|
|
|
||
|
|
Django/Postgres is the canonical durable state machine today:
|
||
|
|
|
||
|
|
- `Task.status`: `READY`, `RUNNING`, `FAILED`, `COMPLETE`
|
||
|
|
- `Task.retry_count`
|
||
|
|
- `TaskAttempt.status`: `RUNNING`, `REWORK_REQUIRED`, `FAILED`, `COMPLETE`
|
||
|
|
- `Worktree.status`
|
||
|
|
- `TestRun.status`
|
||
|
|
- `Review.status`
|
||
|
|
- `Verification.result`
|
||
|
|
- `CommitRecord`
|
||
|
|
- `ModelRequest.status`
|
||
|
|
- `ProgenySignal.status`
|
||
|
|
|
||
|
|
### Event Bus driven transitions
|
||
|
|
|
||
|
|
The Event Bus persists facts but does not currently drive the control flow.
|
||
|
|
|
||
|
|
Events published today include:
|
||
|
|
|
||
|
|
- `PLAN_APPROVED`
|
||
|
|
- `MILESTONE_CREATED`
|
||
|
|
- `TASK_CREATED`
|
||
|
|
- `TASK_READY`
|
||
|
|
- `TASK_STARTED`
|
||
|
|
- `TEST_FAILED`
|
||
|
|
- `REVIEW_FAILED`
|
||
|
|
- `TASK_FAILED`
|
||
|
|
- `TASK_RETRY_EXHAUSTED` as a string event
|
||
|
|
- `COMMIT_CREATED`
|
||
|
|
- `TASK_COMPLETED`
|
||
|
|
- `AGENT_CREATED`
|
||
|
|
- `AGENT_PROMOTED`
|
||
|
|
- `PROGENY_SIGNAL_CREATED` as a string event from `ProgenyService`
|
||
|
|
|
||
|
|
These are audit/event facts, not asynchronous orchestration triggers in the current implementation.
|
||
|
|
|
||
|
|
## Project DAG vs Execution Graph
|
||
|
|
|
||
|
|
### Project DAG
|
||
|
|
|
||
|
|
The Project DAG answers: what work exists, and what depends on what?
|
||
|
|
|
||
|
|
Current implementation:
|
||
|
|
|
||
|
|
- `ProjectPlanContract` represents milestones, features, tasks, and task dependency references.
|
||
|
|
- `validate_project_plan()` validates task dependency references and cycle freedom.
|
||
|
|
- `ProjectPlanBuilder.apply()` persists `ProjectPlan`, `Milestone`, `Feature`, `Task`, and `TaskDependency` rows.
|
||
|
|
- `TaskScheduler` consumes `TaskDependency` rows to claim only unblocked `READY` tasks.
|
||
|
|
|
||
|
|
This is real and production-relevant today.
|
||
|
|
|
||
|
|
### Execution Graph
|
||
|
|
|
||
|
|
The Execution Graph answers: how is one task/feature/milestone autonomously executed?
|
||
|
|
|
||
|
|
Current implementation:
|
||
|
|
|
||
|
|
- This graph is implicit in `AutonomousTaskLoop._execute_task()`.
|
||
|
|
- Nodes are not persisted as graph nodes.
|
||
|
|
- Edges are Python `if`/`continue`/`return` branches.
|
||
|
|
- Looping is a Python `while task.retry_count <= effective_max_retries`.
|
||
|
|
- Checkpointing is coarse-grained through Django rows such as `TaskAttempt`, `TestRun`, `Review`, `Verification`, and `CommitRecord`.
|
||
|
|
|
||
|
|
The execution graph exists conceptually but not as a first-class runtime artifact.
|
||
|
|
|
||
|
|
## CoderToolLoop Placement
|
||
|
|
|
||
|
|
Recommended placement: graph node containing an internal tool loop.
|
||
|
|
|
||
|
|
Do not make `CoderToolLoop` the top-level execution graph yet.
|
||
|
|
|
||
|
|
Rationale:
|
||
|
|
|
||
|
|
- `CoderToolLoop` is a bounded inner loop for a single agent/tool interaction.
|
||
|
|
- Its state is local and tactical: inspection results, mutation results, patch mismatch recovery, operation telemetry.
|
||
|
|
- It should be resumable eventually, but it is not the same graph as the whole task lifecycle.
|
||
|
|
- Making every tool call a top-level graph node now would increase orchestration complexity before there is enough operational pressure.
|
||
|
|
|
||
|
|
Near-term target:
|
||
|
|
|
||
|
|
- `TaskExecutionGraph` has a `coder` node.
|
||
|
|
- The `coder` node invokes `Coder.execute()`.
|
||
|
|
- `Coder.execute()` owns `CoderToolLoop` internally.
|
||
|
|
- `CoderToolLoop` emits structured telemetry and failure evidence that the outer graph records.
|
||
|
|
|
||
|
|
Later target:
|
||
|
|
|
||
|
|
- If long-running coding sessions, human approvals, streamed model/tool execution, or partial resume become necessary, promote internal tool-loop steps to a nested subgraph.
|
||
|
|
|
||
|
|
## Runtime Capability Assessment
|
||
|
|
|
||
|
|
### Conditional edges
|
||
|
|
|
||
|
|
Required.
|
||
|
|
|
||
|
|
Current conditional branches include:
|
||
|
|
|
||
|
|
- no ready task -> stop
|
||
|
|
- dependencies incomplete -> skip task
|
||
|
|
- coder failed -> retry/fail
|
||
|
|
- tests failed -> still Review, then retry/fail through Review
|
||
|
|
- reviewer failed -> retry/fail
|
||
|
|
- judge failed -> retry/fail
|
||
|
|
- all checks passed -> commit
|
||
|
|
|
||
|
|
These map naturally to graph conditional edges.
|
||
|
|
|
||
|
|
### Loops
|
||
|
|
|
||
|
|
Required.
|
||
|
|
|
||
|
|
Current loops include:
|
||
|
|
|
||
|
|
- task retry loop capped at three total attempts
|
||
|
|
- CoderToolLoop patch recovery loop
|
||
|
|
- future investigation and experiment loops
|
||
|
|
|
||
|
|
The retry cap must remain durable and enforced by Django state, not hidden only inside graph runtime memory.
|
||
|
|
|
||
|
|
### Parallel branches
|
||
|
|
|
||
|
|
Useful, not urgent for current V1/V2 guarantees.
|
||
|
|
|
||
|
|
Good candidates later:
|
||
|
|
|
||
|
|
- independent task execution across the Project DAG
|
||
|
|
- parallel tests/static checks/security checks
|
||
|
|
- multi-agent review lanes
|
||
|
|
- Progeny benchmark replay across candidates
|
||
|
|
|
||
|
|
Parallelism should be introduced only after worktree isolation and resource contention controls are explicit.
|
||
|
|
|
||
|
|
### Subgraphs
|
||
|
|
|
||
|
|
Required long-term.
|
||
|
|
|
||
|
|
Natural subgraphs:
|
||
|
|
|
||
|
|
- Task execution
|
||
|
|
- Coder tool loop
|
||
|
|
- Reviewer/Judge verification
|
||
|
|
- Progeny investigation
|
||
|
|
- Progeny experiment/champion-challenger evaluation
|
||
|
|
- Project Brain planning/replanning
|
||
|
|
|
||
|
|
### Checkpoint/resume
|
||
|
|
|
||
|
|
Required, but Django/Postgres should remain canonical.
|
||
|
|
|
||
|
|
Current checkpointing already exists at coarse boundaries:
|
||
|
|
|
||
|
|
- task claimed
|
||
|
|
- attempt created
|
||
|
|
- coder result saved
|
||
|
|
- test run saved
|
||
|
|
- review saved
|
||
|
|
- verification saved
|
||
|
|
- commit saved
|
||
|
|
|
||
|
|
Graph checkpointing should augment these records, not replace them.
|
||
|
|
|
||
|
|
### Human approval nodes
|
||
|
|
|
||
|
|
Required for safe V2+ autonomy.
|
||
|
|
|
||
|
|
Likely approval gates:
|
||
|
|
|
||
|
|
- project plan approval
|
||
|
|
- task split/replan approval
|
||
|
|
- risky migration approval
|
||
|
|
- provider credential/resource approval
|
||
|
|
- promotion of Progeny candidates
|
||
|
|
- destructive infrastructure changes
|
||
|
|
|
||
|
|
Human approval should persist as Django state and be represented as paused graph nodes.
|
||
|
|
|
||
|
|
### Dynamic temporary agent teams
|
||
|
|
|
||
|
|
Required later, not required for the current loop.
|
||
|
|
|
||
|
|
Smart Investigation and Progeny experiments will likely need temporary teams such as:
|
||
|
|
|
||
|
|
- incident analyst
|
||
|
|
- domain expert
|
||
|
|
- prompt/policy editor
|
||
|
|
- benchmark designer
|
||
|
|
- reviewer challenger
|
||
|
|
|
||
|
|
The runtime should support this as graph metadata and persisted `AgentRun`/future team records.
|
||
|
|
|
||
|
|
### Graph versioning
|
||
|
|
|
||
|
|
Required before graph-based production execution.
|
||
|
|
|
||
|
|
Every accepted commit and Progeny investigation should know which execution graph version produced it. Without this, regressions in orchestration will be hard to diagnose.
|
||
|
|
|
||
|
|
### Graph telemetry
|
||
|
|
|
||
|
|
Required.
|
||
|
|
|
||
|
|
Existing telemetry should become graph-node telemetry:
|
||
|
|
|
||
|
|
- mutation operations
|
||
|
|
- patch success/mismatch rates
|
||
|
|
- write-file fallback rate
|
||
|
|
- model requests per task
|
||
|
|
- test duration/status
|
||
|
|
- review findings
|
||
|
|
- judge evidence
|
||
|
|
- retry count
|
||
|
|
- provider errors
|
||
|
|
- signal counts
|
||
|
|
|
||
|
|
This telemetry should feed Progeny.
|
||
|
|
|
||
|
|
### Progeny champion/challenger workflow graphs
|
||
|
|
|
||
|
|
Required for the future Replay Arena / experiment execution layer, but should not be the next refactor.
|
||
|
|
|
||
|
|
The Progeny workflow graph should model:
|
||
|
|
|
||
|
|
- investigation selection
|
||
|
|
- candidate generation
|
||
|
|
- benchmark construction
|
||
|
|
- champion/challenger replay
|
||
|
|
- judge/evaluator comparison
|
||
|
|
- approval/promotion/rejection
|
||
|
|
|
||
|
|
It should reuse the same runtime boundary but have separate graph versions from normal task execution.
|
||
|
|
|
||
|
|
## Where LangGraph Adds Material Value
|
||
|
|
|
||
|
|
LangGraph is likely valuable when Artifex needs durable, inspectable, conditional, resumable multi-agent workflows with nested loops.
|
||
|
|
|
||
|
|
Material-value areas:
|
||
|
|
|
||
|
|
- Explicit task execution graph with conditional retry/fail/commit branches.
|
||
|
|
- Smart Investigation graph: signals -> clusters -> hypotheses -> routing -> candidates.
|
||
|
|
- Human approval gates with pause/resume semantics.
|
||
|
|
- Progeny champion/challenger workflows.
|
||
|
|
- Dynamic temporary agent-team orchestration.
|
||
|
|
- Future parallel branches for tests/reviews/replays.
|
||
|
|
- Graph-level observability and visual traceability.
|
||
|
|
|
||
|
|
Low-value or risky areas for LangGraph right now:
|
||
|
|
|
||
|
|
- Replacing `TaskScheduler` as the canonical Project DAG scheduler.
|
||
|
|
- Replacing Django/Postgres state transitions.
|
||
|
|
- Turning every small Python helper call into a graph node.
|
||
|
|
- Moving worktree mutation safety into graph runtime abstractions.
|
||
|
|
- Hiding retry counters/checkpoints inside LangGraph-only state.
|
||
|
|
|
||
|
|
## Where Django/Postgres Should Remain Canonical
|
||
|
|
|
||
|
|
Django/Postgres should remain the source of truth for:
|
||
|
|
|
||
|
|
- Projects, plans, milestones, features, tasks, and task dependencies.
|
||
|
|
- Task statuses and retry counts.
|
||
|
|
- Worktrees and commit records.
|
||
|
|
- Attempts, coder results, reviews, judge verifications, test runs, artifacts.
|
||
|
|
- Events.
|
||
|
|
- Agent versions and promotion state.
|
||
|
|
- Progeny signals, investigations, and improvement candidates.
|
||
|
|
- Human approvals and audit history.
|
||
|
|
|
||
|
|
LangGraph, if introduced, should read/write through these canonical records rather than owning hidden state.
|
||
|
|
|
||
|
|
## Migration Path
|
||
|
|
|
||
|
|
### Phase 0: Preserve current behavior
|
||
|
|
|
||
|
|
Do not route production task execution through `LangGraphRuntime` yet.
|
||
|
|
|
||
|
|
Current guarantees to preserve:
|
||
|
|
|
||
|
|
- M2 autonomous execution
|
||
|
|
- M3 planning
|
||
|
|
- M4 Archaeologist
|
||
|
|
- M5 Agent Registry / Progeny
|
||
|
|
- M6 self-bootstrap
|
||
|
|
- CoderToolLoop
|
||
|
|
- worktree isolation
|
||
|
|
- inspect-before-edit
|
||
|
|
- safe mutation tools
|
||
|
|
- Reviewer/Judge separation
|
||
|
|
- deterministic tests
|
||
|
|
- three-attempt retry cap
|
||
|
|
- Sol/Qwen provider boundaries
|
||
|
|
- secret isolation
|
||
|
|
|
||
|
|
### Phase 1: Document the implicit execution graph
|
||
|
|
|
||
|
|
Add a declarative graph spec that mirrors current `AutonomousTaskLoop` behavior without executing it yet.
|
||
|
|
|
||
|
|
Suggested nodes:
|
||
|
|
|
||
|
|
- `claim_task`
|
||
|
|
- `prepare_worktree`
|
||
|
|
- `build_context`
|
||
|
|
- `coder`
|
||
|
|
- `run_tests`
|
||
|
|
- `review`
|
||
|
|
- `judge`
|
||
|
|
- `commit`
|
||
|
|
- `retry_or_fail`
|
||
|
|
- `cleanup`
|
||
|
|
|
||
|
|
Suggested conditional edges:
|
||
|
|
|
||
|
|
- `coder.failed -> retry_or_fail`
|
||
|
|
- `tests.failed -> review`
|
||
|
|
- `review.failed -> retry_or_fail`
|
||
|
|
- `judge.failed -> retry_or_fail`
|
||
|
|
- `judge.passed -> commit`
|
||
|
|
- `retry.available -> build_context`
|
||
|
|
- `retry.exhausted -> fail`
|
||
|
|
|
||
|
|
### Phase 2: Add graph run telemetry around the existing loop
|
||
|
|
|
||
|
|
Add persisted graph-run/node-run records or event payloads while keeping direct Python execution.
|
||
|
|
|
||
|
|
This gives observability before changing orchestration.
|
||
|
|
|
||
|
|
### Phase 3: Introduce `TaskExecutionRuntime` behind `GraphRuntime`
|
||
|
|
|
||
|
|
Implement a runtime adapter that can execute the graph spec using the existing service methods.
|
||
|
|
|
||
|
|
At this stage, `AutonomousTaskLoop` can delegate to the runtime but Django state remains canonical.
|
||
|
|
|
||
|
|
### Phase 4: Evaluate LangGraph as one adapter
|
||
|
|
|
||
|
|
Only after the explicit graph spec and telemetry exist, wire `LangGraphRuntime` as an implementation option.
|
||
|
|
|
||
|
|
Acceptance criteria should require deterministic parity with the current loop.
|
||
|
|
|
||
|
|
### Phase 5: Add subgraphs and human approval nodes
|
||
|
|
|
||
|
|
Promote Progeny investigation and champion/challenger replay workflows into graph subgraphs.
|
||
|
|
|
||
|
|
Add explicit approval nodes for candidate promotion and risky changes.
|
||
|
|
|
||
|
|
## Current Architecture Risks
|
||
|
|
|
||
|
|
1. `GraphRuntime` naming implies production graph execution, but it is currently a placeholder.
|
||
|
|
|
||
|
|
Risk: future contributors may assume LangGraph is already in the hot path.
|
||
|
|
|
||
|
|
2. Execution graph is implicit in Python.
|
||
|
|
|
||
|
|
Risk: retry and failure behavior becomes harder to evolve as Progeny wiring, investigation, and human gates are added.
|
||
|
|
|
||
|
|
3. Event Bus is persisted but not orchestration-driving.
|
||
|
|
|
||
|
|
Risk: event names may be interpreted as workflow triggers when they are currently audit facts.
|
||
|
|
|
||
|
|
4. `CoderToolLoop` has internal iteration but only coarse persistence.
|
||
|
|
|
||
|
|
Risk: power loss during model/tool-loop execution resumes only at task-attempt granularity, not tool-step granularity.
|
||
|
|
|
||
|
|
5. Reviewer/Judge are deterministic services, not model-driven graph nodes.
|
||
|
|
|
||
|
|
Risk: current separation is good, but future multi-agent review designs need explicit graph boundaries.
|
||
|
|
|
||
|
|
## Recommendation
|
||
|
|
|
||
|
|
Do not refactor execution into LangGraph immediately.
|
||
|
|
|
||
|
|
The safest next step is to keep the existing Python/Django loop as the production engine and create a first-class, documented `TaskExecutionGraph` specification plus telemetry records around it. This preserves all working guarantees while making the intended graph architecture concrete.
|
||
|
|
|
||
|
|
CoderToolLoop should remain an internal loop inside the Coder graph node for now. Promote it to a subgraph only when resumability or multi-step observability at each tool call becomes necessary.
|
||
|
|
|
||
|
|
LangGraph should be introduced as an adapter behind `GraphRuntime` only after deterministic parity tests prove it reproduces the current loop exactly.
|