mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-21 21:03:38 +08:00
Refine course progression and runtime safety
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
s01 → ... → s16 → [s17](../s17_integrated_harness/) → `s18` → [s19](../s19_goal_loop/)
|
||||
|
||||
> *"One tool_use runs an entire orchestration"* — The `Workflow` tool starts a deterministic, recoverable script runtime that dispatches many subagents in bulk.
|
||||
> *"One tool_use runs an entire orchestration"* — The `Workflow` tool starts a deterministic, recoverable script runtime that coordinates many agent calls.
|
||||
>
|
||||
> **Harness layer**: Orchestration — a deterministic multi-agent script runtime above the single-agent loop.
|
||||
|
||||
@@ -22,7 +22,7 @@ Making the model drive this process one round at a time in the main loop is slow
|
||||
|
||||
## Put the Plan in Code, Not in a Sequence of Chat Turns
|
||||
|
||||
Add a `Workflow` tool to the harness tool pool. The user or model provides a script that expresses deterministic orchestration through a few simple primitives: `agent()`, `parallel()`, `pipeline()`, and `phase()`.
|
||||
Add a `Workflow` tool to the harness tool pool. The host registers trusted scripts built from `agent()`, `parallel()`, `pipeline()`, and `phase()`. The model supplies only a saved workflow name, arguments, and an optional run ID to resume; it does not send executable code or metadata.
|
||||
|
||||
The main loop sees only one `tool_use`. As the script runs, the runtime emits lifecycle and progress events and records every step in a journal on disk. When the script finishes, the call returns the launch envelope, result, and task state. Intermediate script results live in variables instead of taking space in conversation history. When restarted with `resume_from_run_id`, unchanged `agent()` calls hit the journal cache and reuse previous results, resuming from the checkpoint.
|
||||
|
||||
@@ -41,29 +41,41 @@ async def sample_workflow(ctx, args):
|
||||
|
||||
## The Workflow Tool: One Call, One Complete Run
|
||||
|
||||
`Workflow` lives in the main agent's tool pool. The user can request a saved workflow, or the model can select the tool when a task matches a known orchestration. In either case, the model emits one `Workflow(...)` tool call.
|
||||
`Workflow` is added to the s17 host's existing tool pool. The user can request a saved workflow, or the model can select it when a task matches a known orchestration. The adapter resolves the name through the host-owned `WORKFLOWS` registry, then passes its trusted metadata and function to the runtime. The other s17 tools remain available in the same loop.
|
||||
|
||||
The tool parses the arguments, validates metadata, checks permissions, registers a local workflow task, and emits `async_launched` before running the script. Progress events follow, then the final `task_notification`; the call returns the launch envelope, result, and task state.
|
||||
The model-facing schema accepts `name`, `args`, and `resume_from_run_id`. Unknown names and malformed arguments become an error tool result instead of ending the host loop. The runtime then validates the registered metadata, checks permissions, registers a local workflow task, and emits `async_launched` before running the script. Progress events follow, then the final `task_notification`; the call returns JSON-safe launch information, result, and task state.
|
||||
|
||||
```python
|
||||
class WorkflowTool:
|
||||
async def call(self, meta, script_fn, args=None, resume_from_run_id=None):
|
||||
validate_meta(meta)
|
||||
check_permission(meta)
|
||||
run_id = resume_from_run_id or create_run_id(meta)
|
||||
task = LocalWorkflowTask(create_task_id(run_id), run_id, meta)
|
||||
task.event("async_launched", runId=run_id, taskId=task.task_id)
|
||||
...
|
||||
result = await script_fn(ctx, args)
|
||||
task.event("task_notification", status=task.status)
|
||||
return {"launched": launched, "result": result, "task": task}
|
||||
WORKFLOW_TOOL = {
|
||||
"name": "Workflow",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"args": {"type": "object"},
|
||||
"resume_from_run_id": {"type": "string"},
|
||||
},
|
||||
"required": ["name"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
}
|
||||
|
||||
async def run_workflow(name, args=None, resume_from_run_id=None):
|
||||
meta, script_fn = WORKFLOWS[name]
|
||||
out = await WorkflowTool().call(
|
||||
meta, script_fn,
|
||||
args=args,
|
||||
resume_from_run_id=resume_from_run_id,
|
||||
)
|
||||
return {"launched": out["launched"], "result": out["result"],
|
||||
"task": serialize_task(out["task"])}
|
||||
```
|
||||
|
||||
## Workflow Metadata: Validate Before Launch
|
||||
|
||||
Each workflow registers a metadata object with `name`, `description`, and optional `phases`. The runtime validates it before executing any workflow code. `name` and `description` identify the task in the UI, while `phases` names groups in the progress display.
|
||||
Each saved workflow registers trusted metadata with `name`, `description`, and optional `phases`. The runtime validates it before executing workflow code. `name` and `description` identify the task in the UI, while `phases` names groups in the progress display. These fields belong to the host registry, not to model input.
|
||||
|
||||
Invalid input raises `WorkflowInputError` immediately and is rejected during registration. This is the same idea as validating cron expressions in s14: do not wait until execution to discover a bad script.
|
||||
Invalid registration raises `WorkflowInputError` before launch. This is the same idea as validating cron expressions in s14: do not wait until execution to discover a bad saved workflow.
|
||||
|
||||
Because the runtime uses `meta.name` in local artifact filenames, it also requires a 1-64 character safe slug containing letters, numbers, `.`, `_`, or `-`.
|
||||
|
||||
@@ -85,7 +97,7 @@ def validate_meta(meta):
|
||||
|
||||
## Orchestration Primitives: A Small Set Is Enough for Every Flow
|
||||
|
||||
A script runs in an isolated context with only a small set of orchestration primitives as globals. The script does not read files or run shell commands directly. All real code operations are performed by dispatched subagents under their own tool permissions. These primitives are methods on `ExecutionState`:
|
||||
A script receives an `ExecutionState` exposing a small set of orchestration primitives. It does not read files or run shell commands directly. A production integration would put a real agent runner behind `agent()` and keep that runner's tool permissions. This chapter uses `MockAgentRunner` so journal and resume behavior are repeatable; its review findings are fixtures, not a real code audit.
|
||||
|
||||
| Primitive | Purpose |
|
||||
|------|------|
|
||||
@@ -140,7 +152,7 @@ class LocalWorkflowTask:
|
||||
|
||||
## Storage: Snapshot + Journal for Resuming after Interruptions
|
||||
|
||||
The runtime stores each run under `s18_workflow_runtime/.runtime/`: a `<runId>.json` snapshot, `<runId>.output.json` output, and `<runId>.journal.jsonl` journal. The snapshot and journal share a stable `runId`, so resume can locate one run's state and completed steps.
|
||||
The runtime stores each run under `s18_workflow_runtime/.runtime/`: a `<runId>.json` snapshot, `<runId>.output.json` output, `<runId>.journal.jsonl` journal, and `<runId>.lock` coordination file. Every fresh run reserves a new `runId` with exclusive file creation before opening its journal. The run lock stays held through execution and final persistence, so another process cannot resume the same run at the same time. Its snapshot records the workflow name, arguments, and task state; resume validates the saved snapshot and journal before changing either successful artifact.
|
||||
|
||||
The journal is the core of checkpointed resume. It records every `agent()` result one line at a time:
|
||||
|
||||
@@ -172,11 +184,11 @@ if cached is not MISS:
|
||||
|
||||
## Determinism: Reproducibility Makes Resume Meaningful
|
||||
|
||||
Resume works only if the workflow is reproducible. Stable hashes and a deterministic runner make the same workflow plus the same arguments produce the same keys. Workflow code must therefore avoid uncontrolled clocks, randomness, filesystem state, and other inputs that would change those keys between runs.
|
||||
Resume works only if the workflow is reproducible. Stable hashes make the same workflow plus the same arguments produce the same journal keys. This chapter's deterministic runner also makes the sample result repeatable. A real runner may return different content, but it must keep semantic call keys stable and avoid uncontrolled clocks, randomness, or filesystem state in those keys.
|
||||
|
||||
## See It Run
|
||||
|
||||
The sample `review-changes` workflow uses `pipeline` to send each review dimension independently through audit → verify. An `agent()` with a schema finds issues during audit. During verification, `parallel()` dispatches a separate adversarial subagent for every finding. Only confirmed issues remain, sorted by severity.
|
||||
The sample `review-changes` workflow uses `pipeline` to send each review dimension independently through audit → verify. The deterministic runner produces structured fixture findings during audit, then fixture verdicts during verification. This keeps the example focused on pipeline, validation, journal, and resume behavior rather than the quality of a particular model's review.
|
||||
|
||||
```python
|
||||
async def sample_workflow(ctx, args):
|
||||
@@ -206,24 +218,25 @@ async def sample_workflow(ctx, args):
|
||||
|--|-----------|---------------------|
|
||||
| Loop | One model-driven loop | Main loop unchanged; deterministic orchestration added above it |
|
||||
| Who decides the next step | Model decides each round | Script declares the orchestration in advance |
|
||||
| Multiple agents | One-shot s06 subagents | Scripted, reproducible, recoverable bulk orchestration |
|
||||
| New mechanisms | — | Script DSL, task lifecycle, progress events, journal/resume, structured output, deterministic VM |
|
||||
| Multiple agents | One-shot s06 subagents | Scripted, resumable calls through an agent-runner boundary |
|
||||
| New mechanisms | — | Script primitives, host registry and tool adapter, task lifecycle, progress events, journal/resume, structured output |
|
||||
|
||||
s18 does not replace the main loop. It exposes `Workflow` at the tool layer and starts a local workflow runtime behind it: one workflow deterministically drives N agent loops. An s06 subagent is dispatched once at the model's discretion; s18 turns orchestration into a replayable script.
|
||||
s18 does not replace the main loop. It exposes `Workflow` at the tool layer and starts a local workflow runtime behind it: one saved script coordinates N calls through an agent-runner boundary. An s06 subagent is dispatched once at the model's discretion; s18 turns the orchestration into resumable host code.
|
||||
|
||||
## Try It
|
||||
|
||||
```bash
|
||||
python s18_workflow_runtime/code.py # Start review-changes and watch the event stream
|
||||
python s18_workflow_runtime/code.py # Real API: the model can choose Workflow or any s17 tool
|
||||
python s18_workflow_runtime/code.py demo # Deterministic review-changes fixture and event stream
|
||||
python s18_workflow_runtime/code.py resume # Resume by the last runId; every agent() hits the journal cache
|
||||
```
|
||||
|
||||
Watch one launch produce `async_launched`, followed by phase changes and subagent progress, then `task_notification`; the result is stored on the task object. A resumed run reports `agents=0 tokens=0` because every call hits the cache, and its result is byte-for-byte identical.
|
||||
In the default command, ask the model to run the saved `review-changes` workflow; the tool call travels through the same loop and dispatcher as the inherited s17 tools. The `demo` command runs the deterministic fixture directly so lifecycle and resume behavior are repeatable. It reports 11 runner calls and six fixture findings. A resumed run reports `agents=0 tokens=0` because every call hits the cache.
|
||||
|
||||
## Next
|
||||
|
||||
Orchestration adds a layer above agent capabilities: the main loop handles individual operations, while a script manages the whole team's flow. Once work becomes a deterministic, recoverable script, the model changes from the round-by-round driver into an execution unit scheduled by that script. The same `agent()` can be invoked ad hoc by the model in the main loop or orchestrated in bulk inside a workflow.
|
||||
Orchestration adds a layer above agent capabilities: the main loop handles individual operations, while a saved script manages a fixed flow. The sample keeps the agent-runner boundary deterministic; replacing it with a real runner changes the work performed, not the workflow lifecycle, journal, or resume contract.
|
||||
|
||||
Next: [s19 Goal Loop](../s19_goal_loop/) — Orchestration fans work out across agents. The next chapter moves in the opposite direction: a goal pulls control back into the main loop and refuses to let the turn end until the objective is achieved.
|
||||
Next: [s19 Goal Loop](../s19_goal_loop/) — Orchestration fans work out across agents. The next chapter uses a focused loop to pull control back toward a goal: unmet goals continue, while achievement or a safety exit returns control to the user.
|
||||
|
||||
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->
|
||||
<!-- translation-sync: zh@v6, en@v6, ja@v6 -->
|
||||
|
||||
Reference in New Issue
Block a user