Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
83edf72
feat(agent-mode): make AGENTS.md canonical instructions
logancyang Jul 28, 2026
6859e01
fix(agent-mode): close AGENTS.md migration gaps and restore chat prom…
logancyang Jul 28, 2026
4bb0001
Merge origin/v4-preview into codex/issue-238-agents-md
logancyang Jul 31, 2026
308267a
refactor(agent-mode): inline the landing-signature app binder
logancyang Jul 31, 2026
fb60318
merge: resolve v4-preview conflicts on the AGENTS.md branch
logancyang Aug 5, 2026
649ef19
feat(agent-mode): declare AGENTS.md precedence in the stable product …
logancyang Aug 5, 2026
b2cf0ef
feat(agent-mode): move project instructions out of project.md into AG…
logancyang Aug 5, 2026
a991632
feat(settings): move Agent instructions to Basic, retire the Chat pro…
logancyang Aug 5, 2026
1faaa9d
fix(settings): type the BasicSettings agentsFile mock
logancyang Aug 5, 2026
f6a52c9
perf(agent-mode): keep Claude's system prefix static across sessions
logancyang Aug 5, 2026
8fc020d
test(agent-mode): assert exact product-prompt bytes at the opencode a…
logancyang Aug 5, 2026
41669a8
docs: add the Agent Mode instructions and prompt-caching design doc
logancyang Aug 5, 2026
a679e1e
fix(agent-mode): stop the migration from blanking a generated AGENTS.…
logancyang Aug 5, 2026
a2ece8f
fix(search): exclude instruction files on the pattern filter's fast path
logancyang Aug 5, 2026
bc503c2
fix(agent-mode): arm the workspace policy for source-less projects
logancyang Aug 5, 2026
5208f61
fix(agent-mode): keep a user-authored CLAUDE.md visible and correct t…
logancyang Aug 5, 2026
870d59e
fix(search,docs): case-fold instruction-file exclusions; document the…
logancyang Aug 5, 2026
60a9ed6
fix(projects): remove Copilot's own instruction wiring when deleting …
logancyang Aug 5, 2026
90a628b
feat(settings): add a gallery story for the legacy-prompts notice
logancyang Aug 5, 2026
c795232
fix(agent-mode): run the vault-root instruction ensure for project se…
logancyang Aug 5, 2026
ff65553
feat(instructions): edit AGENTS.md inline, in settings and the projec…
logancyang Aug 5, 2026
f341392
fix(settings): top-align the vault instructions label and widen Edit …
logancyang Aug 5, 2026
7b108ed
feat(instructions): cycle example placeholders in the instruction edi…
logancyang Aug 5, 2026
bce50fa
merge: resolve v4-preview conflicts after managed-MCP removal
logancyang Aug 7, 2026
a1035de
fix(instructions): close the review gaps on the AGENTS.md editors
logancyang Aug 7, 2026
e7576e5
merge: take v4-preview's Claude Auto-mode work
logancyang Aug 7, 2026
d6dbc94
fix: gate the instruction case-fold, and keep two failure paths honest
logancyang Aug 7, 2026
2c6cc5f
perf,fix(instructions): stop scanning the vault, stop writing on dial…
logancyang Aug 7, 2026
2863ffa
fix(instructions): decide the legacy seed by ownership, not emptiness
logancyang Aug 7, 2026
d795630
merge: take v4-preview's Basic-tab and Property-source work
logancyang Aug 8, 2026
0b0d425
fix(instructions): model instruction-file state instead of inferring it
logancyang Aug 8, 2026
78f4d88
fix(settings): queue the vault-instruction writes
logancyang Aug 8, 2026
33144a7
docs: describe the instruction editors this PR actually ships
logancyang Aug 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions designdocs/AGENT_HOME_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,16 +349,23 @@ and are left untouched. Source kinds are a single source of truth,
`MATERIALIZED_SOURCE_TYPES` (`web` / `youtube` / `file`), so adding a kind updates
the filename patterns and marker-pruning regex together.

## Project instructions (`project.md` + `AGENTS.md` mirror)

