Skip to main content
BLACKLIGHTrfxnforged in prod
operating·Doc 04 of 8

Setup Flow

Authoritative call sequence for `bl setup`. Agent create, environment, two memory stores, skills sync. Idempotency contract. --sync delta-detection.

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-skills read-only + bl-case read-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=1 line printed

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

Six ordered steps. Each is idempotent.

  1. Discover skill source: cwd skills/$BL_REPO_URL clone → default clone.
  2. 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.
  3. Create agent: POST /v1/agents. Capture the returned id; write to /var/lib/bl/state/agent-id.
  4. Create environment: POST /v1/environments. Capture id; write to /var/lib/bl/state/env-id.
  5. Probe + create memory stores: for each of bl-skills and bl-case that returns empty → POST /v1/memory_stores. Capture ids.
  6. Seed skills: iterate skills/**/*.md, POST each via /v1/memory_stores/<skills-id>/memories. Compute sha256 per file; write to bl-skills/MANIFEST.json for delta detection on --sync.
  7. Print exports: emit export BL_READY=1 to 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

FieldValue
MethodGET
Queryname=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 (name collision 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:

  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 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

ExitCodeMeaning
65PREFLIGHT_FAILAPI key bad / not set / format-rejected
66WORKSPACE_UNSEEDEDCaller is not bl setup, but no agent record exists. Tells operator to run setup first.
67STEP_SCHEMA_FAILA skill or schema fails JSON validation before submit.
68RECEIPT_INVALIDDry-run receipt expired or absent on a non-dry-run apply.
69UPSTREAM_ERROR5xx from Anthropic; retried 3x with exponential backoff.
70RATE_LIMITED429 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.