Best practices¶
Rules of thumb from running kaboo-workflows in production, each with the reason behind it. The recipes show them in full.
Configuration¶
- Keep models and MCP clients apart from workflows, and refer to them by name. The same workflow then runs against real models in production and scripted ones in tests, and a workflow stored in a database never carries a URL or a credential (Testing, per-run workflows).
- Secrets live in the base config; escape
${in everything else. A submitted workflow is interpolated against the server's environment, so a user-authored${…}would resolve there too.$${is a literal${. - Grant tools by exact name, a few at a time.
tool_filters.allowedis an exact-match list. Narrow clients per job and a ceiling of about 20 tools per agent keep prompts small and tool choices good. - Validate on save, not on first run.
load_session_configchecks a workflow exactly as a run would and names what is missing; add your own rules after it and refuse any unfilled placeholder. - Configure only what the model supports. Adaptive thinking, prompt caching and media blocks are model features; a config written for one model can fail at the provider on another.
- Use module paths in workflows submitted per run. Relative paths resolve from a config file's directory; a submitted workflow has none, so they resolve from the service's working directory.
Prompts and skills¶
- Short prompts, reusable skills. Inline only the rules that must always hold; list the rest so agents load them when a task calls for it (Skills).
- Tell agents to reload what compression removed. Long runs compress old messages, and an agent will happily carry on from a skill it can no longer see.
- Keep the start of a prompt stable. Prompt caching keys on the prefix; append changing lines, such as today's date, at the end.
- Write titles, labels and schema descriptions for their reader. Stream titles and tool labels are read by users; schema field descriptions are read by the model. Neither should look like an identifier.
Reliability¶
- Put guard hooks on every agent. Repair transcripts before each model call, refuse a call that has failed twice, stop cleanly when a sign-in expires, and cap tool calls (Guard hooks).
- Respect Bedrock's conversation rules. Every tool call answered in the next
message, no two messages in a row from the same role, no empty text blocks.
A violation stops the run without an error;
ScriptedModelenforces the pairing rule so tests catch it first. - One voice asks the user. Grant
ask_userto the agent that talks to the user, ask once with every open question, and treat a cancelled request as an answer (Ask the user). - On capped hosts, set a response budget. A long turn then pauses and continues in a fresh response instead of being cut off; on AgentCore also report busy while a run is open (Serve on AgentCore).
State and identity¶
- Conversation state belongs to the client. History and pending interrupts travel on the AG-UI state channel, so any replica can serve any turn and a restart loses nothing. Do not keep conversation state in process memory.
- Bound anything process-local, and key it deliberately. Thread id for the conversation, run id for the request, turn id for a side effect that must happen once across an interrupt and its resume (Tools that need run context).
- Identity comes only from the verified principal. Forwarded props are client input; validate them like any other.
- Relay a user's token only on clients built per run. Per-run clients are what keep one user's token out of another's run (Call MCP tools as the signed-in user).
Cost¶
- Cache the system prompt, the tool list and the conversation. Three of Bedrock's four cache points pay for themselves on the first tool loop.
- Think where it pays. Thinking on the agent that plans and judges, off for the ones executing clear instructions.
- Do not resend what the agent no longer needs. Clear old tool results within a run and mask them in history between runs (Cost and context control).
Testing¶
- Script a test for every workflow you ship. A scripted run exercises the real tools, hooks, prompts and orchestration with no model, network or server (Testing your workflows).
- Test what runs, not what is stored. For composed workflows, feed the composer's output to the pipeline and assert on the prompt the model received.
- Prove restarts on a fresh pipeline. Resuming on a new
Pipelinewith the persisted state is the only test that a pause survives a deploy. - Keep model evaluations few and deliberate. Scripted tests prove behaviour; evaluations prove answer quality, at the price of a real model call per item.
Observability¶
- Turn on telemetry and send traces to an OTLP backend such as Langfuse (Evaluation).
- Log every model call's latency and retries with its run id. It answers "why was that slow?" when tracing is off.
- Watch token usage per run after every prompt or config change; caching in particular breaks silently.