On this page

Propose a new design — single call + anti-replay

Required reading: - aflow/README.md § "Three structural contracts" - _common/files-contract.md § "Edit whitelist" - Upstream reference (read once): - third_party/AFlow/scripts/optimizer.py:132–183 (_optimize_graph — the inner while True anti-replay loop) - third_party/AFlow/scripts/optimizer_utils/data_utils.py:40–109 (get_top_rounds + select_round — softmax-mix parent sampling) - third_party/AFlow/scripts/optimizer_utils/experience_utils.py:12–80 (load_experience + check_modification) - third_party/AFlow/scripts/prompts/optimize_prompt.py (WORKFLOW_INPUT template + WORKFLOW_OPTIMIZE_PROMPT)

This skill is invoked inside the loop's Claude conversation — not as a separate sub-agent. The single LLM call + anti-replay retry loop runs as Python orchestration inside this skill. The anti-replay thread is NOT a Reflexion chain: each retry gets a freshly sampled parent + fresh prompt, no cross-attempt critique.

Arguments

/architect:aflow:proposer [<graph> [<version> [<iter>]]]
                          [--graph <name>] [--version <N>] [--iter <M>]
                          [--replay-max-retries K]  default from exp.yaml aflow.replay_max_retries

Iter resolution: loop passes the next iter index (iter_n); manual invocation defaults to "the iter to be created" (max(M)+1).

Pre-conditions

Steps

1. Resolve + read archive + read aflow config

Apply resolve protocol. Read full archive:

archive = [json.loads(line) for line in open(archive_path)]
aflow_cfg = exp_yaml["aflow"]  # K, α, λ, replay_cap, replay_norm

Print:

[aflow:proposer] iter=iter_{n}  archive=v{N}/archive.jsonl ({K_arch} entries)
                 cfg: K_sample={K} α={α} λ={λ} replay_cap={cap} norm={norm}

2. Build per-parent experience map (in-skill Python)

Aggregate archive into:

experience_map = {
    "iter_0": {"success": [], "failure": []},
    "iter_3": {"success": ["mod_str_A", ...], "failure": ["mod_str_B", ...]},
    ...
}

For each archive entry e with e.parent_iter_id != null: - Find the parent's score (look up the entry with iter_id == e.parent_iter_id). - If e.score > parent.score: append e.modification to experience_map[e.parent_iter_id]["success"]. - Else: append to ["failure"].

This mirrors upstream experience_utils.py:12–53 (load_experience), implemented over archive.jsonl instead of per-iter experience.json files. Sentinel: skip entries with modification == "(baseline)" (no anti-replay against the baseline entry itself).

3. Anti-replay retry loop

for retry in range(aflow_cfg.replay_max_retries):
    # 3a. select_round (softmax-mix parent sampling)
    #   get_top_rounds: top-K archive entries by `score`. iter_0 is
    #     ALWAYS force-included even when it is outside the numeric
    #     top-K — verbatim upstream data_utils.py:53-55 (round_1
    #     unconditional inclusion); keeps the baseline reachable as a
    #     parent for the whole run.
    top_K = get_top_rounds(archive, K=aflow_cfg.sample)   # iter_0 always included
    #   select_round: p = λ·uniform + (1−λ)·softmax(α · score·100).
    #     The `score·100` rescale is REQUIRED and load-bearing. `score`
    #     is a bootstrap-median fraction in [0,1]; with α=0.2 a [0,1]
    #     score gap gives exp(α·Δ)≈1 — a near-flat softmax, so parent
    #     selection silently collapses to ≈uniform. Upstream rescales
    #     first (data_utils.py:67, `scores * 100`). select_round MUST
    #     apply the ·100 internally; do NOT pass pre-scaled scores.
    parent = select_round(top_K, alpha=aflow_cfg.alpha, lam=aflow_cfg.lambda_uniform)
    parent_id = parent["iter_id"]

    # 3b. Load parent's effective state
    parent_graph = read(f"outputs/.../iter_{parent_id}/active_workspace/graphs/{graph}.json")
    parent_prompt = render_nodeset_prompts(f"outputs/.../iter_{parent_id}/active_workspace/nodesets/")
    parent_log_samples = sample_log_failures(parent_id, n=3)  # 3 random failure samples from log.jsonl

    # 3c. Load parent's experience slice
    parent_exp = experience_map.get(parent_id, {"success": [], "failure": []})

    # 3d. Build optimize_prompt
    prompt = build_optimize_prompt(
        parent=parent,
        parent_graph=parent_graph,
        parent_prompt=parent_prompt,
        parent_score=parent["score"],
        parent_log_samples=parent_log_samples,
        experience=parent_exp,
        operator_descriptions=load_operator_descriptions(graph),  # see § 5 below
    )

    # 3e. Single LLM call (or sub-agent spawn — see § 4 Sub-agent contract)
    try:
        response = call_meta_llm(prompt, schema=GraphOptimizeSchema)
    except FormatError:
        # 3f. Regex-extract fallback (verbatim optimizer.py:164–174)
        try:
            response = regex_extract(raw_response, tags=["modification", "graph", "prompt"])
        except ExtractionFailed:
            continue  # → next retry, fresh parent

    # 3g. check_modification — anti-replay
    if check_modification(response.modification, parent_exp, norm=aflow_cfg.replay_norm):
        # Duplicate against this parent's history → resample
        print(f"[aflow:proposer] retry {retry+1}/{cap}: modification duplicate against parent={parent_id}")
        continue

    # Accepted
    break
