AkurAI Build
Menu

AkurAI-Build

public

Latest change e11509261b6a2aa4ea9ef67250b0be01bfb4f53b - Restore paths swept in from uncommitted WIP by Ólafur Búi Ólafsson

# Squad Source Map

This file records source evidence for `multica-squads/SKILL.md`.

Use this when the task requires exact source paths, edge-case behavior, tests, or contract verification.

## Object Model

### DB shape

Source:

```text
server/migrations/084_squad.up.sql                # base table: name, description, leader_id, creator_id
server/migrations/085_squad_archive.up.sql        # archived_at, archived_by columns
server/migrations/088_squad_instructions.up.sql   # instructions column
server/pkg/db/queries/squad.sql
packages/core/types/squad.ts
```

Key facts:

- `squad` stores `name`, `description`, `leader_id`, `creator_id` (084), archive
  metadata `archived_at`/`archived_by` (085), and `instructions` (088).
- `squad_member` stores `member_type`, `member_id`, and `role`.
- `member_type` is constrained to `agent` or `member`.
- issue `assignee_type` supports `squad`.

## CLI

Source:

```text
server/cmd/multica/cmd_squad.go
```

Commands:

```bash
multica squad list
multica squad get <squad-id>
multica squad create
multica squad update <squad-id>
multica squad delete <squad-id>
multica squad activity <issue-id> <outcome>

multica squad member list <squad-id>
multica squad member add <squad-id>
multica squad member remove <squad-id>
multica squad member set-role <squad-id>
```

Use `--help` for exact flags before writes.

## Create / Update

Source:

```text
server/internal/handler/squad.go                  # CreateSquad ~200-272, UpdateSquad ~287-364
server/pkg/db/queries/agent.sql                   # GetAgentInWorkspace ~15-17
server/pkg/db/generated/agent.sql.go              # getAgentInWorkspace ~1261
```

Contracts:

- create requires `leader_id` (squad.go:215-218);
- leader must be a workspace agent — both create (squad.go:230-237) and update
  (squad.go:333-338) validate via `GetAgentInWorkspace`;
- archived leader is NOT rejected at create/update: `GetAgentInWorkspace` is
  `WHERE id = $1 AND workspace_id = $2` (agent.sql:15-17) with no archived
  filter, so an archived agent can be set as leader here. Archived-leader fails
  closed later, at routing/dispatch — see the readiness gate (squad.go:945,
  isSquadLeaderReady → service.AgentReadiness at squad.go:1017), assignment
  validation (issue.go:2625-2627), and autopilot admission (autopilot.go:885-891);
- leader is auto-added as member with role `leader` (squad.go:258-263);
- updating `leader_id` auto-adds new leader as member if missing (squad.go:340-347).

## Leader Briefing

Source:

```text
server/internal/handler/squad_briefing.go         # buildSquadLeaderBriefing ~104, buildSquadRoster ~121, renderMemberRow ~169, agentSkillsRosterSegment, formatRosterRow
server/internal/handler/daemon.go                  # briefing injection ~1187, ~1530
```

Contracts:

- squad leader tasks append briefing to leader agent instructions
  (daemon.go:1187, 1530);
- briefing includes operating protocol, roster, and optional instructions
  (squad_briefing.go:104-117);
- `buildSquadLeaderBriefing` takes an `ownsIssueStatus` argument selecting
  responsibility 6 via `squadOperatingProtocolFor`: the status grant
  (`squadParentStatusOwned`) only when `issue.assignee_type == "squad"` and
  `issue.assignee_id == squad.id`, otherwise an explicit prohibition
  (`squadParentStatusNotOwned`). Quick-create passes `false` — no issue exists
  yet. Injection is broader than authority on purpose: it is keyed off
  `is_leader_task`, which also fires for `@squad` mentions on issues owned by
  someone else (MUL-3724);
- when the claim's defensive gate withholds the briefing (NULL `squad_id`,
  squad hard-deleted, leader swapped after enqueue), the handler also clears
  `is_leader_task` on the claim response, so the wire flag means "briefing
  injected" and the run degrades to an ordinary agent turn. The daemon derives
  the leader role from that flag (plus `squad_id` for quick-create), never from
  the briefing text (MUL-5811);
