Skip to content

Commit 12eb5cb

Browse files
committed
adaptive subagent dispatch: first run sequential, subsequent runs parallel
1 parent 4327a98 commit 12eb5cb

3 files changed

Lines changed: 66 additions & 3 deletions

File tree

zsmith/src/main/java/airhacks/zsmith/subagent/control/SubAgentTool.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package airhacks.zsmith.subagent.control;
22

3+
import java.io.IOException;
4+
import java.nio.file.Files;
5+
import java.nio.file.Path;
6+
37
import org.json.JSONObject;
48

59
import airhacks.zsmith.agent.boundary.Agent;
10+
import airhacks.zsmith.configuration.control.ZCfg;
611
import airhacks.zsmith.logging.control.Log;
712
import airhacks.zsmith.tools.control.Tool;
813

914
public class SubAgentTool implements Tool {
1015

1116
static final int DEFAULT_MAX_DEPTH = 3;
17+
static final String FIRST_RUN_MARKER = ".first_run_completed";
1218
static final ScopedValue<Integer> DEPTH = ScopedValue.newInstance();
1319

1420
private final Agent subAgent;
@@ -68,7 +74,7 @@ public String inputSchema() {
6874

6975
@Override
7076
public boolean parallel() {
71-
return this.runParallel;
77+
return this.runParallel && firstRunCompleted();
7278
}
7379

7480
@Override
@@ -83,9 +89,32 @@ public String execute(JSONObject input) {
8389
var result = ScopedValue.where(DEPTH, currentDepth + 1)
8490
.call(() -> this.subAgent.chat(task));
8591
Log.subagent("sub-agent '%s' completed".formatted(this.subAgent.name()));
92+
markFirstRunCompleted();
8693
return result;
8794
} catch (Exception e) {
8895
return "Error: Sub-agent '%s' failed: %s".formatted(this.subAgent.name(), e.getMessage());
8996
}
9097
}
98+
99+
boolean firstRunCompleted() {
100+
return Files.exists(markerPath());
101+
}
102+
103+
void markFirstRunCompleted() {
104+
var path = markerPath();
105+
try {
106+
Files.createDirectories(path.getParent());
107+
if (!Files.exists(path)) {
108+
Files.writeString(path, "");
109+
}
110+
} catch (IOException e) {
111+
Log.warning("could not write first-run marker for "
112+
+ this.subAgent.name() + ": " + e.getMessage());
113+
}
114+
}
115+
116+
Path markerPath() {
117+
var userHome = System.getProperty("user.home");
118+
return Path.of(userHome, "." + ZCfg.APP_NAME, this.subAgent.name(), FIRST_RUN_MARKER);
119+
}
91120
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2026.04.24.03
1+
2026.04.27.01

zsmith/src/test/java/SubAgentToolTest.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import java.nio.file.Files;
2+
import java.nio.file.Path;
3+
14
import airhacks.zsmith.agent.boundary.Agent;
5+
import airhacks.zsmith.configuration.control.ZCfg;
26
import airhacks.zsmith.subagent.control.SubAgentTool;
37

4-
void main() {
8+
void main() throws Exception {
59
var child = new Agent("researcher", "You are a research assistant.");
610
var tool = new SubAgentTool(child);
711

@@ -35,4 +39,34 @@ void main() {
3539
var result = shallowTool.execute(new org.json.JSONObject().put("task", "test"));
3640
assert result.contains("Error") && result.contains("depth")
3741
: "expected depth error but got: " + result;
42+
43+
// adaptive parallel: first run sequential, subsequent runs parallel
44+
var sandboxHome = Files.createTempDirectory("zsmith-subagent-test");
45+
var originalHome = System.getProperty("user.home");
46+
System.setProperty("user.home", sandboxHome.toString());
47+
try {
48+
var firstRunChild = new Agent("first-run-probe", "test");
49+
var firstRunTool = new SubAgentTool(firstRunChild);
50+
51+
var markerPath = Path.of(sandboxHome.toString(), "." + ZCfg.APP_NAME,
52+
"first-run-probe", ".first_run_completed");
53+
Files.deleteIfExists(markerPath);
54+
55+
// runParallel=true but no marker yet → sequential
56+
assert !firstRunTool.parallel()
57+
: "expected parallel()=false on first run (no marker)";
58+
59+
// simulate completion of a first successful run
60+
Files.createDirectories(markerPath.getParent());
61+
Files.writeString(markerPath, "");
62+
assert firstRunTool.parallel()
63+
: "expected parallel()=true once marker exists";
64+
65+
// sequential override always wins, even with marker
66+
var sequentialTool = new SubAgentTool(firstRunChild, false);
67+
assert !sequentialTool.parallel()
68+
: "expected parallel()=false when runParallel=false regardless of marker";
69+
} finally {
70+
System.setProperty("user.home", originalHome);
71+
}
3872
}

0 commit comments

Comments
 (0)