Bug
Dashboard notifications can be written to a different notification store than the one the live dashboard is watching.
AO currently stores dashboard notifications using a path derived from the raw configPath:
getDashboardNotificationStorePath(configPath)
In the hybrid config model, AO may use both:
<project>/agent-orchestrator.yaml
and:
~/.agent-orchestrator/config.yaml
for the same running AO instance. Because the notification store is config-path-scoped, those two config files produce different dashboard notification stores.
Source: local operator report/chat on 2026-05-21
Reported by: Adil
Analyzed against: remote main 50dc18ffa1fe1e7944e214a5f854fba87b1afe61
Confidence: High — observed mismatch between running.json.configPath, dashboard process env, and the notification store that received the test notification.
Observed Behavior
The live AO daemon/dashboard was running with:
running.configPath = /Users/adilshaikh/Desktop/ao/agent-orchestrator.yaml
So the dashboard watched:
~/.agent-orchestrator/1686e4aaaeaa-observability/dashboard-notifications.jsonl
But a dashboard notification was delivered using the global config:
/Users/adilshaikh/.agent-orchestrator/config.yaml
So it was written to:
~/.agent-orchestrator/631d47de14e1-observability/dashboard-notifications.jsonl
The notification was successfully persisted, but the dashboard did not show it because it was watching a different file.
In plain terms:
The notification went to mailbox B, while the dashboard was checking mailbox A.
Reproduction
- Start AO from a project-local config:
cd /path/to/project
ao start
- Ensure the dashboard is running and
running.json points to the project-local config:
{
"configPath": "/path/to/project/agent-orchestrator.yaml",
"port": 3000
}
- Trigger a dashboard notification from a path/context that resolves the global config, for example:
AO_CONFIG_PATH=~/.agent-orchestrator/config.yaml ao notify test --to dashboard
or run a notifier path that loads global config while the dashboard was started from local config.
-
The notification is written to the global-config-derived dashboard store.
-
The live dashboard does not show the notification because it reads the local-config-derived store.
Root Cause
Dashboard notification persistence is scoped by raw config file path, but the dashboard itself is scoped by the running AO daemon.
This is no longer equivalent after the global/root YAML split and multi-project dashboard model.
One live AO dashboard may supervise multiple projects, while notification producers may resolve either the global config or a project-local config. If those paths differ, dashboard notifications split across multiple stores.
Impact
- Dashboard notifications can silently not appear.
- Manual tests such as
ao notify test --to dashboard may report success while the visible dashboard shows nothing.
- Notifications are persisted on disk but in the wrong store.
- Multi-project setups are especially vulnerable because one dashboard can supervise several projects but config-path-scoped stores can fragment notification streams.
- Users lose trust in dashboard notifications because delivery appears successful but the UI does not update.
Recommended Fix
Make dashboard notifications daemon-scoped, not raw-config-path-scoped.
Desired model
One running dashboard/daemon should have one notification stream.
Each notification record already includes:
{
"projectId": "...",
"sessionId": "...",
"type": "...",
"priority": "..."
}
So a single daemon-level store can support multiple projects while still allowing the dashboard to filter/group by project.
Implementation
Add a derived daemon-level dashboard notification store path to running.json.
Example shape:
{
"pid": 1720,
"configPath": "/path/to/project/agent-orchestrator.yaml",
"port": 3000,
"projects": ["project-a", "project-b"],
"dashboardNotificationStore": "<derived-ao-base-dir>/dashboard-notifications.jsonl"
}
Important: the path should not be hardcoded. It should be derived through AO path helpers, e.g. a new helper like:
getDaemonDashboardNotificationStorePath()
internally based on AO's base dir.
Then:
- Dashboard
NotificationBroadcaster reads from running.dashboardNotificationStore.
- Dashboard notifier writes to
running.dashboardNotificationStore when a live daemon exists.
ao notify test --to dashboard uses the live daemon store when a daemon is running.
- If no live daemon exists, fall back to the existing config-scoped store or warn that no live dashboard is available.
Why this is better
- Matches user expectation: notifications go to the dashboard currently running.
- Avoids split stores between global config and project-local config.
- Works for multi-project dashboards.
- Avoids reading all notification stores and accidentally showing stale/unrelated test notifications.
- Keeps notification records project-aware through existing
projectId/sessionId fields.
Related
Bug
Dashboard notifications can be written to a different notification store than the one the live dashboard is watching.
AO currently stores dashboard notifications using a path derived from the raw
configPath:In the hybrid config model, AO may use both:
and:
for the same running AO instance. Because the notification store is config-path-scoped, those two config files produce different dashboard notification stores.
Source: local operator report/chat on 2026-05-21
Reported by: Adil
Analyzed against: remote
main50dc18ffa1fe1e7944e214a5f854fba87b1afe61Confidence: High — observed mismatch between
running.json.configPath, dashboard process env, and the notification store that received the test notification.Observed Behavior
The live AO daemon/dashboard was running with:
So the dashboard watched:
But a dashboard notification was delivered using the global config:
So it was written to:
The notification was successfully persisted, but the dashboard did not show it because it was watching a different file.
In plain terms:
Reproduction
cd /path/to/project ao startrunning.jsonpoints to the project-local config:{ "configPath": "/path/to/project/agent-orchestrator.yaml", "port": 3000 }or run a notifier path that loads global config while the dashboard was started from local config.
The notification is written to the global-config-derived dashboard store.
The live dashboard does not show the notification because it reads the local-config-derived store.
Root Cause
Dashboard notification persistence is scoped by raw config file path, but the dashboard itself is scoped by the running AO daemon.
This is no longer equivalent after the global/root YAML split and multi-project dashboard model.
One live AO dashboard may supervise multiple projects, while notification producers may resolve either the global config or a project-local config. If those paths differ, dashboard notifications split across multiple stores.
Impact
ao notify test --to dashboardmay report success while the visible dashboard shows nothing.Recommended Fix
Make dashboard notifications daemon-scoped, not raw-config-path-scoped.
Desired model
One running dashboard/daemon should have one notification stream.
Each notification record already includes:
{ "projectId": "...", "sessionId": "...", "type": "...", "priority": "..." }So a single daemon-level store can support multiple projects while still allowing the dashboard to filter/group by project.
Implementation
Add a derived daemon-level dashboard notification store path to
running.json.Example shape:
{ "pid": 1720, "configPath": "/path/to/project/agent-orchestrator.yaml", "port": 3000, "projects": ["project-a", "project-b"], "dashboardNotificationStore": "<derived-ao-base-dir>/dashboard-notifications.jsonl" }Important: the path should not be hardcoded. It should be derived through AO path helpers, e.g. a new helper like:
internally based on AO's base dir.
Then:
NotificationBroadcasterreads fromrunning.dashboardNotificationStore.running.dashboardNotificationStorewhen a live daemon exists.ao notify test --to dashboarduses the live daemon store when a daemon is running.Why this is better
projectId/sessionIdfields.Related