else:
    # Loop completed without break — exhausted retries
    print(f"[aflow:proposer] SKIP — anti-replay exhausted after {cap} retries")
    return status=SKIP_REPLAY_EXHAUSTED

4. Sub-agent contract (the LLM call mechanism)

The single call_meta_llm in step 3e is one Agent({subagent_type: "general-purpose", ...}) spawn. Each spawn is one independent Claude sample with full tool access — same mechanism as adas-subagent's propose/R1/R2 sub-agents, but here only one spawn per anti-replay attempt (the anti-replay loop's diversity comes from re-sampling the parent, not from sub-agent reflection).

resp = Agent({
    "subagent_type": "general-purpose",
    "description": f"aflow propose iter_{n} retry={retry}",
    "prompt": (
        "You are the meta-LLM in an AFlow architecture-search loop. "
        "Your task: propose ONE modification to the parent agent's "
        "graph + nodeset Python.\n\n"
        "You have full tool access (Read / Grep / Bash). Use it to "
        "inspect the parent's state, archive entries, eval logs under "
        f"outputs/eval_runs/, related literature.\n\n"
        f"=== Parent ===\nparent_iter_id: {parent_id}\nparent_score: {parent.score}\n\n"
        f"=== Parent's effective graph ===\n{parent_graph_summary}\n\n"
        f"=== Parent's prior modifications (DO NOT REPEAT these) ===\n"
        f"Successful (already explored, don't redo):\n  - {success_list}\n"
        f"Failed (also don't redo):\n  - {failure_list}\n\n"
        f"=== 3 random failure episodes from parent's log.jsonl ===\n{log_samples}\n\n"
        f"=== Operator catalog (available canvas-node types) ===\n{operator_descriptions}\n\n"
        f"=== Optimize prompt (verbatim from upstream prompts/optimize_prompt.py) ===\n{WORKFLOW_OPTIMIZE_PROMPT}\n\n"
        'Return a SINGLE fenced ```json block as your final message '
        'with keys {"modification", "thought", "name", "patch"}. '
        'The "modification" field is a one-sentence natural-language '
        "description of your change (used for anti-replay against the "
        'parent\'s prior attempts). The "patch" field is a change '
        "spec (prose intent + target file list) — see proposer.md "
        "§ 6. Do NOT edit workspace/* directly; the implementer "
        "realizes the change."
    ),
})
response = parse_final_json(resp)

