Create a scan
A scan is one run of a workflow against a target. It bundles the target, the model and harness, and the skills, post-scripts, and rankers you want applied, then the engine schedules it and runs every step.

The form
Open Scans → New scan, then choose one of two starting points:
- Blank scan opens the default form for a new configuration.
- Duplicate an existing scan lets you select a previous scan, copies only its editable inputs, and opens them for review. Results, logs, status, attempts, and timestamps are not copied.
Then fill in or review each section:
Workflow
The blueprint to run. Its steps execute in depth order.
Target
A remote or local repository, an optional
commit_sha, and arepo_scopedescribing what to focus on.Dependencies
Optional dependency repos checked out alongside the target.
Configuration
A JSON configuration blob the agents can read.
Extra
Values for any
extrakeys the workflow requires.Model and harness
The model, provider, harness, and thinking effort to run with.
Skills, post-scripts, rankers
Attach skills, post-scripts, and rankers for this run.
Launch
If another scan is active, choose Start immediately to run concurrently or Queue to wait until all active scans finish.
HTTP API
The UI is the guided creation path. Automation can queue the same scan with
POST /api/scans; ./kritt configures and starts the stack but does not have a scan
subcommand.
Obtain workflow, post-script, skill, and ranker content from their list endpoints first:
curl --fail http://127.0.0.1:3002/api/workflows
curl --fail http://127.0.0.1:3002/api/post-scripts
curl --fail http://127.0.0.1:3002/api/agent-skills
curl --fail http://127.0.0.1:3002/api/severity-rankers
The create body accepts these fields:
| Field | Required | Shape and behavior |
|---|---|---|
workflowId | Yes | Existing workflow ID. |
postScriptId | Yes | Existing primary post-script ID. Put additional IDs in configuration.post_script_ids. |
agentSkillIds | No | Array of existing numeric skill IDs. |
launchPolicy | When another scan is active | immediate starts concurrently; queue waits for all active scans to finish. |
repo_kind | No | remote (default) or local. |
repo_full | Yes | GitHub owner/repo/URL for remote; one immediate folder name under /local_repos for local. |
commit_sha | Remote only | Commit, tag, or branch to check out; defaults to HEAD. Local snapshots ignore it. |
repo_scope | No | Focus description exposed as {{repo_scope}}; defaults to full repository. |
dependencies | No | Array of { "kind", "repo_full", "commit_sha"? } references. |
configuration | No | JSON object exposed as {{configuration}}; repeat_runs and additional post-script IDs also live here. |
model | Yes | Provider model ID or supported alias. |
model_provider | Yes | codex, claude, or openrouter. |
harness | Yes | Supported values are codex and claude-code; it must be compatible with the provider. |
thinking_effort | No | Model/harness-specific: default, low, medium, high, xhigh, max, or ultra; defaults to medium. |
jobLimit | No | Positive whole-number cap on logical workflow and post-processing model jobs. Internal retries do not consume another job. Omit or use null for unlimited. |
severity_ranker | Yes | Non-empty combined Markdown ranking rules, not a ranker ID. |
extra | As required by selected prompts | Object containing every {{extra.<key>}} referenced by the selected workflow or post-scripts. Unknown keys are discarded. |
Provider/harness combinations are codex + codex, claude + claude-code, and
openrouter + claude-code or advanced codex. The API returns 422
with field-specific errors when IDs, required scan extras, local folders, configured
providers, models, or combinations are invalid.
If launchPolicy is omitted while another scan is active, the API returns 409 with
code: "scan_launch_policy_required" and does not create the scan. Resubmit the same body
with immediate or queue.
Remote scan example
Replace the IDs and model with values available in your installation. This example pins the target commit, adds a remote dependency, attaches a skill and two post-scripts, and uses OpenRouter through Claude Code:
curl --fail-with-body \
-X POST http://127.0.0.1:3002/api/scans \
-H 'Content-Type: application/json' \
--data-binary @- <<'JSON'
{
"workflowId": 1,
"postScriptId": 1,
"agentSkillIds": [1],
"repo_kind": "remote",
"repo_full": "octocat/Hello-World",
"commit_sha": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
"repo_scope": "authentication and untrusted input paths",
"dependencies": [
{
"kind": "remote",
"repo_full": "octocat/Spoon-Knife",
"commit_sha": "HEAD"
}
],
"configuration": {
"repeat_runs": 1,
"post_script_ids": [1, 2]
},
"model": "z-ai/glm-5.2",
"model_provider": "openrouter",
"harness": "claude-code",
"thinking_effort": "low",
"severity_ranker": "Rank exploitable, remotely reachable findings first.",
"extra": {}
}
JSON
Local scan example
Create the host folder before submitting. With the default Compose mapping,
./local_repos/qa-smoke-target on the host appears as /local_repos/qa-smoke-target in
the backend and engine. The request contains only the immediate folder name, not either
absolute path:
mkdir -p ./local_repos/qa-smoke-target
# Add the source files to ./local_repos/qa-smoke-target, then:
curl --fail-with-body \
-X POST http://127.0.0.1:3002/api/scans \
-H 'Content-Type: application/json' \
--data-binary @- <<'JSON'
{
"workflowId": 1,
"postScriptId": 1,
"repo_kind": "local",
"repo_full": "qa-smoke-target",
"repo_scope": "full repository",
"dependencies": [],
"configuration": {},
"model": "sonnet",
"model_provider": "claude",
"harness": "claude-code",
"thinking_effort": "medium",
"severity_ranker": "Rank exploitable findings first.",
"extra": {}
}
JSON
Local source contents, including modified and untracked files, are copied into a fixed scan snapshot. Review them for secrets before queueing the request.
Lifecycle
An immediately started scan normally follows:
pending → prewarming_cache (optional) → running → post_processing → completed
A queued scan stays queued until no active scans remain, then moves to running.
prewarming_cache may be skipped when no cache preparation is needed.
A temporary provider limit moves active work to rate_limited. The engine preserves
completed work and returns the scan to running when its automatic retry is due. An
active scan can also move to paused; stopped and failed require an operator to
resume them. Reaching jobLimit also stops a scan after already-started jobs finish;
raise or remove the limit before resuming it.
The following pages break down each part of the form.