Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
93c9579
session: route wide reads to quick tasks, arm their no_progress dial
santoshkumarradha Sep 12, 2026
b6e22b4
manual, session: document the quick no_progress dial and pin it
santoshkumarradha Sep 12, 2026
637550d
changelog: the wave A entry
santoshkumarradha Sep 12, 2026
5e21ba4
changelog: quote the surface list in 1015's sweep entry (base branch …
santoshkumarradha Sep 12, 2026
96b2514
changelog: map the sweep entry's surface onto the enum
santoshkumarradha Sep 12, 2026
e08fb9d
session: consult — a bounded second opinion to the tier that thinks (…
santoshkumarradha Sep 12, 2026
91d3915
session: read quick fan-outs together before any of them starts (wave…
santoshkumarradha Sep 12, 2026
bf189da
changelog: consult and the batch review entries
santoshkumarradha Sep 12, 2026
25325ad
session: lean worker and quick pages (wave B3 experiment)
santoshkumarradha Sep 12, 2026
1c32854
manual: the fan review clause in 'several at once'
santoshkumarradha Sep 12, 2026
f6855f3
revert: quick fan-out read-as-a-group (wave B1) and its table row
santoshkumarradha Sep 13, 2026
44bc4eb
session: the family gate — second-ever quick child is read before it …
santoshkumarradha Sep 13, 2026
c9a799e
ci: kick the pr gate
santoshkumarradha Sep 13, 2026
367b776
Merge commit 'ae80a7436' into tune/handoff-routing
santoshkumarradha Sep 13, 2026
a805d89
session: the already-read pointer is self-limiting
santoshkumarradha Sep 14, 2026
b432861
cmd: a one-shot errand does not divide below three independent parts
santoshkumarradha Sep 14, 2026
c2884de
provider: a slow-TTFT model never opens a hedge arm
santoshkumarradha Sep 14, 2026
486d696
cmd: fix the errand gate to fold unconditionally
santoshkumarradha Sep 14, 2026
e8cf5dc
Merge remote-tracking branch 'origin/dev' into HEAD
santoshkumarradha Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/aforge-replay/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,12 @@ var callSiteRoles = map[string]lane.Role{
"intake": lane.RoleAuxiliary,
"careful": lane.RoleAuxiliary,
"distill": lane.RoleAuxiliary,
// internal/session's quickfan_admission.go tags its errand with
// `string(role)`, and the parts-review settles exactly as `division` does.
"quickreview": lane.RoleDesign,
// and consult's own page — the question of judgement — belongs with the
// judges.
"consult": lane.RoleJudge,
}

// requestOf turns a replayed request into the one a chooser is asked.
Expand Down
2 changes: 1 addition & 1 deletion cmd/aforge/cacheshape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func TestPlanContractsRideTheRunCacheKey(t *testing.T) {
client := adoptLiveClient(settings, capture.model, capture)
plans := &jobPlans{graphs: map[string]plannedJob{}}

subtree, err := planSubtree(settings, client, client, plans, graph, "")(context.Background(), resident.Compiled{
subtree, err := planSubtree(settings, client, client, plans, graph, "", false)(context.Background(), resident.Compiled{
Goal: "review the pull request and deliver REVIEW.md",
Scale: head.ScaleProject,
})
Expand Down
16 changes: 15 additions & 1 deletion cmd/aforge/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -5153,7 +5153,10 @@ func planWindow(settings config.Config, client *liveClient) int {
// terrainRoot is the directory the planner is allowed to look at before it
// plans, and it is empty for every surface but a shared workspace. See
// buildBrain, where the judgement is made once.
func planSubtree(settings config.Config, planClient, workClient *liveClient, plans *jobPlans, history *store.Store, terrainRoot string) resident.PlanFunc {
// oneShotErrand says this planner serves `aforge do`: the smallness gate then
// stands in front of any division the build draws, because a one-shot errand
// under three independent parts runs as one worker (cooperative.go).
func planSubtree(settings config.Config, planClient, workClient *liveClient, plans *jobPlans, history *store.Store, terrainRoot string, oneShotErrand bool) resident.PlanFunc {
return func(ctx context.Context, compiled resident.Compiled) (store.Subtree, error) {
anchor, anchored := resident.PlanAnchorFromContext(ctx)
prefix := anchor.NodeID
Expand Down Expand Up @@ -5372,6 +5375,17 @@ func planSubtree(settings config.Config, planClient, workClient *liveClient, pla
log.Printf("note: the plan for %s was drawn with faults (%s); running it as drawn", prefix, pool.CauseInWords(err))
}
gatePlanDivision(graph, compiled.Goal)
// AND THE ONE-SHOT DOOR HOLDS THE PLAN TO A SECOND, STRICTER READING.
// A conversation's two-part plan stands — the fan-out is visible and
// the person is there for it — but a one-shot errand has one budget
// and nobody to ask, and the measured cost of a small division there
// is the whole run spent on coordination: issue #1007's `do` cell
// divided a two-file fix into three nodes and never settled, where one
// worker landed the same job. Below three genuinely independent parts
// the errand runs as one worker (gateErrandDivision).
if oneShotErrand {
gateErrandDivision(graph)
}
// The acceptance checklist, on the one node that hands the finished
// thing over. See plan.Graph.SetAcceptance for why it goes there and
// nowhere else.
Expand Down
84 changes: 74 additions & 10 deletions cmd/aforge/cooperative.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,24 +196,88 @@ func gatePlanDivision(graph *plan.Graph, goal string) int {
if splitgate.Judge(goal, gateLeaves(graph, leaves)).Keep {
return 0
}
folded := len(leaves)
folded := foldPlanToOneLeaf(graph, fmt.Sprintf(
"split gate: goal enumerates %d items, under the %d-item floor — one sitting",
splitgate.Items(goal), divisionFloor))
log.Printf("split gate: collapsed %d leaves to one (goal names %d items, floor %d)",
folded, splitgate.Items(goal), divisionFloor)
return folded
}


// gateErrandDivision is the smallness gate on the `aforge do` door, and it
// stands on that door ONLY. A conversation plans wide on purpose — visible
// fan-out is the product there, and a person is watching — but a one-shot
// errand is one job, one budget and nobody to ask. Measured on the 17-minute
// cell that ran the floor-of-three here: the parts number passes whatever the
// planner is told to draw, which is all of it, so the counts that pass any
// floor are the counts that still settle badly. So an errand folds — always —
// and the claim-time JIT is still in charge,
// because the one worker on the whole goal may still split at claim time.
func gateErrandDivision(graph *plan.Graph) int {
if graph == nil {
return 0
}
leaves := graph.Leaves()
if len(leaves) < 2 {
return 0
}
foldPlanToOneLeaf(graph,
"smallness gate: a one-shot errand runs as one worker — division is left to the claim-time JIT")
log.Printf("smallness gate: collapsed %d leaves to one (errand folds unconditionally)", len(leaves))
return len(leaves)
}

// independentParts counts the planned work nodes that wait on no other work
// node in the same plan — the parts that could genuinely run at the same
// time. A strict chain counts one however long it is, because every link
// after the first is one worker waiting on another, and that is one sitting
// drawn as several.
func independentParts(graph *plan.Graph, leaves []int) int {
work := make(map[int]bool, len(leaves))
for _, id := range leaves {
work[id] = true
}
independent := 0
for _, node := range graph.Nodes {
if !work[node.ID] {
continue
}
waitsOnAPart := false
for _, need := range node.Needs {
if work[need] {
waitsOnAPart = true
break
}
}
if !waitsOnAPart {
independent++
}
}
return independent
}

// foldPlanToOneLeaf collapses a drawn plan to the smallest plan there is: one
// work node, the goal as its brief, and the reason recorded in Undivided so a
// later pass can see why the graph is one leaf when the spine drew several.
// Both plan-door gates fold through it, so a folded graph is the same shape
// whichever gate folded it.
func foldPlanToOneLeaf(graph *plan.Graph, why string) int {
folded := len(graph.Leaves())
title := graph.Nodes[0].Title
summary := graph.Nodes[0].Summary
graph.Nodes = graph.Nodes[:0]
if len(graph.Stages) > 1 {
graph.Stages = graph.Stages[:1]
}
graph.Add(plan.Node{
Kind: plan.KindWork,
Stage: 1,
Title: title,
Summary: summary,
Brief: graph.Goal,
Undivided: fmt.Sprintf("split gate: goal enumerates %d items, under the %d-item floor — one sitting",
splitgate.Items(goal), divisionFloor),
Kind: plan.KindWork,
Stage: 1,
Title: title,
Summary: summary,
Brief: graph.Goal,
Undivided: why,
})
log.Printf("split gate: collapsed %d leaves to one (goal names %d items, floor %d)",
folded, splitgate.Items(goal), divisionFloor)
return folded
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/aforge/fusion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestACompiledMethodCostsNoSecondCall(t *testing.T) {
plans := &jobPlans{graphs: map[string]plannedJob{}}

const method = "Read the diff first. Done means the note names every migration step."
subtree, err := planSubtree(settings, client, client, plans, graph, "")(context.Background(), resident.Compiled{
subtree, err := planSubtree(settings, client, client, plans, graph, "", false)(context.Background(), resident.Compiled{
Goal: "write the note that announces the change", Scale: head.ScaleTask, Contract: method,
})
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions cmd/aforge/method_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestTaskScaleLeafCarriesAWorkingMethod(t *testing.T) {
plans := &jobPlans{graphs: map[string]plannedJob{}}

const goal = "write the note that announces the change"
subtree, err := planSubtree(settings, client, client, plans, graph, "")(context.Background(), resident.Compiled{
subtree, err := planSubtree(settings, client, client, plans, graph, "", false)(context.Background(), resident.Compiled{
Goal: goal, Scale: head.ScaleTask,
})
if err != nil {
Expand Down Expand Up @@ -79,7 +79,7 @@ func TestLookupScaleBuysNoWorkingMethod(t *testing.T) {
client := adoptLiveClient(settings, capture.model, capture)
plans := &jobPlans{graphs: map[string]plannedJob{}}

subtree, err := planSubtree(settings, client, client, plans, graph, "")(context.Background(), resident.Compiled{
subtree, err := planSubtree(settings, client, client, plans, graph, "", false)(context.Background(), resident.Compiled{
Goal: "say what the current total is", Scale: head.ScaleLookup,
})
if err != nil {
Expand Down Expand Up @@ -115,7 +115,7 @@ func TestThePlannedDeliverableOwnerCarriesTheMethodTheGateReads(t *testing.T) {
client := adoptLiveClient(settings, capture.model, capture)
plans := &jobPlans{graphs: map[string]plannedJob{}}

subtree, err := planSubtree(settings, client, client, plans, graph, "")(context.Background(), resident.Compiled{
subtree, err := planSubtree(settings, client, client, plans, graph, "", false)(context.Background(), resident.Compiled{
Goal: "review the pull request and deliver REVIEW.md",
Scale: head.ScaleProject,
})
Expand Down
6 changes: 3 additions & 3 deletions cmd/aforge/partsroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestTwoUnrelatedRequestsBecomeOnePlannedJobWithNoMachineryTitle(t *testing.
client := adoptLiveClient(settings, planner.model, planner)
plans := &jobPlans{graphs: map[string]plannedJob{}}

subtree, err := planSubtree(settings, client, client, plans, graph, "")(context.Background(), resident.Compiled{
subtree, err := planSubtree(settings, client, client, plans, graph, "", false)(context.Background(), resident.Compiled{
Goal: "Two things, unrelated: a haiku, and what the parser vendors charge.",
Scale: head.ScaleProject,
Parts: []string{haiku, research},
Expand Down Expand Up @@ -173,7 +173,7 @@ func TestAPlannedGatheringRequestCannotBeClaimedWhileAnInputRuns(t *testing.T) {
client := adoptLiveClient(settings, planner.model, planner)
plans := &jobPlans{graphs: map[string]plannedJob{}}

subtree, err := planSubtree(settings, client, client, plans, graph, "")(context.Background(), resident.Compiled{
subtree, err := planSubtree(settings, client, client, plans, graph, "", false)(context.Background(), resident.Compiled{
Goal: "Compare how three countries measure road distance.",
Scale: head.ScaleProject,
Parts: requests,
Expand Down Expand Up @@ -255,7 +255,7 @@ func TestAnAskWithNoSeparableRequestsPlansExactlyAsBefore(t *testing.T) {
client := adoptLiveClient(settings, planner.model, planner)
plans := &jobPlans{graphs: map[string]plannedJob{}}

subtree, err := planSubtree(settings, client, client, plans, graph, "")(context.Background(), resident.Compiled{
subtree, err := planSubtree(settings, client, client, plans, graph, "", false)(context.Background(), resident.Compiled{
Goal: goal, Scale: head.ScaleProject, Parts: parts,
})
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/aforge/resident_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func newResidentReconciler(settings config.Config, graph *store.Store,
}
reconciler := resident.New(graph,
compileIntent(settings, compiler, taskClient, planClient, plans, graph),
planSubtree(settings, planClient, taskClient, plans, graph, terrainRoot),
planSubtree(settings, planClient, taskClient, plans, graph, terrainRoot, oneShotErrand),
)
if oneShotErrand {
reconciler = reconciler.WithOneShotErrands()
Expand Down
118 changes: 118 additions & 0 deletions cmd/aforge/splitgate_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package main

import (
"context"
"os"
"path/filepath"
"strings"
"testing"

"github.com/Agent-Field/aforge-v2/internal/config"
"github.com/Agent-Field/aforge-v2/internal/head"
"github.com/Agent-Field/aforge-v2/internal/plan"
"github.com/Agent-Field/aforge-v2/internal/resident"
)

func TestGateDecisions(t *testing.T) {
Expand Down Expand Up @@ -116,3 +121,116 @@ func threeIndependentSittings(goal string) *plan.Graph {
}
return graph
}

// ── the one-shot door's smallness gate ─────────────────────────────────────
//
// Issue #1007's live cell: `aforge do` divided a two-file fix into three task
// nodes, spent its whole token budget on coordination and never settled,
// where one worker did the same job and landed. These pin the gate that cell
// bought: a one-shot errand divides at three genuinely independent parts and
// not below, and it answers to no pin — the gate stands on the `do` door
// because of what that door is, not because somebody asked.

// The cell itself: a two-part ask is one worker doing them in order, however
// the planner draws it. The fold leaves one work node holding the whole goal
// as its brief — the shape `aforge exec` would have given the run.
func TestAOneShotErrandDoesNotDivideATwoPartAsk(t *testing.T) {
graph := &plan.Graph{
Goal: "Fix the nil cursor in intervals.py and the off-by-one in merge.py",
Stages: []plan.Stage{{Title: "the fixes"}},
}
graph.Add(plan.Node{Kind: plan.KindWork, Stage: 1, Title: "nil cursor", Size: plan.SizeAtomic, Brief: "fix intervals.py"})
graph.Add(plan.Node{Kind: plan.KindWork, Stage: 1, Title: "off-by-one", Size: plan.SizeAtomic, Brief: "fix merge.py"})

if folded := gateErrandDivision(graph); folded != 2 {
t.Fatalf("the gate folded %d leaves, want the two it was handed", folded)
}
if leaves := graph.Leaves(); len(leaves) != 1 {
t.Fatalf("the folded plan has %d work nodes, want one worker", len(leaves))
}
if got := graph.Nodes[0].Brief; got != graph.Goal {
t.Fatalf("the one worker's brief is %q, want the whole goal", got)
}
if !strings.HasPrefix(graph.Nodes[0].Undivided, "smallness gate:") {
t.Fatalf("the fold does not say why: %q", graph.Nodes[0].Undivided)
}
}

// AND THREE INDEPENDENT PARTS FOLD TOO: the floor that called itself
// evidence still passed the measured failure. The errand never divides —
// it is one worker, and the claim-time JIT holds the payer's voice.
func TestAOneShotErrandFoldsEvenThreeIndependentParts(t *testing.T) {
graph := threeIndependentSittings("caption the twelve image files")
if folded := gateErrandDivision(graph); folded != 3 {
t.Fatalf("three independent parts folded %d leaves, want all three", folded)
}
if got := len(graph.Leaves()); got != 1 {
t.Fatalf("the folded plan has %d work nodes, want one worker", got)
}
}

func TestAOneShotErrandFoldsAChainToo(t *testing.T) {
graph := &plan.Graph{
Goal: "Port the parser, then its tests, then the docs",
Stages: []plan.Stage{{Title: "the port"}},
}
first := graph.Add(plan.Node{Kind: plan.KindWork, Stage: 1, Title: "parser", Size: plan.SizeAtomic, Brief: "port the parser"})
second := graph.Add(plan.Node{Kind: plan.KindWork, Stage: 1, Title: "tests", Size: plan.SizeAtomic, Brief: "port the tests"})
third := graph.Add(plan.Node{Kind: plan.KindWork, Stage: 1, Title: "docs", Size: plan.SizeAtomic, Brief: "port the docs"})
if err := graph.AddNeed(second, first); err != nil {
t.Fatal(err)
}
if err := graph.AddNeed(third, second); err != nil {
t.Fatal(err)
}

if folded := gateErrandDivision(graph); folded != 3 {
t.Fatalf("a strict chain folded %d leaves, want all three — one independent part is one sitting", folded)
}
if got := len(graph.Leaves()); got != 1 {
t.Fatalf("the folded chain has %d work nodes, want one worker", got)
}
}

// AND THE GATE STANDS AT THE DOOR THE ERRAND PLANS THROUGH, NOT BESIDE IT.
// The same scripted planner draws the same two independent parts for both
// surfaces; the conversation keeps them, and the one-shot errand runs one
// worker on the whole goal. This is the seam the live cell went through —
// the planner's first reading of the ask, where most divisions are born.
func TestTheErrandDoorFoldsWhatAConversationKeeps(t *testing.T) {
// The pin-driven gate is stood down for the reason the parts-route tests
// state: the only gate speaking here must be the one under test.
t.Setenv("AFORGE_SPLITGATE", "0")
const goal = "Two things, unrelated: a haiku about the first cold morning, and what the parser vendors charge."
for _, probe := range []struct {
name string
errand bool
wantNodes int
}{
{"a conversation keeps the two parts", false, 3},
{"a one-shot errand runs one worker", true, 1},
} {
t.Run(probe.name, func(t *testing.T) {
graph := openCacheStore(t)
settings := config.Config{Model: "worker/model", MaxDepth: 1, NodeBudget: 8}
planner := &partsPlanClient{model: "worker/model", stages: []string{"Answer"}, parts: map[int][]scriptPart{
1: {{title: "Haiku", summary: "Write the haiku."}, {title: "Vendors", summary: "Price the vendors."}},
}}
client := adoptLiveClient(settings, planner.model, planner)
plans := &jobPlans{graphs: map[string]plannedJob{}}

subtree, err := planSubtree(settings, client, client, plans, graph, "", probe.errand)(context.Background(), resident.Compiled{
Goal: goal, Scale: head.ScaleProject,
})
if err != nil {
t.Fatal(err)
}
if len(subtree.Nodes) != probe.wantNodes {
t.Fatalf("the plan admitted %d nodes, want %d — %s", len(subtree.Nodes), probe.wantNodes, probe.name)
}
if probe.errand && subtree.Nodes[0].Brief != goal {
t.Fatalf("the one worker's brief is %q, want the whole goal", subtree.Nodes[0].Brief)
}
})
}
}
2 changes: 1 addition & 1 deletion docs/changes/unreleased/1015-stale-build-sweep.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
kind: added
title: a launch sweep names live sessions on an older build
surface: [chat, engine]
surface: [engine, chat]
pr: 1015
---

Expand Down
11 changes: 11 additions & 0 deletions docs/changes/unreleased/1017-consult-verb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
kind: added
title: consult — a bounded second opinion to the tier that thinks
pr: 1017
surface: [chat, engine]
invalidates:
- "A model facing a decision bigger than its own confidence had no way to buy one judgement before spending: the route cascade and the division review fire only at their own doors. `consult` asks the tier that thinks one question with its facts, at most two a turn, and a task node's belt has no door at all (the armed route is the division review, by design)."
---

The lease is the count, spelled in the wire and enforced in the client, and the
manual teaches when a model reads it.
8 changes: 8 additions & 0 deletions docs/changes/unreleased/1017-errand-smallness-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
kind: fixed
title: the errand's smallness gate folds one worker always — a floor could not see the failure
pr: 1017
surface: [engine]
invalidates:
- "An `aforge do` run may divide at three independent parts, the number issue #1007's live cell drew. The gate now folds to one worker unconditionally, because any floor passed whatever number the planner was told to draw."
---
8 changes: 8 additions & 0 deletions docs/changes/unreleased/1017-hedge-ttft-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
kind: fixed
title: a model whose measured first-token wait is slow never opens a hedge arm
pr: 1017
surface: [engine]
invalidates:
- "Every request a slow model answers send-hedged arms around its TTFT, so a slow-but-moving conversation pays the same question's bill twice (68 of 71 usage rows hedged on a live deepseek-v4.1-flash run). Past the TTFT gate the request rides one lane; a model with no TTFT on record fails open the old way."
---
Loading
Loading