Sub-agent contract: - subagent_type: "general-purpose" — full tool access. - Sub-agent MAY use any tool to ground its proposal. - Sub-agent MUST emit a single fenced ``json block as the final visible chunk. - Sub-agent MUST NOT editworkspace/*directly — patch application is implementer's job. - Sub-agent MAY useAgent(...)` recursively if grounding requires it.

On sub-agent exception (Agent error, no parseable JSON, malformed JSON): continue the anti-replay loop (treat same as FormatError fallback path). Verbatim upstream optimizer.py:170–174 semantics: extraction failures are retries, not iter-SKIPs.

After aflow_cfg.replay_max_retries exhaustion → SKIP iter.

5. Operator descriptions

Upstream injects load_operators_description(self.operators) — descriptions of the workflow-internal building blocks (Custom, ScEnsemble, Programmer, etc.). The AgentCanvas analogue is the loaded NodeSets' canvas-node catalog.

Default behavior (Q7 in algorithm.html, defaulted to "filter to parent's nodes + curated common list"):

def load_operator_descriptions(graph):
    # Per-graph: read the graph's nodeset prefixes from {graph}.json,
    # fetch their node schemas via GET /api/components/node-schemas,
    # plus a curated short-list of commonly-proposed builtin types
    # (controllers, logic, state, history).
    used_prefixes = extract_nodeset_prefixes(graph)
    used_schemas = fetch_schemas(used_prefixes)
    common_schemas = fetch_schemas(["controller", "logic", "state", "history"])
    return render_as_markdown(used_schemas + common_schemas)

If prompt-budget pressure appears: switch to "parent's nodes only" (narrower) or "full catalog" (broader). Tunable.

6. Patch schema (the patch field)

Same change-spec schema as adas-subagent (see its proposer.md § 5). F1 from algorithm.html chose a non-wholesale mutation surface (don't re-emit whole nodeset files — AgentCanvas agents are large multi-file artifacts, unlike ADAS/AFlow's small forward()). Since 2026-05-20 that incremental change is expressed as a prose intent + targets list and realized by the implementer sub-agent's native editing — NOT a typed graph_edits op enum. (The op enum was the compromise forced by routing edits through a deterministic replayer; an agentic implementer edits files directly and needs none — which is also what F1 originally wanted, "free-form patch, no typed action enum".)

{
  "modification": "Add a HistoryTracker node upstream of NavGPTReason; wire its summary into navgpt context; raise max_steps from 5 to 15.",
  "thought": "**Insights:** ... **Overall Idea:** ... **Implementation:** ...",
  "name": "Self-Audit Chain-of-Thought via History Tracker",
  "patch": {
    "intent": "Prose: exactly what to change and why — specific enough for the implementer to realize it by editing files. Describe the change; do NOT write op lists or jsonpaths.",
    "targets": ["workspace/graphs/{graph}.json", "workspace/nodesets/<prefix>.py"]
  }
}

patch MAY be null for a no-change probe. modification (the one-line anti-replay key, § 7) is separate from patch.intent (the full change description the implementer consumes) — keep both.

Upstream's separate <graph> / <prompt> output slots collapse into one intent: a prompt-only change is just an intent naming the nodeset file and the new prompt text, with that nodeset in targets.

7. check_modification semantics

Verbatim upstream from experience_utils.py:69–80 with one non-default option:

def check_modification(new_mod: str, parent_exp: dict, norm: str = "lower_ws") -> bool:
    """
    Return True if new_mod is a duplicate (should be rejected and resampled).
    Return False if new_mod is novel (should be accepted).
    """
    candidates = parent_exp["success"] + parent_exp["failure"]
    if norm == "verbatim":
        return new_mod in candidates
    elif norm == "lower_ws":
        # Default — Q3 deviation from upstream; cheap, robust against trivial paraphrase
        def normalize(s):
            return " ".join(s.lower().split())  # collapse whitespace, lowercase
        return normalize(new_mod) in [normalize(c) for c in candidates]
    elif norm == "embed":
        # Cosine sim > 0.95 — requires embedding model call per check
        # Not enabled by default; opt-in via exp.yaml
        return any(cosine_sim(new_mod, c) > 0.95 for c in candidates)

Default norm="lower_ws" documented as the only soft deviation from upstream's verbatim string equality (Q3 in algorithm.html).

8. Exception handling

Failure Action
FormatError (LLM schema validation fails) regex-extract fallback (step 3f)
Regex-extract fails continue anti-replay loop
check_modification returns True continue anti-replay loop
Sub-agent exception (Agent error, no JSON) continue anti-replay loop
Anti-replay retry cap exceeded return SKIP_REPLAY_EXHAUSTED
Any other Python exception return SKIP_LLM_EXCEPTION

Verbatim upstream optimizer.py:96–105 semantics: any inner-loop exception → score=None → no child written. Loop's response: rm -rf .staging/iter_{n}/, consecutive_skips += 1.

9. Write proposal.md to staging

On success:

mkdir -p outputs/design_runs/aflow/{graph}/v{N}/.staging/iter_{n}/

Write .staging/iter_n/proposal.md:

---
generation: {n}
iter_id: iter_{n}
parent_iter_id: iter_{parent_id}            # NEW vs adas-subagent
name: <from response.name>
modification: <verbatim from response.modification>   # NEW vs adas-subagent — load-bearing for anti-replay on future iters
replay_retries: {retry+1}                   # how many anti-replay loops this iter took
---

# Thought

<response.thought verbatim>

# Modification (one-line description, for anti-replay)

<response.modification verbatim>

# What changed (diff narrative)

<proposer writes a human-readable summary of the intended change>

# Change

```json
{patch from response}

Selection trace


### 10. Return to loop

[aflow:proposer] OK — proposed "" parent = iter_{parent_id} anti-replay = {retry+1}/{cap} retries targets = {count} staging = .staging/iter_{n}/proposal.md ```

Return status=OK to loop.

Outputs

Path What
v{N}/.staging/iter_{n}/proposal.md Frontmatter (with parent_iter_id + modification + replay_retries) + thought + diff narrative + # Change spec ({intent, targets}) + selection trace

Nothing else written until Atomic Writer commits.

Notes