Learning Digest Fan-out
Pollen + Bloom event pattern: a weekly cron emits one signal per learner, each running as that user.
What this demonstrates
The learning example demonstrates the Pollen + Bloom fan-out pattern. A custom WeeklyDigestFanoutProducer fires every Monday at 06:00 UTC, enumerates active learners, and emits one weekly-digest.due signal per (tenant, user) pair. Each signal triggers a Bloom that runs under the addressed_to_user identity — the service account acts on each learner's RAG scope without impersonating them. Because parallelism: per_user is set, all digests run concurrently. Each user only sees their own digest (visibility: addressed).
This example does not include a separate orchid.yml; the event and agent configuration are self-contained in agents.yaml. Wire it into any orchid.yml via agents.config_path.
Run it
Wire agents.yaml into an orchid.yml with your storage and LLM settings:
pip install -e ./orchid -e ./orchid-api
# Point your orchid.yml at examples/learning/agents.yaml
ORCHID_CONFIG=<your-orchid.yml> uvicorn orchid_api.main:app --port 8000Or drive a single fan-out tick in a test without the scheduler:
pip install -e ./orchid -e ./orchid-cli
# See examples/learning/tests/ for the programmatic producer patternConfiguration walkthrough
agents.yaml defines the digest agent and the full event pipeline:
# agents.yaml (trimmed)
version: "1"
defaults:
llm:
model: "ollama/llama3.2"
temperature: 0.2
rag:
enabled: false
agents:
digest:
description: "Personalised weekly learning digest."
prompt: |
Build a short personalised digest for the named learner.
Sections: Highlights from last week, Recommended next steps,
One stretch goal. Keep it under 150 words.
rag:
enabled: false
execution_hints:
parallel_safe: true
events:
enabled: true
store:
class: orchid_ai.events.backends.sqlite.SQLiteEventStorage
extra_args:
dsn: /data/chats.db
queue:
class: orchid_ai.events.queues.sqlite.SQLiteSignalQueue
poll_interval_ms: 200
lease_seconds: 30
max_attempts: 2
producers:
- class: examples.learning.producers.weekly_digest.WeeklyDigestFanoutProducer
extra_args:
cron: "0 6 * * 1" # every Monday at 06:00 UTC
- class: orchid_ai.events.producers.internal.InternalEmissionProducer
processors:
- class: orchid_ai.events.processors.asyncio_pool.AsyncioWorkerPoolProcessor
concurrency: 4
triggers:
- id: weekly-digest
"on": { signal: weekly-digest.due }
emits:
agent: digest
prompt_template: |
Build a personalised weekly learning digest for user
{{user_id}} in tenant {{tenant_key}}, week {{payload.week_iso}}.
identity:
mode: addressed_to_user
service_account: digest-bot
user_id_from: signal.user_id
retry: { max: 2, backoff: exponential }
parallelism: per_user
# ...truncatedThe digest agent lives in agents/digest.md:
---
description: "Personalised weekly learning digest."
rag:
enabled: false
execution_hints:
parallel_safe: true
---
Build a short personalised digest for the named learner.
Sections: Highlights from last week, Recommended next steps,
One stretch goal. Keep it under 150 words.What to look for
producers[0].class: examples.learning.producers.weekly_digest.WeeklyDigestFanoutProducer→ a customOrchidSignalProducersubclass; the framework calls itstick()at each cron interval and it decides how many signals to emit.identity.mode: addressed_to_user→ the Bloom runs with the learner'suser_idin scope (for RAG scoping) without requiring the user to be logged in.parallelism: per_user→ one concurrent Bloom per(tenant, user)pair; the framework ensures two digests for the same user don't overlap.visibility: addressed(implicit default) → each user only sees their own digest via the API; other users in the same tenant cannot access it.concurrency: 4on the processor → up to four Blooms run in parallel within the process; tune this for your host capacity.