Skip to content
Draft
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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

## Windows-Specific Notes

- The dedicated provider-daemon lowers its own priority before constructing provider runtimes. Windows normally launched children inherit BELOW_NORMAL_PRIORITY_CLASS; no process-table scan or external PID mutation is needed. Preserve an already lower launcher priority and continue startup if the OS rejects the request. This leaves Electron, the backend, direct backend-hosted providers, and already-running external runtimes unchanged. macOS/Linux use the same Node priority boundary and normal child inheritance. Native inheritance verification must use an explicit synthetic-process smoke, never change the test runner's priority in the default suite.

- The separate scheduled/manual reliability workflow runs the existing Windows native installer/runtime/uninstaller smoke only on disposable GitHub-hosted Windows runners. It does not run on user machines or move process-backed/provider tests into the default suite. macOS uses its isolated DMG/ZIP smoke, and Linux keeps its existing artifact build and pipeline coverage.

- Generic attachment originals and metadata are private `0600` files where POSIX permissions apply; provider-readable derivatives are `0400`. On Windows leave derivatives user-writable under Cafe's user-owned data-directory ACL instead of setting the read-only attribute, which would prevent safe cleanup. File names are inert display metadata and storage uses server-minted identifiers; Windows path separators in dropped names must never become server paths. Document extraction children use the current executable with `ELECTRON_RUN_AS_NODE=1` for packaged Electron backends and preserve only required Windows system-directory environment entries, not provider credentials or user-selected Node hooks. macOS/Linux retain the same isolated child behavior and their native permission checks.
Expand Down
6 changes: 6 additions & 0 deletions apps/server/src/cli/providerDaemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from "../config.ts";
import { runProviderDaemonServerForever } from "../providerDaemon/ProviderDaemonServer.ts";
import { ProviderDaemonRuntimeLive } from "../providerDaemon/ProviderDaemonRuntime.ts";
import { lowerProviderDaemonPriority } from "../providerDaemon/ProviderDaemonPriority.ts";
import { PROVIDER_SUPERVISOR_PROTOCOL_VERSION } from "../providerDaemon/ProviderSupervisorProcessManager.ts";
import { ObservabilityLive } from "../observability/Layers/Observability.ts";
import packageJson from "../../package.json" with { type: "json" };
Expand Down Expand Up @@ -104,6 +105,11 @@ export const runProviderDaemonCommand = (flags: { readonly bootstrapFd: Option.O
});
}

const priority = yield* Effect.sync(() => lowerProviderDaemonPriority());
if (priority === "unavailable") {
yield* Effect.logWarning("provider.daemon.priority.unavailable");
}

