---
title: Auto Context — auto-inject files like Claude.ai Projects (part 2)
---

# Auto Context (`.claude/docs/` auto-injection) (part 2)

## What it is

This is part 2 of the [Auto Context (`.claude/docs/` auto-injection)](project-docs-auto-injection.md)
page. That page covers what Auto Context is, where the sidebar group and the `.claude/docs/`
folder live, and how files are added, ordered and counted on the way into a session. This half
carries the rest: the flag that promotes a doc from background reference into a binding
instruction, the CLI Control surface an outside tool uses to change the folder, and the
machinery that builds the injection in the first place.

## Where to find it

Two surfaces here, both reached outside the Auto Context panel itself. **Inject as instruction**
is a right-click action on a doc row inside the Auto Context group. **CLI Control** is
Omniscio's local HTTP server, which an external tool — another Claude Code window, a script, a
macro — calls to add or remove files, with your approval in the inbox as the gate.

## How it behaves

### Inject a doc as an authoritative instruction

The `always` and `rag` buckets both deliver a doc as **reference material** — context the agent _may_ consult. Sometimes you instead want a doc to be an **instruction the agent must follow** — a house style, an "always do X" rule, a workflow. For that, flag the doc as an instruction.

**How.** Right-click any markdown/text doc in the Auto Context panel → **Inject as instruction** (toggle it back with **Stop injecting as instruction**). Flagged docs show a small **Instruction** badge on their row.

**What changes.** A flagged doc is pulled OUT of the first-message `## Injected Project Docs` block (the background context the agent can skim past) and delivered instead as a `## Project Instructions` section of the session's **system prompt** — the standing instructions the agent carries on every turn, not one-time background context. This is the per-project equivalent of **Settings → Agent Instructions → Custom Instructions** (which is global); flagging a doc scopes that same authority to one project. It reaches every engine, not just Claude.

**Good to know:**

- A flagged doc claims its share of the injection budget **first**, ahead of the background docs — so a large instruction doc **can** be the reason a background doc is skipped. That is deliberate: you elevated it, so it wins the budget. (The two share one budget rather than getting one each, which is what keeps a session's first message inside the context window its engine actually enforces.)
- If a flagged doc is so large it does not fit the budget on its own, its **body is withheld rather than cut in half** — the agent is told the instruction exists, that it is binding, and given the full path to read it. A half-delivered rule set is worse than a missing one, so it is never truncated. The doc's row shows the same **skipped** warning a background doc gets; if you see it, unflag a doc or move the project to a larger-window engine.
- Like all doc injection, it takes effect on the **next** session's first message — not on sessions already running.
- Rename a flagged doc and the flag follows the new name; delete it and the flag cleans itself up.
- Only a **top-level text/markdown** doc is eligible — a `rag/` doc or a binary can't be an instruction. The body is injected **verbatim and unfenced** (so the agent reads it as instruction, not data), so keep instruction docs concise and directive.

Under the hood the flag is a per-project list of filenames (`projects.doc_instruction_json`, toggled via the `files:set-project-doc-instruction` IPC); the content is assembled by [buildProjectInstructionsContent](../../src/main/services/project-docs-context.ts) and delivered as the engine system-prompt bundle's `project-instructions` fragment. Invariants (exclusion from the always-bucket, first claim on the shared budget, authoritative routing): [project-docs-injection-contract.md](../../.claude/memory/contracts/project-docs-injection-contract.md)
(`instruction-doc-excluded-from-always-bucket-but-NOT-from-the-budget`).

### Adding/removing files from an external agent (CLI Control)

If you want an external AI tool — Claude Code in another window, ChatGPT via a script, an AutoHotKey macro — to add or remove files in a project's `.claude/docs/` without you opening Explorer, Omniscio's CLI Control server (see [cli-control.md](cli-control.md)) exposes three endpoints under `/project/:id/docs`:

- `GET /project/:id/docs` — list the current `.claude/docs/` contents (read-only, auth-gated). Response splits files into `files` (the `always` bucket) and `ragFiles` (the `rag` bucket) with separate `totalBytes` / `ragTotalBytes` totals so external agents can tell which bucket each file lives in without re-classifying paths.
- `POST /project/:id/docs` — enqueue an upload that Omniscio copies in after **you approve it in the inbox**. The request body accepts an optional `bucket: 'always' | 'rag'` field (default `'always'` for backward compatibility); `'rag'` routes the file into the `rag/` sub-folder and skips the 100 MB per-project quota check.
- `DELETE /project/:id/docs/:filename` — enqueue a delete that runs after **you approve it in the inbox**. Also accepts the `bucket` query parameter to disambiguate when a filename exists in both buckets (e.g. `?bucket=rag`).

Both mutations land as `pending` rows in the same `cli_pending_actions` queue that gates settings PATCH and session lifecycle — they appear in Omniscio's inbox as approve/reject cards and have no side effect until you click **Approve**. That's the user-in-the-loop guarantee: an external agent cannot silently rewrite your knowledge folder. The pending-action card shows which bucket the file targets so you can reject an `always`-bucket upload that should have been `rag` (or vice versa) before it lands. See `cli-control.md` for the full request/response shapes (filename rules, 25 MB per-file cap, 100 MB per-project quota — `always` bucket only, idempotency header, error codes).

The path-based upload contract — agent writes a temp file, passes its absolute path, Omniscio does the copy — keeps the request body constant-size regardless of file size and dodges base64 bloat.

## For agents

### How it works

Everything happens in one place — the moment Omniscio builds the agent's **first user message** — and it's all done by Omniscio in TypeScript. There is no shell hook, no `<system-reminder>`, no `CLAUDE.md` write. The docs builder is [buildProjectDocsContext(projectFolderPath, projectId)](../../src/main/services/project-docs-context.ts), which returns the inline block (text content + RAG TOC) or `null` when there's nothing to inject.

**Every engine, the same builders.** `buildProjectDocsContext` is wrapped by [buildSessionDocsContext](../../src/main/services/session/session-auto-context.ts) in Omniscio's single-source-of-truth `session-auto-context` module — the same builder the Claude spawn path, the crash-recovery replay, AND every external engine's first message build on. The external engines compose the docs + your AI-coaching profile via [buildFirstMessageAutoContext](../../src/main/services/session/session-auto-context.ts). So the docs reach not just Claude but **Codex, Gemini, Cursor, Pi, OpenClaw, OpenCode, and Hermes** too — and any external engine added later inherits it for free (a build-failing guard forbids one from composing a first message without it). Full mechanics + per-engine invariants: [launch-auto-context-contract.md](../../.claude/memory/contracts/launch-auto-context-contract.md).

**Where it's wired in.** A session's first message reaches its engine through one of these code paths, each prepending the docs block:

1. **Claude, with an initial prompt** (Super Prompt, automation, recipe, cron-heal, coaching, deep link). In [session-create.ts](../../src/main/services/session/session-create.ts) the block is prepended to `launchPrompt` before the process launches.
2. **Claude, without an initial prompt** (you open a blank session and type the first message). In [session-service.ts](../../src/main/services/session/session-service.ts)'s `sendResponse`, gated on the first message only.
3. **External engine, at launch** (CLI `/project/:id/new`, deep link, Quick Launch, automation). In [launch.ts](../../src/main/ipc/session/launch.ts)'s `launchGatedNonClaudeProvider`, prepended to the SENT text.
4. **External engine, at first send** — Codex / Gemini / Cursor / Pi / OpenClaw / OpenCode / Hermes. Each engine's branch in [session-service.ts](../../src/main/services/session/session-service.ts) prepends the block via the shared `externalFirstMessageAutoContext` helper, gated on the first operator message only.

All paths **skip virtual projects** (Omniscio's own UI agents carry purpose-built context, not user docs) and inject in exactly one place per first message to avoid double-injection. The builders wrap each phase in try/catch so a docs-enumeration failure can never block the spawn or the send — worst case the prompt goes out without the block and the agent can still `Read` the files.

**Invisible on every engine.** The block is prepended to what is **SENT** to the engine only — never the persisted operator row. On Claude that's `launchPrompt` / the built prompt (`session-create.ts` persists the raw `input.initialPrompt`; `sendResponse` persists the typed `displayText`). On external engines the launch path passes the RAW user text as a separate `displayText` argument, so an engine that persists the operator row at launch (gemini-ACP, the one-shot family) writes only what the user typed — never the docs block. So the chat bubble, the database, search, export, and share all show exactly what the user wrote, on every engine.

**The `always` bucket — text inlined.** `buildProjectDocsContext` enumerates the top level of `.claude/docs/` (skipping the `rag/` sub-folder and symlinks), filters to text-classified extensions via `TEXT_EXTENSIONS` from [project-docs.ts](../../src/main/services/project-docs.ts), orders them with `applyDocOrder` (same sidebar order as everywhere else), and emits:

```
## Injected Project Docs

### style-guide.md
` ` `
…full file contents…
` ` `

### glossary.md
` ` `
…full file contents…
` ` `
```

There is **no per-file cap** — any single text file is inlined in full. The project-wide budget is `docInjectionBudgetBytes(provider, model)` in [project-docs-caps.ts](../../src/main/services/project-docs-caps.ts): a `DOC_SHARE_OF_WINDOW` (25%) share of `enforcedContextWindowTokens` — the window the `claude` binary actually holds that engine to, which is NOT always the window the vendor publishes — clamped by `MAX_TOTAL_BYTES` / `MAX_TOTAL_TOKENS` (500 KB / 125K tokens). Those two constants are the **ceiling**, not the allowance: raising them only ever loosens the 1M-window lane, and can never hand a ~200K-window engine more than its share. A file that would push the running total over the budget is replaced by a one-line `*Skipped — would push total over NNK token cap. Use Read tool on \`<absolute path>\` when needed.\*`note carrying the engine's real number, and the loop **keeps going** so a smaller file later in the order can still fit. That path is absolute on purpose: the docs live in the **project** folder while the agent's working directory is the session's worktree, so a relative pointer would resolve against the wrong directory and the`Read`would fail. Docs flagged "inject as instruction" are subtracted from this same budget before the background bucket sees it, so the two blocks together can never exceed one share. The same budget feeds the sidebar's **skipped** counter, the always-inject portion of its "Injected" total, and — through`budgetTokensK`, the single bytes-to-thousands conversion the skip tooltip also calls — the cap figure its two summary lines name, so the docs-bucket accounting in the UI and the actual injection never disagree. (The headline "Injected" number additionally folds in the natively-loaded System Instructions — see the Auto Context group description above — but this budget governs only these always-inject docs.)

**The `rag` bucket — table of contents.** `buildProjectDocsContext` then enumerates `.claude/docs/rag/` (alphabetical) and appends a catalog under its own heading:

```
## Available Reference Docs (RAG — fetch with Read tool)

- .claude/docs/rag/schema.sql (~3K tokens) — "Canonical DB schema"
- .claude/docs/rag/full-api-spec.md (~61K tokens) — "Public API reference"
- .claude/docs/rag/archive-2024.pdf (8.2 MB)
- … and 4 more
```

Text entries carry a token estimate plus a one-line description (the first markdown `# H1`, or the first non-empty line, truncated to 120 chars); binary entries carry a human-readable size only. The TOC is capped at **100 entries** with a `- … and N more` overflow line so its token cost stays bounded regardless of how many files live in `rag/`. The bytes of the RAG files themselves are never inlined and never count against the `always` bucket's injection budget — only the ~one-line-per-file catalog does. The bucket is skipped entirely (no empty heading) when `rag/` is missing or empty; a project that uses ONLY the RAG bucket still gets its catalog because each bucket is built independently and the two non-empty blocks are joined.

**The `always` bucket — binaries attached.** Binary handling is unchanged and lives in [session-service.ts](../../src/main/services/session/session-service.ts)'s first-message path, separate from `buildProjectDocsContext`. On the first message it resolves the session's `projectId` → `Project.folderPath` → `resolveProjectWorkDir(folderPath)` → `<workdir>/.claude/docs/`, then calls `getProjectDocsFiles()` to list files whose extension is in `ATTACHABLE_EXTENSIONS` (images, PDFs, `.docx`/`.xlsx`/`.pptx`). **The enumerator deliberately skips the `rag/` sub-folder** — RAG-bucket binaries are catalog-only, never auto-attached. The resulting absolute paths are passed to `buildPromptWithAttachments()` in [image-store.ts](../../src/main/services/image-store.ts), which prepends a "Please examine the following file(s): …" header plus the paths to the prompt. From the CLI's perspective this is indistinguishable from the user having clicked the attach button, so PDFs and images go through its native multimodal ingestion pipeline. Virtual projects work because `resolveProjectWorkDir()` in [claude-project.ts](../../src/main/services/claude-project.ts) maps sentinels like `__claude__` → `~/Claude` before the lookup; virtual projects with no real workdir throw on `readdirSync`, which `getProjectDocsFiles()` catches and returns `[]` — a correct no-op.

**Why text-inline-as-content rather than attaching the text files too.** Text is cheap to drop straight into the message body and reads naturally as context the model already has — no extra `Read` round-trip, no multimodal pipeline. Binaries can't be inlined as text (the markdown body can't carry a PDF or an image), so they need real message attachments: `buildPromptWithAttachments()` builds a user message whose paths the CLI's native multimodal pipeline opens. The user explicitly rejected "tell the model to please read the PDF" and demanded "force-attach it like the user clicked attach" — so binaries route through the attachment path, not a "here are some filenames" note. Full design record and rejected approaches in the [auto-injection postmortem](../../.claude/memory/postmortems/claude-docs-auto-injection-postmortem.md).

**Kill switch.** `AMC_DISABLE_DOCS_INJECTION=1` (strict `=== '1'`) makes `buildProjectDocsContext` return `null` — disabling the inline text + RAG TOC path only. Binary attachment via `getProjectDocsFiles()` is a separate code path and is **not** affected by this switch.

**Why the RAG bucket has no separate retrieval system.** Omniscio deliberately does NOT ship an embedding store, vector DB, or selector-based retrieval. The agent already has a `Read` tool, the agent already sees the file catalog in the first message's TOC, and the agent is competent at picking relevant files from a labeled list. Adding an embedding layer would be a separate moving part to maintain, a separate index to keep coherent with disk, and a separate failure mode (semantic mismatch) — for no win over "list the filenames and let the agent decide". If a project's RAG bucket grows past ~50 files and the agent starts struggling to pick from the catalog, the right answer is to organize the bucket (rename for clarity, split into multiple projects, or move stale files out) rather than to layer retrieval on top.

## Related

[Auto Context (`.claude/docs/` auto-injection)](project-docs-auto-injection.md) is the first half
of this page — the buckets, the sidebar group, ordering, and the first-message chip. The
contracts and source files named under **For agents** above hold the invariants behind everything
this half describes.
