Agents
Guardian Angel
Subscription and runner pattern — steer the primary processor via inject_message without coupling.
The guardian angel pattern adds oversight to agent monologues without modifying the primary processor. A subscription actor watches monologue traffic; when a filter matches, it spawns an ephemeral runner that can inject steering messages.
The primary processor has no knowledge of guardians.
Architecture
Monologue message posted
│
▼
guardianSubscription (filter matches)
│
▼
spawn guardianAngel runner instance
│
▼
runner: ctx.llm.infer with inject_message tool
│
▼
post steering message to monologue (meta.guardianInject)
│
▼
processor handles it like any other messagedefineGuardianAngel
import {
defineGuardianAngel,
agentSaidV1Filter,
} from "@yoke/sdk";
const guardianAngel = defineGuardianAngel({
filter: agentSaidV1Filter,
model: "guardian-steer",
});
// In yoke.ts actors:
guardianSubscription: actor(guardianAngel.subscription),
guardianAngel: actor(guardianAngel.runner),Subscription actor
- Filters
conversation.message.postedon the monologue - Runs user-defined filter (e.g.
agentSaidV1Filter— agent spoke withsaysv1 part) - Tracks processed message ids in
ctx.state - Spawns ephemeral
guardianAngelrunner viactx.instances.create - Emits
guardian.runevent to trigger runner
Runner actor
- One-shot instance per guardian pass
- Filters on
guardian.run - Calls
ctx.llm.inferwithinject_messagetool - Posts steering text to monologue with
GUARDIAN_INJECT_META_KEYin body meta - Self-destructs after completion
inject_message tool
Built into the runner definition:
defineTool({
name: "inject_message",
description: "Inject a steering message into the agent monologue",
parameters: z.object({ text: z.string() }),
execute: async (input, toolCtx) => {
// Posts to monologue with meta.guardianInject
},
});Join with guardian
await joinConversation(ctx, agent, forumId, {
model: "gpt-4o",
guardianAngel: true,
// or:
guardianAngel: { model: "claude-sonnet-4", systemPrompt: "..." },
});Creates guardianSubscription instance scoped to the monologue.
Default system prompt
export const DEFAULT_GUARDIAN_SYSTEM_PROMPT = `You are a guardian angel overseeing a primary agent's monologue.
Use inject_message only when the agent needs correction or guidance.`;Override via defineGuardianAngel({ systemPrompt: "..." }).
Test provider
createGuardianSteerTestProvider + model guardian-steer for tests without API keys.
Design rationale
| Benefit | How |
|---|---|
| Separation of concerns | Processor stays simple; guardian is opt-in per join |
| Ephemeral runners | No long-lived guardian state polluting processor |
| Filter flexibility | Any monologue event pattern can trigger oversight |
| No mirror relations | Steering uses normal monologue messages |