Add files, extension point contributions, and dependencies to the generated project with block directives.
Every agentc generate and agentc build writes the generated project again from the manifest, so
changes made by hand to generated files do not last. The block directive makes those changes part
of code generation instead. During the generate stage, the compiler renders each block's templates
and adds the output to the generated project alongside the archetype's own files. Use it when the
generated project needs modules, configuration, or dependencies that the archetype does not produce.
For where this sits in the build, see The compilation pipeline.
This guide uses one case throughout: building the agent with a custom release profile, which Cargo
only reads from Cargo.toml.
A block's content comes from Jinja2 templates. Template paths resolve against the build context
directory, like every other path in the manifest. The resolved manifest is available under ctx, so
a template can use values such as {{ ctx.agent_name }} or {{ ctx.agent.version }}.
The release profile is TOML with no values from the manifest:
[profile.release]
lto = "fat"
codegen-units = 1
strip = trueAn extension point is a named place inside a file the archetype generates. The contributes map adds
rendered content to extension points instead of writing a file of its own. Each key is an extension
point name, and each value is a contribution whose kind matches what the point accepts:
| Kind | Use it for |
|---|---|
text | Content inserted as written, such as TOML tables. |
tokens | Rust code, such as module declarations or struct fields. |
cargo_dependencies | Crates added to Cargo.toml. |
The cargo::tables extension point inserts text at the end of the generated Cargo.toml, which is
where the release profile belongs:
block "release_profile" {
description = "Optimizes release builds."
contributes = {
"cargo::tables" = {
kind = "text"
template = "./codegen/profile.release.toml.j2"
}
}
}The standalone extension points list the points the archetype declares, the kind each accepts, and where its content is placed.
The generates map writes whole files into the generated project. Each key is an output path within
that project, and each value is a template path. A generated Rust module also needs its mod
declaration, which a tokens contribution adds to main::modules:
block "build_info" {
generates = {
"src/build_info.rs" = "./codegen/build_info.rs.j2"
}
contributes = {
"main::modules" = {
kind = "tokens"
template = "./codegen/modules.rs.j2"
}
}
}pub const AGENT_VERSION: &str = "{{ ctx.agent.version }}";mod build_info;A cargo_dependencies contribution to cargo::dependencies adds crates to the generated
Cargo.toml. Each key in crates is a crate name, and each value holds its dependency fields:
block "build_info" {
contributes = {
"cargo::dependencies" = {
kind = "cargo_dependencies"
crates = {
const_format = {
version = "0.2"
features = ["rust_1_64"]
}
}
}
}
}Manifest blocks are added after the archetype, graph, and protocol blocks, in label order.
Contributions to the same extension point follow that order, so the release profile comes after
anything the archetype places at the end of Cargo.toml. If a block generates a path that a
built-in block also writes, the manifest block's file is the one written.
Run agentc generate and open the generated project. Cargo.toml ends with
the contributed profile:
[profile.release]
lto = "fat"
codegen-units = 1
strip = trueEvery agentc build now produces the project with the profile in place.
© 2026 pogue.dev. All rights reserved.
CC BY 4.0