Skip to content

Commit 28d9ece

Browse files
author
Reuben Brooks
committed
squad/council: give workers default tools + longer idle timeout for local models
Two fixes from actually running `scud squad --worker-model gemma-4-e4b`: 1. Squad and council workers now default to Read/Grep/Glob/Bash. Without tools they can only speculate — useless for "explore this codebase" queries. Write tools stay excluded: captain-driven synthesis expects workers to analyze, not mutate. 2. executeAgents idle timeout was hardcoded 60s, which kills slow local models (gemma-4 on a shared llama-server with N parallel slots often spends 60+ seconds between stream events while waiting for its compute turn). Now idle scales with base timeout, capped 180s. Verified with 4 gemma workers × --timeout 300 → 2/4 completed successfully at 147s and 402s elapsed; before this fix all 4 died at 60s.
1 parent 620663a commit 28d9ece

3 files changed

Lines changed: 20 additions & 3 deletions

File tree

internal/heavy/council.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ func buildCouncilWorkers(models []string) []Agent {
138138
Name: fmt.Sprintf("Council%d", i+1),
139139
Domain: m, // surface the model in output so the user knows who said what
140140
SystemPrompt: councilWorkerPrompt,
141+
Tools: squadWorkerTools,
141142
Model: m,
142143
}
143144
}

internal/heavy/ensemble.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,10 +508,19 @@ func executeAgents(ctx context.Context, agents []Agent, query, model string, con
508508
g.Go(func() error {
509509
start := time.Now()
510510

511-
// Adaptive timeout: base timeout as configured, but extend up to 3x
512-
// if the agent is still actively producing output (idle timeout = 60s)
511+
// Adaptive timeout: base timeout as configured, extend up to 3x if the
512+
// agent is still actively producing output. Idle = min(base, 180s) so
513+
// slow local models (e.g. gemma-4 on a shared llama-server with N
514+
// parallel slots) don't get killed mid-tool-call while waiting for
515+
// their compute turn. Capped so runaway hangs still get reaped.
513516
baseDur := time.Duration(timeoutSecs) * time.Second
514-
idleDur := 60 * time.Second
517+
idleDur := baseDur
518+
if idleDur > 180*time.Second {
519+
idleDur = 180 * time.Second
520+
}
521+
if idleDur < 60*time.Second {
522+
idleDur = 60 * time.Second
523+
}
515524
maxDur := baseDur * 3
516525
agentCtx, adaptive := rho.NewAdaptiveTimeout(gctx, baseDur, idleDur, maxDur)
517526

internal/heavy/squad.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,20 @@ const squadWorkerPrompt = "You are one member of a team of reasoning agents. " +
139139
"Answer the user's query thoroughly and independently — do not coordinate with other members. " +
140140
"Lay out your reasoning, evidence, assumptions, and final answer clearly so a coordinator can combine perspectives."
141141

142+
// squadWorkerTools is the default toolset for squad/council workers. Read-only
143+
// plus Bash so workers can explore a codebase or check state, but can't write
144+
// or edit — captain-driven synthesis expects the workers to *analyze*, not
145+
// mutate. If a use case needs write access, fork the agent definition.
146+
var squadWorkerTools = []string{"Read", "Grep", "Glob", "Bash"}
147+
142148
func buildSquadWorkers(n int, model string) []Agent {
143149
out := make([]Agent, n)
144150
for i := 0; i < n; i++ {
145151
out[i] = Agent{
146152
Name: fmt.Sprintf("Worker%d", i+1),
147153
Domain: "General reasoning",
148154
SystemPrompt: squadWorkerPrompt,
155+
Tools: squadWorkerTools,
149156
Model: model,
150157
}
151158
}

0 commit comments

Comments
 (0)