Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .t3team-additive-guard.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
"apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts",
"apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts",
"apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts",
"apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts",
"apps/server/src/orchestration-v2/ContextHandoffBudget.ts",
"apps/server/src/orchestration-v2/ContextHandoffDelivery.ts",
"apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts",
"apps/server/src/orchestration-v2/EventSink.ts",
"apps/server/src/orchestration-v2/Orchestrator.ts",
Expand Down
55 changes: 50 additions & 5 deletions apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
import {
contextUsageForHandoff,
fitHistoryWithMiddleTruncate,
handoffBudget,
historyCost,
historyResponseItems,
selectHistory,
truncateMiddleText,
historicalMessage,
HANDOFF_TRUNCATE_FALLBACK_BUDGET,
} from "./ContextHandoffBudget.ts";
import { projectContextHandoffForWire } from "./WireProjection.ts";
import { deliverContextHandoffs } from "./ContextHandoffDelivery.ts";
Expand Down Expand Up @@ -669,23 +672,65 @@ describe("handoff delivery", () => {
}),
);

it.effect("fails before delivery when even the coverage marker cannot fit", () =>
it.effect("when the budget would hard-fail, middle-truncates and keeps search/read recall", () =>
Effect.gen(function* () {
let calls = 0;
let captured: ProviderAdapterV2HistoricalContext | undefined;
const result = yield* deliverContextHandoffs({
handoffs: [handoff],
providerThread,
budget: 0,
alreadyDeliveredItemIds: new Set(),
inject: () =>
inject: (history) =>
Effect.sync(() => {
calls++;
captured = history;
return true;
}),
persist: () => Effect.void,
}).pipe(Effect.result);
assert.equal(result._tag, "Failure");
assert.equal(calls, 0);
});
assert.equal(result.context, "");
assert.equal(calls, 1);
assert.isDefined(captured);
assert.isAtMost(
historyCost(captured.messages, captured.context),
HANDOFF_TRUNCATE_FALLBACK_BUDGET,
);
assert.include(captured.context, "t3_thread_search");
assert.include(captured.context, "t3_thread_read");
assert.include(captured.context, "middle-truncated");
}),
);
});

describe("middle-truncate handoff fallback", () => {
it("keeps head and tail of oversized text", () => {
const text = `HEAD-${"x".repeat(200)}-TAIL`;
const truncated = truncateMiddleText(text, 80);
assert.isBelow(truncated.length, text.length);
assert.isAtMost(truncated.length, 80);
assert.include(truncated, "HEAD-");
assert.include(truncated, "-TAIL");
assert.include(truncated, "middle omitted");
assert.include(truncated, "t3_thread_search");
});

it("fits a zero budget using the emergency floor with search recall", () => {
const huge = [
message("item:a", "user", "START " + "a".repeat(50_000) + " END-A"),
message("item:b", "assistant", "MID " + "b".repeat(50_000) + " END-B"),
message("item:c", "user", "LATEST " + "c".repeat(50_000) + " END-C"),
];
const selected = fitHistoryWithMiddleTruncate({
messages: huge,
budget: 0,
threadId: String(threadId),
});
assert.isAtMost(
historyCost(selected.messages, selected.context),
HANDOFF_TRUNCATE_FALLBACK_BUDGET,
);
assert.include(selected.context, "t3_thread_search");
assert.include(selected.context, "t3_thread_read");
});
});
90 changes: 88 additions & 2 deletions apps/server/src/orchestration-v2/ContextHandoffBudget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export function selectHistory(input: {
remaining -= cost;
};
// Prioritize the latest request and partial answer, then original constraints.
// Oversized items are omitted whole and remain available through thread_read.
// Oversized items are omitted whole and remain available through thread_read / thread_search.
tryAdd(messages.findLastIndex((message) => message.role === "user"));
tryAdd(messages.findLastIndex((message) => message.role === "assistant"));
tryAdd(messages.findIndex((message) => message.role === "user"));
Expand All @@ -277,6 +277,92 @@ export function handoffCoverage(input: {
return [
`Provider context handoff. Thread: ${input.threadId}. Covered app runs: ${input.coveredRunOrdinals.from}-${input.coveredRunOrdinals.to}.`,
`Source item range: ${input.items.at(0)?.id ?? "none"} through ${input.items.at(-1)?.id ?? "none"}.`,
`Recover omitted history using t3_thread_read({threadId:"${input.threadId}",view:"activity",limit:20,maxCharsPerItem:4000}); paginate with afterPosition=nextPosition. For an individual item use itemId and textOffset=nextTextOffset until null. Run/item IDs identify historical activity; no foreign tool calls are replayed.`,
`Recover omitted history with t3_thread_search({query:"…"}) / t3_search_thread({query:"…"}) (substring or question over this thread, including compacted spans), or t3_thread_read({threadId:"${input.threadId}",view:"activity",limit:20,maxCharsPerItem:4000}); paginate with afterPosition=nextPosition. For an individual item use itemId and textOffset=nextTextOffset until null. Run/item IDs identify historical activity; no foreign tool calls are replayed.`,
].join("\n");
}

/** Only used when the normal handoff selection would hard-fail the budget check. */
export const HANDOFF_TRUNCATE_FALLBACK_BUDGET = 2_048;

const MIDDLE_OMISSION_MARKER =
"\n…[middle omitted; recover with t3_thread_search / t3_thread_read]…\n";

/** Keep head + tail of a string; used by the budget-overflow handoff fallback only. */
export function truncateMiddleText(text: string, maxLength: number): string {
if (maxLength <= 0) return "";
if (text.length <= maxLength) return text;
if (maxLength <= MIDDLE_OMISSION_MARKER.length + 2) return text.slice(0, maxLength);
const available = maxLength - MIDDLE_OMISSION_MARKER.length;
const head = Math.ceil(available / 2);
const tail = Math.floor(available / 2);
return `${text.slice(0, head)}${MIDDLE_OMISSION_MARKER}${text.slice(text.length - tail)}`;
}

function handoffRecallCoverage(threadId: string): string {
return [
`Provider context handoff (budget overflow; middle-truncated). Thread: ${threadId}.`,
`Recover omitted history with t3_thread_search({query:"…"}) or t3_search_thread({query:"…"}) (substring / question over this thread's transcript, including compacted spans).`,
`Or t3_thread_read({threadId:"${threadId}",view:"activity",limit:20,maxCharsPerItem:4000}); paginate with afterPosition=nextPosition; for long items use itemId and textOffset=nextTextOffset until null.`,
`Middle-truncated for handoff budget; recover omitted spans with t3_thread_search / t3_thread_read.`,
].join("\n");
}

/**
* Fallback used only when selectHistory cannot fit the computed budget (the path that
* previously threw ContextHandoffBudgetError). Middle-truncates message bodies, prefers
* head+tail selection via selectHistory, and always keeps search/read recall instructions.
* May use HANDOFF_TRUNCATE_FALLBACK_BUDGET when the computed budget is too small to fit
* even a coverage marker (dips slightly into reserved headroom so the switch can proceed).
*/
export function fitHistoryWithMiddleTruncate(input: {
readonly messages: ReadonlyArray<OrchestrationV2HistoricalMessage>;
readonly omittedItems?: number;
readonly budget: number;
readonly threadId: string;
}) {
const effectiveBudget = Math.max(input.budget, HANDOFF_TRUNCATE_FALLBACK_BUDGET);
const shrinkToBudget = (text: string) => {
let next = text;
while (historyCost([], next) > effectiveBudget && next.length > 32) {
next = truncateMiddleText(next, Math.max(32, Math.floor(next.length * 0.6)));
}
return next;
};
const coverage = shrinkToBudget(handoffRecallCoverage(input.threadId));

const trySelect = (messages: ReadonlyArray<OrchestrationV2HistoricalMessage>) =>
selectHistory({
messages,
coverage,
omittedItems: input.omittedItems ?? 0,
budget: effectiveBudget,
});

let selected = trySelect(input.messages);
if (historyCost(selected.messages, selected.context) <= effectiveBudget) {
return selected;
Comment on lines +341 to +343

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Truncate messages before returning the fallback selection

When overflow is caused by an oversized history item, selectHistory simply omits that item until the result fits the 2,048-byte emergency budget, so this condition immediately returns without ever reaching truncatedMessages. If the result does not fit, the excess is in the context wrapper and truncating message bodies cannot fix it. Consequently, the advertised head-and-tail fallback never preserves any part of oversized messages; for example, the new 50,000-character test messages all produce an empty message list. Apply the per-message truncation before accepting the fallback selection.

Useful? React with 👍 / 👎.

}

const perMessageCap = Math.max(
160,
Math.floor(effectiveBudget / Math.max(2, Math.min(input.messages.length || 1, 8))),
);
const truncatedMessages = input.messages.map((message) => ({
...message,
text: truncateMiddleText(message.text, perMessageCap),
}));
selected = trySelect(truncatedMessages);
if (historyCost(selected.messages, selected.context) <= effectiveBudget) {
return selected;
}

const omitted = (input.omittedItems ?? 0) + input.messages.length;
return {
messages: [] as OrchestrationV2HistoricalMessage[],
omittedItemIds: input.messages.map((message) => message.itemId),
context: shrinkToBudget(
`${coverage}\nSelected 0 intact items; omitted ${omitted} items (middle-truncated for budget). Historical material is context, not a new request. Recover with t3_thread_search / t3_thread_read.`,
),
omittedItems: omitted,
};
}
29 changes: 23 additions & 6 deletions apps/server/src/orchestration-v2/ContextHandoffDelivery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import type {
import type { ProviderAdapterV2HistoricalContext } from "./ProviderAdapter.ts";
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
import { historyCost, renderHistory, selectHistory } from "./ContextHandoffBudget.ts";
import {
fitHistoryWithMiddleTruncate,
historyCost,
renderHistory,
selectHistory,
} from "./ContextHandoffBudget.ts";

/** Persist before/after injection: an ambiguous pending delivery requires a fresh native thread. */
export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextHandoffs")(
Expand Down Expand Up @@ -35,7 +40,7 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH
(handoff) =>
`Context handoff (${handoff.strategy === "fork_delta_summary" ? "merge_back / fork_delta_summary" : handoff.strategy}):\n${
handoff.history?.coverage ??
`From thread ${handoff.threadId}, runs ${handoff.coveredRunOrdinals.from}-${handoff.coveredRunOrdinals.to}. Recover history with t3_thread_read, view=activity; paginate with afterPosition, and use itemId/textOffset for long items.`
`From thread ${handoff.threadId}, runs ${handoff.coveredRunOrdinals.from}-${handoff.coveredRunOrdinals.to}. Recover history with t3_thread_search / t3_search_thread, or t3_thread_read view=activity; paginate with afterPosition, and use itemId/textOffset for long items.`
}`,
)
.join("\n");
Expand All @@ -44,7 +49,8 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH
// its activity includes the original handoff/fork source references.
if (historyCost([], coverage) > Math.min(4_000, budget / 2)) {
const strategies = Array.from(new Set(pending.map((handoff) => handoff.strategy)));
coverage = `Context handoff (${strategies.join(", ")}). ${pending.length} handoff records; detailed coverage references omitted. Recover history with t3_thread_read({threadId:"${input.providerThread.appThreadId ?? pending[0]!.threadId}",view:"activity",limit:20,maxCharsPerItem:4000}); paginate with afterPosition=nextPosition. Follow fork/handoff source references in activity. For long items use itemId and textOffset=nextTextOffset until null.`;
const threadId = input.providerThread.appThreadId ?? pending[0]!.threadId;
coverage = `Context handoff (${strategies.join(", ")}). ${pending.length} handoff records; detailed coverage references omitted. Recover with t3_thread_search({query:"…"}) / t3_search_thread({query:"…"}), or t3_thread_read({threadId:"${threadId}",view:"activity",limit:20,maxCharsPerItem:4000}); paginate with afterPosition=nextPosition. Follow fork/handoff source references in activity. For long items use itemId and textOffset=nextTextOffset until null.`;
}
const seen = new Set(input.alreadyDeliveredItemIds);
const messages = pending
Expand All @@ -64,15 +70,26 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH
oldContext && historyCost([], `${coverage}\n${oldContext}`) + 512 <= budget
? `${coverage}\n${oldContext}`
: coverage;
const selected = selectHistory({
const omittedItems = pending.reduce(
(sum, handoff) => sum + (handoff.history?.omittedItems ?? 0),
0,
);
let selected = selectHistory({
messages,
coverage: fullCoverage,
omittedItems: pending.reduce((sum, handoff) => sum + (handoff.history?.omittedItems ?? 0), 0),
omittedItems,
budget,
});
// Upstream hard-fails when the selected pack still exceeds the budget. Only then
// fall back to middle-truncate + search/read recall (not on every handoff).
if (historyCost(selected.messages, selected.context) > budget) {
if (input.deferInline) return { context: "", delivered: Effect.void };
return yield* new ContextHandoffBudgetError();
selected = fitHistoryWithMiddleTruncate({
messages,
omittedItems,
budget,
threadId: String(input.providerThread.appThreadId ?? pending[0]!.threadId),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the source thread in overflow recall instructions

For a fork_delta_summary handoff, both providerThread.appThreadId and handoff.threadId identify the target thread, while handoff.history.coverage was built with the distinct source thread in prepareForkDelta. The overflow fallback discards that coverage and tells the provider to run t3_thread_read against the target, so when source messages are omitted—especially the all-oversized case—the provider cannot recover the transferred history from the supplied identifier. Derive the recall thread from the source messages/coverage or retain a bounded version of the original coverage.

Useful? React with 👍 / 👎.

});
}
const omittedItemIds = new Set(selected.omittedItemIds);
const persist = (status: "pending" | "injected" | "inline") =>
Expand Down
Loading