Context
Charter synthesis is no longer an internal model call. The host LLM or harness
does the reasoning work, reads project evidence, and writes doctrine YAML into
the generated-artifact staging area. spec-kitty then validates those files,
records provenance, emits the project DRG layer, and promotes the results into
the live project doctrine tree.
The important boundary is simple:
- The harness owns reading evidence and generating doctrine
spec-kittyowns validation, neutrality gating, provenance, staging, and promotion
This ADR exists to keep that boundary explicit and prevent future drift back to an in-process model adapter.
Decision
1. Canonical generated-artifact layout
The harness writes generated doctrine YAML under:
.kittify/charter/generated/
directives/
<NNN>-<slug>.directive.yaml
tactics/
<slug>.tactic.yaml
styleguides/
<slug>.styleguide.yaml
The live promoted tree remains:
.kittify/doctrine/
directives/
tactics/
styleguides/
.kittify/charter/
provenance/
synthesis-manifest.yaml
2. Canonical identity rules
The harness must match both the filename rule and the YAML identity rule.
- Directives use
artifact_id = PROJECT_<NNN>and filename<NNN>-<slug>.directive.yaml - Tactics use
artifact_id = <slug>and filename<slug>.tactic.yaml - Styleguides use
artifact_id = <slug>and filename<slug>.styleguide.yaml - The top-level YAML
idfield must exactly match the targetartifact_id
Examples:
directive:PROJECT_001→.kittify/charter/generated/directives/001-project-intent-directive.directive.yaml(renamed from001-mission-type-scope-directive, see the 2026-10-19 amendment)tactic:testing-philosophy-tactic→.kittify/charter/generated/tactics/testing-philosophy-tactic.tactic.yamlstyleguide:python-style-guide→.kittify/charter/generated/styleguides/python-style-guide.styleguide.yaml
If the file is missing or the YAML id does not match, synthesis fails closed.
3. Evidence ownership
Evidence is split on purpose:
spec-kittyowns evidence declaration and identity:- code-reading signals
- configured URL lists
- bundled corpus snapshots
- hashing and provenance fields derived from that evidence
- The harness owns evidence consumption:
- reading the repo and the URLs
- deciding how that evidence should shape generated doctrine text
- writing the generated YAML files that
spec-kittywill validate
No internal HTTP fetcher, browser reader, or model client belongs inside
spec-kitty for this contract.
4. Operator CLI sequence
The canonical operator flow is:
- Collect interview answers and any configured synthesis inputs.
- Have the harness write generated YAML into
.kittify/charter/generated/. - Validate the handoff without promotion:
spec-kitty charter synthesize --dry-run
- Promote the validated artifacts into the live tree:
spec-kitty charter synthesize
- Regenerate one bounded topic after the harness updates the corresponding generated artifact:
spec-kitty charter resynthesize --topic directive:PROJECT_001
- Inspect operator state, evidence summary, manifest health, and provenance:
spec-kitty charter status --provenance
5. Fixture adapter policy
--adapter fixture remains supported only as the deterministic regression
harness. It is not the default runtime path and it is not a production
generation policy.
Consequences
Positive
- One inference engine, the host harness already talking to the user.
- No hidden SDK calls or second API-key story inside
spec-kitty. - Clear separation between generation and validation.
- Provenance stays trustworthy because the CLI records the exact validated handoff, not a hidden model invocation.
Negative
- The harness now has to respect the file layout and identity rules exactly.
- Operator tooling must make the handoff state visible enough that a bad file is
easy to diagnose. That is why
charter statusand generated-artifact error UX matter.
More information
src/charter/synthesizer/generated_artifact_adapter.pysrc/specify_cli/cli/commands/charter.pysrc/charter/synthesizer/write_pipeline.pytests/agent/cli/commands/test_charter_status_cli.py
Amendment 2026-10-19: the always-synthesized directive is renamed
The always-synthesized directive's slug is now project-intent-directive (title "Project Intent Directive"); it was mission-type-scope-directive (#6443, mission charter-synthesis-graph-and-languages-01M59RZ5). The generated-input layout above is unchanged except for that file name: directives/<NNN>-project-intent-directive.directive.yaml. The declared PROJECT_00N id, the directive:PROJECT_00N URN and the interview section label mission_type do not change, and synthesis no longer emits a requires --> mission_type:* edge for it.
An existing project converges through one charter-layer helper, rename_legacy_project_intent_directive (charter.activation.api). It moves the artifact file and this harness input, rewrites the provenance and synthesis-manifest entries (recomputing the manifest hashes), relabels the overlay node, drops the dead edge, rewrites stem tokens in the activation write target and restamps the bundle hash. It never rewrites the artifact body, is idempotent, and refuses on a target collision having changed nothing. The upgrade migration 4.0.0rc8_project_intent_directive_rename and charter synthesize / resynthesize (before they write, so a legacy file can never leave two files declaring PROJECT_00N) both call it. Without the harness-input move, the next synthesize would raise GeneratedArtifactMissingError for the new file name.