mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 12:14:40 +00:00
fix(agent): prevent team V2 leads from manually creating tasks before spawn
Strengthen TEAM.md prompt with WRONG/CORRECT examples and NEVER wording to stop models from calling `team_tasks create` before `spawn` in V2. Prompt changes: - V2 leads: explicit WRONG/CORRECT pattern, NEVER create before spawn - V1 leads: separate workflow with manual create→spawn instructions - team_tasks summary: de-emphasize "create", highlight auto-creation - spawn team_task_id: clarify "omit to auto-create (recommended)" Backend guards: - Reject spawn with in-progress team_task_id (prevents reuse) - Log warning on early claim race instead of silently ignoring
This commit is contained in:
1 parent
8ad425f5f8
commit
08823c057e
4 files changed
+41
-12
No files matched your search
@@ -142,18 +142,24 @@ func buildTeamMD(team *store.TeamData, members []store.TeamMemberData, selfID uu
|
||||
}
|
||||
}
|
||||
|
||||
// Workflow guidance
|
||||
// Workflow guidance — version-aware to match backend behavior.
|
||||
// V2: spawn auto-creates tasks, leads should NOT manually create.
|
||||
// V1: leads must create tasks first, then spawn with team_task_id.
|
||||
sb.WriteString("\n## Workflow\n\n")
|
||||
if selfRole == store.TeamRoleLead {
|
||||
sb.WriteString("**ONE delegation = ONE spawn call.** System auto-creates a tracking task per delegation.\n")
|
||||
sb.WriteString("The `label` parameter sets the task title (keep it short). Tasks auto-complete when delegation finishes.\n\n")
|
||||
sb.WriteString("Rules:\n")
|
||||
sb.WriteString("- Call all spawns first, then briefly tell the user what you delegated\n")
|
||||
sb.WriteString("- Do NOT add confirmations (\"Done!\", \"Got it!\") — just state what was assigned\n")
|
||||
sb.WriteString("- Parallel results arrive in a single combined notification — do NOT present partial results\n")
|
||||
sb.WriteString("- For dependency chains: `team_tasks` create with blocked_by, then `spawn` with team_task_id\n")
|
||||
|
||||
if isV2 {
|
||||
sb.WriteString("**NEVER use `team_tasks create` before spawning.** The system handles task creation automatically.\n\n")
|
||||
sb.WriteString("WRONG: `team_tasks(action=\"create\", ...)` → `spawn(team_task_id=...)`\n")
|
||||
sb.WriteString("CORRECT: `spawn(agent=\"...\", task=\"...\", label=\"short title\")`\n\n")
|
||||
sb.WriteString("Just call `spawn` — the system auto-creates one tracking task per delegation.\n")
|
||||
sb.WriteString("The `label` parameter sets the auto-created task title. Tasks auto-complete when delegation finishes.\n\n")
|
||||
sb.WriteString("Rules:\n")
|
||||
sb.WriteString("- Each spawn call creates its own task — never pass the same `team_task_id` to multiple spawns\n")
|
||||
sb.WriteString("- Call all spawns first, then briefly tell the user what you delegated\n")
|
||||
sb.WriteString("- Do NOT add confirmations (\"Done!\", \"Got it!\") — just state what was assigned\n")
|
||||
sb.WriteString("- Parallel results arrive in a single combined notification — do NOT present partial results\n")
|
||||
sb.WriteString("- `team_tasks create` is ONLY for dependency chains (tasks with `blocked_by`), then `spawn` with that `team_task_id`\n")
|
||||
|
||||
sb.WriteString("\n## Orchestration Patterns\n\n")
|
||||
sb.WriteString("- **Sequential**: A finishes → review → delegate to B with A's output\n")
|
||||
sb.WriteString("- **Iterative**: A drafts → B reviews → A revises with feedback\n")
|
||||
@@ -164,6 +170,14 @@ func buildTeamMD(team *store.TeamData, members []store.TeamMemberData, selfID uu
|
||||
sb.WriteString("\n## Follow-up Reminders\n\n")
|
||||
sb.WriteString("When waiting for user reply: create+claim task, then `await_reply` with text=<reminder>.\n")
|
||||
sb.WriteString("System auto-sends reminders. Call `clear_followup` when user replies.\n")
|
||||
} else {
|
||||
sb.WriteString("Create a task with `team_tasks` first, then `spawn` with that `team_task_id`.\n")
|
||||
sb.WriteString("Tasks auto-complete when delegation finishes.\n\n")
|
||||
sb.WriteString("Rules:\n")
|
||||
sb.WriteString("- Each task should have exactly one spawn (1:1 mapping)\n")
|
||||
sb.WriteString("- Call all spawns first, then briefly tell the user what you delegated\n")
|
||||
sb.WriteString("- Do NOT add confirmations (\"Done!\", \"Got it!\") — just state what was assigned\n")
|
||||
sb.WriteString("- Parallel results arrive in a single combined notification — do NOT present partial results\n")
|
||||
}
|
||||
|
||||
sb.WriteString("\nFor simple questions about team composition, answer directly from the member list above.\n")
|
||||
|
||||
@@ -85,7 +85,7 @@ var coreToolSummaries = map[string]string{
|
||||
"handoff": "Transfer conversation to another agent (ONLY when user explicitly asks to switch agents — NOT for task delegation)",
|
||||
"evaluate_loop": "Run a generate→evaluate→revise loop between two agents for quality-critical tasks",
|
||||
"delegate_search": "Search for agents by expertise to find the right delegation target",
|
||||
"team_tasks": "Manage team task board (list, create, complete, cancel tasks)",
|
||||
"team_tasks": "Team task board — track progress, manage dependencies (spawn auto-creates delegation tasks)",
|
||||
"team_message": "Send messages to teammates (progress updates, questions)",
|
||||
"workspace_write": "Write files to the team shared workspace (visible to all team members)",
|
||||
"workspace_read": "Read, list, delete, pin, tag files in the team shared workspace",
|
||||
|
||||
@@ -116,6 +116,18 @@ func (dm *DelegateManager) prepareDelegation(ctx context.Context, opts DelegateO
|
||||
opts.TeamTaskID, teamTask.Status, ownerLabel)
|
||||
}
|
||||
|
||||
// Guard: reject in-progress tasks — prevent multiple delegations on the same task.
|
||||
// This catches the pattern where an LLM reuses a team_task_id across parallel spawns.
|
||||
if teamTask.Status == store.TeamTaskStatusInProgress {
|
||||
ownerLabel := "another delegation"
|
||||
if teamTask.OwnerAgentKey != "" {
|
||||
ownerLabel = teamTask.OwnerAgentKey
|
||||
}
|
||||
return nil, nil, fmt.Errorf(
|
||||
"team_task_id %s is already in progress (claimed by %q). Each spawn needs its own task — omit team_task_id to auto-create.",
|
||||
opts.TeamTaskID, ownerLabel)
|
||||
}
|
||||
|
||||
if team != nil {
|
||||
if teamTask.TeamID != team.ID {
|
||||
return nil, nil, fmt.Errorf("team_task_id does not belong to your team")
|
||||
@@ -141,7 +153,10 @@ func (dm *DelegateManager) prepareDelegation(ctx context.Context, opts DelegateO
|
||||
// tasks that are already running. The ClaimTask in autoCompleteTeamTask()
|
||||
// will harmlessly fail (WHERE status='pending' won't match).
|
||||
if team != nil && IsTeamV2(team) {
|
||||
_ = dm.teamStore.ClaimTask(ctx, opts.TeamTaskID, targetAgent.ID, teamTask.TeamID)
|
||||
if err := dm.teamStore.ClaimTask(ctx, opts.TeamTaskID, targetAgent.ID, teamTask.TeamID); err != nil {
|
||||
slog.Warn("delegate: task claim race — another delegation may have claimed this task",
|
||||
"task_id", opts.TeamTaskID, "target", opts.TargetAgentKey, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ func (t *SpawnTool) Parameters() map[string]any {
|
||||
}
|
||||
props["team_task_id"] = map[string]any{
|
||||
"type": "string",
|
||||
"description": "Team task ID to auto-complete when task finishes (for team workflows)",
|
||||
"description": "Omit to auto-create a task (recommended). Only set for dependency chains with blocked_by tasks.",
|
||||
}
|
||||
props["estimated_duration"] = map[string]any{
|
||||
"type": "number",
|
||||
|
||||
Reference in new issue
Block a user