Instructions¶
The [instructions] section declares the agent's system prompt and rule files. For normative field constraints, see the manifest spec.
Fields¶
[instructions]
system = "instructions/system.md"
[instructions.rules.safety]
src = "instructions/rules/safety.md"
summary = "Input validation guardrails"
apply = "always"
[instructions.rules.style]
src = "instructions/rules/style.md"
description = "Code style preferences - apply when editing source files"
apply = "model-decision"
[instructions.rules.rust]
src = "instructions/rules/rust.md"
apply = "glob"
apply_to = ["*.rs"]
System¶
| Field | Type | Description |
|---|---|---|
system |
str |
Relative path to system prompt markdown file — MUST end in .md |
Rules¶
Rules are named tables under [instructions.rules.<name>]. Each rule points to a markdown file and carries metadata controlling when the harness injects it into model context.
A rule is a markdown file containing natural language instructions that the harness injects into the model's context window before inference. The injection point varies by harness — most inject into the system prompt. The rule file MUST be pure content — activation metadata (apply, apply_to, description) lives in theta.toml, and theta cast injects harness-specific frontmatter when the target expects it.
| Field | Type | Description |
|---|---|---|
src |
LocalOrGitRef |
REQUIRED — rule source |
summary |
str |
Short label for display purposes (e.g. theta list rules) |
description |
str |
Model-facing text — REQUIRED when apply = "model-decision" |
apply |
ApplyMode |
Activation mode — defaults to "always" |
apply_to |
arr[str] |
File glob patterns (*, **, ?, [...]) — SHOULD be present when apply = "glob" |
Rule names MUST match ^[a-z0-9]+(-[a-z0-9]+)*(/[a-z0-9]+(-[a-z0-9]+)*)*$ — kebab-case segments optionally separated by /.
Path-qualified names (e.g. backend/typescript, review/pr-review) group related rules into logical categories. The / separator maps to subdirectories in the theta store (rules/backend/typescript.md) and in harnesses that support recursive scanning (Copilot). Harnesses with flat rule directories (Claude Code, Cursor) receive flattened names: backend/typescript --> backend-typescript.
System store names remain simple kebab-case (no /). theta add rule backend/typescript --system typescript registers a store rule under a path-qualified local name.
theta CLI — removal
theta rm rule <name> --delete removes the rule from the manifest and deletes the source .md file. theta rm system --delete does the same for the system prompt. Omitting --delete removes the manifest entry only and leaves the source file in place. The --delete pattern is consistent across theta rm rule|system|skill|subagent; theta rm tool takes no --delete since tools have no source files.
Apply modes¶
| Value | Meaning | When to use |
|---|---|---|
"always" |
Always injected into context | General rules (safety, coding standards) |
"model-decision" |
Model reads description, decides whether to apply |
Contextual rules the model should selectively apply |
"glob" |
Applied when active file matches apply_to patterns |
Language-specific or directory-scoped rules |
"manual" |
Only applied on explicit user invocation | Rarely-needed reference rules |
Harness support for activation modes
"always" and "glob" are supported by all harnesses (Codex CLI approximates glob via concatenation). "model-decision" and "manual" are only natively supported by Cursor — casting to other harnesses is lossy (see apply mode casting).
Validation¶
theta check validates rules:
- Rule names MUST match
[a-z0-9]+(-[a-z0-9]+)*(/[a-z0-9]+(-[a-z0-9]+)*)* - ≥3 path segments --> warning (deeply nested rules are an anti-pattern)
src(when a local path string) MUST end in.mdapply = "model-decision"withoutdescription--> warning (model has no signal to decide)apply = "glob"withoutapply_to(or empty) --> warning (rule never activates)apply_toset butapplyis not"glob"--> warning (dead config)- Rule file does not exist --> error
- Rule file is empty --> warning
- Rule file still matches scaffold template --> warning
Writing effective rules¶
Recommendations synthesized from official harness documentation:
- Be concrete and verifiable — "use 2-space indentation" not "format code properly" (Claude Code, Copilot)
- Include reasoning behind rules — "use
date-fnsinstead ofmoment.jsbecause moment.js is deprecated" (Copilot) - Reference files instead of copying their contents — prevents staleness (Cursor)
- Skip rules the model already knows (common tools, obvious practices) (Cursor)
- Only add rules when you notice the agent making the same mistake repeatedly (Cursor)
- Prefer bullet points and markdown structure over long paragraphs (Claude Code)
Conventional directory¶
project/
├── theta.toml
├── instructions/
│ ├── system.md <-- [instructions].system
│ └── rules/
│ ├── safety.md <-- [instructions.rules.safety]
│ ├── style.md <-- [instructions.rules.style]
│ └── backend/
│ └── typescript.md <-- [instructions.rules."backend/typescript"]
Default directories are configurable via theta-settings:
THETA_INSTRUCTIONS_DIR(default:instructions)THETA_RULES_DIR(default:rules)
Resolution order: CLI flag > env var > built-in default.
These settings are also documented in the protocol guide.
Casting¶
| Field | Claude Code | Codex CLI | Cursor | Copilot |
|---|---|---|---|---|
system |
CLAUDE.md |
AGENTS.md |
.cursor/rules/system.md |
.github/copilot-instructions.md |
| Rules | .claude/rules/<name>.md |
Concatenated into AGENTS.md |
.cursor/rules/<name>.mdc |
.github/instructions/<name>.instructions.md |
Path-qualified names and flat harnesses
Path-qualified rule names (backend/typescript) cast to subdirectories in Copilot (.github/instructions/backend/typescript.instructions.md). Flat harnesses receive a flattened filename: .claude/rules/backend-typescript.md, .cursor/rules/backend-typescript.mdc.
Claude Code: file rename
Claude Code reads CLAUDE.md, not AGENTS.md. Casting MUST rename or generate the file. See Claude Code harness reference.
Apply mode casting¶
How each apply value maps to harness-native frontmatter:
apply |
Cursor (.mdc) |
Copilot | Claude Code | Codex CLI |
|---|---|---|---|---|
"always" |
alwaysApply: true |
No frontmatter | No frontmatter | Concatenated |
"model-decision" |
alwaysApply: false + description |
Lossy (warn) | Lossy (warn) | Lossy (warn) |
"glob" |
alwaysApply: false + globs |
applyTo |
paths |
Lossy (warn) |
"manual" |
alwaysApply: false |
Lossy (warn) | Lossy (warn) | Lossy (warn) |
Rules frontmatter by harness¶
Native frontmatter keys each caster emits for rules:
| Harness | Frontmatter keys |
|---|---|
| Cursor | description, globs, alwaysApply (YAML in .mdc) |
| Copilot | applyTo (YAML in .instructions.md) |
| Claude Code | paths (YAML in .md under .claude/rules/) |
| Codex CLI | None — rules concatenated into AGENTS.md |
Output size constraints¶
Hard and soft limits imposed by harnesses on cast output:
| Harness | Constraint | Type | Value | Source |
|---|---|---|---|---|
| Codex CLI | Combined AGENTS.md size |
Hard | 32 KiB | developers.openai.com |
| Cursor | Per-rule line count | Soft | 500 lines | cursor.com/docs/rules |
| Claude Code | Per-file line count | Soft | 200 lines | code.claude.com |
| Copilot | — | None | — | — |
theta cast emits warnings (Diagnostic::warn) for hard limits and hints (Diagnostic::hint) for soft limits.
Metadata: theta.toml vs frontmatter¶
Rule configuration metadata (description, globs, activation mode) lives in theta.toml, not in any file frontmatter. The rule markdown file is pure content. theta cast injects frontmatter when the target harness expects it (e.g., Cursor .mdc).
The guiding principle: if a resource has config metadata + free-text content, the config metadata belongs in theta.toml.
Global instructions¶
User-level instructions (e.g., ~/.claude/CLAUDE.md, user rules in Cursor settings) are a runtime concern owned by each harness. They are not project-scoped and not part of theta.toml.