diff --git a/coderd/exp_chats_test.go b/coderd/exp_chats_test.go index 34438f895b..b9718c996e 100644 --- a/coderd/exp_chats_test.go +++ b/coderd/exp_chats_test.go @@ -10546,7 +10546,10 @@ func TestChatSystemPrompt(t *testing.T) { memberClientRaw, _ := coderdtest.CreateAnotherUser(t, adminClient.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) - const workspaceAwareness = "There is no workspace associated with this chat yet. Create one using the create_workspace tool before using workspace tools like execute, read_file, write_file, etc." + const workspaceAwareness = `No workspace is attached to this chat yet. +Do not create or start a workspace by default. Many requests can be completed using the conversation, provider tools such as web_search when available, or configured external MCP tools. +Workspace tools such as execute, read_file, write_file, and edit_files require an attached workspace. Only call create_workspace or start_workspace when the user explicitly asks for a workspace-backed task, or when the task cannot be completed without inspecting, editing, or running files in a workspace. +If a workspace is needed, use list_templates and read_template as needed before create_workspace.` updateChatSystemPrompt := func(t *testing.T, ctx context.Context, req codersdk.UpdateChatSystemPromptRequest) { t.Helper() diff --git a/coderd/x/chatd/chatd.go b/coderd/x/chatd/chatd.go index c8c3d8968f..991108a02b 100644 --- a/coderd/x/chatd/chatd.go +++ b/coderd/x/chatd/chatd.go @@ -1582,11 +1582,9 @@ func (p *Server) CreateChat(ctx context.Context, opts CreateOptions) (database.C } userPrompt := SanitizePromptText(opts.SystemPrompt) - var workspaceAwareness string + workspaceAwareness := workspaceDetachedAwareness if opts.WorkspaceID.Valid { - workspaceAwareness = "This chat is attached to a workspace. You can use workspace tools like execute, read_file, write_file, etc." - } else { - workspaceAwareness = "There is no workspace associated with this chat yet. Create one using the create_workspace tool before using workspace tools like execute, read_file, write_file, etc." + workspaceAwareness = workspaceAttachedAwareness } workspaceAwarenessContent, err := chatprompt.MarshalParts([]codersdk.ChatMessagePart{ codersdk.ChatMessageText(workspaceAwareness), diff --git a/coderd/x/chatd/chatd_test.go b/coderd/x/chatd/chatd_test.go index e9a0e8e1c2..f35aba147a 100644 --- a/coderd/x/chatd/chatd_test.go +++ b/coderd/x/chatd/chatd_test.go @@ -2270,7 +2270,7 @@ func TestCreateChatInsertsWorkspaceAwarenessMessage(t *testing.T) { for _, msg := range messages { if msg.Role == database.ChatMessageRoleSystem { content := string(msg.Content.RawMessage) - if strings.Contains(content, "no workspace associated") { + if strings.Contains(content, "No workspace is attached to this chat yet") { workspaceMsg = &msg break } @@ -2279,6 +2279,10 @@ func TestCreateChatInsertsWorkspaceAwarenessMessage(t *testing.T) { require.NotNil(t, workspaceMsg, "workspace awareness system message should exist") require.Equal(t, database.ChatMessageRoleSystem, workspaceMsg.Role) require.Equal(t, database.ChatMessageVisibilityModel, workspaceMsg.Visibility) + workspaceContent := string(workspaceMsg.Content.RawMessage) + require.Contains(t, workspaceContent, "Do not create or start a workspace by default") + require.Contains(t, workspaceContent, "Only call create_workspace or start_workspace") + require.NotContains(t, workspaceContent, "Create one using the create_workspace tool before using workspace tools") }) } diff --git a/coderd/x/chatd/chattool/createworkspace.go b/coderd/x/chatd/chattool/createworkspace.go index 2766733569..de0d617218 100644 --- a/coderd/x/chatd/chattool/createworkspace.go +++ b/coderd/x/chatd/chattool/createworkspace.go @@ -89,7 +89,11 @@ type createWorkspaceArgs struct { func CreateWorkspace(db database.Store, organizationID, chatID uuid.UUID, options CreateWorkspaceOptions) fantasy.AgentTool { return fantasy.NewAgentTool( "create_workspace", - "Create a new workspace from a template. Requires a "+ + "Create a new workspace from a template only when workspace-backed "+ + "file inspection, command execution, or file editing is required, "+ + "or when the user explicitly asks for one. Do not use this as a "+ + "default first step for requests answerable from conversation "+ + "context, provider tools, or external MCP tools. Requires a "+ "template_id (from list_templates). Optionally provide "+ "a name and parameter values (from read_template). "+ "If no name is given, one will be generated. "+ diff --git a/coderd/x/chatd/chattool/createworkspace_internal_test.go b/coderd/x/chatd/chattool/createworkspace_internal_test.go index ca87428200..06c4a95a84 100644 --- a/coderd/x/chatd/chattool/createworkspace_internal_test.go +++ b/coderd/x/chatd/chattool/createworkspace_internal_test.go @@ -34,6 +34,19 @@ func newCreateWorkspaceMockStore(ctrl *gomock.Controller) *dbmock.MockStore { return db } +func TestCreateWorkspaceDescriptionDelaysWorkspaceCreation(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + db := newCreateWorkspaceMockStore(ctrl) + tool := CreateWorkspace(db, uuid.New(), uuid.New(), CreateWorkspaceOptions{}) + info := tool.Info() + + require.Contains(t, info.Description, "Create a new workspace from a template only when workspace-backed") + require.Contains(t, info.Description, "user explicitly asks") + require.Contains(t, info.Description, "Do not use this as a default first step") +} + func TestWaitForAgentReady(t *testing.T) { t.Parallel() diff --git a/coderd/x/chatd/instruction_internal_test.go b/coderd/x/chatd/instruction_internal_test.go index b897d711c0..794efe0c40 100644 --- a/coderd/x/chatd/instruction_internal_test.go +++ b/coderd/x/chatd/instruction_internal_test.go @@ -135,6 +135,42 @@ func TestDefaultSystemPromptContainsVersionControlSafety(t *testing.T) { require.Contains(t, DefaultSystemPrompt, "Never treat the original request as confirmation") } +func TestWorkspaceAwarenessDelaysWorkspaceCreation(t *testing.T) { + t.Parallel() + + detached := workspaceDetachedAwareness + require.Contains(t, detached, "No workspace is attached to this chat yet") + require.Contains(t, detached, "Do not create or start a workspace by default") + require.Contains(t, detached, "Only call create_workspace or start_workspace") + require.NotContains(t, detached, "Create one using the create_workspace tool before using workspace tools") + + delegated := workspaceDetachedNoCreateAwareness + require.Contains(t, delegated, "This delegated chat cannot create or start a workspace") + require.Contains(t, delegated, "report that need to the parent agent") + require.NotContains(t, delegated, "Only call create_workspace or start_workspace") + + attached := workspaceAttachedAwareness + require.Contains(t, attached, "This chat is attached to a workspace") +} + +func TestDefaultSystemPromptDelaysWorkspaceCreation(t *testing.T) { + t.Parallel() + + require.Contains(t, DefaultSystemPrompt, "Do not create a workspace by default") + require.Contains(t, DefaultSystemPrompt, "Do not clone repositories already present") + require.Contains(t, DefaultSystemPrompt, "including AGENTS.md") + require.NotContains(t, DefaultSystemPrompt, "create and start one first using create_workspace and start_workspace") +} + +func TestPlanningOverlayPromptDelaysWorkspaceCreation(t *testing.T) { + t.Parallel() + + prompt := PlanningOverlayPrompt() + require.Contains(t, prompt, "do not create one as the first action merely because you are planning") + require.Contains(t, prompt, "Before cloning, inspect the current workspace and reuse existing repositories") + require.NotContains(t, prompt, "create and start one with create_workspace and start_workspace before investigating") +} + func TestInsertSystemInstructionAfterSystemMessages(t *testing.T) { t.Parallel() diff --git a/coderd/x/chatd/prompt.go b/coderd/x/chatd/prompt.go index 978a582856..178db12919 100644 --- a/coderd/x/chatd/prompt.go +++ b/coderd/x/chatd/prompt.go @@ -2,6 +2,17 @@ package chatd const defaultSystemPromptPlanPathBlockPlaceholder = "{{CODER_CHAT_PLAN_FILE_PATH_BLOCK}}" +const workspaceAttachedAwareness = "This chat is attached to a workspace. You can use workspace tools like execute, read_file, write_file, etc." + +const workspaceDetachedAwarenessBase = `No workspace is attached to this chat yet. +Do not create or start a workspace by default. Many requests can be completed using the conversation, provider tools such as web_search when available, or configured external MCP tools. +Workspace tools such as execute, read_file, write_file, and edit_files require an attached workspace.` + +const workspaceDetachedAwareness = workspaceDetachedAwarenessBase + ` Only call create_workspace or start_workspace when the user explicitly asks for a workspace-backed task, or when the task cannot be completed without inspecting, editing, or running files in a workspace. +If a workspace is needed, use list_templates and read_template as needed before create_workspace.` + +const workspaceDetachedNoCreateAwareness = workspaceDetachedAwarenessBase + ` This delegated chat cannot create or start a workspace. If workspace-backed work is required, report that need to the parent agent instead of trying workspace tools.` + // DefaultSystemPrompt is used for new chats when no deployment override is // configured. const DefaultSystemPrompt = `You are the Coder agent — an interactive chat tool that helps users with software-engineering tasks inside of the Coder product. @@ -15,6 +26,8 @@ You MUST execute AS MANY TOOLS to help the user accomplish their task. You are COMFORTABLE with vague tasks - using your tools to collect the most relevant answer possible. If a user asks how something works, no matter how vague, you MUST use your tools to collect the most relevant answer possible. Use tools first to gather context and make progress. +When no workspace is attached, use available non-workspace tools first. Do not create a workspace by default. +Reuse existing chat and workspace context. Do not clone repositories already present in the workspace. Treat injected files, including AGENTS.md, as read; re-read only for exact current contents or suspected changes. Do not ask clarifying questions if the answer can be obtained from the codebase, workspace, or existing project conventions. Ask concise clarifying questions only when: - the user's intent is materially ambiguous; @@ -96,7 +109,9 @@ Propose a plan when: - The task is too ambiguous to implement with confidence. - The user asks for a plan. -If no workspace is attached to this chat yet, create and start one first using create_workspace and start_workspace. +If no workspace is attached to this chat yet, do not create one as the first action merely because you are planning. +First use the conversation, provider tools such as web_search when available, configured external MCP tools, and template metadata when they are sufficient. +Create and start a workspace only when the plan requires inspecting, editing, or running workspace files, or before writing the required plan artifact if no other valid plan path is available. Once a workspace is available: ` + defaultSystemPromptPlanningGuidance + ` 2. Use write_file to create a Markdown plan file at the absolute @@ -114,8 +129,11 @@ var planningOverlayPrompt = `You are in Plan Mode. Every response must work toward producing a plan. The only intentional authored workspace artifact is the plan file at the path specified in the block below. You may use execute and process_output for exploration, including cloning repositories, searching code, and running inspection commands needed to build the plan. +Before cloning, inspect the current workspace and reuse existing repositories when they are already available. Do not use Plan Mode to implement the requested changes or intentionally modify project files outside the plan file. -If no workspace is attached to this chat yet, create and start one with create_workspace and start_workspace before investigating. +If no workspace is attached to this chat yet, do not create one as the first action merely because you are planning. +First use the conversation, provider tools such as web_search when available, configured external MCP tools, and template metadata when they are sufficient. +Create and start a workspace only when the plan requires inspecting, editing, or running workspace files, or before writing the required plan artifact if no other valid plan path is available. If the plan file already exists, read it first with read_file before replacing or refining it. ` + planningOverlaySubagentGuidance() + ` Use write_file to create the plan file and edit_files to refine it. diff --git a/coderd/x/chatd/subagent.go b/coderd/x/chatd/subagent.go index 29d6fef9d2..cc3e35f78c 100644 --- a/coderd/x/chatd/subagent.go +++ b/coderd/x/chatd/subagent.go @@ -1005,9 +1005,9 @@ func (p *Server) createChildSubagentChatWithOptions( return xerrors.Errorf("insert child chat: %w", err) } - workspaceAwareness := "There is no workspace associated with this chat yet. Create one using the create_workspace tool before using workspace tools like execute, read_file, write_file, etc." + workspaceAwareness := workspaceDetachedNoCreateAwareness if insertedChat.WorkspaceID.Valid { - workspaceAwareness = "This chat is attached to a workspace. You can use workspace tools like execute, read_file, write_file, etc." + workspaceAwareness = workspaceAttachedAwareness } workspaceAwarenessContent, err := chatprompt.MarshalParts([]codersdk.ChatMessagePart{ codersdk.ChatMessageText(workspaceAwareness),