Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,20 @@ function ServerSettingsDetail(props: { readonly page: SettingsPage }) {
))}
</SettingsSection>
) : null}
<SettingsSection title="Long threads">
<SettingsSwitchRow
icon="clock"
label="Fresh session after a long break"
subtitle={
projectSelected
? "Environment-wide setting. Select All projects to change it."
: "After an hour away, continue a long thread in a new provider session with recent context. Lowers cached input cost."
}
value={uniform("freshSessionOnColdReturn")}
disabled={disabledFor("freshSessionOnColdReturn")}
onValueChange={(value) => write({ freshSessionOnColdReturn: value })}
/>
</SettingsSection>
<SettingsSection title="Skills">
<SettingsSwitchRow
icon="square.grid.2x2"
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/features/settings/settings-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ const SETTINGS_SEARCH_TERMS = {
"project memory",
"assisted learning",
"conversation summaries",
"fresh session",
"long break",
"cached input",
"external history",
"pstack",
"skills library",
Expand Down
208 changes: 201 additions & 7 deletions apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as PlatformError from "effect/PlatformError";
import * as Schema from "effect/Schema";
import * as TestClock from "effect/testing/TestClock";

import * as ServerConfig from "../config.ts";
import { withWorkspaceLease } from "../workspace/workspaceLease.ts";
Expand Down Expand Up @@ -83,11 +84,14 @@ const normalize = (target: string) => {
}
return `/${parts.join("/")}`;
};
const layerWorktreeLocation = (worktreesDir = managedWorktreesDir) =>
const layerWorktreeLocation = (
worktreesDir = managedWorktreesDir,
settings?: Parameters<typeof ServerSettings.layerTest>[0],
) =>
Layer.mergeAll(
Path.layer,
Layer.mock(ServerConfig.ServerConfig)({ worktreesDir } as ServerConfig.ServerConfig["Service"]),
ServerSettings.layerTest(),
ServerSettings.layerTest(settings),
);

function startWithMissingWorktree(
Expand Down Expand Up @@ -286,6 +290,7 @@ it.each([
});

function makeLocalCommandHarness(input: {
readonly settings?: Parameters<typeof ServerSettings.layerTest>[0];
readonly pstack?: PstackPack.PstackPack["Service"];
readonly text: string;
readonly knowledgeStore?: Effect.Success<typeof KnowledgePersistence.makeStore>;
Expand All @@ -307,6 +312,8 @@ function makeLocalCommandHarness(input: {
readonly ensureThreadFailure?: unknown;
readonly historyReadFailureAfterFallback?: unknown;
readonly resumeFailure?: ProviderAdapter.ProviderAdapterV2Error;
readonly pendingBackgroundWorkOnResume?: boolean;
readonly pendingBackgroundWorkAfterHandoff?: boolean;
readonly interruptOpen?: boolean;
readonly interruptRunBeforeOpenFailure?: boolean;
readonly writeFailure?: unknown;
Expand Down Expand Up @@ -786,7 +793,20 @@ function makeLocalCommandHarness(input: {
const nativeStart = vi.fn<ProviderAdapter.ProviderAdapterV2SessionRuntime["startTurn"]>(
() => Effect.void,
);
const fallbackEnsureThread = vi.fn(() => Effect.succeed(providerThread));
const fallbackEnsureThread = vi.fn(() =>
Effect.succeed(
input.recoveryNativeId === undefined
? providerThread
: {
...providerThread,
nativeThreadRef: {
driver: providerThread.driver,
nativeId: input.recoveryNativeId,
strength: "strong" as const,
},
},
),
);
const resumeFallbackSession = {
driver: providerThread.driver,
resumeThread: () =>
Expand Down Expand Up @@ -832,13 +852,19 @@ function makeLocalCommandHarness(input: {
forkThread,
startTurn,
};
let resumedNativeThread = false;
let preparedRecoveryHandoff = false;
const open = vi.fn(() =>
input.sideChat !== undefined
? Effect.succeed(sideChatSession as never)
: input.interruptOpen === true
? Effect.interrupt
: "historyReadFailureAfterFallback" in input || "resumeFailure" in input
? Effect.succeed(resumeFallbackSession as never)
? Effect.succeed({
...resumeFallbackSession,
providerSession,
startTurn: nativeStart,
} as never)
: "ensureThreadFailure" in input
? Effect.succeed({ driver: providerThread.driver, ensureThread } as never)
: "openFailure" in input
Expand Down Expand Up @@ -870,7 +896,18 @@ function makeLocalCommandHarness(input: {
strength: "strong" as const,
},
}),
resumeThread: () => Effect.succeed(projection.providerThreads.at(-1)),
resumeThread: () =>
Effect.sync(() => {
resumedNativeThread = true;
return projection.providerThreads.at(-1);
}),
hasPendingBackgroundWorkForThread: () =>
Effect.sync(
() =>
(input.pendingBackgroundWorkOnResume === true && resumedNativeThread) ||
(input.pendingBackgroundWorkAfterHandoff === true &&
preparedRecoveryHandoff),
),
} as never)
: Effect.die("A local command must not open a native session."),
);
Expand Down Expand Up @@ -1062,7 +1099,13 @@ function makeLocalCommandHarness(input: {
summaryText: input.sideChat ? "PARENT_HISTORY" : "Recovered history",
createdByProviderInstanceId: input.sideChat ? request.toProviderInstanceId : null,
updatedAt: request.createdAt,
} as OrchestrationV2ContextHandoff),
} as OrchestrationV2ContextHandoff).pipe(
Effect.tap(() =>
Effect.sync(() => {
preparedRecoveryHandoff = true;
}),
),
),
}),
Layer.mock(EventSink.EventSinkV2)({
writeIfRunCurrent,
Expand All @@ -1076,7 +1119,7 @@ function makeLocalCommandHarness(input: {
}),
}),
IdAllocator.layer,
layerWorktreeLocation(input.worktreesDir),
layerWorktreeLocation(input.worktreesDir, input.settings),
input.fileSystem === undefined
? FileSystem.layerNoop({ exists: () => Effect.succeed(true) })
: Layer.succeed(FileSystem.FileSystem, input.fileSystem),
Expand Down Expand Up @@ -2434,6 +2477,157 @@ effectIt.effect(
}),
);

effectIt.effect.each([
{ name: "a long idle thread", enabled: true, usedTokens: 120_000, idleMinutes: 61, fresh: true },
{ name: "the setting off", enabled: false, usedTokens: 120_000, idleMinutes: 61, fresh: false },
{ name: "a recent turn", enabled: true, usedTokens: 120_000, idleMinutes: 59, fresh: false },
{ name: "a small thread", enabled: true, usedTokens: 99_999, idleMinutes: 61, fresh: false },
{
name: "recent completion after an old token report",
enabled: true,
usedTokens: 120_000,
idleMinutes: 61,
turnCompletedMinutes: 60,
fresh: false,
},
{
name: "insufficient room for a fresh handoff",
enabled: true,
usedTokens: 120_000,
idleMinutes: 61,
modelContextWindow: 16_000,
fresh: false,
},
{
name: "a native thread held by another client",
enabled: true,
usedTokens: 120_000,
idleMinutes: 61,
threadInUse: true,
fresh: false,
},
{
name: "background work discovered while resuming",
enabled: true,
usedTokens: 120_000,
idleMinutes: 61,
pendingBackgroundWorkOnResume: true,
fresh: false,
},
{
name: "background work discovered while preparing the handoff",
enabled: true,
usedTokens: 120_000,
idleMinutes: 61,
pendingBackgroundWorkAfterHandoff: true,
fresh: false,
},
{
name: "a command message",
enabled: true,
usedTokens: 120_000,
idleMinutes: 61,
text: "/compact",
fresh: false,
},
{
name: "a message not from a person",
enabled: true,
usedTokens: 120_000,
idleMinutes: 61,
createdBy: "system" as const,
fresh: false,
},
] as const)("starting a turn with $name picks fresh session: $fresh", (scenario) =>
Effect.gen(function* () {
const harness = makeLocalCommandHarness({
settings: { freshSessionOnColdReturn: scenario.enabled },
text: "text" in scenario ? scenario.text : "Keep going on the migration",
previousTurn: { runStatus: "completed", turnStatus: "completed" },
recoveryNativeId: "fresh-native-conversation",
...("modelContextWindow" in scenario
? { modelContextWindow: scenario.modelContextWindow }
: {}),
...("threadInUse" in scenario
? {
resumeFailure: new ProviderAdapter.ProviderAdapterThreadInUseError({
driver: ProviderDriverKind.make("codex"),
providerThreadId: ProviderThreadId.make("new-provider-thread"),
detail: "This conversation is open in another Codex client.",
cause: new Error("thread existing-native-conversation already has an active writer"),
}),
}
: {}),
...("pendingBackgroundWorkOnResume" in scenario
? { pendingBackgroundWorkOnResume: scenario.pendingBackgroundWorkOnResume }
: {}),
...("pendingBackgroundWorkAfterHandoff" in scenario
? { pendingBackgroundWorkAfterHandoff: scenario.pendingBackgroundWorkAfterHandoff }
: {}),
});
const projection = harness.projection();
const previousAt = "2026-09-04T12:00:00.000Z";
const previousAttemptId = RunAttemptId.make("previous-failed-attempt");
harness.setProjection({
...projection,
messages: projection.messages.map((message) => ({
...message,
...("createdBy" in scenario ? { createdBy: scenario.createdBy } : {}),
})),
attempts: [
...projection.attempts,
{
...projection.attempts[0]!,
id: previousAttemptId,
runId: RunId.make("previous-failed-run"),
rootNodeId: NodeId.make("previous-root"),
nativeThreadId: "existing-native-conversation",
providerThreadId: projection.providerThreads[0]!.id,
status: "completed",
},
],
providerTurns: projection.providerTurns.map((turn) => ({
...turn,
tokenUsage: { usedTokens: scenario.usedTokens, updatedAt: previousAt },
...("turnCompletedMinutes" in scenario
? {
completedAt: DateTime.makeUnsafe(
Date.parse(previousAt) + scenario.turnCompletedMinutes * 60_000,
),
}
: {}),
})),
});
yield* TestClock.setTime(Date.parse(previousAt) + scenario.idleMinutes * 60_000);

yield* harness.start;

if ("threadInUse" in scenario) {
expect(harness.fallbackEnsureThread).not.toHaveBeenCalled();
expect(harness.startRootRun).not.toHaveBeenCalled();
expect(harness.projection().runs.at(-1)).toMatchObject({ status: "failed", startedAt: null });
}
// Commands run without a root run, which also means no fresh session.
const nativeId =
harness.startRootRun.mock.calls[0]?.[0].providerThread.nativeThreadRef?.nativeId;
expect(nativeId === "fresh-native-conversation").toBe(scenario.fresh);
expect(harness.events.some((event) => event.type === "context-handoff.updated")).toBe(
scenario.fresh,
);
if (
"pendingBackgroundWorkOnResume" in scenario ||
"pendingBackgroundWorkAfterHandoff" in scenario
) {
expect(nativeId).toBe("existing-native-conversation");
}
if ("modelContextWindow" in scenario) {
yield* dispatchKnowledge(harness.startRootRun.mock.calls[0]![0]);
expect(nativeId).toBe("existing-native-conversation");
expect(harness.nativeStart).toHaveBeenCalledOnce();
}
}),
);

effectIt.effect.each(["prepared", "uncertain"] as const)(
"recovers a %s knowledge receipt through the native handoff fallback without resuming the ambiguous conversation",
(status) =>
Expand Down
Loading
Loading