diff --git a/backend/internal/session_manager/manager.go b/backend/internal/session_manager/manager.go index 1ae83837..9104a8c7 100644 --- a/backend/internal/session_manager/manager.go +++ b/backend/internal/session_manager/manager.go @@ -594,25 +594,18 @@ func buildPrompt(cfg ports.SpawnConfig) string { return cfg.Prompt } -// orchestratorKickoffPrompt is the default first turn for an orchestrator -// spawned without an explicit prompt. The role definition rides the system -// prompt, and an interactive agent launched with only a system prompt sits at -// an empty input box; this gives it a turn to act on. -const orchestratorKickoffPrompt = "Get oriented: review the current repo state and any active worker sessions, then report your status and wait for direction." - // buildSpawnTexts returns the user-facing prompt and the system prompt to // deliver separately to the agent. Orchestrator role instructions and worker // coordination hints are placed in the system prompt so they are treated as -// standing instructions rather than part of the human's task request. +// standing instructions rather than part of the human's task request. A +// promptless spawn delivers no user prompt at all: the agent simply lands at an +// empty input box rather than receiving an auto-generated kickoff turn. func (m *Manager) buildSpawnTexts(ctx context.Context, cfg ports.SpawnConfig) (prompt, systemPrompt string, err error) { prompt = buildPrompt(cfg) systemPrompt, err = m.buildSystemPrompt(ctx, cfg.Kind, cfg.ProjectID) if err != nil { return "", "", err } - if cfg.Kind == domain.KindOrchestrator && prompt == "" { - prompt = orchestratorKickoffPrompt - } return prompt, systemPrompt, nil } @@ -858,7 +851,7 @@ func restoreArgv(ctx context.Context, agent ports.Agent, id domain.SessionID, wo WorkspacePath: workspacePath, Metadata: map[string]string{ports.MetadataKeyAgentSessionID: meta.AgentSessionID}, } - cmd, ok, err := agent.GetRestoreCommand(ctx, ports.RestoreConfig{Session: ref, Config: agentConfig, Permissions: agentConfig.Permissions}) + cmd, ok, err := agent.GetRestoreCommand(ctx, ports.RestoreConfig{Session: ref, SystemPrompt: systemPrompt, Config: agentConfig, Permissions: agentConfig.Permissions}) if err != nil { return nil, fmt.Errorf("restore command: %w", err) } diff --git a/backend/internal/session_manager/manager_test.go b/backend/internal/session_manager/manager_test.go index 01ee199c..521b72e7 100644 --- a/backend/internal/session_manager/manager_test.go +++ b/backend/internal/session_manager/manager_test.go @@ -168,6 +168,11 @@ func (a *recordingAgent) GetLaunchCommand(_ context.Context, cfg ports.LaunchCon func (a *recordingAgent) GetRestoreCommand(_ context.Context, cfg ports.RestoreConfig) ([]string, bool, error) { a.lastConfig = cfg.Config a.lastRestore = cfg + // Mirror real adapters: with no native agent-session id to resume, signal + // "cannot restore" so the manager falls back to a fresh launch. + if cfg.Session.Metadata[ports.MetadataKeyAgentSessionID] == "" { + return nil, false, nil + } return []string{"resume"}, true, nil } @@ -635,10 +640,10 @@ func TestSpawnOrchestrator_UsesCoordinatorPrompt(t *testing.T) { t.Fatalf("coordinator role must not be in the user prompt:\n%s", agent.lastLaunch.Prompt) } - // A promptless orchestrator still needs a first turn: with the role in the - // system prompt only, an interactive agent would idle at an empty input box. - if agent.lastLaunch.Prompt != orchestratorKickoffPrompt { - t.Fatalf("prompt = %q, want kick-off prompt", agent.lastLaunch.Prompt) + // A promptless orchestrator gets no auto-generated kickoff turn: spawning + // must deliver nothing to the agent, leaving it idle at an empty input box. + if agent.lastLaunch.Prompt != "" { + t.Fatalf("prompt = %q, want empty (no kickoff turn)", agent.lastLaunch.Prompt) } }