Skip to main content
Automation Suggestions turn recurring requests into scheduled jobs — proposed explicitly, or offered automatically when the assistant notices the same intent recurring — but only ever scheduled when you tap Accept. Two paths feed one consent gate — a manual propose() call, or an automatic observe() that fires once a request recurs.

Quick Start

1

Let the assistant offer it (feed each turn)

Feed each completed turn’s inferred blueprint + slots. After the same intent recurs a few times, the engine offers it — you still tap Accept.
Nothing runs until the user taps ✓ Accept. Below the threshold (default 3), observe() just counts and returns None.
2

See pending suggestions

Type /automations in chat. The bot lists each pending suggestion with its own Accept/Dismiss buttons.
3

Accept from chat

Tap the ✓ Accept button on a suggestion. Exactly one scheduled job is created — nothing runs until you accept.
4

Or create from a template

Skip suggestions and build a job straight from a blueprint:

How It Works

Two paths lead to the same result — a scheduled job — but only ever on an explicit accept.
Button taps travel through the shared callback contract: sug:accept:<id> accepts a suggestion, sug:dismiss:<id> dismisses it. The interactive registry decodes sug:accept:<id> as namespace sug with the remainder as the payload — a Telegram-side detail you never type by hand.

Auto-generating suggestions with observe()

observe() is the recurrence generator — a caller feeds every turn’s inferred intent, and the engine offers an automation once it recurs. Both entry points end in the same store and both require Accept — pick the one that matches who notices the recurrence. Use observe() when the assistant should notice recurrence itself (per-turn feed from a bot/gateway). Use propose() when your code already knows an automation is worth offering (manual CLI, dashboard, explicit rule). observe() is deterministic, not ML — a per-intent trailing count keyed on (principal, blueprint_name, frozen(slots)). Two module-level constants set its defaults:

Parameters

Example

observe() returns the new suggestion ID only when this observation crossed the threshold and the store admitted the proposal. It returns None in three cases: still below threshold, the store’s pending cap is full, or the store’s dedup window already holds this identical intent.
Counters are per-(principal, blueprint, slots), so one user’s recurrence can never trigger another’s suggestion. After a fire, the counter resets to empty and the store’s dedup window guards the identical intent — Accept-once, Dismiss-forever still holds for auto-proposals.

Tuning

Adjust threshold and window_seconds to fit the workflow:
Immediately after a fire, another observation with the same key is below-threshold again and the store dedups the identical intent, so there is no double-offer. self._observations is process-local best-effort memory — a restart merely resets counters; the durable state stays in the SuggestionStore.

Built-in Blueprints

Three blueprints ship ready to use, straight from blueprint_catalogue.py.
If your app already registers a custom /automations or /blueprint handler via @bot.on_command, that handler wins — the built-ins step aside for those two names only. All other built-in commands still take precedence.

Custom Blueprints (YAML)

Author your own blueprints under ~/.praisonai/blueprints/<name>/blueprint.yaml; a custom blueprint overrides a built-in of the same name.
Discovery scans each <name>/blueprint.yaml subdirectory — see BlueprintCatalogue._load_from_directory for the exact rule.

Chat Commands

/automations

List pending suggestions with inline ✓ Accept / ✕ Dismiss buttons. Telegram only today. Full detail in Bot Chat Commands.

/blueprint

Create an automation from a template: /blueprint <name> [slot=value ...]. Telegram only today. Full detail in Bot Chat Commands.

CLI Commands

Drive the same engine from the shell — full flags in Schedule CLI. observe() is a Python-level generator called from your bot/gateway; there is no CLI entry point today. Use praisonai schedule suggestion-propose <blueprint> for a one-shot manual proposal.

Python Usage

Propose and accept a suggestion programmatically.
propose() returns None when the pending cap (20) is reached or the same blueprint + slots was suggested within the 24-hour dedup window. On a bot gateway the engine reads SessionContext.unified_user_id for you. Pass principal= yourself only from a custom script that has no session installed.

User Interaction Flows

Flow A — Accept an existing suggestion in chat:
Flow B — Create directly from a blueprint:
Flow D — Auto-offered after a recurring request:
Mapping natural-language turns → blueprint_name + slots is the caller’s job (bot/gateway), not something observe() infers by itself. Flow C — Two users, one bot, no cross-talk (multi-tenant): On a bot gateway you don’t call this by hand — the gateway installs a SessionContext per turn, and SuggestionEngine.propose(...) reads it automatically. The pseudo-code below shows what the store sees end-to-end.
Before per-principal scoping, Bob’s identical proposal was silently blocked by Alice’s dedup entry, and either user could accept or dismiss the other’s suggestion.

Multi-tenant isolation

Each end-user sees only their own suggestions once the gateway passes their resolved identity as principal=. Alice and Bob can share one gateway yet never see or mutate each other’s pending queue; a single-user deployment leaves principal=None and keeps the global pool. principal is any stable, opaque string that names the caller — a user id, an email hash, a tenant id. The store never parses or validates it; it only compares it. Leave it unset (None) for the global, single-tenant behaviour used by the CLI and single-user deployments. A gateway handler that already knows who’s calling threads that identity through as principal:
Cross-owner access is rejected, not silently merged:
  • list_pending(principal="alice") never returns Bob’s suggestions.
  • accept(sug_id, principal="alice") returns False if the suggestion is Bob’s.
  • dismiss(sug_id, principal="alice") returns False if the suggestion is Bob’s.
  • The pending cap (MAX_PENDING_CAP, 20) and 24-hour dedup window are measured per principal, so one tenant can neither exhaust another’s slots nor block their identical proposal.
There is no admin bypass — a call passing principal="bob" cannot see Alice’s items even when the process runs as root. Scoping is enforced only by the principal the caller passes.

Safe by Default

The suggestion store’s isolation is automatic on any bot gateway — the gateway threads SessionContext.unified_user_id through as principal= on every read/write call, so one user never sees or mutates another’s queue. When you use the store’s API directly (custom scripts, no gateway), you own the identity: pass principal= yourself, or leave it None for the global pool used by CLI / single-user deployments.
Since PR #3507, Suggestion carries an optional principal owner and the store isolates list_pending / accept / dismiss per owner. See Per-User Scheduler Isolation for the full multi-tenant model. Access is still gated by CommandAccessPolicy (the automations permission is re-checked on every accept/dismiss tap). Nothing is ever auto-created — the engine only materialises a job on an explicit accept. MAX_PENDING_CAP (20), the 24-hour dedup window, and the 3-day TTL are enforced within each principal, so one tenant cannot exhaust the cap or shadow another’s suggestion. Leaving principal=None measures them against the global pool.

Platform Coverage

Telegram-only today. Discord, Slack, and WhatsApp adapters do not register /automations or /blueprint yet — parity is tracked in future PRs. The shared helper module keeps the accept/dismiss/blueprint glue in one place so those adapters can wire it later.
The commands also degrade gracefully: if the scheduler extra isn’t installed, they reply ❌ Automations are not available (scheduler not installed).

Best Practices

Add one engine.observe(...) call per completed turn, passing the inferred blueprint + slots. That single line is what makes the assistant offer automations without a manual propose(). Thread principal= from SessionContext.unified_user_id so recurrence counts stay isolated per user.
Use threshold=1 for a fast signal that fires on the first observation (handy for testing). Use threshold=5, window_seconds=3600 * 24 * 30 when you want stronger evidence over a 30-day window before offering. The recurrence counter is process-local in-memory state, so a restart resets it — only the SuggestionStore is durable.
For multi-user gateways, pass the resolved end-user identity as principal= when reading or mutating suggestions — one user’s pending queue is then invisible and non-mutable to another’s. See Per-User Scheduler Isolation for the full multi-tenant model.
Store-level enforcement (per-principal pending cap + dedup window) means one tenant cannot exhaust the queue or shadow another’s suggestion. Leaving principal=None preserves the pre-scoping global pool for single-user deployments.
As of PR #3786, list_suggestions / accept_suggestion / dismiss_suggestion in _automations.py all thread SessionContext.unified_user_id as principal=, so per-user isolation happens without any wiring on your side. If you still want an admin-only gate on /automations for policy reasons — for example, limiting who may create automations at all — configure CommandAccessPolicy. That is orthogonal to per-user isolation: it gates whether a user may use automations, not whose they see. See Command Access Control and Per-User Scheduler Isolation.
When a suggestion already exists for a pattern, accept it rather than running /blueprint — the dedup key latches on accept and avoids duplicate jobs.
Drop a blueprint.yaml in ~/.praisonai/blueprints/<name>/ to codify team-specific automations. A custom blueprint overrides a built-in of the same name.
For monitoring blueprints like important-mail, add a --pre-run gate to the resulting job so ticks with no new work spend no tokens. See the pre-run gate in the Schedule CLI.

Bot Chat Commands

Full /automations and /blueprint command details.

Schedule CLI

Blueprint and suggestion subcommands with every flag.

Async Scheduler

Recurring jobs with the same per-owner principal isolation.

Proactive Delivery

Home channels and delivery tokens for scheduled jobs.

Scheduled Run Policy

Guardrails and the pre-run gate for unattended runs.