Skip to content

feat(memory): ambient nudges for record_decision and record_code_area - #144

Open
rajkumarsakthivel wants to merge 3 commits into
mainfrom
feat/memory-nudges
Open

feat(memory): ambient nudges for record_decision and record_code_area#144
rajkumarsakthivel wants to merge 3 commits into
mainfrom
feat/memory-nudges

Conversation

@rajkumarsakthivel

Copy link
Copy Markdown
Member

Summary

CCE's cross-session memory depends on the agent calling record_decision and record_code_area, but that only happens when the agent obeys CLAUDE.md instructions. If context compacts or the agent just forgets, the session produces no durable memory. This PR makes recording ambient by nudging the agent from within tool results.

Inline nudges on context_search:

  • Decision nudge fires after 4 context searches with 0 decisions recorded this session. Message includes the search count and a directive to call record_decision if a non-obvious choice was made. Includes "skip if this session is just exploration/reading" to prevent garbage recordings. After the first record_decision, the threshold doubles to 8 (longer leash once the agent has shown it knows how).

  • Code area nudge fires when 3+ files have been explored (via context_search or expand_chunk) without any record_code_area call. Lists the specific unrecorded file paths (max 5) so the agent has concrete material to act on rather than a generic reminder. Files that were already record_code_area'd are excluded from the count.

Both nudges can fire in the same response. They appear before the output compression directive, as part of the tool result text.

Stop hook summary:

The existing Stop hook (fires on session end) now returns a compact summary: search count, decisions recorded, code areas recorded, with a directive to record if counts are low. The hook script is updated to capture Stop stdout (previously only SessionStart output was captured). Users get the updated script on next cce init.

Example nudge (after 4th search, 0 decisions):

[CCE] 4 context searches this session, 0 decision(s) recorded.
If you chose a library, resolved an ambiguity, or established a convention, call record_decision(decision="...", reason="...").
Skip if this session is just exploration/reading.

Example code area nudge:

[CCE] 3 files explored but not recorded: src/auth.py, src/middleware.py, src/routes.py
If you modified or traced a non-obvious flow, call record_code_area(file_path="...", description="...").
Skip for files you only glanced at.

Recording decisions and code areas currently depends on the agent obeying
CLAUDE.md instructions. This makes memory opt-in and fragile. Agents
that forget (or whose context compacts away the instructions) silently
produce sessions with no durable memory.

Two mechanisms make recording ambient:

1. Inline nudges on context_search results. After 4 searches with no
   record_decision, a directive appears listing the search count and
   prompting the agent to record if it made non-obvious choices. A
   separate nudge fires when 3+ files have been explored (via search
   or expand_chunk) without any record_code_area call, listing the
   specific unrecorded file paths. Both nudges include a "skip if"
   clause so the agent can ignore them when just browsing. After the
   first record_decision, the search threshold doubles to 8.

2. Stop hook summary. The Stop hook (already installed) now returns a
   compact session-end summary to stdout. The hook script is updated
   to capture Stop output (like SessionStart) so it gets injected into
   the agent's context. The summary lists search count, decisions
   recorded, and code areas recorded, with a directive to record if
   counts are low.

New tests: 4 (decision nudge threshold, reset after record, code area
nudge with unrecorded files, exclusion of recorded files).

Copilot AI left a comment

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.

Pull request overview

This PR adds “ambient” memory-recording nudges to improve cross-session memory reliability by prompting agents (via tool output) to call record_decision and record_code_area when activity suggests they should.

Changes:

  • Appends decision/code-area nudges to context_search tool results based on session activity thresholds.
  • Adds a Stop-hook nudge generator that summarizes low-recording activity at session end.
  • Updates the hook installer scripts to capture and print Stop-hook stdout (so it can be injected into model context), and adds integration tests for the new context_search nudges.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tests/integration/test_mcp_server.py Adds integration tests verifying decision/code-area nudges appear (or not) under expected thresholds.
src/context_engine/memory/hooks.py Adds Stop-hook nudge builder and returns Stop-hook output intended for model-context injection.
src/context_engine/memory/hook_installer.py Updates installed hook scripts to capture/emit Stop output similarly to SessionStart.
src/context_engine/integration/mcp_server.py Introduces per-session nudge state and appends nudges to context_search results; resets some state on record_decision.
Comments suppressed due to low confidence (1)

src/context_engine/integration/mcp_server.py:747

  • The decision nudge will fire on every context_search after the threshold is reached because the condition uses ">=". That will repeatedly inject the same reminder into tool output and can quickly become noisy; trigger once when the threshold is crossed (or track a last-nudged counter).
        if self._searches_since_last_decision >= threshold:

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/context_engine/integration/mcp_server.py
Comment thread src/context_engine/memory/hooks.py Outdated
Comment thread src/context_engine/memory/hooks.py
- Fix misleading comment claiming nudge resets on record_code_area
- Return 204 instead of {"ok":true} JSON when Stop has no nudge
- Add tests for _build_stop_nudge (fires, silent-few, silent-recorded)

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

src/context_engine/memory/hooks.py:385

  • handle_stop() now intentionally returns plain text (nudge) or 204 to avoid injecting JSON into the model context, but the exception path still returns a JSON body. If Stop enqueue fails, that JSON will be captured by the hook script and can pollute the model context the same way the comment below is trying to avoid. Consider returning a small text/plain message (or an empty body) on failures as well for consistency.
    except Exception:
        log.exception("Stop enqueue failed")
        return web.json_response({"ok": False}, status=202)

src/context_engine/memory/hooks.py:404

  • _build_stop_nudge() docstring says it only queries tool_events and decisions, but the function also counts code_areas and uses that to decide whether to emit a nudge. Updating the docstring will keep the behavior discoverable and prevent future confusion when adjusting thresholds.
    Queries memory.db for tool_events (context_search calls) and decisions
    recorded during this session. Returns a short directive if activity
    was significant but recording was low. Returns "" if nothing to nudge.

src/context_engine/integration/mcp_server.py:763

  • _build_nudge() calls get_session_snapshot() twice and indexes snap["decisions"]. In tests/mocks or future snapshot implementations, a missing "decisions" key would raise a KeyError. Reusing the same snapshot and using .get(...) makes this more robust and avoids redundant snapshot calls.
        # ── decision nudge ────────────────────────────────────────────
        threshold = (
            self._DECISION_NUDGE_REPEAT
            if self._has_recorded_decision
            else self._DECISION_NUDGE_FIRST

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.

3 participants