ChangePlan from your initializer or migration. It holds ordered operations; constructing it does not write files.
ChangePlan() when a hook has no work to propose.
File operations
Paths must be canonical project-relative paths. Absolute paths, traversal, source symlinks, and reserved state paths such as
.git/, .harnest/, and harnest-packs.lock are rejected. File/directory conversions require manual migration.
YAML operations
Use tuples for key paths.
("deployment", "region") identifies a nested field; ("deployment.region",) identifies a literal key containing a dot. A rename cannot move a field into itself or its descendants. A scalar parent is a conflict, not an object to replace implicitly.
YAML documents must be mappings with unique keys and a single document. Edits preserve unrelated values but normalise formatting and do not preserve comments.
Write policies
Use the enum values fromharnest.authoring, rather than strings:
IF_MISSING is the default for file writes, verbatim copies, templates, and YAML sets. MANAGED is appropriate when a later pack release updates a generated workflow or an unchanged default.
Add team documentation to the project
Teams can copy Markdown guides, API references, runbooks, PDFs, images, and other files into generated projects. Usefrom_file to preserve the original bytes without decoding text or substituting dollar expressions.
Set templates on your ProjectPack to the directory containing the packaged assets:
source is relative to the pack’s templates directory. The destination path is relative to the generated agent. The source must resolve to a regular file inside the pack’s template directory; missing files and escaping paths block the operation. Include these asset files when distributing the team’s Python package.
Each operation copies one file. These documents remain in the generated project for teammates and are excluded from compilation. The compile manifest has no resources field. Use an agent template to create standard instructions.md or skill content when the agent itself needs guidance.
Copied files use the same ownership records, conflict checks, and migration policies as text files. In a versioned migration, use policy=WritePolicy.MANAGED to update an unchanged pack-owned document. Local edits block replacement instead of being overwritten.
Templates
Settemplates=Path(__file__).parent / "templates" when defining your pack. Without values, template content is copied literally, including dollar signs.
Supplying values enables Python string.Template substitutions:
team-notes.txt
$$ for a literal dollar sign when substitution is enabled. Missing templates or substitutions fail the hook and block application.