Skip to content

feat(home-feed): replace reflection producer with activity-log roll-up [JARVIS-512]#25594

Merged
alex-nork merged 2 commits into
mainfrom
alex-nork/jarvis-512-rollup-producer
Apr 14, 2026
Merged

feat(home-feed): replace reflection producer with activity-log roll-up [JARVIS-512]#25594
alex-nork merged 2 commits into
mainfrom
alex-nork/jarvis-512-rollup-producer

Conversation

@alex-nork
Copy link
Copy Markdown
Contributor

@alex-nork alex-nork commented Apr 14, 2026

Summary

Fourth step of JARVIS-512, stacked on #25593 (instrumentation, open). Pivots the LLM-driven producer from "reflect on relationship state from nothing""consolidate recent action items into digests/threads." Same skeleton, same cadence mechanism, new input and new intent.

What changes

Rename: reflection-producer.tsrollup-producer.ts (git rename tracked so blame history is preserved). The new producer:

  • Reads recent action items from readHomeFeed() via a new defaultLoadRecentActions helper — sorted newest-first, capped at MAX_ACTIONS_IN_PROMPT = 30 for token budget. Non-action items are excluded (the input is the raw activity log, not existing consolidations).
  • Still loads relationship state for context, but the prompt is explicit that it's secondary framing only — the model is instructed NOT to invent roll-ups from facts alone.
  • Narrows the tool schema to type: digest | thread only. No nudges, no actions. Actions come from background jobs; nudges aren't this producer's concern.
  • Adds a no_actions skip reason for when the activity log is empty. The producer short-circuits before the provider call.

Scheduler update (feed-scheduler.ts):

  • Symbol rename: reflectionRunner/reflectionRan/REFLECTION_INTERVAL_MS/lastReflectionAtrollup*.
  • no_actions treated like no_provider: does NOT advance the cooldown gate, so the next tick retries as soon as new actions land instead of waiting the full 30-minute window. Real LLM attempts (success/empty_items/malformed_output/provider_error) still advance the gate to preserve backoff.

Cadence stays at 30 min in this PR. The drop to 120 min + on-visit refresh in home-feed-routes.ts is the next (and final) step in JARVIS-512.

Loop / cost audit

  • The roll-up reads the feed but never writes actions — output is digest/thread only. No feedback path.
  • no_actions short-circuit means an empty feed produces zero LLM calls even on every tick (the producer bails before provider.sendMessage).
  • readHomeFeed() is the only new data dependency; it's a synchronous file read that the writer's merge loop already uses, so no new I/O cost.

Testing

  • bun run typecheck — clean
  • bun run lint — clean
  • bun test src/home/ — 147/147 pass
  • Migrated coverage: all previous reflection-producer tests carry over with digest/thread fixture values
  • New tests: no_actions short-circuit, action-item serialization into the prompt, nudge/action types rejected at coercion, no_actions cooldown behavior in the scheduler

Remaining JARVIS-512 scope

  • Step 5 — on-visit refresh in home-feed-routes.ts (debounced fire-and-forget from handleGetHomeFeed)
  • Step 6 — drop rollup cadence from 30 min → 120 min (bundled with on-visit refresh so there's no staleness window)
  • Deferred from earlier PRs: Gmail watcher instrumentation, task-runner direct, skill runner completion

🤖 Generated with Claude Code


Open with Devin

alex-nork and others added 2 commits April 14, 2026 15:03
…JARVIS-512]

First batch of background-job instrumentation for the home activity
log. Wires emitFeedEvent into the safe, bounded completion points
where a real user-visible signal lands:

scheduler.ts — 4 emit points:
  - notify-mode one-shot success (dedupKey: oneshot:<jobId>)
  - notify-mode recurring success (dedupKey: schedule-run:<runId>)
  - execute-mode task success (dedupKey: schedule-run:<runId>)
  - execute-mode message success (dedupKey: schedule-run:<runId>)
  Execute-mode emits are gated on !job.quiet to match the existing
  notifySchedule behavior; notify-mode emits always fire since
  notify-mode IS a notification by design.

sequence/engine.ts — emit after recordSend on each successful step
  (dedupKey: sequence-step:<enrollmentId>:<stepIndex>). Skipped for
  requireApproval draft steps — those pause without sending, so
  there's no real signal yet.

