Skip to main content
operating·Doc 06 of 13

Setup Flow

Authoritative call sequence for `bl setup`. CAS-versioned agent updates, archive-not-delete retire, the M15 live-API corrections, idempotency, --sync delta.

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 PATCH to POST with optimistic CAS, the agent retire moved from DELETE to POST .../archive, and the sessions.create body field was renamed from agent_id to agent. "Path C" / "M13" refers to the same-week realignment that retired the flat bl-skills memory 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) with networking.type:"unrestricted" so per-session apt installs reach mirrors
  • 1 memory store (bl-case read_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.json written atomically; per-key files retained as fallback reads

Preconditions

  • ANTHROPIC_API_KEY exported. 108-char key format (verified-len check OK; actual format unchecked: first API call surfaces key-shape errors as exit 65).
  • curl available. Any version supporting -sS, -w, -X: essentially any curl since 2003.
  • jq available (1.5+).
  • Bash 4.1+ (bl floor; CentOS 6 era minimum).
  • /var/lib/bl/ writable by the invoking user (host-root or user with directory permission).
  • $BL_REPO_URL optional; 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)

Setup-flow sequence: Runner runs local preflight and loads state.json, then issues POST /v1/environments, POST /v1/memory_stores, multipart file uploads for reference Files, POST /v1/skills (with a 404 fallback to Files), POST /v1/agents with optimistic CAS, and finally saves state.json atomically

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.

  1. Local preflight: bl_setup_local_preflight carries the same ANTHROPIC_API_KEY + curl + jq + state-dir checks as bl_preflight (which is bypassed for setup).
  2. Load state: bl_setup_load_state reads state.json; first-run migration consumes any pre-M15 per-key files into the JSON, with a timestamped recovery backup.
  3. Ensure environment: POST /v1/environments with body {name, config:{type:"cloud", networking:{type:"unrestricted"}}} only. packages is not a valid env-create field on managed-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.
  4. Ensure memory store: POST /v1/memory_stores {name: "bl-case"}. bl-skills is not created, retired in Path C.
  5. Seed reference Files (8): bl_setup_seed_corpus SHA-256-diffs each skills-corpus/*.md against state.files.<mount-path>.content_sha256; uploads changed via bl_files_create; old file_id queued in state.files_pending_deletion[] for --gc. Mount path = /skills/<basename>.
  6. Seed routing Skills (6): bl_setup_seed_skills probes /v1/skills with raw curl. HTTP 200 → create / version-bump per routing-skills/*/. HTTP 404 → fall through to bl_setup_seed_skills_as_files, which uploads each SKILL.md as a reference file at /skills/<name>-skill.md.
  7. Ensure agent: bl_setup_ensure_agent creates via POST /v1/agents if first-run, or updates via POST /v1/agents/<id> with optimistic-CAS body {version: <current>, ...} if existing. On HTTP 409 (Concurrent modification detected) the client refetches via GET /v1/agents/<id> and retries once with the fresh version. PATCH returns 405. (See API Notes §4 for the related sessions.create field rename.)
  8. Save state: state.json written atomically (mv from .tmp.$$). last_sync stamped.

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)

FieldValue
MethodGET
Querynone, ?name= is silently ignored by the server (see API Notes §6)
Body(none)
Filterclient-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 each SKILL.md as 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 CAS version field 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:

  1. Current working directory has skills/ and prompts/: use those.
  2. $BL_REPO_URL set: shallow clone to $XDG_CACHE_HOME/blacklight/repo, use.
  3. 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 lmd copies files/hooks/bl-lmd-hook to /etc/blacklight/hooks/, edits /usr/local/maldetect/conf.maldet to set post_scan_hook="..." (flock-serialized; bash -n syntax check; restore-from-backup on parse fail).
  • --import-from-lmd reads conf.maldet, writes notification credentials (email, Slack, Telegram, Discord) under /etc/blacklight/notify.d/* with chmod 0600. Metacharacter rejection and key-allowlist before any write.

Failure modes

ExitCodeMeaning
65BL_EX_PREFLIGHT_FAILAPI key bad / missing / curl or jq missing / state-dir unwritable
66BL_EX_WORKSPACE_NOT_SEEDEDnon-setup verb invoked, but no agent record exists. Tells operator to run bl setup --sync.
67BL_EX_SCHEMA_FAILA step/payload fails JSON validation before submit.
68BL_EX_TIER_GATE_DENIEDTier gate refused the action.
69BL_EX_UPSTREAM_ERROR5xx from Anthropic; retried 3x with exponential backoff.
70BL_EX_RATE_LIMITED429 from Anthropic; outbox-queued for bl_preflight to drain on next invocation.
71BL_EX_CONFLICT409 from a concurrent state mutation; CAS-update path retries once.
72BL_EX_NOT_FOUNDTargeted resource missing.

After setup

Every non-setup, non-help bl invocation starts with bl_preflight (30-preflight.sh). Eight ordered checks:

  1. ANTHROPIC_API_KEY set and non-empty → exit 65 on fail.
  2. curl and jq on PATH → exit 65 on fail.
  3. mkdir -p "$BL_STATE_DIR" → exit 65 on fail.
  4. Seed BL_MEMSTORE_CASE_ID from state.json .case_memstores._default (M15: old per-key file retired).
  5. Use cached BL_AGENT_ID_FILE if present (early return 0).
  6. Else probe GET /v1/agents (list all; filter client-side on .name == "bl-curator", ?name= is not honored). Miss → emit bootstrap message, return 66 BL_EX_WORKSPACE_NOT_SEEDED.
  7. Age-gated outbox drain when the oldest queued entry is ≥ 1 h old.
  8. 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