You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a wake_briefing SystemTask that gives an agent felt continuity on wake: a digest of what changed across the install recently, composed and passively injected into the agent's context via the existing memory-file injection machinery. No push, no new transport, no new storage table, no mutable timestamp state.
Motivation
An agent running beside a WordPress install (e.g. via an external coding-agent runtime) wakes fresh each session. MEMORY.md/SOUL.md give persistent knowledge, but nothing tells the agent what changed recently. Real example: a recurring task (workspace_disk_emergency_cleanup) failed hourly for 7 days, logging 213 errors, and was only discovered by accident — nothing surfaced it on wake. A wake-briefing closes that "no proprioception across time" gap.
CRITICAL DESIGN CONSTRAINT: parallel sessions, no shared clock
An agent is not a single linear consumer — it is a fan-out of many concurrent sessions (10+ parallel sessions are normal). This kills the naive "store a last_wake timestamp and show the delta since then" design:
single shared counter (BROKEN):
t0 session A composes context → reads delta → resets last_wake = t0
t1 session B composes context → delta is EMPTY (A just reset it) → reads a lie
t2 session C composes context → delta is EMPTY → reads a lie
A single mutable last_wake is a race: whichever session composes first resets the clock and blinds every concurrent session. With N parallel sessions, N-1 get a lie. Do not build a shared mutable wake timestamp.
Additionally, the website cannot cleanly observe an agent "wake" — sessions spawn in the external runtime, and WordPress only learns the agent exists when it makes a call. There is no reliable on_wake hook to stamp. So "since last wake" is not observable server-side anyway.
Design: stateless rolling window (v1)
The briefing is always "what changed in the last N hours" (default 24h, filterable), recomputed fresh each time the file is composed. No timestamp to race, no per-session store, no "who read it last."
stateless window (no race):
every composition, any session:
delta = events WHERE created_at > (now - window)
all parallel sessions see the same honest recent snapshot — nothing to reset
Correct under any parallelism by having no shared mutable state — there is nothing to race.
Trade-off (acceptable): it is "recent activity," not strictly "since you personally were last here." Two sessions waking 20 min apart see near-identical windows. That overlap is harmless; the goal is felt continuity, not exactly-once delivery. A lie (empty delta when things changed) is the only unacceptable outcome, and the window approach can't produce one.
Cacheable: compose once per window-tick; every session reads the same artifact. Use a short transient (e.g. recompute at most every 5-10 min) so 10 concurrent sessions don't each run the queries.
Explicitly out of scope for v1 (do NOT build speculatively): per-session watermarks keyed on the runtime session ID. That is a real v2 only if the flat rolling window proves too noisy in practice. Per RULES.md, do not add stateful infrastructure before proving the stateless version is insufficient. The stateless window is sufficient.
CRITICAL DESIGN CONSTRAINT: signal discipline is the actual feature
The data-gathering is the easy 80%. The hard 20% — and the part that determines whether this is net-positive or net-negative — is editorial judgment about what is worth surfacing. The briefing rides in every session's context window on every wake, so every line is a permanent tax on every conversation, including ones where the agent is just doing a quick scoped edit. A verbose or noisy briefing is pure clutter the agent pays for forever.
The bar: ruthless terseness. Red things only. Quiet is a valid, good state.
Concrete rules the implementation MUST follow:
Group, don't enumerate. The motivating bug logged 213 identical errors. A naive briefing screams "213 ERRORS!" every wake for a week and trains the agent to ignore it (alarm fatigue → worse than nothing). The correct surfacing is ONE grouped line: ⚠ workspace_disk_emergency_cleanup — 24 failures in 24h (same error). Group by task_type + message signature; show the count, not the rows.
Threshold, don't dump. Only surface things that cross a "worth the agent's context budget" bar: repeated/recurring failures, stuck jobs, NEW error signatures not seen before in the window, deploy drift. A single transient error is noise — drop it. Make the threshold filterable.
Empty state is first-class and terse. When nothing crosses the bar, the file should be a single quiet line (e.g. Nothing notable in the last 24h.) — NOT a "dashboard" of green checkmarks. If the briefing ever reads like a status dashboard, it has failed.
Lead with the one line that matters. The single highest-value output is one line: ⚠ N task types failing repeatedly in last 24h — investigate?. That line alone, on day one, would have saved the 7 days the motivating bug went unnoticed. Everything beyond that line is nice-to-have and must justify its context cost.
Triage framing, not just display. Prefer phrasing that invites action ("investigate?", "N awaiting merge") over passive logging. The briefing surfaces; it cannot compel — but it should at least point.
Anti-goal: a comprehensive activity feed. This is not analytics, not a log viewer, not a dashboard. It is a terse "anything red since recently?" glance. If a reviewer can't read the whole typical-case briefing in 3 seconds, it is too long.
It is pure composition — no new infrastructure
All source data already exists in core; the task only reads + projects over the rolling window:
Pulse
Source (already exists)
Jobs (failed/stuck in window)
datamachine_jobs table (jobs summary)
Error pulse (grouped, in window)
DM log store (logs read --level=error)
Recent sessions
ConversationStore->list_sessions_for_day() (already used by DailyMemoryTask)
Mid-flight work
pending-actions
(optional) deploy drift
plugin header Version vs git on origin/main
Delivery = passive injection, NOT push
Decision (confirmed with maintainer): passive only. An active push is explicitly rejected — it duplicates the existing Agent Progress Ping flow. The briefing arrives by being part of the context already injected on session start.
This is config, not a core allowlist change. MemoryFileRegistry::register() + the datamachine_memory_files filter already support registering an injected file with modes/retrieval_policy => always. So:
WakeBriefingTask (extends SystemTask, task type wake_briefing) composes the rolling-window digest and overwrites a dedicated agent-layer file (e.g. WAKE.md) each run.
The file is registered via MemoryFileRegistry::register() with an always-inject policy so it rides into every session's context like SOUL/USER/MEMORY do.
It MUST live outside the DailyMemory compaction body, or DailyMemoryTask will try to archive it as session-specific content (it IS temporal by design). A standalone registered file avoids that fight.
Implementation notes
Template is inc/Engine/AI/System/Tasks/DailyMemoryTask.php — 1:1 for registration (datamachine_tasks filter via SystemAgentServiceProvider), getTaskType(), getTaskMeta() (supports_run => true), and the getWorkflow() → executeTask() contract.
Register the recurring schedule via datamachine_recurring_schedules (see RecurringScheduleRegistry docblock).
Agent-context gate (learn from data-machine-code#564): this task reads jobs/logs and writes a file — it does not act as an agent. If the recompute is site-scoped, override requiresAgentContext(): false so it doesn't fail at the TaskScheduler gate the way Multi-agent: partition agent files by user_id #564 did. If the digest must be composed per-agent (because "recent sessions" is agent-scoped), use per_agent => true so each agent's identity is supplied. Pick one deliberately; do NOT inherit the default true against an agent-less schedule (that is exactly the Multi-agent: partition agent files by user_id #564 bug).
Reuse AgentMemory/DiskAgentMemoryStore for the file write (same as DailyMemoryTask). Overwrite, never append.
Acceptance criteria
wp datamachine system run wake_briefing composes a rolling-window digest and writes the registered briefing file.
The file is injected into a fresh session's context (verify via the directives that inject core memory files).
Digest is a stateless rolling window (last N hours), recomputed each run — no shared last_wake timestamp, no per-session state.
Running the task from many concurrent sessions produces consistent, non-empty digests (no race that blanks the delta).
Recompute is throttled (short transient cache) so concurrent sessions don't each re-run the queries.
File is overwritten each run, not appended; lives outside DailyMemory compaction; does not get cannibalized.
Signal discipline (the real bar): repeated/identical events are GROUPED into one line with a count, never enumerated. A 213-identical-error situation renders as ONE grouped line, not 213.
Empty state is a single terse line, not a green-checkmark dashboard.
A typical-case briefing is readable in ~3 seconds; only threshold-crossing items appear (repeated failures, stuck jobs, new error signatures, deploy drift) — single transient events are dropped.
Constraints
Conventional commits (feat:).
Do NOT edit CHANGELOG.md; do NOT hand-bump versions.
Layer-pure: generic agent-continuity infra — no Extra-Chill-specific names, no vendor/transport names (no external-runtime brand names) in data-machine core.
Summary
Add a
wake_briefingSystemTask that gives an agent felt continuity on wake: a digest of what changed across the install recently, composed and passively injected into the agent's context via the existing memory-file injection machinery. No push, no new transport, no new storage table, no mutable timestamp state.Motivation
An agent running beside a WordPress install (e.g. via an external coding-agent runtime) wakes fresh each session.
MEMORY.md/SOUL.mdgive persistent knowledge, but nothing tells the agent what changed recently. Real example: a recurring task (workspace_disk_emergency_cleanup) failed hourly for 7 days, logging 213 errors, and was only discovered by accident — nothing surfaced it on wake. A wake-briefing closes that "no proprioception across time" gap.CRITICAL DESIGN CONSTRAINT: parallel sessions, no shared clock
An agent is not a single linear consumer — it is a fan-out of many concurrent sessions (10+ parallel sessions are normal). This kills the naive "store a
last_waketimestamp and show the delta since then" design:A single mutable
last_wakeis a race: whichever session composes first resets the clock and blinds every concurrent session. With N parallel sessions, N-1 get a lie. Do not build a shared mutable wake timestamp.Additionally, the website cannot cleanly observe an agent "wake" — sessions spawn in the external runtime, and WordPress only learns the agent exists when it makes a call. There is no reliable
on_wakehook to stamp. So "since last wake" is not observable server-side anyway.Design: stateless rolling window (v1)
The briefing is always "what changed in the last N hours" (default 24h, filterable), recomputed fresh each time the file is composed. No timestamp to race, no per-session store, no "who read it last."
Explicitly out of scope for v1 (do NOT build speculatively): per-session watermarks keyed on the runtime session ID. That is a real v2 only if the flat rolling window proves too noisy in practice. Per RULES.md, do not add stateful infrastructure before proving the stateless version is insufficient. The stateless window is sufficient.
CRITICAL DESIGN CONSTRAINT: signal discipline is the actual feature
The data-gathering is the easy 80%. The hard 20% — and the part that determines whether this is net-positive or net-negative — is editorial judgment about what is worth surfacing. The briefing rides in every session's context window on every wake, so every line is a permanent tax on every conversation, including ones where the agent is just doing a quick scoped edit. A verbose or noisy briefing is pure clutter the agent pays for forever.
The bar: ruthless terseness. Red things only. Quiet is a valid, good state.
Concrete rules the implementation MUST follow:
⚠ workspace_disk_emergency_cleanup — 24 failures in 24h (same error). Group bytask_type+ message signature; show the count, not the rows.Nothing notable in the last 24h.) — NOT a "dashboard" of green checkmarks. If the briefing ever reads like a status dashboard, it has failed.⚠ N task types failing repeatedly in last 24h — investigate?. That line alone, on day one, would have saved the 7 days the motivating bug went unnoticed. Everything beyond that line is nice-to-have and must justify its context cost.Anti-goal: a comprehensive activity feed. This is not analytics, not a log viewer, not a dashboard. It is a terse "anything red since recently?" glance. If a reviewer can't read the whole typical-case briefing in 3 seconds, it is too long.
It is pure composition — no new infrastructure
All source data already exists in core; the task only reads + projects over the rolling window:
datamachine_jobstable (jobs summary)logs read --level=error)ConversationStore->list_sessions_for_day()(already used by DailyMemoryTask)pending-actionsDelivery = passive injection, NOT push
Decision (confirmed with maintainer): passive only. An active push is explicitly rejected — it duplicates the existing Agent Progress Ping flow. The briefing arrives by being part of the context already injected on session start.
This is config, not a core allowlist change.
MemoryFileRegistry::register()+ thedatamachine_memory_filesfilter already support registering an injected file withmodes/retrieval_policy => always. So:WakeBriefingTask(extendsSystemTask, task typewake_briefing) composes the rolling-window digest and overwrites a dedicated agent-layer file (e.g.WAKE.md) each run.MemoryFileRegistry::register()with an always-inject policy so it rides into every session's context like SOUL/USER/MEMORY do.DailyMemoryTaskwill try to archive it as session-specific content (it IS temporal by design). A standalone registered file avoids that fight.Implementation notes
inc/Engine/AI/System/Tasks/DailyMemoryTask.php— 1:1 for registration (datamachine_tasksfilter viaSystemAgentServiceProvider),getTaskType(),getTaskMeta()(supports_run => true), and thegetWorkflow()→executeTask()contract.datamachine_recurring_schedules(seeRecurringScheduleRegistrydocblock).requiresAgentContext(): falseso it doesn't fail at theTaskSchedulergate the way Multi-agent: partition agent files by user_id #564 did. If the digest must be composed per-agent (because "recent sessions" is agent-scoped), useper_agent => trueso each agent's identity is supplied. Pick one deliberately; do NOT inherit the defaulttrueagainst an agent-less schedule (that is exactly the Multi-agent: partition agent files by user_id #564 bug).AgentMemory/DiskAgentMemoryStorefor the file write (same as DailyMemoryTask). Overwrite, never append.Acceptance criteria
wp datamachine system run wake_briefingcomposes a rolling-window digest and writes the registered briefing file.last_waketimestamp, no per-session state.requiresAgentContext()/per_agentdeliberately and does not reproduce the Multi-agent: partition agent files by user_id #564 agent-context-gate failure.Constraints
feat:).