How to Use Prompt Argument Substitution with `{{KEY}}` Placeholders in Sandcastle
Sandcastle supports dynamic placeholder substitution using {{KEY}} syntax in prompt files, allowing you to inject runtime values via the --prompt-args CLI option before sending the prompt to the LLM agent.
The prompt argument substitution feature in mattpocock/sandcastle enables you to write reusable prompt templates with variables like {{ISSUE_NUMBER}} that get replaced at runtime. This functionality only works with prompt files referenced via --prompt-file, not with inline prompts passed directly on the command line.
How Prompt Argument Substitution Works
The substitution pipeline follows a strict validation and resolution flow defined across several core modules.
Step 1: Prompt Source Resolution
The system first determines where the prompt originates. In [src/PromptResolver.ts](https://github.com/mattpocock/sandcastle/blob/main/src/PromptResolver.ts), the resolvePrompt function distinguishes between inline prompts (passed via --prompt) and template prompts (loaded from --prompt-file). Only template sources are eligible for argument substitution.
Step 2: Validation Guards
Before substitution occurs, Sandcastle enforces two critical validation rules in [src/PromptArgumentSubstitution.ts](https://github.com/mattpocock/sandcastle/blob/main/src/PromptArgumentSubstitution.ts):
validateNoArgsWithInlinePrompt: Throws aPromptErrorif you supply--prompt-argswhile using an inline prompt, preventing silent failures where substitution would be ignored.validateNoBuiltInArgOverride: Blocks attempts to override reserved keys (SOURCE_BRANCH,TARGET_BRANCH) via user-supplied arguments, as these are injected automatically by the system.
Step 3: Placeholder Detection and Replacement
The substitutePromptArgs function handles the actual text transformation:
- Sanitization: Removes internal shell-block markers from arguments to prevent injection issues.
- Missing Key Detection: Uses
findMissingPromptArgKeyswith thePLACEHOLDER_PATTERNregex to scan for{{KEY}}patterns and verify all placeholders have corresponding values in thepromptArgsobject. Missing keys trigger aPromptError. - Substitution: Replaces each
{{KEY}}occurrence withargs[KEY].toString(). - Unused Key Warnings: Emits CLI warnings for any supplied arguments not present in the template, unless the key is listed in the optional
silentKeysset (used internally for built-in arguments).
Using {{KEY}} Placeholders in Practice
To implement prompt argument substitution, you must store your template in a file and reference it when running Sandcastle.
Creating a Prompt Template
Create a markdown file containing your placeholders:
cat > .sandcastle/prompt.md <<'EOF'
The issue number is {{ISSUE_NUMBER}}.
Please summarize the changes in branch {{SOURCE_BRANCH}}.
EOF
Running with Arguments
Pass values using the --prompt-args flag with KEY=value syntax:
sandcastle run \
--prompt-file .sandcastle/prompt.md \
--prompt-args ISSUE_NUMBER=42
When executed, resolvePrompt identifies the template source, validation passes, and substitutePromptArgs replaces {{ISSUE_NUMBER}} with 42. If {{SOURCE_BRANCH}} isn't provided and isn't automatically injected, the operation fails with a missing key error.
Leveraging Built-in Arguments
Sandcastle automatically injects Git context via built-in keys when running from a worktree:
SOURCE_BRANCH: The current Git branchTARGET_BRANCH: The target branch for comparison
These require no --prompt-args entries:
sandcastle run \
--prompt-file .sandcastle/prompt.md \
--prompt-args ISSUE_NUMBER=108
If the current branch is feature/login and the target is main, the final prompt becomes:
The issue number is 108.
Please summarize the changes in branch feature/login.
Handling Validation and Warnings
The substitution system includes strict error handling to ensure template integrity.
Missing Placeholder Values
If a template contains {{KEY}} but the key is absent from promptArgs and not built-in, findMissingPromptArgKeys returns the missing keys and Sandcastle throws a PromptError before contacting the LLM.
Unused Argument Warnings
Supplying extra arguments not present in the template triggers a CLI warning. To suppress this for specific keys (as done internally for built-ins), the substitutePromptArgs function accepts an optional silentKeys Set:
// Internal usage example from the codebase
substitutePromptArgs(
template,
{ ISSUE_NUMBER: "7", UNUSED: "value" },
new Set(["UNUSED"]) // silences warning for UNUSED
);
Shell Block Integration
The preprocessor in [src/PromptPreprocessor.ts](https://github.com/mattpocock/sandcastle/blob/main/src/PromptPreprocessor.ts` interacts with the substitution logic by marking shell blocks (!...`) so the system can distinguish between template placeholders and executable shell commands extracted from the prompt.
Summary
- Prompt argument substitution only works with
--prompt-file, not inline--promptstrings, as enforced byvalidateNoArgsWithInlinePrompt. - Placeholders use the
{{KEY}}syntax scanned by thePLACEHOLDER_PATTERNregex insubstitutePromptArgs. - Built-in keys
SOURCE_BRANCHandTARGET_BRANCHare reserved and injected automatically; attempting to override them via--prompt-argscauses aPromptError. - Missing placeholder values trigger immediate errors, while unused arguments generate warnings unless silenced via the
silentKeysparameter. - The orchestration happens in [
src/run.ts](https://github.com/mattpocock/sandcastle/blob/main/src/run.ts`), which wires CLI options toPromptResolver.tsandPromptArgumentSubstitution.ts.
Frequently Asked Questions
Can I use prompt argument substitution with inline prompts?
No. The validateNoArgsWithInlinePrompt function in src/PromptArgumentSubstitution.ts explicitly throws a PromptError if you supply --prompt-args while using the --prompt flag. Substitution requires a template file source so that the system can scan for {{KEY}} patterns before sending the text to the agent.
Why am I getting an error about SOURCE_BRANCH or TARGET_BRANCH?
These are built-in arguments that Sandcastle injects automatically based on your Git context. The validateNoBuiltInArgOverride function prevents you from setting these via --prompt-args to avoid conflicts. Remove these keys from your --prompt-args and let the system populate them from the repository state.
What happens if I provide extra arguments that don't exist in the template?
Sandcastle emits a warning in the CLI output indicating that certain arguments were unused. This helps catch typos in placeholder names. To suppress these warnings for specific keys (as done internally for built-ins), the substitutePromptArgs function accepts a silentKeys Set parameter that filters warnings for approved keys.
How does Sandcastle handle missing placeholder values?
Before substitution, findMissingPromptArgKeys compares the template's {{KEY}} occurrences against the supplied promptArgs object. If any placeholders lack corresponding values (and aren't built-in keys), Sandcastle throws a PromptError immediately, preventing incomplete prompts from reaching the LLM.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →