Skip to content

feat(proactive): Phase 3.5 Step 13 — proactive intelligence overlay (opt-in)#29

Merged
AVADSA25 merged 1 commit intomainfrom
feat/phase35-proactive-overlay
May 3, 2026
Merged

feat(proactive): Phase 3.5 Step 13 — proactive intelligence overlay (opt-in)#29
AVADSA25 merged 1 commit intomainfrom
feat/phase35-proactive-overlay

Conversation

@AVADSA25
Copy link
Copy Markdown
Owner

@AVADSA25 AVADSA25 commented May 3, 2026

Summary

Phase 3.5 Step 13. The last big remaining feature from Phase 3 backlog. Observer-driven contextual nudges via the Step 6 trigger system pattern.

OFF by default. User opts in via PROACTIVE_OVERLAY_ENABLED=true env var to avoid surprise notifications.

Anchor example

User has been on notion.so/my-research-doc for 31 minutes. CODEC's observer notices the dwell. Posts: "Want me to summarize? You've been on this Notion doc for over 30 minutes. I can pull a summary into your notes. [Acknowledge] [Dismiss today] [Disable forever]"

If you click Acknowledge → audit emits proactive_suggestion_acknowledged. Dismiss today → blocked until tomorrow UTC. Disable forever → pattern in killed_patterns permanently.

What ships

Path Type Purpose
codec_proactive.py NEW (~280 LOC) Pattern registry + state + check_for_proactive + dismiss/acknowledge
tests/test_proactive.py NEW (12 tests) Audit constants, kill switches, dwell matching, cooldowns, dismiss/ack, atomic writes, list_patterns
codec_audit.py +13 3 new event constants + PHASE35_PROACTIVE_EVENTS frozenset
codec_observer.py +29 poll() extension: after Step 6 triggers, calls check_for_proactive(). Defensive try/except so observer never breaks
routes/agents.py +49 3 new endpoints: GET /api/proactive/patterns, POST /api/proactive/acknowledge, POST /api/proactive/dismiss

Pattern v1: long_form_dwell

Matches when ALL true:

  • Active window title or bundle contains notion.so / docs.google.com / substack.com / medium.com / nytimes.com / ft.com / economist.com / newyorker.com
  • Window has been active for ≥ 30 minutes consecutively (computed by walking observer history backwards)

Cooldown: 1 hour per pattern. Global rate limit: 30 min between any two suggestions (so cluster of patterns can't burst).

Easy to extend PATTERNS list with more:

  • multi_tab_research (3+ tabs on same domain)
  • heavy_editing (5+ edits in 10 min)
  • error_log_dwell (Console.app foreground + recent error)

State

~/.codec/proactive_state.json (atomic tmp+rename writes):

{
  "schema": 1,
  "last_fired_at": {"long_form_dwell": 1715000000.0},
  "dismissed_today": {"long_form_dwell": "2026-05-03"},
  "killed_patterns": [],
  "last_global_fire_at": 1715000000.0
}

Audit emits

3 new schema:1 events:

  • proactive_suggestion_emitted (info, extra: pattern_id, title_excerpt)
  • proactive_suggestion_acknowledged (info, extra: pattern_id)
  • proactive_suggestion_dismissed (info, extra: pattern_id, scope)

Test plan

  • 🧪 tests/test_proactive.py → 12 passed
  • 🧪 Full suite — 950 passed / 20 failed / 73 skipped (same 20/73 baseline, +12 from proactive)
  • OFF by default verified (kill-switch test)
  • Atomic state writes verified
  • Post-merge deploy:
    cd ~/codec-repo
    git pull
    pm2 restart codec-dashboard codec-observer
    # OPT IN to test:
    pm2 stop codec-observer
    PROACTIVE_OVERLAY_ENABLED=true pm2 start ecosystem.config.js --only codec-observer
    # Then dwell on notion.so/anything for 30 min → expect suggestion

Why opt-in default

Proactive nudges have a high blast radius for "annoying user". Defaulting OFF means users explicitly enable via env var or pm2 set codec-observer:env.PROACTIVE_OVERLAY_ENABLED true. Same pattern as Phase 2 Step 5's ocr_enabled (also defaulted to a safe fallback after the popup-storm incident).

🤖 Generated with Claude Code

Observer-driven contextual nudges. Reads codec_observer's snapshot, checks
declarative patterns, posts at most one suggestion per pattern per cooldown
window. OFF by default — user opts in via PROACTIVE_OVERLAY_ENABLED env var
to avoid surprise notifications.

## What ships

`codec_proactive.py` (NEW, ~280 LOC):
- Pattern registry with 1 v1 pattern: `long_form_dwell` (≥30 min on
  Notion/Docs/Substack/Medium/major news → "want me to summarize?")
- 3-gate kill model:
  1. Global env: `PROACTIVE_OVERLAY_ENABLED=true|false` (default false)
  2. Per-pattern killed forever: `~/.codec/proactive_state.json:killed_patterns`
  3. Per-day dismissed: `~/.codec/proactive_state.json:dismissed_today`
- Rate limits:
  - Per-pattern cooldown: 1 hour (configurable per Pattern)
  - Global rate limit: 30 min between any two suggestions
- Atomic state writes (tmp+rename, mirrors Step 8/10 pattern)
- Public API: `check_for_proactive(snapshot, history)`, `acknowledge`,
  `dismiss(pattern_id, scope)`, `list_patterns`, `is_enabled`,
  `is_pattern_killed`, `is_pattern_dismissed_today`

## Integration

`codec_observer.poll()` extension:
- After Step 6 trigger evaluation, calls `check_for_proactive(snapshot, history)`
- If a pattern matches, posts via `codec_agent_messaging.post_message`
  with `agent_id="proactive"` and `type=agent_question`
- Try/except wrapping so observer keeps running on any failure
  (defensive — proactive is a side effect, not a hard dependency)

## PWA endpoints (`routes/agents.py` extended)

- `GET /api/proactive/patterns` — list registered patterns + state for settings UI
- `POST /api/proactive/acknowledge {pattern_id}` — user clicked Acknowledge
- `POST /api/proactive/dismiss {pattern_id, scope: "today"|"forever"}`

## Audit envelope

3 new schema:1 events + `PHASE35_PROACTIVE_EVENTS` frozenset:
- `proactive_suggestion_emitted` (info, extra: pattern_id, title_excerpt)
- `proactive_suggestion_acknowledged` (info, extra: pattern_id)
- `proactive_suggestion_dismissed` (info, extra: pattern_id, scope)

## Tests

12 new tests in `tests/test_proactive.py`:
- Audit constants
- Kill switch (default off, env-var on)
- Pattern matching (long_form_dwell match / short-dwell skip / non-long-form skip)
- Cooldowns (per-pattern + global rate limit)
- Dismiss today + forever
- Atomic state writes
- list_patterns

Full suite: 950 passed / 20 failed / 73 skipped (same 20/73 baseline,
+12 from proactive).

## Why opt-in default

Proactive nudges have a high blast radius for "annoying user". Defaulting
OFF means users explicitly enable via:
  `pm2 stop codec-observer && PROACTIVE_OVERLAY_ENABLED=true pm2 start codec-observer`
or by editing ecosystem.config.js. Same pattern as ocr_enabled.

## Future patterns (Phase 4+)

Easy to add to `PATTERNS` list:
- multi_tab_research (3+ tabs same domain → consolidate notes?)
- heavy_editing (5+ edits in 10 min on same file → auto-checkpoint?)
- error_log_dwell (Console.app + recent error → debug?)

Each is just a (match_fn, suggestion_fn, cooldown) tuple. Tests are
template-based.
@AVADSA25 AVADSA25 merged commit 1e4fab6 into main May 3, 2026
1 check passed
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.

2 participants