Agent playbook
Read me at the start of every session. This is the load-bearing discipline that turns passive capture into reliable cross-session memory.
Hooks already record what you did (files touched, commands run, prompts seen). They cannot infer why, whether it worked, or how it was fixed. That’s your job, via four explicit tools.
The four explicit calls
Section titled “The four explicit calls”1. decision_log — every non-obvious architectural / technical choice
Section titled “1. decision_log — every non-obvious architectural / technical choice”Trigger: you just picked between two real alternatives, or chose a non-default approach, or made a call that the next agent would have to re-derive from code archaeology.
decision_log( project_id=<this project>, title="Use pgvector instead of a separate vector DB", rationale="Scaleway Managed RDB ships pgvector; avoids a second store and lets us join fragments to structured entities transactionally.", alternatives_considered=["Pinecone", "Self-host Qdrant"], tags=["architecture", "phase-6"],)Don’t log: typos, obvious refactors, single-line bug fixes, code style tweaks. Decisions are forever — keep the bar high.
2. bug_resolve — when a bug is actually fixed
Section titled “2. bug_resolve — when a bug is actually fixed”Trigger: you fixed something the test suite (or the user) flagged as
broken. Use this even if you didn’t open a bug_report first — call
bug_report then bug_resolve in the same turn.
The contract enforces both fields, server-side returns 409 otherwise:
root_cause— non-empty, what was actually wrong.fix_narrative— ≥ 20 characters, how a future agent will understand the fix without re-investigating.
bug_resolve( project_id=<…>, bug_id=<…>, root_cause="asyncpg pool acquire raced with starlette BaseHTTPMiddleware's response wrapping", fix_narrative="Dropped BaseHTTPMiddleware; routes now read app.state.pool directly and acquire per-query.",)3. deploy_log — before AND after every deploy
Section titled “3. deploy_log — before AND after every deploy”Trigger: you ran a deploy script (CI deploy.yml, scw container update,
kubectl apply, anything that mutates a staging/prod environment).
# Mark pending the moment you trigger:deploy_log(project_id=<…>, environment="prod", status="started", version="v0.1.0", commit_sha="513b069")# When it returns 0 / Scaleway reports ready:deploy_log(project_id=<…>, environment="prod", status="succeeded", version="v0.1.0", closes_bug_ids=[…])A future agent looking at a regression must be able to correlate it with a specific deploy. No deploy log → no correlation → wasted hours.
4. credential_ref_upsert — when a new secret enters the project
Section titled “4. credential_ref_upsert — when a new secret enters the project”Trigger: you provisioned an API key, generated a JWT signing key, created an SSH deploy key, or otherwise added a secret to Keychain / Secret Manager / Gitea Secrets that future agents will need to know about.
Never log the value. Only the reference:
credential_ref_upsert( project_id=<…>, name="erold.prod.api-key", credential_type="api_key", store="keychain", lookup_key="erold.prod.api-key", provision_instructions="Run `secret set erold.prod.api-key`, paste from app.erold.dev → Settings → API Keys.",)The server rejects payloads containing value, secret, token,
password, key, hash fields at any depth. If your provision_instructions
contains a literal secret pattern (AKIA…, sk-ant-…, ghp_…, etc.) the
server also rejects.
When the hooks already covered it
Section titled “When the hooks already covered it”Don’t re-log what passive capture already records:
| You did | Hook captures | Don’t also call |
|---|---|---|
Edit / Write a file | file_change event with path | log() |
Bash command | pre_tool_use_bash + post_tool_use_bash (hash, exit, duration) | log() |
| User typed a prompt | user_prompt_submit (hash, word count, intent class) | — |
| Subagent finished | subagent_stop event | — |
| Session ends | session_end event | — |
The gap signal
Section titled “The gap signal”get_context() returns a gaps[] array. If you see any of these on session
start, surface to the user and offer to fill them:
no_decisions_logged— fragments exist but no Decision entities. Bad cross-session continuity.no_deploys_logged— there’s history of code changes but no deploys. Can’t correlate regressions.no_credential_refs_registered— credentials are in use (you’ll seesecret getcalls in Bash history) but never registered as references.unresolved_bugs_lack_root_cause— open bugs have noroot_cause— investigation is incomplete.fragments_search_lagging— Smart-Strip pipeline is behind (~1-3 s). Retry search shortly; structured entities are still authoritative.
Failure modes to recognise
Section titled “Failure modes to recognise”-
You think you logged a decision, but
get_contextdoesn’t show it. The local outbox queues events client-side. If the daemon is down or the backend is unreachable, your log call goes into the outbox JSONL but hasn’t shipped yet. Check~/.erold/outbox/events.jsonland~/.erold/error.log. Re-running the daemon (any new SessionStart spawns it idempotently) drains the queue. -
bug_resolvereturns 409. You’re transitioning from a status other thaninvestigating. Callbug_investigatefirst, thenbug_resolve. -
Pydantic 422 with
CREDENTIAL_VALUE_FORBIDDEN. Your payload had a field namedvalue,secret,token,password,key,hash, orencrypted_value. Strip it. The contract is: names + how to fetch, never the value itself.
Decisions / bug fix narratives / deploy notes are read by future agents in a different session, with no other context. Write them as if you’re briefing a smart colleague who just walked in:
- Lead with the conclusion.
- One sentence of why.
- One sentence of what was rejected.
- Concrete: file paths, function names, error codes — not “the thing was broken so I fixed the thing.”
Bad: "Fixed the bug."
Good: "Race in TenantRLSMiddleware: held one conn for whole request while routes acquired others from the same pool. Switched to app.state.pool only; auth acquires per-call."