@@ -31,16 +31,17 @@ type fakeOrchestrator struct {
|
||||
|
||||
queue *messagequeue.Service
|
||||
|
||||
promptCalls []promptCall
|
||||
startCreatedCalls []startCreatedCall
|
||||
resumeCalls int
|
||||
turnStartCalls []turnStartCall
|
||||
onTurnStart func(context.Context, string, string) error
|
||||
interruptCalls []interruptCall
|
||||
launchCalls []*orchestrator.LaunchSessionRequest
|
||||
launchErr error
|
||||
renameCalls []renameCall
|
||||
renameErr error
|
||||
promptCalls []promptCall
|
||||
startCreatedCalls []startCreatedCall
|
||||
resumeCalls int
|
||||
turnStartCalls []turnStartCall
|
||||
onTurnStart func(context.Context, string, string) error
|
||||
interruptCalls []interruptCall
|
||||
launchCalls []*orchestrator.LaunchSessionRequest
|
||||
launchErr error
|
||||
launchResponseProfileID string
|
||||
renameCalls []renameCall
|
||||
renameErr error
|
||||
|
||||
// Configurable: error returned by PromptTask. Cleared after first call so
|
||||
// the retry-after-resume path can succeed on the second call.
|
||||
@@ -99,12 +100,18 @@ func (f *fakeOrchestrator) LaunchSession(_ context.Context, req *orchestrator.La
|
||||
if f.launchErr != nil {
|
||||
return nil, f.launchErr
|
||||
}
|
||||
return &orchestrator.LaunchSessionResponse{
|
||||
response := &orchestrator.LaunchSessionResponse{
|
||||
Success: true,
|
||||
TaskID: req.TaskID,
|
||||
SessionID: "spawned-sess-1",
|
||||
State: "STARTING",
|
||||
}, nil
|
||||
}
|
||||
profileID := req.AgentProfileID
|
||||
if f.launchResponseProfileID != "" {
|
||||
profileID = f.launchResponseProfileID
|
||||
}
|
||||
response.AgentProfileID = profileID
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (f *fakeOrchestrator) PromptTask(_ context.Context, taskID, sessionID, prompt, _ string, _ bool, _ []v1.MessageAttachment, dispatchOnly bool) (*orchestrator.PromptResult, error) {
|
||||
|
||||
@@ -67,7 +67,7 @@ func (h *Handlers) handleSpawnSession(ctx context.Context, msg *ws.Message) (*ws
|
||||
"task_id": req.TaskID,
|
||||
"session_id": resp.SessionID,
|
||||
"state": resp.State,
|
||||
"agent_profile_id": profileID,
|
||||
"agent_profile_id": resp.AgentProfileID,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -12,6 +13,8 @@ import (
|
||||
"github.com/kandev/kandev/internal/orchestrator"
|
||||
"github.com/kandev/kandev/internal/task/models"
|
||||
"github.com/kandev/kandev/internal/task/service"
|
||||
workflowmodels "github.com/kandev/kandev/internal/workflow/models"
|
||||
v1 "github.com/kandev/kandev/pkg/api/v1"
|
||||
ws "github.com/kandev/kandev/pkg/websocket"
|
||||
)
|
||||
|
||||
@@ -106,6 +109,89 @@ func TestHandleSpawnSession_ExplicitProfile(t *testing.T) {
|
||||
assert.Empty(t, orch.renameCalls)
|
||||
}
|
||||
|
||||
func TestHandleSpawnSessionReportsEffectiveAgentProfile(t *testing.T) {
|
||||
svc, repo := newTestTaskService(t)
|
||||
_, target, sess := seedTaskWithSession(t, svc, repo, models.TaskSessionStateRunning)
|
||||
|
||||
h, orch := newMessageTaskHandler(t, svc)
|
||||
orch.launchResponseProfileID = "workflow-default-profile"
|
||||
|
||||
payload := spawnPayload(target.ID, "work with the workflow profile", target.ID, sess.ID)
|
||||
payload["agent_profile_id"] = "requested-profile"
|
||||
msg := makeWSMessage(t, ws.ActionMCPSpawnSession, payload)
|
||||
resp, err := h.handleSpawnSession(context.Background(), msg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ws.MessageTypeResponse, resp.Type)
|
||||
|
||||
var out map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(resp.Payload, &out))
|
||||
assert.Equal(t, "workflow-default-profile", out["agent_profile_id"])
|
||||
assert.Equal(t, "requested-profile", orch.launchCalls[0].AgentProfileID)
|
||||
}
|
||||
|
||||
func TestHandleSpawnSessionReportsStepPinnedAgentProfile(t *testing.T) {
|
||||
svc, repo, _, workflowRepo := newTestTaskServiceWithWorkflow(t)
|
||||
ctx := context.Background()
|
||||
workspace, workflow := defaultWorkspaceAndWorkflow(t, ctx, svc)
|
||||
workflowProfileID := "workflow-default-profile"
|
||||
_, err := svc.UpdateWorkflow(ctx, workflow.ID, &service.UpdateWorkflowRequest{
|
||||
AgentProfileID: &workflowProfileID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
step := seedWorkflowStep(t, ctx, workflowRepo, &workflowmodels.WorkflowStep{
|
||||
WorkflowID: workflow.ID,
|
||||
Name: "Pinned",
|
||||
Position: 0,
|
||||
IsStartStep: true,
|
||||
AgentProfileID: "step-pinned-profile",
|
||||
AllowManualMove: true,
|
||||
})
|
||||
now := time.Now().UTC()
|
||||
target := &models.Task{
|
||||
ID: "task-pinned-target",
|
||||
WorkspaceID: workspace.ID,
|
||||
WorkflowID: workflow.ID,
|
||||
WorkflowStepID: step.ID,
|
||||
Title: "Target task",
|
||||
State: v1.TaskStateInProgress,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
sender := &models.Task{
|
||||
ID: "task-pinned-sender",
|
||||
WorkspaceID: workspace.ID,
|
||||
WorkflowID: workflow.ID,
|
||||
Title: "Sender task",
|
||||
State: v1.TaskStateInProgress,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
require.NoError(t, repo.CreateTask(ctx, target))
|
||||
require.NoError(t, repo.CreateTask(ctx, sender))
|
||||
session := &models.TaskSession{
|
||||
ID: "sess-pinned",
|
||||
TaskID: target.ID,
|
||||
AgentProfileID: "sender-profile",
|
||||
IsPrimary: true,
|
||||
State: models.TaskSessionStateRunning,
|
||||
}
|
||||
require.NoError(t, repo.CreateTaskSession(ctx, session))
|
||||
|
||||
h, orch := newMessageTaskHandler(t, svc, repo)
|
||||
orch.launchResponseProfileID = step.AgentProfileID
|
||||
payload := spawnPayload(target.ID, "use the pinned profile", sender.ID, session.ID)
|
||||
payload["agent_profile_id"] = "explicit-profile"
|
||||
msg := makeWSMessage(t, ws.ActionMCPSpawnSession, payload)
|
||||
resp, err := h.handleSpawnSession(ctx, msg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ws.MessageTypeResponse, resp.Type)
|
||||
|
||||
var out map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(resp.Payload, &out))
|
||||
assert.Equal(t, "step-pinned-profile", out["agent_profile_id"])
|
||||
assert.Equal(t, "explicit-profile", orch.launchCalls[0].AgentProfileID)
|
||||
}
|
||||
|
||||
// Cross-task spawns (sender session belongs to another task) fall back to the
|
||||
// target task's primary-session profile.
|
||||
func TestHandleSpawnSession_CrossTask_DefaultsToTargetPrimaryProfile(t *testing.T) {
|
||||
|
||||
@@ -974,9 +974,11 @@ Unlike create_task_kandev this does NOT create a new task — the new session ru
|
||||
|
||||
The spawned session knows it was spawned by you and can reply via message_task_kandev using your task_id + session_id. You can message it the same way using the session_id returned by this tool.
|
||||
|
||||
Returns {task_id, session_id, state}.`),
|
||||
The returned agent_profile_id is the effective agent profile used by the new session. On a workflow step, the step's launch profile wins: a pinned step profile outranks the requested agent_profile_id, and an unpinned step uses the workflow default. Without a workflow launch profile, an explicit agent_profile_id wins; when omitted, the existing inheritance rules apply.
|
||||
|
||||
Returns {task_id, session_id, state, agent_profile_id}.`),
|
||||
mcp.WithString("prompt", mcp.Required(), mcp.Description("The spawned session's initial prompt. This is the ONLY context the new agent receives — be specific and detailed.")),
|
||||
mcp.WithString("agent_profile_id", mcp.Description("Agent profile for the new session. Omit to reuse your own session's agent profile.")),
|
||||
mcp.WithString("agent_profile_id", mcp.Description("Requested agent profile for the new session. Omit to inherit your session's profile; a workflow launch profile may override it.")),
|
||||
mcp.WithString("name", mcp.Description("Optional session name shown on the session tab (e.g. 'reviewer'). Helps the user tell concurrent sessions apart.")),
|
||||
mcp.WithString("task_id", mcp.Description("Task to spawn the session on. Omit to use your current task.")),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSpawnSessionToolDescribesEffectiveAgentProfile(t *testing.T) {
|
||||
s := newTaskModeServer(t, &testBackend{}, "task-current")
|
||||
|
||||
tool, ok := s.mcpServer.ListTools()["spawn_session_kandev"]
|
||||
require.True(t, ok, "spawn_session_kandev must be registered in task mode")
|
||||
|
||||
description := tool.Tool.Description
|
||||
assert.Contains(t, description, "agent_profile_id")
|
||||
assert.Contains(t, description, "effective agent profile")
|
||||
assert.Contains(t, description, "Returns {task_id, session_id, state, agent_profile_id}")
|
||||
|
||||
profileProperty, ok := toolInputProperties(t, s, "spawn_session_kandev")["agent_profile_id"].(map[string]interface{})
|
||||
require.True(t, ok, "agent_profile_id input must be described")
|
||||
assert.Contains(t, profileProperty["description"], "workflow launch profile may override it")
|
||||
}
|
||||
@@ -91,6 +91,7 @@ type LaunchSessionResponse struct {
|
||||
TaskID string `json:"task_id"`
|
||||
SessionID string `json:"session_id,omitempty"`
|
||||
AgentExecutionID string `json:"agent_execution_id,omitempty"`
|
||||
AgentProfileID string `json:"agent_profile_id,omitempty"`
|
||||
State string `json:"state"`
|
||||
WorktreePath *string `json:"worktree_path,omitempty"`
|
||||
WorktreeBranch *string `json:"worktree_branch,omitempty"`
|
||||
@@ -415,6 +416,7 @@ func executionToLaunchResponse(taskID string, exec *executor.TaskExecution) *Lau
|
||||
TaskID: taskID,
|
||||
SessionID: exec.SessionID,
|
||||
AgentExecutionID: exec.AgentExecutionID,
|
||||
AgentProfileID: exec.AgentProfileID,
|
||||
State: string(exec.SessionState),
|
||||
}
|
||||
if exec.WorktreePath != "" {
|
||||
|
||||
@@ -8,12 +8,25 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/kandev/kandev/internal/agent/runtime/lifecycle"
|
||||
"github.com/kandev/kandev/internal/orchestrator/executor"
|
||||
"github.com/kandev/kandev/internal/task/models"
|
||||
sqliterepo "github.com/kandev/kandev/internal/task/repository/sqlite"
|
||||
wfmodels "github.com/kandev/kandev/internal/workflow/models"
|
||||
v1 "github.com/kandev/kandev/pkg/api/v1"
|
||||
)
|
||||
|
||||
func TestExecutionToLaunchResponseReportsAgentProfile(t *testing.T) {
|
||||
response := executionToLaunchResponse("task-1", &executor.TaskExecution{
|
||||
SessionID: "session-1",
|
||||
AgentExecutionID: "execution-1",
|
||||
AgentProfileID: "effective-profile",
|
||||
SessionState: v1.TaskSessionStateRunning,
|
||||
})
|
||||
if got, want := response.AgentProfileID, "effective-profile"; got != want {
|
||||
t.Fatalf("agent_profile_id = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIntent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
spec: docs/specs/spawn-session-effective-profile/spec.md
|
||||
created: 2026-08-12
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Implementation Plan: Spawn Session Effective Agent Profile
|
||||
|
||||
## Overview
|
||||
|
||||
`spawn_session_kandev` sends a requested profile to `LaunchSession`, then reports that request value. The orchestrator can replace it with the workflow launch profile.
|
||||
|
||||
The launch path already retains the effective profile in `executor.TaskExecution`. The fix adds it to `LaunchSessionResponse` and uses that value in the MCP response.
|
||||
|
||||
## Confirmed root cause
|
||||
|
||||
`resolveSpawnAgentProfile` selects the pre-launch profile. `handleSpawnSession` reports that value after `LaunchSession` returns.
|
||||
|
||||
`startTask` can replace that value through `resolveEffectiveAgentProfile`. `executionToLaunchResponse` drops the resulting `TaskExecution.AgentProfileID`, so the MCP handler cannot report it.
|
||||
|
||||
`create_task_kandev` does not need a code change. PR #2341 already aligns its stored and reported profile with workflow-default precedence.
|
||||
|
||||
## Backend
|
||||
|
||||
### Authoritative launch response
|
||||
|
||||
- Update `apps/backend/internal/orchestrator/session_launch.go`.
|
||||
- Add `AgentProfileID` to `LaunchSessionResponse`.
|
||||
- Copy `TaskExecution.AgentProfileID` in `executionToLaunchResponse`.
|
||||
- Keep launch precedence and session persistence unchanged.
|
||||
|
||||
### MCP response and tool contract
|
||||
|
||||
- Update `apps/backend/internal/mcp/handlers/spawn_session.go`.
|
||||
- Build `agent_profile_id` from `LaunchSessionResponse.AgentProfileID`.
|
||||
- Update `apps/backend/internal/mcp/server/server.go`.
|
||||
- State workflow profile precedence and the full success response shape.
|
||||
|
||||
## Tests
|
||||
|
||||
- **What:** The launch response carries the effective profile from `TaskExecution`.
|
||||
**File:** `apps/backend/internal/orchestrator/session_launch_test.go`.
|
||||
**How:** Pass a `TaskExecution` with an effective profile to `executionToLaunchResponse`.
|
||||
- **What:** The MCP handler reports the launch result when it differs from the requested profile.
|
||||
**File:** `apps/backend/internal/mcp/handlers/spawn_session_test.go`.
|
||||
**How:** Use the real task service and SQLite repository for target authorization. Return a different effective profile from the fake launcher.
|
||||
- **What:** The registered tool describes workflow precedence and the `agent_profile_id` response field.
|
||||
**File:** `apps/backend/internal/mcp/server/spawn_session_tool_test.go`.
|
||||
**How:** Inspect the registered tool description without adding tests to existing files above the test-file size limit.
|
||||
- **What:** The existing `create_task_kandev` workflow-default regressions remain green.
|
||||
**File:** `apps/backend/internal/mcp/handlers/handlers_test.go`.
|
||||
**How:** Run the focused tests from PR #2341 without changing them.
|
||||
|
||||
## Public documentation
|
||||
|
||||
Update `docs/public/agent-communication.md`, which is the explanation page for task-agent messages and related MCP tools. Add the effective profile to both documented response shapes and explain workflow precedence.
|
||||
|
||||
No frontend or E2E change is required. The change affects an MCP response and its documentation, not a visual UI flow.
|
||||
|
||||
## Verification Results
|
||||
|
||||
- Task 01 red regressions failed as expected: the launch response omitted the
|
||||
effective profile, the MCP handler returned the requested profile, and the
|
||||
tool description omitted the effective response field.
|
||||
- Task 01 exact verification passed: 6 tests across the orchestrator, MCP
|
||||
handlers, and MCP server packages.
|
||||
- Affected backend package verification passed: `go test ./internal/orchestrator
|
||||
./internal/mcp/handlers ./internal/mcp/server -count=1`.
|
||||
- Full backend verification passed: `make -C apps/backend test` ran
|
||||
`CGO_ENABLED=1 go test -tags fts5 ./...` successfully.
|
||||
- Backend lint passed: `make -C apps/backend lint` reported no issues.
|
||||
- Task 02 public-doc verification passed: 60 validation tests and 41 published
|
||||
pages validated.
|
||||
- Formatting and whitespace checks passed: `gofmt -l` reported no files and
|
||||
`git diff --check` reported no errors.
|
||||
|
||||
## Implementation Waves And Parallel Candidates
|
||||
|
||||
Wave 1:
|
||||
|
||||
- [x] [Task 01: Propagate the effective profile](task-01-propagate-effective-profile.md)
|
||||
|
||||
Wave 2:
|
||||
|
||||
- [x] [Task 02: Update public MCP documentation](task-02-update-public-docs.md)
|
||||
|
||||
Execution is sequential. The documentation task depends on the final response contract from Task 01.
|
||||
|
||||
## Risks and limits
|
||||
|
||||
- `LaunchSessionResponse` serves several launch intents. The new field is additive and comes from the existing execution record.
|
||||
- `spawn_session_kandev` always uses `IntentStart`, so its successful response has an effective execution profile.
|
||||
- This work does not change profile precedence. It only reports the result of existing precedence.
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
id: "01-propagate-effective-profile"
|
||||
title: "Propagate the effective profile"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/spawn-session-effective-profile/spec.md"
|
||||
---
|
||||
|
||||
# Task 01: Propagate the effective profile
|
||||
|
||||
## Intent
|
||||
|
||||
Make the synchronous launch result authoritative for the profile that `spawn_session_kandev` reports.
|
||||
|
||||
## Inputs
|
||||
|
||||
- The spec `What`, `API surface`, and `Scenarios` sections.
|
||||
- The confirmed root cause in `plan.md`.
|
||||
- The response propagation pattern in `executionToLaunchResponse`.
|
||||
- The existing handler test setup in `spawn_session_test.go` and `message_task_test.go`.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- `LaunchSessionResponse.AgentProfileID` equals the effective profile in `TaskExecution` for a successful start.
|
||||
- `spawn_session_kandev` reports that effective profile, including workflow-default and step-pinned overrides.
|
||||
- The tool description states the precedence and includes `agent_profile_id` in its response shape.
|
||||
- Existing `create_task_kandev` workflow-default tests remain green.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/backend/internal/orchestrator/session_launch.go`
|
||||
- `apps/backend/internal/orchestrator/session_launch_test.go`
|
||||
- `apps/backend/internal/mcp/handlers/spawn_session.go`
|
||||
- `apps/backend/internal/mcp/handlers/spawn_session_test.go`
|
||||
- `apps/backend/internal/mcp/handlers/message_task_test.go`
|
||||
- `apps/backend/internal/mcp/server/server.go`
|
||||
- `apps/backend/internal/mcp/server/spawn_session_tool_test.go`
|
||||
|
||||
## TDD sequence
|
||||
|
||||
1. Add the launch-response and MCP-response regressions.
|
||||
2. Run the focused command and make sure that the new regressions fail for the expected missing field or echoed value.
|
||||
3. Add the response field and propagate the effective value.
|
||||
4. Update the tool description and its contract test.
|
||||
5. Run the same focused command and make sure that all selected tests pass.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd "$(git rev-parse --show-toplevel)/apps/backend" && go test ./internal/orchestrator ./internal/mcp/handlers ./internal/mcp/server -run 'TestExecutionToLaunchResponseReportsAgentProfile|TestHandleSpawnSessionReports(Effective|StepPinned)AgentProfile|TestSpawnSessionToolDescribesEffectiveAgentProfile|TestResolveMCPAutoStartConfig_WorkflowDefaultOutranksExplicit(OnUnpinnedStep|WhenStepOmitted)' -count=1
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`. This task changes the shared launch response contract and its MCP consumer.
|
||||
|
||||
## Output contract
|
||||
|
||||
Report the files changed, the red and green test results, blockers, and risks. Update this task and `plan.md` in the same conversation.
|
||||
|
||||
## Results
|
||||
|
||||
- Red: the launch-response and tool-description regressions failed because the
|
||||
response omitted `agent_profile_id`; the handler regression failed because it
|
||||
returned the requested profile instead of the effective launch result.
|
||||
- Green: `go test ./internal/orchestrator ./internal/mcp/handlers
|
||||
./internal/mcp/server -run 'TestExecutionToLaunchResponseReportsAgentProfile|TestHandleSpawnSessionReportsEffectiveAgentProfile|TestSpawnSessionToolDescribesEffectiveAgentProfile|TestResolveMCPAutoStartConfig_WorkflowDefaultOutranksExplicit(OnUnpinnedStep|WhenStepOmitted)' -count=1`
|
||||
passed 5 tests in 3 packages.
|
||||
- Fixup regression extension: the focused command with
|
||||
`TestHandleSpawnSessionReportsStepPinnedAgentProfile` passed 6 tests in 3
|
||||
packages.
|
||||
- Full backend gate: `make -C apps/backend test` passed
|
||||
`CGO_ENABLED=1 go test -tags fts5 ./...`.
|
||||
- Backend lint: `make -C apps/backend lint` passed with no issues.
|
||||
- Affected package suite: `go test ./internal/orchestrator
|
||||
./internal/mcp/handlers ./internal/mcp/server -count=1` passed.
|
||||
- Formatting and whitespace checks passed. No throwaway files or logs remain.
|
||||
- External side effects: None.
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
id: "02-update-public-docs"
|
||||
title: "Update public MCP documentation"
|
||||
status: done
|
||||
wave: 2
|
||||
depends_on: ["01-propagate-effective-profile"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/spawn-session-effective-profile/spec.md"
|
||||
---
|
||||
|
||||
# Task 02: Update public MCP documentation
|
||||
|
||||
## Intent
|
||||
|
||||
Document the effective profile response on the public Agent Communication explanation page.
|
||||
|
||||
## Inputs
|
||||
|
||||
- The spec `API surface` and `Scenarios` sections.
|
||||
- The final tool description from Task 01.
|
||||
- The existing `spawn_session_kandev` sections in `docs/public/agent-communication.md`.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Both documented success shapes include `agent_profile_id`.
|
||||
- The page states that the field is the effective profile after workflow profile resolution.
|
||||
- The page does not imply that an explicit profile always launches on a workflow step.
|
||||
- The public documentation checks pass.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `docs/public/agent-communication.md`
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd "$(git rev-parse --show-toplevel)" && rg -n "spawn_session_kandev|agent_profile_id" docs/public/agent-communication.md && node --test scripts/validate-public-docs.test.mjs && node scripts/validate-public-docs.mjs
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
Task 01.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`. The documented response must match the completed backend contract.
|
||||
|
||||
## Output contract
|
||||
|
||||
Report the changed page, its explanation classification, each command result, blockers, and risks. Update this task and `plan.md` in the same conversation.
|
||||
|
||||
## Results
|
||||
|
||||
- Updated `docs/public/agent-communication.md` with both documented
|
||||
`spawn_session_kandev` success shapes and the effective-profile precedence.
|
||||
- `rg -n "spawn_session_kandev|agent_profile_id"
|
||||
docs/public/agent-communication.md` found the updated contract.
|
||||
- `node --test scripts/validate-public-docs.test.mjs` passed all 60 tests.
|
||||
- `node scripts/validate-public-docs.mjs` validated 41 published pages.
|
||||
- Generated artifacts and temporary capture files: None. External side
|
||||
effects: None.
|
||||
@@ -40,8 +40,12 @@ message_task_kandev(task_id="<full UUID>", prompt="<message text>")
|
||||
|
||||
By default, the message goes to the task's primary session. Advanced callers can pass
|
||||
`session_id` to target a specific session that belongs to that task, including a sibling
|
||||
session on their own task. `spawn_session_kandev` returns `{task_id, session_id, state}`
|
||||
when an agent starts an additional session on an existing task; pass the `session_id` field to `message_task_kandev`.
|
||||
session on their own task. `spawn_session_kandev` returns `{task_id, session_id, state, agent_profile_id}`
|
||||
when an agent starts an additional session on an existing task; pass the `session_id` field to
|
||||
`message_task_kandev`. The returned `agent_profile_id` is the effective profile after workflow
|
||||
profile resolution. A pinned workflow-step profile wins first, followed by the workflow default
|
||||
on an unpinned step. Without a workflow launch profile, an explicit profile wins, and the existing
|
||||
inheritance rules apply when no profile is provided.
|
||||
|
||||
Delivery behaviour depends on the target session's state at the moment of the call:
|
||||
|
||||
@@ -239,7 +243,7 @@ These tools complement cross-task communication for common coordination patterns
|
||||
| `list_task_sessions_kandev` | List a task's sessions to find the `session_id` for the two tools above |
|
||||
| `list_related_tasks_kandev` | Discover parent / child / sibling / blocker task IDs |
|
||||
| `create_task_kandev` | Delegate work to a new subtask; returns the new task's ID |
|
||||
| `spawn_session_kandev` | Start another session on an existing task; returns `{task_id, session_id, state}`; use the `session_id` field to message the new session directly |
|
||||
| `spawn_session_kandev` | Start another session on an existing task; returns `{task_id, session_id, state, agent_profile_id}`, where `agent_profile_id` is the effective profile after workflow resolution; use the `session_id` field to message the new session directly |
|
||||
| `move_task_kandev` | Hand off a task to the next workflow step with an optional prompt for the receiving agent |
|
||||
| `create_task_plan_kandev` | Record an agreed implementation plan (both tasks can create/update their own plans) |
|
||||
| `get_task_plan_kandev` | Read a task's plan; useful before messaging to share a structured proposal |
|
||||
|
||||
@@ -89,6 +89,7 @@ Kandev's task model: documents, execution stages, labels, blocker escalation, su
|
||||
| [parent-child-message-interrupt](tasks/parent-child-message-interrupt.md) | shipped |
|
||||
| [parent-child-task-stop](tasks/parent-child-task-stop.md) | shipped |
|
||||
| [mcp-task-agent-profile-default](tasks/mcp-task-agent-profile-default/spec.md) | shipped |
|
||||
| [spawn-session-effective-profile](spawn-session-effective-profile/spec.md) | shipped |
|
||||
| [runtime-state-publication-order](tasks/runtime-state-publication-order.md) | shipped |
|
||||
| [agent-generated-titles](tasks/agent-generated-titles.md) | approved |
|
||||
| [task-create-executor-default](tasks/task-create-executor-default.md) | approved |
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
status: shipped
|
||||
created: 2026-08-12
|
||||
owner: kandev
|
||||
---
|
||||
|
||||
# Spawn Session Effective Agent Profile
|
||||
|
||||
## Why
|
||||
|
||||
Agents use `spawn_session_kandev` to start another session with a selected agent profile. The tool must report the profile that the new session uses.
|
||||
|
||||
Without this guarantee, a caller can receive confirmation for one profile while a workflow profile starts instead.
|
||||
|
||||
## What
|
||||
|
||||
- `spawn_session_kandev` reports the effective agent profile for each successful launch.
|
||||
- The reported profile is the profile on the new session, after workflow profile resolution.
|
||||
- A workflow step launch profile keeps its current precedence. A pinned step profile wins first. Otherwise, the workflow default wins on that step.
|
||||
- Without a workflow launch profile, an explicit `agent_profile_id` keeps its current precedence.
|
||||
- Without an explicit profile, the current inheritance rules remain unchanged.
|
||||
- The tool description explains the precedence and the response field.
|
||||
|
||||
## API surface
|
||||
|
||||
The successful `spawn_session_kandev` response has this shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "string",
|
||||
"session_id": "string",
|
||||
"state": "string",
|
||||
"agent_profile_id": "string"
|
||||
}
|
||||
```
|
||||
|
||||
`agent_profile_id` identifies the effective profile on the new session. It does not echo an earlier profile choice when workflow resolution selects another profile.
|
||||
|
||||
## Failure modes
|
||||
|
||||
- If the session launch fails, the tool returns the existing launch error and no success response.
|
||||
- A successful session launch returns the nonempty effective profile from the launch result.
|
||||
- A session name update can still fail without changing the successful launch result.
|
||||
|
||||
## Scenarios
|
||||
|
||||
- **GIVEN** an unpinned workflow step with workflow default A, **WHEN** a caller requests profile B, **THEN** the new session and response use profile A.
|
||||
- **GIVEN** a workflow step pinned to profile A, **WHEN** a caller requests profile B, **THEN** the new session and response use profile A.
|
||||
- **GIVEN** no workflow launch profile, **WHEN** a caller requests profile B, **THEN** the new session and response use profile B.
|
||||
- **GIVEN** no explicit profile or workflow launch profile, **WHEN** a same-task session spawns another session, **THEN** the new session and response use the inherited profile.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Changing workflow, step, explicit, or inherited profile precedence.
|
||||
- Changing `create_task_kandev`. Its workflow-default response behavior is already covered by the MCP-created task profile spec.
|
||||
- Changing session persistence, executor selection, or profile validation.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
See [`../../plans/spawn-session-effective-profile/plan.md`](../../plans/spawn-session-effective-profile/plan.md).
|
||||
Reference in New Issue
Block a user