Project Context

Automatically load project instructions from NOUMEN.md and CLAUDE.md files with hierarchical scoping, rules directories, and inline includes.

Project context files give your agent persistent, per-project instructions that are loaded into the system prompt automatically. This is the same concept as CLAUDE.md — and noumen supports CLAUDE.md natively — but also recognizes NOUMEN.md as its own convention. Both formats work, and both can coexist in the same project.

Why opt-in?

Project context loading scans the filesystem at startup — walking from the working directory up to the filesystem root, plus user home and managed directories — to discover instruction files. This adds initialization latency and injects content into the system prompt that increases token usage on every request. If your project doesn't have NOUMEN.md or CLAUDE.md files, this work is wasted. Keeping it opt-in means you only pay the scan and prompt overhead when you've actually set up project instructions.

Quick start

  1. Create a NOUMEN.md (or CLAUDE.md) in your project root:
# Project instructions

This is a TypeScript monorepo using pnpm workspaces.

- Use strict TypeScript. Prefer `const` over `let`.
- Write vitest tests for all new code.
- Use 2-space indentation.
  1. Enable project context loading in your Agent options:
import { Agent, AiSdkProvider } from "noumen";
import { LocalSandbox } from "noumen/local";
import { createOpenAI } from "@ai-sdk/openai";

const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });

const code = new Agent({
  provider: new AiSdkProvider({ model: openai.chat("gpt-5") }),
  sandbox: LocalSandbox({ cwd: "/my/project" }),
  options: {
    projectContext: true,
  },
});

await code.init(); // discovers and loads context files

That's it. The agent now sees your project instructions in every conversation.

How it works

The loader scans four layers in order, from lowest to highest priority. Each layer generates filename variants from the configured dotDirs list (defaults: [".noumen", ".claude"], which yields NOUMEN.* and CLAUDE.*):

LayerScopeFiles scanned
ManagedEnterprise/MDM<managedDir>/NOUMEN.md, <managedDir>/CLAUDE.md, rules in <managedDir>/.noumen/rules/, <managedDir>/.claude/rules/
UserPer-user home~/.noumen/NOUMEN.md, ~/.claude/CLAUDE.md, rules in ~/.noumen/rules/, ~/.claude/rules/
ProjectRepo ancestorsFor each directory from filesystem root down to cwd: <dir>/NOUMEN.md, <dir>/CLAUDE.md, <dir>/.noumen/NOUMEN.md, <dir>/.claude/CLAUDE.md, rules in <dir>/.noumen/rules/, <dir>/.claude/rules/
LocalGitignored<dir>/NOUMEN.local.md, <dir>/CLAUDE.local.md (same ancestor walk)

Files loaded later in the list have higher priority. This means your project-level NOUMEN.md at the repo root overrides user-level defaults, and NOUMEN.local.md (which you gitignore for personal overrides) has the highest priority of all.

Customizing or restricting the dot-directory list

dotDirs accepts any ordered list of directory names. Names earlier in the list also win for tooling writes (sessions, checkpoints, config.json, etc.) — see the Dot Directories section below.

// Only scan .noumen (ignore .claude entirely):
options: {
  projectContext: { cwd: "/my/project", dotDirs: { names: [".noumen"] } },
}

// Use a custom name first, fall back to .noumen:
options: {
  projectContext: { cwd: "/my/project", dotDirs: { names: [".mine", ".noumen"] } },
}

CLAUDE.md compatibility

If your project already has a CLAUDE.md or .claude/ directory, noumen picks it up automatically — no renaming needed. When both NOUMEN.md and CLAUDE.md exist in the same directory, both are loaded. This lets you maintain compatibility with Claude Code while also using noumen-specific instructions.

Rules directories

For fine-grained control, add markdown files under .noumen/rules/ or .claude/rules/:

my-project/
  NOUMEN.md              # main project instructions
  .noumen/
    rules/
      style.md           # coding style rules
      testing.md          # testing conventions
      api/
        rest.md          # REST API patterns

Each .md file in the rules directory is loaded as a separate context file. Subdirectories are scanned recursively.

Conditional rules

Rules can target specific file patterns using paths: frontmatter, just like Skills:

---
paths:
  - "src/**/*.tsx"
  - "src/**/*.jsx"
---

