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
41 changes: 29 additions & 12 deletions docs/adr/chronological-system-transcript.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,45 @@ transport support from a models.dev metadata match or an account endpoint
override. Both Pi and models.dev metadata projections can carry that binding;
unverified routes and generic records retain the conservative fallback.

The Pi 1.0.0 dependency patch adds the missing mid-conversation system
The Pi 1.0.1 dependency patch adds the missing mid-conversation system
capability to its `deepseek-flash` catalog entry. Authorized official-endpoint
experiments confirmed both preserved cache reuse and effective updated
instructions. Keep this correction in the single Pi catalog, not a parallel
Desktop allowlist; remove the hunk when an upgraded Pi catalog carries it.
The model/API/endpoint binding check still excludes aliases and relays. Native
tool-addition and tool-change flags are not enabled by this correction.

## Fixed declarations for the verified Flash route

For the exact official `deepseek-flash` / `openai-completions` binding with
verified chronological system support, declare the complete current tool catalog
in deterministic name order from the first request. ToolSearch changes execution
activation only. This is a Desktop declaration policy, not an additional Pi
transport capability or an endpoint switch. Other bindings keep on-demand
schema publication; native tool-state flags alone do not prove cache stability.
## Stable declarations across provider transports

Choose the declaration strategy by the bound Pi transport capabilities, not a
model-name allowlist. Responses (OpenAI/Codex) with verified system support
and `additional_tools` or client tool search, Chat Completions with verified
system/tool additions, and Pi's transcript transport retain native chronological
additions. Other transports declare the complete current catalog in deterministic
name order from the first request. The pinned Pi 1.0.1 Anthropic adapter now
supports inline tool definitions: verified system and tool-change support retain
native `tool_addition` messages without growing the initial schema list. Claude
bindings with only system-message support still use fixed declarations. Do not
infer tool-change support from a model name or unverified relay.
This policy also covers compatible relays without enabling unsupported native
message roles or switching their configured API.

Desktop currently exposes Chat Completions, Responses, Codex Responses,
Anthropic Messages, Gemini and Pi Messages bindings. This change does not add
new selectable Azure, Vertex, Bedrock or Mistral native bindings; models offered
through existing compatible endpoints follow that endpoint's adapter.

Keep activation separate from declarations in a versioned `tool_activation`
system section. The section records active deferred names and a SHA-256 identity
of the account, model, API, endpoint, declarations and deferred-name set. Updates
append after tool results and persist through the existing Host journal and
compaction checkpoint. Restoration never interprets the complete declaration
compaction checkpoint. Before provider conversion, omit this Desktop-only
activation section and any resulting empty metadata-only message. Preserve all
other instruction sections, content and tool deltas. Providers that fold system
messages must not rewrite their leading instructions just because execution
activation changed. ToolSearch results tell the model which tools were activated;
uncertain models may search again. Canonical persisted history is not mutated.
Restoration never interprets the complete declaration
snapshot as permission to execute every tool. Successful ToolSearch results
newer than the saved activation section recover an interrupted activation.
Invalid, unknown-version or mismatched state grants no activation. Legacy
Expand All @@ -79,8 +96,8 @@ removed tools cannot be invoked. Temporary prompt replacement must preserve the
activation metadata. Current runtime activation remains authoritative between
prompts; declarations do not re-grant revoked activation.

DeepSeek limits a request to 128 functions. If the full catalog exceeds that
limit, or its estimated prompt/schema cost leaves less than the ordinary
Use 128 functions as a conservative shared fixed-catalog ceiling, including
DeepSeek Chat Completions' limit. If the full catalog exceeds that ceiling, or its estimated prompt/schema cost leaves less than the ordinary
retained-tail budget below the automatic compaction threshold, use the existing
on-demand path and log the fallback reason. Never truncate a catalog. Context
estimation charges the full declared catalog while fixed declarations are active.
Expand Down
31 changes: 21 additions & 10 deletions docs/spec/03-runtime/02-agent-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -1412,10 +1412,9 @@ grammar and validated against another fails every call.

### 7.1 Active tool context and on-demand loading (D185, ADR 0048)

The sidecar builds one complete tool registry. By default, each provider request
declares the mode's core set plus activated deferred tools. The verified Flash
binding uses the fixed-declaration policy below, while preserving the same
execution activation rules:
The sidecar builds one complete tool registry. Native anchored-addition routes
declare core tools and add activated deferred tools in place. Other routes use
the fixed-declaration policy below. Both preserve these execution activation rules:

