Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Compact

Request manual compaction for the active Loop or an exact Loop.

developer

Compaction rewrites the active native loop’s conversation at a safe boundary so the next request has room in its context window. The public API requests manual compaction and returns only a correlation ID. The compactor’s summary and context measurements arrive as events; the call does not block until a summary is available.

Public API and command

type Session interface {
	Compact(context.Context) (uuid.UUID, error)
	CompactToLoop(context.Context, uuid.UUID) (uuid.UUID, error)
}

type Compact struct {
	Header
	identity.Coordinates
}

Compact samples the active loop once. CompactToLoop targets one exact live loop. Both methods stamp user agency, session and loop coordinates, append the command intent when the journal is configured, and return the command ID after the command is handed to the actor.

The session rejects an unknown or exited target with *session.SessionError (SessionLoopNotFound or SessionLoopExited). It also rejects foreign loops and native loops without a configured compaction policy with SessionCompactionUnsupported. A faulted session admits no new compaction.

Lifecycle events

The command starts a compaction attempt. The event stream carries the actual result:

type CompactionStarted struct {
	ephemeral
	loopScoped
	Header
	AttemptID CompactAttemptID `json:"attempt_id"`
	Reason    CompactionReason `json:"reason"`
	Basis     ContextBasis `json:"basis"`
}

type CompactionCommitted struct {
	enduring
	loopScoped
	Header
	AttemptID        CompactAttemptID     `json:"attempt_id"`
	WaiterCommandIDs []uuid.UUID          `json:"waiter_command_ids"`
	Reason           CompactionReason     `json:"reason"`
	Basis            ContextBasis         `json:"basis"`
	Summary          *content.UserMessage `json:"summary"`
	PostContext      ContextMeasurement   `json:"post_context"`
	Duration         time.Duration        `json:"duration,omitzero"`
}

type CompactionRejected struct {
	enduring
	loopScoped
	Header
	AttemptID        CompactAttemptID    `json:"attempt_id"`
	WaiterCommandIDs []uuid.UUID         `json:"waiter_command_ids"`
	Reason           CompactionReason    `json:"reason"`
	Basis            ContextBasis        `json:"basis"`
	RejectReason     CompactRejectReason `json:"reject_reason"`
	Duration         time.Duration       `json:"duration,omitzero"`
}

type CompactWaiterResolved struct {
	enduring
	loopScoped
	Header
	AttemptID        CompactAttemptID `json:"attempt_id"`
	CommittedEventID uuid.UUID        `json:"committed_event_id"`
}

type CompactWaiterRejected struct {
	enduring
	loopScoped
	Header
	AttemptID CompactAttemptID    `json:"attempt_id"`
	Reason    CompactRejectReason `json:"reason"`
}

These declarations are the source definitions, including their lifecycle and scope mixins. CompactionStarted is Ephemeral. CompactionCommitted, CompactionRejected, and the waiter replies are Enduring. A manual request uses CompactionReasonManual; automatic policy requests use CompactionReasonAutomatic.

RejectionMeaning
CompactRejectControlLaneFullthe loop cannot admit another control request
CompactRejectShuttingDownteardown has started
CompactRejectInterrupted / CompactRejectCanceledthe attempt was canceled before commit
CompactRejectStaleBasisthe conversation changed since the attempt basis
CompactRejectUnavailablethe compaction capability is unavailable
CompactRejectExecutionFailedthe compactor failed to produce a valid result
CompactRejectInvalidSummary / CompactRejectSummaryTooLargethe returned summary failed validation
CompactRejectContextCountFailed / CompactRejectContextLimitUnknowncontext accounting could not establish a safe limit
CompactRejectProgressPublication / CompactRejectInternalthe durable progress path failed

Correlation and safe boundaries

The returned command ID is a waiter correlation key, not the compaction attempt ID. One compaction attempt can serve multiple waiting command IDs. Match event.CompactWaiterResolved or event.CompactWaiterRejected by the command’s causation ID and use AttemptID to group the shared attempt.

%%{init: {"theme":"dark"}}%%
sequenceDiagram
    participant App as Application
    participant S as Session
    participant L as Native loop
    participant E as Event fan-in

    App->>S: CompactToLoop(ctx, loopID)
    S->>L: Compact{SessionID, LoopID, CommandID}
    S-->>App: waiter command ID
    L->>E: CompactionStarted (ephemeral)
    L->>E: CompactionCommitted or CompactionRejected
    L->>E: CompactWaiterResolved or CompactWaiterRejected
    E-->>App: correlate by command ID

Compaction runs at an actor-owned boundary. A running turn is not rewritten underneath its own in-flight request. On a committed result, the summary is the new durable conversation basis and PostContext records the checked result. On rejection, no partial summary is installed. There is no public CompactWithSummary or generic RunCompaction method; callers cannot inject arbitrary conversation state through this command.

func requestCompaction(ctx context.Context, s session.Session, loopID uuid.UUID) error {
	commandID, err := s.CompactToLoop(ctx, loopID)
	if err != nil {
		var se *session.SessionError
		if errors.As(err, &se) && se.Kind == session.SessionCompactionUnsupported {
			return fmt.Errorf("loop %s has no native compaction: %w", loopID, err)
		}
		return err
	}
	log.Printf("compaction requested as %s", commandID)
	return nil
}

The dispatch and unsupported-target proof is internal/sessionruntime/session_compact_test.go. The event validation and waiter shape are covered by pkg/event/compaction.go and its focused tests. Those are executable proofs of behavior, not copy-paste CLI commands.

Source and proof

← back to documentation