From 2475b9a2ffd43ad80e67c0a941e4a043ac6d7c31 Mon Sep 17 00:00:00 2001 From: "@mrubens" <2600+mrubens@users.noreply.github.com> Date: Wed, 9 Sep 2026 22:27:19 +0000 Subject: [PATCH] fix: preserve environment config during agent updates --- .changeset/preserve-environment-config-updates.md | 6 ++++++ .../__tests__/tool-descriptions.test.ts | 6 ++++++ apps/worker/src/mcp/roomote-mcp-server/index.ts | 2 +- .../__tests__/environmentSetupSkill.test.ts | 12 ++++++++++++ .../skills/standard/environment-setup/SKILL.md | 4 +++- 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 .changeset/preserve-environment-config-updates.md diff --git a/.changeset/preserve-environment-config-updates.md b/.changeset/preserve-environment-config-updates.md new file mode 100644 index 0000000000..a3e8c96fa3 --- /dev/null +++ b/.changeset/preserve-environment-config-updates.md @@ -0,0 +1,6 @@ +--- +"@roomote/cloud-agents": patch +"@roomote/worker": patch +--- + +Preserve unrelated environment settings when an agent revises an existing environment definition, and clarify that update calls replace the full definition. diff --git a/apps/worker/src/mcp/roomote-mcp-server/__tests__/tool-descriptions.test.ts b/apps/worker/src/mcp/roomote-mcp-server/__tests__/tool-descriptions.test.ts index c135cfb848..b79fef2d53 100644 --- a/apps/worker/src/mcp/roomote-mcp-server/__tests__/tool-descriptions.test.ts +++ b/apps/worker/src/mcp/roomote-mcp-server/__tests__/tool-descriptions.test.ts @@ -1191,6 +1191,12 @@ describe('roomote MCP tool descriptions', () => { expect(definitionSchema).toBeInstanceOf(z.ZodString); expect(definitionSchema).not.toBeInstanceOf(z.ZodUnion); expect(definitionField.description).toContain('YAML or JSON string'); + expect(definitionField.description).toContain( + 'An update replaces the full definition', + ); + expect(definitionField.description).toContain( + 'preserve every field that is not intentionally changing', + ); }); it('forwards issueNumber from manage_source_control tool params', async () => { diff --git a/apps/worker/src/mcp/roomote-mcp-server/index.ts b/apps/worker/src/mcp/roomote-mcp-server/index.ts index a7e52beed1..756ce23a0e 100644 --- a/apps/worker/src/mcp/roomote-mcp-server/index.ts +++ b/apps/worker/src/mcp/roomote-mcp-server/index.ts @@ -1127,7 +1127,7 @@ roomoteMcpServer.registerTool( .string() .optional() .describe( - 'Environment definition as a YAML or JSON string. Must satisfy EnvironmentConfig (e.g., include name and repositories). Required for "create" and "update".', + 'Complete environment definition as a YAML or JSON string. Must satisfy EnvironmentConfig (e.g., include name and repositories). Required for "create" and "update". An update replaces the full definition: start from the current definition and preserve every field that is not intentionally changing.', ), environmentId: z .string() diff --git a/packages/cloud-agents/src/server/workflows/__tests__/environmentSetupSkill.test.ts b/packages/cloud-agents/src/server/workflows/__tests__/environmentSetupSkill.test.ts index 88af6329da..98dd19cb86 100644 --- a/packages/cloud-agents/src/server/workflows/__tests__/environmentSetupSkill.test.ts +++ b/packages/cloud-agents/src/server/workflows/__tests__/environmentSetupSkill.test.ts @@ -137,6 +137,18 @@ describe('environment-setup guidance', () => { expect(skillContent).toContain( 'When the task explicitly identifies an existing environment to revise, update that environment instead of creating a duplicate.', ); + expect(skillContent).toContain( + 'treat the complete current environment definition supplied in the trusted task context as the baseline', + ); + expect(skillContent).toContain( + 'Preserve every unrelated field verbatim, especially `agentInstructions`, `skills`, `manualSkills`, environment variables, MCP servers, services, Docker projects, ports, and setup commands.', + ); + expect(skillContent).toContain( + 'The update API replaces the full definition, so an omitted field is a deletion; never regenerate an existing definition from repository evidence alone.', + ); + expect(skillContent).toContain( + 'never submit a newly derived partial replacement', + ); }); it('tells the agent to keep environment names plain instead of decorated variants', () => { diff --git a/packages/cloud-agents/src/server/workflows/skills/standard/environment-setup/SKILL.md b/packages/cloud-agents/src/server/workflows/skills/standard/environment-setup/SKILL.md index 135f001263..fa272417f7 100644 --- a/packages/cloud-agents/src/server/workflows/skills/standard/environment-setup/SKILL.md +++ b/packages/cloud-agents/src/server/workflows/skills/standard/environment-setup/SKILL.md @@ -27,6 +27,7 @@ You are an expert Roomote environment analyst. Analyze the already-checked-out r Treat repository context as: - Repository: `` - Default branch: `` + When revising an existing environment, treat the complete current environment definition supplied in the trusted task context as the baseline. Repository evidence may justify targeted changes, but it does not replace or supersede unrelated persisted configuration. The repository target and branch baseline are explicit before config drafting starts. @@ -88,6 +89,7 @@ You are an expert Roomote environment analyst. Analyze the already-checked-out r Create the smallest valid Roomote environment YAML from static evidence. Produce exactly one initial YAML config. + For an existing environment, start from its complete current definition and apply only the requested or validation-backed changes. Preserve every unrelated field verbatim, especially `agentInstructions`, `skills`, `manualSkills`, environment variables, MCP servers, services, Docker projects, ports, and setup commands. The update API replaces the full definition, so an omitted field is a deletion; never regenerate an existing definition from repository evidence alone. Copy each task-provided repository identifier verbatim into its matching `repositories[].repository` field. In particular, preserve all three `organization/project/repository` segments for Azure DevOps repositories. Use repository default branch unless strong evidence indicates a different branch. Assume the repositories listed in the environment already exist in the workspace; do not add repository clone commands or other duplicate checkout steps. @@ -369,7 +371,7 @@ You are an expert Roomote environment analyst. Analyze the already-checked-out r In Slack-started setup tasks, send a concise `send_chat_reply` message with `purpose` set to `progress` naming the required keys and what they unblock, but do not include the secure `/setup` link yourself because the platform automatically accompanies the request with that secure-entry link after `request_environment_variables` succeeds. In non-web surfaces, ask only for local environment variable additions in the current task, and provide exact variable names and exact actions. For apps with a required human-facing localhost surface, create or update the environment only after startup and loopback reachability are successful enough to proceed, including validation of `initialUrl` through non-browser evidence. For backend services and libraries without such a surface, successful install and canonical tests may be sufficient only when omitted credentials affect optional integrations or external runtime capabilities rather than a required local runtime; do not require unrelated external credentials merely to exercise a broader runtime path. -When the task explicitly identifies an existing environment to revise, update that environment instead of creating a duplicate. +When the task explicitly identifies an existing environment to revise, update that environment instead of creating a duplicate. Use its complete current definition as the update baseline and preserve every field outside the requested or evidence-backed change; never submit a newly derived partial replacement. After successful environment persistence, use the Roomote MCP tool `mcp__roomote__manage_tasks` to launch a lightweight verification task against the created or updated environment and monitor it yourself instead of leaving verification as an implicit manual next step. Before launching that verification task, call the Roomote MCP tool `mcp__roomote__manage_tasks` with `action: "list_environments"` so the environment target is grounded in current Roomote data and you can copy the exact returned `environmentId`. When the verification task launch succeeds, treat the `notifyOnSettle` settle notification as the primary completion signal and monitor the task with the Roomote MCP tool `mcp__roomote__manage_tasks` using `action: "get_summary"` only as a fallback, spacing fallback checks roughly 60-90 seconds apart with one blocking `sleep` per wait. Use that per-task summary surface as the source of truth for task status and surfaced startup failures.