const baseConfig = yield* resolveProviderDaemonServerConfig({
bootstrap,
logLevel,
Expand Down
43 changes: 43 additions & 0 deletions apps/server/src/providerDaemon/ProviderDaemonPriority.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, it, vi } from "vitest";

import { lowerProviderDaemonPriority, PROVIDER_DAEMON_PRIORITY } from "./ProviderDaemonPriority.ts";

describe("provider daemon scheduling", () => {
it("lowers only the current daemon from normal priority", () => {
const setPriority = vi.fn();
expect(lowerProviderDaemonPriority({ getPriority: () => 0, setPriority })).toBe("lowered");
expect(setPriority).toHaveBeenCalledExactlyOnceWith(0, PROVIDER_DAEMON_PRIORITY);
});

it.each([PROVIDER_DAEMON_PRIORITY, 19])("preserves a launcher priority of %i", (priority) => {
const setPriority = vi.fn();
expect(lowerProviderDaemonPriority({ getPriority: () => priority, setPriority })).toBe(
"already-lower",
);
expect(setPriority).not.toHaveBeenCalled();
});

it("contains an unavailable priority read without changing a process", () => {
const setPriority = vi.fn();
expect(
lowerProviderDaemonPriority({
getPriority: () => {
throw new Error("host diagnostic contains private data");
},
setPriority,
}),
).toBe("unavailable");
expect(setPriority).not.toHaveBeenCalled();
});

it("contains a rejected priority write without exposing host diagnostics", () => {
expect(
lowerProviderDaemonPriority({
getPriority: () => 0,
setPriority: () => {
throw new Error("host diagnostic contains private data");
},
}),
).toBe("unavailable");
});
});
24 changes: 24 additions & 0 deletions apps/server/src/providerDaemon/ProviderDaemonPriority.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as NodeOs from "node:os";

export const PROVIDER_DAEMON_PRIORITY = NodeOs.constants.priority.PRIORITY_BELOW_NORMAL;

export interface ProviderDaemonPriorityOperations {
readonly getPriority: () => number;
readonly setPriority: (pid: number, priority: number) => void;
}

/** Apply before provider runtime construction so ordinary descendants inherit it. */
export function lowerProviderDaemonPriority(
operations: ProviderDaemonPriorityOperations = NodeOs,
): "lowered" | "already-lower" | "unavailable" {
try {
// Larger nice values mean lower scheduling priority. Preserve a process
// that its launcher has already assigned an even lower priority.
if (operations.getPriority() >= PROVIDER_DAEMON_PRIORITY) return "already-lower";
operations.setPriority(0, PROVIDER_DAEMON_PRIORITY);
return "lowered";
} catch {
// Scheduling policy is best effort and cannot become a provider outage.
return "unavailable";
}
}
25 changes: 25 additions & 0 deletions docs/provider-daemon-priority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Provider daemon scheduling

The dedicated provider daemon lowers its CPU scheduling priority before it starts
provider runtimes. Normally launched children inherit that priority. This reduces
CPU contention with interactive work. It does not cap memory, GPU use, network
use, or agent count.

The change applies to a new `provider-daemon` process with a valid bootstrap.
Electron, the backend, direct backend-hosted providers, the optional supervisor,
and existing external runtimes retain their current scheduling policy.
An OS failure produces a fixed warning and lets startup continue.
An already lower priority is preserved.

Windows documents below-normal inheritance in
[Scheduling Priorities](https://learn.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities).
Child programs can still change their own scheduling policy.

Run the explicit local smoke from the repository root:

```sh
node scripts/provider-priority-smoke.ts
```

It checks two disposable Node processes and leaves the caller's priority alone.
The default unit suite uses injected operations and never changes process priority.
36 changes: 36 additions & 0 deletions scripts/provider-priority-smoke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { spawnSync } from "node:child_process";
import * as NodeOs from "node:os";

// Opt-in synthetic process check. Only the disposable child lowers its priority.
const moduleUrl = new URL(
"../apps/server/src/providerDaemon/ProviderDaemonPriority.ts",
import.meta.url,
).href;
const childSource = `
import { spawnSync } from 'node:child_process';
import * as os from 'node:os';
import { lowerProviderDaemonPriority } from ${JSON.stringify(moduleUrl)};
const status = lowerProviderDaemonPriority();
const child = spawnSync(process.execPath, ['-e', 'process.stdout.write(String(require("node:os").getPriority()))'], {
stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8', windowsHide: true, timeout: 5000,
});
if (status === 'unavailable' || child.status !== 0) process.exit(1);
process.stdout.write(JSON.stringify({status, parent: os.getPriority(), child: Number(child.stdout)}));
`;
const result = spawnSync(process.execPath, ["--input-type=module", "-e", childSource], {
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf8",
windowsHide: true,
timeout: 10_000,
});
if (result.status !== 0) throw new Error("Synthetic provider priority check failed.");
const evidence = JSON.parse(result.stdout) as { parent: number; child: number };
if (
!Number.isFinite(evidence.parent) ||
!Number.isFinite(evidence.child) ||
evidence.parent < NodeOs.constants.priority.PRIORITY_BELOW_NORMAL ||
evidence.child < evidence.parent
) {
throw new Error("Synthetic child did not inherit the lower scheduling priority.");
}
process.stdout.write(`${JSON.stringify(evidence)}\n`);
Loading