Skip to content

Compaction loses all context on the immediately executing task #1571

Open

Description

@antoinecellerier

Describe the bug

This has happened a few times to me over the past days. After compaction happens, context from the previously executing action seems to become completely lost which effectively requires manually stitching things back together. This is not consistently happening and I do not know how to reproduce reliably.

Here's an example which happened right now:

Image you can see right after compaction that it's trying to figure out what it was doing right before (e.g. about to check screenshots it had just taken) Image

I'm happy to share any local files which could help debug this.

Affected version

GitHub Copilot CLI 0.0.413-0.

Steps to reproduce the behavior

No response

Expected behavior

Expected behavior would be for copilot to continue with ongoing work after compaction without needing to reinfer everything from repo state.

Additional context

Let me know what would be useful to debug.

Activity

  1. added
    area:context-memoryContext window, memory, compaction, checkpoints, and instruction loading
    on Apr 6, 2026
  2. John-Rood commented on Sep 25, 2026

    @John-Rood

    The loss at compaction is specific, and it is the same thing every time: the summary keeps the goals, the decisions, the plan state, and your instructions, and it drops the cursor. What was executing at that moment, what the last tool call produced, and which action was queued next live only in the raw turns. After the swap the model re-derives all of it from repo state, which is the screenshot behavior in your example.

    One prerequisite: the build in the report predates the mechanisms below (preCompact landed in 1.0.5, compaction checkpoints in 1.0.85, current is 1.0.88). On a current build the loop runs today.

    1. Move the cursor into a file. NEXT.md at the repo root, three lines, overwritten in place so it never grows into a second summarization problem:
    # NEXT
    doing: comparing login-flow screenshots 3 and 4 in .copilot/tmp
    next: patch src/auth/login.ts:212, rerun the flow test
    verified: flow test still fails before the patch
    1. Add the rule that keeps it current. Copilot CLI loads AGENTS.md as agent instructions (nearest file in the tree wins), so this block is part of every session's instructions:
    ## Work state
    - Before starting a multi-step task, write NEXT.md. Update it after each completed step and before anything slow.
    - If earlier turns are not visible, read NEXT.md first and continue from its `next` line.
    1. Snapshot the transcript at the compaction boundary, and reorient on resume, with one hook file. Create .github/hooks/lifecycle.json (or the same file under ~/.copilot/hooks/ to apply it everywhere):
    {
      "version": 1,
      "hooks": {
        "preCompact": [
          {
            "type": "command",
            "bash": "d=.copilot-transcripts; mkdir -p \"$d\"; cp \"$(jq -r .transcriptPath)\" \"$d/transcript-$(date +%Y%m%d-%H%M%S).jsonl\"",
            "powershell": "$d='.copilot-transcripts'; New-Item -ItemType Directory -Force $d | Out-Null; $p=(ConvertFrom-Json ([Console]::In.ReadToEnd())).transcriptPath; Copy-Item $p (\"$d/transcript-{0}.jsonl\" -f (Get-Date -Format 'yyyyMMdd-HHmmss'))",
            "cwd": ".",
            "timeoutSec": 10
          }
        ],
        "sessionStart": [
          {
            "type": "command",
            "bash": "printf '{\"additionalContext\": \"If NEXT.md exists, read it and continue from its next line.\"}'",
            "powershell": "Write-Output '{\"additionalContext\": \"If NEXT.md exists, read it and continue from its next line.\"}'",
            "cwd": "."
          }
        ]
      }
    }

    The preCompact entry fires right before compaction, manual or automatic, and its payload carries the transcript path (it arrives on stdin), so the full record gets copied while it still exists. The sessionStart entry returns additionalContext, which the CLI injects at the top of a new or resumed session. Command hooks fire on resume, prompt hooks do not. Put .copilot-transcripts/ in your .gitignore, and note the bash side needs jq.

    Test the loop in two minutes: start a task, let it get mid-step, drop a NEXT.md in place, run /compact by hand (manual compactions fire the same hook, and Esc cancels if you change your mind), then ask what it was doing and what comes next. It should answer from the file instead of re-deriving from the repo, and .copilot-transcripts should hold a fresh copy. If it re-derives, the AGENTS.md block did not load, and /instructions lists the instruction files the session actually discovered. /context shows where you stand against the window, and /session checkpoints lists what each compaction kept when you need to read back.

  3. kvnloo commented on Sep 27, 2026

    @kvnloo

    I think “task context” should be split from the prose summary.

    The summary can remain lossy, but compaction should preserve a tiny structured continuation record verbatim:

    • current objective
    • active step
    • last completed tool/result
    • pending next action
    • relevant artifact/path refs
    • blocked/waiting state

    The key invariant is: compaction may summarize history, but it must not synthesize or rewrite the execution cursor.

    A deterministic regression test could compact between a tool result and the next planned action, then assert the post-compaction agent continues with exactly that next action without rediscovering state from the repo.

  4. John-Rood commented on Sep 27, 2026

    @John-Rood

    The record only stays verbatim if nothing has to author it. A model-written continuation record is stale at an arbitrary compaction point, because it reflects the last time the model chose to update it, not the last thing that happened. Some of your fields are mechanical though. The last tool call and its result, path refs included, sit in the transcript tail and can be carried over byte for byte with zero synthesis. Pending next action is not in the transcript at all unless the model said it out loud before the call, so the regression test as written passes or fails on prompt style rather than on the compactor.

    The enforceable version: pin the last assistant turn plus its tool call and result outside the summary, verbatim, on every compaction. That is the cursor. It is always current and needs nothing from the model. Put the structured record on top for objective and blocked state, the fields only the model knows. The test then has two assertions: the pinned tail is byte identical before and after, and the first post-compaction tool call matches the one an uncompacted run produces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:context-memoryContext window, memory, compaction, checkpoints, and instruction loading

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions