kaboo_workflows.hooks¶
Lifecycle hooks: event publishing, guards, sanitizers, and interrupts.
kaboo_workflows.hooks ¶
Reusable HookProvider implementations for strands agents.
APPROVAL_SCOPES
module-attribute
¶
APPROVAL_SCOPES = ('once', 'thread', 'always')
Decision scopes, narrowest first: this call, this conversation, from now on.
GRANTS_PROP
module-attribute
¶
GRANTS_PROP = 'kaboo_grants'
forwardedProps key carrying the decisions the host remembers.
TOOL_APPROVAL_KEY
module-attribute
¶
TOOL_APPROVAL_KEY = 'kaboo_approval'
Key of the reply a tool sends instead of a result when a call needs approval.
ContextHook ¶
ContextHook(
*,
clear_trigger=None,
keep_last=3,
min_chars=DEFAULT_MIN_CHARS,
keep_tools=(),
client_tools=(),
workspace=(),
views=(),
)
Bases: HookProvider
Rewrites the copy of an agent's conversation its model is sent.
Initialize the ContextHook.
Pass it to the agent at construction (Agent(hooks=[...])): it
installs its middleware when the agent finishes initializing, so a hook
added to an existing agent never runs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
clear_trigger
|
int | None
|
Estimated input tokens (system prompt plus messages,
four characters a token) past which old tool results are
cleared before a model call. |
None
|
keep_last
|
int
|
How many of the most recent turns of tool results always stay; a turn is every result of one model response. |
3
|
min_chars
|
int
|
Tool results shorter than this always stay. |
DEFAULT_MIN_CHARS
|
keep_tools
|
Collection[str]
|
Tools whose results always stay. A delegate's always do. |
()
|
client_tools
|
Collection[str]
|
The browser tools the agent may call; context items that belong to another browser tool stay out of its calls. |
()
|
workspace
|
Collection[str]
|
JSON Pointers into the shared state the agent sees at
every model call (see
:func: |
()
|
views
|
Collection[str | Callable[[ViewRequest], View | None]]
|
Working views: callables taking a :class: |
()
|
Source code in src/kaboo_workflows/hooks/context_hook.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Install the model-input middleware once the agent is initialized.
Source code in src/kaboo_workflows/hooks/context_hook.py
166 167 168 169 | |
View
dataclass
¶
View(base=None, changes=None)
A working view: a base for the whole invocation and the changes since it.
ViewRequest
dataclass
¶
ViewRequest(
agent,
thread_id,
run_id,
first_call,
context,
state=dict(),
)
What a working view is asked to render at one model call.
first_call
instance-attribute
¶
first_call
Whether this is the invocation's first model call, when the base is kept.
context
instance-attribute
¶
context
The run's AG-UI context items, such as what the canvas has open.
state
class-attribute
instance-attribute
¶
state = field(default_factory=dict)
The shared state as of this call, state effects included. Read it; never modify it.
EventPublisher ¶
EventPublisher(
callback,
agent_name,
*,
tool_labels=None,
stream_group="",
stream_title="",
is_chat_reply=False,
max_result_len=_MAX_RESULT_LEN,
)
Bases: HookProvider
Unified event publisher for single-agent and multi-agent orchestrations.
Initialize the EventPublisher.
Converts strands hook events into :class:StreamEvent objects and
delivers them to an external callback. Emits an AGENT_COMPLETE event at
the end of each invocation with usage metrics from EventLoopMetrics.
For TOKEN and REASONING events use :meth:as_callback_handler to
create a strands-compatible callback_handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
EventCallback
|
Called with each :class: |
required |
agent_name
|
str
|
Identifier for the agent or orchestrator. |
required |
tool_labels
|
dict[str, str] | None
|
Optional mapping of tool names to display labels. |
None
|
stream_group
|
str
|
Dot-path stream group for hierarchical activity attribution. Empty string when not configured. |
''
|
stream_title
|
str
|
Human-readable title for the stream group. |
''
|
max_result_len
|
int
|
Maximum character length for tool result text in TOOL_END events. Default: 16,000. |
_MAX_RESULT_LEN
|
Example::
publisher = EventPublisher(callback=on_event, agent_name="analyzer")
agent = Agent(
hooks=[publisher],
callback_handler=publisher.as_callback_handler(),
)
Source code in src/kaboo_workflows/hooks/event_publisher.py
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | |
mark_inline_chat_owner ¶
mark_inline_chat_owner()
Flag this publisher's group as the inline chat owner (see __init__).
Source code in src/kaboo_workflows/hooks/event_publisher.py
245 246 247 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Register hook callbacks for agent and multiagent events.
Source code in src/kaboo_workflows/hooks/event_publisher.py
270 271 272 273 274 275 276 277 278 279 280 281 282 283 | |
as_callback_handler ¶
as_callback_handler()
Return a strands-compatible callback_handler for TOKEN, REASONING, and HANDOFF events.
Handles the following kwarg patterns emitted by strands:
- data (str): A streamed text chunk -> TOKEN event.
- reasoningText (str): A reasoning chunk -> REASONING event.
- type == "multiagent_handoff": A :class:~strands.types._events.MultiAgentHandoffEvent
fired during Swarm/Graph node transitions -> HANDOFF event.
Returns:
| Type | Description |
|---|---|
Callable[..., None]
|
A callable compatible with strands |
Source code in src/kaboo_workflows/hooks/event_publisher.py
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 | |
ForwardedPropsHook ¶
ForwardedPropsHook(
*, apply_agent_config=False, state_key="forwarded_props"
)
Bases: HookProvider
Copy forwarded props into agent state; optionally apply agent overrides.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
apply_agent_config
|
bool
|
When |
False
|
state_key
|
str
|
Agent-state key the props are stored under. |
'forwarded_props'
|
Source code in src/kaboo_workflows/hooks/forwarded_props_hook.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
HistoryHook ¶
HistoryHook(key, *, enabled, policy=None)
Bases: HookProvider
Seeds an agent's history from, and captures it back into, the request's
:class:~kaboo_workflows._context.HistoryExchange.
One instance is attached per agent, carrying that agent's history key
(its shared-transcript bucket or stable dot-path) and whether history is
enabled for it.
Initialize the HistoryHook.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
History bucket key for this agent. Agents that share a
|
required |
enabled
|
bool
|
Whether this agent remembers across turns of the same
conversation. When |
required |
policy
|
HistoryPolicy | None
|
How the written-back transcript is shaped. Defaults to writing it back in full. |
None
|
Source code in src/kaboo_workflows/hooks/history_hook.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Register seed (before) and capture (after) invocation callbacks.
The capture is asynchronous only for a policy that summarises, since only then does it call a model.
Source code in src/kaboo_workflows/hooks/history_hook.py
121 122 123 124 125 126 127 128 129 130 131 132 | |
HistoryPolicy
dataclass
¶
HistoryPolicy(
keep="full",
min_chars=DEFAULT_MIN_CHARS,
keep_tools=frozenset(),
summarize_over=None,
summarizer=None,
)
How a :class:HistoryHook shapes the transcript it writes back.
Attributes:
| Name | Type | Description |
|---|---|---|
keep |
Literal['full', 'masked']
|
|
min_chars |
int
|
Tool results shorter than this survive masking. |
keep_tools |
frozenset[str]
|
Tools whose results survive masking. A delegate's always do. |
summarize_over |
int | None
|
Estimated tokens a masked copy may reach before its
oldest calls are summarised. |
summarizer |
Model | None
|
The model that writes the summary. |
GateRule
dataclass
¶
GateRule(pattern, remember=None, subject=None)
Which tools a gate covers, and how far a decision about them may reach.
Attributes:
| Name | Type | Description |
|---|---|---|
pattern |
str
|
Tool name or |
remember |
str | None
|
Widest scope for these tools, or |
subject |
SubjectFn | None
|
Callable |
InterruptHook ¶
InterruptHook(
tools,
*,
agent_name="",
ttl_seconds=None,
skip_read_only=False,
remember="once",
)
Bases: HookProvider
Gate tool calls on the person's approval via strands interrupts.
When the agent calls a tool matching one of tools, this hook raises an
approval request (event.interrupt()) describing the call, the subject a
remembered decision would cover and the scopes the person may choose. The
call runs only when the answer is approved; anything else — a denial,
a skip, an expired request — cancels it with a message the model can read.
A permission the host remembers (forwardedProps.kaboo_grants) for the
same tool and subject lets the call run without asking. Scopes wider than
once are offered only when the host remembers decisions and the run
publishes its open requests to the host.
An approval may carry an edited tool_input (AG-UI approveWithEdits):
the call then runs with the person's arguments instead of the agent's.
Configure which tool calls wait for approval.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tools
|
list[str | GateRule]
|
Tool names, globs or :class: |
required |
agent_name
|
str
|
The agent's name. |
''
|
ttl_seconds
|
int | None
|
Optional deadline for each request. An answer arriving after it does not run the call. |
None
|
skip_read_only
|
bool
|
Let tools annotated |
False
|
remember
|
str
|
Default widest scope: |
'once'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If a scope is not one of :data: |
Source code in src/kaboo_workflows/hooks/interrupt_hook.py
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | |
MaxToolCallsGuard ¶
MaxToolCallsGuard(max_calls=25)
Bases: HookProvider
Stops the agent after a maximum number of tool calls per invocation.
Initialize the MaxToolCallsGuard.
On first violation the LLM is instructed to stop using tools and write a final answer (graceful shutdown). If the LLM ignores that and requests another tool call, the event loop is terminated immediately (hard stop).
Uses strands' invocation_state dict for per-invocation state — the
counter and flags reset automatically on each new agent() call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_calls
|
int
|
Maximum tool calls allowed per invocation. Default: 25. |
25
|
Source code in src/kaboo_workflows/hooks/max_calls_guard.py
40 41 42 43 44 45 46 47 48 49 50 51 52 53 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Register the BeforeToolCallEvent callback.
Source code in src/kaboo_workflows/hooks/max_calls_guard.py
55 56 57 58 | |
MCPCallMetaHook ¶
MCPCallMetaHook(
provider=None,
*,
stamp_tool_call_id=True,
stamp_actor=False,
)
Bases: HookProvider
Attach per-call _meta to every MCP tool call an agent makes.
On :class:~strands.hooks.events.BeforeToolCallEvent — which fires in the
caller's request context — the hook computes the metadata for this call and
swaps event.selected_tool for a delegate that forwards it via
MCPClient.call_tool_async(meta=...) (the documented tool-replacement
seam of BeforeToolCallEvent). Non-MCP tools are untouched.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
Any | None
|
Callable |
None
|
stamp_tool_call_id
|
bool
|
When |
True
|
stamp_actor
|
bool
|
When |
False
|
Source code in src/kaboo_workflows/hooks/mcp_meta_hook.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
ReferenceHook ¶
ReferenceHook(*, enabled, inline, tool_enabled)
Bases: HookProvider
Injects the reference manifest into an in-scope agent before each invocation.
One instance is attached per agent, carrying that agent's policy: whether it is in scope at all, whether it also gets inline media, and whether the resolver tool is available (for the manifest wording).
Initialize the ReferenceHook.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Whether this agent is in scope for references. When
|
required |
inline
|
bool
|
Whether to additionally prepend resolved media
|
required |
tool_enabled
|
bool
|
Whether the built-in reference tools are registered (affects the manifest's guidance wording only). |
required |
Source code in src/kaboo_workflows/hooks/reference_hook.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Register manifest (before-invocation) + inline media (before-model) callbacks.
The before-model callback is registered only for inline agents; media
the model fetches with fetch_attachment comes back in that tool's
result, so it needs no injection.
Source code in src/kaboo_workflows/hooks/reference_hook.py
95 96 97 98 99 100 101 102 103 104 105 | |
SessionStateHook ¶
Bases: HookProvider
Restore client-supplied interrupt state onto the executing agent.
Also records that agent on the request's
:class:~kaboo_workflows._context.SessionExchange, so the adapter can
serialize its state once the run has paused or finished.
StateEffectCall
dataclass
¶
StateEffectCall(tool, input, result, agent, run_id, state)
What a state effect's mapper receives about the call that succeeded.
Attributes:
| Name | Type | Description |
|---|---|---|
tool |
str
|
The tool's name. |
input |
Any
|
The call's arguments. |
result |
Any
|
The tool's result: its text parsed as JSON when it is JSON, otherwise the text. |
agent |
str
|
The agent that made the call. |
run_id |
str
|
The run the call belongs to. |
state |
dict[str, Any]
|
The shared state before this effect, to read and never modify. |
StateEffectsHook ¶
StateEffectsHook(*, agent_name='')
Bases: HookProvider
Apply the run's state effects after each successful tool call.
Initialize the hook.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent_name
|
str
|
The agent's name, handed to mappers. |
''
|
Source code in src/kaboo_workflows/hooks/state_effects.py
133 134 135 136 137 138 139 | |
MultiAgentStopGuard ¶
MultiAgentStopGuard(stop_check)
Bases: HookProvider
Cancels node execution when an external stop condition is met.
Initialize the MultiAgentStopGuard.
Counterpart to :class:StopGuard for multi-agent orchestrations.
Registers a BeforeNodeCallEvent callback on a Swarm or Graph's
hook registry. When the stop_check callable returns True, the
hook sets cancel_node to prevent the next node from starting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stop_check
|
Callable[[], bool]
|
Callable returning |
required |
Source code in src/kaboo_workflows/hooks/stop_guard.py
91 92 93 94 95 96 97 98 99 100 101 102 103 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Register the before-node-call guard.
Source code in src/kaboo_workflows/hooks/stop_guard.py
105 106 107 108 | |
StopGuard ¶
StopGuard(stop_check)
Bases: HookProvider
Cancels the agent's event loop when an external stop condition is met.
Initialize the StopGuard.
The stop condition is checked before every tool call. When it returns
True, the current tool is cancelled and the event loop is stopped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stop_check
|
Callable[[], bool]
|
Callable that returns
|
required |
Source code in src/kaboo_workflows/hooks/stop_guard.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Register the before-tool-call guard.
Source code in src/kaboo_workflows/hooks/stop_guard.py
45 46 47 48 | |
ToolApprovalHook ¶
ToolApprovalHook(*, agent_name='')
Bases: HookProvider
Let tools ask the person to approve a call.
Wraps each tool the agent calls. When a tool replies
with kaboo_approval instead of a result, the run pauses with an
approval request: the same one the gate raises, plus the tool's
request ID. The reply never reaches the model. On resume:
- Approved: the tool is called once more, with the person's edits if any, and its result is returned. If it asks again, the model reads that the call still needs approval, so the agent cannot loop.
- Denied, skipped or expired: the tool is not called again, and the model reads the same note as for a gated call.
The host must tell the tool's service about the decision before the run
continues (kaboo-runtime's onDecision). The service, not the agent,
decides whether the second call runs.
Configure the hook.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent_name
|
str
|
Agent name, used to name the approval request. |
''
|
Source code in src/kaboo_workflows/hooks/tool_approval_hook.py
163 164 165 166 167 168 169 | |
ToolAttachmentsHook ¶
Bases: HookProvider
Unpack attachments in a tool result into media blocks of that result.
Registered on every agent the config builds; a result without the field is
left untouched. An MCP result's structuredContent copy loses the field
too, so the base64 is not kept beside the media.
register_hooks ¶
register_hooks(registry, **kwargs)
Register the after-tool-call callback.
Source code in src/kaboo_workflows/hooks/tool_attachments_hook.py
111 112 113 114 | |
ToolNameSanitizer ¶
Bases: HookProvider
Strips model-injected artifacts from tool names so Strands can look them up.
Registers on: - AfterModelCallEvent: rewrites tool names in the model response message. - BeforeToolCallEvent: safety net — fixes or cancels still-garbled names.
register_hooks ¶
register_hooks(registry, **kwargs)
Register AfterModelCall and BeforeToolCall sanitization callbacks.
Source code in src/kaboo_workflows/hooks/tool_name_sanitizer.py
93 94 95 96 97 | |
restore_session_state ¶
restore_session_state(agent)
Seed agent with the interrupt state the client sent, if this run resumes.
Returns whether anything was restored. Only a resume restores: a fresh run's message supersedes any gate the state still holds, and a host that replays an older snapshot can hand over a gate already answered. A run restores at most once, so the adapter can call it on the resume path — where the state is needed before the run starts, to build the interrupt responses — and the hook can call it on every invocation without either clobbering the other, or a later invocation bringing back the gate the run has since answered.
Source code in src/kaboo_workflows/hooks/session_state_hook.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | |
session_state_snapshot ¶
session_state_snapshot()
Serialize the current request's agent state for the outgoing snapshot.
Returns None when there is nothing worth sending: no exchange, no agent
reached invocation, or the agent is not holding an interrupt. Writing an
empty state would still be correct, but it would add a key to every
snapshot of every run that never pauses.
Source code in src/kaboo_workflows/hooks/session_state_hook.py
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
stop_guard_from_event ¶
stop_guard_from_event(event=None)
Create a StopGuard backed by a threading.Event.
Convenience factory for the common pattern of using a
threading.Event as the external stop signal. Can be used to
wire stop-on-disconnect or for programmatic stop control.
Example::
guard, stop = stop_guard_from_event()
agent.hooks.add_hook(guard)
# later, from any thread:
stop.set() # agent stops at next tool-call boundary
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
Event | None
|
Optional pre-existing |
None
|
Returns:
| Type | Description |
|---|---|
tuple[StopGuard, Event]
|
Tuple of |
Source code in src/kaboo_workflows/hooks/stop_guard.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |