How to Configure Interjections
๐ Prompt ยท ๐ฌ Conversation
Interjections are messages the harness adds to the running turn, between the agent's model calls. Each comes from an interjection source that watches the transcript: one speaks up when a tool keeps failing, another has a separate reviewer read the trajectory and steer. The agent reads them as advisory and keeps the user's request as its objective.
A source can speak at two points of every model and tool iteration:
| Hook | When it runs |
|---|---|
before_model_call | Before every model call of the turn, the first included, with the transcript the model is about to receive |
after_model_call | Once the model's response and the results of the tools it called are in the transcript |
What a source says is appended to the transcript as a developer message starting with [interjection: <source>], and is saved with the rest of the transcript. After-call messages follow the tool results, so each tool result stays next to its call. Sources speak in configuration order. A source that fails is logged and skipped. With no source active the turn skips both hooks.
Enable Interjectionsโ
{
"agent_configuration": {
"interjections_provider_configuration": { "enabled": true },
"repeated_tool_failure_interjection_source_configuration": { "enabled": true },
"reviewer_interjection_source_configuration": { "enabled": true, "reviewer_agent_identifier": "reviewer" }
}
}
za agents capabilities interjection-sources list
za agents capabilities interjection-sources configure <source-name>
za agents capabilities interjection-sources settings
Provider Settingsโ
interjections_provider_configuration:
| Setting | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Run the active sources at both hooks. With at least one source active, the system prompt gets a ## Interjections section telling the model what these messages are |
include_sources / exclude_sources | list | null | Source filtering, as on every provider |
Interjection Sourcesโ
| Source | CLI name | Hook | Speaks when |
|---|---|---|---|
| Repeated Tool Failure | repeated-tool-failure | after | One tool failed a number of times in a row |
| Reviewer | reviewer | before | A failure streak, or every N tool steps; a separate agent reviews the transcript |
| Attempt Plateau | attempt-plateau | after | The last attempts at a scored task were all rejected |
| Attempt Transcript Recorder | attempt-transcript-recorder | after | Never; it saves the transcript next to each committed attempt |
| Refine | refine | after | Every N model calls, when a gate agent finds the turn worth a refinement |
All sources are disabled by default.
Repeated Tool Failureโ
Tells the agent to change approach when the same tool fails several times in a row. A failure is a tool result that starts with Error in executing tool. A success, or a failure of another tool, ends the streak. The source speaks once each time the streak crosses another multiple of the threshold.
za agents capabilities interjection-sources configure repeated-tool-failure
repeated_tool_failure_interjection_source_configuration:
| Setting | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Switch the source on |
max_consecutive_failures | int | 3 | Failures in a row of one tool before it speaks |
message | str or null | null | Replace the built-in text. Placeholders: {tool_name}, {count}, {last_error} |
Reviewerโ
A separate agent, with a clean context, reads the transcript and replies with a diagnosis and two or three directions to try. Its reply is the interjection. The stuck agent does not review itself.
za agents capabilities interjection-sources configure reviewer
reviewer_interjection_source_configuration:
| Setting | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Switch the source on |
on_consecutive_tool_failures | int or null | 3 | Review when one tool has failed this many times in a row. null turns this trigger off |
every_n_tool_steps | int or null | null | Also review every N tool steps, failures or not. null turns this trigger off |
reviewer_agent_identifier | str or null | null | Agent in agent_setups.json that reviews. null runs a fresh copy of the current agent |
max_transcript_chars | int | 24000 | Characters from the end of the rendered transcript given to the reviewer |
instructions | str or null | null | Replace the built-in reviewer instructions |
Point reviewer_agent_identifier at a lean agent with tools and memory off. A copy of the current agent starts every provider it has for each review.
Attempt Plateauโ
Part of the attempts gate. When the last attempts at a scored task were all rejected, tells the agent the line of changes has plateaued and to try a different kind of change. It speaks once per crossed multiple of the threshold, and stays silent on turns that matched no rubric or when no attempt store is enabled.
za agents capabilities interjection-sources configure attempt-plateau
attempt_plateau_interjection_source_configuration:
| Setting | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Switch the source on |
max_consecutive_rejections | int | 3 | Rejected attempts in a row before it speaks |
message | str or null | null | Replace the built-in text. Placeholders: {count}, {scores}, {best} |
Attempt Transcript Recorderโ
Part of the attempts gate. It never speaks: after a tool batch that committed an attempt, it saves the transcript up to that point next to the attempt (transcript.json in the file store), so you, a reviewer or the agent can later read how the attempt came about.
za agents capabilities interjection-sources configure attempt-transcript-recorder
attempt_transcript_recorder_interjection_source_configuration:
| Setting | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Switch the source on |
Refineโ
The harness side of refinement. Every every_n_model_calls model calls, and at most once per cooldown_seconds, a gate agent reads the harness (memories, skills, instructions, sub-agents) and the transcript so far, and answers whether this checkpoint holds something worth keeping. On yes, the refiner runs with the gate's instructions and the interjection lists what was kept. On no, nothing else happens: a rejected checkpoint costs one gate call. The source is off when the refiner has nowhere to write.
za agents capabilities interjection-sources configure refine
refine_interjection_source_configuration:
| Setting | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Switch the source on |
every_n_model_calls | int | 8 | Model calls between two gate reviews within a turn |
cooldown_seconds | float | 120.0 | Minimum time between two gate reviews |
gate_agent_identifier | str or null | null | Agent in agent_setups.json that decides. null runs a fresh copy of the current agent; prefer a lean agent with tools and memory off |
gate_instructions | str or null | null | Replace the built-in gate instructions. The gate must reply with a JSON object {"should_refine": bool, "rationale": str, "instructions": str} |
Custom Interjection Sourcesโ
A source subclasses InterjectionSource and overrides before_model_call, after_model_call or both. Each receives an InnerLoopContext and returns the message text, or None to stay silent:
| Field | Type | Description |
|---|---|---|
transcript | List[ChatCompletion] | The turn's transcript at this hook |
remaining_tool_steps | int | Tool steps the turn has left |
response | ChatCompletion or null | The model's response; set in after_model_call only |
from typing import Optional
from zav.pydantic_compat import BaseModel
from zav.agents_sdk import AgentDependencyFactory, AgentDependencyRegistry
from zav.agents_sdk.adapters.interjections import InterjectionSource
from zav.agents_sdk.adapters.llm_models.chat_turn_request import InnerLoopContext
class StepBudgetInterjectionSourceConfiguration(BaseModel):
enabled: bool = False
warn_at: int = 3
class StepBudgetInterjectionSource(InterjectionSource):
source_name = "step_budget"
def __init__(self, enabled: bool, warn_at: int) -> None:
self.enabled = enabled
self.warn_at = warn_at
async def before_model_call(self, context: InnerLoopContext) -> Optional[str]:
if context.remaining_tool_steps != self.warn_at:
return None
return f"{self.warn_at} tool steps left. Start writing the answer."
class StepBudgetInterjectionSourceFactory(AgentDependencyFactory):
@classmethod
def create(
cls,
step_budget_interjection_source_configuration: (
StepBudgetInterjectionSourceConfiguration
) = StepBudgetInterjectionSourceConfiguration(),
) -> StepBudgetInterjectionSource:
configuration = step_budget_interjection_source_configuration
return StepBudgetInterjectionSource(configuration.enabled, configuration.warn_at)
AgentDependencyRegistry.register(StepBudgetInterjectionSourceFactory)
The source is configured like the built-in ones, as step_budget_interjection_source_configuration. za agents capabilities interjection-sources add <name> scaffolds the file.
Hooks in a Custom Agentโ
The interjections provider is the built-in agent's implementation of InnerLoopHooks (zav.agents_sdk.adapters.llm_models.chat_turn_request). A custom agent passes its own to the chat client: client.complete(..., hooks=my_hooks). Both methods take an InnerLoopContext and return a list of ChatCompletion messages to append, empty for none.