Context
PraisonAI PR #2186 (merged 2026-06-23, fixes #2185) makes ChannelDirectory (src/praisonai/praisonai/bots/delivery.py) durable and refreshable. Previously it was purely in-memory and built opportunistically from observed traffic — reachable targets and friendly aliases were lost on every restart and never refreshed from live adapters.
The behaviour change is user-visible (new files on disk, new constructor args, new behaviour for describe_targets(), new methods on DeliveryRouter) and is not reflected anywhere in the docs today.
Decision: update existing pages (no new page)
The feature is an enhancement to the existing ChannelDirectory / DeliveryRouter system, which is already documented across three pages. Creating a new page would fragment the story. Update the existing pages instead.
Per AGENTS.md placement rules: all edits below are under docs/features/ — no docs/concepts/ edits.
SDK ground truth (read before writing)
Source file (PraisonAI repo): src/praisonai/praisonai/bots/delivery.py (on main at SHA f7ea543).
In this PraisonAIDocs repo, the synced mirror is at praisonai/bots/delivery.py.
New ChannelDirectory constructor signature
ChannelDirectory(
persist_path: Optional[Path] = None, # default: ~/.praisonai/state/channel_directory.json
aliases_path: Optional[Path] = None, # default: ~/.praisonai/state/channel_aliases.json
)
| Arg |
Type |
Default |
Description |
persist_path |
Optional[Path] |
~/.praisonai/state/channel_directory.json |
Where home + observed channels are saved. Atomic write via temp file + os.replace. |
aliases_path |
Optional[Path] |
~/.praisonai/state/channel_aliases.json |
Hand-editable alias overlay re-applied on every load + refresh. |
Both paths sit alongside HomeChannelRegistry under ~/.praisonai/state/. Persistence is best-effort — failures are logged and never raised.
New + changed behaviour
| Method / Behaviour |
What it does |
__init__ |
Calls _load() then _apply_alias_overlay() so a fresh process starts pre-populated. |
set_home_channel(platform, channel_id) |
Now also auto-persists. |
observe_channel(platform, channel_id) |
Now auto-persists (no-op when the channel is already known, to avoid write storms). |
refresh_from_adapters(adapters: Dict[str, Any]) |
New. Enumerates each adapter that exposes a list_channels() method (e.g. Discord/Slack), merges results into the observed set, re-applies the alias overlay, persists. Adapter errors are caught and skipped. Channel IDs are read via getattr(ch, "id", ch) so plain-string IDs also work. |
describe_targets() |
Now also surfaces observed channels not represented by a home or alias, as {'name': '<platform>:<channel_id>', 'platform': ..., 'channel_id': ..., 'kind': 'observed'}. |
_apply_alias_overlay() |
Reads the overlay file on every load + refresh. Aliases sourced from the overlay are tracked in _overlay_aliases so deletions from the file are honoured on the next refresh (manually-added aliases via add_alias() are never pruned). Aliased channels are also added to _observed so they become reachable even without prior traffic. |
New DeliveryRouter method
DeliveryRouter.refresh_directory() -> None
Walks self._botos.list_bots(), calls self._botos.get_bot(platform) for each, builds an {platform: adapter} dict and calls self.directory.refresh_from_adapters(...). Intended to be called periodically by the gateway's background loop.
Alias overlay file format (~/.praisonai/state/channel_aliases.json)
Two accepted shapes per entry — long form and shorthand. Invalid entries (missing channel_id, malformed shorthand without :) are skipped with a warning, not raised.
{
"engineering": { "platform": "discord", "channel_id": "555" },
"ops": "slack:C111"
}
A kind: "observed" entry from describe_targets() looks like:
{"name": "discord:777", "platform": "discord", "channel_id": "777", "kind": "observed"}
Backward compatibility
Fully backward-compatible. Default ChannelDirectory() / DeliveryRouter(botos) construction is unchanged — both new args are optional with sensible defaults.
Pages to update
1. docs/features/proactive-delivery.mdx — primary update
This is the main page for ChannelDirectory + DeliveryRouter. Add a new section after "Configuration" (around current line 168), before "Common Patterns".
Add: "Persistence" section (one-sentence intro, then content):
- Explain that home + observed channels auto-persist to
~/.praisonai/state/channel_directory.json and reload on start — reachable targets survive restarts.
- Show the directory after a restart in a code block: build a
ChannelDirectory(), call set_home_channel + observe_channel, restart, show the same channels still resolvable.
- Note: persistence is best-effort; failures log, never raise.
- Custom paths example:
ChannelDirectory(persist_path=Path("/var/lib/myapp/dir.json"), aliases_path=Path("/var/lib/myapp/aliases.json")).
Add: "Pre-naming channels (alias overlay)" section:
- One-sentence intro: "Drop a JSON file at
~/.praisonai/state/channel_aliases.json to pre-name channels before they produce any traffic."
- Show both accepted forms (long + shorthand) in one JSON block.
- Show how an overlay-defined alias makes the channel reachable even with no prior traffic.
- Note: editing the file and calling
refresh_directory() (or restarting) honours deletions; manually-added aliases via add_alias() are not pruned by the overlay.
Add: "Refreshing from live adapters" section:
- Show
botos.delivery_router.refresh_directory() driven from the gateway background loop.
- Show
directory.refresh_from_adapters({"discord": discord_bot, "slack": slack_bot}) for the lower-level API.
- Explain the contract: adapters must expose
list_channels() returning iterables of objects with .id (or plain strings). Adapters without list_channels are silently skipped; adapter errors are caught.
Update: "How It Works" target table (around line 111): add a row for kind: "observed":
| Target |
Resolves to |
Example |
<platform>:<id> |
Observed channel from traffic or adapter refresh (no alias / not home) |
Surfaced in describe_targets() as kind: "observed" |
Add: Mermaid diagram showing the persistence + refresh lifecycle (use standard colour scheme from AGENTS.md §3.1):
Start → Load (channel_directory.json) → Apply overlay (channel_aliases.json)
→ Observe traffic / Refresh from adapters → Save → ...
2. docs/features/platform-aware-agents.mdx
Two small edits:
ReachableTarget table (around line 156): change kind row description to include "observed": 'home', 'alias', or 'observed'.
- "How It Works" target listing: note that
describe_targets() now also surfaces reachable-but-unnamed channels from live traffic + adapter refresh.
3. docs/features/channels-gateway.mdx
Update the "Reachable Targets" section (around line 361):
- After the existing
describe_targets() table, add a third row for kind: "observed": surfaced from live traffic or refresh_from_adapters(), appears as "<platform>:<channel_id>".
- Add a short paragraph + code snippet showing the gateway calling
botos.delivery_router.refresh_directory() on a background loop so the agent sees channels it has not yet been messaged from.
User-facing flow to show in each page
Each updated section should demonstrate the agent-centric flow (per AGENTS.md §1.1 #9):
- User asks the agent: "Post a summary to the engineering channel".
- Agent has never received traffic from that channel — previously it would not have been reachable.
- With this change: the channel is reachable because either (a)
channel_aliases.json pre-named it, or (b) the gateway's periodic refresh_directory() already enumerated it via the Discord adapter's list_channels().
deliver("engineering", summary) succeeds.
Style requirements (from AGENTS.md)
- Place all edits under
docs/features/ only.
- Use Mintlify components (
<Steps>, <AccordionGroup>, <Tabs>, <Card>).
- Mermaid diagrams use the standard colour palette (
#8B0000, #189AB4, #10B981, #F59E0B, #6366F1, white text, #7C90A0 strokes).
- Imports stay simple and user-facing:
from praisonai.bots.delivery import ChannelDirectory, from praisonai.bots import BotOS, TelegramBot.
- One-sentence section intros; active voice; no forbidden phrases ("In this section…", "As you can see…", etc.).
- Code examples must run as-is. Use realistic but simple channel IDs (
"C0123456", "555").
Acceptance checklist
References
Context
PraisonAI PR #2186 (merged 2026-06-23, fixes #2185) makes
ChannelDirectory(src/praisonai/praisonai/bots/delivery.py) durable and refreshable. Previously it was purely in-memory and built opportunistically from observed traffic — reachable targets and friendly aliases were lost on every restart and never refreshed from live adapters.The behaviour change is user-visible (new files on disk, new constructor args, new behaviour for
describe_targets(), new methods onDeliveryRouter) and is not reflected anywhere in the docs today.Decision: update existing pages (no new page)
The feature is an enhancement to the existing
ChannelDirectory/DeliveryRoutersystem, which is already documented across three pages. Creating a new page would fragment the story. Update the existing pages instead.Per
AGENTS.mdplacement rules: all edits below are underdocs/features/— nodocs/concepts/edits.SDK ground truth (read before writing)
Source file (PraisonAI repo):
src/praisonai/praisonai/bots/delivery.py(onmainat SHAf7ea543).In this PraisonAIDocs repo, the synced mirror is at
praisonai/bots/delivery.py.New
ChannelDirectoryconstructor signaturepersist_pathOptional[Path]~/.praisonai/state/channel_directory.jsonos.replace.aliases_pathOptional[Path]~/.praisonai/state/channel_aliases.jsonBoth paths sit alongside
HomeChannelRegistryunder~/.praisonai/state/. Persistence is best-effort — failures are logged and never raised.New + changed behaviour
__init___load()then_apply_alias_overlay()so a fresh process starts pre-populated.set_home_channel(platform, channel_id)observe_channel(platform, channel_id)refresh_from_adapters(adapters: Dict[str, Any])list_channels()method (e.g. Discord/Slack), merges results into the observed set, re-applies the alias overlay, persists. Adapter errors are caught and skipped. Channel IDs are read viagetattr(ch, "id", ch)so plain-string IDs also work.describe_targets(){'name': '<platform>:<channel_id>', 'platform': ..., 'channel_id': ..., 'kind': 'observed'}._apply_alias_overlay()_overlay_aliasesso deletions from the file are honoured on the next refresh (manually-added aliases viaadd_alias()are never pruned). Aliased channels are also added to_observedso they become reachable even without prior traffic.New
DeliveryRoutermethodWalks
self._botos.list_bots(), callsself._botos.get_bot(platform)for each, builds an{platform: adapter}dict and callsself.directory.refresh_from_adapters(...). Intended to be called periodically by the gateway's background loop.Alias overlay file format (
~/.praisonai/state/channel_aliases.json)Two accepted shapes per entry — long form and shorthand. Invalid entries (missing
channel_id, malformed shorthand without:) are skipped with a warning, not raised.{ "engineering": { "platform": "discord", "channel_id": "555" }, "ops": "slack:C111" }A
kind: "observed"entry fromdescribe_targets()looks like:{"name": "discord:777", "platform": "discord", "channel_id": "777", "kind": "observed"}Backward compatibility
Fully backward-compatible. Default
ChannelDirectory()/DeliveryRouter(botos)construction is unchanged — both new args are optional with sensible defaults.Pages to update
1.
docs/features/proactive-delivery.mdx— primary updateThis is the main page for
ChannelDirectory+DeliveryRouter. Add a new section after "Configuration" (around current line 168), before "Common Patterns".Add: "Persistence" section (one-sentence intro, then content):
~/.praisonai/state/channel_directory.jsonand reload on start — reachable targets survive restarts.ChannelDirectory(), callset_home_channel+observe_channel, restart, show the same channels still resolvable.ChannelDirectory(persist_path=Path("/var/lib/myapp/dir.json"), aliases_path=Path("/var/lib/myapp/aliases.json")).Add: "Pre-naming channels (alias overlay)" section:
~/.praisonai/state/channel_aliases.jsonto pre-name channels before they produce any traffic."refresh_directory()(or restarting) honours deletions; manually-added aliases viaadd_alias()are not pruned by the overlay.Add: "Refreshing from live adapters" section:
botos.delivery_router.refresh_directory()driven from the gateway background loop.directory.refresh_from_adapters({"discord": discord_bot, "slack": slack_bot})for the lower-level API.list_channels()returning iterables of objects with.id(or plain strings). Adapters withoutlist_channelsare silently skipped; adapter errors are caught.Update: "How It Works" target table (around line 111): add a row for
kind: "observed":<platform>:<id>describe_targets()askind: "observed"Add: Mermaid diagram showing the persistence + refresh lifecycle (use standard colour scheme from
AGENTS.md§3.1):2.
docs/features/platform-aware-agents.mdxTwo small edits:
ReachableTargettable (around line 156): changekindrow description to include"observed":'home','alias', or'observed'.describe_targets()now also surfaces reachable-but-unnamed channels from live traffic + adapter refresh.3.
docs/features/channels-gateway.mdxUpdate the "Reachable Targets" section (around line 361):
describe_targets()table, add a third row forkind: "observed": surfaced from live traffic orrefresh_from_adapters(), appears as"<platform>:<channel_id>".botos.delivery_router.refresh_directory()on a background loop so the agent sees channels it has not yet been messaged from.User-facing flow to show in each page
Each updated section should demonstrate the agent-centric flow (per
AGENTS.md§1.1 #9):channel_aliases.jsonpre-named it, or (b) the gateway's periodicrefresh_directory()already enumerated it via the Discord adapter'slist_channels().deliver("engineering", summary)succeeds.Style requirements (from
AGENTS.md)docs/features/only.<Steps>,<AccordionGroup>,<Tabs>,<Card>).#8B0000,#189AB4,#10B981,#F59E0B,#6366F1, white text,#7C90A0strokes).from praisonai.bots.delivery import ChannelDirectory,from praisonai.bots import BotOS, TelegramBot."C0123456","555").Acceptance checklist
docs/features/proactive-delivery.mdxhas new "Persistence", "Pre-naming channels (alias overlay)" and "Refreshing from live adapters" sections.docs/features/proactive-delivery.mdx"How It Works" table coverskind: "observed".docs/features/proactive-delivery.mdxhas a Mermaid diagram of the persist + load + overlay + refresh lifecycle.docs/features/platform-aware-agents.mdxReachableTarget.kindlists"home","alias","observed".docs/features/channels-gateway.mdx"Reachable Targets" section covers observed channels +refresh_directory().docs/concepts/(human-only perAGENTS.md§1.8).docs.jsonunchanged (no new pages; existing pages stay in their current groups).praisonai/bots/delivery.pyin this repo.References
src/praisonai/praisonai/bots/delivery.py@f7ea543praisonai/bots/delivery.pydocs/features/proactive-delivery.mdx,docs/features/platform-aware-agents.mdx,docs/features/channels-gateway.mdx