Skip to content

Commit 363d879

Browse files
committed
Clarify AGENTS.md layering: workspace replaces project, not layers
Make it clear that workspace and project instructions are mutually exclusive: - Global instructions are always layered - Workspace instructions REPLACE project instructions (not layered) - Project instructions are only used as fallback when workspace doesn't exist Changes: - Simplified logic to only read project if workspace doesn't exist - Updated comments to emphasize replacement vs layering - Updated documentation to make behavior crystal clear - Mode extraction only checks workspace OR project, not both
1 parent e8b0b9c commit 363d879

File tree

2 files changed

+35
-43
lines changed

2 files changed

+35
-43
lines changed

docs/instruction-files.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
## Overview
44

5-
cmux layers instructions from multiple locations:
5+
cmux layers instructions from two sources:
66

7-
1. `~/.cmux/AGENTS.md` (+ optional `AGENTS.local.md`) — global defaults
8-
2. `<workspace>/AGENTS.md` (+ optional `AGENTS.local.md`) — workspace-specific context (if exists)
9-
3. `<project>/AGENTS.md` (+ optional `AGENTS.local.md`) — project fallback
7+
1. **Global**: `~/.cmux/AGENTS.md` (+ optional `AGENTS.local.md`) — always included
8+
2. **Context**: Either workspace OR project AGENTS.md (not both):
9+
- **Workspace**: `<workspace>/AGENTS.md` (+ optional `AGENTS.local.md`) — if exists
10+
- **Project**: `<project>/AGENTS.md` (+ optional `AGENTS.local.md`) — fallback if workspace doesn't exist
1011

1112
Priority within each location: `AGENTS.md``AGENT.md``CLAUDE.md` (first match wins). If the base file is found, cmux also appends `AGENTS.local.md` from the same directory when present.
1213

13-
**Fallback behavior**: If a workspace doesn't have its own AGENTS.md, the project root's AGENTS.md is used as a fallback. This is particularly useful for SSH workspaces where files may not be fully cloned yet.
14+
**Fallback behavior**: Workspace instructions **replace** project instructions (not layered). If a workspace doesn't have AGENTS.md, the project root's AGENTS.md is used. This is particularly useful for SSH workspaces where files may not be fully cloned yet.
1415

1516
## Mode Prompts
1617

@@ -22,7 +23,7 @@ cmux reads mode context from sections inside your instruction files. Add a headi
2223

2324
Rules:
2425

25-
- Workspace instructions are checked first, then project, then global instructions
26+
- Context instructions (workspace or project fallback) are checked first, then global instructions
2627
- The first matching section wins (at most one section is used)
2728
- The section's content is everything until the next heading of the same or higher level
2829
- Missing sections are ignored (no error)

