Prompt System
Each stage owns its prompts locally — there is no central PromptBuilder or
shared prompt registry. Prompt templates are .txt files living alongside each
stage's stage.py, loaded at runtime from the package data.
Prompt ownership
| Stage | Prompt directory | Template files |
|---|---|---|
| Plan | stages/plan/prompts/ |
default.txt |
| Threat model (codex direct) | stages/threat_model/prompts/ |
codex_direct.txt |
| Threat model (bug class) | stages/threat_model/prompts/ |
bug_class_selection.txt |
| Threat model (default) | stages/threat_model/prompts/ |
default.txt |
| Audit (default) | stages/audit/prompts/ |
default.txt |
| Audit (variant analysis) | stages/audit/prompts/ |
variant_analysis.txt |
| Verify | stages/verify/prompts/ |
default.txt, threat_model_simplify.txt |
| Patch — generate | stages/patch/prompts/ |
generate.txt |
| Patch — minimality review | stages/patch/prompts/ |
minimality_review.txt |
| Patch — vulnerability recheck | stages/patch/prompts/ |
vulnerability_recheck.txt |
| Patch — fallback | stages/patch/prompts/ |
default.txt |
| Plugin (Linux) | plugins/builtin/linux/prompts/ |
plugin-specific |
| Plugin (Web application) | plugins/builtin/web_application/ |
audit_prompt.txt |
Plugin prompt injection
Plugins customize prompts through the build_prompt_sections hook rather than
modifying stage templates directly:
class StagePlugin(Protocol):
def build_prompt_sections(
self,
context: RunContext,
stage: StageName,
input: StageInput,
) -> tuple[PromptSection, ...]: ...
@dataclass(frozen=True)
class PromptSection:
title: str
content: str
metadata: Mapping[str, Any] = field(default_factory=dict)
BaseStage calls this hook before execute(...) and injects the returned
sections into the prompt before the agent call. The concrete stage template
includes placeholder markers where plugin sections are inserted.
Example: Linux plugin sections
LinuxPlugin.build_prompt_sections(...) for StageName.AUDIT returns sections
that include:
- Linux-specific vulnerability classes to focus on (kernel memory safety, privilege escalation paths, syscall surface issues)
- Reproduction guidance for kernel code
- Audit scope constraints
Example: Web application plugin sections
WebApplicationPlugin.build_prompt_sections(...) for StageName.AUDIT
returns sections loaded from audit_prompt.txt covering OWASP Top 10 patterns,
session/auth boundaries, injection sinks, and serialization risks.
Schema enum overrides
Plugins can also narrow or extend the allowed values for structured enum fields in stage output schemas:
class StagePlugin(Protocol):
def output_schema_overrides(
self,
context: RunContext,
stage: StageName,
) -> tuple[SchemaEnumOverride, ...]: ...
@dataclass(frozen=True)
class SchemaEnumOverride:
stage: StageName
field: str # extension point field name
values: tuple[str, ...]
mode: SchemaOverrideMode = "replace" # "replace" | "extend"
prompt_hint: str | None = None # appended to prompt if set
BaseStage calls apply_schema_enum_overrides(base_schema, ...) from
stages/schema_overrides.py to produce the effective output schema for the
agent call. Any prompt_hint strings are injected into the prompt alongside
plugin sections.
Audit extension points
AuditStage exposes these schema extension points:
| Field | Default values | Allowed modes |
|---|---|---|
bug_class |
General security bug classes | replace, extend |
attacker_profile |
Generic attacker profiles | replace, extend |
required_state |
Generic state requirements | replace, extend |
required_services |
Generic service requirements | replace, extend |
LinuxPlugin overrides
Replaces bug_class with kernel-specific values: use_after_free,
double_free, race_condition, integer_overflow, privilege_escalation,
oob_read, oob_write, etc.
WebApplicationPlugin overrides
Replaces bug_class with web-specific values: sql_injection, xss,
csrf, ssrf, broken_auth, insecure_deserialization, etc. Also overrides
attacker_profile and required_services with web-relevant values.
Static threat model (plugin shortcut)
Rather than injecting a prompt, a plugin can bypass the threat-model agent call entirely:
class StagePlugin(Protocol):
def build_static_threat_model(
self,
context: RunContext,
input: StageInput,
source_snapshot: Mapping[str, Any],
plan: Mapping[str, Any],
) -> Mapping[str, Any] | None: ...
If this returns a non-None mapping, BaseThreatModelStage.execute(...) writes
it directly as vega.threat_model without running an agent. DefaultPlugin
and LinuxPlugin return None; WebApplicationPlugin returns a pre-built
static threat model for web targets.
Prompt construction flow
1. BaseStage.run() calls plugin.before_stage(context, stage, input)
→ plugin may augment input (e.g. add data fields)
2. BaseStage calls plugin.output_schema_overrides(context, stage)
→ apply_schema_enum_overrides() produces effective schema
3. Stage.execute() builds prompt:
a. Load stage template text
b. Interpolate source/plan/artifact context
c. Append plugin.build_prompt_sections() results
d. Append schema override prompt_hints
e. Append PR review focus block if metadata["pr_review"] present
4. AgentRunner.run(AgentTask(prompt=..., output_schema=effective_schema)) called
Agent task schema validation
Each stage passes its effective output schema to AgentRunner as
AgentTask.output_schema. The Codex adapter passes this as a --json-schema
flag to the Codex CLI, which enforces structured JSON output. The resulting
agent output is re-validated against the same schema before further parsing.