When working on React components:
- Use functional components with hooks
- Co-locate tests in __tests__ directories
- Export component props as a named type

This rule only activates when the agent reads or edits files matching those globs. Conditional rules keep the system prompt focused — the agent only sees instructions relevant to the files it is working on.

@ includes

Context files can reference other files using inline @path syntax:

# Project instructions

See @./docs/architecture.md for the system design.
See @./docs/conventions.md for coding standards.

Paths are resolved relative to the including file. The @ reference is detected anywhere in prose (outside fenced code blocks and inline code spans), and the referenced file's contents are loaded and attached to the parent.

  • Maximum include depth: 5 levels (configurable via maxIncludeDepth)
  • Cycles are detected and skipped automatically
  • Missing referenced files are silently ignored

Configuration

Pass true for sensible defaults, or a ProjectContextConfig object for full control:

options: {
  // Simple: use defaults (cwd from sandbox, load everything)
  projectContext: true,

  // Full control:
  projectContext: {
    cwd: "/my/project",
    homeDir: "/home/user",                        // for sandboxed environments
    managedDir: "/etc/noumen",                    // enterprise managed settings
    dotDirs: { names: [".noumen", ".claude"] },   // which dot-dir names to scan
    loadUserContext: true,                        // load from homeDir (default: true)
    loadProjectContext: true,                     // load from cwd ancestors (default: true)
    loadLocalContext: true,                       // load *.local.md files (default: true)
    excludes: ["**/secret.md"],                   // glob patterns to skip
    maxIncludeDepth: 5,                           // @ include depth limit (default: 5)
  },
}

ProjectContextConfig fields

FieldTypeDefaultDescription
cwdstringrequiredWorking directory (project root)
homeDirstringUser home directory. Pass explicitly for sandboxed environments.
managedDirstringEnterprise/MDM settings directory
dotDirsDotDirConfig{ names: [".noumen", ".claude"] }Ordered list of dot-dir names to scan. Each name produces matching <NAME>.md / .<name>/rules/ variants.
loadUserContextbooleantrueLoad user-scope context from homeDir
loadProjectContextbooleantrueLoad project-scope context from cwd ancestors
loadLocalContextbooleantrueLoad *.local.md files
excludesstring[][]Glob patterns to exclude
maxIncludeDepthnumber5Maximum @ include recursion depth

Dot directories

Noumen uses "dot directories" — .noumen/ and .claude/ by default — for agent-managed state: skills, rules, sessions, checkpoints, tool-result spillover, worktrees, OAuth tokens, and the CLI config.json. The same ordered list controls both reads (first match wins, with later fallbacks) and writes (always to dotDirs.names[0]).

Configure the list once at the Agent level and every subsystem follows:

const agent = new Agent({
  provider,
  sandbox,
  options: {
    // Default — same as omitting this field:
    dotDirs: { names: [".noumen", ".claude"] },

    // Or: write to .mine, fall back to .noumen and .claude for reads:
    // dotDirs: { names: [".mine", ".noumen", ".claude"] },
  },
});

Reads (NOUMEN.md/CLAUDE.md, skills, config.json, OAuth tokens) check each directory in order. Writes (new sessions, checkpoints, noumen init, token refresh) always target dotDirs.names[0]. Dangerous-path protection in the tool permissions pipeline is also built from this list — every configured name is treated as protected.

ContextFile type

Each loaded context file is represented as:

interface ContextFile {
  path: string;
  scope: "managed" | "user" | "project" | "local";
  content: string;
  globs?: string[];       // from paths: frontmatter (conditional rules)
  includes?: ContextFile[]; // files referenced via @
}

Standalone usage

You can use the loader and formatter independently of Agent, for example to build your own prompt pipeline:

import { loadProjectContext, buildProjectContextSection } from "noumen";
import { LocalFs } from "noumen/local";

const fs = new LocalFs({ basePath: "/" });

const files = await loadProjectContext(fs, {
  cwd: "/my/project",
  homeDir: "/home/user",
});

const promptSection = buildProjectContextSection(files);
// Insert into your own system prompt

The filterActiveContextFiles helper filters conditional rules by touched file paths:

import { filterActiveContextFiles } from "noumen";

const active = filterActiveContextFiles(files, ["/my/project/src/App.tsx"], "/my/project");
const section = buildProjectContextSection(active);