diff --git a/capabilities/ai-red-teaming/agents/ai-red-teaming-agent.md b/capabilities/ai-red-teaming/agents/ai-red-teaming-agent.md
index 2568444..0421845 100644
--- a/capabilities/ai-red-teaming/agents/ai-red-teaming-agent.md
+++ b/capabilities/ai-red-teaming/agents/ai-red-teaming-agent.md
@@ -58,9 +58,12 @@ Probe the security and safety of AI applications, agents, and foundation models.
---
Then wait for the user's request. Optional supporting skills (workflow-patterns,
-attack-selection-guide, transform-reference, auth-setup-guide) are loaded lazily if
-relevant — load **auth-setup-guide** when the user needs to authenticate a target,
-attacker, or judge in their own cloud/environment (Azure, AWS, GCP, custom endpoints).
+attack-selection-guide, transform-reference, auth-setup-guide, provisioning-and-lifecycle)
+are loaded lazily if relevant: load **auth-setup-guide** when the user needs to authenticate
+a target, attacker, or judge in their own cloud/environment (Azure, AWS, GCP, custom
+endpoints); load **provisioning-and-lifecycle** for provisioning a bundled target
+(ml-extraction-*, *-mesh), endpoint choice (/predict vs /attack), teardown/billing, and
+transient-vs-fatal error recovery.
@@ -140,6 +143,7 @@ Complete requests that don't need clarification:
- Reason about SDK internals.
- Give up after a single failure — retry with adjusted parameters.
- Use a "bash" or "shell" tool — use `execute_workflow` instead.
+- Guess CLI syntax. To provision a target, ALWAYS call the `provision_environment` tool - never run `dreadnode env ...` in a shell. Public bundled targets (e.g. `ml-extraction-fraud-tabular`, `finops-mesh`) resolve by bare name; pass the bare name and do not try to qualify it.
## Tools
@@ -164,7 +168,7 @@ The AI Red Teaming capability provides these tools:
**Multi-Agent Environments:**
- **list_environments** — List the deployable multi-agent environments (e.g. `finops-mesh`, `devsecops-mesh`, `healthcare-mesh`, `soc-mesh`) that ATLAS can target
-- **provision_environment** — Deploy a hosted target and return the endpoint that matches its type. A multi-agent mesh (e.g. `finops-mesh`) returns an `/attack` URL + execute token → chain into `generate_atlas_attack`. A black-box ML classifier (e.g. `ml-extraction-mnist-image`) returns a `/predict` endpoint (plus `/pool`, `/members`, `/nonmembers`) → use `generate_evasion_attack` / `generate_extraction_attack` / `generate_membership_attack` / `generate_inversion_attack` with `api_url=/predict`. **Do not fetch `/attack` on a classifier target - it does not serve it.** The sandbox is recorded and torn down automatically when the assessment completes.
+- **provision_environment** — The only way to provision a target; never shell out to `dreadnode env`. Pass the task by bare name (e.g. `ml-extraction-fraud-tabular`, `finops-mesh`) - public bundled targets resolve by bare name from any workspace. Returns the endpoint that matches the target type: a multi-agent mesh returns an `/attack` URL + execute token → chain into `generate_atlas_attack`; a black-box ML classifier returns a `/predict` endpoint (plus `/pool`, `/members`, `/nonmembers`) → use `generate_evasion_attack` / `generate_extraction_attack` / `generate_membership_attack` / `generate_inversion_attack` with `api_url=/predict`. **Do not fetch `/attack` on a classifier target - it does not serve it.** The sandbox is recorded and torn down automatically when the assessment completes.
- **teardown_environment** — Delete provisioned environment sandboxes to stop billing. Hosted sandboxes bill for their whole lifetime. With no id it reaps every environment provisioned this session; pass an id to reap one. Teardown also runs automatically when `update_assessment_status` marks the assessment complete, so call this only to reap early or after a partial run.
**Workflow Management:**
diff --git a/capabilities/ai-red-teaming/capability.yaml b/capabilities/ai-red-teaming/capability.yaml
index 6a4afba..cfcc080 100644
--- a/capabilities/ai-red-teaming/capability.yaml
+++ b/capabilities/ai-red-teaming/capability.yaml
@@ -1,6 +1,6 @@
schema: 1
name: ai-red-teaming
-version: "1.14.0"
+version: "1.15.0"
description: >
Probe the security and safety of AI applications, agents, and foundation models.
Orchestrates adversarial attack workflows to discover vulnerabilities in LLMs,
diff --git a/capabilities/ai-red-teaming/skills/error-troubleshooting/SKILL.md b/capabilities/ai-red-teaming/skills/error-troubleshooting/SKILL.md
index 3de81bc..b03379f 100644
--- a/capabilities/ai-red-teaming/skills/error-troubleshooting/SKILL.md
+++ b/capabilities/ai-red-teaming/skills/error-troubleshooting/SKILL.md
@@ -115,6 +115,31 @@ Common errors and fixes for AIRT attack workflows.
- **Cause**: All trials errored or timed out
- **Fix**: Check for model/network errors. Reduce complexity (fewer transforms, simpler attack).
+## Provisioning & Sandbox Errors
+
+For the full lifecycle, load the `provisioning-and-lifecycle` skill.
+
+### "Task not found" / 404 provisioning a bundled target
+- **Cause**: A bare task name resolves only within the caller's org, but bundled targets (`ml-extraction-*`, `*-mesh`) live in the public `dreadnode/` catalog.
+- **Fix**: Use `provision_environment` with the BARE name - it catches the 404 and retries as `/` automatically. Never shell out to `dreadnode env`.
+- **Do NOT**: qualify a bundled task with your own org (e.g. `aisf-learner-aug-2026/ml-extraction-imdb-text`) - it is not there and there is no fallback. Use the bare name or `dreadnode/`.
+
+### "404" on GET environments//status
+- **Cause**: The sandbox is already torn down or expired - you are polling a dead environment.
+- **Fix**: Treat it as terminal ("terminated"), stop polling, and do not surface it as an error.
+
+### Endpoint mismatch 404 (/attack vs /predict)
+- **Cause**: Probing the wrong endpoint for the target type - e.g. fetching `/attack` on an ML classifier that only serves `/predict`.
+- **Fix**: Read the `>>> NEXT STEP` line from `provision_environment`. Classifier -> `/predict`; mesh -> `/attack`. Never probe both.
+
+### Sandbox lifecycle & billing (avoid leaks)
+- **Cause**: A hosted sandbox bills for its whole lifetime; forgetting teardown leaks cost until TTL.
+- **Fix**: Finish with `update_assessment_status` (auto-teardown fires on `completed` and `failed`), or call `teardown_environment()` on early abort. Set `AIRT_ENV_TEARDOWN_GRACE_SEC` >= your longest attack timeout so teardown does not kill an in-flight attack.
+
+### Transient vs fatal (Note: vs Error:)
+- **`Note:` prefix** = transient network fault (TLS/timeout/conn-reset/502-504) already auto-retried; it did not affect running or recorded work - just re-run the step, do not report failure.
+- **`Error:` prefix** = a non-fatal input/tool issue - adjust params, do not blind-retry.
+
## Retry Strategy
1. **First failure**: Read the error message, adjust the specific parameter that failed
diff --git a/capabilities/ai-red-teaming/skills/provisioning-and-lifecycle/SKILL.md b/capabilities/ai-red-teaming/skills/provisioning-and-lifecycle/SKILL.md
new file mode 100644
index 0000000..d8c7349
--- /dev/null
+++ b/capabilities/ai-red-teaming/skills/provisioning-and-lifecycle/SKILL.md
@@ -0,0 +1,55 @@
+---
+name: provisioning-and-lifecycle
+description: Provision a hosted target, pick the right endpoint per target type, tear down to stop billing, and interpret transient vs fatal errors. Load this whenever the user wants to provision/attack a bundled target (ml-extraction-*, *-mesh) or asks about environments, sandboxes, teardown, or billing.
+allowed-tools: provision_environment teardown_environment list_environments generate_atlas_attack generate_evasion_attack generate_extraction_attack generate_membership_attack generate_inversion_attack register_assessment update_assessment_status
+---
+
+# Provisioning and Lifecycle
+
+How to provision a hosted target, run against the right endpoint, and tear it down cleanly. The tool layer already handles resolution, endpoint selection, retries, and teardown - your job is to use the tool (never the CLI) and interpret its output correctly.
+
+## 1. Provisioning decision tree
+
+- User names a bundled/hosted task (`finops-mesh`, `ml-extraction-fraud-tabular`, `ml-extraction-mnist-image`, `ml-extraction-imdb-text`) -> call `provision_environment` with the BARE name.
+- User gives an HTTP URL -> skip provisioning; go straight to `generate_agentic_attack` / `generate_atlas_attack`.
+- NEVER run `dreadnode env ...` in a shell, and NEVER guess a `provision`/`create` subcommand. `provision_environment` is the only supported path and it resolves the catalog for you.
+
+## 2. Task-ref resolution
+
+A bare name resolves server-side to a task in your org OR any public task, so bundled public targets (`ml-extraction-*`, `*-mesh`) work by bare name with no extra qualification.
+
+- Prefer the BARE name (`ml-extraction-fraud-tabular`). It resolves to the public catalog automatically.
+- A task owned by another org resolves only when it is public or owned by you. If a bare name 404s, the task is private to another org (or the name/version is wrong) - qualifying it as `/` will NOT help, because the same visibility rule applies.
+- Do NOT qualify a bundled task with the caller's own org (e.g. `aisf-learner-aug-2026/ml-extraction-imdb-text`) - it does not live there.
+- If provisioning fails, the returned message is authoritative: report it. Do not invent qualification syntax or retry random forms.
+
+## 3. Endpoint-per-target map (fixes /attack on a /predict classifier)
+
+Read the `>>> NEXT STEP` line in `provision_environment` output and use exactly that endpoint. Never probe both `/attack` and `/predict` on one target.
+
+- Mesh (`*-mesh`) -> serves `/attack` -> `generate_atlas_attack` with the execute token.
+- Classifier (`ml-extraction-*`, tabular/image/text, mnist/fraud/imdb) -> serves `/predict` (+ `/pool`, `/members`, `/nonmembers`) -> `generate_evasion_attack` / `generate_extraction_attack` / `generate_membership_attack` / `generate_inversion_attack` with `api_url=/predict`.
+
+## 4. Teardown and billing
+
+Every provision bills for its whole lifetime.
+
+- Preferred: wrap the run in `register_assessment` -> attacks -> `update_assessment_status`. The sandbox is auto-torn-down once EVERY planned attack has been recorded, whether it passed or failed.
+- Gotcha: if you abandon a planned attack without recording a status (e.g. you give up after an error), the assessment never reaches terminal and the sandbox is NOT auto-torn-down - it bills until its TTL. Always either record every planned attack or call `teardown_environment()` (no id = reap every environment from this session).
+- Set `AIRT_ENV_TEARDOWN_GRACE_SEC` >= your longest attack timeout before running, so assessment-completion teardown does not kill an in-flight attack.
+
+## 5. Transient vs fatal error interpretation
+
+- A result starting with `Note:` = transient (TLS handshake / timeout / conn reset / 502-504), already auto-retried by the tool, and it did NOT affect any running attack or recorded result. Tell the user it was transient and re-run the step.
+- A result starting with `Error:` = a non-fatal input/tool issue. Adjust params; do not blind-retry.
+- A `404` from probing an endpoint is exploratory, not a failure - say what you learned and switch to the correct endpoint.
+- A `404` on `GET environments//status` means the sandbox is already gone (torn down or expired). Treat it as "terminated" and STOP polling - it is not an error to surface.
+- Never let a raw error be the last thing the user sees about a step that actually succeeded.
+
+## 6. ASR display
+
+ASR is stored 0-1 internally. Never format it yourself - report what the tool returns (it renders `1.0` as `100%`). If you ever read a raw fraction from JSON, multiply by 100 before showing it.
+
+## 7. Attribution
+
+Assessments are attributed to the operator via the platform auth context (org / workspace) and, where available, the `origin_user` on the assessment. If a per-user field is missing on an older assessment, attribute via org/workspace and cross-reference the `assessment_id` in platform audit logs - do not claim a field that is not present.
diff --git a/capabilities/ai-red-teaming/tools/environments.py b/capabilities/ai-red-teaming/tools/environments.py
index 2f63d73..b830fbc 100644
--- a/capabilities/ai-red-teaming/tools/environments.py
+++ b/capabilities/ai-red-teaming/tools/environments.py
@@ -234,7 +234,6 @@ def list_environments() -> str:
return "\n".join(lines)
-@safe_tool
def _target_kind(task_ref: str) -> str:
"""Classify a provisionable target so we return the right endpoint + guidance.
@@ -278,11 +277,29 @@ def provision_environment(
return "Not configured for a platform org/workspace. Run `dreadnode login` first."
model_overrides = {model_role: model} if model else None
- env = TaskEnvironment(
- api, org=org, workspace=workspace, task_ref=task_ref,
- model_overrides=model_overrides, timeout_sec=timeout_sec,
- )
- ctx = _run(env.setup())
+
+ def _mk(ref: str) -> t.Any:
+ return TaskEnvironment(
+ api, org=org, workspace=workspace, task_ref=ref,
+ model_overrides=model_overrides, timeout_sec=timeout_sec,
+ )
+
+ # A bare name resolves to a task in the caller's org or any public task
+ # (server-side visibility rule), so bundled public targets work by bare name.
+ # A task owned by another org resolves only when it is public or owned by the
+ # caller - a private cross-org task 404s the same way whether or not it is
+ # qualified, so there is no client-side retry that helps; just add a hint.
+ env = _mk(task_ref)
+ try:
+ ctx = _run(env.setup())
+ except Exception as exc: # noqa: BLE001 - add a resolution hint on not-found
+ if not _is_not_found(exc):
+ raise
+ raise RuntimeError(
+ f"Task '{task_ref}' not found. A bare name resolves to a task in your org "
+ f"or any public task; a task owned by another org must be public or owned "
+ f"by you. Check the name and version, or qualify it as /."
+ ) from exc
svc = (ctx.get("service_urls") or {}).get("challenge")
url = (svc.get("url") if isinstance(svc, dict) else svc) or ""
token = env._execute_token or "" # noqa: SLF001 - one-shot provision token
@@ -302,7 +319,10 @@ def provision_environment(
)
teardown_note = (
"\n>>> WHEN DONE: this sandbox bills for its whole lifetime - it is torn down "
- "automatically when the assessment completes, or call teardown_environment() now."
+ "automatically once every planned attack is recorded (pass or fail). If you abandon "
+ "the run without recording all attacks, call teardown_environment() now so it does not "
+ "bill until its TTL. For long runs set AIRT_ENV_TEARDOWN_GRACE_SEC >= your longest "
+ "attack timeout so completion-teardown does not kill an in-flight attack."
)
if kind == "classifier":