-
Notifications
You must be signed in to change notification settings - Fork 68
AGENTS md
FreeCAD AI can load project-specific instructions from an AGENTS.md (or FREECAD_AI.md) file and inject them into the LLM system prompt. This lets you define design conventions, material specs, naming rules, or any other guidance that applies to a particular project without repeating yourself every chat session.
The feature supports multi-location search, include directives for splitting instructions across files, and live variable substitution with document-level values.
Source: /freecad_ai/extensions/agents_md.py
When FreeCAD AI builds the system prompt (in build_system_prompt() inside core/system_prompt.py), it calls load_agents_md() which searches for instruction files in this order:
-
Active document's directory -- the folder containing the currently open
.FCStdfile - Parent directories -- up to 3 levels above the document directory
-
User config directory --
<FreeCADAI dir>/AGENTS.md
The first file found wins. If neither AGENTS.md nor FREECAD_AI.md exists at any of these locations, no project instructions are included.
At each directory level, the loader checks filenames in this order:
AGENTS.mdFREECAD_AI.md
If both files exist in the same directory, AGENTS.md takes priority. The constants controlling this are defined in the source:
INSTRUCTION_FILENAMES = ["AGENTS.md", "FREECAD_AI.md"]
MAX_PARENT_LEVELS = 3If the active document has never been saved (no FileName), the document directory search is skipped entirely and only the user config fallback is checked. This is handled in _get_document_directory():
def _get_document_directory() -> str:
try:
import FreeCAD as App
doc = App.ActiveDocument
if doc and doc.FileName:
return os.path.dirname(doc.FileName)
except ImportError:
pass
return ""You can split instructions across multiple files using include directives:
<!-- include: materials.md -->
<!-- include: conventions.md -->
<!-- include: ../shared/company-standards.md -->- The include path is resolved relative to the directory containing the file with the directive (not relative to the document or working directory).
- Includes are processed recursively: an included file can itself contain
<!-- include: ... -->directives. - Maximum nesting depth is 5 levels to prevent infinite recursion.
- If a referenced file does not exist, a comment is inserted:
<!-- include not found: filename.md --> - If reading fails (permissions, encoding), a different comment appears:
<!-- include failed: filename.md -->
The regex pattern that matches include directives is:
INCLUDE_RE = re.compile(r"<!--\s*include:\s*(.+?)\s*-->")This means whitespace around include: and the filename is flexible. All of these are equivalent:
<!-- include: materials.md -->
<!--include:materials.md-->
<!-- include: materials.md -->Instruction files can reference live document values using {{variable_name}} placeholders. These are replaced at load time with current values from the active FreeCAD document.
| Variable | Description | Example Value |
|---|---|---|
{{document_name}} |
Internal name of the active document | Enclosure |
{{document_path}} |
Full file path, or (unsaved)
|
/home/user/project/Enclosure.FCStd |
{{object_count}} |
Number of objects in the document | 12 |
{{active_body}} |
Label of the active PartDesign Body | EnclosureBase |
- Variables are replaced after include directives are resolved, so included files can also use variables.
- Unknown variables are preserved as-is --
{{unknown_var}}remains literally{{unknown_var}}in the prompt. - If no document is open, all variables resolve to empty strings (or
"0"forobject_count).
The substitution regex is:
VARIABLE_RE = re.compile(r"\{\{(\w+)\}\}")Only \w+ (letters, digits, underscore) characters are matched inside the braces.
Here is a full example AGENTS.md file for an electronics enclosure project:
# Project: {{document_name}}
## Design Conventions
- All dimensions in millimeters
- Wall thickness: 2.0mm minimum
- Fillet radius on exterior edges: 1.5mm
- Use PartDesign workflow (Body -> Sketch -> Pad/Pocket) for all features
- Label bodies descriptively: "EnclosureBase", "EnclosureLid"
## Material
- ABS plastic (injection molding target)
- Draft angle: 1 degree on vertical walls
- Minimum feature size: 0.8mm
## Hardware
<!-- include: hardware-specs.md -->
## PCB Mounting
- PCB dimensions: 80mm x 50mm
- Mounting holes: M3, 4 corners, 3mm from edges
- Standoff height: 5mm
## Shared Standards
<!-- include: ../company/naming-conventions.md -->
<!-- include: ../company/tolerance-rules.md -->
## Status
Document path: {{document_path}}
Objects so far: {{object_count}}
Active body: {{active_body}}The full processing order when load_agents_md() is called:
- Search -- find the first matching file in the directory chain or user config
- Load -- read the raw file content
-
Resolve includes -- recursively expand
<!-- include: ... -->directives (up to 5 levels deep) -
Substitute variables -- replace
{{variable}}placeholders with live values -
Inject -- the processed text is appended to the system prompt under the heading
## Project Instructions (from AGENTS.md)
- Keep instructions focused and specific. The entire AGENTS.md content is included in every LLM request, so long files increase token usage.
- Use includes for shared standards. If multiple projects share the same material specs or naming conventions, put those in separate files and include them.
-
Put general defaults in the user config. Place
<FreeCADAI dir>/AGENTS.mdwith defaults that apply to all your projects. -
Put project-specific instructions next to your
.FCStdfile. This ensures the instructions travel with the project if you move or share it. -
Leverage parent directory search. For a workspace with multiple related FreeCAD files, put a shared
AGENTS.mdone level up and project-specific overrides in each subdirectory. -
Use variables for dynamic context.
{{object_count}}and{{active_body}}help the LLM understand the current state without you describing it manually.