Setup Flow
Authoritative call shape for bl setup. Which Anthropic API endpoints get hit, in what order, with which headers and bodies. Idempotent by contract. Reflects the live-API corrections that landed late in the build.
Vernacular shortcut. "M15" is the Day-6 hackathon milestone (2026-04-26) where the agent-update verb moved from
PATCHtoPOSTwith optimistic CAS, the agent retire moved fromDELETEtoPOST .../archive, and the sessions.create body field was renamed fromagent_idtoagent. "Path C" / "M13" refers to the same-week realignment that retired the flatbl-skillsmemory store. See the docs index glossary for the full decoder ring.
Purpose
One-time per Anthropic workspace bootstrap. After bl setup --sync completes on host 1, every subsequent host running bl against the same ANTHROPIC_API_KEY finds the workspace pre-seeded, bl_preflight cache-hits on the cached agent id and skips the create path entirely.
Outputs:
- 1 agent record (
bl-curator) with three custom tools mounted (report_step,synthesize_defense,reconstruct_intent) - 1 environment record (
bl-curator-env) withnetworking.type:"unrestricted"so per-session apt installs reach mirrors - 1 memory store (
bl-caseread_write) - Up to 6 routing Skills (or 6 reference-file fallbacks if the Skills allowlist is gated, see API Notes §2)
- 8 workspace reference Files mounted at
/skills/<basename> state.jsonwritten atomically; per-key files retained as fallback reads
Preconditions
ANTHROPIC_API_KEYexported. 108-char key format (verified-len check OK; actual format unchecked: first API call surfaces key-shape errors as exit 65).curlavailable. Any version supporting-sS,-w,-X: essentially any curl since 2003.jqavailable (1.5+).- Bash 4.1+ (
blfloor; CentOS 6 era minimum). /var/lib/bl/writable by the invoking user (host-root or user with directory permission).$BL_REPO_URLoptional; defaults to public GitHub clone.
No TLS cert pinning beyond what curl + host OS certificate store provides. No mTLS. No service account. Sole secret = ANTHROPIC_API_KEY.
Happy path sequence (M15 live-API correct)
Eight ordered steps. Each is idempotent. state.json.lock is held via flock -x -w 30 203 for the full sync to serialize concurrent invocations.
- Local preflight:
bl_setup_local_preflightcarries the sameANTHROPIC_API_KEY+curl+jq+ state-dir checks asbl_preflight(which is bypassed forsetup). - Load state:
bl_setup_load_statereadsstate.json; first-run migration consumes any pre-M15 per-key files into the JSON, with a timestamped recovery backup. - Ensure environment:
POST /v1/environmentswith body{name, config:{type:"cloud", networking:{type:"unrestricted"}}}only.packagesis not a valid env-create field onmanaged-agents-2026-04-01, see API Notes §1. Curator-side packages (apache2, mod_security2, yara, duckdb, pandoc, weasyprint) are installed per-session by the agent via the bash tool. - Ensure memory store:
POST /v1/memory_stores {name: "bl-case"}.bl-skillsis not created, retired in Path C. - Seed reference Files (8):
bl_setup_seed_corpusSHA-256-diffs eachskills-corpus/*.mdagainststate.files.<mount-path>.content_sha256; uploads changed viabl_files_create; oldfile_idqueued instate.files_pending_deletion[]for--gc. Mount path =/skills/<basename>. - Seed routing Skills (6):
bl_setup_seed_skillsprobes/v1/skillswith raw curl. HTTP 200 → create / version-bump perrouting-skills/*/. HTTP 404 → fall through tobl_setup_seed_skills_as_files, which uploads eachSKILL.mdas a reference file at/skills/<name>-skill.md. - Ensure agent:
bl_setup_ensure_agentcreates viaPOST /v1/agentsif first-run, or updates viaPOST /v1/agents/<id>with optimistic-CAS body{version: <current>, ...}if existing. On HTTP 409 (Concurrent modification detected) the client refetches viaGET /v1/agents/<id>and retries once with the fresh version. PATCH returns 405. (See API Notes §4 for the related sessions.create field rename.) - Save state:
state.jsonwritten atomically (mvfrom.tmp.$$).last_syncstamped.
Standard headers
x-api-key: $ANTHROPIC_API_KEY
anthropic-version: 2023-06-01
anthropic-beta: managed-agents-2026-04-01
content-type: application/json
Absent anthropic-version causes 400 on otherwise-valid bodies.
Per-call specification
Preflight: GET /v1/agents (client-side filter)
| Field | Value |
|---|---|
| Method | GET |
| Query | none, ?name= is silently ignored by the server (see API Notes §6) |
| Body | (none) |
| Filter | client-side: jq -r '.data[] | select(.name == "bl-curator") | .id' |
| Success (200) | filtered result empty → emit bootstrap message, return 66 BL_EX_WORKSPACE_NOT_SEEDED; 1+ → cache id, return 0 |
| Failure (401/403) | bad key → exit 65 BL_EX_PREFLIGHT_FAIL |
| Failure (429) | rate limited → exit 70 BL_EX_RATE_LIMITED; outbox-queued; exponential backoff |
| Failure (5xx) | upstream → exit 69 BL_EX_UPSTREAM_ERROR after 3 retries |
Create / update agent: POST /v1/agents (or /v1/agents/<id>)
{
"name": "bl-curator",
"model": "claude-opus-4-7",
"system": "<contents of prompts/curator-agent.md>",
"tools": [
{"type": "agent_toolset_20260401"},
{"type": "custom", "name": "report_step",
"description": "Emit a proposed blacklight wrapper action. One call per step.",
"input_schema": "<schemas/step.json stripped of $schema/$id/title>"},
{"type": "custom", "name": "synthesize_defense",
"description": "Propose a defensive payload for this case.",
"input_schema": "<schemas/defense.json stripped>"},
{"type": "custom", "name": "reconstruct_intent",
"description": "Walk obfuscation layers of a mounted shell sample.",
"input_schema": "<schemas/intent.json stripped>"}
]
}
Strict-field policy (verified against live, see API Notes §8). The managed-agents-2026-04-01 beta rejects thinking, output_config, skill_versions, top-level skills[], input_schema.additionalProperties, and per-field description inside input_schema as extra inputs (HTTP 400 invalid_request_error). Required: name + model + system + tools[]. Tool descriptions are required (not optional) on every custom tool. Schemas have $schema/$id/title stripped before submit.
Update verb (M15 P4): POST /v1/agents/<id> with {version: <current>, ...}, not PATCH. PATCH returns 405. On HTTP 409 the client refetches and retries with the fresh version (bl_setup_update_agent_cas in 84-setup.sh).
Retire verb (M15): POST /v1/agents/<id>/archive, not DELETE. DELETE returns 405. Empty {} body. bl setup --reset aborts if archive fails, state.json must never be wiped while a live agent still exists.
Create environment: POST /v1/environments
{
"name": "bl-curator-env",
"config": {
"type": "cloud",
"networking": { "type": "unrestricted" }
}
}
This is the only accepted body shape on managed-agents-2026-04-01. The legacy top-level {type, packages, networking} shape returns 400 "Extra inputs are not permitted" for every field but name. Per-session apt-package install is curator-driven via the bash tool until Anthropic exposes a setup_script or image field. See API Notes §1.
networking.type:"unrestricted" is required so the curator's bash tool can reach apt mirrors during the per-session install.
Create memory store: POST /v1/memory_stores
{ "name": "bl-case" }
One call. bl-skills is not created, retired in Path C / M13. Path-style namespacing (bl-case/<case>/...) gives every case its own subtree inside the single memstore.
Seed reference Files: POST /v1/files (multipart)
For each skills-corpus/*.md, upload via bl_files_create with mount path /skills/<basename>. SHA-256 delta-check against state.files.<mount-path>.content_sha256; unchanged files are skipped. Old file_id queued in state.files_pending_deletion[] for --gc.
Seed routing Skills: POST /v1/skills (or fallback)
bl_setup_seed_skills probes /v1/skills with raw curl using a body+status-code response shim portable across real curl and the test mock. SHA-256 delta-check covers both description.txt and SKILL.md bodies.
- HTTP 200 → create or version-bump per
routing-skills/<name>/. - HTTP 404 (workspace not allowlisted) → fall through to
bl_setup_seed_skills_as_files, uploading eachSKILL.mdas a reference File at/skills/<name>-skill.md. The curator system prompt names the reference-file paths explicitly; description-routed selection is not available in fallback mode. See API Notes §2. - HTTP 401/403 → propagate as preflight failure.
- HTTP 5xx → exit 69 with retry policy.
Sessions.create body shape (per-case: not part of setup)
Documented here for completeness because the field-name correction landed in the same M15 sweep:
{
"agent": "<agent-id>",
"environment_id": "<env-id>",
"resources": [
{"type": "file", "file_id": "<id>", "mount_path": "/skills/foundations.md"},
...
]
}
The field is agent, not agent_id. environment_id is required even on a single-env workspace. See API Notes §4.
Idempotency contract
All setup operations must be safely re-executable.
- If agent exists →
POST /v1/agents/<id>with CASversionfield instead of create; on 409 refetch and retry once. - If environment exists → reuse cached id; no second create.
- If memory store exists → name-probe hit; no second create.
- If reference File content unchanged (sha256 matches
state.files.<path>.content_sha256) → skip upload. - If routing Skill content unchanged → skip version-bump.
Operator running bl setup --sync on host 5 after already running it on host 1 produces a no-op with a friendly summary. This is required because hosting providers and MSPs run bl setup from a configuration management layer (Puppet, Ansible), every node will run setup; only the first creates anything.
Source-of-truth resolution
bl setup discovers skill content in this order:
- Current working directory has
skills/andprompts/: use those. $BL_REPO_URLset: shallow clone to$XDG_CACHE_HOME/blacklight/repo, use.- Default →
git clone https://github.com/rfxn/blacklight $XDG_CACHE_HOME/blacklight/repo, use.
This covers three adoption paths: operator-from-clone, operator-from-fork, operator-from-quickstart.
bl setup --sync
Compares local routing-skills/<name>/{description.txt,SKILL.md} and skills-corpus/*.md sha256 against state.json (agent.skill_versions and files.<path>.content_sha256), POSTs only changed files, updates state. Also surfaces deletions (local file missing → queue old file_id for --gc). Safe to re-run daily.
Verified by end-to-end tests in the BATS suite. A per-invocation curl-shim request log makes the assertion possible.
bl setup --reset
Archives the agent (POST /v1/agents/<id>/archive), deletes Skills + workspace Files, clears state.json to a fresh schema-1 shape but preserves case_memstores, case_files, case_id_counter, case_current so an operator who resets workspace identity keeps their case audit trail. Defensive ordering: agent archive must succeed before any deletes; an archive failure aborts the reset.
bl setup --gc
Walks state.files_pending_deletion[]; deletes each file_id via DELETE /v1/files/<id> if no live session in state.session_ids references it. Anthropic does not currently expose per-file usage enumeration (see API Notes §7), conservative posture is to skip when any live session is present.
bl setup --check
Reports the state.json snapshot + per-resource health. Hits no write endpoints.
bl setup --eval [--promote]
Live skill-routing eval (BL_EVAL_LIVE=1). Exercises the curator's description routing against a fixture set, scores hit rate per routing Skill, optionally promotes the agent version on pass.
bl setup --install-hook lmd / --import-from-lmd
LMD integration sub-verbs (M14):
--install-hook lmdcopiesfiles/hooks/bl-lmd-hookto/etc/blacklight/hooks/, edits/usr/local/maldetect/conf.maldetto setpost_scan_hook="..."(flock-serialized;bash -nsyntax check; restore-from-backup on parse fail).--import-from-lmdreadsconf.maldet, writes notification credentials (email, Slack, Telegram, Discord) under/etc/blacklight/notify.d/*withchmod 0600. Metacharacter rejection and key-allowlist before any write.
Failure modes
| Exit | Code | Meaning |
|---|---|---|
| 65 | BL_EX_PREFLIGHT_FAIL | API key bad / missing / curl or jq missing / state-dir unwritable |
| 66 | BL_EX_WORKSPACE_NOT_SEEDED | non-setup verb invoked, but no agent record exists. Tells operator to run bl setup --sync. |
| 67 | BL_EX_SCHEMA_FAIL | A step/payload fails JSON validation before submit. |
| 68 | BL_EX_TIER_GATE_DENIED | Tier gate refused the action. |
| 69 | BL_EX_UPSTREAM_ERROR | 5xx from Anthropic; retried 3x with exponential backoff. |
| 70 | BL_EX_RATE_LIMITED | 429 from Anthropic; outbox-queued for bl_preflight to drain on next invocation. |
| 71 | BL_EX_CONFLICT | 409 from a concurrent state mutation; CAS-update path retries once. |
| 72 | BL_EX_NOT_FOUND | Targeted resource missing. |
After setup
Every non-setup, non-help bl invocation starts with bl_preflight (30-preflight.sh). Eight ordered checks:
ANTHROPIC_API_KEYset and non-empty → exit 65 on fail.curlandjqon PATH → exit 65 on fail.mkdir -p "$BL_STATE_DIR"→ exit 65 on fail.- Seed
BL_MEMSTORE_CASE_IDfromstate.json.case_memstores._default(M15: old per-key file retired). - Use cached
BL_AGENT_ID_FILEif present (early return 0). - Else probe
GET /v1/agents(list all; filter client-side on.name == "bl-curator",?name=is not honored). Miss → emit bootstrap message, return 66BL_EX_WORKSPACE_NOT_SEEDED. - Age-gated outbox drain when the oldest queued entry is ≥ 1 h old.
- M14: load
/etc/blacklight/blacklight.conf(allowlisted keys); register notify channels.
After the first successful call, the agent id is cached on disk; subsequent invocations skip the live probe entirely. Steady-state cost of bl_preflight is one stat + one read.
See also
- Architecture · Path C primitives map
- Skills Architecture · Path C
- Anthropic API Notes, the friction log this page corrects against