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.
Purpose
One-time per Anthropic workspace bootstrap. After bl setup completes on host 1, every subsequent host running bl against the same ANTHROPIC_API_KEY finds the workspace pre-seeded and skips the create path entirely (the preflight GET cache-hits).
Outputs:
- 1 agent record (
bl-curator) with the three custom tools mounted - 1 environment record (
bl-curator-env) with Apache + mod_security + YARA + jq + zstd + duckdb - 2 memory stores (
bl-skillsread-only +bl-caseread-write) seeded - Local state cached at
/var/lib/bl/state/{agent-id,env-id,memstore-skills-id,memstore-case-id} - Operator-facing
export BL_READY=1line printed
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
Six ordered steps. Each is idempotent.
- Discover skill source: cwd
skills/→$BL_REPO_URLclone → default clone. - Preflight:
GET /v1/agents?name=bl-curator. If result list non-empty → cache the agent id to/var/lib/bl/state/agent-id, skip to step 5. If empty → proceed. - Create agent:
POST /v1/agents. Capture the returnedid; write to/var/lib/bl/state/agent-id. - Create environment:
POST /v1/environments. Captureid; write to/var/lib/bl/state/env-id. - Probe + create memory stores: for each of
bl-skillsandbl-casethat returns empty →POST /v1/memory_stores. Capture ids. - Seed skills: iterate
skills/**/*.md, POST each via/v1/memory_stores/<skills-id>/memories. Compute sha256 per file; write tobl-skills/MANIFEST.jsonfor delta detection on--sync. - Print exports: emit
export BL_READY=1to stdout; operator sources their shell or adds to.bashrc.
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?name=bl-curator
| Field | Value |
|---|---|
| Method | GET |
| Query | name=bl-curator |
| Body | (none) |
| Success (200) | .data[], 0 matches → proceed to create; 1+ → extract .data[0].id, cache, skip-create |
| Failure (401/403) | bad key → exit 65 PREFLIGHT_FAIL |
| Failure (429) | rate limited → exit 70 RATE_LIMITED; queues via /var/lib/bl/outbox/; backoff 2s/5s/10s/30s |
| Failure (5xx) | upstream → exit 69 UPSTREAM_ERROR after 3 retries |
Create agent: POST /v1/agents
{
"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", "input_schema": "<schemas/step.json>"},
{"type": "custom", "name": "synthesize_defense", "input_schema": "<schemas/defense.json>"},
{"type": "custom", "name": "reconstruct_intent", "input_schema": "<schemas/intent.json>"}
]
}
The managed-agents-2026-04-01 beta rejects thinking and output_config as extra inputs (HTTP 400 invalid_request_error), both verified against the live endpoint. Reasoning is model-internal; structured output ships through custom tools. Schemas have $schema/$id/title stripped before submit.
Create environment: POST /v1/environments
{
"name": "bl-curator-env",
"type": "cloud",
"packages": {
"apt": ["apache2", "libapache2-mod-security2", "modsecurity-crs",
"yara", "jq", "zstd", "duckdb"]
},
"networking": { "type": "unrestricted" }
}
Networking must be unrestricted for apt at env creation. Sessions can run with limited thereafter.
Create memory stores: POST /v1/memory_stores
{ "name": "bl-skills", "access": "read_only" }
{ "name": "bl-case", "access": "read_write" }
Two separate calls. Both ids cached locally.
Seed skills: POST /v1/memory_stores/<skills-id>/memories
For each skills/**/*.md, POST with the file's relative path as the memory path and the content as the memory body. Compute sha256 per file; the manifest is written as a final memory at MANIFEST.json so --sync can compare next time.
Idempotency contract
All setup operations must be safely re-executable.
- If agent exists → skip create (preflight short-circuit).
- If environment exists → skip create (
namecollision detected; reuse existing id). - If memory store exists → skip create (name probe).
- If skill content unchanged (sha256 matches manifest entry) → skip push.
Operator running bl setup 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 one 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 skills/**/*.md sha256 against bl-skills/MANIFEST.json, POSTs only changed files, updates manifest. Also detects deletions (local file missing → remote DELETE). Safe to re-run daily.
Verified by an end-to-end test in the BATS suite: three-skill fake repo, baseline content hashes captured, modify one file, run bl setup --sync, and assert exactly one memory POST for the modified file and zero for the others. A per-invocation curl-shim request log makes the assertion possible.
bl setup --check
Dry-run preflight only. Reports what would be created/updated. Hits no write endpoints.
Failure modes
| Exit | Code | Meaning |
|---|---|---|
| 65 | PREFLIGHT_FAIL | API key bad / not set / format-rejected |
| 66 | WORKSPACE_UNSEEDED | Caller is not bl setup, but no agent record exists. Tells operator to run setup first. |
| 67 | STEP_SCHEMA_FAIL | A skill or schema fails JSON validation before submit. |
| 68 | RECEIPT_INVALID | Dry-run receipt expired or absent on a non-dry-run apply. |
| 69 | UPSTREAM_ERROR | 5xx from Anthropic; retried 3x with exponential backoff. |
| 70 | RATE_LIMITED | 429 from Anthropic; outbox-queued for bl_preflight to drain on next invocation. |
After setup
Every subsequent bl invocation starts with bl_preflight:
bl_preflight() {
: "${ANTHROPIC_API_KEY:?blacklight: ANTHROPIC_API_KEY not set}"
# One GET; ~200 ms; cached in /var/lib/bl/state/agent-id for subsequent runs
if [[ -f /var/lib/bl/state/agent-id ]]; then
BL_AGENT_ID=$(< /var/lib/bl/state/agent-id)
return 0
fi
# ... probe, friendly error if workspace unseeded ...
}
After the first call, the agent id is cached on disk; subsequent invocations skip the probe entirely. Steady-state cost of bl_preflight is one stat + one read.