All emits use source="assistant", are fire-and-forget via .catch so
a writer hiccup can never interrupt the 15s scheduler tick, and key
on the schedule-run / enrollment-step identifier so each run is a
distinct activity-log entry (the writer's per-source cap bounds
total volume).

Deferred to follow-up:
  - Gmail watcher event processing (loop-risk if the LLM-processing
    path re-triggers work)
  - task-runner direct (manual non-schedule task runs) — needs a
    dedup story with the scheduler layer to avoid double-emits
  - skill runner completion — no single well-defined hook to latch
    onto yet

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p [JARVIS-512]

Pivot the LLM-driven producer from "reflect on relationship state from
nothing" → "consolidate recent action items into digests/threads."
Same skeleton, same cadence mechanism (30 min), new input and new
intent.

Rename reflection-producer.ts → rollup-producer.ts (git rename
tracked so blame history is preserved). The new producer:

  - Reads recent `action` items from readHomeFeed() via a new
    defaultLoadRecentActions helper (sorted newest-first, capped at
    MAX_ACTIONS_IN_PROMPT = 30 for token budget). Non-action items
    are excluded — the roll-up's input is the raw activity log, not
    existing consolidations.
  - Still loads relationship state for context, but only as
    secondary framing (explicitly instructed in the prompt NOT to
    invent roll-ups from facts alone).
  - Narrows the write_feed_items tool schema to type: digest | thread
    only. No nudges, no actions — actions come from background jobs,
    not the LLM.
  - New `no_actions` skip reason: when the activity log is empty,
    the producer short-circuits before the provider call. The
    scheduler treats this like no_provider — does NOT advance the
    cooldown gate, so the next tick retries as soon as new actions
    land instead of waiting the full 30-minute window. Real LLM
    attempts (success/empty_items/malformed_output/provider_error)
    still advance the gate to preserve backoff.

feed-scheduler.ts renamed symbols to match:
  reflectionRunner → rollupRunner
  reflectionRan → rollupRan
  REFLECTION_INTERVAL_MS → ROLLUP_INTERVAL_MS
  lastReflectionAt → lastRollupAt

Cadence stays at 30 min in this PR. The drop to 120 min + on-visit
refresh in home-feed-routes.ts is the next step in JARVIS-512.

Tests migrated from reflection-producer.test.ts → rollup-producer.
test.ts with all existing coverage plus new cases:
  - no_actions short-circuits before the provider call
  - action items are serialized into the user prompt
  - nudge/action types are rejected at coercion time even if the
    model bypasses the tool schema

feed-scheduler.test.ts adds a no_actions cooldown test alongside
the existing no_provider test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +7 to +10
* small set of digests or threads." This is the replacement for the
* old "reflect from nothing" producer: the roll-up starts from real
* side effects instead of prompting the model to hallucinate signal
* from relationship state alone.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 AGENTS.md violation: new comment references removed code

Lines 7–10 of the new module comment explicitly reference the removed reflection producer: "This is the replacement for the old 'reflect from nothing' producer". This violates the mandatory rule in assistant/AGENTS.md:19-21: "When writing or updating comments, do not reference code that has been removed. Comments should describe the current state of the codebase, not narrate its history." The comment should describe what the rollup producer does without referencing the old implementation.

Suggested change
* small set of digests or threads." This is the replacement for the
* old "reflect from nothing" producer: the roll-up starts from real
* side effects instead of prompting the model to hallucinate signal
* from relationship state alone.
* small set of digests or threads." The roll-up starts from real
* side effects (action items deposited by background jobs) rather
* than asking the model to invent signal from relationship state
* alone.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f65fbea98

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

"Seconds the user must be away before this item appears. Use 0 for a roll-up the user should see immediately.",
},
},
required: ["type", "title", "summary"],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require source in roll-up items

The roll-up path depends on source-scoped replacement for digests, but source is optional here (required omits it), so model output without source is accepted and written. In mergeIncoming (feed writer), digest replacement only happens when incoming.source is present, so source-less digests/threads will accumulate as new rows on repeated runs instead of collapsing, which degrades feed quality and can grow the file unexpectedly.

Useful? React with 👍 / 👎.

