Skip to content

Emit a structured clear_boundary signal mirroring compact_boundary #656

@Seluj78

Description

@Seluj78

Note: Claude handled the investigation and prose via back-and-forth. Idea, decisions, and everything else are mine. — @Seluj78

Problem

When the user runs /clear, the Claude Code SDK ends its current internal conversation and starts a fresh one. The agent has no memory of prior turns after this point. From the ACP client's perspective, there is no structured signal that this happened: the adapter forwards /clear as a regular user prompt, the SDK handles it internally, and the next assistant turn just looks like a normal response with empty context.

Compare against compact_boundary, which the adapter explicitly handles in acp-agent.js:478-507:

case "compact_boundary": {
    // Send used:0 ... post-compaction context size dropped dramatically
    await this.client.sessionUpdate({
        sessionId: message.session_id,
        update: { sessionUpdate: "usage_update", used: 0, size: session.contextWindowSize },
    });
    await this.client.sessionUpdate({
        sessionId: message.session_id,
        update: {
            sessionUpdate: "agent_message_chunk",
            content: { type: "text", text: "\n\nCompacting completed." },
        },
    });
    break;
}

compact_boundary lets ACP clients render a visible divider, reset transcript expectations, and update the context-window indicator. /clear produces the same kind of state transition (context wiped, transcript misleading from the model's perspective) but the adapter emits nothing comparable.

Why this matters for ACP clients

Without a structured signal, clients have to detect /clear heuristically (substring match on the user prompt text). That fragile pattern:

  • Breaks on locale variants if /clear ever gets localised.
  • Breaks on user-defined custom clear commands.
  • Can't tell "user pasted the word /clear" from "user invoked the /clear command."

Most importantly, the transcript divergence after /clear is severe: the agent has no continuity at all (vs /compact which retains a summary). Without a boundary signal, the client either renders the full pre-clear transcript as if continuity exists (misleading) or has to text-match heuristically. Neither is robust.

What we'd like

Mirror compact_boundary. When /clear completes inside the SDK, emit either:

Option 1: _meta flag on the next agent message chunk

Same convention used for parentToolUseId. On the first chunk emitted after the clear:

update: {
    sessionUpdate: "agent_message_chunk",
    content: { type: "text", text: ... },
    _meta: { claudeCode: { clearBoundary: true } },
}

Minimal protocol surface. ACP clients that understand the flag render a clear boundary; clients that ignore it see a normal chunk. Same shape as compact_boundary's _meta would be if it were ever extended.

Option 2: dedicated sessionUpdate variant

A new top-level sessionUpdate: "session_cleared" variant the adapter emits when the SDK reports the clear boundary. Cleaner long-term protocol but needs ACP spec coordination.

Option 1 is the practical v1; Option 2 the right shape if the spec is being extended.

Concrete behavioral asks

When /clear lands inside the SDK and the adapter sees the corresponding boundary system message:

  1. Reset the cached lastAssistantTotalUsage / lastAssistantUsage to zero (matches compact_boundary behavior).
  2. Emit a usage_update with used: 0, size: session.contextWindowSize. Clients keep their context-window indicator from showing stale pre-clear numbers.
  3. Emit the clear-boundary signal (Option 1 or 2) so clients can render a divider and update transcript-presentation logic.
  4. Trigger sendAvailableCommandsUpdate(sessionId) so the client refreshes its slash palette (separate issue tracking this; mentioned here because the two land naturally together).

Out of scope

  • Surfacing the SDK's internal "what was cleared" payload. Clients only need to know the boundary happened; the prior transcript is on the client side already.
  • Locale handling of /clear itself. Boundary signal is independent of which surface command triggered it.

Context

Filed from the agent-of-empires side as the upstream half of njbrake/agent-of-empires#1101 (cockpit /clear UX gap). The aoe-side fix can ship with text-match detection as v1 (mirroring how /compact was handled in aoe), but a structured signal would eliminate that fragility and let any ACP client implement the same UX without per-client heuristics.

Tested against @agentclientprotocol/claude-agent-acp package at the version currently installed via npx claude-agent-acp. Adapter source inspected at dist/acp-agent.js lines 460-520 (system message switch) and 1796+ (getAvailableSlashCommands).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions