Files
claude-playbooks/playbook_background_agent_prompts.md
2026-06-27 01:04:06 -05:00

8.2 KiB

name, description, metadata
name description metadata
playbook-background-agent-prompts REQUIRED before writing any background agent prompt: full template, 5 discipline rules, mandatory wrap-up block, and anti-patterns; read this before drafting any agent prompt
node_type type originSessionId
memory project 075715ba-4454-464c-ba39-dfbcdefc6590

When to use background agents

Use a background agent when:

  • The task is bounded (clear start, clear end, defined output)
  • It does not require real-time decisions from the user
  • It can run in parallel with other work without file/resource conflicts
  • It fits within 15 tool calls

Do NOT use a background agent for:

  • Open-ended research or design tasks (those belong in main session)
  • Tasks that require human decisions mid-way
  • Tasks that share files or DB rows with another running agent

The 5 Prompt Discipline Rules

Every background agent prompt must satisfy all 5. A prompt missing any of these will loop, stall, or silently fail.

Rule 1 — Hard turn limit

Always include --max-turns 15 in the prompt (or in the claude -p invocation). Without this, a looping agent runs indefinitely and consumes unbounded SDK credits.

Rule 2 — Loop detection instruction

Include this verbatim:

"If you issue the same tool call or command twice with identical arguments, STOP immediately and output the mandatory wrap-up with status=partially_succeeded."

Rule 3 — Credential map inline

Every credential the agent needs must be provided inline in the prompt — exact Vault path and field name. Never send an agent to search for auth at runtime. Use the infrastructure auth map (secret/data/claude/infrastructure-auth-map → yaml field) as the source.

Format:

## Credentials you will need
- sudo-bridge (primary): Vault path secret/data/sudo-bridge → api_key
- Gitea token: Vault path secret/data/gitea → admin_token
- AppRole bootstrap: role-id at /opt/appdata/docker/docker-compose/vault/approle/role-id

Always include the AppRole bootstrap pattern when Vault access is needed:

VAULT_IP=$(docker inspect vault-iwaulpoi5hwirdlogshmul40 --format '{{json .NetworkSettings.Networks}}' | python3 -c "import sys,json; nets=json.load(sys.stdin); print(list(nets.values())[0]['IPAddress'])")
ROLE_ID=$(cat /opt/appdata/docker/docker-compose/vault/approle/role-id | tr -d '[:space:]')
SECRET_ID=$(cat /opt/appdata/docker/docker-compose/vault/approle/secret-id | tr -d '[:space:]')
VAULT_TOKEN=$(curl -sf -X POST -H "Content-Type: application/json" \
  -d "{\"role_id\":\"$ROLE_ID\",\"secret_id\":\"$SECRET_ID\"}" \
  http://$VAULT_IP:8200/v1/auth/approle/login | python3 -c "import sys,json; print(json.load(sys.stdin)['auth']['client_token'])")
# Always revoke after use:
curl -sf -X POST -H "X-Vault-Token: $VAULT_TOKEN" http://$VAULT_IP:8200/v1/auth/token/revoke-self

Rule 4 — Defined output format

Specify the exact output the agent must produce. Never say "report what you did" — always define the schema. Use the mandatory wrap-up block (Rule 5 below) as the minimum output requirement. Add task-specific fields as needed.

Rule 5 — Mandatory wrap-up block (structural, not instructional)

Add this block verbatim to every prompt, immediately before the loop detection instruction. This is structural — the agent cannot satisfy the prompt without emitting it.

## MANDATORY WRAP-UP (required regardless of success or failure)

Before stopping for ANY reason — task complete, error, or approaching turn limit — output this JSON as your final message. Do not stop without it.

{
  "status": "succeeded|partially_succeeded|failed",
  "actions_taken": ["action 1 — outcome", "action 2 — outcome"],
  "actions_failed": ["action — reason it failed"],
  "notes": "anything relevant for the next session"
}

If you hit --max-turns before finishing, set status="partially_succeeded" and list what remains in actions_failed.

Prompt template

You are [one sentence role description]. [One sentence scope — what you will and will not touch.]

## Task
[Clear, bounded description of what to accomplish.]

## Context / pre-fetched data
[Include inline any data the agent needs: DB rows, config values, file contents. Do not make the agent fetch things that can be included here.]

## Credentials you will need
[List exact Vault paths and field names for every secret needed. Include AppRole bootstrap if Vault access required.]

## Step-by-step
[Numbered steps if the task has a defined sequence.]

## MANDATORY WRAP-UP (required regardless of success or failure)

Before stopping for ANY reason — task complete, error, or approaching turn limit — output this JSON as your final message. Do not stop without it.

{
  "status": "succeeded|partially_succeeded|failed",
  "actions_taken": ["action 1 — outcome", "action 2 — outcome"],
  "actions_failed": ["action — reason it failed"],
  "notes": "anything relevant for the next session"
}

If you hit --max-turns before finishing, set status="partially_succeeded" and list what remains in actions_failed.

--max-turns 15
If you issue the same tool call or command twice with identical arguments, STOP immediately and output the mandatory wrap-up with status=partially_succeeded.

Parallel agent rules

  • Max comfortable parallel: 4-5 agents (primary server has 18GB available RAM, ~300MB per agent)
  • Max safe parallel: 8 agents
  • Do not parallelize: Tasks that write to the same file, the same DB row, or the same Gitea repo
  • Do parallelize: Tasks that touch independent files, services, or repos
  • Token cost is the same whether serial or parallel — parallelism saves wall-clock time only

Anti-patterns

Anti-pattern Why it fails Fix
"Report what you did when done" Instructional — skipped under context pressure Use mandatory wrap-up JSON block
Sending agent to find its own credentials Agent loops searching for auth Provide all Vault paths inline
Open-ended task ("investigate and fix anything wrong") No defined completion state Bound the task: specific file, specific check, specific output
No --max-turns Looping agent runs forever Always include --max-turns 15
Sharing a file between two parallel agents Race condition, one overwrites the other Assign non-overlapping files/resources
Making design decisions in the agent Agent guesses wrong, wasted run Pre-decide everything in main session, send agent only to execute
curl -sf ... | python3 -c for API calls -sf suppresses body on error; inline python escaping breaks Write a /tmp Python script using urllib instead
secrets-proxy /shell with target="server-01" Only accepts target=sandbox or target=production — 400 error Server-01 ops go through sudo-bridge-server01 at http://192.168.1.90:8082 directly
Pre-fetching everything before writing prompts Burns context; most things agents can discover in 1 tool call Pre-fetch only what's blocking — include discovery steps inside the agent prompt instead
Writing files on server-01 without allowlist entry 403 — command not in allowlist Add pattern to allowlist (tier 1 = auto, tier 2 = NTFY approval) first; use base64 python3 -c for file writes
Downgrading tier-2 wildcard to tier-1 to bypass NTFY First-registered wildcard wins — adding tier-1 python3 -c * after a tier-2 python3 -c * has no effect Use docker exec <container> python3 -c instead (different prefix, not matched by python3 -c *)
Pre-hashing keys in PROXY_CALLERS env var secrets-proxy app.py expects raw keys in PROXY_CALLERS and hashes internally — pre-hashing causes double-hash, all auth returns 403 Invalid API key Store raw keys: PROXY_CALLERS=json.dumps(callers) not json.dumps({n: sha256(k) for n,k in callers.items()})
Relying on NTFY approvals for unattended agents NTFY notifications may be missed or time out, stalling the agent indefinitely Design server-01 file writes to use docker exec pattern (tier-1) for unattended runs; reserve tier-2 for explicit interactive sessions

Evolution

Update when: new anti-patterns discovered; AppRole bootstrap pattern changes; parallel agent limits change based on server hardware; new credential types need standard inline format.