Skip to content

dashboard notifications can be written to a store the live dashboard is not watching #1984

@whoisasx

Description

@whoisasx

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

  1. Start AO from a project-local config:
cd /path/to/project
ao start
  1. Ensure the dashboard is running and running.json points to the project-local config:
{
  "configPath": "/path/to/project/agent-orchestrator.yaml",
  "port": 3000
}
  1. 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.

  1. The notification is written to the global-config-derived dashboard store.

  2. 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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpriority: mediumFix when convenient

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions