Compare commits

...
Author SHA1 Message Date
Aiden Cline be6d424b67 feat(core): record overflow as a compaction reason 2026-09-27 22:14:42 -05:00
7 changed files with 43 additions and 31 deletions
+3 -3
View File
@@ -1174,7 +1174,7 @@ export type SessionLogOutput =
| undefined
readonly data: {
readonly sessionID: Session.ID
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly recent: string
readonly inputID?: SessionMessage.ID | undefined
}
@@ -1193,7 +1193,7 @@ export type SessionLogOutput =
| undefined
readonly data: {
readonly sessionID: Session.ID
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly model?: Model.Ref | undefined
readonly providerState?: SessionMessage.ProviderState | undefined
readonly providerContext?:
@@ -1237,7 +1237,7 @@ export type SessionLogOutput =
| undefined
readonly data: {
readonly sessionID: Session.ID
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
readonly inputID?: SessionMessage.ID | undefined
readonly cost?: (number & Brand.Brand<"Money.USD">) | undefined
+15 -15
View File
@@ -141,7 +141,7 @@ export type SessionMessageCompactionRunning = {
metadata?: { [x: string]: JsonValue }
time: { created: number }
status: "running"
reason: "auto" | "manual"
reason: "auto" | "manual" | "overflow"
summary: string
recent: string
}
@@ -534,7 +534,7 @@ export type SessionMessageCompactionFailed = {
metadata?: { [x: string]: JsonValue }
time: { created: number }
status: "failed"
reason: "auto" | "manual"
reason: "auto" | "manual" | "overflow"
error: SessionStructuredError
cost?: MoneyUSD
tokens?: TokenUsageInfo
@@ -788,7 +788,7 @@ export type SessionCompactionStarted = {
type: "session.compaction.started"
durable: { aggregateID: string; seq: number; version: 1 }
location?: LocationRef
data: { sessionID: string; reason: "auto" | "manual"; recent: string; inputID?: string }
data: { sessionID: string; reason: "auto" | "manual" | "overflow"; recent: string; inputID?: string }
}
export type SessionCompactionFailed = {
@@ -800,7 +800,7 @@ export type SessionCompactionFailed = {
location?: LocationRef
data: {
sessionID: string
reason: "auto" | "manual"
reason: "auto" | "manual" | "overflow"
error: SessionStructuredError
inputID?: string
cost?: MoneyUSD
@@ -1776,7 +1776,7 @@ export type SessionMessageCompactionCompleted = {
metadata?: { [x: string]: JsonValue }
time: { created: number }
status: "completed"
reason: "auto" | "manual"
reason: "auto" | "manual" | "overflow"
model?: ModelRef
providerState?: SessionMessageProviderState
summary: string
@@ -1795,7 +1795,7 @@ export type SessionCompactionEnded = {
location?: LocationRef
data: {
sessionID: string
reason: "auto" | "manual"
reason: "auto" | "manual" | "overflow"
model?: ModelRef
providerState?: SessionMessageProviderState1
providerContext?: SessionProviderContext
@@ -3226,7 +3226,7 @@ export type SessionImportInput = {
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly status: "running"
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly summary: string
readonly recent: string
}
@@ -3236,7 +3236,7 @@ export type SessionImportInput = {
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly status: "completed"
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly providerState?: { readonly [x: string]: JsonValue }
readonly summary: string
@@ -3267,7 +3267,7 @@ export type SessionImportInput = {
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly status: "failed"
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
readonly cost?: number
readonly tokens?: {
@@ -3543,7 +3543,7 @@ export type SessionImportInput = {
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly status: "running"
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly summary: string
readonly recent: string
}
@@ -3553,7 +3553,7 @@ export type SessionImportInput = {
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly status: "completed"
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly providerState?: { readonly [x: string]: JsonValue }
readonly summary: string
@@ -3584,7 +3584,7 @@ export type SessionImportInput = {
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly status: "failed"
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
readonly cost?: number
readonly tokens?: {
@@ -3860,7 +3860,7 @@ export type SessionImportInput = {
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly status: "running"
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly summary: string
readonly recent: string
}
@@ -3870,7 +3870,7 @@ export type SessionImportInput = {
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly status: "completed"
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly providerState?: { readonly [x: string]: JsonValue }
readonly summary: string
@@ -3901,7 +3901,7 @@ export type SessionImportInput = {
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly status: "failed"
readonly reason: "auto" | "manual"
readonly reason: "auto" | "manual" | "overflow"
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
readonly cost?: number
readonly tokens?: {
+7 -4
View File
@@ -376,7 +376,11 @@ export const layer = Layer.effect(
const context = trigger.context
// The runner opened the manual compaction's message when it delivered the `/compact` item.
if (trigger.reason !== "manual") {
yield* bus.publish(SessionEvent.Compaction.Started, { sessionID: context.session.id, reason: "auto", recent })
yield* bus.publish(SessionEvent.Compaction.Started, {
sessionID: context.session.id,
reason: trigger.reason,
recent,
})
}
if (prepared.event.result) return yield* fromHook(context, prepared.event.result, recent)
@@ -614,13 +618,12 @@ export const layer = Layer.effect(
): Effect.fn.Return<Outcome> {
const context = trigger.context
const sessionID = context.session.id
const reason = trigger.reason === "manual" ? "manual" : "auto"
const usage = spent.get(sessionID)
if ("error" in outcome) {
yield* bus.publish(SessionEvent.Compaction.Failed, {
sessionID,
reason,
reason: trigger.reason,
inputID: trigger.reason === "manual" ? trigger.inputID : undefined,
error: outcome.error,
...usage,
@@ -632,7 +635,7 @@ export const layer = Layer.effect(
SessionEvent.Compaction.Ended,
{
sessionID,
reason,
reason: trigger.reason,
model: context.model.ref,
providerState: outcome.providerState,
providerContext: outcome.providerContext,
+8 -5
View File
@@ -3234,11 +3234,14 @@ describe("SessionRunnerLLM", () => {
expect(userTexts(s.requests[1]).at(-1)).toContain("## Objective")
expect(userTexts(s.requests[2])[0]).toContain("<summary>\n## Objective\n- Recover overflow\n</summary>")
expect(yield* s.context).toMatchObject([
{ type: "compaction", summary: "## Objective\n- Recover overflow" },
{ type: "compaction", reason: "overflow", summary: "## Objective\n- Recover overflow" },
{ type: "assistant", finish: "stop" },
])
yield* replaySessionProjection(sessionID)
expect(yield* s.context).toMatchObject([{ type: "compaction" }, { type: "assistant", finish: "stop" }])
expect(yield* s.context).toMatchObject([
{ type: "compaction", reason: "overflow" },
{ type: "assistant", finish: "stop" },
])
})
scenario("refreshes preparation after overflow compaction without promoting new input", function* (s) {
@@ -3476,13 +3479,13 @@ describe("SessionRunnerLLM", () => {
expect.objectContaining({
type: "compaction",
status: "failed",
reason: "auto",
reason: "overflow",
error: { type: "provider.error", message: "summary unavailable" },
}),
)
expect(context.slice(-3)).toMatchObject([
Expected.user("Continue"),
{ type: "compaction", status: "failed", reason: "auto" },
{ type: "compaction", status: "failed", reason: "overflow" },
{ type: "assistant", finish: "error", error: { message: "prompt too long" } },
])
})
@@ -3509,7 +3512,7 @@ describe("SessionRunnerLLM", () => {
expect.objectContaining({
type: "compaction",
status: "failed",
reason: "auto",
reason: "overflow",
error: { type: "compaction.interrupted", message: "Compaction was interrupted" },
}),
)
+1 -1
View File
@@ -588,7 +588,7 @@ export namespace Compaction {
...options,
schema: {
...Base,
reason: Schema.Literals(["auto", "manual"]),
reason: SessionMessage.CompactionRunning.fields.reason,
recent: Schema.String,
inputID: SessionMessage.ID.pipe(optional),
},
+6 -3
View File
@@ -237,6 +237,9 @@ export const Assistant = Schema.Struct({
const CompactionBase = { type: Schema.tag("compaction"), ...Base }
/** `overflow`: the provider rejected the request as too long, before our own size check called for it. */
const CompactionReason = Schema.Literals(["auto", "manual", "overflow"])
/** Usage of the compaction request itself, not the size of the resulting context. */
const CompactionUsage = {
cost: Money.USD.pipe(optional),
@@ -247,7 +250,7 @@ export interface CompactionRunning extends Schema.Schema.Type<typeof CompactionR
export const CompactionRunning = Schema.Struct({
...CompactionBase,
status: Schema.tag("running"),
reason: Schema.Literals(["auto", "manual"]),
reason: CompactionReason,
summary: Schema.String,
recent: Schema.String,
}).annotate({ identifier: "Session.Message.Compaction.Running" })
@@ -256,7 +259,7 @@ export interface CompactionCompleted extends Schema.Schema.Type<typeof Compactio
export const CompactionCompleted = Schema.Struct({
...CompactionBase,
status: Schema.tag("completed"),
reason: Schema.Literals(["auto", "manual"]),
reason: CompactionReason,
model: Model.Ref.pipe(optional),
providerState: ProviderState.pipe(optional),
summary: Schema.String,
@@ -269,7 +272,7 @@ export interface CompactionFailed extends Schema.Schema.Type<typeof CompactionFa
export const CompactionFailed = Schema.Struct({
...CompactionBase,
status: Schema.tag("failed"),
reason: Schema.Literals(["auto", "manual"]),
reason: CompactionReason,
error: SessionError.Error,
...CompactionUsage,
}).annotate({ identifier: "Session.Message.Compaction.Failed" })
@@ -2052,6 +2052,9 @@ function CompactionMessage(props: { message: Extract<SessionMessageInfo, { type:
? "Provider compaction"
: "Compaction"}
</text>
<Show when={props.message.reason === "overflow"}>
<text fg={color()}>· context overflow</text>
</Show>
<Show when={cancelled()}>
<text fg={color()}>· cancelled</text>
</Show>