# How Shannon's Prompt Manager Handles Variable Substitution for Context: A Technical Deep Dive

> Explore how Shannon's prompt manager handles variable substitution for context. Learn about its pure-function pipeline for template loading, include directives, and interpolation.

- Repository: [KeygraphHQ/shannon](https://github.com/keygraphhq/shannon)
- Tags: deep-dive
- Published: 2026-02-16

---

**Shannon's prompt manager performs variable substitution through a three-stage pure-function pipeline that loads raw templates, expands `@include` directives, and interpolates built-in placeholders plus config-driven rules.**

The **KeygraphHQ/shannon** repository implements a deterministic prompt construction system designed for reproducible AI security testing. Understanding how Shannon's prompt manager handles variable substitution for context is essential for customizing agent behaviors and ensuring consistent prompt generation across distributed testing scenarios.

## The Three-Step Substitution Pipeline

The core engine in [`src/prompts/prompt-manager.ts`](https://github.com/KeygraphHQ/shannon/blob/main/src/prompts/prompt-manager.ts) executes variable substitution through discrete, composable stages. Each stage operates as a pure async function, guaranteeing that identical inputs always produce identical prompt outputs.

### Step 1: Loading the Raw Template

The `loadPrompt` function initiates the pipeline by resolving the appropriate template file and reading its raw contents. The manager selects between regular production templates and pipeline-testing variants based on the agent identifier provided.

```typescript
// From src/prompts/prompt-manager.ts#L21-L28
async function loadPrompt(agentId: string, variables: PromptVariables, config?: DistributedConfig) {
  const templatePath = resolveTemplatePath(agentId);
  let template = await fs.readFile(templatePath, 'utf-8');
  // Pipeline continues...
}

```

### Step 2: Processing @include Directives

Before any variable substitution occurs, the `processIncludes` function scans the template for `@include(<path>)` directives. This mechanism enables modular prompt composition by injecting reusable snippet files from the `prompts/` directory into the main template.

*Source:* `src/prompts/prompt-manager.ts#L6-L24`

This step ensures that shared context blocks—such as common security testing guidelines—are maintained in single locations while being dynamically composed into agent-specific prompts.

### Step 3: Interpolating Variables

The final stage, `interpolateVariables`, performs the actual substitution of placeholders with runtime values. This function handles both static built-in variables and dynamic configuration-driven content injection.

*Source:* `src/prompts/prompt-manager.ts#L27-L55`

## Variable Substitution Mechanics in Detail

The interpolation engine distinguishes between three categories of variable substitution, each handled through specific logic paths in [`src/prompts/prompt-manager.ts`](https://github.com/KeygraphHQ/shannon/blob/main/src/prompts/prompt-manager.ts).

### Built-in Placeholders and MCP Server Assignment

The manager first constructs an `enhancedVariables` object that merges caller-supplied values with automatically derived context. Using the `MCP_AGENT_MAPPING` constant defined in [`src/constants.ts`](https://github.com/KeygraphHQ/shannon/blob/main/src/constants.ts), the system assigns the appropriate **MCP server** endpoint for the specific agent.

*Source:* `src/prompts/prompt-manager.ts#L38-L45`

The three core placeholders are then replaced via string scanning:

- **`{{WEB_URL}}`** — The target application URL for testing
- **`{{REPO_PATH}}`** — Local filesystem path to the repository under test
- **`{{MCP_SERVER}}`** — The resolved MCP server endpoint for agent communication

*Source:* `src/prompts/prompt-manager.ts#L52-L55`

### Config-Driven Rule Injection

When a `DistributedConfig` object is provided via [`src/types/config.ts`](https://github.com/KeygraphHQ/shannon/blob/main/src/types/config.ts), the substitution engine injects dynamic testing rules into the prompt context.

The manager generates a **rules block** containing:

- **Avoid rules**: Security testing constraints that must not be violated
- **Focus rules**: Specific vulnerability classes or endpoints to prioritize

If no rules are defined, the engine inserts a clean "no rules" message to maintain template structure.

*Source:* `src/prompts/prompt-manager.ts#L57-L73`

### Authentication and Login Instructions

For distributed testing scenarios requiring authenticated sessions, the manager constructs login instructions from the `config.authentication` structure. The `buildLoginInstructions` helper transforms the authentication flow into step-by-step textual guidance inserted at the `{{LOGIN_INSTRUCTIONS}}` placeholder.

*Source:* `src/prompts/prompt-manager.ts#L75-L80`

When no configuration is provided, the system inserts default empty rule blocks and clears the login instructions placeholder to prevent template pollution.

*Source:* `src/prompts/prompt-manager.ts#L81-L87`

### Validation and Error Handling

After substitution completes, the engine performs a validation scan to detect any remaining `{{...}}` tokens. Unresolved placeholders trigger warnings through the custom `PentestError` class defined in [`src/error-handling.ts`](https://github.com/KeygraphHQ/shannon/blob/main/src/error-handling.ts), enabling developers to identify missing context data before prompt execution.

*Source:* `src/prompts/prompt-manager.ts#L89-L93`

## Practical Implementation Example

The following example demonstrates how to invoke the prompt manager with both mandatory variables and optional distributed configuration:

```typescript
import { loadPrompt } from './prompts/prompt-manager.js';
import type { DistributedConfig } from './types/config.js';

// Minimal variables required by every prompt
const vars = {
  webUrl: 'https://app.example.com',
  repoPath: '/path/to/repo',
};

// Optional distributed testing configuration
const config: DistributedConfig = {
  avoid: [{ description: 'Do not enumerate admin endpoints' }],
  focus: [{ description: 'Check for XSS in comment fields' }],
  authentication: {
    login_type: 'form',
    login_flow: ['Enter $username', 'Enter $password', 'Submit'],
    credentials: { username: 'alice', password: 's3cr3t' },
  },
};

async function build() {
  // Load a normal prompt (e.g., the "vuln-xss" agent)
  const prompt = await loadPrompt('vuln-xss', vars, config);
  console.log(prompt);
}

build();

```

This implementation:

1. Supplies the mandatory `WEB_URL` and `REPO_PATH` values
2. Provides a `DistributedConfig` containing rule lists and authentication flow
3. Invokes `loadPrompt`, which automatically selects the MCP server for the `vuln-xss` agent using `MCP_AGENT_MAPPING`
4. Receives a fully-rendered prompt with rule sections, login instructions, and all placeholders resolved

## Summary

Shannon's prompt manager handles variable substitution for context through a rigorous three-stage pipeline that ensures deterministic, reproducible prompt generation:

- **Template Loading**: `loadPrompt` resolves and reads the correct template file from the `prompts/` directory
- **Modular Composition**: `processIncludes` expands `@include()` directives to assemble reusable context blocks
- **Variable Interpolation**: `interpolateVariables` replaces built-in placeholders (`{{WEB_URL}}`, `{{REPO_PATH}}`, `{{MCP_SERVER}}`) and injects dynamic content from `DistributedConfig` objects, including testing rules and authentication instructions

The system validates all substitutions to detect unresolved placeholders, preventing incomplete prompts from reaching AI agents.

## Frequently Asked Questions

### What placeholders does Shannon's prompt manager support by default?

Shannon recognizes three built-in placeholders: `{{WEB_URL}}` for the target application URL, `{{REPO_PATH}}` for the local repository filesystem path, and `{{MCP_SERVER}}` for the Model Context Protocol server endpoint. The manager automatically resolves the MCP server value by looking up the agent identifier in the `MCP_AGENT_MAPPING` constant defined in [`src/constants.ts`](https://github.com/KeygraphHQ/shannon/blob/main/src/constants.ts).

### How does the @include directive work in Shannon templates?

The `@include(<path>)` directive enables modular prompt composition by injecting external snippet files into the main template before variable substitution occurs. The `processIncludes` function scans the raw template for these directives, resolves the relative paths against the `prompts/` directory, and recursively inserts the referenced content. This mechanism allows security testing guidelines and common instructions to be maintained in single locations while being reused across multiple agent-specific prompts.

### Can I use Shannon's prompt manager without a DistributedConfig?

Yes, the `loadPrompt` function treats the configuration parameter as optional. When invoked without a `DistributedConfig`, the interpolation engine inserts default empty rule blocks and clears the `{{LOGIN_INSTRUCTIONS}}` placeholder. The manager still performs all built-in placeholder substitutions (`{{WEB_URL}}`, `{{REPO_PATH}}`, `{{MCP_SERVER}}`) and validates the final output for unresolved tokens, ensuring that basic prompt generation functions correctly without distributed testing context.

### What happens if a placeholder remains unresolved after substitution?

After `interpolateVariables` completes, the prompt manager executes a validation scan to detect any remaining `{{...}}` tokens in the final string. If unresolved placeholders are found, the system emits a warning through the custom `PentestError` class defined in [`src/error-handling.ts`](https://github.com/KeygraphHQ/shannon/blob/main/src/error-handling.ts). This early detection mechanism prevents incomplete or malformed prompts from being passed to AI agents, allowing developers to identify missing context data during the build phase rather than at runtime.