- every claim response carries `leader_role_resolved: true`, the capability
  that tells the daemon those fields are authoritative. Servers predating it
  omit it, and a daemon seeing it absent falls back to the legacy
  "`## Squad Operating Protocol` appears in instructions" inference. That is
  the only correct read of either older shape: before #4951 no `is_leader_task`
  was sent at all, and after it the flag was sent without any guarantee that a
  briefing came with it. The field is claim-only and never rendered into a
  prompt;
- `instructions` section appears only when non-empty (squad_briefing.go:110-112);
- archived agent members are skipped from roster (squad_briefing.go:178-179);
- agent member roster rows list assigned workspace skills via
  `loadSquadMemberSkillNames` (ListAgentSkillNamesByAgentIDs) and
  `agentSkillsRosterSegment` — "skills: a, b" or
  "no skills assigned"; builtin multica-* skills are excluded and human
  members carry no skills segment (squad_briefing.go renderMemberRow);
- no traced behavior injects `instructions` into every squad member.

## Issue Assignment

Source:

```text
server/internal/handler/issue.go                  # assignee validation ~2614-2632
server/internal/handler/squad.go                   # shouldEnqueueSquadLeaderOnAssign ~990, enqueueSquadLeaderTask ~1027
server/internal/service/task.go
```

Contracts:

- `assignee_type="squad"` routes to `squad.leader_id` (squad.go:1028-1050);
- backlog assignment does not immediately enqueue (squad.go:991-993);
- moving out of backlog can enqueue leader (squad.go:990-994 → isSquadLeaderReady);
- assignee change cancels existing issue tasks first;
- private leader access is checked at assign-time (issue.go:2629-2632) and at
  enqueue-time via `canEnqueueSquadLeader` (squad.go:1037);
- archived squad / archived leader rejected at assign-time (issue.go:2622-2627);
- pending task dedup is applied (squad.go:1042-1048);
- parent status is agent-managed: assignment brief (`writeWorkflowAssignment` with
  `IsSquadLeader`) requires `in_progress` on the first turn and forbids
  unconditional `in_review` on that dispatch turn; Squad Operating Protocol
  (`squad_briefing.go`) owns the ongoing `in_progress` → later `in_review`
  contract. `StartTask` / `CompleteTask` do not write issue status. On
  comment-triggered leader turns `writeWorkflowComment` names that protocol
  responsibility as the one exception to "do not change status unless the
  comment asks" — without it the @mention-dispatch shape (no child issues, so
  no child-done ask) would strand the parent in `in_progress`.

## Comment / Mention

Source:

```text
server/internal/handler/comment.go                # comment triggers ~1057-1199, squad mention branch ~1352
server/internal/handler/squad.go                   # enqueueSquadLeaderTask ~986 (assign/backlog paths), lastTaskWasLeader ~915
server/internal/service/task.go                   # EnqueueTaskForSquadLeader
```

Contracts:

- commenting on a squad-assigned issue can wake the leader — the comment path
  computes triggers via `computeCommentAgentTriggers` (comment.go:1124), whose
  assigned-squad branch is `computeAssignedSquadLeaderCommentTrigger`
  (comment.go:1162-1199); the same computation backs the trigger-preview
  endpoint;
- explicit `mention://squad/<id>` resolves squad and adds the leader trigger
  (comment.go:1352-1391);
- squad mention does not fan out to members — enqueue targets `squad.LeaderID`
  only (comment.go:1104-1112, and squad.go:1007 on the assign/backlog paths);
- leader task uses `is_leader_task=true` (via `EnqueueTaskForSquadLeader`);
- leader self-trigger loops are guarded — same-leader / last-task-was-leader
  guards (comment.go:1173-1176, lastTaskWasLeader at squad.go:915) and member
  explicit-mention skip (comment.go:1177-1179).

## Autopilot

Source:

```text
server/internal/service/autopilot.go              # resolveAutopilotLeader ~617-655, dispatch ~88-111
server/internal/handler/autopilot.go              # save-time validateAutopilotAssignee ~845-893
```

Contracts:

- squad autopilot resolves executable agent from `squad.leader_id` —
  `resolveAutopilotLeader` squad branch (autopilot.go:639-651);
- readiness/admission checks target the leader: save-time validation rejects an
  archived squad/leader (handler/autopilot.go:881-891), and dispatch re-runs
  `resolveAutopilotLeader` + `AgentReadiness`;
- archived squad fails closed / skips dispatch — `errSquadArchived`
  (autopilot.go:644-645);
- `create_issue` keeps the issue assigned to the squad (autopilot.go:88-97);
- `run_only` creates task directly for leader (autopilot.go:99-106, dispatch via
  `resolveAutopilotLeader` at autopilot.go:284).

## Child-done Parent Trigger

Source:

```text
server/internal/handler/issue_child_done.go       # dispatchParentAssigneeTrigger ~246, triggerChildDoneSquad ~304
```

Contracts:

- when a child issue closes a stage barrier and the parent is assigned to a
  squad, the parent squad leader is triggered (triggerChildDoneSquad in
  issue_child_done.go);
- routing is leader-only — one `EnqueueTaskForSquadLeader` on the leader, no
  member fan-out (triggerChildDoneSquad / dispatchParentAssigneeTrigger);
- no self-trigger guard: a same-squad or shared-leader child still wakes the
  parent squad leader — the wake is a serial handoff onto the PARENT and is the
  only carrier of the stage-barrier "advance / wrap up" instruction (MUL-3969,
  mirrors the agent path from MUL-2808). Re-triggering is bounded only by
  `HasPendingTaskForIssueAndAgent` (idempotent per parent issue + agent).
- no leader-invocation gate: child-done does NOT re-check whether the child's
  completer can invoke the leader. The parent was already permission-checked at
  squad-assign time (`validateAssigneePair`), so waking its own leader is a
  coordination handoff, not a fresh invocation. Re-checking it here failed
  closed for the DEFAULT private leader (the child's completer is an
  agent/system actor with no resolvable human originator), stranding every
  process-squad pipeline after stage 1 while direct-to-leader-agent parents
  advanced fine (MUL-4063 / GH #4928). Agent and squad child-done now share one
  ungated path; any future invocation gate must be added to BOTH together.
- parent status is not auto-advanced by the barrier: the system comment asks the
  leader to continue or — when the overall goal is met — run
  `multica issue status <parent-id> in_review`. That explicit ask is what lets a
  comment-triggered leader turn change status (the comment workflow otherwise
  forbids status flips unless asked). `done` remains human / integration owned.

## Private Leader Access

Source:

```text
server/internal/handler/agent_access.go           # canInvokeAgent ~48-108, canEnqueueSquadLeader ~261-267
server/internal/handler/squad.go                   # enqueueSquadLeaderTask gate ~955-974
```

Contracts (invocation gate, MUL-3963 — this is the *trigger* gate, distinct from
the view gate `canAccessPrivateAgent`):

- `canEnqueueSquadLeader` loads the leader and delegates to `canInvokeAgent`
  (agent_access.go:261-267);
- `canInvokeAgent` judges by the *effective invoking user*: a member actor is
  itself; an agent/system actor is the top-of-chain human originator
  (`originatorUserID`), which is `""` when none resolved (agent_access.go:48-54);
- the agent owner may always invoke their own agent (agent_access.go:57-59);
- `permission_mode != "public_to"` (i.e. private) is deny-by-default — no admin
  bypass, no A2A bypass; only the owner branch passes (agent_access.go:61-65);
- `public_to` consults the invocation-target allow-list: a `workspace` target
  admits any workspace member AND workspace-internal agent/system principals even
  with no resolved human (`workspaceBroad`); `member` targets require the
  resolved human to match; `team` targets are inert in V1 (agent_access.go:82-106);
- wired into `enqueueSquadLeaderTask` (squad.go:955-974): the squad
  assign/promote path denies the enqueue when the actor cannot invoke the leader
  (member authors are their own originator; agent-authored triggers pass `""`).
- NOTE: the child-done wake does NOT use this gate anymore — see "Child-done
  Parent Trigger" above (MUL-4063).

## Tests

Relevant test groups:

```text
server/internal/handler/squad_assign_trigger_test.go
server/internal/handler/squad_comment_trigger_test.go
server/internal/handler/squad_briefing_test.go
server/internal/handler/squad_private_leader_test.go
server/internal/handler/autopilot_private_leader_test.go
server/internal/handler/squad_no_action_test.go
```

Verification command:

```bash
go test ./internal/handler -run 'Test.*Squad|Test.*squad|Test.*Autopilot.*Squad|Test.*ChildDone.*Squad'
```