`project.md` is the single source of truth for a project's config and
instructions. A **marker-gated `AGENTS.md` mirror** is generated alongside it by
`ensureAgentsMirror`: codex and opencode auto-discover `AGENTS.md` from the
session cwd, and claude receives the same composed instructions via
`getProjectProfile`. A built-in project policy is layered into each project's
instructions — for claude always; for codex/opencode through the generated
mirror, which **yields to a user-authored, unmarked `AGENTS.md`** (the mirror only
manages the file it owns, so it never clobbers a hand-written one).
## Project instructions (`project.md` + `AGENTS.md`)

`project.md` remains the project metadata/config record. `AGENTS.md` is the
canonical user-and-agent-editable instruction file and is never regenerated by
Copilot. The project info popover opens that file directly.

For an older project with no `AGENTS.md`, the file is initialized once from the
legacy `project.md` body — by the open action, or by
`ensureAgentsFileForDiscovery` at session start, so a project that predates the
file layout still delivers its instructions without a manual click. A
marker-owned generated mirror is converted to that raw body; unmarked user files
are preserved. Nothing is created for a scope with no instructions to carry
over, so a fresh project folder stays empty. No bulk migration rewrites existing
projects.
Codex and OpenCode discover `AGENTS.md` from the session cwd; a sibling
`CLAUDE.md` imports `@AGENTS.md` so Claude reads the same file. Vault-root files
provide the broader layer and project files provide the more-specific layer.

## History scope

Expand Down
312 changes: 312 additions & 0 deletions designdocs/AGENT_INSTRUCTIONS_AND_PROMPT_CACHING.md

Large diffs are not rendered by default.

23 changes: 18 additions & 5 deletions docs/projects.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Projects

Projects are focused AI workspaces. Each project has its own model, system prompt, context sources, and completely isolated chat history. Use projects to keep separate AI conversations per client, topic, or area of work.
Projects are focused AI workspaces. Each project has its own instructions, context sources, and
isolated chat history. Use projects to keep separate AI conversations per client, topic, or area
of work.

Projects support **50+ file types** beyond markdown, including PDFs, Word documents, PowerPoint, Excel, images, and more — making them ideal for analyzing large or diverse document collections.

Expand All @@ -14,7 +16,7 @@ In regular chat, all conversations share the same settings and model. Projects l

- **A specific context** — Specific notes, folders, URLs, or YouTube videos the AI always has access to
- **A dedicated model** — Different projects can use different AI models
- **A custom system prompt** — Each project can have its own instructions for the AI
- **Project instructions** — Each Agent Mode project can have its own `AGENTS.md`
- **Isolated chat history** — Conversations in one project don't mix with conversations in another

**Example use cases:**
Expand Down Expand Up @@ -56,9 +58,20 @@ Choose which AI model to use for this project. The available options are the mod

Override the default temperature and max tokens specifically for this project.

### System Prompt
### Agent Mode Instructions

Set a custom system prompt for this project. This replaces (or supplements) the global default. See [System Prompts](system-prompts.md) for details.
Open the project info popover and select **AGENTS.md**. This opens the real file in Obsidian; there
is no separate prompt editor in project settings.

Vault instructions apply first, followed by the project's `AGENTS.md`, so project rules take
precedence. For an older project without `AGENTS.md`, the file is initialized from the Project
System Prompt already stored in `project.md` — the first time you open it, or automatically when
you next start a chat in that project, so existing projects keep working without any migration
step. A legacy Copilot-generated mirror is converted to that same text; user-authored files are
left alone, and a project with no instructions gets no file at all.

`project.md` remains the project's metadata and context configuration record. It is not the agent
instruction file and is not renamed or migrated.

---

Expand Down Expand Up @@ -134,6 +147,6 @@ As an alpha feature, projects have some known limitations:
## Related

- [Chat Interface](chat-interface.md) — Chat modes overview, new chat behavior, history
- [System Prompts](system-prompts.md) — Custom system prompts for projects
- [Instructions and System Prompts](system-prompts.md) — Vault and project instructions
- [Context and Mentions](context-and-mentions.md) — How context works
- [Copilot Plus and Self-Host](copilot-plus-and-self-host.md) — Plus features
98 changes: 62 additions & 36 deletions docs/system-prompts.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# System Prompts
# Instructions and System Prompts

A system prompt is a set of instructions you give the AI that shapes how it behaves in all conversations. Think of it as a persistent briefing: "You are an assistant that helps me with academic writing. Always cite sources. Respond in formal English."

---

## Overview

Copilot has two layers of system prompts:
Copilot has two instruction surfaces:

1. **Built-in system prompt** — Always active. Defines core behaviors specific to Obsidian (how to format Obsidian links, how to handle note references, etc.)
2. **Custom system prompt** — Optional. You can write your own instructions that are appended to the built-in prompt.
1. **Built-in system prompt** — Internal Agent Mode behavior maintained by Copilot.
2. **User instructions** — `AGENTS.md` for Agent Mode; selectable custom prompt files for Chat mode.

---

Expand All @@ -32,13 +32,52 @@ This prompt ensures Copilot's output is correctly formatted for Obsidian and awa

---

## Custom System Prompts
## Agent Mode Instructions

Custom system prompts let you add your own instructions on top of the built-in prompt.
Agent Mode uses the standard `AGENTS.md` file:

- Vault-wide instructions live at `<vault>/AGENTS.md`.
- Project instructions live at `<project>/AGENTS.md`.
- Project instructions are more specific and take precedence over vault instructions.

Each file has two edit surfaces, and both write the same file:

- **Vault instructions** — **Settings → Copilot → Basic → Custom instructions** has a
**Custom vault instructions** box that saves as you type. **Open AGENTS.md** beside it opens
the file as an ordinary note instead.
- **Project instructions** — the Edit Project dialog has a **Project instructions** box that
saves when you accept the dialog. The project info popover's **AGENTS.md** row opens the file
as a note.

Because these are ordinary notes, the box and the editor always agree; use whichever suits the
edit. If a file is missing, opening or saving creates it. Copilot also adds an `@AGENTS.md`
reference to the sibling `CLAUDE.md` without replacing other content, so Claude reads the same
instructions as Codex and OpenCode.

Changes apply to new agent sessions.

### Upgrading from an earlier version

- A project that had a **Project System Prompt** is migrated automatically: the first time a
session starts in it, that text moves into the project's `AGENTS.md` and is removed from the
project record, so it lives in exactly one place.
- The vault-level `AGENTS.md` starts **blank**. Your Chat system prompt files are not copied
into it — they remain in the `system-prompts/` sub-folder of your Copilot folder, and
Settings shows a notice pointing there. Open one and paste across anything you want Agent
Mode to keep following.

Your own hand-written `AGENTS.md` and `CLAUDE.md` files are never replaced.

---

## Chat Mode Custom System Prompts

Chat mode custom system prompts let you add instructions on top of its built-in prompt.

### Where They're Stored

Custom system prompts are stored as markdown files in your vault, in the `system-prompts/` sub-folder of your Copilot folder:

```
copilot/system-prompts/
```
Expand All @@ -47,17 +86,9 @@ This location is derived from your Copilot folder. To move it, change the root i

### Creating a System Prompt

#### From Settings

1. Go to **Settings → Copilot → Advanced**
2. Under **User System Prompt**, click the `+` button
3. Enter a title for the prompt (e.g., "Academic Writing")
4. A new markdown file is created in your system prompts folder
5. Open the file and write your instructions

#### From the System Prompts Folder

Create any `.md` file in the `copilot/system-prompts/` folder. Its filename (without `.md`) becomes the prompt's title.
Create any `.md` file in the `copilot/system-prompts/` folder, like any other note. Its
filename (without `.md`) becomes the prompt's title, and the file body is the prompt. New
files appear in the chat prompt picker automatically.

### Writing Good System Prompts

Expand All @@ -70,8 +101,10 @@ Tips for effective system prompts:
- **Limit scope**: "Only answer questions related to my research notes on climate science"

**Example system prompt:**

