diff --git a/src/agent/subagents/manager.ts b/src/agent/subagents/manager.ts index 652cc3f..b8a7e70 100644 --- a/src/agent/subagents/manager.ts +++ b/src/agent/subagents/manager.ts @@ -328,8 +328,9 @@ function buildSubagentArgs( // conversation_id is sufficient (headless derives agent from it) args.push("--conv", existingConversationId); } else if (existingAgentId) { - // agent_id only - headless creates new conversation - args.push("--agent", existingAgentId); + // agent_id only - use --new to create a new conversation for thread safety + // (multiple parallel calls to the same agent need separate conversations) + args.push("--agent", existingAgentId, "--new"); } // Don't pass --system (existing agent keeps its prompt) // Don't pass --model (existing agent keeps its model) diff --git a/src/headless.ts b/src/headless.ts index 7a0a0d4..fe1a70d 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -641,16 +641,15 @@ export async function handleHeadlessCommand( ); process.exit(1); } - } else if (forceNewConversation || forceNew) { - // --new flag (new conversation) or --new-agent (new agent): create a new conversation - // When creating a new agent, always create a new conversation alongside it + } else if (forceNewConversation) { + // --new flag: create a new conversation (for concurrent sessions) const conversation = await client.conversations.create({ agent_id: agent.id, isolated_block_labels: isolatedBlockLabels, }); conversationId = conversation.id; } else { - // Default: use the agent's "default" conversation (OG single-threaded behavior) + // Default (including --new-agent, --agent): use the agent's "default" conversation conversationId = "default"; } markMilestone("HEADLESS_CONVERSATION_READY"); diff --git a/src/index.ts b/src/index.ts index 1bd4ef3..0a9009b 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1731,16 +1731,15 @@ async function main(): Promise { } throw error; } - } else if (forceNewConversation || forceNew) { - // --new flag (new conversation) or --new-agent (new agent): create a new conversation - // When creating a new agent, always create a new conversation alongside it + } else if (forceNewConversation) { + // --new flag: create a new conversation (for concurrent sessions) const conversation = await client.conversations.create({ agent_id: agent.id, isolated_block_labels: [...ISOLATED_BLOCK_LABELS], }); conversationIdToUse = conversation.id; } else { - // Default: use the agent's "default" conversation (OG single-threaded behavior) + // Default (including --new-agent): use the agent's "default" conversation conversationIdToUse = "default"; // Load message history from the default conversation diff --git a/src/tests/startup-flow.test.ts b/src/tests/startup-flow.test.ts index 47c2775..14315ba 100644 --- a/src/tests/startup-flow.test.ts +++ b/src/tests/startup-flow.test.ts @@ -187,7 +187,6 @@ describe("Startup Flow - Invalid Inputs", () => { describe("Startup Flow - Integration", () => { // Store created agent/conversation IDs for cleanup and reuse let testAgentId: string | null = null; - let testConversationId: string | null = null; test( "--new-agent creates agent and responds", @@ -214,7 +213,6 @@ describe("Startup Flow - Integration", () => { // Save for later tests testAgentId = output.agent_id; - testConversationId = output.conversation_id; }, { timeout: 130000 }, ); @@ -253,16 +251,41 @@ describe("Startup Flow - Integration", () => { test( "--conversation with valid ID derives agent and uses conversation", async () => { - // Skip if previous test didn't create an agent/conversation - if (!testAgentId || !testConversationId) { - console.log("Skipping: no test conversation available"); + // Skip if previous test didn't create an agent + if (!testAgentId) { + console.log("Skipping: no test agent available"); return; } + // First, create a real conversation with --new (since --new-agent uses "default") + const createResult = await runCli( + [ + "--agent", + testAgentId, + "--new", + "-m", + "haiku", + "-p", + "Say CREATED", + "--output-format", + "json", + ], + { timeoutMs: 120000 }, + ); + expect(createResult.exitCode).toBe(0); + const createJsonStart = createResult.stdout.indexOf("{"); + const createOutput = JSON.parse( + createResult.stdout.slice(createJsonStart), + ); + const realConversationId = createOutput.conversation_id; + expect(realConversationId).toBeDefined(); + expect(realConversationId).not.toBe("default"); + + // Now test that --conversation can derive the agent from this conversation const result = await runCli( [ "--conversation", - testConversationId, + realConversationId, "-m", "haiku", "-p", @@ -279,9 +302,9 @@ describe("Startup Flow - Integration", () => { // Should use the same agent that owns the conversation expect(output.agent_id).toBe(testAgentId); // Should use the specified conversation - expect(output.conversation_id).toBe(testConversationId); + expect(output.conversation_id).toBe(realConversationId); }, - { timeout: 130000 }, + { timeout: 180000 }, ); test(