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:
ForeshadowingComponent
extendsAgentComponent
and overrideson_pre_evaluate
.- The component inspects the incoming
StoryIdea
, chooses a genre-specific clue, and saves it intoinputs.state
. CampfireStoryEngine
pulls that hint frominputs.state
to 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
EvalInputs
before 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_count
for escalating twists. - Log the clue to
ctx
for tracing-friendly observability.
2. Measuring crowd energy after each pitchΒΆ
Run the example:
Walkthrough:
CheerMeterComponent
overrideson_post_evaluate
andon_post_publish
.- After each engine run it increments an
applause_level
, stores a normalizedcrowd_energy
metric, 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_publish
is 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_publish
with a Slack webhook call. - Reset
applause_level
whenevercrowd_energy
hits 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.