Project Config - alexherrero/agentm GitHub Wiki
You configure each project in .harness/project.json. This file holds vault_project, github, and env. It also adds an enablement block. This block includes type, skills, hooks, registered_at, registered_via, operator_overrides, and last_redetect_at. It records your repo's detected and approved configuration. You keep the verification ledger in a separate file. You leave features.json and its passes untouched.
| Question | Answer |
|---|---|
| Where does enablement config live? |
.harness/project.json โ NOT features.json (locked DC-1). |
| Who writes it? |
scripts/project_config.py โ register() calls merge_enablement(), which preserves every other key (vault_project / github / env) and overwrites only the enablement keys. |
| Where does the file resolve to? | Vault-resident post-V4 #26: the project directory's own desk/project.json (agentm-vault ยง Projects and tasks โ desk/ is the project's home for machine files since the projects migration, plan 15 retired the older _harness/project.json fallback rung). The writer routes through harness_memory.write_machine_file (.project-mode-aware โ routes to the repo-local home <repo>/.harness/project.json for local-mode projects, or any project with no vault; distinct from the migrate-nagging legacy fallback). |
| What counts as "registered"? |
is_registered() = project.json carries a non-empty skills block OR the repo has a repo_registry entry. |
What's the default type? |
"coding" (the type taxonomy is deferred to V5). |
| Related pages | Detection rules, Configure a new project, Repo layout |
The build_enablement_block function emits 7 keys. The merge_enablement function overwrites these keys. You preserve everything else in project.json verbatim during the merge.
| Field | Type | Default | Meaning |
|---|---|---|---|
type |
string | "coding" |
Project type. The taxonomy beyond "coding" is deferred to V5. |
skills |
object | the 6 enableable skills, all enabled | Map <name> โ TargetState. Names: memory, design, diataxis-author, pii-scrubber, ship-release, dependabot-fixer. |
hooks |
object | the 7 enableable hooks, all enabled | Map <name> โ TargetState. Names: kill-switch, steer, commit-on-stop, memory-recall-session-start, memory-recall-prompt-submit, memory-reflect-idle, memory-reflect-stop. |
registered_at |
string (ISO-8601 UTC) | now, at write time | When the block was first written, e.g. 2026-05-29T14:03:00Z. |
registered_via |
string | "auto-detect" |
How the repo was registered โ "auto-detect" (the flow) or "manual". |
operator_overrides |
array | [] |
[{at, skill_or_hook, action, reason}] โ one entry per opt-out recorded by apply_override (action defaults to "disabled-at-registration"). |
last_redetect_at |
string | null | null |
When re-detection last scanned this repo. Stays null until the first redetect run; a --dry-run preview leaves it alone. |
| Field | Type | Meaning |
|---|---|---|
enabled |
bool | Whether the skill/hook is active for this project (false after an opt-out). |
auto_detected |
bool |
true if a rule matched this target; false if it carries the default rationale only. |
rationale |
string | Why this target is relevant โ a detection reason (when matched) or a default reason. |
rule_id |
string | null | The matching rule's id (e.g. R-wiki), or null when default-enabled. |
operator_action |
string | null |
null until an opt-out flips it to "disabled-at-registration". |
You re-scan a configured repo with redetect. It diffs a fresh detection result against the stored block and prints what moved. It writes no enablement key unless you pass --apply.
python3 scripts/project_config.py redetect . # surface the diff
python3 scripts/project_config.py redetect . --format json # machine-readable
python3 scripts/project_config.py redetect . --dry-run # read-only; no stamp
python3 scripts/project_config.py redetect . --apply # refresh the rationale| Change category | What moved | What --apply does |
|---|---|---|
newly-detected |
A rule matches a target that carried only the default rationale. | Writes the rule's rule_id and rationale onto the entry. |
no-longer-detected |
The rule that justified a target stopped matching. | Returns the entry to auto_detected: false with the default rationale. |
rule-changed |
A different rule justifies the target now. | Writes the new rule_id and rationale. |
new-target |
The harness added an enableable your config predates. | Adds the entry with operator_action: null. |
retired-target |
The harness dropped an enableable your config still lists. | Removes the stale entry. |
Three rules govern the flow:
-
Applying refreshes rationale, never enablement.
--applywritesauto_detected,rule_id, andrationale. It carriesenabledandoperator_actionover from the stored entry untouched, so re-detection can never turn a skill off behind you. Detection surfaces and never gates (DC-7), so a lapsed rule returns a target to its default rationale rather than disabling it. You disable things through the override path instead. -
An
operator_overridesentry suppresses suggestions about its target. Those land in a separatesuppressedlist that names the override and your original reason, so a decline you already made never comes back as a fresh choice. Aretired-targetis the one exception โ a dead config entry is housekeeping, not a re-suggestion. -
--applyre-scans from scratch. There is no stored diff to go stale between the run that shows you a change and the run that applies it.
Exit codes: 0 the config still matches the repo, 1 changes were surfaced or applied, 2 re-detect could not run (no enablement block yet, or the repo is the harness source and detection bypasses it).
You add the enablement block. The merge-writer preserves these existing keys.
| Key | Meaning |
|---|---|
vault_project |
MemoryVault slug for this repo. |
github |
GitHub Projects linkage (owner/number/url/repo). See GitHub Projects integration. |
env |
Per-project environment settings. Pre-existing; untouched by the enablement merge. |
-
Detection rules โ You populate
rationaleandrule_idin theskillsandhooksmaps here. - Configure a new project โ You write this block using this flow.
- Re-detect a configured project โ You refresh this block after the repo changes.
-
Repo layout โ You store
.harness/project.jsonon disk here. -
Auto-detect + auto-configure โ You see why config lives here and not in
features.json(DC-1).