Comment on lines +280 to +282
return feed.items
.filter((i) => i.type === "action")
.sort((a, b) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict roll-up input to fresh action events

This loader includes every persisted action item and only sorts by createdAt; it does not filter by freshness/status. Because newly-added schedule/sequence emits create non-expiring actions by default, actions.length will remain non-zero after initial activity, so the new no_actions fast-path almost never triggers and the model keeps reprocessing stale history on each cooldown cycle. That increases token spend and tends to regenerate repetitive summaries rather than reacting to truly new activity.

Useful? React with 👍 / 👎.

@alex-nork alex-nork merged commit ba7b127 into main Apr 14, 2026
12 checks passed
@alex-nork alex-nork deleted the alex-nork/jarvis-512-rollup-producer branch April 14, 2026 19:30
Jasonnnz pushed a commit that referenced this pull request Apr 15, 2026
…p [JARVIS-512] (#25594)

* feat(home-feed): instrument scheduler + sequence with emitFeedEvent [JARVIS-512]

First batch of background-job instrumentation for the home activity
log. Wires emitFeedEvent into the safe, bounded completion points
where a real user-visible signal lands:

scheduler.ts — 4 emit points:
  - notify-mode one-shot success (dedupKey: oneshot:<jobId>)
  - notify-mode recurring success (dedupKey: schedule-run:<runId>)
  - execute-mode task success (dedupKey: schedule-run:<runId>)
  - execute-mode message success (dedupKey: schedule-run:<runId>)
  Execute-mode emits are gated on !job.quiet to match the existing
  notifySchedule behavior; notify-mode emits always fire since
  notify-mode IS a notification by design.

sequence/engine.ts — emit after recordSend on each successful step
  (dedupKey: sequence-step:<enrollmentId>:<stepIndex>). Skipped for
  requireApproval draft steps — those pause without sending, so
  there's no real signal yet.

All emits use source="assistant", are fire-and-forget via .catch so
a writer hiccup can never interrupt the 15s scheduler tick, and key
on the schedule-run / enrollment-step identifier so each run is a
distinct activity-log entry (the writer's per-source cap bounds
total volume).

Deferred to follow-up:
  - Gmail watcher event processing (loop-risk if the LLM-processing
    path re-triggers work)
  - task-runner direct (manual non-schedule task runs) — needs a
    dedup story with the scheduler layer to avoid double-emits
  - skill runner completion — no single well-defined hook to latch
    onto yet

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(home-feed): replace reflection producer with activity-log roll-up [JARVIS-512]

Pivot the LLM-driven producer from "reflect on relationship state from
nothing" → "consolidate recent action items into digests/threads."
Same skeleton, same cadence mechanism (30 min), new input and new
intent.

Rename reflection-producer.ts → rollup-producer.ts (git rename
tracked so blame history is preserved). The new producer:

  - Reads recent `action` items from readHomeFeed() via a new
    defaultLoadRecentActions helper (sorted newest-first, capped at
    MAX_ACTIONS_IN_PROMPT = 30 for token budget). Non-action items
    are excluded — the roll-up's input is the raw activity log, not
    existing consolidations.
  - Still loads relationship state for context, but only as
    secondary framing (explicitly instructed in the prompt NOT to
    invent roll-ups from facts alone).
  - Narrows the write_feed_items tool schema to type: digest | thread
    only. No nudges, no actions — actions come from background jobs,
    not the LLM.
  - New `no_actions` skip reason: when the activity log is empty,
    the producer short-circuits before the provider call. The
    scheduler treats this like no_provider — does NOT advance the
    cooldown gate, so the next tick retries as soon as new actions
    land instead of waiting the full 30-minute window. Real LLM
    attempts (success/empty_items/malformed_output/provider_error)
    still advance the gate to preserve backoff.

feed-scheduler.ts renamed symbols to match:
  reflectionRunner → rollupRunner
  reflectionRan → rollupRan
  REFLECTION_INTERVAL_MS → ROLLUP_INTERVAL_MS
  lastReflectionAt → lastRollupAt

Cadence stays at 30 min in this PR. The drop to 120 min + on-visit
refresh in home-feed-routes.ts is the next step in JARVIS-512.

Tests migrated from reflection-producer.test.ts → rollup-producer.
test.ts with all existing coverage plus new cases:
  - no_actions short-circuits before the provider call
  - action items are serialized into the user prompt
  - nudge/action types are rejected at coercion time even if the
    model bypasses the tool schema

feed-scheduler.test.ts adds a no_actions cooldown test alongside
the existing no_provider test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant