Mantis Pipeline Designer (/mantis-pipeline-adapter)
System Goal
Interactive Pipeline Design Consultant. Assists the user in designing and
implementing their own deterministic orchestrator harness for Mantis Skills.
Helps the user apply best practices for reliability, token efficiency, and
custom environment integration.
Command Definition
- Command:
- Description: Interactively guides the design and implementation of custom
deterministic orchestrator harnesses.
Input/Output Contract
- Reads:
workspace/.mantis_state.json
(to track current loop pass).
workspace/.mantis_state.json
fields , ,
and — the per-pass snapshot pin, present only when
the target harness has opted into sync (absent on today's single-snapshot
runs; see Reference Architecture Guideline 5).
- (as the canonical pipeline specification reference).
workspace/findings/*.json
(as the State Store).
workspace/learnings.jsonl
(to understand memory rotation).
- User's interactive configuration input.
- Writes:
- Outputs user-customized orchestrator harness code, configurations, or
architecture documentation.
- Preconditions:
- User initiates interactive design session.
- Idempotency Guarantee:
- As a consulting agent, it advises the user to implement idempotency in their
custom harness using three primary mechanisms: (1) state store
synchronization, (2) atomic transactional file/VCS operations, and (3)
proper locks (e.g. database/file level locks).
Instructions
Interactively guide the user in designing and building a deterministic pipeline
that wraps Mantis Skills.
Follow these guidelines during the consultation:
- Understand User Context: Ask about their target programming language,
agent framework (if any), execution environments (VMs, local containers,
physical hardware), and scale requirements.
- Recommend Core Principles: Guide them to implement the reference
architecture patterns (detailed below), specifically emphasizing:
- Deterministic Orchestration: Use code (not LLM) for control flow.
- State Store: Use a database or structured filesystem as the single
source of truth.
- Token Efficiency: Use the UUID-based referencing pattern to avoid LLM
text duplication.
- Custom Environment Integration: Use Custom MCP servers for isolated
testing (VMs) or hardware interaction.
- Ensure Schema Consistency: Advise the user to strictly adhere to the
inter-stage data contracts defined in schema.json when
building their harness.
- Adaptive Design: Help them draft the code/architecture tailored to their
specific stack, rather than imposing a rigid template.
- Advise on Scale and Concurrency: If they have high-scale needs, guide
them on decomposing the pipeline and implementing locking mechanisms to
prevent race conditions.
- Suggest Evaluations: Remind them to perform empirical evaluations when
choosing cheaper models for utility stages.
- Advise the Pass Lifecycle Contract (living / synced codebases): If the
user wants their harness to continue a run after the target code changes,
or to sync the target repo at the start of a new pass, walk them through
the harness-agnostic Pass Lifecycle Contract in Reference Architecture
Guideline 5 below. Emphasize that this support is opt-in: a harness that
does not implement the contract MUST leave unset, which
preserves today's single-snapshot behavior byte-for-byte. When is
requested, the harness PINs in the PIN step and passes
/ normally; Block A (Locator Resolution) is
universal across all code-reading stages.
- Advise on Semantic Retrieval at Scale: If the user is targeting a large
codebase (e.g., thousands of source files, multi-pass campaigns, or multiple
teams contributing findings), walk them through the optional semantic
retrieval patterns in Reference Architecture Guidelines 6 and 7 below.
Emphasize that these are opt-in: they augment the pipeline via a
dedicated query skill or MCP tools, but never modify the existing skills'
own deterministic logic or fail-safe invariants.
- Advise on SAST Seeding: If the user wants to augment LLM-based discovery
with external SAST tool findings (CodeQL, Semgrep, etc.), walk them through
the optional SAST seeding pattern in Reference Architecture Guideline 8
below. Emphasize that this is opt-in: it ingests external findings as
candidates that must earn their verdict through unchanged downstream gates,
and it follows exactly the RAG pattern (provenance-tracked, snapshot-aware,
fallback on failure).
- Advise on Structural Code Indexing: If the user is targeting a large
codebase where grep-based call-site discovery is unreliable, walk them
through the optional structural code index stage in Reference Architecture
Guideline 9 below. Emphasize that this is an optional first-class stage:
it provides structural context (function boundaries, call graphs) to improve
LLM reasoning, runs after the snapshot is pinned and before the first
code-reading analysis stage, and degrades gracefully to grep when
unavailable.
- Advise on Tiered Iterative Reproduction & Multi-Conversation Retries: If
the user is targeting complex services where single-shot repro is brittle,
walk them through the tiered iterative reproduction strategy and
multi-conversation retry pattern in Reference Architecture Guideline 10.
Reference Architecture Guidelines
Use the following guidelines as your technical reference when advising the user.
Core Principles
- Deterministic Orchestration: Do not let the LLM decide the control flow
of the pipeline. Use a programmatic harness to call skills sequentially or in
parallel.
- State on Disk / Database: Use the filesystem
(
workspace/findings/*.json
) or a database as the single source of truth.
Skills should read from and write to this store. For horizontal scaling,
recommend a centralized database.
- Deterministic Reporting: Treat findings as internal state. Minimize the
use of the LLM to convert JSON findings into Markdown reports for human
consumption; instead, write deterministic scripts to render the JSON into
reports or upload them to bug trackers. Only use an LLM for non-deterministic
subsets of this (like textual synthesis), such as by providing an executive
summary if necessary.
- Token Efficiency & Reusable Deterministic Tools: Structure LLM outputs to
return only the minimum necessary information (e.g., UUIDs, status codes).
Do not force the LLM to write one-off scripts (e.g., Python or bash) on the
fly for routine tasks like appending JSON fields or merging findings, as this
wastes reasoning tokens. Instead, the harness should provide reusable,
deterministic tools (such as pre-written helper scripts or MCP endpoints)
that the LLM can simply invoke to perform text manipulation and state
updates.
- State Store & Memory Rotation: To prevent token bloat and infinite loops,
ephemeral queues (like
workspace/learnings.jsonl
) must be rotated. Upon
successful completion and verification of the Knowledge Base synthesis stage,
the orchestrator should ensure the archive directory exists (e.g.,
mkdir -p workspace/archive/learnings/
) and move
workspace/learnings.jsonl
to a numbered archive (e.g.,
workspace/archive/learnings/learnings_pass_${N}_${X}.jsonl
where is
the loop pass and is a sub-index). If the synthesis fails, the active
queue must be left intact to prevent data loss.
Architectural Overview
mermaid
graph TD
Harness[Programmatic Harness / Orchestrator] <--> DB[(State Store: Disk/DB)]
subgraph Stages [Decomposed Stages]
KB[KB Architect]
TM[Threat Modeler]
P[Plan]
R[Researcher]
D[Deduplicator]
V[Validator/Review]
C[Critic]
Rep[Reproducer]
Ch[Chainer]
Pat[Patcher]
Cal[Calibrator]
Ref[Reflector]
end
Harness --> KB
Harness --> TM
Harness --> P
Harness --> R
Harness --> D
Harness --> V
Harness --> C
Harness --> Rep
Harness --> Ch
Harness --> Pat
Pat -.->|Re-attack Bypass Loop| Rep
Harness --> Cal
Harness --> Ref
subgraph LLM Pool [Tailored LLMs]
ModelA[Frontier Model: Deep Reasoning]
ModelB[Flash/Lite Model: Fast & Cheap]
ModelC[Alternative Provider: Diversified Logic]
end
KB -.-> ModelA
TM -.-> ModelB
P -.-> ModelB
R -.-> ModelA
R -.-> ModelC
D -.-> ModelB
V -.-> ModelB
C -.-> ModelA
Rep -.-> ModelA
Ch -.-> ModelA
Pat -.-> ModelA
Cal -.-> ModelB
Ref -.-> ModelB
1. UUID-Based Referencing Pattern
To prevent the LLM from repeating large blocks of text (which increases latency,
cost, and the risk of mangling data), use UUIDs as the primary key for all
findings.
A. Researcher Stage
- Action: Sweeps the codebase and identifies potential vulnerabilities.
- LLM Output: Generates a unique UUID for each finding and writes
workspace/findings/<UUID>.json
containing the full details (matching the
standard schema in Mantis Researcher).
B. Deduplication Stage (Optimized)
Instead of asking the LLM to read all findings, merge them in context, and write
them back, use the following pattern:
-
Harness Action: Reads all
workspace/findings/*.json
files and prepares
a summary list for the LLM containing only key identifiers. To align with the
standard schema, map the
array (which uses
format)
to a simplified summary for the LLM:
[ { "id": "UUID", "file": "path", "line": 12, "snippet": "..." } ]
.
-
LLM Action: Analyzes the summary and outputs a mapping of duplicates:
json
{
"primary_uuid_1": ["duplicate_uuid_a", "duplicate_uuid_b"],
"primary_uuid_2": []
}
-
Harness Action (Deterministic):
- Reads the content of the affected files.
- Programmatically merges fields following the rules in
Mantis Deduplicator (e.g., union of
, taking highest severity, concatenating history).
- Updates
workspace/findings/primary_uuid_1.json
on disk.
- Ensures the trash directory exists (e.g.,
mkdir -p workspace/findings/.trash/
).
- Moves
workspace/findings/duplicate_uuid_a.json
and
workspace/findings/duplicate_uuid_b.json
to the trash staging directory
(workspace/findings/.trash/
).
C. Validation & Review Stages (Reviewer, Critic)
- Harness Action: For each finding
workspace/findings/<UUID>.json
, pass
only the relevant code context and finding description to the LLM.
- LLM Action: Output only a structured verification result (e.g.,
{"valid": true, "reason": "..."}
).
- Harness Action (Deterministic): Programmatically update the
workspace/findings/<UUID>.json
file with the validation status and reason.
2. Adaptable Reproducers via Custom MCP
When validating findings, the agent may need to interact with diverse
environments (VMs, physical hardware). Use the Model Context Protocol (MCP)
to expose a clean, restricted API.
- Architecture:
[Reproducer Agent] <--- MCP ---> [Custom MCP Server] <--- API ---> [Target Env]
- Custom Environments:
- VMs: Implement tools like , .
- Hardware/USB: Implement tools like (via smart
plug), .
- Integration Note: If the user's harness uses raw LLM APIs (e.g., direct
Gemini API calls) instead of an MCP-native client framework, the harness must
manually register these tools in the API's schema format and handle
dispatching tool calls to the MCP server.
3. Decomposition & Multi-Model Strategy
A. Pipeline Decomposition & Concurrency
The pipeline can be split into independent services. When scaling horizontally
(e.g., multiple workers running the
stage in parallel):
- Concurrency Control: Implement database or file locking to ensure two
workers do not attempt to process or update the same finding simultaneously.
- Parallel Trajectory Search: For deep reasoning stages (,
), spawn multiple parallel agents attempting to solve the exact same
finding using diverse logic paths. For the stage, prune all other
trajectories as soon as one worker succeeds to save compute costs while
escaping LLM "give up" loops. For the stage, wait for all patches to
be generated and tested, then evaluate the successful ones to select the most
minimal, idiomatic, and correct fix.
B. Heterogeneous LLM Selection (Multi-Model)
Match task complexity with the appropriate model tier:
- Frontier Models: For deep reasoning (Research, Reproduce, Patch).
- Flash/Lite Models: For structured utility tasks (Dedupe, Calibrate).
- Variability: Run different models in parallel during the Research stage to
increase bug-hunting coverage.
C. Importance of Evaluation
Emphasize that using cheaper models for utility stages (like deduplication or
calibration) must be validated with empirical evaluations against a benchmark
dataset to ensure quality is not degraded.
4. The Planning Stage and workspace/plan.json
The planning stage plays a critical role in structuring the security campaign.
The strategist (
) generates
to define
targeted investigations, context pointers, and specific questions for the
auditor. The researcher (
) reads
at
startup to guide its sweep. By decoupling strategy and execution via this
structured contract, the orchestrator can easily direct subagents, parallelize
sweeps, and maintain historical context across pipeline runs without repeating
work.
5. The Pass Lifecycle Contract (Living / Synced Codebases)
A custom orchestrator (a bespoke CLI, an ADK agent, an MCP-native pipeline, or
any deterministic harness) does
not inherit the living-project lifecycle
that
implements. To support
continue-after-edits and
opt-in boundary sync without producing silent wrong results (false
, false
, dropped regressions), the
harness must implement the following harness-agnostic contract. This is the same
contract recorded in
schema.json under
Non-JSON Contracts;
the
–
and
references below name mechanisms each
Mantis stage already carries in its own
.
Mantis runs under multiple harnesses (various CLIs, ADK, custom deterministic
pipelines), so the lifecycle must not live only in
. Any
harness is
conformant iff, per pass, it:
- SYNCs first (Block C) — the very first action; never mid-pass.
- Detects + computes (Block D steps 1-5) — only
after sync.
- PINs the immutable copy + writes the sentinel + appends
(Block D step 5, not RECORD).
- Records (incl. ) + . Never
record an id or pin before syncing.
- Runs every stage with
--snapshot_root=<SNAPSHOT_ROOT> --snapshot_id=<SNAPSHOT_ID> --state_root=<workspace parent>
.
- Archives & increments (existing Stage 15); retried findings keep their
original .
A harness that does not implement the contract MUST leave
unset → today's behavior. When
is requested, the harness PINs in the
PIN step and passes
/
normally; Block A
(Locator Resolution) is universal across all code-reading stages.
Advisory notes when helping a builder implement this contract
- Opt-in, default off. Sync/pinning is a feature the builder turns on. A
harness that never sets behaves exactly like today (one live
snapshot per run). Downstream stages treat an absent
/ as the conservative branch, so an
un-upgraded harness is always safe — just not living-project-aware. Do not
advise treating these absent fields as an error.
- Store snapshots OUTSIDE . The pinned copy ()
must live under
<state_root>/.mantis_snapshots/pass_<N>
(or a clean-VCS
worktree/archive), and its path must not contain the segment
— otherwise 's state-vs-code path guard misfires. Keep the last
2 snapshots and garbage-collect older ones with the matching teardown
( for copies, git worktree remove/prune
for worktrees).
- Non-destructive sync only. Sync is the first action of a pass,
never mid-pass, and must be skipped when the tree is dirty, ahead of
upstream, detached, or has no upstream. The harness must never run
, , , or , or
any command that discards uncommitted/untracked/local-commit state — user
edits and in-progress work must survive every pass.
- Full-fidelity s, including dirty / no-VCS. Compute the id
over the whole pinned copy: clean git/hg → ; dirty git/hg →
commit_hash + ":" + content_hash
; multi-vcs →
revision + ":" + content_hash
; no-VCS / unknown copyable tree →
"content:" + content_hash
. The embedded content hash is exactly what lets an
unchanged dirty or no-VCS tree MATCH across passes and still receive
verification + dedup — and what makes a that advances commits
under an unchanged manifest compare unequal. Never trust a bare
branch name or manifest revision string as an identity.
- Pass the three roots to EVERY stage. Include the findings-only stages
(report, calibrate, reflect): they do not read target code, but they still
read for provenance/annotation. When the harness archives
and increments, retried findings must keep their original
.
Conformance scenarios
The scenarios below expose nearly every issue in the snapshot model. They are
reference checks, not features: the harness is responsible for preventing or
handling each one in its own environment. The table is a quick-reference; prose
detail follows for each scenario. The
State column uses the 3-STATE RULE
(MODE-OFF / HALT / PINNED, branched on
presence — see the
global backward-compat rule in
schema.json and the advisory
notes above);
formats follow the ladder in the advisory notes
above (e.g.
signals an unpinned/HALT pass).
Invariant legend (the labels below name safety properties enforced by the
blocks and the global backward-compat rule in schema.json):
| Label | Property | Enforced by |
|---|
| INV-1 | No false | Block G + HALT ceiling |
| INV-2 | No false | Block F + HALT ceiling |
| INV-3 | No dropped regression | Block B NOT_MATCHED + POSSIBLE REGRESSION |
| INV-4 | Within-pass consistency | Block A sentinel + single pinned snapshot |
| INV-5 | No user data loss | Block C non-destructive sync + Block A step 4 |
| INV-6 | Fail-safe on missing data | Global backward-compat rule |
Quick-reference table:
| # | Scenario | State | Harness behavior | Stage behavior | Block / INV | Key fields |
|---|
| 1 | Colocated state | PINNED | HALT-and-yield (safe default), or relocate outside when explicitly authorized (e.g. ); path must not contain | state-vs-code guard misfires; Block A step 3 confuses SNAPSHOT- vs STATE-relative paths | A:3, D:3; INV-5 | , , |
| 2 | Stale active_snapshot (active_snapshot.pass != state.pass_number
) | PINNED → STOP or HALT-degrade | Block D step 0: handles same-pass re-entry only; if dir missing → STOP, yield to user | Block A step 2 sentinel may still MATCH (dir retained); CURRENT-PASS CHECK (active_snapshot.pass == state.pass_number
) required: mismatch → STOP or HALT-degrade (Block B NOT_MATCHED, no authoritative verdicts) | A:2, D:0, B; INV-1, INV-3, INV-4, INV-6 | active_snapshot.{root, snapshot_id, snapshot_pinned, pass}
, , |
| 3 | Pin failure | HALT | Block D step 2/4: skip copy on ENOSPC/error → step 5b; still write + pass roots | Authoritative verdicts forbidden; Block B always NOT_MATCHED; reproduce ; patch | D:2, D:4, D:5b; INV-1, INV-2, INV-6 | active_snapshot.{snapshot_id, snapshot_pinned}
|
| 4 | Patched shadows | PINNED (pass); arg | Pass --target_root=<PATCHED_SHADOW_ROOT>
+ to reattack sub-agent | Block A step 1a: (authoritative); step 2 sentinel SKIPPED (sentinel-EXEMPT) | A:1a, A:2; INV-4 | , (arg), , |
| 5 | Different-snapshot duplicate candidates | PINNED | No special action — both passes pinned correctly; dedupe handles it | Block B pairwise: differs → NOT_MATCHED → keep ACTIVE + ; POSSIBLE REGRESSION if archived was RESOLVED | B; INV-3, INV-6 | , , , |
| 6 | Absent sink evidence | Any | No special action — Block F is a stage-level mechanical gate | Block F: evidence absent (build error, exit 127, sink unreached) → (retry-eligible), NEVER ; HALT ceiling additionally forces | F; INV-2, INV-6 | , , |
Per-scenario detail:
1. Colocated state (
nested inside
/ snapshot root)
— The pinned
must live under
<state_root>/.mantis_snapshots/pass_<N>
(or a clean-VCS worktree/archive), and
its path
must not contain the segment
— otherwise
's state-vs-code path guard misfires (state files appear to be
"under
"). If
itself is inside
, the harness
must HALT-and-yield (safe default) or, when explicitly authorized (e.g.
), relocate it outside the snapshot before pinning. Block
A step 3 distinguishes SNAPSHOT-RELATIVE path fields (read under
)
from STATE-RELATIVE fields (read under
, never prefixed
with
); colocation breaks this separation.
2. Stale active_snapshot (
active_snapshot.pass != state.pass_number
—
was preserved across the Stage 15 pass increment) — Block D
step 0 (crash-resume) handles only the SAME-pass re-entry case
(
active_snapshot.pass == N
→ reuse). It does NOT catch a stale snapshot
carried across the Stage 15 pass increment, because Stage 15 deliberately
preserves
while bumping
(see Stage 15). Two
sub-cases:
(a) The prior snapshot dir is now MISSING: Block D step 0 STOPs and yields to
the user (never re-pin to a possibly-drifted live tree). (b) The prior snapshot
dir still EXISTS (default keep-2 retention) and its sentinel matches the
preserved
active_snapshot.snapshot_id
: Block A step 2 sentinel check SUCCEEDS
(it only compares the sentinel file to
, not to the current pass).
Block B's pairwise
check would MATCH a carried-forward
finding against a new finding stamped with the same stale
,
silently dropping it as
— a false authoritative verdict.
To prevent (b), the HARNESS MUST guarantee that
active_snapshot.pass == state.pass_number
before any consumer stage reads it.
The reference harness (
) satisfies this by re-pinning every
pass (Block D step 0 sees
active_snapshot.pass != N
→ re-pins → refreshes
before any stage runs), so sub-case (b) never fires
there. A custom harness that preserves
across the Stage 15
pass increment WITHOUT re-pinning MUST either (a) re-pin every pass (the
reference behavior), or (b) inject an equivalent pre-stage gate that refreshes
or clears
entirely before invoking
stages. Stages CANNOT self-detect this staleness via Block B (which is
-only, not
-aware): a carried-forward finding and a new
finding stamped with the same stale
will MATCH in Block B despite
the snapshot being stale. The
field is defined in
#/$defs/state/active_snapshot/pass
for exactly this check. The
harness's Block D step 0 reuse check is NOT a substitute: it only fires on
same-pass re-entry. (Stages that read
MAY additionally
self-check defensively — see each stage's Step 0 sentinel check — but the
binding guarantee is on the harness.)
3. Pin failure (snapshot copy fails — disk full, permissions, too-large
tree) — Block D step 2 (free-space precheck): compare
of the live tree
to
free space at
; if it won't fit → skip copy → step 5b. Block
D step 4 (failure-tolerant verify): check copy exit status + sanity check (file
count/size within ~90%); on failure → step 5b (unpinned/HALT). Step 5b:
SNAPSHOT_ROOT=<live root>
,
,
SNAPSHOT_ID="live:"+ISO8601
. The harness still writes
and
still passes
/
to stages so they see the HALT
signal. Every stage then degrades conservatively: authoritative verdicts
forbidden (
,
,
,
,
); Block B always returns NOT_MATCHED; reproduce
records
; patch's best attainable is
.
4. Patched shadows (
pointing at a pre-mutated tree;
sentinel-exempt path 1a in Block A) —
passes
--target_root=<PATCHED_SHADOW_ROOT>
and
to the
reproduce sub-agent for re-attack verification. Block A step 1a:
CODE_ROOT = --target_root
(authoritative override, overrides
and state fallback). Block A step 2: sentinel check SKIPPED (a
tree is deliberately mutated and is sentinel-EXEMPT). The
argument is the sentinel-exemption, NOT a HALT signal
— detect HALT by reading STATE (
active_snapshot.snapshot_id
starts with
, equivalently
active_snapshot.snapshot_pinned
is
in state),
never from the argument passed on this invocation. The finding's
is unaffected — it retains the pass-level
from
when it was discovered; only the
argument is local to
the reattack invocation.
5. Different-snapshot duplicate candidates (cross-pass dedupe where
differs — the pairwise Block B NOT_MATCHED path) — Both
passes pinned correctly; the findings simply come from different snapshots.
Block B pairwise check compares the CURRENT finding's
against the ARCHIVED finding's
(NOT
against the global
). If they differ → NOT_MATCHED. NOT_MATCHED
keeps the current finding ACTIVE and sets
(a soft,
non-terminal hint — the finding is NOT filtered or trashed). If the archived
finding was RESOLVED (
in {
,
} OR
==
OR
==
) AND the pair is NOT_MATCHED → POSSIBLE
REGRESSION: keep ACTIVE, add a history note, never filter (a reverted fix
re-discovered on new code must never be trashed).
6. Absent sink evidence (Block F — PoC compiles but produces no reached-sink
evidence;
vs
) —
Block
F: if EVIDENCE is ABSENT (any compiler/build nonzero exit, exit 127
command-not-found, exit 2 "No such file", or the sink was never reached) →
repro_status = not_attempted
(retry-eligible), STOP. NEVER
. In
mode: leave
UNSET with
a history note "setup_failed" — NEVER
.
is reserved for when the harness PROVABLY reached the vulnerable entrypoint —
i.e. reached-sink evidence, not setup evidence — but the bug did not fire.
Reached-sink evidence must originate INSIDE the invoked path or from
target-produced tracing/backtraces: (a) a PoC script/source harness writes
MANTIS_REACHED_ENTRYPOINT
to a sidecar file at the point just before the sink
call, within its own execution flow (the marker write is part of the invoked
path, not a pre-launch step); OR (b) for binary/firmware/raw-payload targets,
the captured crash backtrace or sanitizer trace (ASan/UBSan/MSan/TSan)
explicitly names the target sink function (target-produced tracing). A marker
written by an external wrapper BEFORE invoking the target is SETUP EVIDENCE ONLY
(proves "launch attempted," not "sink reached") and does NOT by itself justify
— treat it as EVIDENCE ABSENT for the decision gate.
Evidence is recorded in
. In HALT mode, the HALT ceiling
additionally forces
(no
), since a negative
result on an unpinned tree cannot be trusted as authoritative.
6. Semantic Retrieval (RAG) for Large Codebases
For small repositories, the planner can manually scan
and the researcher can grep for call-sites. At scale (thousands of files, deep
directory trees, multi-pass campaigns), these approaches miss relevant context
and waste tokens reading irrelevant files. A semantic retrieval layer lets the
planner and researcher query for relevant KB entries and code locations without
reading everything.
Two implementations are supported, sharing the same data contract:
- Option A (Default — Skill-Based): A dedicated skill that runs a
BM25/TF-IDF helper script over . Zero external dependencies —
works air-gapped, no vector embeddings or vector store required. Optional
vector embedding support if available.
- Option B (Maximum Scale — MCP-Based): The harness owns a persistent vector
index using vector embeddings, serving persistent /
MCP tools. Better for very large codebases where
per-invocation BM25 is too slow.
Both are opt-in. The existing skills are not modified; the planner and
researcher receive runtime instructions to use whichever retrieval mechanism is
available, falling back to today's manual behavior if neither is present.
Retrieval results are coverage HINTs only — they decide ordering and
prioritization, never the membership of the audit set. A miss must never cause a
file, call-site, or investigation to be skipped or dropped.
A. Shared Data Contract:
After Stage 2 (
) completes, chunks are extracted into
workspace/kb/chunks.jsonl
(one JSON object per line). The harness can do this
post-hoc by reading
, or the architecture skill can be
instructed to write it during synthesis as a text-only side effect. Two chunk
types are produced:
-
KB chunks from the existing
files:
json
{"id": "auth_module:0", "source_file": "workspace/kb/entities/auth_module.md", "entity_type": "entity", "chunk_text": "The auth module handles..."}
-
Code chunks from
(the pinned snapshot). Each chunk includes
the file path and line range so the researcher can request specific files
from the snapshot:
json
{"id": "src/parser.c:0", "source_file": "src/parser.c", "start_line": 1, "end_line": 80, "chunk_text": "int parse_input(..."}
The first line of
is a provenance header recording the
the chunks were built against:
json
{"_provenance": true, "snapshot_id": "abc123", "kb_snapshot_id": "abc123"}
Before serving queries, check
in the provenance header against the
current
; rebuild if they differ. In MODE-OFF (no
),
is never stamped — skip the index entirely
and let skills fall back to manual scanning. Never build code chunks from the
live tree — they must reflect the pinned copy the skills are reading.
B. Option A: Skill-Based Retrieval (Default — No Infrastructure)
A dedicated skill reads
and writes+runs a helper script (e.g.
workspace/helpers/search_chunks.py
) that performs BM25/TF-IDF similarity
search. The script is generated by the agent at runtime — no code is shipped
with the skill (same pattern as
's
). This
requires zero external dependencies — no embedding model, no vector store, no
MCP server. It works in air-gapped and VPC-SC environments.
A complete reference blueprint for this skill is available at
references/mantis-kb-query.md. Builders can
adapt it to their environment. The blueprint includes Block A (Locator
Resolution), chunk provenance checking, the versioned helper script contract
(
MANTIS_HELPER_VERSION = 1
), and the JSON output schema.
- Invocation: The planner or researcher spawns the skill as a sub-agent with
a query string. The skill writes the helper if not already present, runs it,
and returns top-K matching chunks as JSON.
- Optional embeddings: If vector embeddings are available, the agent can be
instructed to use cosine similarity instead of BM25. This is a runtime
configuration toggle, not a different skill.
- Snapshot safety: The skill reads from state via Block A
(same as every other skill) and checks chunk provenance before serving.
C. Option B: MCP-Based Retrieval (For Maximum Scale)
For very large codebases where per-invocation BM25 is too slow, the harness can
own a persistent vector index using vector embeddings, serving two MCP tools
(following the same pattern as Guideline 2's Custom MCP for VMs/hardware):
-
semantic_search_kb(query: string) → [{id, source_file, entity_type, chunk_text, score}]
— Searches KB chunks. Returns relevant entity/vulnerability markdown context.
-
semantic_search_code(query: string) → [{file, start_line, end_line, snippet, score}]
— Searches code chunks from the pinned snapshot. Returns relevant code
locations.
The harness manages the vector index lifecycle: build from
(or
directly from
), rebuild when
changes, and handle
freshness checks. In HALT mode, serve with a
flag or refuse. In
MODE-OFF, skip entirely.
D. Per-Skill Augmentation Guidance
When a retrieval mechanism (skill or MCP) is available, instruct the following
skills to use it. These are runtime instructions passed by the harness or
meta-agent when invoking the skill — the skill files themselves are not
modified:
-
mantis-architecture: No changes needed. The harness chunks the existing
files after the architect completes Stage 2. If the
builder prefers, they may instruct the architect to also write
workspace/kb/chunks.jsonl
during synthesis (Step 3) as a text-only side
effect — but this is optional, since the harness can extract chunks post-hoc.
-
mantis-plan: If a retrieval mechanism is available, instruct the planner
to use it to discover
for each investigation instead of only
manually scanning
. For each investigation, query with
the investigation title and target file names, then add the top-K matching KB
entity/vulnerability files to the
array. Manual scanning of
remains the fallback when no mechanism is available.
-
mantis-researcher: If a retrieval mechanism is available, instruct Wave 1
sub-agents to use it to PRIORITIZE relevant call-sites and cross-module data
flows into sinks (e.g., "where does untrusted input reach
in the
parser module"). Semantic search SUPPLEMENTS grep as a ranking HINT ONLY — it
decides ORDER, never MEMBERSHIP of the audit set. It MUST NEVER replace the
exhaustive Step-3 call-site sweep; every call-site or data-flow that a full
grep would reach must still be audited whether or not it ranks in top-K. Audit
the union of grep results and semantic search results. The researcher's
existing Wave 1/Wave 2 structure is unchanged.
E. Snapshot Safety
The retrieval index — whether served by the skill or the harness — is a cache
of the pinned snapshot, never a live view:
- Build code chunks from (the pinned snapshot), not the live tree.
- Rebuild when changes (new pass, new pin).
- In HALT mode (), serve results with a flag or
refuse to serve — same conservative degradation as every other stage.
- In MODE-OFF, skip the index entirely.
7. Embedding-Based Deduplication Pre-Filtering
When the pipeline runs many passes over a large codebase, the deduplicator
(
) must compare each current finding against every archived
finding — an O(n×m) comparison performed by an LLM reading summaries. At scale
(hundreds of findings across many passes), this is token-expensive and slow.
The harness can use embeddings as a
fast pre-filter to reduce the candidate
space before invoking
. The skill's existing deterministic
matching (
+
+
) remains the
sole
authority for hard dedup decisions.
This is entirely harness-side and opt-in. is not modified.
A. How It Works
-
Harness Action: Reads all
workspace/findings/*.json
(current) and
workspace/archive/findings_pass_*/*.json
(archived). For each finding,
computes an embedding from a normalized text representation (e.g.,
title + description + first code_paths entry with line stripped
).
-
Harness Action: Computes pairwise cosine similarity between current and
archived findings. Surfaces candidate pairs above a configurable threshold
(e.g., 0.85).
-
Harness Action: Writes a candidate-pairs manifest (e.g.,
workspace/helpers/dedup_candidates.json
) containing the UUID pairs and
similarity scores.
-
mantis-dedupe invocation: The harness invokes
as usual.
If the candidate manifest exists, instruct the skill to read it and
prioritize those pairs for the LLM's pairwise comparison, instead of
comparing every finding against every archived finding.
All existing
deterministic matching rules apply unchanged — the manifest only narrows
the search space.
B. Safety Guardrails
-
Pre-filter only, never the decision. The embedding similarity score can
never cause a
verdict, a trash move, or a
assignment. Only the skill's existing
,
, and
checks can do that. A miss in the pre-filter only
over-retains a duplicate (safe — the LLM sees it and skips it); it never
under-retains (never drops a real duplicate).
-
No false negatives. The threshold should be set low enough (e.g., 0.75) to
avoid missing true duplicates. Better to surface too many candidates than to
miss a real one — the LLM and deterministic matching will filter false
positives.
-
Fallback on failure. If the embedding computation is unavailable, the
candidate manifest is absent, or any error occurs,
falls back
to its existing O(n×m) comparison. The skill must not stop or error if the
manifest is missing.
-
Snapshot awareness. The harness should not compute embeddings across
different
values without flagging them as cross-snapshot
candidates — the skill's Block B pairwise check will handle the final
MATCHED/NOT_MATCHED decision.
C. Shared Infrastructure
If the builder also implements Guideline 6 (Semantic Retrieval), reuse the same
vector embedding infrastructure for finding embeddings. The finding embedding is
a different payload (finding JSON, not KB chunks) but the same embedding
capability can serve both.
8. SAST Seeding (External Tool Ingestion)
Mantis's discovery engine is 100% LLM-generative (grep swarm + reasoning). A
weak LLM can structurally under-detect whole-program taint classes (injection,
path traversal, deserialization, UAF, format-string) that mature SAST tools
(CodeQL, Semgrep-taint) encode as interprocedural queries. A SAST seeding
adapter ingests external tool findings as
/
candidates that must earn their verdict through the unchanged
downstream gates. This is purely additive (INV-2-strengthening) — it expands
detection breadth without weakening any verification gate.
This follows exactly the RAG pattern from Guideline 6: opt-in, default off,
provenance-tracked, snapshot-aware, fallback on failure.
Platform-agnostic IR (not SARIF): Rather than tying the adapter to SARIF (a
complex, tool-specific format), the adapter consumes a minimal JSONL
intermediate representation (IR). Any SAST tool's output (SARIF, Semgrep JSON,
Bandit JSON, etc.) is converted to this IR by a thin wrapper. This maximizes
platform agnosticism — the adapter works with any tool that can produce the
simple JSONL format.
Two implementations are supported, sharing the same data contract:
- Option A (Default — Skill-Based): A dedicated skill ()
reads the IR and uses LLM reasoning to normalize findings into mantis finding
JSONs. The LLM reads actual source code at each reported location under
CODE_ROOT to verify the finding and enrich the description with root-cause
analysis. Zero external dependencies — works air-gapped.
- Option B (Maximum Control — Harness-Based): The harness directly
normalizes SAST output into finding JSONs using deterministic code (e.g., a
SARIF-to-finding converter script), bypassing the LLM for the normalization
step. The harness writes finding JSONs to before
invoking .
Both are
opt-in. The existing skills are not modified. Seeded findings enter
alongside researcher findings and flow through the
unchanged downstream gates (dedupe -> review -> critic -> reproduce -> patch ->
calibrate).
A complete reference blueprint is available at
references/mantis-sast-seed.md.
A. Shared Data Contract:
The IR is a JSONL file at
workspace/sast_findings.jsonl
(STATE-RELATIVE). It
follows the same provenance-header pattern as
(Guideline 6A).
Line 1 — Provenance header:
json
{"_provenance": true, "scan_snapshot_id": "abc123def456", "tool": "codeql", "tool_version": "2.15.0", "scan_timestamp": "2026-07-22T10:00:00Z"}
- : The SNAPSHOT_ID the scan was run against. This is the
primary provenance anchor — compared byte-for-byte to the current pass
(same comparison as Block B). If the harness ran the SAST tool
against the pinned CODE_ROOT, it sets this to .
- : Tool name (e.g., , , ). Informational.
- : Tool version. Informational.
- : ISO 8601. Informational.
Lines 2+ — One finding per line:
json
{"rule_id": "cpp/sql-injection", "rule_name": "SQL injection", "cwe": "CWE-89", "severity": "HIGH", "code_paths": ["src/db/query.c:42"], "message": "User input flows into SQL query without sanitization"}
| Field | Type | Required | Description |
|---|
| string | Yes | Tool-specific rule identifier |
| string | Yes | , , , (Mantis scale) |
| array | Yes | Array of strings (SNAPSHOT-RELATIVE) |
| string | Yes | Original SAST finding message |
| string | No | Human-readable rule name |
| string | No | CWE identifier (e.g., ) |
IR conversion (harness responsibility): The harness converts SAST tool
output to this IR before invoking the adapter. Conversion examples:
- SARIF: extract , map to severity (->,
->, ->), extract to , copy
.
- Semgrep JSON: extract as , map to Mantis
severity, extract as , copy .
B. Option A: Skill-Based Ingestion (Default — No Infrastructure)
A dedicated skill reads
, applies allow-listing, verifies
provenance, computes
/
/
, and writes
finding JSONs. The LLM reads actual source code at each reported location under
CODE_ROOT to verify the finding and enrich the description.
- Invocation: The harness invokes with
// after Stage 6 (Research) and
before Stage 7 (Dedupe). The seeded findings land in
before runs.
- Inert until wired: If is absent, the skill outputs
nothing and notifies the caller. It never fails — it simply returns empty.
- Anti-hallucination: The LLM MUST read the actual code at each reported
location before writing the finding JSON. It MUST NOT invent findings not
present in the SAST output. The
sast_provenance.line_verified
field records
whether verification succeeded.
- Snapshot safety: The skill reads from state via Block A
and stamps only when the snapshot is pinned and the
finding's location is verified under CODE_ROOT.
C. Option B: Harness-Based Ingestion (For Deterministic Ingestion)
For maximum determinism, the harness can normalize SAST output into finding
JSONs using deterministic code. This bypasses the LLM for the normalization
step:
- Harness Action: Reads the SAST tool output, parses it deterministically,
and writes finding JSONs to .
- Harness Action: Stamps only if the scan provably ran
against the pinned CODE_ROOT.
- Harness Action: Applies allow-listing filters.
- mantis-dedupe invocation: Proceeds as usual — the seeded findings are
indistinguishable from researcher findings.
D. Provenance Verification
The adapter stamps
ONLY if the scan provably ran against a
line-identical pinned CODE_ROOT. Verification ladder:
- Read from
workspace/.mantis_state.json
(via Block A). If
absent, this is MODE-OFF — skip to step 5.
- If is false, this is HALT — skip to step 6.
- Read the IR provenance header's .
- VERIFIED: present AND exactly equals .
Stamp
discovery_commit = SNAPSHOT_ID
. Status = .
- MODE-OFF: absent. OMIT . Status =
(MODE-OFF permits all verdicts).
- HALT: is false. OMIT . Status =
.
- DRIFT: present but differs. OMIT
entirely. Status = .
- UNVERIFIED: absent. OMIT . Status =
.
Line-existence verification (additional check when VERIFIED or DRIFT and
CODE_ROOT is resolved): For each finding, verify each
entry: strip
trailing
, check file exists under CODE_ROOT, check line number is within
file's line count. If file missing or line out of range -> downgrade to DRIFT
(omit
, status =
).
This is the exact same "UNTRUSTED-IF-ABSENT" pattern as
(schema.json) and
/
.
E. Allow-Listing and Noise Control
workspace/sast_allowlist.json
(optional config file, STATE-RELATIVE):
json
{
"enabled": true,
"severity_filter": ["CRITICAL", "HIGH"],
"cwe_allowlist": {
"enabled": true,
"cwes": ["CWE-89", "CWE-78", "CWE-79", "CWE-22", "CWE-787", "CWE-416", "CWE-502"]
},
"rule_allowlist": {
"enabled": false,
"rules": []
},
"per_rule_cap": 5,
"total_cap": 50
}
If the config file is absent, defaults apply: CRITICAL+HIGH only,
,
.
How allow-listing protects the retry cap: The reproduce stage
(
) has a hard ceiling of 6 attempts per finding (absolute,
never reset). If 1000 SAST findings are seeded without filtering, the reproduce
stage would need up to 6000 attempts — starving the retry budget. The
allow-listing chain (severity filter -> CWE/rule filters -> per-rule cap ->
total cap) ensures only a bounded, high-signal set of candidates enters the
pipeline. The review (13-rule negative filter) and critic (production viability)
stages further filter before reproduce runs.
F. Per-Skill Augmentation Guidance
When the SAST seed skill is available, instruct the harness to invoke it between
Stage 6 (Research) and Stage 7 (Dedupe). These are runtime instructions passed
by the harness — the skill files themselves are not modified:
- mantis-meta-agent: Invoke with
// after
completes and before .
- mantis-dedupe: No changes needed. Seeded findings are in
alongside researcher findings. Dedupe processes them
identically (signature-based matching, Block B pairwise check).
- mantis-review: No changes needed. The 13-rule negative filter applies to
seeded findings identically.
- mantis-report: No changes needed. The field is
informational and can be displayed in reports.
G. Snapshot Safety
The SAST seed adapter follows Block A (Locator Resolution) exactly like every
other code-reading skill:
- Resolve CODE_ROOT from / state .
- Honor the sentinel check (Block A step 2).
- Read source files under CODE_ROOT (SNAPSHOT-RELATIVE) for line-existence
verification.
- Write findings under
state_root/workspace/findings/
(STATE-RELATIVE).
- Never write under CODE_ROOT when pinned.
- In MODE-OFF: proceed without . All verdicts permitted.
- In HALT: omit . Seeded findings get .
- In PINNED: verify each finding's location under CODE_ROOT, stamp
discovery_commit = SNAPSHOT_ID
if verified.
The SAST tool output itself is NOT snapshot-aware — it may have been produced
against a different tree. The adapter's provenance verification is what bridges
the gap: it re-grounds each finding against the pinned CODE_ROOT before stamping
.
H. Safety Guardrails
- Purely additive. Seeded findings are /
candidates. They must pass through the unchanged downstream
gates: dedupe (Block B), review (13-rule filter), critic (viability),
reproduce (Block F reached-sink evidence + HALT ceiling), patch (Block G
re-attack), calibrate (sanity caps). No gate is weakened.
- No false . Seeded findings start at
— they can never reach without passing through the full
patch + re-attack pipeline.
- No false . Seeded findings that reach reproduce are
subject to the same Block F evidence gate and HALT ceiling.
- No dropped regression. If a SAST-seeded finding matches an archived
finding with a different , Block B returns NOT_MATCHED ->
-> finding stays active.
- Fail-safe on missing data. If is absent, no findings
written. If a finding can't be verified, .
- No existing skills modified. The adapter writes findings to
before runs.
- is informational only. It does not affect any safety-
critical invariant, gate, or verdict. The finding's is the
field that governs Block B snapshot matching.
9. Structural Code Index (AST-Level Context)
For small repositories, the researcher can grep for call-sites and the planner
can infer dependencies. At scale, grep-based call-site discovery is unreliable
(misses indirect calls, cannot distinguish calls from comments/strings, no
function boundary awareness). A structural code index provides AST-level context
(function boundaries, call graphs, symbol tables) to improve LLM reasoning
quality during discovery.
This follows the RAG pattern from Guideline 6: optional, provenance-tracked,
snapshot-aware, fallback on failure. The structural index is a coverage HINT
only — it decides ordering and prioritization, never the membership of the
audit set. A miss must never cause a file, call-site, or investigation to be
skipped or dropped.
The full specification — including the manifest schema, SQLite serving store,
capability-based per-partition backend selection, canonical symbol IDs, query
interface, baseline-plus-delta overlay, deterministic partial coverage, and
safety guardrails — lives in a single source of truth:
→ ../mantis-structural-index/SKILL.md
A thin reference blueprint is at
references/mantis-structural-index.md.
Two implementations are supported, sharing the same query contract:
- Option A (Default — Skill-Based): The skill
generates and runs helper scripts (
build_structural_index.py
and
query_structural_index.py
, both # MANTIS_HELPER_VERSION = 5
) using
capability-based per-partition backend selection, degrading to grep.
- Option B (Maximum Power — MCP-Based): The harness owns a persistent
structural index serving , ,
get_function_boundary(file, line)
MCP tools, backed by the same SQLite
serving store.
Both are optional. A non-conformant harness simply skips the structural
index stage. The structural index supplements grep as a ranking HINT ONLY — it
decides ORDER, never MEMBERSHIP of the audit set.
Consumers MUST use the query interface (
query_structural_index.py
) rather than
filtering JSONL directly. The query interface provides bounded results,
pagination, explicit name resolution, precision/backend metadata, and coverage
on empty results.
D. Per-Skill Augmentation Guidance
When a structural index is available, instruct the following skills to use it.
These are the consumption contract — runtime instructions passed by the harness.
The structural index is a HINT-only enhancement; skills that do not use it
behave exactly as they do today:
- mantis-architecture: Optionally read the pre-built structural index (built
by at Stage 0.5) during KB synthesis,
cross-referencing it with .
- mantis-plan: Use the structural index for function-level dependency
fan-out (more precise than file-level ).
- mantis-researcher: Wave 1 sub-agents use to SUPPLEMENT
grep as a ranking HINT for call-site discovery — it decides ORDER, never
MEMBERSHIP. It MUST NEVER replace the exhaustive Step-3 call-site sweep; every
call-site that a full grep would reach must still be audited whether or not it
ranks in the structural index. Audit the union of grep results and structural
index results. Wave 2 deep auditors use to start
with the enclosing function, expanding to callers/callees/file as needed for
cross-function context. The researcher's existing Wave 1/Wave 2 structure is
unchanged.
E. Integration with RAG (Guideline 6)
- Structural index entries can be added to as
entity_type: "structural"
chunks.
- Code chunks can become function-boundary-aligned (instead of dumb line-range
slices) by using function boundaries from the structural index.
- The structural index and the RAG index can share the same vector embedding
infrastructure if both are implemented.
10. Tiered Iterative Reproduction & Multi-Conversation Retry Strategy
For complex services, attempting a single-shot reproduction directly against a
full sandboxed service often suffers from high search entropy, brittle
configuration, and hard-to-debug failures. A tiered strategy breaks reproduction
into incremental milestones, while an inter-conversation retry architecture
prevents reasoning deadlocks and context bloat.
A. Orchestration & Inter-Conversation Retries
-
Intra-Conversation Retries (Local Agent Trajectory):
- The active subagent conversation retries 2–3 times locally within its
context window to adjust parameters, fix setup bugs, or refine payloads.
-
Inter-Conversation Retries (Fresh Context + Accumulated Artifacts):
- Trigger: If intra-conversation retries fail to reach Tier 3
(), the orchestrator terminates the stalled conversation and
launches a new subagent conversation (a fresh context window).
- Context Provisioning: The orchestrator populates the new prompt with
structured attempt data from
state_root/workspace/archive/.repro_attempts.json
and trajectory
learnings from workspace/learnings.jsonl
(e.g., "Attempt 1 failed due to
missing auth header X; Attempt 2 proved parser strips unescaped quotes").
- Benefit: Eliminates context bloat and reasoning inertia ("hallucination
traps"), enabling a fresh agent to solve the problem using prior empirical
observations without repeating past mistakes.
-
Attempt Cap Accounting & Arithmetic:
- The orchestrator maintains
state_root/workspace/archive/.repro_attempts.json
.
- Tier 3 Increment Only: Only Tier-3 full sandboxed service executions
(or full end-to-end reproducer runs) increment the per-finding attempt
counter toward the absolute hard ceiling of 6.
- Stepping-Stone Sub-Budget: Internal Tier-1 and Tier-2 trial runs are
bounded by a local sub-budget (max 3 trial executions per conversation) and
do not consume the absolute 6-attempt cap.
B. Harness-Enforced Tier 4 (Staging & Live Pre-Production Execution)
To prevent un-gated exploit execution on live infrastructure, Tier 4 (Staging /
Pre-Production verification) is strictly owned and enforced by the
programmatic orchestrator harness, never by LLM discretion:
- Sandbox Boundary: executes strictly within isolated
local sandboxes (Tiers 1–3) ending at Tier 3 ( /
).
- Deterministic Gate: The harness intercepts a Tier 3 verdict.
If live/staging validation (Tier 4) is configured, the harness MUST NOT
automatically invoke remote execution. It must enforce a programmatic
Human-in-the-Loop gate:
- Prompt the human operator for explicit interactive confirmation, OR
- Require a cryptographically signed approval token / authorization callback.
- Fail-Closed Default: If human approval is missing or denied, Tier 4 is
skipped and the Tier 3 sandboxed verdict remains authoritative.
C. Local Ingress / Middlebox Edge Annotation (Post-Tier-3 Sandbox Verification)
To eliminate false-positive findings caused by default edge filters ("Works on
localhost:8080, but dies at the WAF/proxy"), the orchestrator can optionally
execute a Tier 3 PoC through a local reverse proxy or API gateway (e.g. NGINX,
Envoy, ModSecurity) running inside the local sandbox:
- Annotation Only: A payload blocked by a local middlebox MUST NOT downgrade
a Tier 3 verdict to .
- Critic Integration: The harness records (or edge
filter details) in the finding's or history. This provides
empirical evidence for to classify as
(mitigated by default edge proxy configuration).