Custom Agent Components: Foreshadow & HypeΒΆ
Agent components let you bend an agentβs behaviour without touching its core engine. In this tutorial youβll meet two companions:
- ForeshadowingComponent β slips clues into state before the engine runs.
- CheerMeterComponent β keeps track of crowd energy after each pitch.
Both examples live in examples/06-agent-components/ so you can run and remix them immediately.
1. Foreshadowing before the punchlineΒΆ
Run the example:
Walkthrough:
ForeshadowingComponentextendsAgentComponentand overrideson_pre_evaluate.- The component inspects the incoming
StoryIdea, chooses a genre-specific clue, and saves it intoinputs.state. CampfireStoryEnginepulls that hint frominputs.stateto sprinkle foreshadowing into the finalStoryBeat.
Snippet:
class ForeshadowingComponent(AgentComponent):
async def on_pre_evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalInputs:
idea = StoryIdea(**inputs.artifacts[0].payload)
clue = self._choose_clue(idea.genre.lower())
self.sprinkle_count += 1
inputs.state["foreshadow"] = clue
inputs.state["sprinkle_count"] = self.sprinkle_count
return inputs
Why it works:
- Components mutate
EvalInputsbefore the engine executes. - State keys survive the call chain, enabling lightweight coordination tricks.
- You can keep private counters (
sprinkle_count) on the component instance for running totals.
Try it yourself:
- Add a new genre (e.g., βsci-fiβ) to the clue table.
- Modify the engine to branch on
sprinkle_countfor escalating twists. - Log the clue to
ctxfor tracing-friendly observability.
2. Measuring crowd energy after each pitchΒΆ
Run the example:
Walkthrough:
CheerMeterComponentoverrideson_post_evaluateandon_post_publish.- After each engine run it increments an
applause_level, stores a normalizedcrowd_energymetric, and appends a log entry. - The engine then uses
ctx.state["crowd_energy"]to switch between mellow and high-energy closing lines.
Snippet:
class CheerMeterComponent(AgentComponent):
async def on_post_evaluate(
self, agent, ctx, inputs: EvalInputs, result: EvalResult
) -> EvalResult:
self.applause_level += 1
crowd_energy = min(1.0, self.applause_level / 5)
result.metrics["crowd_energy"] = crowd_energy
result.logs.append(f"Crowd energy surged to {crowd_energy:.2f}")
result.state["crowd_energy"] = crowd_energy
return result
Why it works:
- Post-evaluate hooks can modify metrics, logs, or state before outputs reach the blackboard.
on_post_publishis perfect for side-channel effects (printing, instrumentation, telemetry).- Metrics added here surface automatically in tracing dashboards.
Try it yourself:
- Replace the print in
on_post_publishwith a Slack webhook call. - Reset
applause_levelwhenevercrowd_energyhits 1.0 to simulate encore cycles. - Attach the component to multiple agents to compare how each pitch deck performs.
Choosing the right hookΒΆ
| Hook | When it runs | Great for |
|---|---|---|
on_pre_consume | Before subscription filtering | Mutating incoming artifacts |
on_pre_evaluate | Before engine logic | Setting up state, caching, guardrails |
on_post_evaluate | After engine returns | Metrics, logs, result shaping |
on_post_publish | After artifacts hit the board | Notifications, analytics |
on_error | When an exception bubbles up | Cleanup, fallbacks, telemetry |
Remember that components are regular Pydantic models, so you can inject configuration fields, defaults, and type validation effortlessly.
Next steps:
- Pair these components with tracing (
FLOCK_AUTO_TRACE=true) to watch state flow through every hook. - Mix and match multiple components on a single agent to build rich behaviours without touching engine code.
- Dive deeper into lifecycle hooks in the Agent Components Guide.