Skip to content

feat: wake-briefing — passive 'since last session' delta injected into agent context #2557

@chubes4

Description

@chubes4

Summary

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.
  • The recurring schedule sets requiresAgentContext()/per_agent deliberately and does not reproduce the Multi-agent: partition agent files by user_id #564 agent-context-gate failure.
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions