-
Notifications
You must be signed in to change notification settings - Fork 4
feat: add versioned SpawnSpec axes #394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -53,6 +53,9 @@ import { | |||||||||||||||||||
| MAX_RESPAWN_ATTEMPTS, | ||||||||||||||||||||
| type AgentRoute, | ||||||||||||||||||||
| type AgentRecord, | ||||||||||||||||||||
| type AgentAuthority, | ||||||||||||||||||||
| type AgentFunction, | ||||||||||||||||||||
| type AgentPlacement, | ||||||||||||||||||||
| type AgentRole, | ||||||||||||||||||||
| type AgentState, | ||||||||||||||||||||
| type CliType, | ||||||||||||||||||||
|
|
@@ -222,6 +225,9 @@ export interface SpawnAgentParams { | |||||||||||||||||||
| worktree_branch?: string; | ||||||||||||||||||||
| parent_agent_id?: string; | ||||||||||||||||||||
| role?: AgentRole; | ||||||||||||||||||||
| authority?: AgentAuthority; | ||||||||||||||||||||
| function?: AgentFunction; | ||||||||||||||||||||
| placement?: AgentPlacement; | ||||||||||||||||||||
| auto_archive_on_done?: boolean; | ||||||||||||||||||||
| max_cost_per_agent?: number; | ||||||||||||||||||||
| crash_recover?: boolean; | ||||||||||||||||||||
|
|
@@ -236,6 +242,7 @@ export interface SpawnAgentParams { | |||||||||||||||||||
|
|
||||||||||||||||||||
| export interface SpawnAgentResult { | ||||||||||||||||||||
| agent_id: string; | ||||||||||||||||||||
| parent_agent_id: string | null; | ||||||||||||||||||||
| surface_id: string; | ||||||||||||||||||||
| workspace_id?: string; | ||||||||||||||||||||
| state: AgentState; | ||||||||||||||||||||
|
|
@@ -2499,6 +2506,9 @@ export class AgentEngine { | |||||||||||||||||||
| agent.repo, | ||||||||||||||||||||
| identity.session_id, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| if (!updated.agent_id.includes("-pending-")) { | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High The substring check Use 🚀 Reply "fix it for me" or copy this AI Prompt for your agent:There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the repository name itself contains AGENTS.md reference: AGENTS.md:L29-L34 Useful? React with 👍 / 👎. |
||||||||||||||||||||
| return updated; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if (updated.agent_id === finalAgentId) { | ||||||||||||||||||||
| return updated; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
@@ -5319,21 +5329,12 @@ export class AgentEngine { | |||||||||||||||||||
| parentAgent = parent; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Job role is authoritative. When no role was declared, spawn context is | ||||||||||||||||||||
| // stronger evidence than the CLI: a worker's child is worker work even if | ||||||||||||||||||||
| // the selected harness is Claude. CLI/launcher inference is the final | ||||||||||||||||||||
| // compatibility fallback only (#378). | ||||||||||||||||||||
| // Job role is authoritative. The versioned tool rejects missing agent | ||||||||||||||||||||
| // axes; direct legacy engine callers default to worker without consulting | ||||||||||||||||||||
| // the selected harness. | ||||||||||||||||||||
| const role = spawnParams.role !== undefined | ||||||||||||||||||||
| ? inferAgentRole({ role: spawnParams.role }) | ||||||||||||||||||||
| : parentAgent && inferRecordRoleOrNull(parentAgent) === "worker" | ||||||||||||||||||||
| ? "worker" | ||||||||||||||||||||
| : inferAgentRole({ | ||||||||||||||||||||
| cli: spawnParams.cli, | ||||||||||||||||||||
| launcherName: launcherNameForCli( | ||||||||||||||||||||
| spawnParams.repo, | ||||||||||||||||||||
| spawnParams.cli, | ||||||||||||||||||||
| ), | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| : "worker"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| this.spawnGuard.check(spawnParams.workspace); | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
@@ -5440,6 +5441,11 @@ export class AgentEngine { | |||||||||||||||||||
| parent_agent_id: parentAgentId, | ||||||||||||||||||||
| spawn_depth: spawnDepth, | ||||||||||||||||||||
| role, | ||||||||||||||||||||
| authority: | ||||||||||||||||||||
| spawnParams.authority ?? (role === "orchestrator" ? "lead" : "worker"), | ||||||||||||||||||||
| function: spawnParams.function ?? "implementor", | ||||||||||||||||||||
| placement: | ||||||||||||||||||||
| spawnParams.placement ?? (role === "orchestrator" ? "left" : "right"), | ||||||||||||||||||||
|
Comment on lines
+5444
to
+5448
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win Derive
♻️ Proposed fix to keep the persisted axes consistent+ const authority: AgentAuthority =
+ spawnParams.authority ?? (role === "orchestrator" ? "lead" : "worker");Place the constant next to the - authority:
- spawnParams.authority ?? (role === "orchestrator" ? "lead" : "worker"),
+ authority,
function: spawnParams.function ?? "implementor",
- placement:
- spawnParams.placement ?? (role === "orchestrator" ? "left" : "right"),
+ placement:
+ spawnParams.placement ?? (authority === "lead" ? "left" : "right"),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| auto_archive_on_done: spawnParams.auto_archive_on_done, | ||||||||||||||||||||
| deletion_intent: false, | ||||||||||||||||||||
| quality: "unknown", | ||||||||||||||||||||
|
|
@@ -5585,6 +5591,7 @@ export class AgentEngine { | |||||||||||||||||||
| this.schedulePostSpawnLivenessAssertion(agentId); | ||||||||||||||||||||
| return { | ||||||||||||||||||||
| agent_id: agentId, | ||||||||||||||||||||
| parent_agent_id: parentAgentId, | ||||||||||||||||||||
| surface_id: surface.surface, | ||||||||||||||||||||
| workspace_id: surface.workspace, | ||||||||||||||||||||
| state: "booting", | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -312,7 +312,9 @@ function roleFromSeatOrLauncher(input: { | |
| }); | ||
| } catch (error) { | ||
| if (isAgentRoleInferenceError(error)) { | ||
| return inferAgentRole({ cli: input.cli }); | ||
| if (input.cli === "gemini" || input.cli === "kiro") { | ||
| return "worker"; | ||
| } | ||
| } | ||
| throw error; | ||
| } | ||
|
Comment on lines
313
to
320
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium When } catch (error) {
if (isAgentRoleInferenceError(error)) {
- if (input.cli === "gemini" || input.cli === "kiro") {
- return "worker";
- }
}
throw error;
}🚀 Reply "fix it for me" or copy this AI Prompt for your agent: |
||
|
|
@@ -2296,7 +2298,8 @@ export class AgentRegistry { | |
| if (shouldRetainCrashRecoveryError(agent)) { | ||
| continue; | ||
| } | ||
| if (inferRecordRoleOrNull(agent) === "orchestrator") { | ||
| const role = inferRecordRoleOrNull(agent); | ||
| if (role === null || role === "orchestrator") { | ||
| continue; | ||
| } | ||
| if (this.matchingLiveSurface(agent, surfaces)) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -257,20 +257,6 @@ function roleFromLauncherLabel(label: string | undefined): AgentRole | null { | |
| return null; | ||
| } | ||
|
|
||
| function roleFromCli(cli: string | undefined): AgentRole | null { | ||
| switch (cli) { | ||
| case "claude": | ||
| return "orchestrator"; | ||
| case "codex": | ||
| case "cursor": | ||
| case "gemini": | ||
| case "kiro": | ||
| return "worker"; | ||
| default: | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| function normalizeExplicitRole( | ||
| role: AgentRole | "ic" | undefined, | ||
| ): AgentRole | undefined { | ||
|
|
@@ -286,8 +272,7 @@ export function canInferAgentRole(input: { | |
| return Boolean( | ||
| input.role || | ||
| roleFromLauncherLabel(input.launcherName) || | ||
| roleFromLauncherLabel(input.title) || | ||
| roleFromCli(input.cli), | ||
| roleFromLauncherLabel(input.title), | ||
| ); | ||
| } | ||
|
|
||
|
|
@@ -305,9 +290,6 @@ export function inferAgentRole(input: { | |
| roleFromLauncherLabel(input.title); | ||
| if (launcherRole) return launcherRole; | ||
|
|
||
| const cliRole = roleFromCli(input.cli); | ||
| if (cliRole) return cliRole; | ||
|
|
||
| throw new AgentRoleInferenceError(input); | ||
| } | ||
|
|
||
|
|
@@ -331,13 +313,9 @@ export function launcherNameForCli(repo: string, cli: CliType): string { | |
| export function inferRecordRole( | ||
| agent: Pick<AgentRecord, "role" | "cli" | "repo">, | ||
| ): AgentRole { | ||
| return ( | ||
| normalizeExplicitRole(agent.role) ?? | ||
| inferAgentRole({ | ||
| cli: agent.cli, | ||
| launcherName: launcherNameForCli(agent.repo, agent.cli), | ||
| }) | ||
| ); | ||
| const explicitRole = normalizeExplicitRole(agent.role); | ||
| if (explicitRole) return explicitRole; | ||
| throw new AgentRoleInferenceError({ role: agent.role, cli: agent.cli }); | ||
|
Comment on lines
+316
to
+318
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an older persisted record has no AGENTS.md reference: AGENTS.md:L29-L34 Useful? React with 👍 / 👎.
Comment on lines
+317
to
+318
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For an upgraded roleless Claude record that reaches AGENTS.md reference: AGENTS.md:L11-L16 Useful? React with 👍 / 👎.
Comment on lines
313
to
+318
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Find inferRecordRole call sites and show surrounding error handling.
set -euo pipefail
rg -nP --type=ts -C 8 '\binferRecordRole\s*\(' src tests
rg -nP --type=ts -C 4 '\bisAgentRoleInferenceError\s*\(' srcRepository: EtanHey/cmuxlayer Length of output: 6747 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- crash-recovery implementation ---'
sed -n '3230,3375p' src/agent-engine.ts
printf '%s\n' '--- createAgentSurface error handling ---'
sed -n '2025,2105p' src/agent-engine.ts
printf '%s\n' '--- role inference definitions and tests ---'
sed -n '210,360p' src/layout-policy.ts
rg -n -C 5 'inferRecordRole|respawn_attempts|Crash recovery failed|crash recovery' tests src/agent-engine.tsRepository: EtanHey/cmuxlayer Length of output: 50373 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- remainder of recoverCrashedAgents ---'
sed -n '3360,3450p' src/agent-engine.ts
printf '%s\n' '--- recovery failure helper ---'
sed -n '3050,3150p' src/agent-engine.ts
printf '%s\n' '--- relevant role-policy tests ---'
sed -n '270,320p' tests/layout-policy.test.ts
printf '%s\n' '--- recovery-related tests mentioning roleless records ---'
rg -n -i -C 6 'roleless|role.*undefined|inferRecordRole|legacy.*recover|recover.*legacy|crash.recover.*role' tests srcRepository: EtanHey/cmuxlayer Length of output: 50373 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '6170,6220p' src/agent-engine.ts
rg -n -C 8 'function isCrashRecoveryEligible|const isCrashRecoveryEligible|isCrashRecoveryEligible' src testsRepository: EtanHey/cmuxlayer Length of output: 6569 Handle roleless records before incrementing
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| export function inferRecordRoleOrNull( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium
src/agent-engine.ts:5003When
spawnParams.authorityorspawnParams.placementis explicitly supplied by the caller, the value is persisted without checking that it agrees with the resolvedrole. A direct engine caller can passrole: "worker", authority: "lead", placement: "left"and produce a durable record where the SpawnSpec axes contradict the role used for surface placement — downstream consumers see a worker-placed surface that claims lead authority on the left column.Since the comment above declares role authoritative,
authorityandplacementshould always be derived fromrolerather than accepting arbitrary caller overrides.🚀 Reply "fix it for me" or copy this AI Prompt for your agent: