Enable pull-based notification workflow: on heartbeat, Ava runs agentd status --json | jq filtered/sorted by a timestamp that reflects the last time an agent's state changed, so we can detect 'new idle' vs 'already relayed idle' without a push notification subsystem.
Ben's design (see recent Telegram thread with Ava): the agentd notification channel should be pull-based, not push-based. The heartbeat pattern will be something like:
agentd status --json | jq '.[] | select(.status == "idle") | select(.updated_at > $LAST_CHECK)'
No need for mail/webhook subsystem.
AgentInfo (intent/Omni/Agentd/Daemon.hs:224) currently has aiStartedAt and aiCompletedAt but no general 'last state change' timestamp. A long-running persistent agent that transitions running→idle→running→idle only updates started_at once and completed_at never (it doesn't complete), so there's no way to tell *when* it became idle.
1. Add aiUpdatedAt :: Maybe Time.UTCTime field to AgentInfo in intent/Omni/Agentd/Daemon.hs (near aiStartedAt).
2. Add updated_at TEXT column to the agents table schema (intent/Omni/Agentd/Daemon.hs initDb ~line 346). Include a schema migration: ALTER TABLE agents ADD COLUMN updated_at TEXT guarded so re-running is safe.
3. Emit "updated_at" .= aiUpdatedAt info in the ToJSON instance (~line 285).
4. Update every UPDATE agents SET ... statement (lines 418, 425, 432, 440, 447, 454, 461, 468) to also set updated_at = ? with the current time. Easiest approach: add a helper touchUpdated :: SQL.Connection -> Text -> IO () and call it at every status/summary transition, OR add updated_at = ? to each existing UPDATE (and pass now as an extra parameter).
5. SELECT queries that build AgentInfo must populate aiUpdatedAt from the column.
6. Verify: spawn a persistent agent, let it go idle, confirm agentd status --json shows an updated_at that changes on each turn_end.
agentd status — we're using jq for filtering.agentd run -n test-idle 'say hi then wait' → wait for idle → agentd status --json | jq '.[] | select(.run_id == "test-idle") | .updated_at' should be populated and more recent than started_at.
Implemented updated_at tracking for agent status in Omni/Agentd/Daemon.hs. Added aiUpdatedAt to AgentInfo and emitted updated_at in JSON. Added agents.updated_at schema column + ALTER migration. Updated AgentInfo SELECT/FromRow to load updated_at. Added touchUpdated helper and wired all agent-row mutations to refresh updated_at (status/summary/title/pid), with inline updated_at on started/completed transitions and persistent stop SQL. Also set updated_at on agent inserts. Added unit assertion that AgentInfo JSON includes updated_at key. Verified with typecheck.sh Omni/Agentd/Daemon.hs, bild Omni/Agentd/Daemon.hs, and bild --test Omni/Agentd/Daemon.hs.