151 lines
8.2 KiB
Markdown
151 lines
8.2 KiB
Markdown
---
|
|
name: playbook-background-agent-prompts
|
|
description: "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"
|
|
metadata:
|
|
node_type: memory
|
|
type: project
|
|
originSessionId: 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:
|
|
```bash
|
|
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.
|