src/services/systemMessage.ts

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as os from "os";
22
import * as path from "path";
33
import type { WorkspaceMetadata } from "@/types/workspace";
4-
import { gatherInstructionSets, readInstructionSet, INSTRUCTION_FILE_NAMES } from "@/utils/main/instructionFiles";
4+
import { readInstructionSet, INSTRUCTION_FILE_NAMES } from "@/utils/main/instructionFiles";
55
import { extractModeSection } from "@/utils/main/markdown";
66
import type { Runtime } from "@/runtime/Runtime";
77
import { readFileString } from "@/utils/runtime/helpers";
@@ -96,13 +96,13 @@ async function readInstructionSetFromRuntime(
9696
/**
9797
* Builds a system message for the AI model by combining multiple instruction sources.
9898
*
99-
* Instruction sources are layered in this order:
100-
* 1. Global instructions: ~/.cmux/AGENTS.md (+ AGENTS.local.md)
101-
* 2. Workspace instructions: <workspacePath>/AGENTS.md (+ AGENTS.local.md) - if exists
102-
* 3. Project instructions: <projectPath>/AGENTS.md (+ AGENTS.local.md) - fallback if workspace doesn't have one
103-
* 4. Mode-specific context (if mode provided): Extract a section titled "Mode: <mode>"
104-
* (case-insensitive) from the instruction file. We search at most one section in
105-
* precedence order: workspace instructions first, then project, then global instructions.
99+
* Instruction sources are layered as follows:
100+
* 1. Global instructions: ~/.cmux/AGENTS.md (+ AGENTS.local.md) - always included
101+
* 2. Context instructions: EITHER workspace OR project AGENTS.md (not both)
102+
* - Workspace: <workspacePath>/AGENTS.md (+ AGENTS.local.md) - if exists (read via runtime)
103+
* - Project: <projectPath>/AGENTS.md (+ AGENTS.local.md) - fallback if workspace doesn't exist
104+
* 3. Mode-specific context (if mode provided): Extract a section titled "Mode: <mode>"
105+
* (case-insensitive) from the instruction file. Priority: context instructions, then global.
106106
*
107107
* Each instruction file location is searched for in priority order:
108108
* - AGENTS.md
@@ -138,43 +138,34 @@ export async function buildSystemMessage(
138138
const systemDir = getSystemDirectory();
139139
const projectDir = metadata.projectPath;
140140

141-
// Read workspace instructions using runtime (may be remote for SSH)
142-
// Try to read AGENTS.md from workspace directory first
141+
// Layer 1: Global instructions (always included)
142+
const globalInstructions = await readInstructionSet(systemDir);
143+
144+
// Layer 2: Workspace OR Project instructions (not both)
145+
// Try workspace first (via runtime, may be remote for SSH)
146+
// Fall back to project if workspace doesn't have AGENTS.md
143147
const workspaceInstructions = await readInstructionSetFromRuntime(runtime, workspacePath);
148+
const projectInstructions = workspaceInstructions ? null : await readInstructionSet(projectDir);
144149

145-
// Gather instruction sets from global and project directories (always local)
146-
// Note: We gather from both systemDir and projectDir, but workspace is handled separately
147-
const localInstructionDirs = [systemDir, projectDir];
148-
const localInstructionSegments = await gatherInstructionSets(localInstructionDirs);
149-
150-
// Combine all instruction sources
151-
// Priority: global, workspace (if found), project (as fallback)
152-
const allSegments = [...localInstructionSegments];
153-
if (workspaceInstructions) {
154-
// Insert workspace instructions after global (index 0) but before project
155-
allSegments.splice(1, 0, workspaceInstructions);
156-
}
157-
const customInstructions = allSegments.join("\n\n");
150+
// Combine instruction sources
151+
// Result: global + (workspace OR project)
152+
const instructionSegments = [globalInstructions, workspaceInstructions ?? projectInstructions].filter(
153+
Boolean
154+
);
155+
const customInstructions = instructionSegments.join("\n\n");
158156

159157
// Look for a "Mode: <mode>" section inside instruction sets
160-
// Priority: workspace instructions, then project, then global
158+
// Priority: workspace (or project fallback), then global
159+
// We only check the workspace OR project instructions, not both
161160
// This behavior is documented in docs/instruction-files.md - keep both in sync when changing.
162161
let modeContent: string | null = null;
163162
if (mode) {
164-
if (workspaceInstructions) {
165-
modeContent = extractModeSection(workspaceInstructions, mode);
166-
}
167-
if (!modeContent) {
168-
const projectInstructions = await readInstructionSet(projectDir);
169-
if (projectInstructions) {
170-
modeContent = extractModeSection(projectInstructions, mode);
171-
}
163+
const contextInstructions = workspaceInstructions ?? projectInstructions;
164+
if (contextInstructions) {
165+
modeContent = extractModeSection(contextInstructions, mode);
172166
}
173-
if (!modeContent) {
174-
const globalInstructions = await readInstructionSet(systemDir);
175-
if (globalInstructions) {
176-
modeContent = extractModeSection(globalInstructions, mode);
177-
}
167+
if (!modeContent && globalInstructions) {
168+
modeContent = extractModeSection(globalInstructions, mode);
178169
}
179170
}
180171

0 commit comments

Comments
 (0)