Composition lets you maintain shared instructions without copying them into every Recipe. For Sonny 0.12.7, choose the mechanism by what should be shared, then inspect the result before running it. This guide is for authors who have validated the research example and understand its tool boundary.

Choose the reference mechanism

MechanismWhat it refers toImportant boundary
pack.parentAnother discovered Recipe IDInherits baseline configuration before includes resolve; identity remains the child’s. Inherited policy needs review.
pack.includesOther discovered Recipe IDsContributes prompt content. It does not independently approve the included Recipe’s services or credentials.
prompts[].refA prompt-fragment file relative to the Recipe fileUses the smaller fragment document shape; a nonempty referenced system field can replace the inline system text.
compose.includePrompt IDs within the same RecipeCombines prompt components using the configured merge mode. It is different from including an entire Recipe ID.
agents.sub_packs[].pack_refA complete sub-Recipe fileBuilds a child scope; inheritance and host execution policy apply separately.
agents.sub_packs[].inheritConcern-specific child inheritance settingsTools, MCP, variables and prompt content have distinct rules. A child is not a copy of the parent’s authority.

Use the exact schema for each shape. A prompt fragment starts with fields such as system and guidelines; it does not need a document version or pack wrapper. The research example’s prompts/research.yaml is a complete fragment loaded by brief.yaml.

Keep prompt IDs distinct

The installed parent test combined a parent prompt named baseline and a child entry named main; both instructions appeared in the resolved subject. Giving both different content under main instead failed with pack.collision.prompt_id. Do not expect implicit renaming or a silent child override to make that collision safe. The compatibility positive for that older expectation remains nonzero in qualification.

Give shared prompts stable, distinct IDs and inspect the composed text. Changes to a parent can affect every child. Tool allowlists can merge across a parent chain; removing a name from the child’s list is not a reliable way to remove an inherited capability. Inspect the effective policy and execute a denied-action case, rather than treating composition as a permission review.

Understand fragment merge order

In the installed fragment test, the declaring prompt’s inline system text was replaced by the fragment’s nonempty system text. Guidelines sharing an ID combined their inline and referenced values. This is not a universal “local wins” rule.

Keep a system instruction in one place when possible. If both locations contain it, resolve the Recipe and check which text survived. Paths are relative to the Recipe file; moving only that file can break its fragment references. A source or backup needs the referenced files too.

sonny recipe resolve evidence-brief --file ./brief.yaml --format json

For the research example, inspect system_prompt, components and digests in the returned subject. Resolution describes a compiled subject. It does not certify the actual provider request, credential admission or execution of each tool. The tutorial separately checks a real request and an actual tool denial.

Diagnose a broken graph

A missing include failed a single-file load with pack.include.unknown. In a directory load, the broken Recipe was disabled while a healthy, uniquely named parent/child graph still resolved. Selecting the disabled ID then failed. Read the diagnostic stream as well as the selected subject: success for one Recipe does not establish that every file in the directory loaded.

Missing fragment files fail resolution. Restore the reviewed file or correct its path and validate again. Do not replace a missing trusted component with arbitrary text just to remove the error. A changed reference can also invalidate a model variant’s recorded base/component digest.

Variables declare a contract

pack.variables.definitions declares names, descriptions, required/default values and informational types. The key is definitions. A required variable with no value or default failed the installed run before any provider request.

Release limitation: supplying --var audience=review council did not expand literal {{.Vars.audience}} text in either an inline system prompt or a referenced fragment in the qualified file-backed headless path. The provider received the literal token. Both binding positives remain nonzero. The independent in-memory render tests do not establish this file-backed behavior.

The working research Recipe uses reviewed literal audience instructions. For a variable-driven Recipe, inspect the actual rendered/provider input before relying on substitution. Keep secret values out of prompt variables: a prompt value can be sent to the provider and retained in local results or traces. Declaring a variable is not a credential flow.

Continue with source discovery , trust and model variants and evaluation .