- Agent: `Read`, `Bash`, `Edit`, and `Write` (matching pi's coding-agent core)
- Agent: `Skill` whenever the skill catalog is non-empty (D404, ADR 0230) — the
Expand Down Expand Up @@ -1454,9 +1453,15 @@ results from deferred tools also restore their names. Failed results,
missing-result placeholders and assistant/user prose never activate tools.
Only names in the current mode's deferred catalog are eligible.

For the exact official `deepseek-flash` Chat Completions binding with verified
mid-conversation system support, the runtime instead declares the complete
catalog in deterministic name order on the first request. ToolSearch changes
Select by the bound transport, not a model name. Responses with verified system
support plus `supportsAdditionalTools` or `supportsToolSearch`, Chat Completions
with verified system/tool additions, and Pi Messages retain native additions.
The Pi 1.0.1 Anthropic adapter with verified system and tool-change support also
retains native additions, carrying full definitions in later `tool_addition`
blocks. System-message support alone does not enable this path. Other bindings,
including Claude without native tool changes, Gemini, ordinary Chat Completions,
older Responses/Codex models and compatible relays, declare the complete catalog
in deterministic name order on the first request. ToolSearch changes
activation without changing the declared schemas. A visible schema does not
permit execution: inactive deferred calls are rejected before extension hooks
and the Host; activated calls still require the existing mode and Host checks.
Expand All @@ -1466,7 +1471,12 @@ Fixed declarations persist separately from activation. A version-1
`tool_activation` section records active names and a fingerprint of the account,
model, API, endpoint, schema catalog and deferred set. Activation changes append
at the continuation boundary, and the existing system journal/checkpoint saves
both declarations and activation. Restore only validated activation for a
both declarations and activation. Strip the activation section only from the
provider projection, dropping metadata-only empty messages but preserving all
other sections/content/tool deltas. This prevents folding APIs from moving an
activation change into the leading prompt; persisted state remains complete.
Model guidance uses successful ToolSearch results, not private activation JSON.
Restore only validated activation for a
matching fingerprint, plus successful ToolSearch results newer than that state;
never activate tools merely because the full snapshot declared them. Malformed,
unknown-version and mismatched activation state fail closed. A catalog/schema,
Expand All @@ -1476,8 +1486,9 @@ activation. Removal immediately removes the tool from executable registration.
If the full catalog exceeds 128 functions or its prompt/schema estimate cannot
leave the normal retained-tail budget below the compaction threshold, retain
on-demand declarations and emit a diagnostic explaining that ToolSearch cache
stability is not guaranteed. Do not truncate tools. Other models and unverified
routes retain the existing Pi projection. First-request schema overhead increases;
stability is not guaranteed. The 128-function ceiling is conservative across
fixed-catalog APIs. Do not truncate tools or opt unknown endpoints into native
capabilities. First-request schema overhead increases;
cache stability does not imply that short conversations become cheaper.

For user-visible HTML deliverables, the default system prompt asks the agent to
Expand Down
21 changes: 20 additions & 1 deletion docs/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -16487,7 +16487,7 @@ renderer's durable transcript reads. No real model or provider is contacted.
regressions also cover legacy identities and genuine account/model changes. Cache
percentages are observations, not deterministic pass thresholds.

### E2E-FIXED-TOOL-DECLARATIONS: Stable Flash schemas with independent activation
### E2E-FIXED-TOOL-DECLARATIONS: Stable schemas across transports with independent activation

- Fixture: production AgentSidecar and isolated Host, official Pi Flash binding,
and a child-process fetch boundary redirected to local HTTP/SSE. Credentials
Expand Down Expand Up @@ -16520,6 +16520,25 @@ renderer's durable transcript reads. No real model or provider is contacted.
through successful recovery. Terminal failure and Stop retain their existing
closure behavior. Covered by the parameterized runtime overflow user-path test.

- Cross-provider acceptance: `fixed-tool-providers.test.ts` enters the real runtime
prompt/ToolSearch/execution path and captures each Desktop-selectable Pi
adapter's actual serialized payload at `onPayload`, before network dispatch.
Cover Chat Completions, Anthropic (no native updates, system-only, inline tools), Responses and Codex
(fallback, additional tools, client tool search), Gemini and Pi Messages.
Search A, execute A, search B, execute B, then finish: all five requests retain
their top-level schema state and prior semantic message prefix. Native routes
retain deferred schema additions. Activation JSON never reaches the provider.
Anthropic inline definitions must remain absent from the first request and
appear as `tool_definition` blocks after successful ToolSearch.
This proves request construction, not server cache hits or paid API acceptance.
- Run `node scripts/e2e-fixed-tool-declarations.mjs` for the official Flash
binding and `PI_FIXED_TOOL_FIXTURE_ROUTE=compatible node scripts/e2e-fixed-tool-declarations.mjs`
for the compatible binding. The local HTTP/SSE fixture covers official Flash, unflagged Chat
Completions and a compatible relay. Canonical activation restoration, denial
before Host execution, mode/account/catalog invalidation and compaction remain
required. Fixed declarations increase first-request size; oversized catalogs
explicitly fall back without a cache-stability guarantee.


#### E2E-262: Transcript path:line opens and scrolls the host file viewer

Expand Down
12 changes: 10 additions & 2 deletions docs/zh-CN/spec/03-runtime/02-agent-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -983,7 +983,7 @@ sidecar 最多激活四个匹配项,并将名称写入 canonical

Deferred activation remains sticky within a live runtime. Restoration uses
successful activation evidence and the current catalog; old declarations do not
re-grant tools revoked from the live activation set. For official bound Flash,
re-grant tools revoked from the live activation set. For routes without verified native anchored tool additions,
full declarations and execution activation are independent: versioned
`tool_activation` sections carry the account/model/API/endpoint/catalog identity
and active names through restart and compaction. Only matching, valid state and
Expand All @@ -992,7 +992,15 @@ epochs fail closed. Inactive declared tools are blocked before extension/Host
execution, and activation never bypasses mode or approval checks. The full
catalog is deterministic from the first request. More than 128 tools or an
insufficient context budget falls back to on-demand declarations with a
diagnostic, without truncation. Other bindings retain their existing projection.
diagnostic, without truncation. Responses and Chat Completions bindings with
verified anchored additions, and Pi Messages, retain native incremental
publication. The pinned Pi 1.0.1 Anthropic adapter also retains native additions
with verified system and tool-change support: later messages carry full tool
definitions. System-message support alone is insufficient; other Claude bindings
use fixed declarations. Strip only the
private activation section from provider projection, preserving canonical state
and all other instructions. This also stabilizes ToolSearch on folding APIs and
compatible relays without enabling new transport capabilities.
Fixed declarations may increase total cost for short conversations. See the
English section 7.1 and the chronological-system-transcript ADR for the complete
contract.
Expand Down
21 changes: 20 additions & 1 deletion docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -9290,7 +9290,7 @@ the latest destination. These assertions measure work counts, not device FPS.
- **里程碑:** M6+
- **状态:** 单测和源码契约覆盖(`update-cache.test.mjs`、`auto-update.test.mjs`);仍需 Windows 安装器/E2E 验证。

### E2E-FIXED-TOOL-DECLARATIONS: Stable Flash schemas with independent activation
### E2E-FIXED-TOOL-DECLARATIONS: Stable schemas across transports with independent activation

- Fixture: production AgentSidecar and isolated Host, official Pi Flash binding,
and a child-process fetch boundary redirected to local HTTP/SSE. Credentials
Expand Down Expand Up @@ -9323,6 +9323,25 @@ the latest destination. These assertions measure work counts, not device FPS.
through successful recovery. Terminal failure and Stop retain their existing
closure behavior. Covered by the parameterized runtime overflow user-path test.

- Cross-provider acceptance: `fixed-tool-providers.test.ts` enters the real runtime
prompt/ToolSearch/execution path and captures each Desktop-selectable Pi
adapter's actual serialized payload at `onPayload`, before network dispatch.
Cover Chat Completions, Anthropic (no native updates, system-only, inline tools), Responses and Codex
(fallback, additional tools, client tool search), Gemini and Pi Messages.
Search A, execute A, search B, execute B, then finish: all five requests retain
their top-level schema state and prior semantic message prefix. Native routes
retain deferred schema additions. Activation JSON never reaches the provider.
Anthropic inline definitions must remain absent from the first request and
appear as `tool_definition` blocks after successful ToolSearch.
This proves request construction, not server cache hits or paid API acceptance.
- Run `node scripts/e2e-fixed-tool-declarations.mjs` for the official Flash
binding and `PI_FIXED_TOOL_FIXTURE_ROUTE=compatible node scripts/e2e-fixed-tool-declarations.mjs`
for the compatible binding. The local HTTP/SSE fixture covers official Flash, unflagged Chat
Completions and a compatible relay. Canonical activation restoration, denial
before Host execution, mode/account/catalog invalidation and compaction remain
required. Fixed declarations increase first-request size; oversized catalogs
explicitly fall back without a cache-stability guarantee.

#### E2E-262:聊天 path:line 引用打开文件并滚动到目标行

- **前提:** 隔离 Electron/Chromium、活动工作区和会话、可用的随应用打包文件管理器视图,以及确定性的文件系统 IPC fixture。
Expand Down
3 changes: 2 additions & 1 deletion packages/agent-runtime/src/extensions/managed-exec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ it("cancels an owned process tree after an explicit readiness signal", async ()
const root = mkdtempSync(join(tmpdir(), "pi-owned-exec-"));
const ready = join(root, "ready.json");
const owner = new AbortController();
const childSource = `require('node:fs').writeFileSync(${JSON.stringify(ready)}, JSON.stringify([process.ppid,process.pid])); setInterval(()=>{},1000);`;
// Publish readiness only after the complete PID list is visible to the reader.
const childSource = `const fs=require('node:fs'); fs.writeFileSync(${JSON.stringify(`${ready}.tmp`)}, JSON.stringify([process.ppid,process.pid])); fs.renameSync(${JSON.stringify(`${ready}.tmp`)}, ${JSON.stringify(ready)}); setInterval(()=>{},1000);`;
const parentSource = `require('node:child_process').spawn(process.execPath,['-e',${JSON.stringify(childSource)}],{stdio:'inherit'}); setInterval(()=>{},1000);`;
const pending = managedExec(process.execPath, ["-e", parentSource], root, owner.signal);
try {
Expand Down
25 changes: 22 additions & 3 deletions packages/agent-runtime/src/fixed-tool-declarations.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { describe, expect, it } from "vitest";
import type { AgentTool } from "@earendil-works/pi-agent-core";
import type { AgentMessage, AgentTool } from "@earendil-works/pi-agent-core";
import { DEEPSEEK_MODELS } from "@earendil-works/pi-ai/providers/deepseek.models";
import { Type, type Api, type Model } from "@earendil-works/pi-ai";
import { toolDeclarationPolicy, toolActivationSection, restoredToolActivation, TOOL_ACTIVATION_SECTION } from "./fixed-tool-declarations.js";
import { replaceSystemPrompt, systemTranscriptCheckpoint } from "./system-transcript.js";
import { convertToLlm } from "./pi-runtime-messages.js";

const model = Object.values(DEEPSEEK_MODELS).find((model) => model.id === "deepseek-flash")!;
const tool = (name: string): AgentTool => ({ name, label: name, description: "Fixture tool", parameters: Type.Object({}),
Expand All @@ -13,6 +14,24 @@ const policy = (tools = [tool("Alpha"), tool("Beta")], overrides: Partial<Model<
toolDeclarationPolicy({ ...model, ...overrides } as Model<Api>, tools, deferred, prompt, "fixture-account");

describe("fixed tool declaration policy", () => {
it("persists activation without projecting it into model instructions", () => {
const current = policy();
const messages: AgentMessage[] = [
{ role: "system" as const, content: "", timestamp: 1, toolsAdded: current.tools,
sections: { runtime: "Rules", [TOOL_ACTIVATION_SECTION]: toolActivationSection(current.key, new Set()) } },
{ role: "system" as const, content: "", timestamp: 2,
sections: { [TOOL_ACTIVATION_SECTION]: toolActivationSection(current.key, new Set(["Alpha"])) } },
{ role: "system" as const, content: "Changed instruction", timestamp: 3,
sections: { obsolete: null, [TOOL_ACTIVATION_SECTION]: null }, toolsRemoved: [{ name: "Beta" }] },
];
const before = JSON.stringify(messages);
const projected = convertToLlm(messages);
expect(projected).toHaveLength(2);
expect(projected[0]).toMatchObject({ sections: { runtime: "Rules" }, toolsAdded: current.tools });
expect(projected[1]).toMatchObject({ content: "Changed instruction", sections: { obsolete: null }, toolsRemoved: [{ name: "Beta" }] });
expect(JSON.stringify(projected)).not.toContain(TOOL_ACTIVATION_SECTION);
expect(JSON.stringify(messages)).toEqual(before);
});
it("has a deterministic declaration order and snapshot identity", () => {
const first = policy();
const second = policy([tool("Beta"), tool("Alpha")]);
Expand All @@ -23,8 +42,8 @@ describe("fixed tool declaration policy", () => {
it.each([
{ id: "deepseek-pro" }, { api: "openai-responses" }, { baseUrl: "https://relay.invalid" },
{ baseUrl: "https://api.deepseek.com/v1" }, { compat: { supportsMidConvoSystemMessages: false } },
] as Partial<Model<Api>>[])("does not enable unverified bindings: %j", (overrides) => {
expect(policy(undefined, overrides).tools).toBeUndefined();
] as Partial<Model<Api>>[])("stabilizes declarations without assuming transcript capabilities: %j", (overrides) => {
expect(policy(undefined, overrides).tools?.map((tool) => tool.name)).toEqual(["Alpha", "Beta"]);
expect(policy(undefined, overrides).fallback).toBeUndefined();
});
it("falls back without truncating catalogs beyond the provider's function limit", () => {
Expand Down
Loading
Loading