```markdown
You are a Zettelkasten assistant helping me build a knowledge base.

- Always connect new ideas to existing notes when possible
- Suggest up to 3 related concepts per response
- Format all note suggestions as [[Note Title]]
Expand All @@ -80,25 +113,17 @@ You are a Zettelkasten assistant helping me build a knowledge base.

---

## Setting a Global Default

You can set one of your custom prompts as the global default — it will be used for all new chat sessions:

1. Go to **Settings → Copilot → Advanced**
2. Under **Default System Prompt**, select your prompt from the dropdown
3. Any new conversation will start with this prompt active

To stop using a custom default, select **None (use built-in prompt)** from the dropdown.

---

## Per-Session Override (Gear Icon)
## Choosing a Prompt (Gear Icon)

You can override the system prompt for just the current conversation:
Prompts are selected per conversation:

1. Click the **gear icon** in the chat panel toolbar
2. Select a different system prompt (or type a one-off prompt directly)
3. This applies to the current session only and resets when you start a new chat
2. Select a system prompt from the list, or **None (use built-in prompt)**
3. The selection applies to the current session and resets when you start a new chat

The Settings section that set a global chat default was removed. A default chosen in an
earlier version keeps applying to new chats until you pick a different prompt (or **None**)
in a conversation. This applies to Chat mode only — Agent Mode reads `AGENTS.md` instead.

---

Expand All @@ -113,14 +138,15 @@ Both sets of instructions are active simultaneously. Your custom instructions ca

---

## Per-Project System Prompts
## Per-Project Instructions

Each [Project](projects.md) can have its own system prompt, independent of the global default. Configure this in the project settings under **System Prompt**.
Agent Mode projects use their own `AGENTS.md`. Legacy Chat mode projects retain their existing
Project System Prompt field.

---

## Related

- [Chat Interface](chat-interface.md) — Per-session gear settings
- [Projects](projects.md) — Per-project system prompts
- [Projects](projects.md) — Per-project instructions
- [Getting Started](getting-started.md) — Initial setup
5 changes: 4 additions & 1 deletion docs/troubleshooting-and-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,10 @@ Yes — use `[[Note Title]]` syntax directly in your message. Copilot adds that

### How do I make Copilot always reply in English?

Go to **Settings → Copilot → Advanced → Default System Prompt**, create a custom prompt, and add "Always respond in English." as an instruction. See [System Prompts](system-prompts.md).
For Agent Mode, go to **Settings → Copilot → Basic → Custom instructions** and add "Always
respond in English." to the **Custom vault instructions** box. For Chat mode, create a prompt file in the
`system-prompts/` sub-folder of your Copilot folder and pick it per conversation from the chat
settings gear. See [Instructions and System Prompts](system-prompts.md).

### Can Copilot understand images in my notes?

Expand Down
2 changes: 1 addition & 1 deletion src/agentMode/backends/claude/descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ export const ClaudeBackendDescriptor: ClaudeDescriptor = {
// symlink fanout already enforces visibility (no link = not seen). If the
// Claude Agent SDK ever grows a per-skill deny hook, wire
// `composeDenyList(getManagedSkills(), "claude")` in here.
getSystemPromptAppend: (opts) => buildAgentSystemPrompt(opts),
getSystemPromptAppend: () => buildAgentSystemPrompt(),
});
},

Expand Down
40 changes: 30 additions & 10 deletions src/agentMode/backends/codex/CodexBackend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "@/system-prompts/state";
import type { UserSystemPrompt } from "@/system-prompts/type";
import { SYMPOSIUM_WORKSPACE_ROOT_ENV } from "@/symposium/constants";
import { buildAgentSystemPrompt } from "@/agentMode/backends/shared/agentSystemPrompt";
import { CodexBackend, toTomlBasicString } from "./CodexBackend";

jest.mock("@/logger", () => ({
Expand Down Expand Up @@ -94,28 +95,48 @@ describe("CodexBackend.buildSpawnDescriptor", () => {
expect(value).not.toContain("copilot/skills/<name>/SKILL.md");
});

it("appends the user's selected custom prompt to developer_instructions", async () => {
it("encodes the shared product prompt into both paths, byte for byte", async () => {
const desc = await new CodexBackend().buildSpawnDescriptor({ vaultBasePath: "/vault" });
const shared = buildAgentSystemPrompt();

// `toBe`, not `toContain`: this string is the provider cache prefix, and a containment
// check passes while stray bytes push everything after it out of the cache.
expect(JSON.parse(desc.env.CODEX_CONFIG as string).developer_instructions).toBe(shared);
const cIdx = desc.args.indexOf("-c");
expect(desc.args[cIdx + 1]).toBe(`developer_instructions=${toTomlBasicString(shared)}`);
});

it("keeps those bytes identical when the vault path changes", async () => {
const backend = new CodexBackend();
const a = await backend.buildSpawnDescriptor({ vaultBasePath: "/vault" });
const b = await backend.buildSpawnDescriptor({ vaultBasePath: "/somewhere/else/vault" });

expect(JSON.parse(b.env.CODEX_CONFIG as string).developer_instructions).toBe(
JSON.parse(a.env.CODEX_CONFIG as string).developer_instructions
);
expect(b.args[b.args.indexOf("-c") + 1]).toBe(a.args[a.args.indexOf("-c") + 1]);
});

it("does not copy Chat mode custom prompts into developer_instructions", async () => {
updateCachedSystemPrompts([makeSystemPrompt("Haiku", "respond in haiku")]);
setSelectedPromptTitle("Haiku");
const backend = new CodexBackend();
const desc = await backend.buildSpawnDescriptor({ vaultBasePath: "/vault" });
const value = desc.args[desc.args.indexOf("-c") + 1];
expect(value).toContain("Obsidian Copilot");
// The TOML basic string escapes newlines as \n, so match the wrapper +
// content rather than the literal multi-line block.
expect(value).toContain("<user_custom_instructions>");
expect(value).toContain("respond in haiku");
expect(value).not.toContain("<user_custom_instructions>");
expect(value).not.toContain("respond in haiku");
});

it("suppresses the base prompt when 'disable builtin' is on, keeping the user prompt + pill directive", async () => {
it("suppresses the base prompt when 'disable builtin' is on, keeping the pill directive", async () => {
updateCachedSystemPrompts([makeSystemPrompt("Haiku", "respond in haiku")]);
setSelectedPromptTitle("Haiku");
setDisableBuiltinSystemPrompt(true);
const backend = new CodexBackend();
const desc = await backend.buildSpawnDescriptor({ vaultBasePath: "/vault" });
const value = desc.args[desc.args.indexOf("-c") + 1];
expect(value).not.toContain("Obsidian Copilot");
expect(value).toContain("respond in haiku");
expect(value).not.toContain("respond in haiku");
// Pill directive is functional wiring, not builtin framing — always sent.
expect(value).toContain("{folder_name}");
});
Expand Down Expand Up @@ -291,9 +312,8 @@ describe("CodexBackend.buildSpawnDescriptor", () => {
});

it("does not add a project.md fallback to the codex spawn args", async () => {
// Session-start ensureAgentsMirror supersedes the spawn-level fallback for project scopes;
// omitting it also prevents a GLOBAL session from treating a vault-root project.md note as
// codex instructions (the spawn descriptor has no scope to gate on).
// Omitting the fallback prevents a GLOBAL session from treating a vault-root project.md note
// as codex instructions (the spawn descriptor has no scope to gate on).
const backend = new CodexBackend();
const desc = await backend.buildSpawnDescriptor({ vaultBasePath: "/vault" });
expect(desc.args).not.toContainEqual(expect.stringContaining("project_doc_fallback_filenames"));
Expand Down
15 changes: 4 additions & 11 deletions src/agentMode/backends/codex/CodexBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ export class CodexBackend implements AcpBackend {
INITIAL_AGENT_MODE: "agent",
}
);
// Forward the shared composed system prompt — the Copilot base framing
// (unless the user disabled it), the pill-syntax directive, and the user's
// custom prompt — via codex's `developer_instructions` config field as a
// Forward the shared built-in prompt — the Copilot base framing, tool
// guidance, and pill-syntax directive — via codex's `developer_instructions` as a
// TOML 1.0 basic string. codex appends `developer_instructions` to its own
// base prompt, so this adds the Obsidian-vault framing on top. Read at
// spawn time; the host restarts codex on prompt changes via
Expand Down Expand Up @@ -60,14 +59,8 @@ export class CodexBackend implements AcpBackend {
"-c",
'sandbox_mode="workspace-write"',
];
// DESIGN NOTE: deliberately no `project_doc_fallback_filenames=["project.md"]`.
// Post-Phase-2 the session-start `ensureAgentsMirror` (AgentSessionManager, run before
// `resolveSessionCwd` for codex/opencode project sessions) guarantees the marker'd
// `AGENTS.md` mirror exists in the project cwd, so a `project.md` fallback is redundant.
// This descriptor only knows `vaultBasePath`, not the session scope: a spawn-level fallback
// would also apply to GLOBAL sessions and let codex read a user's vault-root `project.md`
// note as instructions. On the rare ensure failure a project session gets no instructions
// (ensure never throws and re-runs next session) rather than the frontmatter-laden source.
// Deliberately no `project_doc_fallback_filenames=["project.md"]`: project.md is metadata,
// while Codex discovers the canonical AGENTS.md instructions from the session cwd.
return descriptor;
}
}
Expand Down
Loading
Loading