mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
fix(squad): clarify active worker briefing copy
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -169,13 +169,13 @@ func buildSquadExecutionState(ctx context.Context, q *db.Queries, issueID pgtype
|
||||
sb.WriteString("This is a live snapshot taken when you were triggered — read it before you decide.\n\n")
|
||||
|
||||
if len(workers) == 0 {
|
||||
sb.WriteString("**No worker session is currently running on this issue.** ")
|
||||
sb.WriteString("No delegated agent is executing right now. A worker's task ends the moment it stops producing output, so a recent comment that reads like work is still in progress (\"I'll continue later\", \"still verifying\", a partial/interim report) does NOT mean anything is running — nothing resumes automatically. ")
|
||||
sb.WriteString("**No worker task is currently active on this issue.** ")
|
||||
sb.WriteString("No delegated agent has an active queued, waiting, or running task right now. A worker's task ends the moment it stops producing output, so a recent comment that reads like work is still in progress (\"I'll continue later\", \"still verifying\", a partial/interim report) does NOT mean anything is active — nothing resumes automatically. ")
|
||||
sb.WriteString("Before you record `no_action`, confirm the issue is genuinely done or genuinely waiting on a human. If work remains, delegate the next step, or mark the issue `blocked` and escalate. An interim report with no active session is a stalled issue, not a progressing one.\n")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
fmt.Fprintf(&sb, "**%d worker session(s) currently running on this issue.** A delegated agent is executing right now:\n", len(workers))
|
||||
fmt.Fprintf(&sb, "**%d worker task(s) currently active on this issue.** A delegated agent has queued, waiting, or running work:\n", len(workers))
|
||||
for _, t := range workers {
|
||||
name := "a squad member"
|
||||
if ag, err := q.GetAgent(ctx, t.AgentID); err == nil {
|
||||
@@ -183,7 +183,7 @@ func buildSquadExecutionState(ctx context.Context, q *db.Queries, issueID pgtype
|
||||
}
|
||||
fmt.Fprintf(&sb, "- %s — status `%s`\n", name, t.Status)
|
||||
}
|
||||
sb.WriteString("\nYou usually do NOT need to delegate the same work again while a session is in flight — recording `no_action` and waiting for it to finish is appropriate. Re-delegate only if the running work is clearly wrong or stuck, or hand off a genuinely independent next step.\n")
|
||||
sb.WriteString("\nYou usually do NOT need to delegate the same work again while a worker task is in flight — recording `no_action` and waiting for it to finish is appropriate. Re-delegate only if the active work is clearly wrong or stuck, or hand off a genuinely independent next step.\n")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
|
||||
@@ -226,34 +226,34 @@ func TestBuildSquadLeaderBriefing_MentionsRoundTrip(t *testing.T) {
|
||||
// claimAndDecodeAgent runs ClaimTaskByRuntime for the given runtime and
|
||||
// returns the agent block of the response. Fails the test on non-200.
|
||||
func claimAndDecodeAgent(t *testing.T, runtimeID string) *TaskAgentData {
|
||||
t.Helper()
|
||||
w := httptest.NewRecorder()
|
||||
req := newDaemonTokenRequest("POST", "/api/daemon/runtimes/"+runtimeID+"/claim", nil, testWorkspaceID, "test-claim-squad-briefing")
|
||||
req = withURLParam(req, "runtimeId", runtimeID)
|
||||
testHandler.ClaimTaskByRuntime(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("ClaimTaskByRuntime: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
var resp struct {
|
||||
Task *struct {
|
||||
Agent *TaskAgentData `json:"agent"`
|
||||
} `json:"task"`
|
||||
}
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if resp.Task == nil || resp.Task.Agent == nil {
|
||||
t.Fatalf("expected task.agent in response, got: %s", w.Body.String())
|
||||
}
|
||||
return resp.Task.Agent
|
||||
t.Helper()
|
||||
w := httptest.NewRecorder()
|
||||
req := newDaemonTokenRequest("POST", "/api/daemon/runtimes/"+runtimeID+"/claim", nil, testWorkspaceID, "test-claim-squad-briefing")
|
||||
req = withURLParam(req, "runtimeId", runtimeID)
|
||||
testHandler.ClaimTaskByRuntime(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("ClaimTaskByRuntime: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
var resp struct {
|
||||
Task *struct {
|
||||
Agent *TaskAgentData `json:"agent"`
|
||||
} `json:"task"`
|
||||
}
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if resp.Task == nil || resp.Task.Agent == nil {
|
||||
t.Fatalf("expected task.agent in response, got: %s", w.Body.String())
|
||||
}
|
||||
return resp.Task.Agent
|
||||
}
|
||||
|
||||
// queueSquadIssueTaskFor creates an issue assigned to the squad and a queued
|
||||
// task for the given (agentID, runtimeID). Returns the issue + task IDs.
|
||||
func queueSquadIssueTaskFor(t *testing.T, squadID, agentID, runtimeID string, issueNumber int) (issueID, taskID string) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
if err := testPool.QueryRow(ctx, `
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
if err := testPool.QueryRow(ctx, `
|
||||
INSERT INTO issue (
|
||||
workspace_id, title, status, priority, creator_id, creator_type,
|
||||
assignee_type, assignee_id, number, position
|
||||
@@ -261,101 +261,101 @@ assignee_type, assignee_id, number, position
|
||||
'squad', $3, $4, 0)
|
||||
RETURNING id
|
||||
`, testWorkspaceID, testUserID, squadID, issueNumber).Scan(&issueID); err != nil {
|
||||
t.Fatalf("create squad-assigned issue: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { testPool.Exec(ctx, `DELETE FROM issue WHERE id = $1`, issueID) })
|
||||
t.Fatalf("create squad-assigned issue: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { testPool.Exec(ctx, `DELETE FROM issue WHERE id = $1`, issueID) })
|
||||
|
||||
if err := testPool.QueryRow(ctx, `
|
||||
if err := testPool.QueryRow(ctx, `
|
||||
INSERT INTO agent_task_queue (agent_id, runtime_id, issue_id, status, priority)
|
||||
VALUES ($1, $2, $3, 'queued', 0)
|
||||
RETURNING id
|
||||
`, agentID, runtimeID, issueID).Scan(&taskID); err != nil {
|
||||
t.Fatalf("queue task: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { testPool.Exec(ctx, `DELETE FROM agent_task_queue WHERE id = $1`, taskID) })
|
||||
return
|
||||
t.Fatalf("queue task: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { testPool.Exec(ctx, `DELETE FROM agent_task_queue WHERE id = $1`, taskID) })
|
||||
return
|
||||
}
|
||||
|
||||
// TestClaimTask_LeaderGetsBriefing — when the squad leader claims a task on
|
||||
// a squad-assigned issue, the response's agent.instructions must include
|
||||
// the Operating Protocol + Roster + user instructions.
|
||||
func TestClaimTask_LeaderGetsBriefing(t *testing.T) {
|
||||
if testHandler == nil {
|
||||
t.Skip("database not available")
|
||||
}
|
||||
ctx := context.Background()
|
||||
if testHandler == nil {
|
||||
t.Skip("database not available")
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
var leaderID, runtimeID string
|
||||
if err := testPool.QueryRow(ctx,
|
||||
`SELECT id, runtime_id FROM agent WHERE workspace_id = $1 ORDER BY created_at ASC LIMIT 1`,
|
||||
testWorkspaceID,
|
||||
).Scan(&leaderID, &runtimeID); err != nil {
|
||||
t.Fatalf("get leader agent: %v", err)
|
||||
}
|
||||
var leaderID, runtimeID string
|
||||
if err := testPool.QueryRow(ctx,
|
||||
`SELECT id, runtime_id FROM agent WHERE workspace_id = $1 ORDER BY created_at ASC LIMIT 1`,
|
||||
testWorkspaceID,
|
||||
).Scan(&leaderID, &runtimeID); err != nil {
|
||||
t.Fatalf("get leader agent: %v", err)
|
||||
}
|
||||
|
||||
squad := seedSquadForBriefing(t, leaderID, "Briefing Claim Squad", "Be terse.")
|
||||
squad := seedSquadForBriefing(t, leaderID, "Briefing Claim Squad", "Be terse.")
|
||||
|
||||
helper := createHandlerTestAgent(t, "Briefing Helper", []byte("[]"))
|
||||
addAgentMember(t, squad.ID, helper, "implementer")
|
||||
helper := createHandlerTestAgent(t, "Briefing Helper", []byte("[]"))
|
||||
addAgentMember(t, squad.ID, helper, "implementer")
|
||||
|
||||
queueSquadIssueTaskFor(t, util.UUIDToString(squad.ID), leaderID, runtimeID, 95001)
|
||||
queueSquadIssueTaskFor(t, util.UUIDToString(squad.ID), leaderID, runtimeID, 95001)
|
||||
|
||||
agent := claimAndDecodeAgent(t, runtimeID)
|
||||
for _, want := range []string{
|
||||
"## Squad Operating Protocol",
|
||||
"## Squad Roster",
|
||||
"Leader (you):",
|
||||
"## Squad Instructions (Briefing Claim Squad)",
|
||||
"Be terse.",
|
||||
"`[@Briefing Helper](mention://agent/" + helper + ")`",
|
||||
} {
|
||||
if !strings.Contains(agent.Instructions, want) {
|
||||
t.Errorf("expected agent.instructions to contain %q\n--- instructions ---\n%s", want, agent.Instructions)
|
||||
}
|
||||
}
|
||||
agent := claimAndDecodeAgent(t, runtimeID)
|
||||
for _, want := range []string{
|
||||
"## Squad Operating Protocol",
|
||||
"## Squad Roster",
|
||||
"Leader (you):",
|
||||
"## Squad Instructions (Briefing Claim Squad)",
|
||||
"Be terse.",
|
||||
"`[@Briefing Helper](mention://agent/" + helper + ")`",
|
||||
} {
|
||||
if !strings.Contains(agent.Instructions, want) {
|
||||
t.Errorf("expected agent.instructions to contain %q\n--- instructions ---\n%s", want, agent.Instructions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestClaimTask_NonLeaderGetsNoBriefing — when a non-leader squad member
|
||||
// claims a task on a squad-assigned issue, NO briefing is injected.
|
||||
func TestClaimTask_NonLeaderGetsNoBriefing(t *testing.T) {
|
||||
if testHandler == nil {
|
||||
t.Skip("database not available")
|
||||
}
|
||||
ctx := context.Background()
|
||||
if testHandler == nil {
|
||||
t.Skip("database not available")
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
var leaderID string
|
||||
if err := testPool.QueryRow(ctx,
|
||||
`SELECT id FROM agent WHERE workspace_id = $1 ORDER BY created_at ASC LIMIT 1`,
|
||||
testWorkspaceID,
|
||||
).Scan(&leaderID); err != nil {
|
||||
t.Fatalf("get leader agent: %v", err)
|
||||
}
|
||||
var leaderID string
|
||||
if err := testPool.QueryRow(ctx,
|
||||
`SELECT id FROM agent WHERE workspace_id = $1 ORDER BY created_at ASC LIMIT 1`,
|
||||
testWorkspaceID,
|
||||
).Scan(&leaderID); err != nil {
|
||||
t.Fatalf("get leader agent: %v", err)
|
||||
}
|
||||
|
||||
squad := seedSquadForBriefing(t, leaderID, "Non-Leader Squad", "Squad guidance.")
|
||||
squad := seedSquadForBriefing(t, leaderID, "Non-Leader Squad", "Squad guidance.")
|
||||
|
||||
// Create a second agent (NOT the leader) with its own runtime so the
|
||||
// claim path picks its task without ambiguity.
|
||||
helperID := createHandlerTestAgent(t, "Non Leader Helper", []byte("[]"))
|
||||
addAgentMember(t, squad.ID, helperID, "")
|
||||
var helperRuntime string
|
||||
if err := testPool.QueryRow(ctx,
|
||||
`SELECT runtime_id FROM agent WHERE id = $1`, helperID,
|
||||
).Scan(&helperRuntime); err != nil {
|
||||
t.Fatalf("get helper runtime: %v", err)
|
||||
}
|
||||
// Create a second agent (NOT the leader) with its own runtime so the
|
||||
// claim path picks its task without ambiguity.
|
||||
helperID := createHandlerTestAgent(t, "Non Leader Helper", []byte("[]"))
|
||||
addAgentMember(t, squad.ID, helperID, "")
|
||||
var helperRuntime string
|
||||
if err := testPool.QueryRow(ctx,
|
||||
`SELECT runtime_id FROM agent WHERE id = $1`, helperID,
|
||||
).Scan(&helperRuntime); err != nil {
|
||||
t.Fatalf("get helper runtime: %v", err)
|
||||
}
|
||||
|
||||
queueSquadIssueTaskFor(t, util.UUIDToString(squad.ID), helperID, helperRuntime, 95002)
|
||||
queueSquadIssueTaskFor(t, util.UUIDToString(squad.ID), helperID, helperRuntime, 95002)
|
||||
|
||||
agent := claimAndDecodeAgent(t, helperRuntime)
|
||||
for _, mustNot := range []string{
|
||||
"Squad Operating Protocol",
|
||||
"Squad Roster",
|
||||
"Squad Instructions (Non-Leader Squad)",
|
||||
} {
|
||||
if strings.Contains(agent.Instructions, mustNot) {
|
||||
t.Errorf("non-leader claim should NOT contain %q\n--- instructions ---\n%s", mustNot, agent.Instructions)
|
||||
}
|
||||
}
|
||||
agent := claimAndDecodeAgent(t, helperRuntime)
|
||||
for _, mustNot := range []string{
|
||||
"Squad Operating Protocol",
|
||||
"Squad Roster",
|
||||
"Squad Instructions (Non-Leader Squad)",
|
||||
} {
|
||||
if strings.Contains(agent.Instructions, mustNot) {
|
||||
t.Errorf("non-leader claim should NOT contain %q\n--- instructions ---\n%s", mustNot, agent.Instructions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// seedSquadIssue creates a squad-assigned issue and returns its UUID.
|
||||
@@ -415,14 +415,14 @@ func TestBuildSquadLeaderBriefing_ExecutionState(t *testing.T) {
|
||||
if !strings.Contains(out, "## Current Execution State") {
|
||||
t.Fatalf("expected Current Execution State section, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "No worker session is currently running on this issue.") {
|
||||
if !strings.Contains(out, "No worker task is currently active on this issue.") {
|
||||
t.Errorf("expected no-worker-session line, got:\n%s", out)
|
||||
}
|
||||
|
||||
// The leader's own active task must NOT count as a worker session.
|
||||
insertActiveTask(t, leaderID, issueID, true)
|
||||
out = buildSquadLeaderBriefing(ctx, testHandler.Queries, squad, issueID)
|
||||
if !strings.Contains(out, "No worker session is currently running on this issue.") {
|
||||
if !strings.Contains(out, "No worker task is currently active on this issue.") {
|
||||
t.Errorf("leader's own task must not count as a worker session, got:\n%s", out)
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ func TestBuildSquadLeaderBriefing_ExecutionState(t *testing.T) {
|
||||
insertActiveTask(t, worker, issueID, false)
|
||||
out = buildSquadLeaderBriefing(ctx, testHandler.Queries, squad, issueID)
|
||||
for _, want := range []string{
|
||||
"worker session(s) currently running on this issue.",
|
||||
"worker task(s) currently active on this issue.",
|
||||
"Exec Worker",
|
||||
"status `running`",
|
||||
} {
|
||||
|
||||
Reference in New Issue
Block a user