feat(storage): move desktop SQLite ownership into a storage utility process
Main no longer opens desktop business SQLite. A single utility-process
storage owner holds the Assistant, Knowledge, attachment, document-result,
remote-binding and native model-ledger databases and runs whole domain
transactions; Main and Runtime callers use explicit async domain operations.
- Remove the Main readWithFallback path and the supervision worker's
separate business write connection; manifests are scanned read-only and
committed by the sole writer.
- Remote events are ACKed only after commit; process loss reports
unconfirmed writes instead of replaying them.
- Use native utilityProcess messaging with one in-flight bound (32) and
ordered waiting instead of capacity rejection and retries.
- Start the storage process before the window so startup overlaps.
- Keep short tool output in memory and store full output once in owned
storage; paging, branching, restart and deletion reuse that copy.
- Put required runtime launch files under goodbuddy-prefixed owned temp
directories and reclaim confirmed stale ones; stop writing duplicate
parsed.md.
- Add lint guards for SQLite ownership and architecture, concurrency
acceptance fixtures, and maintenance/architecture documentation.
Validation: typecheck and lint pass. Alternating baseline (edf019aa) vs
candidate runs show no regression on single-session, C01, C02, C04 and a
5-minute C06; Main p99 drops from ~21 ms to ~16.7 ms under concurrency.
Fresh portable copy (1.75M events) upgrade content unchanged; real-model
output paging passes with 4 external requests. Full npm test runs had
1-9 different timing-sensitive failures per run that pass when rerun.
Not covered: C03 review publication, C07 source body, app-level C05.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Assistant Storage Consumers
|
||||
|
||||
The S02 domain consumers use the typed port in
|
||||
`src/main/assistant-storage-port.ts`. This implements the consumer side of
|
||||
[Desktop Storage Foundation](./desktop-storage-foundation.md). Production
|
||||
composition in `src/main/index.ts` supplies the client after storage readiness;
|
||||
the consumer services do not construct an Assistant database.
|
||||
|
||||
## Main Integration
|
||||
|
||||
```ts
|
||||
const storage = new DesktopStorageClient({
|
||||
...paths,
|
||||
onChanged: createAssistantStorageOnChanged({
|
||||
onMagicNotesChanged,
|
||||
onMagicTodosChanged,
|
||||
onModelUsageChanged,
|
||||
onExecutionStatsChanged
|
||||
})
|
||||
})
|
||||
await storage.ready
|
||||
const assistant = createAssistantStoragePort(storage)
|
||||
```
|
||||
|
||||
`createAssistantStoragePort` binds only the explicit Assistant operation allowlist
|
||||
and `supervisionContext`. Its return type is `AsyncAssistantStoragePort`: every
|
||||
operation returns a Promise. There is no SQL, arbitrary property proxy, remote
|
||||
callback or live repository API. `AssistantStoragePort` uses `Awaitable` results
|
||||
so existing synchronous database fixtures can be injected into unit tests.
|
||||
Domain change callbacks run in Main through `DesktopStorageClient.onChanged`.
|
||||
|
||||
The subagent service, native client coordinator, terminal manager, computer-control
|
||||
audit sink, channel dedup/outbox, image service and Magic Notes IPC helpers accept
|
||||
this port. Notes helpers retain sender validation, schema validation, source-message
|
||||
checks and revision checks. Note analysis awaits the model-usage callback before
|
||||
publishing its result. Channel and audit adapters return persistence completion to
|
||||
their existing asynchronous callers.
|
||||
|
||||
Image service `initialize`, `persistUploads`, `getOperation`, `cancel`,
|
||||
`cancelConversation`, `regenerate` and `dispose` return Promises. Await upload
|
||||
persistence before submitting generation. Await cancellation and shutdown writes.
|
||||
`onUsage` may return a Promise and must return the actual persistence operation.
|
||||
Notification-only `onOperation` errors are reported without changing provider
|
||||
outcomes; storage and usage-write failures reject instead of reporting durable
|
||||
success. Shutdown reports retained persistence failures.
|
||||
|
||||
Subagent event and model-usage callbacks may return Promises. The service awaits
|
||||
task creation, events and terminal status writes; shutdown also drains task
|
||||
creation and queued cancellation writes that have not entered a scheduler slot.
|
||||
Native client composition additionally forwards the storage-backed
|
||||
`openModelCallLedger` factory to both browser and terminal clients.
|
||||
|
||||
Stop producers and await service disposal before closing `DesktopStorageClient`.
|
||||
This consumer migration does not establish full desktop performance acceptance or
|
||||
replace the separate Main/IPC composition and concurrent acceptance work.
|
||||
|
||||
## Validation
|
||||
|
||||
The 11 focused suites pass with 94 tests, including asynchronous image storage,
|
||||
commit-before-notification, usage-write rejection, subagent shutdown during task
|
||||
creation, channel delivery, audit persistence and IPC sender/schema checks.
|
||||
Repository-wide type checking remains dependent on the concurrent Main/IPC,
|
||||
Knowledge and runtime-storage integrations. No latency or full concurrent-desktop
|
||||
acceptance claim is made by these unit and loopback tests.
|
||||
@@ -1,75 +1,388 @@
|
||||
# Desktop Storage Direction
|
||||
# Desktop Storage And Concurrent Execution
|
||||
|
||||
## Status and scope
|
||||
## Decision and delivery
|
||||
|
||||
2026-10-05: proposed target for the remaining PERF-15/PERF-16 work. The user has
|
||||
requested documentation first, followed by bounded local fixes and a commit.
|
||||
The full storage-service migration remains a separate discussion after those fixes.
|
||||
Current desktop storage still has Main-owned business writes and selected worker
|
||||
reads; this document does not describe a completed migration.
|
||||
Updated 2026-10-05 against `2be57492`. The user selected one complete architecture
|
||||
delivery for PERF-15/PERF-16 and the necessary performance guards. This document
|
||||
supersedes the earlier proposal to leave full migration for a later discussion.
|
||||
The integrated migration and acceptance are not complete. The implemented storage
|
||||
foundation, production composition and remaining acceptance work are recorded in
|
||||
[Storage Foundation Integration](./desktop-storage-foundation.md).
|
||||
Local fixes in `b90ff50f` remain the measured starting point.
|
||||
|
||||
## Target responsibilities
|
||||
Deliver the integrated design in one candidate, with normal product workflows and
|
||||
concurrent acceptance passing together. Internal implementation dependencies do
|
||||
not create separate partial product deliveries. A worker-only prototype, an async
|
||||
facade over Main SQL, or a dual-write transition does not meet completion criteria.
|
||||
|
||||
- Renderer owns presentation, domain stores and disposable visible-data caches.
|
||||
It consumes summaries, pages and incremental notifications, not full database copies.
|
||||
- Electron Main owns windows, trusted IPC, credentials, OS integration and service
|
||||
lifecycle. Heavy SQL, JSON assembly and document processing execute elsewhere.
|
||||
- A desktop storage utility process would own business database connections and
|
||||
complete domain transactions. Retain the existing database files, schemas and
|
||||
durability distinctions. One write owner per database does not require merging databases.
|
||||
- Existing read-only workers can serve heavy queries under the storage service's
|
||||
lifecycle. Do not place every read and write behind one long-running global job.
|
||||
- Parsing, OCR, model inference and Runtime execution remain separate workloads.
|
||||
Remote Agent databases remain remote; desktop persistence acknowledges remote
|
||||
events only after the required local commit succeeds.
|
||||
This document owns architecture and scope. [Performance principles](./performance-principles.md)
|
||||
own normative budgets and concurrency rules; [concurrent acceptance](../quality/concurrent-desktop-acceptance.md)
|
||||
owns reproducible workloads and evidence. The [roadmap](../roadmap/product-performance-experience-improvement-plan.md)
|
||||
tracks status, and the [local repair record](../review/kiss-local-fixes-2026-10-05.md)
|
||||
preserves earlier measurements. Migration completion requires new acceptance evidence.
|
||||
|
||||
The service boundary consists of domain operations such as publishing a review or
|
||||
committing an event batch. Do not expose arbitrary SQL, transport transaction
|
||||
callbacks, or split an atomic transaction across individual IPC calls. Synchronous
|
||||
SQLite inside its dedicated execution environment can remain appropriate.
|
||||
## Requirements
|
||||
|
||||
## Correctness and lifecycle
|
||||
| ID | Required outcome |
|
||||
| --- | --- |
|
||||
| SA-01 | Desktop Main does not open or query business SQLite, directly or through a helper. |
|
||||
| SA-02 | Complete domain transactions execute under one write owner per database. |
|
||||
| SA-03 | Multiple projects, conversations and subtasks progress without unrelated global waits. |
|
||||
| SA-04 | Requests, event buffers, result payloads and caches have bounded lifetimes and capacity. |
|
||||
| SA-05 | Persistence, ordering, cancellation, recovery and user interaction semantics are preserved. |
|
||||
| SA-06 | Startup, process failure and shutdown have one tested ownership path. |
|
||||
| SA-07 | Static guards and concurrent production-path tests enforce the architecture. |
|
||||
| SA-08 | GoodBuddy eliminates unnecessary intermediate disk writes and duplicate full outputs; necessary temporary resources have explicit ownership and cleanup. |
|
||||
|
||||
Preserve write-before-read ordering, result/checkpoint atomicity, cancellation
|
||||
settlement, event deduplication and commit-before-ACK. A rejected caller Promise
|
||||
does not establish that a writer stopped. Reuse existing operation IDs and unique
|
||||
constraints to resolve uncertain outcomes; do not blindly replay mutations or add
|
||||
a general recovery journal.
|
||||
## Responsibilities
|
||||
|
||||
Open business connections after required upgrades. Stop producers before draining
|
||||
required writes and closing storage. A failed storage process must not silently
|
||||
fall back to Main SQL or an empty persistent-data replacement. Keep bounded requests
|
||||
and explicit failure/retry behavior without adding product approval gates.
|
||||
```mermaid
|
||||
flowchart TB
|
||||
UI[Renderer: domain stores and visible views] <--> MAIN[Main: trusted IPC and lifecycle]
|
||||
MAIN <--> STORAGE[Storage utilityProcess: domain operations and write owners]
|
||||
STORAGE <--> READ[Existing bounded read workers]
|
||||
MAIN <--> RUNTIME[Runtime hosts and compute workers]
|
||||
MAIN <--> AGENT[Remote Agent]
|
||||
```
|
||||
|
||||
## VS Code comparison
|
||||
- Renderer retains presentation, local interaction state and disposable visible-data
|
||||
caches. Domain selectors subscribe narrowly. Hidden views retain required state,
|
||||
not continuous full rendering or whole-history refreshes.
|
||||
- Main retains windows, trusted sender/schema checks, OS APIs, credential decryption,
|
||||
network/runtime orchestration and process lifecycle. SQL, large result assembly,
|
||||
hashing and bulk attachment file work do not execute on its event loop.
|
||||
- One desktop storage utility process owns the existing business database files and
|
||||
exposes typed asynchronous domain operations. Keep synchronous SQLite inside this
|
||||
dedicated environment where it simplifies transaction correctness.
|
||||
- Reuse bounded read-only workers for expensive reads. They open only after upgrade,
|
||||
read committed WAL snapshots, and do not run initialization or recovery. Storage
|
||||
owns their lifecycle; a project or conversation does not create a new worker.
|
||||
- Parsing, OCR, embedding and Runtime execution retain their existing hosts. Network
|
||||
requests and model execution must not run inside storage transactions or hold the
|
||||
storage write queue. This work does not relocate browser-dependent OCR engines.
|
||||
- Remote Agent databases remain remote. Changes to desktop storage must preserve
|
||||
remote replay, controller ownership and commit-before-ACK.
|
||||
|
||||
VS Code is a reference for service separation, asynchronous work, lazy activation,
|
||||
cached UI state and shutdown coordination, not a requirement to copy its process map.
|
||||
Its application/profile/workspace state connections are owned by Electron Main;
|
||||
the backend uses asynchronous `@vscode/sqlite3`, while cached reads are synchronous
|
||||
and writes are batched. GoodBuddy's relational history, source bodies and durable
|
||||
remote-event acknowledgements require different payload and persistence boundaries.
|
||||
One write owner means exclusive business-write authority, not necessarily one OS
|
||||
thread for all reads. Startup upgrades and existing maintenance/checkpoint workers
|
||||
must be explicitly owned and excluded from competing business writes. Keep their
|
||||
current bounded mechanisms where valid; do not create independent writers per domain.
|
||||
|
||||
Sources reviewed on 2026-10-05 (upstream main, not a pinned performance comparison):
|
||||
## Ownership inventory
|
||||
|
||||
Inventory records current owners; destinations below are targets, not implementation facts.
|
||||
|
||||
| Surface | Current entry | Required destination |
|
||||
| --- | --- | --- |
|
||||
| `assistant.sqlite` | `src/main/assistant/assistant-database.ts`, constructed in Main | Storage service: conversations, tasks/events, activity, notes metadata, channels, supervision and timing persistence |
|
||||
| `knowledge.sqlite` | `src/main/knowledge/knowledge-service.ts` and `knowledge-database.ts` | Storage service: schema, writes and external metadata; existing workers for costly reads |
|
||||
| `conversation-attachments.sqlite` and referenced files | `src/main/conversation-attachment-storage.ts` | Storage service: references, adoption, reconciliation and bounded file IO |
|
||||
| Document-result persistent lookup and note bodies | `document-result-storage.ts`, `magic-notes` storage | Async domain access to associated files; preserve original directories and reference semantics |
|
||||
| Local remote-runtime binding DB | `src/main/remote-agent/managed-remote-execution-services.ts` | Storage service: transactional binding transitions and uniqueness |
|
||||
| Native terminal / DS Web model-call ledgers | `native-terminal-client.ts`, `native-dsh-web-client.ts` instantiate `AgentModelCallLedger` | Storage service manages desktop ledger files under the existing client lifecycle; Main retains model/credential routing |
|
||||
| Read, checkpoint and upgrade connections | `readonly-query-worker.ts`, `assistant-storage-worker.ts` | Service-owned steady-state reads/checkpoints; exclusive upgrade execution before readiness |
|
||||
| Supervision manifest writer | `readonly-query-worker.ts` supervision mode and `SupervisionReviewStore` | Reuse scanning and manifest logic, but route its business mutations through the same write owner as publication; remove independent writer authority |
|
||||
| Remote Agent journal, owners, prompts and ledger | `src/agent-daemon/daemon.ts`, `runtime-composition.ts` | Existing remote ownership, unchanged by desktop connection migration |
|
||||
| Encrypted JSON settings and transient runtime caches | Existing settings/runtime services | Keep existing appropriate owners; async settings do not move merely to match a diagram |
|
||||
|
||||
An import scan must also find `openPrivateSqliteDatabase` and transitive constructors:
|
||||
an implementation under `agent-daemon` can still open SQLite when called in desktop
|
||||
Main. Do not weaken shared Agent code or relocate remote databases as a side effect.
|
||||
|
||||
Preserve existing database names, table formats, IDs, paths, scopes and durability
|
||||
settings. Process relocation alone does not require a schema bump. Necessary schema
|
||||
changes follow the [migration guide](../development/database-migrations.md).
|
||||
|
||||
## Existing work and consolidation
|
||||
|
||||
This inventory was checked against production callers on 2026-10-05. Reuse means
|
||||
preserving working algorithms and contracts, not claiming their current transport,
|
||||
cancellation or close semantics already meet the target. Implement the replacement
|
||||
at the existing seam and remove superseded callers in the same delivery.
|
||||
|
||||
| Existing mechanism | Reuse or extend | Retire or avoid |
|
||||
| --- | --- | --- |
|
||||
| [ReadonlyQueryReader](../../src/main/readonly-query-reader.ts), [worker handlers](../../src/main/readonly-query-worker.ts) | Reuse request correlation, named operations, errors, lazy lifecycle and reader handlers; adapt the outer utility transport and strengthen typed operations, readiness, byte bounds and settlement-aware close | No second equivalent storage RPC client or admission queue; remove production infrastructure fallback through `readWithFallback`, not legitimate owner-local transaction reads |
|
||||
| [Startup storage](../../src/main/assistant-storage-startup.ts), [upgrade worker](../../src/main/assistant-storage-worker.ts) | Preserve migration algorithms, progress, retry and cancellation; move SQL preflight into the storage-owned exclusive startup path | Remove Main SQL preflight and competing upgrade launch ownership; do not add a new migration framework |
|
||||
| [Checkpoint and supervision connections](../../src/main/assistant/assistant-database.ts), [review store](../../src/main/assistant/supervision-review-store.ts) | Reuse PASSIVE checkpoint and manifest scan/revision algorithms; make checkpoint exit awaited and manifest writes owner-controlled | No second maintenance service or independent business writer hidden in a read-worker entry |
|
||||
| [EmbeddingIndexRepository](../../src/main/knowledge/embedding-index-coordinator.ts), [binding store interface](../../src/main/agent/runtime-session-binding-store.ts), [activity repository](../../src/main/assistant/activity-history-repository.ts) | Reuse domain interfaces and transaction algorithms; adapt the desktop backing implementation and async callers | Do not add remote object proxies or duplicate repositories; Promise-returning methods are not proof of off-Main execution |
|
||||
| [RemoteEventBatcher](../../src/main/remote-event-batcher.ts) | Extend existing batching with awaited persistence/flush and pending-plus-in-flight bounds; update recovery, background and interactive IPC consumers | No second durable event coalescer, receipt ledger or retry queue; no ACK or forwarding before commit |
|
||||
| [AgentModelCallLedger](../../src/agent-daemon/agent-model-gateway.ts), [terminal client](../../src/main/agent/native-terminal-client.ts), [DS Web client](../../src/main/agent/native-dsh-web-client.ts) | Preserve schema, claim/deduplication and uncertain outcomes; desktop access becomes awaited storage operations | Do not relocate remote Agent ledgers; remove desktop concrete synchronous opens and early directory deletion |
|
||||
| [Utility transport](../../src/main/knowledge/embedding-utility-transport.ts), [Harness readiness](../../src/main/agent/deepseek-harness-utility-launcher.ts), [shutdown helpers](../../src/main/shutdown.ts) | Reuse fork/entry/exit adapters and phased application shutdown; add storage readiness and drain at those seams | No new generic process supervisor, duplicate shutdown path or copied Harness streaming/version protocol |
|
||||
| [Runtime controller](../../src/main/agent/runtime-controller.ts), [selected runtimes](../../src/main/agent/selected-runtime-manager.ts), [remote backend](../../src/agent-daemon/runtime-acp-backend.ts) | Keep request ownership, session reuse and retirement; inspect remaining global open/prepare/close waits with healthy peers | No second Runtime pool or global execution manager; the prior route-cleanup fix does not prove every global wait is gone |
|
||||
| [Subagent scheduler](../../src/main/assistant/subagent-scheduler.ts), [supervision model pool](../../src/main/assistant/supervision-model-pool.ts), [review admission](../../src/main/assistant/supervisor-service.ts) | Retain task slots, review single-flight and per-model-call pool as distinct existing scopes; extend capacity/settlement tests where needed | Do not merge ordinary chat into the supervision pool or create another task state machine/provider scheduler |
|
||||
|
||||
The current ordinary reader removes a cancelled request from `pending` before worker
|
||||
settlement, and `close()` provides no exit barrier. Supervision waits longer and can
|
||||
substitute cancellation for a successful result. Extend these semantics to
|
||||
represent actual execution and committed outcomes; do not copy them unchanged into
|
||||
the storage client and call its capacity or shutdown complete.
|
||||
|
||||
Supervision's temporary scan table belongs to one SQLite connection. Retain the
|
||||
bounded scan, transfer only bounded manifest data to its write owner, and preserve
|
||||
initialization/resume invariants. Existing 200-row commits run in synchronous loops;
|
||||
they do not themselves yield an execution turn. Yield at safe existing batch boundaries
|
||||
without dividing final atomic publication or introducing a general scheduler.
|
||||
|
||||
Renderer consolidation stays within existing domains:
|
||||
|
||||
- [Conversation store](../../src/renderer/src/conversation-store.ts) and
|
||||
[persistence](../../src/renderer/src/conversation-persistence.ts) already own ordered
|
||||
deltas, save serialization, history requests and quit flushing. Extend those paths,
|
||||
not a second `conversation-sync` queue. Its current React-aware integration is not
|
||||
a reason to introduce another store library.
|
||||
- [Activity sync](../../src/renderer/src/activity-sync.ts) owns paging, pending writes
|
||||
and retry; [task sync](../../src/renderer/src/task-sync.ts) owns task/artifact refresh.
|
||||
Tighten projections and retention there. A resolved flush that caught an error and
|
||||
requeued data is not a durable success barrier; extend its explicit completion
|
||||
semantics where dependent reads or shutdown require persistence.
|
||||
- [Live messages](../../src/renderer/src/live-message-store.ts),
|
||||
[public event buffering](../../src/main/agent-event-buffer.ts), and
|
||||
[terminal batching](../../src/renderer/src/terminal-output-batcher.ts) serve distinct
|
||||
display stages. Keep them separate from durable remote batching and avoid an
|
||||
additional universal event bus or a second coalescing delay.
|
||||
- Retain domain cache owners: [keep-alive](../../src/renderer/src/keep-alive-cache.ts),
|
||||
[Markdown](../../src/renderer/src/markdown-render-cache.ts),
|
||||
[channel status](../../src/renderer/src/channel-status-store.ts), and
|
||||
[Supervisor scope reads](../../src/renderer/src/SupervisionStories.tsx).
|
||||
Preserve pinned-pane exceptions and scope/revision guards; strengthen bounds at
|
||||
their existing owners.
|
||||
|
||||
Runtime session behavior remains authoritative in the
|
||||
[reuse design](../features/assistant-workbar/runtime-process-reuse-technical-design.md).
|
||||
[Task timing](../features/task-and-job/technical-design.md) and
|
||||
[supervision model scheduling](../features/conversation-supervision/technical-design.md)
|
||||
retain their existing semantics. Ending an elapsed-time segment on abort does not
|
||||
release an execution slot before cleanup; do not unify these clocks or restore
|
||||
historical timing scans/statistics workers during this migration.
|
||||
|
||||
Completion includes a removal inventory tied to the table above: old Main opens,
|
||||
external live-store factories, production sync fallbacks, obsolete transport callers
|
||||
and independent business writers have no production references. Remove only the
|
||||
superseded implementations, not valid in-owner synchronous algorithms, remote stores,
|
||||
released-data compatibility or historical test evidence. No permanent dual path or
|
||||
unused compatibility wrapper is part of the delivery.
|
||||
|
||||
## Operation boundary
|
||||
|
||||
Reuse repository algorithms behind domain operations: save conversation changes,
|
||||
commit remote events, publish a review, adopt attachment references, or transition
|
||||
a runtime binding. Public renderer IPC can keep its current shape while Main awaits
|
||||
the storage client. No generic SQL endpoint or function serialization is introduced.
|
||||
|
||||
Existing transaction callbacks and returned live repository objects stay inside the
|
||||
owner. Replace their external use with typed inputs/results. In particular,
|
||||
`readSnapshot`, Knowledge publication callbacks and Supervisor store factories must
|
||||
not be transported or accept an asynchronous callback that outlives a sync transaction.
|
||||
|
||||
The channel extends the request/handler and utility-adapter seams identified above,
|
||||
correlates requests/results and carries named operations,
|
||||
serializable arguments, cancellation and readiness/failure. Reuse installed Electron
|
||||
transport patterns; do not add a general RPC dependency, persisted transport sequence,
|
||||
protocol negotiation or a service discovery layer for this bundled child process.
|
||||
Validate actual transport behavior for buffers, Maps and errors; use DTOs. Live classes,
|
||||
subscriptions and handles remain with their owners. Errors retain usable codes and causes
|
||||
without credentials or user content in diagnostic logs.
|
||||
|
||||
Queries return the requested projection. Lists use summaries/pages; source bodies,
|
||||
tool outputs and attachments load through detail paths. Large results must not be
|
||||
JSON-assembled in Main or copied through several caches. Existing source records are
|
||||
not truncated to satisfy a latency check. A read transaction ends before posting its
|
||||
reply so a slow consumer cannot keep a WAL snapshot open.
|
||||
|
||||
## Concurrency and ordering
|
||||
|
||||
Keep ordering at the smallest required scope. A conversation's persisted sequence
|
||||
and a database's commits remain ordered; independent model waits, sessions and
|
||||
read requests do not share a global long-running lock. Runtime process reuse retains
|
||||
per-binding/session/operation identity; cancelling one request must not stop its peers.
|
||||
|
||||
Use existing finite concurrency and batch mechanisms before adding scheduling code.
|
||||
Apply capacity to queued bytes as well as operation count where payload size varies.
|
||||
When a queue fills, wait or report the existing actionable busy/error state; never
|
||||
silently discard user messages, tool results or acknowledged events. Obsolete read
|
||||
refreshes and display progress can coalesce; persisted evidence cannot. Preserve one
|
||||
bounded batch of work before yielding so other admitted producers can progress;
|
||||
change scheduling only where concurrent measurements demonstrate starvation.
|
||||
|
||||
Heavy reads use the read workers and leave necessary writes able to progress. UI/control
|
||||
requests must not wait behind model networking, import parsing or route cleanup.
|
||||
Control processing must not depend on acquiring another execution slot. This is a
|
||||
resource-boundary rule, not a mandate for a new global priority scheduler.
|
||||
|
||||
For long publications, compute immutable data before the write transaction when
|
||||
possible, then validate current database preconditions inside the transaction and
|
||||
commit the complete result/checkpoint atomically. Do not await models, filesystem
|
||||
callbacks or IPC while holding a write transaction. An atomic publication cannot be
|
||||
split into externally visible partial commits to improve timing. Worker relocation
|
||||
does not eliminate the single-writer bottleneck: acceptance measures queue wait and
|
||||
commit time, as well as Main responsiveness.
|
||||
|
||||
## Persistence and cancellation
|
||||
|
||||
| Operation | Required completion meaning |
|
||||
| --- | --- |
|
||||
| Cached UI preference/read | Synchronous memory access is allowed; cache ownership and invalidation are explicit |
|
||||
| Existing coalesced local saves | Preserve current flush semantics and expose the existing flush barrier to dependent reads/exit |
|
||||
| Remote event batch | Persist event/checkpoint under existing durability, then notify/ACK; enqueueing is not commit |
|
||||
| Channel outbox / runtime ledger | Preserve duplicate-dispatch prevention and uncertain-outcome handling; no blind retry after lost replies |
|
||||
| Review publication | Facts, result status and checkpoints commit together or roll back together |
|
||||
|
||||
Abort removes queued work before dispatch. In-flight work reaches a defined safe
|
||||
point and reports its actual outcome; capacity is released only after settlement or
|
||||
confirmed host exit. Caller cancellation, operation termination and resource release
|
||||
are distinct observations, not three new persistent product states.
|
||||
|
||||
`worker_threads` SharedArrayBuffer cancellation must not be assumed to cross
|
||||
`utilityProcess` IPC. Reuse it only within verified thread boundaries. A message
|
||||
cannot interrupt synchronous SQL while the receiver is blocked. Keep SQL/transaction
|
||||
units bounded, check cancellation between safe units, and report a completed commit
|
||||
as committed even if cancellation arrived too late. Do not terminate a shared storage
|
||||
process to cancel one request or promise instantaneous rollback of a committed write.
|
||||
|
||||
Filesystem adoption and SQL references are not automatically one atomic transaction.
|
||||
Preserve existing staging, adoption and reconciliation order with async file APIs.
|
||||
Do not delete originals or release protected in-flight files early; do not introduce
|
||||
a cross-database transaction engine or recovery journal.
|
||||
|
||||
## Lifecycle and failure
|
||||
|
||||
Main starts and supervises one storage process using existing utility-process
|
||||
patterns. Process spawn is not readiness: required migrations, connection opening
|
||||
and startup recovery must finish before storage-dependent operations are accepted.
|
||||
Show the existing startup/upgrade progress and retry UI. Render the application shell
|
||||
when safe without marking unavailable data as an empty successful result.
|
||||
|
||||
After process loss, reject affected pending requests with bounded diagnostics, stop
|
||||
admitting writes, confirm exit and close transport resources before replacement.
|
||||
The existing user retry/reopen lifecycle can re-establish storage readiness. Read-only
|
||||
work can be repeated when requested. For writes without a response, consult existing
|
||||
task/event/result identities and deduplication before claiming failure or retrying;
|
||||
model dispatch marked uncertain must not be sent again automatically. No Main SQL
|
||||
fallback, silent empty database, second durable receipt ledger or unbounded restart loop.
|
||||
|
||||
Shutdown stops producers and new admission, cancels unnecessary reads, drains required
|
||||
writes, closes readers/checkpoints/connections, and observes storage process exit.
|
||||
Extend the existing phased shutdown and deadline, including explicit storage failure
|
||||
reporting; existing settled-error helpers alone do not prove a successful drain. Native client ledger
|
||||
directories are removed only after their connection is closed. Upgrade/maintenance
|
||||
requires exclusive ownership before changing files, following the existing maintenance
|
||||
path; it cannot open an extra Main business connection.
|
||||
|
||||
## Output and temporary resource ownership
|
||||
|
||||
This work is included in the same architecture delivery. The [source investigation](./runtime-storage.md#output-and-temporary-file-investigation)
|
||||
records current producers and persistence gaps. Scope is GoodBuddy-created material;
|
||||
third-party MCP/Runtime internals and user workspaces are not cleanup targets.
|
||||
|
||||
Decide content ownership before choosing a temporary directory. Ordinary requests,
|
||||
responses, concatenation and short outputs stay in bounded memory. Where an existing
|
||||
record or artifact already contains complete content, pass its reference and read pages
|
||||
from that owner. Do not write a temporary copy merely to cross IPC or call MCP.
|
||||
|
||||
Current tool events often contain only previews. Preserve full paging access by adapting
|
||||
the existing `PagedOutputStore` interface to the storage/attachment owner: output that
|
||||
must outlive bounded in-memory capture has one backing content object, with events
|
||||
carrying previews and its reference. Write directly to that backing object; do not spool
|
||||
to Temp and later copy the same bytes to another full-output store. Reuse existing file
|
||||
adoption/reference/release operations and paged reads, without a second output database,
|
||||
generic blob service or durable transport journal. Final answers, previews and full
|
||||
stdout/stderr remain distinct data.
|
||||
|
||||
The storage object's retention follows its consumer. Call-only output is released when
|
||||
its existing owner no longer needs paging; content referenced by persisted history is
|
||||
released through that history's existing reference cleanup. Being stored by the storage
|
||||
service does not make every intermediate output permanent. Keep small call-only output
|
||||
in memory and preserve existing model limits. Avoid adding whole-output copies to events
|
||||
or retaining all concurrent streams in an unbounded buffer. Size alone does not justify
|
||||
an additional temporary copy.
|
||||
|
||||
The replacement must specify the complete-content reference and byte cursor through
|
||||
process, search, subagent and `output_read` paths before removing the old file backend.
|
||||
Inspect remote mapping separately: it does not use the local process spool and cannot
|
||||
recover omitted bytes from already-pruned Agent events. Preserve supported remote
|
||||
content/ACK semantics; this work does not promise new full-history capture from third-party
|
||||
runtimes. Remove the old spool creation and synchronous Main paging only once the shared
|
||||
replacement preserves the existing full-output contract.
|
||||
|
||||
### Necessary temporary material
|
||||
|
||||
External CLI configuration, plugin modules and atomic replacement/install staging may
|
||||
still require files. Reuse environment/in-memory configuration when already supported;
|
||||
otherwise keep the actual external contract. Atomic sibling files and installer staging
|
||||
stay on their required filesystem under their existing owner, not a forced central root.
|
||||
|
||||
For disposable runtime launch material, pass a root from application initialization
|
||||
through existing constructors; create it lazily:
|
||||
|
||||
```text
|
||||
<userData>/temp/goodbuddy-runtime/<appVersion>/<runId>/
|
||||
goodbuddy-runtime-launch/
|
||||
```
|
||||
|
||||
Add purpose directories only for evidenced necessary files. Do not precreate process,
|
||||
search or subagent output trees. User-visible disposable roots retain `goodbuddy-`
|
||||
prefixes. Tests use `<repository>/temp/goodbuddy-<task>-<runId>/`; remote resources use
|
||||
GoodBuddy-owned prefixed roots on the Host. Do not modify global `TEMP` or relocate
|
||||
third-party histories/caches through an indiscriminate environment override.
|
||||
|
||||
The creator removes partial creations on failure. Existing service shutdown closes
|
||||
children/handles and then releases its resources. Startup, including the next upgrade,
|
||||
reclaims obsolete owned run directories after the existing single-instance check and
|
||||
verification that no owning child remains active. Version/run IDs identify candidates;
|
||||
they do not establish inactivity. Reuse process ownership checks, with only the minimal
|
||||
owner information needed to identify surviving children; do not create a lease service.
|
||||
Cleanup runs in bounded background work and cannot delay the first screen with a full
|
||||
filesystem scan. Failed deletion leaves the known directory available to retry.
|
||||
|
||||
Legacy OS-Temp prefixes require an explicit list of layouts, creator and inactivity
|
||||
evidence. Reclaim confirmed obsolete instances, preserve ambiguous/live directories,
|
||||
and report the remaining scope. Never delete arbitrary `goodbuddy-*`, `.tmp` or `.partial`
|
||||
paths by age. Existing installer/reference reconciliation retains its own roots and
|
||||
rules; no global garbage collector, cleanup ledger or user-data migration is introduced.
|
||||
|
||||
## KISS constraints
|
||||
|
||||
- Keep Electron, TypeScript, SQLite and existing provider/runtime integrations.
|
||||
- Add the one storage utility process, not processes per project/session/subtask.
|
||||
- Reuse reader, checkpoint, parser, batching and lifecycle mechanisms where appropriate.
|
||||
- Keep credentials/OS integration in Main; storage receives only required business data.
|
||||
- Do not add general schedulers, distributed transactions, durable RPC journals,
|
||||
compatibility readers for unshipped intermediates, new approval gates or user modes.
|
||||
- Synchronous cache reads and pure calculations need not become RPC calls.
|
||||
- The runtime host, database driver and UI framework are not replaced as incidental work.
|
||||
|
||||
## VS Code reference
|
||||
|
||||
VS Code demonstrates service separation, asynchronous execution, lazy activation,
|
||||
cached state and coordinated close. Its application/profile/workspace storage is
|
||||
owned by Electron Main, uses async `@vscode/sqlite3`, and batches cached key/value
|
||||
writes. GoodBuddy retains `DatabaseSync` in a dedicated execution environment because
|
||||
its relational history, graph queries and durable event acknowledgements differ.
|
||||
Do not copy full-state memory loading or empty-store recovery into business history.
|
||||
|
||||
Sources reviewed on 2026-10-05, upstream main; no same-machine benchmark is claimed:
|
||||
|
||||
- [Connection ownership](https://github.com/microsoft/vscode/blob/main/src/vs/platform/storage/electron-main/storageMain.ts)
|
||||
- [SQLite backend](https://github.com/microsoft/vscode/blob/main/src/vs/base/parts/storage/node/storage.ts)
|
||||
- [Cache and flush lifecycle](https://github.com/microsoft/vscode/blob/main/src/vs/base/parts/storage/common/storage.ts)
|
||||
- [Extension hosts](https://code.visualstudio.com/api/advanced-topics/extension-host)
|
||||
|
||||
## Current implementation boundary
|
||||
## One-delivery completion
|
||||
|
||||
The immediate work reuses existing workers, narrows Supervisor loading and payloads,
|
||||
optimizes measured queries, virtualizes lists, and removes repeated digest/snapshot
|
||||
work. It does not add the utility process, convert every storage interface, or claim
|
||||
exclusive ownership of assistant.sqlite. Read-only workers plus remaining Main writes
|
||||
are an explicit transition state.
|
||||
SA-01 through SA-08 must pass together under the [acceptance matrix](../quality/concurrent-desktop-acceptance.md).
|
||||
All listed desktop owners, normal/background/error/cancel/exit callers, and native
|
||||
client ledger paths must use the final boundary. Remove the superseded Main openings,
|
||||
direct storage access and synchronous fallbacks, including helper-based bypasses.
|
||||
No feature-disable workaround or loss of user interaction counts as completion.
|
||||
|
||||
Full migration should proceed through verified domain operations and converge by
|
||||
database ownership. Do not leave undocumented competing write paths. Completion
|
||||
requires no Main-owned desktop business SQL, transactions wholly inside the owner,
|
||||
responsive reads/writes under heavy work, and validated startup/cancellation/shutdown.
|
||||
Run production UI-to-storage tests, existing full validators, old-data integrity and
|
||||
concurrent before/after measurements; validate affected desktop-to-Agent behavior on
|
||||
the shared Linux x64 Host. Native platform CI verifies process startup, SQLite and
|
||||
packaged entry/resource paths. Follow the release runbook for production builds and
|
||||
packaged-app checks, including measurement runs.
|
||||
|
||||
The [local implementation record](../review/kiss-local-fixes-2026-10-05.md) owns this
|
||||
round's scope and measurements. The [performance principles](./performance-principles.md)
|
||||
remain the acceptance criteria; regression ceilings are not equivalent to those budgets.
|
||||
The acceptance record must report latency, throughput, backlog and resource cleanup,
|
||||
including failures. A responsive UI with stalled background commits is not success.
|
||||
No target implementation, new performance baseline or migration completion is claimed
|
||||
by this documentation update.
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
# Desktop File Storage Integration (S04)
|
||||
|
||||
Implemented domain operations, async consumers and production composition on
|
||||
2026-10-05. The storage owner is registered by the foundation and the current Main
|
||||
composition passes its file facades to ContextManager, document parsing and model
|
||||
output. This does not claim that the full application has passed concurrent or
|
||||
packaged acceptance.
|
||||
The governing constraints are [performance principles](./performance-principles.md)
|
||||
and [storage ownership](./desktop-storage-direction.md).
|
||||
|
||||
## Foundation API
|
||||
|
||||
`src/main/desktop-storage-files.ts` exports the complete integration surface:
|
||||
|
||||
- `DesktopFileStorageDomains`: intersect into `DesktopStorageDomains`. The domains
|
||||
are `attachments` and `documentResults`.
|
||||
- `attachmentStorageMethods` and `documentResultStorageMethods`: explicit allowed
|
||||
operations. `attachments.reconcile` is an additional owner composite, with
|
||||
arguments `[startup?, protectedIds?]`; its callback never crosses IPC.
|
||||
- `openDesktopStorageFiles(userDataRoot, hasOwner, persistentResultLookup?)`:
|
||||
construct once inside the existing utility process, after assistant and knowledge
|
||||
databases open. Pass the existing userData root, not the assistant DB directory.
|
||||
Add that root to foundation options and forward it in the client's open message.
|
||||
- `DesktopStorageFilesOwner.dispatch(domain, method, args, signal)`: dispatch the
|
||||
two domains here from the existing owner's dispatcher. It normalizes transported
|
||||
original bytes, checks operation allowlists, and supplies cancellation to result
|
||||
saves. Keep the existing transport admission and result limits.
|
||||
- Before readiness, run `files.attachments.reconcile(hasOwner, true)`. The callback
|
||||
uses the already-open assistant owner. It checks released references, marks
|
||||
interrupted parsing, removes stale call-only output refs, and reclaims owned
|
||||
orphan UUID directories. Do not run it concurrently with file producers.
|
||||
- After draining admitted requests, `await files.close()` before closing assistant
|
||||
and knowledge. Close drops call-only output refs, preserves history refs, removes
|
||||
temporary document results, and closes the existing attachment database.
|
||||
|
||||
No new database, RPC transport, SQL endpoint, or process is introduced. Existing
|
||||
attachment reference transactions and schema 1 remain in place. New relative paths
|
||||
use slash separators; reads and reconciliation also accept released Windows paths.
|
||||
|
||||
## Main And Knowledge
|
||||
|
||||
After `storage.ready`, production composition uses `createDesktopStorageFiles(storage)` to obtain
|
||||
`{ attachments, results, backingStore }`. Pass attachments to `ContextManager` and
|
||||
results to `DocumentParsingService` and Knowledge's result-access dependency.
|
||||
Do not also construct `ConversationAttachmentStorage` or `DocumentResultStorage`
|
||||
in Main. Facades only call the existing storage client; importing them does not open
|
||||
SQLite. Their result `close()` is intentionally inert: shared result shutdown belongs
|
||||
to `DesktopStorageFilesOwner.close()`, after all producers drain.
|
||||
|
||||
Context operations that now require await: `storePastedImage`, `enrichRequest`,
|
||||
`remove`, `serializeForQueue`, `restoreFromQueue`, `getDraft`, `hasImageInputs`,
|
||||
`validateForSend`, `saveDraft`, `sendOriginal`, and `cancelUnavailableImport`.
|
||||
Previously async import/reparse/image operations now await every storage operation.
|
||||
Conversation/target validators may return Promises. Memory-only `clear` and
|
||||
`activeContextIds` remain synchronous. `ContextManager.dispose()` cancels and drains
|
||||
the active import before collecting unreferenced assets. Stop other context producers
|
||||
and await their operations before disposing. Parsing-service disposal drains all
|
||||
diagnostics, including those without an operation ID.
|
||||
|
||||
IPC must await attachment/result calls, including `results.original(id)` before
|
||||
`shell.openPath`, and array predicates/maps after `get`, `draft`, `pendingParsing`,
|
||||
and `getDraft`. Replace callback-based Main reconciliation with
|
||||
`await attachments.reconcile(false, contextManager.activeContextIds())`.
|
||||
|
||||
Knowledge must accept `DocumentResultStorageAccess`, await `detach`, `original`,
|
||||
and `isTemporaryResult`, and remove Main `setPersistentLookup` registration. Supply
|
||||
that lookup to `openDesktopStorageFiles` instead: use owner-local
|
||||
`knowledge.getDocumentByParsedResultId(id)` and the existing
|
||||
`knowledge-assets/<libraryId>/<documentId>/<resultId>` directory and source location.
|
||||
Keep Knowledge's publish/move/detach/release order. Result release never deletes an
|
||||
external source original. No user-data directory migration is needed.
|
||||
|
||||
Project deletion supplies the project's conversation IDs captured before assistant
|
||||
deletion to `attachments.deleteProject(conversationIds, protectedIds?)`. It removes
|
||||
their refs in one attachment transaction, then collects only unreferenced assets.
|
||||
Shared assets remain available to other conversations. Call this after the assistant
|
||||
deletion commits; startup reconciliation covers an interrupted cleanup. Use
|
||||
`deleteConversation` for single conversations and reconciliation for full clearing.
|
||||
|
||||
## Full Output
|
||||
|
||||
Pass the returned `backingStore` to each existing `PagedOutputStore`. It implements
|
||||
`create/append/finish/read/release` directly in the existing attachment owner.
|
||||
An output handle's UUID identifies its attachment row throughout capture, promotion,
|
||||
history retention, restart, and deletion. Capture writes `original.txt` directly in
|
||||
the owned attachment directory. Promotion renames that directory; no temporary full
|
||||
copy or second backing is created. `request.json` contains only the descriptor.
|
||||
Appends are bounded to 64 KiB; backing reads allow 32 KiB plus UTF-8 lookahead.
|
||||
Small outputs remain in the existing bounded in-memory capture.
|
||||
|
||||
For history retention, await
|
||||
`attachments.outputAdopt(callOwnerId, handle, conversationId, kind, referenceId)`
|
||||
before the runtime releases its call reference. It returns the resource ID and adds
|
||||
the reference without replacing the owner's existing attachments. Persist the
|
||||
complete-output handle/reference in the owning event/message path; a preview alone
|
||||
is not full output. Retain the call until required history adoption/persistence has
|
||||
settled. Failed history publication uses existing release/reconciliation, not a
|
||||
second durable operation log. Incomplete captures cannot be adopted.
|
||||
|
||||
`release` drops only the matching `output-call` ref. Persisted history can page after
|
||||
call disposal or restart with
|
||||
`attachments.outputRead(conversationId, handle, cursor, length)`, reading the same
|
||||
bytes; it must use the owning conversation ID. History deletion uses the existing
|
||||
message/queue/conversation reference cleanup. Runtime cancellation must await writer
|
||||
abort/release. Startup removes stale call refs while retaining valid history refs.
|
||||
The remote Agent path does not use this desktop backing and is unchanged.
|
||||
|
||||
## Validation Boundary
|
||||
|
||||
The focused suites exercise real SQLite/files through owner operations and async
|
||||
facades, using structured-cloned arguments/results to cover Buffer-to-Uint8Array
|
||||
conversion. They cover complete UTF-8 paging, one backing file, adoption, release,
|
||||
abort, restart, project deletion with shared history, existing path separators,
|
||||
temporary-result cleanup, failed adoption retry, async restore, and shutdown drain.
|
||||
New result writes and copies omit `parsed.md`; old manifests and directories remain
|
||||
readable. Result image writes settle sequentially before failure cleanup.
|
||||
|
||||
Local Windows validation: six focused suites passed (83 tests), covering this owner,
|
||||
attachment references, ContextManager, parsing service, paged output, and HTTP OCR
|
||||
integration. Scoped ESLint and diff whitespace checks passed. Node type checking
|
||||
reported no diagnostics in S04 source/tests; the complete checkout still has pending
|
||||
integration errors in Main/IPC and concurrent domain migrations.
|
||||
|
||||
These tests do not replace the foundation's utility-process tests or UI acceptance.
|
||||
The real utility-process coverage for these domains is present in the focused
|
||||
storage fixtures. Concurrent desktop acceptance and packaged validation remain
|
||||
outstanding. No latency or throughput improvement is claimed from these focused
|
||||
tests. Test resources live under `temp/goodbuddy-files-upgrade` and individual
|
||||
fixtures remove their files.
|
||||
@@ -0,0 +1,308 @@
|
||||
# Desktop Storage Foundation Integration
|
||||
|
||||
Implemented 2026-10-05. This is the storage foundation for
|
||||
[the desktop storage direction](./desktop-storage-direction.md). The production Main
|
||||
composition now opens one `DesktopStorageClient` and supplies its typed facades to
|
||||
the migrated domains. Full application acceptance is still incomplete. Do not open
|
||||
the existing Main databases alongside the owner.
|
||||
|
||||
## Client API
|
||||
|
||||
Import `DesktopStorageClient` from `src/main/desktop-storage-client.ts` in Main.
|
||||
Construct it after Electron readiness and the application's single-instance check.
|
||||
The existing database directories must exist. Preserve the current database paths.
|
||||
|
||||
```ts
|
||||
const storage = new DesktopStorageClient({
|
||||
assistantPath,
|
||||
knowledgePath,
|
||||
defaultRootPath,
|
||||
userDataPath,
|
||||
onProgress: progress => publishExistingUpgradeProgress(progress),
|
||||
onChanged: domain => publishExistingDomainNotification(domain),
|
||||
onFailure: error => reportStorageFailure(error)
|
||||
})
|
||||
await storage.ready
|
||||
|
||||
await storage.call('assistant', 'saveLocalConversations', [changes])
|
||||
const conversation = await storage.call(
|
||||
'assistant', 'getConversation', [conversationId], { signal }
|
||||
)
|
||||
const results = await storage.call('knowledge', 'search', [searchOptions], { signal })
|
||||
|
||||
// After stopping producers and awaiting their persistence flushes:
|
||||
await storage.close()
|
||||
```
|
||||
|
||||
`call(domain, method, argumentTuple, { signal? })` infers argument and result types
|
||||
from the explicit allowlists in `desktop-storage-contracts.ts`. All results are
|
||||
Promises. Pass cancellation as the transport option, not inside a DTO. Functions,
|
||||
signals and live class instances are rejected before posting. Maps and Uint8Arrays
|
||||
are supported; Node Buffers arrive as Uint8Arrays across Electron IPC.
|
||||
|
||||
`userDataPath` is the existing application user-data root, not a database directory
|
||||
inferred from a filename. `ready` means upgrade worker exit, database initialization,
|
||||
attachment startup reconciliation and runtime-binding owner initialization completed.
|
||||
Calls before readiness fail without queueing. `retry()` retains the same public
|
||||
client and facades. A live failed startup retries in that process; a failed/exited
|
||||
utility is replaced only after exit confirmation and old-request settlement. The
|
||||
replacement gets fresh request correlation, never the old writes. The confirmed
|
||||
upgrade decision is acknowledged into Main memory before upgrade execution, retained
|
||||
across startup loss, and cleared when that upgrade completes. Wire `retry()` to the
|
||||
existing retry UI. There is no automatic restart or replay of unconfirmed writes.
|
||||
|
||||
The defaults locate `desktop-storage-entry.js`, `readonly-query-worker.js` and
|
||||
`assistant-storage-worker.js` as packaged siblings. `entryPath`, `readerWorkerPath`
|
||||
and `upgradeWorkerPath` overrides support isolated tests and explicit composition.
|
||||
The new entry is included in `electron.vite.config.ts`.
|
||||
|
||||
## Owner-Side Operations
|
||||
|
||||
| Existing call shape | Storage operation |
|
||||
| --- | --- |
|
||||
| Assistant/Knowledge ordinary database methods | `call('assistant' / 'knowledge', method, args)` using the explicit allowlists |
|
||||
| SSH project create/update | `assistant.createSshProject` / `assistant.updateSshProject` accept `Omit<SshProjectWrite, 'assertCurrent'>`; Main checks the live host/runtime before dispatch, and the owner retains cancellation and project-revision checks inside the existing transaction |
|
||||
| `activityHistory()` live repository | Existing `getActivityHistoryPage`, `getActivityHistorySummary`, `updateActivityHistory`, `replaceActivityHistory`, `clearActivityHistory`, `reconcileActivityHistory` operations |
|
||||
| `supervisionReviewStore()` | `call('review', method, args)`; initialization/resume execute on the write owner, not the old independent writer |
|
||||
| `supervisionStories()` | `call('stories', method, args)` |
|
||||
| `supervisionExperiences()` | `call('experiences', method, args)` |
|
||||
| `supervisionSuggestions()` | `call('suggestions', method, args)`; resolution uses `assistant.resolveSupervisionSuggestion` |
|
||||
| Knowledge `externalStore` | `call('external', method, args)` |
|
||||
| `readSnapshot(callback)` for review context | `call('assistant', 'supervisionContext', [request])`; summary/background share one reader snapshot |
|
||||
| Knowledge `publishDocument(..., { afterChunksInserted })` | `call('knowledge', 'publishDocument', [input, chunks, { graph, embeddingReplacement, embeddingError }])` |
|
||||
| Knowledge graph-only replacement | `call('knowledge', 'replaceDocumentGraph', [library, document, graph])`; reuses the existing evidence replacement transaction |
|
||||
| Attachment references and full-output backing | `call('attachments', method, args)`; includes owner-local `reconcile(startup?, protectedIds?)` |
|
||||
| Temporary and persisted document results | `call('documentResults', method, args)` |
|
||||
| Runtime bindings and native model-call ledgers | `call('runtime', method, args)` |
|
||||
|
||||
Repository instances never leave the owner. The publication graph DTO is the
|
||||
existing `GraphExtractionResult`. The owner creates the graph callback and performs
|
||||
entity identity resolution, evidence insertion, chunk/embedding publication and
|
||||
pruning within `KnowledgeDatabase.publishDocument`'s existing transaction. Replace
|
||||
the consumer's publication callback with this DTO; remove its duplicate graph
|
||||
publication path when migrating that consumer. Extraction/model work remains outside
|
||||
storage. Publication and replacement share `storeExtractedGraph` from
|
||||
`desktop-storage-knowledge-operations.ts`. Callback-bearing `replaceEvidenceForDocument`
|
||||
is excluded from the transport allowlist. File adoption/release and task-status
|
||||
orchestration remain outside this SQLite transaction.
|
||||
|
||||
After readiness, `createDesktopStorageFiles(storage)` supplies attachments, results
|
||||
and output backing; `createDesktopRuntimeStorageAdapters((domain, method, args) =>
|
||||
storage.call(domain, method, args))` supplies runtime bindings and ledger access.
|
||||
The facade caller types now share the core DTO types, so these calls need no casts.
|
||||
`StorageData` preserves the existing recursive `JsonValue` as-is rather than
|
||||
recursively rebuilding it; runtime validation still rejects callbacks and handles.
|
||||
|
||||
The owner supplies both file callbacks. Attachment reconciliation checks
|
||||
`assistant.hasAttachmentOwner`. Persistent document lookup reads
|
||||
`knowledge.getDocumentByParsedResultId` and resolves the existing
|
||||
`<userDataPath>/knowledge-assets/<libraryId>/<documentId>/<resultId>` directory and
|
||||
source location. Main must not register another persistent lookup on the results
|
||||
facade. The existing `onProgress`, `onChanged` and `onFailure` APIs are unchanged,
|
||||
including `createAssistantStorageOnChanged`'s four notification mappings.
|
||||
|
||||
Keep these composites intact during integration:
|
||||
|
||||
- Remote task/conversation batches use the existing `appendRemote*Batch` or
|
||||
`appendRemote*Once` methods. Await commit before forwarding or ACK. Never translate
|
||||
a batch into independently acknowledged per-event writes.
|
||||
- Review publication uses `assistant.saveSupervisionResult`, which owns result facts
|
||||
and checkpoint commits. `commitCheckpoints` is deliberately not an external review
|
||||
operation. No callback-based transaction API is exposed.
|
||||
- Story/experience apply and review batch save retain their existing repository
|
||||
transactions. A model request must finish before calling these operations.
|
||||
- Additional multi-statement snapshots need a named owner/reader handler; do not
|
||||
recreate `readSnapshot` as several awaited Main calls.
|
||||
|
||||
## Bounds And Lifecycle
|
||||
|
||||
The utility adapter retains `ReadonlyQueryReader` request correlation, errors and
|
||||
admission. Its existing request limit is 32, with 32 MiB of argument admission
|
||||
credit. This is no longer a content-size restriction: one oversized complete input
|
||||
uses the entire credit budget exclusively. Smaller concurrent inputs still share
|
||||
the budget; saturation returns `STORAGE_CAPACITY` without dropping work.
|
||||
|
||||
`desktop-storage-wire.ts` transfers complete values on the same channel in frames
|
||||
of at most 256 KiB accounted data. Each direction sends one frame and waits for its
|
||||
acknowledgement before constructing the next. Strings are split without assembling
|
||||
a whole JSON/serialized copy; binary frames copy only their slice and fill the
|
||||
receiver's final buffer. The receiver exposes a value only after its last frame.
|
||||
These frame acknowledgements provide transport backpressure, not durable write ACKs.
|
||||
Cancellation bypasses the frame queue and is retained for an incompletely received
|
||||
request until dispatch can reject it safely.
|
||||
|
||||
Known complete-note/history/result reads share one materialization/transfer slot,
|
||||
so queued requests do not build many huge reply objects while another reply drains.
|
||||
Writes and bounded queries retain ordinary admission. A complete user value itself
|
||||
still needs memory proportional to its size; the frame budget is not a claim that
|
||||
total process memory or every supported history fits in 32 MiB. There is no result
|
||||
size rejection or truncation, and no `STORAGE_RESULT_CAPACITY` outcome after a commit.
|
||||
|
||||
Renderer conversation persistence now targets 8 MiB batches as well as the existing
|
||||
100-conversation bound. A single oversized conversation stays complete and uses the
|
||||
framed transport. Quit flushing drains all remaining batches and propagates a failed
|
||||
write. Oversized single histories no longer fail solely because of the transport cap.
|
||||
`STORAGE_UNCONFIRMED` means the process exited without confirming a pending operation;
|
||||
reconcile using existing domain identities, not blind retries.
|
||||
|
||||
Queued cancellation is checked before dispatch. In-flight reader cancellation uses
|
||||
the existing thread SharedArrayBuffer inside the owner; that buffer is never sent
|
||||
over utility-process IPC. Synchronous commits cannot be interrupted by an incoming
|
||||
message, so a completed commit returns success even when cancellation was requested
|
||||
too late. Request capacity remains occupied until settlement.
|
||||
|
||||
`close()` stops client admission, drains admitted requests, awaits file-owner cleanup,
|
||||
closes runtime ledgers/bindings and read workers, awaits the existing PASSIVE
|
||||
checkpoint worker's exit, closes both database owners
|
||||
and observes utility-process exit. An exit without the close acknowledgement rejects
|
||||
close. Apply the application's existing shutdown deadline to the whole operation;
|
||||
the transport separately reports an unconfirmed exit after its 10-second exit wait.
|
||||
Startup failure follows the same release barriers. A failed close attempts the
|
||||
remaining independent closes, reports `STORAGE_CLOSE_FAILED`, and exits unsuccessfully;
|
||||
it does not acknowledge a clean drain or permit a second owner to open in that host.
|
||||
|
||||
## Remaining Integration And Acceptance
|
||||
|
||||
### Supervision And Heartbeat Callers
|
||||
|
||||
`createSupervisionDomainPorts(storage)` in
|
||||
`src/main/assistant/supervision-domain-ports.ts` supplies Main-side RPC facades:
|
||||
|
||||
```ts
|
||||
const ports = createSupervisionDomainPorts(storage)
|
||||
const supervisor = createProductionSupervisorService(ports.supervision, getSettings, resolveRuntime, pool, persistUsage)
|
||||
const phrase = createProductionSuggestionPhraser(ports.supervision, getSettings, resolveRuntime, pool, persistUsage)
|
||||
const heartbeat = new HeartbeatService(ports.heartbeat, actions)
|
||||
await deriveSuggestions(ports.suggestions, phrase, input)
|
||||
```
|
||||
|
||||
The production factory's other arguments are unchanged. If provided, `persistUsage`
|
||||
must return the persistence Promise. All heartbeat methods, including configuration
|
||||
CRUD and history, now return Promises. Review repository factories may return a
|
||||
Promise of the typed facade. The facades contain closures only; no database or live
|
||||
repository is transported. `initializeSupervisionReview` and `resumeSupervisionReview`
|
||||
route to `review.initialize` / `review.resume`, with signals in transport options.
|
||||
The caller does not invoke the old independent supervision writer.
|
||||
|
||||
`story-assignment-service.ts` and `experience-extraction-service.ts` are the sole
|
||||
story/experience model orchestrators, including their prompts, output schemas and
|
||||
model-facing types. Production, tests and live scripts import those services.
|
||||
The repository files retain their SQL methods and transactions, with type-only
|
||||
imports for validated model output. `supervision-suggester.ts` awaits candidate
|
||||
selection and suggestion publication. The heartbeat live script also awaits plan
|
||||
creation before using its ID.
|
||||
|
||||
Callers await leaf/navigation writes, final publication, task usage/status,
|
||||
heartbeat projections, story/experience results and error bookkeeping. Cancellation
|
||||
aborts model work immediately, but review admission remains occupied until the
|
||||
cancel write and active execution settle. A late rejected cancel does not overturn
|
||||
a confirmed publication. Failure to persist a suggestion failure is propagated;
|
||||
a model phrasing failure whose status was saved preserves the completed review.
|
||||
|
||||
Validation includes unchanged behavioral assertions in the existing review/story/
|
||||
experience suites, delayed-commit tests, and `supervision-storage.test.ts`: a real
|
||||
Electron storage utility process runs heartbeat, two review leaves, navigation,
|
||||
stories, experiences, suggestion publication, usage, a no-change heartbeat and
|
||||
cancellation using a local deterministic Runtime. This does not validate final
|
||||
Main/IPC composition or concurrent latency budgets.
|
||||
|
||||
On 2026-10-05, after removing the duplicate orchestrators, the targeted supervision/
|
||||
heartbeat run passed 215 tests across 21 files, including the real utility-process
|
||||
test. Scoped ESLint and Node type checking passed.
|
||||
|
||||
The bounded `scripts/supervision-storage-live.cjs <runtime-settings.json>` probe
|
||||
used the current supervisor profile (`gpt-6-astra`, OpenAI Responses) and production
|
||||
utility storage with three synthetic calibration messages. Exactly four actual
|
||||
HTTP requests completed, all HTTP 200: review, story assignment, experience
|
||||
extraction and suggestion phrasing. It committed one leaf covering all three
|
||||
sources (589 characters), assigned three events to one story, created one experience
|
||||
and published two suggestions. The unchanged review made zero additional requests.
|
||||
Persisted usage was four calls, 3,495 input and 1,251 output tokens. Two initial
|
||||
preflight attempts sent zero requests; the probe was corrected to install the
|
||||
isolated Electron encryption state before readiness. Current settings remained
|
||||
byte-identical; credentials were not logged, and isolated databases, encryption
|
||||
state and bundles were removed. This is functional evidence, not concurrent
|
||||
performance acceptance.
|
||||
|
||||
### Integrated Owner
|
||||
|
||||
The foundation registers assistant, knowledge, external metadata, supervision,
|
||||
attachments, document results and runtime operations without Main SQL fallback. The
|
||||
production composition in `src/main/index.ts` constructs the Assistant, Knowledge,
|
||||
file and Runtime facades after storage readiness and passes them to the relevant
|
||||
Main services. Remote Agent database ownership is unchanged.
|
||||
|
||||
The remaining work is product-level acceptance: complete C01-C07/A01-A10 coverage,
|
||||
packaged cross-platform checks, and removal or verification of any caller that is
|
||||
outside the migrated desktop domains. Focused domain tests do not establish those
|
||||
results.
|
||||
|
||||
The S05 runtime-domain owner, Main adapters, required composition wiring and focused
|
||||
validation are documented in [Runtime Storage Integration](./desktop-storage-runtime-integration.md).
|
||||
|
||||
Supervision now uses `ReadonlyQueryReader.reviewManifest(runId)`. The existing
|
||||
read-only assistant worker scans once under a read snapshot into connection-local
|
||||
temporary metadata, ends the snapshot, and returns at most 200 metadata rows per
|
||||
page. It holds at most one live scan manifest, rejects a competing scan explicitly,
|
||||
and releases that temporary data in the operation's settlement path. It never
|
||||
opens a business-write connection.
|
||||
|
||||
The sole write owner clears/rebuilds disposable manifest rows in bounded batches,
|
||||
commits each insertion batch atomically, and yields between commits. Cancellation
|
||||
and persisted pause/cancel status are checked between batches. `initializing`
|
||||
remains set after interruption; clearing it and deleted-source cleanup share the
|
||||
final transaction. Resume rebuilds partial manifests, or validates existing frozen
|
||||
revisions in read-worker pages of at most 200 rows, yielding between pages. A failed
|
||||
scan never falls back to synchronous owner scanning. Final result/checkpoint
|
||||
publication remains the existing atomic operation.
|
||||
|
||||
The old writable `supervision` worker mode, connection opener and separate reader
|
||||
field were removed. `AssistantDatabase`'s direct initialization/resume methods use
|
||||
the same bounded implementation; in-memory owner-local use retains its synchronous
|
||||
algorithm because a separate read connection cannot share that database. Other
|
||||
non-worker operations still execute synchronously inside storage; no latency
|
||||
improvement or full concurrent performance acceptance is claimed.
|
||||
|
||||
The current focused validation passes 70 tests across eleven suites: core storage,
|
||||
file/runtime domains, assistant facade, read workers, bounded review-store algorithms,
|
||||
existing supervision worker regressions, startup, wire backpressure, renderer batching
|
||||
and real supervision/storage orchestration.
|
||||
Real Electron coverage includes graph replacement rollback, SSH revision conflicts,
|
||||
owner-side persistent document lookup after reopen, full-output retention and crash
|
||||
reconciliation, ledger recovery, runtime startup failure/retry and pending file-save
|
||||
drain before cleanup. Review tests cover 200-row bounds, concurrent owner writes,
|
||||
frozen scan revisions, cancellation, failed-batch rollback, reader loss, marker
|
||||
recovery, deleted sources and final marker/cleanup atomicity.
|
||||
|
||||
Real Electron large-value tests save an explicitly typed 16 MiB MP4 payload, read a
|
||||
complete two-entry note, and round-trip all 25,600,230 characters of an 80-message
|
||||
history. Hashes verify complete video and message bodies. The measured accounted
|
||||
frame high-water marks were 248,332 bytes in Main and 252,578 bytes in storage, below
|
||||
262,144 bytes; argument credit peaked at 33,554,432 bytes. Main RSS peaked at
|
||||
426,049,536 bytes including fixture inputs, complete results and application memory.
|
||||
This is queue-bound evidence, not a whole-process memory or latency budget claim.
|
||||
The real process tests also kill an in-progress write transfer and confirm no replay
|
||||
after retry, and verify startup decision retention/clearing across process loss.
|
||||
|
||||
Full Node, Agent and renderer typechecking passes, as does scoped ESLint. Temporary checks and databases use
|
||||
`temp/goodbuddy-storage-foundation`; tests remove their generated bundles/data.
|
||||
The earlier foundation production-build check predates this domain integration.
|
||||
Packaged cross-platform and full UI-to-storage concurrent acceptance remain outstanding.
|
||||
|
||||
### Reader Ownership Cleanup
|
||||
|
||||
The obsolete `readWithFallback` export, database-owned readonly-reader fields and
|
||||
checkpoint lifecycle methods, and the database async wrappers were removed. Heavy
|
||||
reads are dispatched by `DesktopStorageOwner`; owner-local synchronous reads remain
|
||||
available inside transactions and focused database tests. Read-worker and checkpoint
|
||||
tests now create explicit `ReadonlyQueryReader`/`Worker` owners and await their close.
|
||||
The three perf harnesses create explicit owner-local reader/checkpoint resources under
|
||||
the repository temporary root, report `executionBoundary: owner-local`, and close
|
||||
those resources before deleting their copies. No production or test source reference
|
||||
remains to the removed compatibility APIs.
|
||||
|
||||
The cleanup validation adds termination barriers and failed-frame poisoning: a reader
|
||||
cannot reopen capacity while its prior worker termination is pending, a transport
|
||||
posting failure cannot feed later values into a partial decoder, and close waits for
|
||||
wire drain and owner settlement. The focused cleanup run passes 122 tests; Node and
|
||||
renderer type checks and scoped lint pass.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Knowledge Main Storage Migration (S03)
|
||||
|
||||
Knowledge Main now receives `KnowledgeServiceOptions.database`, a
|
||||
`KnowledgeStoragePort`. Production composition constructs it with
|
||||
`createKnowledgeStoragePort(storageClient)` after `storageClient.ready`.
|
||||
`KnowledgeService` no longer opens, initializes, closes, or starts readers for
|
||||
the business database. The application storage owner retains those duties.
|
||||
|
||||
The adapter sends explicit `knowledge` and `external` domain calls. Search
|
||||
signals travel as transport options, not serialized arguments. The port's
|
||||
`Awaitable` return type permits synchronous owner-local test implementations;
|
||||
the production adapter always returns the storage client's promises.
|
||||
|
||||
Main awaits source, document, task, graph, embedding and external metadata
|
||||
operations. IPC mutations await their writes before returning or refreshing a
|
||||
snapshot. Parsing, models, credentials and source watchers remain in their
|
||||
existing Main services. Embedding batches retain their existing sizes and
|
||||
staging/publication sequence.
|
||||
|
||||
Publication passes the extracted graph through foundation
|
||||
`publishDocument(..., { graph })`. Graph-only replacement calls
|
||||
`replaceDocumentGraph(library, document, result)`. Its implementation in
|
||||
`src/main/desktop-storage-knowledge-operations.ts` runs the existing synchronous
|
||||
graph algorithm inside `replaceEvidenceForDocument`'s transaction. No callback
|
||||
or SQL crosses the storage boundary.
|
||||
|
||||
Shutdown aborts producers immediately, drains tracked work and observes the
|
||||
task-interruption write and embedding status persistence. The service does not
|
||||
close the shared storage client. Embedding allocation returns its replacement
|
||||
ID even if cancellation arrives with the reply, allowing cleanup; a committed
|
||||
finish is not reported as rolled back. Rebuild admission remains occupied
|
||||
until its final status write settles, and status-write failures reach the
|
||||
completion barrier.
|
||||
|
||||
## Current Production Boundary
|
||||
|
||||
The following boundary is implemented in the current production composition:
|
||||
|
||||
- `replaceDocumentGraph(library: KnowledgeBase, document: Document,
|
||||
result: GraphExtractionResult): void` to the knowledge domain contract and
|
||||
dispatch it to the exported owner-local operation, passing the owned database
|
||||
as its first argument. It is not part of the concrete database method
|
||||
allowlist because it is a composite owner operation.
|
||||
- Foundation publication reuses the exported `storeExtractedGraph` helper.
|
||||
- `StorageData` preserves the existing recursive JSON type without rebuilding it
|
||||
in the adapter.
|
||||
- Persistent document lookup is resolved by the storage owner through the
|
||||
Knowledge domain; Main does not register a second lookup.
|
||||
- Application composition injects the port, awaits asynchronous gateway and
|
||||
tool-provider calls, and closes shared storage after Knowledge disposal.
|
||||
|
||||
## Validation
|
||||
|
||||
The focused Knowledge service, external service, embedding coordinator and
|
||||
embedding repository suites use a Promise-backed adapter over a test-owned
|
||||
SQLite connection. Coverage includes publication rollback, embedding batch
|
||||
ordering, late cancellation, replacement cleanup, external request cancellation,
|
||||
credential persistence and restart. The production gateway now awaits its library
|
||||
and search operations. Remaining evidence is limited to the focused suites and
|
||||
does not establish full concurrent desktop acceptance.
|
||||
|
||||
Owned production files pass ESLint. The current worktree typecheck and focused
|
||||
storage validation cover the injected production port and owner dispatch. Global
|
||||
architecture acceptance, packaged checks and concurrent performance acceptance
|
||||
remain with the parent delivery; these tests do not establish a new performance
|
||||
baseline.
|
||||
@@ -0,0 +1,96 @@
|
||||
# Desktop Runtime Storage Integration
|
||||
|
||||
S05 domain implementation and production composition, 2026-10-05. This supplies
|
||||
the runtime domain for the [desktop storage foundation](./desktop-storage-foundation.md).
|
||||
The current composition passes the storage-owned binding store and model-call
|
||||
ledger factory to Main runtime services. Full desktop migration and performance
|
||||
acceptance remain incomplete.
|
||||
|
||||
## Integration API
|
||||
|
||||
`src/main/desktop-storage-runtime-operations.ts` exports:
|
||||
|
||||
- `DesktopStorageRuntimeOperations`: the existing `RuntimeSessionBindingStore`
|
||||
interface plus named ledger lifecycle and transition operations.
|
||||
- `runtimeStorageMethods`: the explicit host dispatch allowlist.
|
||||
- `DesktopStorageRuntimeOwner.open(userDataPath)`: creates the sole binding owner
|
||||
at `remote-runtime-bindings.sqlite`. Await it before publishing storage readiness.
|
||||
Native ledgers open lazily at their existing paths through `openLedger(id, path)`.
|
||||
- `createDesktopRuntimeStorageAdapters(call)`: Main adapters returning
|
||||
`bindingStore` and `openModelCallLedger`. `call` has the same domain/method/tuple
|
||||
shape as `DesktopStorageClient.call`, restricted to the `runtime` domain.
|
||||
- `DesktopStorageRuntimeOwner.close()`: closes remaining native ledgers and the
|
||||
binding database. The outer storage owner must first drain admitted operations.
|
||||
This method is not part of the external domain allowlist.
|
||||
|
||||
The storage-core integration adds `runtime: DesktopStorageRuntimeOperations` to
|
||||
`DesktopStorageDomains`, initializes one runtime owner from the existing user-data
|
||||
directory, and dispatches allowed runtime methods against that owner. Include its
|
||||
close in the storage owner's release path, including startup failure. No new RPC
|
||||
client, SQL endpoint, admission queue or process is needed.
|
||||
|
||||
After registering the domain, Main composition is:
|
||||
|
||||
```ts
|
||||
await storage.ready
|
||||
const runtimeStorage = createDesktopRuntimeStorageAdapters(
|
||||
(domain, method, args) => storage.call(domain, method, args)
|
||||
)
|
||||
|
||||
// ManagedRemoteExecutionServices options:
|
||||
// bindingStore: runtimeStorage.bindingStore
|
||||
// The former userDataPath option is removed; the host owns that path.
|
||||
|
||||
// NativeClientCoordinator, NativeTerminalClient and NativeDshWebClientService options:
|
||||
// openModelCallLedger: runtimeStorage.openModelCallLedger
|
||||
```
|
||||
|
||||
`ManagedRemoteExecutionServices` borrows the binding interface and does not close
|
||||
it on disposal. Native clients stop their producers and proxy, await their ledger
|
||||
close, then delete launch material. DS Web retains native history. A failed or
|
||||
unconfirmed close retains files and propagates the error. A lost open reply also
|
||||
retains files because the host may still own the connection. Shutdown must stop
|
||||
native/remote producers before closing the shared storage client.
|
||||
|
||||
## Ordering And Ownership
|
||||
|
||||
The shared `ModelCallLedger` type allows synchronous or Promise-returning methods;
|
||||
the concrete `AgentModelCallLedger` stays synchronous and remains the remote
|
||||
Agent's owner. Schema, database names, call identities, retention and transaction
|
||||
algorithms are unchanged.
|
||||
|
||||
`AgentModelGateway` awaits claim before provider dispatch, complete before returning
|
||||
the response, delivered before resolving the delivery acknowledgement, and
|
||||
outcome-unknown before reporting provider failure. Cancellation while claim is
|
||||
pending does not dispatch after claim completes. Persistence rejection propagates;
|
||||
the gateway does not replay a provider request after an unconfirmed write.
|
||||
|
||||
## Validation
|
||||
|
||||
The final focused run passed 113 tests across eight files, with 11 existing opt-in
|
||||
cases skipped. It included the official DS Web process using standard Node,
|
||||
binding uniqueness/rotation/cursor persistence, concrete ledger reopen and uncertain
|
||||
outcomes, delayed/rejected persistence, and both native clients' close ordering.
|
||||
|
||||
`desktop-storage-runtime-operations.test.ts` also builds real Electron fixtures.
|
||||
The production native terminal loopback gateway uses Main adapters and the real
|
||||
`DesktopStorageClient`/transport to reach a utility-process runtime owner, then
|
||||
closes its ledger before removing launch files. Production core registration is
|
||||
covered by `src/main/index.ts`; the fixture remains focused on the runtime domain.
|
||||
Its model response is local and deterministic, with no external provider call.
|
||||
|
||||
The shared gateway was separately validated on the pinned Linux x64 test Host via
|
||||
the existing lifecycle harness rebuilt from current source. OpenCode ran a bounded
|
||||
tool command in a dedicated `/root/tmp/gb-lifecycle-*` workspace. Desktop detached,
|
||||
the test Agent was promoted, the controller resumed, and the prompt completed.
|
||||
There were exactly **2 real text-provider calls**, both HTTP 200, **0 image calls**,
|
||||
33 persisted event entries, 17 checkpoints, and successful replay deduplication.
|
||||
The old test daemon retired; final owned process count was zero. The harness removed
|
||||
its remote directories. Local artifacts were isolated in
|
||||
`temp/goodbuddy-ledger-upgrade` and cleaned after validation.
|
||||
|
||||
Scoped ESLint and `git diff --check` passed. The current composition supplies
|
||||
`bindingStore` to `ManagedRemoteExecutionServices` and `openModelCallLedger` to
|
||||
the native coordinator. Whole-application acceptance, packaged cross-platform
|
||||
checks and concurrent performance measurements are not established by these
|
||||
focused tests.
|
||||
@@ -5,11 +5,12 @@
|
||||
- **适用范围:** Desktop(Electron Main、Preload、Renderer)及其后台进程和线程。
|
||||
- **用途:** 新功能设计和代码评审的约束。多个 agent 并行修改代码时,以本文为统一标准。
|
||||
- **关联文档:** `docs/roadmap/product-performance-experience-improvement-plan.md`(测量记录与实施进度)。
|
||||
- **架构与验收:** [桌面存储与并发执行](./desktop-storage-direction.md)定义一次完整交付的职责和范围;[桌面并发验收](../quality/concurrent-desktop-acceptance.md)定义多项目、多会话、多子任务负载及证据。本文件是性能规范的唯一数值来源。
|
||||
- **参照:** VS Code 的进程分工和渲染做法。我们没有在同一台机器上实测过 VS Code,下面的预算按常见的流畅标准设定。
|
||||
|
||||
## 1. 性能预算
|
||||
|
||||
“流畅”用下面的数字判断,不靠感觉。“标准机”指不降速运行 `npm run perf:app`,“低端机”指加上 `GB_PERF_CPU_THROTTLE=4`。
|
||||
“流畅”用下面的数字判断,不靠感觉。“标准机”指记录了硬件环境、不降速运行的参考机;Renderer 降速场景使用 `GB_PERF_CPU_THROTTLE=4`。该降速仅作用于 Renderer,不等于真实低配整机,也不模拟慢 Main 或磁盘。预算适用于单场景及规定的并发负载。
|
||||
|
||||
| 指标 | 标准机 | 低端机 |
|
||||
| --- | --- | --- |
|
||||
@@ -21,7 +22,9 @@
|
||||
| Main 事件循环延迟,p99 | ≤ 30 ms | — |
|
||||
| DOM 节点数 | 不随会话数、消息数增长 | 同左 |
|
||||
|
||||
标准机的阈值由 `npm run perf:check` 检查。低端机的数值目前只做记录,等结果稳定后再加进阈值检查。
|
||||
当前 `npm run perf:check` 使用历史回归上限,部分数值宽于本表,也未覆盖全部并发入口。检查通过不能宣称本表达标。目标交付须在验收报告中分别给出规范预算和回归上限的结果;低端机降速目前只做记录,真实低配和新增自动检查的完成状态另行记录。
|
||||
|
||||
点击反馈、所需内容可用和后台工作完成分别计时。显示加载状态只能证明及时反馈;不能用它替代结果就绪。每个进程分别报告长任务/事件循环和排队耗时,禁止把多个独立探针相加为端到端延迟。
|
||||
|
||||
## 2. 原则
|
||||
|
||||
@@ -32,6 +35,7 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
- 新代码不能在 Main 里使用 `DatabaseSync` 和同步文件 API。lint 已有白名单,名单只能减少。
|
||||
- 暂时必须留在 Main 的同步操作,单次耗时要能证明在 16 ms 以内,并在代码里注明理由。
|
||||
- 耗时可能超过 16 ms 的操作,必须支持取消和进度上报,不能一直占着 Main。
|
||||
- 目标交付后 Main 不再直接或通过 helper 打开桌面业务 SQLite;只读、失败回退、维护及退出均在检查范围内。当前迁移例外不是新增同步工作的许可。存储宿主可继续使用同步 SQLite,事务和计算仍需有界。
|
||||
|
||||
### P2 界面状态局部订阅
|
||||
|
||||
@@ -53,7 +57,8 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
|
||||
- `App.tsx` 只负责布局外壳、路由和把 store 实例通过 context 提供给子树。新增领域状态一律走上表四层。
|
||||
- 每个 store 的单元测试和 store 文件放在一起(`<domain>-store.test.ts`),覆盖顺序、失败重试和边界情况。
|
||||
- Main 侧对应:`<domain>` 的数据库访问集中在一个 repository 类里,IPC handler 只做校验和转发。
|
||||
- 领域数据库访问集中在 repository,目标架构中由存储宿主执行;Main 的 IPC handler 只做校验并等待领域操作。受管只读 Worker 与远端 Agent 按各自明确的连接归属执行,不能按源码目录把它们误判为 Main 访问。
|
||||
- 多项目/多会话流式更新只通知相关领域与实体;后台子任务不得让全部项目视图重渲染。隐藏视图保留草稿、选择和必要执行订阅,停止无关绘制与全量重读,切回时按已有摘要及增量恢复。
|
||||
|
||||
### P3 高频数据走专门通道,按帧合并
|
||||
|
||||
@@ -62,12 +67,16 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
- 多条更新合并后,每帧最多提交一次;同步到全局状态的频率要更低,目前是 250 ms。
|
||||
- 高频路径上不能读取布局(`getBoundingClientRect`、`scrollHeight` 等),布局读取要集中到每帧一次。
|
||||
- 合并更新不能丢失、重复或打乱顺序。每条数据带序号,并有测试覆盖。
|
||||
- 显示合并与持久化分开。可以合并进度或替换过时刷新,不能静默丢弃消息、工具结果或需确认的事件;多生产者缓冲达到容量时等待或明确失败,不无限增加内存。
|
||||
|
||||
### P4 数据只有一份来源,只传变化部分
|
||||
|
||||
- 每类数据只有一个权威来源,由数据所在进程负责持久化。界面不把整份数据写回。
|
||||
- 跨进程的通知直接带上变化的内容,接收方不再“收到通知后重新查全量”。
|
||||
- 写入合并成批次和事务,不按每个事件单独开一个事务。
|
||||
- 一个数据库有一个业务写入所有者,完整事务不跨 IPC 拆成单条 SQL。Main 不保存第二份可写业务状态;依赖写入的读取等待对应提交,远程 ACK 等待所需事件与 checkpoint 提交成功。
|
||||
- 缓存必须说明归属、失效和释放条件,不按浏览过的项目/会话无限积累。查询返回摘要、分页或所需详情,保持完整信息可访问,不用截断历史满足预算。
|
||||
- GoodBuddy 自身的中转数据优先使用有界内存;已有完整记录或文件时复用引用,避免重复落盘。预览、最终回答与工具全文分别核实,不能以“已保存消息”推断全文已保存。需要超出内存保留的内容直接使用所属存储的唯一正文,分页读取同一内容,避免临时全文再复制到正式存储。必要文件及清理范围遵守[输出与临时资源设计](./desktop-storage-direction.md#output-and-temporary-resource-ownership)。
|
||||
|
||||
### P5 工作量和可见内容成正比
|
||||
|
||||
@@ -80,22 +89,26 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
- 首屏之前只做显示应用壳和恢复上次界面所需的工作。
|
||||
- 其他初始化(Provider、Runtime、知识库、通知渠道等)按需执行,或等首屏显示后再空闲执行。
|
||||
- 后台模块在第一次使用时才加载。
|
||||
- 冷启动、首次进入功能和暖返回分别验收;预加载只使用有界空闲工作,不能为每个项目预建进程、连接或整份历史缓存。进程 spawn 不代表服务就绪,首次可用反馈与数据实际可用分开记录。
|
||||
|
||||
### P7 先测量,再修改
|
||||
|
||||
- 先跑测量,拿到数据再改代码;每项改动都要有前后对比,基线用临时 worktree 构建。
|
||||
- 每个版本至少跑 3 轮,交替运行;结果差异小于同一版本多次运行之间的波动时,不能说成“有提升”。
|
||||
- 测量用隔离的用户目录和本地假模型,不发出外部模型请求,结果里不含用户内容。
|
||||
- 必测多项目、多会话、多子任务及导入/检索/回顾发布重叠场景,负载和采样按[并发验收](../quality/concurrent-desktop-acceptance.md)。记录 offered load、实际吞吐、排队和排空、资源高水位;不得降低输入速率、串行化夹具或丢事件来取得流畅结果。
|
||||
- 基准使用固定版本、数据和环境。性能测量与真实模型功能验收分开;受发布构建限制时遵守发布手册,不以性能原则授权额外生产打包。
|
||||
|
||||
### P8 正确性优先于速度
|
||||
|
||||
- 事件顺序、持久化、取消、崩溃恢复和显示结果都不能因为性能而改变。
|
||||
- 异步改造要先确认“先写入、后读取”的顺序,没有测试覆盖就不合并。
|
||||
- 风险高、收益小,或者会改变现有功能的优化先不做,在计划文档里记录理由。
|
||||
- 取消请求、执行结算和容量释放不是同一时刻;调用方停止等待不能提前释放仍运行的工作。取消一个会话或子任务不得杀死仍服务其他任务的共享宿主。已提交事务如实报告成功,不伪装成回滚;未确认写入不盲目重放。
|
||||
|
||||
### P10 体验不倒退
|
||||
|
||||
性能优化只能让体验变好,不能用体验换速度。“行为不变”不仅指数据和结果,也包括用户能感知到的交互:
|
||||
性能优化必须保留数据、结果和用户交互语义,不能用体验换速度:
|
||||
|
||||
- **动画和过渡保持原样。** 平滑滚动(如“到底部”按钮)、展开收起、悬停、淡入淡出都不能因为优化而消失。任何瞬间写入 `scrollTop`/`scrollTo` 的代码都会打断正在进行的平滑滚动,必须在平滑滚动期间暂停。
|
||||
- **只渲染可见部分(P5)时,用户状态不能丢。** 行被卸载后,它的局部状态也会消失。以下行必须保持挂载,或者把状态存到行外:有焦点的行、用户展开过 `<details>` 或输入过内容的行、选中文本的两端、待回答的问题和待审批项、打开菜单或正在重命名的行。
|
||||
@@ -109,6 +122,24 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
- lint 规则和性能阈值是合并前的硬性条件,不靠人工记得。
|
||||
- 规则只能收紧,白名单和上限只能往下调。
|
||||
- 每个 agent 只提交自己改的文件和片段,不把别人未提交的改动带进自己的提交。
|
||||
- 检查直接 SQL 导入及已知间接打开函数/构造点,按实际宿主区分测试、存储与远端代码。白名单和上限需要与已提交基线比较,不能只靠注释约定;全局同步回退同样要被发现。
|
||||
- 普通 CI 与性能执行环境分别提供功能和预算证据;新增并发与首次进入场景纳入候选必需检查,缺少运行环境或结果时保留验收缺口,不由单测通过推断达标。
|
||||
|
||||
### P11 并发隔离与有界工作
|
||||
|
||||
同一会话的事件顺序和同一数据库的提交顺序必须保持;不同项目、会话、子任务的模型等待、重查询与清理不因此共用一把长时间全局锁。
|
||||
|
||||
- Main 全局控制队列不得等待模型、网络、原生启动或耗时清理。必要登记和状态提交可短暂串行,等待在所属操作范围内完成。
|
||||
- 重查询复用有界 Worker;必要写入不排在整个导入解析或模型请求之后。长事务先减少事务内无关计算,再测写入排队,不把阻塞简单搬进唯一存储队列。
|
||||
- 复用现有池、批处理和队列,记录在途数、排队数及字节上限。有限任务在持续竞争下仍须完成,繁忙会话不能无限抢占下一批;有实测饥饿才引入最小调度调整,不预建通用优先级框架。
|
||||
- 控制和取消不等待新的执行槽位;饱和时按业务语义等待或返回可恢复错误。禁止静默丢失、无限缓冲、无限重启或自动扩增进程。
|
||||
- 每个异步响应携带已有项目/会话/任务/操作归属;迟到响应不得覆盖新选择或新操作。归属检查先于改变共享请求代次、缓存或队列状态。
|
||||
|
||||
### P12 架构完整性与 KISS
|
||||
|
||||
一次性交付按最终职责边界验收,不以全面 Promise 化或局部热点迁出代替。目标范围内正常、后台、失败、重试、取消和退出路径均使用同一权威实现,清理原直接入口和回退。
|
||||
|
||||
保持现有技术栈、数据库文件及业务合同,复用已有资源机制。不得顺带新增分布式事务、持久 RPC receipt、通用恢复框架、每项目独立服务或产品审批模式。同步内存缓存读取可以保留,不把纯函数和每个 getter 远程化。设计新增机制必须指出当前失败、现有机制的不足与对应验证,不以“将来可能并发”代替证据。
|
||||
|
||||
## 3. 评审清单
|
||||
|
||||
@@ -125,21 +156,30 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
9. 动画、平滑滚动、过渡还在吗?有没有新增会打断平滑滚动的瞬间滚动写入?
|
||||
10. 只渲染可见部分的列表,滚走再滚回来,展开状态、输入内容、选区、焦点还在吗?
|
||||
11. 合并或延迟处理后,第一次响应(打字回显、点击反馈)有没有变慢?复制、导出读到的是不是屏幕上的最新内容?
|
||||
12. 多项目/多会话/多子任务同时运行时,取消或清理一个请求是否拖住其他任务?队列和缓存是否持续增长?
|
||||
13. 存储提交、远程 ACK、调用方返回和资源释放的顺序是否有真实路径及故障注入验证?
|
||||
14. 是否删除目标范围内的旧直接访问和同步回退?新增进程或调度机制是否复用现有设施并有实际必要性?
|
||||
15. 请求或输出是否产生非必要临时文件、重复全文?是否核实完整内容与预览的区别,并验证分页、引用释放、必要文件的创建失败清理及升级后的残留回收?
|
||||
|
||||
## 4. 当前差距
|
||||
|
||||
截至 2026-10-04(第一期结项,见 `docs/review/performance-phase-1-report-2026-10-04.md`),按原则对照的主要差距:
|
||||
截至 2026-10-05,以下状态结合[存储升级实施记录](../quality/storage-upgrade-implementation-2026-10-05.md)和[并发验收规范](../quality/concurrent-desktop-acceptance.md)核对。表中“已实现”只表示代码或定向测试已有证据,不表示 PERF-15、PERF-16 或 C01-C07 已完成。
|
||||
|
||||
| 原则 | 差距 |
|
||||
| --- | --- |
|
||||
| P1 | 同步数据库调用仍在 Main,没有独立数据进程;但热点读已在 worker,高频写单次 0.1–0.3 ms,WAL checkpoint 在后台线程,启动恢复按索引读取。文档解析、分块和大文件哈希已移到 worker。消息附加数据冗余(子任务工具输出、`tools` 与 `blocks` 重复),大会话读取约 170 ms |
|
||||
| P1 | Assistant、Knowledge、文件和 Runtime 生产域已由 `DesktopStorageClient` 接入 utility process;剩余缺口是全量间接访问扫描、异常/维护/退出路径核验,以及 Main 阻塞和存储排队的并发测量。文档解析、分块和大文件哈希仍在各自 worker。 |
|
||||
| P2 | `App.tsx` 约 7,900 行。输入区、隐藏路由、任务和结果事件、切换会话已不再整体重渲染 App;当前会话的流式吸收(每 250 ms)、队列和活动记录仍经过 App |
|
||||
| P3 | 已基本满足:流式输出和终端输出都已按帧合并 |
|
||||
| P4 | 活动记录已改为增量保存、按页读取(界面只保留已加载的页,计数和分组来自 Main 的汇总);部分 `*Changed` 通知之后还会重新查全量 |
|
||||
| P5 | 已基本满足:消息、会话、笔记、文件树、知识库文档表格都只渲染可见部分 |
|
||||
| P6 | 空配置启动到输入框可用约 0.96 秒;真实数据(2.4 GB)约 1.36 秒。本地工具环境已后台初始化,低频模块按需加载 |
|
||||
| P9 | 性能阈值还没有接入 CI,也没有真实低端机的基线 |
|
||||
| P3 | 流式输出和终端输出已有按帧合并;C01/C02/C06 的长期负载仍需验证是否出现排队增长、长帧或内容丢失。 |
|
||||
| P4 | 存储域已统一到各自 owner,输出 backing 和附件引用已接入;部分 `*Changed` 通知仍会重新读取全量,重复缓存和跨域通知的并发影响待测。 |
|
||||
| P5 | 消息、会话、笔记、文件树、知识库文档表格已有窗口化;C07 仍需记录冷/暖内容可用时刻和切换后的状态保留。 |
|
||||
| P6 | 空配置和真实数据启动有历史测量;当前 C07 尚未完成监督概览、图谱、历史选择和来源正文的完整冷/暖验收。 |
|
||||
| P9 | 已有 lint 和历史回归上限;间接 SQLite 打开、白名单单调收紧、并发指标缺失和 CI/性能环境接入仍待补齐。 |
|
||||
| P10 | 已有局部输入、刷新和项目切换修复;需要在 C01/C02/C06 中继续验证动画、焦点、滚动、草稿和取消语义没有因降载或合并而退化。 |
|
||||
| P11 | 存储请求和运行时路径已有有界容量与按域归属;C02、C04、C05、C06 尚未证明健康 peer 隔离、饱和恢复、队列排空和有限后台任务完成。 |
|
||||
| P12 | 目标边界和 KISS 约束已写入架构文档;S01-S07 已有生产组合,但删除旧直接入口、异常路径和全候选统一验收仍未完成。 |
|
||||
|
||||
2026-10-04 补充:监督回顾的来源初始化和继续前版本校验已移入现有 Worker 入口的独立连接,扫描不持有写锁,清单按 200 条提交,生产失败不回退 Main 同步扫描。最终发布事务仍在 Main;实现与实测边界见[监督者进度](../features/conversation-supervision/progress.md#2026-10-04-回顾初始化阻塞修复)。
|
||||
|
||||
2026-10-05 补充:监督者 UI 的概览、图谱、故事读取也已迁到现有只读 Worker;概览按需加载、列表窗口化及摘要缓存见[KISS 局部修复](../review/kiss-local-fixes-2026-10-05.md)。这与工具侧 Story Graph 读取是不同入口。Main 最终发布及部分大响应仍有超预算样本,不能把局部完成视为全项达标。存储服务目标另见[桌面存储方向](./desktop-storage-direction.md),本轮不实施全面迁移。
|
||||
2026-10-05 局部修复记录:监督者 UI 的概览、图谱、故事读取也已迁到现有只读 Worker;概览按需加载、列表窗口化及摘要缓存见[KISS 局部修复](../review/kiss-local-fixes-2026-10-05.md)。这与工具侧 Story Graph 读取是不同入口。Main 最终发布及部分大响应仍有超预算样本,不能把局部完成视为全项达标。
|
||||
|
||||
随后用户确定按[完整存储与并发架构](./desktop-storage-direction.md)一次性交付。当前已完成存储 owner、主要生产组合和部分输出迁移,但没有完成全面架构验收。P11/P12 与[并发验收矩阵](../quality/concurrent-desktop-acceptance.md)约束当前候选的剩余验证,不能作为已有实现或实测结果;上述第一期历史状态也不替代当前验收。
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
# Runtime Storage And Background Reads
|
||||
|
||||
## Current Implementation
|
||||
|
||||
This file records current runtime timing, reader behavior and temporary-resource
|
||||
ownership. The production runtime receives the storage-owned output backing store and
|
||||
output adoption function during Main startup. Main also computes one per-version,
|
||||
per-run temporary root and passes it to the local runtime cache paths and native
|
||||
client coordinator.
|
||||
|
||||
SQLite owns persisted task, event and conversation evidence. The desktop business
|
||||
connection is long-lived. WAL permits readers alongside a writer; synchronous SQL
|
||||
still blocks the thread that executes it. Memory caches are disposable derived data.
|
||||
Persisted evidence is not necessarily full tool output; the inventory below records
|
||||
the current distinction. The target ownership rules are in the architecture document.
|
||||
|
||||
Startup storage upgrades run in `assistant-storage-worker` before the business
|
||||
connection opens. Runtime readers must not invoke initialization, migrations or
|
||||
@@ -16,6 +26,17 @@ or result cache. Main owns current segments independently of renderer visibility
|
||||
The [duration design](../features/task-and-job/technical-design.md) defines the
|
||||
state boundaries, restart handling and incomplete history.
|
||||
|
||||
## Planned Architecture
|
||||
|
||||
The [complete desktop architecture](./desktop-storage-direction.md) defines the
|
||||
remaining target, including desktop binding databases and native-client model ledgers.
|
||||
Its migration and [concurrent acceptance](../quality/concurrent-desktop-acceptance.md)
|
||||
are one delivery; remote Agent database ownership remains unchanged. Main will still
|
||||
observe runtime transitions while the storage service persists them in order; the
|
||||
indexed timing representation and semantics remain.
|
||||
|
||||
## Runtime Read Lifecycle
|
||||
|
||||
Read transactions end before replies are posted. Long-lived read transactions
|
||||
would prevent WAL checkpoints from reclaiming old pages. Each reader has its own
|
||||
SQLite page cache; adding workers increases memory usage and requires evidence of
|
||||
@@ -34,3 +55,193 @@ reopening. Results remain scoped; superseded responses are discarded.
|
||||
The [task statistics design](../features/task-and-job/technical-design.md) owns the
|
||||
snapshot contract and timing rules. Tests and timings demonstrate only their fixture
|
||||
conditions; they do not establish a latency guarantee for arbitrary retained history.
|
||||
|
||||
## Output And Temporary File Investigation
|
||||
|
||||
Source review on 2026-10-05. The inventory records the current production flow. No
|
||||
user Temp directory was scanned or deleted.
|
||||
|
||||
| Producer | Current content flow | Retention and consequence |
|
||||
| --- | --- | --- |
|
||||
| Local process and ripgrep | [Process capture](../../src/main/agent/direct-model-process-service.ts) writes stdout/stderr through [PagedOutputStore](../../src/main/agent/paged-output-store.ts); model receives a JSON-bounded preview, up to 96 KiB per stream, and a reference when truncated | Every stream opens a file, including empty stderr. Small files are deleted on finish; retained files and their in-memory handles belong to the conversation/service |
|
||||
| Direct-model subagent | [Subagent service](../../src/main/assistant/direct-model-subagent-service.ts) captures child text through the same store, returning up to 192 KiB and an optional reference | [Provider event bridge](../../src/main/agent/model-tool-provider.ts) forwards preview/status but drops the output reference from the subagent event; child text is not an independent full parent-history record |
|
||||
| Tool events and conversation history | [Model runtime](../../src/main/agent/model-runtime.ts) emits at most 16,000 characters of serialized tool result as the event preview; [IPC](../../src/main/ipc.ts) and [database](../../src/main/assistant/assistant-database.ts) persist the supplied event | Tool events still carry bounded previews, while output history adopts finished handles through attachment storage. Even `output_read` responses are previewed in tool events. Persisted event history does not by itself guarantee a complete stdout/stderr copy |
|
||||
| Remote ACP | [Agent transcript](../../src/agent-daemon/semantic-prompt-store.ts) retains received notifications pending ACK; [desktop adapter](../../src/main/agent/acp-remote-runtime.ts) bounds mapped events before persistence | ACK prunes Agent transcript rows. Notifications themselves may already be bounded by the Runtime. Do not treat this journal as a permanent full-output source or remove commit-before-ACK |
|
||||
| MCP calls | [MCP transport](../../src/main/capabilities/mcp-client-transport.ts), provider and [gateway](../../src/main/agent/knowledge-mcp-gateway.ts) pass objects through stdio/HTTP/SSE | No per-call request/response scratch-file creation was found in these paths. Calling a tool over MCP does not itself require a payload file; third-party server internals are outside this inventory |
|
||||
|
||||
`PagedOutputStore` keeps short and empty output in memory and delegates larger
|
||||
or JSON-preview-truncated content to an injected `PagedOutputBackingStore`. Capture
|
||||
has a shared 8 MiB budget, at most 1,024 pending chunks per writer and 4,096 active or
|
||||
retained handles per store; backing writes are at most 64 KiB. Producers must await
|
||||
append, or receive an explicit capacity error. Local process pipes pause until each
|
||||
append settles. The store does not create a temporary file backend or another DB.
|
||||
UTF-8 byte cursors, JSON-escaped preview bounds, owner checks and failed/cancelled
|
||||
output remain covered by focused tests using real async files and local children.
|
||||
|
||||
The production owner exposes output create, append, finish, read, adopt and release
|
||||
through the existing storage transport. Main passes its `backingStore` to the runtime
|
||||
and passes `outputAdopt` to the tool provider. When output history is enabled, finished
|
||||
stdout/stderr references are adopted into the conversation attachment reference for
|
||||
the target message; the bounded preview and output reference are returned together.
|
||||
Missing backing configuration still explicitly fails capture that requires full
|
||||
storage. A model's final answer is not an equivalent copy of its tool output.
|
||||
|
||||
### Planned Architecture And Remaining Acceptance
|
||||
|
||||
The planned architecture still requires full end-to-end history and restart coverage,
|
||||
resource reconciliation and the remaining storage-owner migration. The file-backed
|
||||
test fixture alone does not establish that architecture or its acceptance.
|
||||
|
||||
Tool previews can also appear in both conversation `tools` and `blocks`. This is a
|
||||
separate persisted-preview duplication issue, not evidence that full paged output is
|
||||
already stored. Existing [subagent progress compaction](../../src/main/assistant/subagent-progress-storage.ts)
|
||||
already handles repeated progress snapshots; retain that implementation.
|
||||
|
||||
### Other Owned Files
|
||||
|
||||
| Existing owner | Source finding | Disposition for the upgrade |
|
||||
| --- | --- | --- |
|
||||
| [DocumentResultStorage](../../src/main/document-result-storage.ts) and [attachment storage](../../src/main/conversation-attachment-storage.ts) | `parsed.md` duplicates `manifest.json.content`; normal reads use the manifest and `copyResult` copies the extra file | Remove the redundant new write/copy. Preserve original files, extracted images, manifests and ownership transfer; old directories follow existing reference cleanup |
|
||||
| [OpenCode runtime](../../src/main/agent/opencode-runtime.ts) | Configuration already uses `OPENCODE_CONFIG_CONTENT`; scratch supplies isolated XDG state and skill registration | Keep required isolation, reuse immutable skill caches. Do not invent a config-file optimization for a path already using memory/environment |
|
||||
| [Continue adapter](../../src/main/agent/continue-host-adapter.ts) | CLI uses `--config` plus an isolated global directory; creation can fail before the outer owner receives the path | Retain the external-file interface and clean partial creation at the creating function, including skill-staging failure; preserve pre-existing files on `EEXIST` |
|
||||
| [Continue helper](../../src/agent-daemon/continue-acp-helper.ts) and [model bridge helper](../../src/agent-daemon/model-bridge-helper.ts) | Temporary config/plugin resources use helper finalization; setup before the protected scope and termination need lifecycle coverage | Extend existing ownership and shutdown, verify child exit before deletion. Plugin file URLs and Unix sockets remain necessary interfaces under current contracts |
|
||||
| [Managed Python](../../src/main/local-tool-environment/managed-python-install.ts), [model packages](../../src/main/model-package-utils.ts), [Agent packages](../../src/main/remote-agent/agent-package-manager.ts) | Install staging, partial downloads and backups support validation/publication; existing cleanup has owner-specific patterns | Extend those cleanup functions for demonstrated leftovers; preserve active staging and verified reusable installations. No global installer sweep |
|
||||
| [Magic note storage](../../src/main/magic-notes/magic-note-storage.ts) | Sibling atomic-write `.tmp` files protect replacement; memory-database fixtures have a separate temporary root | Keep atomic replacement and normal failure cleanup. Test scratch follows repository rules; persistent note directories are user data |
|
||||
|
||||
Document results may move from temporary ownership into attachment references, including
|
||||
shared branch references. A directory under `temp/document-parsing` can still be owned
|
||||
by persisted data. Its location alone does not authorize deletion. Native client history,
|
||||
model caches, database WAL/SHM and exported user files are likewise not disposable scratch.
|
||||
|
||||
Some tests/build helpers share production-looking prefixes, including `goodbuddy-process-*`
|
||||
and `goodbuddy-opencode-*`. Prefix and age alone cannot establish who owns a legacy directory.
|
||||
The [architecture rules](./desktop-storage-direction.md#output-and-temporary-resource-ownership)
|
||||
define the replacement and cleanup scope; the [acceptance matrix](../quality/concurrent-desktop-acceptance.md#输出与临时资源验收)
|
||||
defines evidence required before claiming improvement.
|
||||
|
||||
### Creator Cleanup Validation (2026-10-05)
|
||||
|
||||
Continue now removes its partially written config and failed skill-staging directory
|
||||
at the creator, before returning ownership to the run. Exclusive creation failures
|
||||
leave existing files/directories intact. Run finalization preserves prepared caches
|
||||
and external configuration. Both Agent helpers protect setup with finalization and
|
||||
handle SIGTERM/SIGINT; Continue stops prompt admission, aborts and drains active
|
||||
runs, while the OpenCode helper waits for child exit before deleting its plugin.
|
||||
Managed Python's existing pre-install cleanup also removes regular download files
|
||||
with the exact UUID layout, preserving directories, unknown names, caches and history.
|
||||
|
||||
Validation in the concurrent working tree:
|
||||
|
||||
- Five focused suites (`continue-host-adapter`, `continue-acp-helper`, `model-bridge`,
|
||||
`managed-python-install`, `local-tool-environment-service`): 81 passed, 10 Unix-only
|
||||
cases skipped on Windows. Failure injection covers partial creation, EEXIST,
|
||||
failed skill staging, setup-time signals and shutdown cleanup.
|
||||
- Final `npm run typecheck` passed; ESLint passed for all eight owned source/test files.
|
||||
Repository lint passed with `npm run lint -- --ignore-pattern temp`; unfiltered
|
||||
lint encountered generated validation bundles in ignored task/release directories.
|
||||
Task-owned `temp/goodbuddy-scratch-upgrade` was subsequently removed and its absence
|
||||
verified; unrelated release scratch was preserved.
|
||||
- Full Vitest run: 517 suites passed, 7 failed, 15 skipped; 6,244 tests passed,
|
||||
6 failed, 94 skipped. Failures outside this cleanup scope included OpenCode config
|
||||
preparation (`spawnSync npm.cmd EINVAL`), the MCPVault Electron fixture, and
|
||||
message-expansion and workspace-import Electron assertions. These results do not
|
||||
establish full architecture acceptance.
|
||||
- Reused the existing `a01-runtime-host-build.cjs`/`a01-runtime-host-run.cjs` harness
|
||||
logic, rebuilding Agent, daemon and Desktop driver from current source under
|
||||
`temp/goodbuddy-scratch-upgrade`. Connected to Linux x64 over the LAN using
|
||||
GoodBuddy encrypted credentials and the pinned Host identity. Both routes were
|
||||
reachable; only the LAN route was used for execution.
|
||||
- Both OpenCode and Continue passed real tool execution, startup cancellation,
|
||||
disconnect/reconnect during execution, checkpoint persistence/replay deduplication,
|
||||
and retirement. Final-source cancellation took 217 ms and 233 ms respectively;
|
||||
each run reported zero owned processes after retirement and shutdown. There were
|
||||
eight real model requests across the initial and final runs, four per runtime.
|
||||
Remote artifacts stayed in dedicated `/root/tmp/gb-lifecycle-*` directories and
|
||||
followed the harness cleanup. These timings are scenario observations, not a
|
||||
comparative performance claim.
|
||||
|
||||
### Version And Run Scratch Follow-up (2026-10-05)
|
||||
|
||||
`runtimeTemporaryRoot(userDataPath, appVersion)` computes the lazy
|
||||
`temp/goodbuddy-runtime/<version>/<runId>/goodbuddy-runtime-launch` path.
|
||||
Main passes that same value through existing `continueHostCacheRoot` and
|
||||
`opencodeSharedCacheRoot` options. The native client coordinator receives it as
|
||||
`temporaryRoot` and passes it to both native clients. The existing constructors need
|
||||
no new parameter.
|
||||
OpenCode creates its isolated launch directory beneath the supplied shared root;
|
||||
Continue puts generated configuration and isolated globals in one per-call directory.
|
||||
Prepared bundles and immutable Skill/config caches remain reusable within the run.
|
||||
The helpers on a remote Host use `goodbuddy-runtime-launch` beside their existing
|
||||
model-bridge socket, under that process owner's scratch directory. Global `TEMP`
|
||||
is unchanged.
|
||||
|
||||
Each local launch has a sibling owner file containing creator and child PIDs. Keeping
|
||||
metadata outside the deleted tree preserves retry evidence after partial deletion.
|
||||
The launch root also records its creator for cache-only reclamation. After the
|
||||
single-instance check and first screen, call `cleanupRuntimeTemporaryDirectories`
|
||||
in the existing delayed background startup work. It examines at most 256 directory
|
||||
entries per invocation, sequentially, and accepts the startup shutdown signal.
|
||||
Service disposal still stops children before deleting its own launch material;
|
||||
an unconfirmed child exit retains the material. Startup preserves live/reused PIDs,
|
||||
permission-denied process checks, malformed ownership and the ambiguous spawn-to-PID
|
||||
recording window. Empty version/run parents are removed after owned material.
|
||||
This is not a new process owner or an installer collector.
|
||||
|
||||
Legacy candidates are limited to the known OS-Temp layouts `goodbuddy-opencode-*`
|
||||
(desktop isolated state), `goodbuddy-continue-*` (Continue helper), and
|
||||
`goodbuddy-opencode-plugin-*` (model-bridge plugin). Deletion requires valid owner
|
||||
metadata and confirmed inactive creator/child PIDs. Releases that wrote no such
|
||||
metadata cannot be identified as obsolete from their names, contents or age alone;
|
||||
those directories are retained and reported. A bounded pass can leave candidates
|
||||
unvisited. Native-client histories, existing version-only caches and unrelated
|
||||
OS-Temp files are outside this reclamation contract. The old version/layout-only
|
||||
cache sweep is not evidence that a surviving child has exited.
|
||||
|
||||
Remote environment preparation now retains its existing pending-operation record
|
||||
when connection acquisition or staging cleanup fails. Because that store has one
|
||||
record per Host, a replacement prepare retries cleanup before overwriting it;
|
||||
confirmed cleanup or `operation-unavailable` clears it. Already adopted environments
|
||||
remain usable. Agent package delayed removals retain their pending entry until
|
||||
deletion succeeds, retry on inventory access, and rediscover verified obsolete
|
||||
versions through the existing package scan after restart. Active archive leases
|
||||
still prevent deletion.
|
||||
|
||||
Continue bundle staging now waits for all parallel writes to settle before failure
|
||||
cleanup. Existing settings atomic replacement and selected-runtime diagnostic
|
||||
teardown already use `finally`; no global `.tmp` sweep or new diagnostic owner was
|
||||
added. Native clients now accept an optional `temporaryRoot` for the same application
|
||||
version/run launch root. `NativeTerminalClient` keeps histories and reusable caches
|
||||
under its existing `rootDirectory`; only per-launch material moves to scratch.
|
||||
DS Web keeps `DSH_HOME`, sessions and user preferences in place and supplies its
|
||||
generated configuration through `web --patch <scratch-file>`. Its ledger also lives
|
||||
in scratch. Both reuse the existing sibling PID metadata and await ledger close
|
||||
before releasing material; unconfirmed child exit retains it. ConPTY registration
|
||||
waits for the actual child PID, which can be unavailable immediately after spawn.
|
||||
The collector recognizes `goodbuddy-native-dsh-*` alongside the existing prefixes.
|
||||
The production wiring propagates `temporaryRoot` through the native coordinator to
|
||||
both constructors without relocating the retained-history root.
|
||||
Focused verification passed 60 tests, including a real Windows PTY, official DS Web
|
||||
session persistence across restart, and delayed ledger-close cleanup. Typechecking
|
||||
and scoped lint passed; this native follow-up made zero real model calls.
|
||||
|
||||
Final-source verification for this follow-up is separate from the earlier eight
|
||||
real requests above: two real provider requests total, one per runtime. Current-source
|
||||
Continue/OpenCode helpers ran on Linux x64 over pinned SSH, each executed its native
|
||||
command tool, returned the real model answer, and removed launch files after exit.
|
||||
A separate active-request shutdown probe passed for both helpers with zero real
|
||||
provider requests. Four successful scenarios used deterministic command responses;
|
||||
probe setup retries before these results did not reach a provider. A previous-process
|
||||
version/run directory was reclaimed using actual dead PIDs. After the final
|
||||
enumeration-error guard, a separate Host probe confirmed that the current collector
|
||||
retains a real live child and reclaims that same run after its exit, with zero model
|
||||
requests. The timed-out probe's ledger was audited and contained zero calls; all
|
||||
task probe artifacts were removed locally and remotely. These probes cover the
|
||||
changed helper and scratch lifecycle, not full Desktop UI or installation acceptance.
|
||||
|
||||
The seven primary suites passed 267 tests with 10 platform skips; related runtime,
|
||||
native-terminal and diagnostic-owner suites also passed. Agent and web typechecks
|
||||
passed. Full desktop typecheck and repository lint remain blocked by concurrent
|
||||
`portable-storage-upgrade-fixture.ts` typing and `portable-storage-upgrade.mjs` lint
|
||||
errors; unfiltered lint also visits generated files under `temp`. Focused lint for
|
||||
the edited source/tests passed. The C01-C07 concurrent acceptance matrix remains
|
||||
incomplete; these results do not establish full storage-architecture acceptance.
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
# Agent 功能维护约束
|
||||
|
||||
本文面向在 GoodBuddy 中新增功能的 Agent。它只记录做决定时必须遵守的维护规则,
|
||||
不替代架构设计、性能规范或功能文档。若本文与代码或权威文档不一致,以已验证的生产
|
||||
代码和下列权威文档为准,并修正文档链接或内容:
|
||||
|
||||
- [桌面存储与并发执行](../architecture/desktop-storage-direction.md):职责边界、存储所有权和操作边界。
|
||||
- [桌面存储基础集成](../architecture/desktop-storage-foundation.md):`DesktopStorageClient`、domain operation 和生命周期。
|
||||
- [桌面 Runtime 存储集成](../architecture/desktop-storage-runtime-integration.md):binding、model-call ledger 和关闭顺序。
|
||||
- [性能原则](../architecture/performance-principles.md):唯一的性能数值、并发规则和评审清单。
|
||||
- [桌面并发架构验收](../quality/concurrent-desktop-acceptance.md):真实生产路径、负载和证据要求。
|
||||
- [运行时存储与后台读取](../architecture/runtime-storage.md):输出正文、预览、临时资源和当前迁移状态。
|
||||
- [数据库迁移与大数据升级指南](./database-migrations.md):已发布用户数据的迁移边界。
|
||||
- [AGENTS.md](../../AGENTS.md):临时文件、真实 Host 验证、测试和文档维护规则。
|
||||
|
||||
## 决策检查
|
||||
|
||||
开始编码前,逐项回答下面的问题。任一项没有答案时,先补充设计或查找现有机制,
|
||||
不要直接增加新的 owner、队列、缓存或恢复记录。
|
||||
|
||||
- 数据的唯一权威来源和写入 owner 是什么?它是否已经存在?
|
||||
- 该功能是否需要数据库事务、跨表快照、文件引用或运行时 ledger?若需要,操作是否落在对应 owner 内?
|
||||
- Main 是否只负责 IPC 校验、凭据、生命周期和调度?是否把 SQL、大文件读写、哈希或模型工作留在了 Main?
|
||||
- 新操作能否使用现有 domain port 和显式 allowlist?是否误把 SQL、回调、实例、函数或 handle 放进跨进程 DTO?
|
||||
- 依赖链是否按“先提交,再通知、ACK、读取或返回成功”的顺序实现?提交未确认时是否禁止盲目重放?
|
||||
- 排队取消、在途取消、执行结算和资源释放分别是什么时刻?容量是否一直占用到实际结算或确认的 Host 退出?
|
||||
- 输出是预览还是完整正文?完整正文是否只有一个所属存储,引用是否随历史、分支和重启继续有效?
|
||||
- 是否创建了临时文件?创建者、使用者、删除条件、子进程退出确认和删除失败后的重试位置是否明确?
|
||||
- 新的操作数、排队数、字节数、句柄数和缓存是否有界?是否复用了已有预算并避免无限缓冲?
|
||||
- 测试是否覆盖真实 UI 到生产实现的路径、失败、取消、重试、关闭和迟到响应?是否需要真实数据、模型或 Host?
|
||||
- 受影响的架构、功能、质量、构建或运维文档由谁维护?是否已有同一事实的权威定义,新增内容能否只链接它?
|
||||
|
||||
## 存储所有权
|
||||
|
||||
桌面业务 SQLite 由一个 storage utility process 持有。Main 通过
|
||||
`DesktopStorageClient.call(domain, method, args, { signal })` 使用显式操作;
|
||||
`src/main/desktop-storage-contracts.ts` 的 allowlist 是可调用面的边界。Main 不得再
|
||||
打开同一业务数据库,也不得通过 helper、同步回退或隐藏的 repository 构造绕过 owner。
|
||||
远程 Agent 的数据库仍由远程 Agent 持有,不能因桌面存储改造而迁移。
|
||||
|
||||
新增持久化时,优先扩展已有 domain repository 和 owner operation。完整事务、跨表快照、
|
||||
去重、checkpoint 与发布规则留在 owner 内;模型、网络、解析和文件准备在事务外完成,
|
||||
进入事务后只校验当前前提并提交完整结果。需要多个语句的读快照,应增加一个有名字的
|
||||
owner/reader operation,不要在 Main 中拆成多次 `await`。
|
||||
|
||||
Main 侧服务只持有异步 port 和普通 DTO。live repository、数据库连接、事务 callback、
|
||||
订阅、函数、AbortSignal 和运行时 handle 不跨 utility transport。`StorageData` 允许的
|
||||
值以当前契约为准;新增类型先确认序列化、大小计算和错误传播,再加入 allowlist。
|
||||
|
||||
文件和输出也有 owner。附件引用、document result、paged output backing、Runtime
|
||||
binding 和 native model-call ledger 应通过既有 storage facade 访问。文件所在目录不等于
|
||||
可删除的临时目录;持久化引用、WAL/SHM、native history、用户导出文件和仍被分支引用的
|
||||
正文都必须按 owner 释放。
|
||||
|
||||
## 增加领域操作
|
||||
|
||||
按以下顺序定位扩展点:
|
||||
|
||||
1. 找到已有 repository、port 或 facade,以及它实际打开的数据库和文件。
|
||||
2. 在 owner 内保留原有事务和 ID、顺序、去重、耐久性语义,必要时抽出可复用的内部函数。
|
||||
3. 为外部调用增加一个有明确名称的 domain operation,更新类型 allowlist 和 transport handler。
|
||||
4. 在 Main 组合处注入已有 typed port;调用者等待 Promise,不保存第二份可写事实。
|
||||
5. 为提交前、提交后响应前、取消、失败、重试、关闭和重启增加针对性断言。
|
||||
|
||||
操作应表达业务动作,例如 `appendRemoteTaskEventsBatch`、`publishDocument` 或
|
||||
`transition`。不要暴露 `executeSql`、任意方法代理、通用对象代理或把 repository
|
||||
整个返回给调用者。一个已有操作能表达需求时,扩展其参数或返回值并更新权威文档,
|
||||
不要平行创建同义 operation。
|
||||
|
||||
通知不是提交确认。持久化成功后才能发送依赖该事实的 change notification、远程 ACK、
|
||||
模型下一轮输入或成功响应;通知失败要按现有语义处理,不能把已提交事实伪装成未提交。
|
||||
|
||||
## Main 与 utility 的边界
|
||||
|
||||
Main 保留窗口、可信 sender 和 schema 校验、凭据解密、网络与 Runtime 编排、OS API、
|
||||
请求归属和进程生命周期。utility storage 保留业务 SQLite、owner-local 事务、升级后
|
||||
的读 worker 生命周期和必要的同步 SQL。Parser、OCR、embedding、模型请求和远程 Agent
|
||||
继续使用各自宿主,不因“改成异步”而塞入 storage 事务。
|
||||
|
||||
Main 里的同步数据库调用、同步文件 IO 和可能超过 16 ms 的计算都要按性能原则处理。
|
||||
Promise 返回本身不能证明工作已离开 Main。启动顺序是先完成 storage readiness,再发布
|
||||
依赖它的服务;关闭顺序是先停止生产者并等待必要的持久化,再关闭 ledgers、bindings、
|
||||
读 worker、数据库 owner 和 utility process。失败或未确认退出时保留可能仍被 child
|
||||
持有的文件,并将错误传播给调用方。
|
||||
|
||||
## 异步顺序与取消
|
||||
|
||||
为每个操作写清四个时点:入队、开始执行、提交或执行结算、资源释放。最小必要范围内
|
||||
保持顺序:同一会话事件按序提交,同一数据库事务按序落盘;不同项目、会话和子任务
|
||||
不得共用长时间的全局等待。
|
||||
|
||||
取消规则如下:
|
||||
|
||||
- 排队中的请求在 dispatch 前检查 abort,取消后不得执行。
|
||||
- 在途请求先向实际 owner 或 Host 发送取消,调用方可以先得到取消结果,但容量只能在执行结算或确认 Host 退出后释放。
|
||||
- 同步 SQL 无法被 IPC 消息打断;安全批次之间检查取消,已经提交的事务必须报告为已提交。
|
||||
- 取消一个请求不得杀掉仍被其他请求使用的共享 Runtime、utility process 或 Worker。
|
||||
- 迟到结果必须携带已有项目、会话、任务或 operation 归属,先通过归属检查,再改变代次、缓存或队列。
|
||||
- 存储进程替换前等待旧进程退出并结算旧请求;未确认写入按 domain identity reconciliation 处理,不能自动重放写入。
|
||||
|
||||
`AbortSignal` 是 transport option,不是业务 DTO 字段。不要用 UI 隐藏、组件卸载或清空
|
||||
按钮代替服务层取消。控制和取消不得等待新的执行槽位。
|
||||
|
||||
## 输出引用与临时文件
|
||||
|
||||
短输出可以保留有界预览;超过预览上限的输出必须由所属 backing store 保存完整正文,
|
||||
并在后续模型 `output_read`、历史和分支路径中复用同一 handle。分别核对模型收到的内容、
|
||||
工具事件预览和历史正文,不能用最终回答代替工具全文,也不能把正文再次复制成临时全文。
|
||||
输出 writer 的 append 必须被生产者等待;容量错误要显式传播,不能丢块或无限排队。
|
||||
|
||||
创建临时配置、插件、socket 或 launch 目录的函数承担创建失败清理。删除前关闭拥有
|
||||
文件或目录的 child、ledger 和句柄,并确认 child 已退出;无法确认时保留材料和 owner
|
||||
metadata,等待后续有界回收。应用临时文件使用仓库根目录 `temp/<task>/`,远程 Host
|
||||
使用专用 `/root/tmp` 子目录。不要扫描或删除用户 Temp、未知归属目录或仅凭名称和年龄
|
||||
判断为过期的资源。
|
||||
|
||||
持久化目录可以位于 `temp/` 下,位置本身不能授权删除。删除前先检查 attachment、
|
||||
document result、history、branch 或其他 durable reference;释放一个调用引用不能删除
|
||||
仍被历史引用的正文。旧数据清理沿用所属 owner,不增加全局 Temp sweep。
|
||||
|
||||
## 并发预算
|
||||
|
||||
数值只引用[性能原则](../architecture/performance-principles.md),负载和证据只引用
|
||||
[并发验收](../quality/concurrent-desktop-acceptance.md)。当前 storage client 的请求、
|
||||
读写 pending 和 32 MiB admission budget 是实现约束;paged output 的 8 MiB capture、
|
||||
每 writer 1,024 个 pending chunks、4,096 个 active/retained handles 和 64 KiB backing
|
||||
write 也必须服从当前实现与测试。修改前先确认代码中的常量和适用范围,不在本文件复制
|
||||
一套新数字。
|
||||
|
||||
容量应同时约束操作数和字节数。读 worker、写 owner、输出 backing、模型池和 UI 刷新各自
|
||||
使用现有有限机制;队列满时等待或返回可恢复的 busy/capacity 错误。禁止静默丢事件、
|
||||
无限重启、按项目预建服务、为每个调用新增 scheduler,或用串行夹具掩盖饱和。
|
||||
|
||||
## 测试与真实验证
|
||||
|
||||
单元测试用于确定字节边界、事务回滚、顺序、归属、取消竞态、失败清理和重试。行为改动
|
||||
还必须覆盖生产组合:正常 UI action 经过 Preload、Main、storage/Runtime 宿主并产生
|
||||
实际结果。mock bridge、独立 SQL、只测试握手或只测试 schema 都不能替代这条路径。
|
||||
|
||||
需要持久化时使用真实 SQLite owner 和隔离数据库;需要文件时使用真实异步文件;需要
|
||||
Runtime 时使用本地确定性模型验证工具、事件、取消和关闭。不要把真实提供商网络速度
|
||||
混入性能基准。需要模型能力证明时,配置检查不够,必须发起实际生成或工具请求,并
|
||||
记录实际请求数和范围。
|
||||
|
||||
涉及桌面到 Agent 的 Runtime、安装、workspace、模型 bridge 或生命周期时,按
|
||||
`AGENTS.md` 在共享 Linux x64 Host 使用当前源码验证。覆盖面应对应改动,包括断开、重连、
|
||||
取消或重启;远程临时目录必须专用,验证后清理。未能连接 Host 时记录为验证阻塞,不能
|
||||
用单测或旧发布包替代当前源码证据。
|
||||
|
||||
性能改动先取得基线,至少按权威验收矩阵记录 offered load、排队、吞吐、排空、资源高水位、
|
||||
Main/Renderer 指标和样本条件。功能测试通过不等于并发预算通过;未测平台和缺失指标保留
|
||||
为缺口。
|
||||
|
||||
## 文档与重复设计
|
||||
|
||||
代码行为改变后,检查受影响的架构、功能、质量、构建和运维文档,更新真正的 owner、
|
||||
顺序、限制和验证证据。每项事实只保留一个权威定义:本文件写维护决策,架构文档写
|
||||
职责与边界,性能原则写数值,验收文档写场景和证据,功能 README 或 progress 写功能
|
||||
行为和状态。其他地方使用链接,不复制完整表格、状态机、预算或验收结果。
|
||||
|
||||
新增机制前先搜索同类名称、队列、owner、transport、shutdown 和测试。若现有机制只缺
|
||||
一个操作、一个容量检查或一个清理分支,扩展它并补测试;只有现有 owner 无法表达真实
|
||||
边界时才提出新设计,并在对应架构文档中记录问题、取舍和验证条件。未提交的实验代码、
|
||||
测试夹具或接口存在,不等于生产路径已经接入。
|
||||
|
||||
## 允许与禁止的模式
|
||||
|
||||
| 允许 | 禁止 |
|
||||
| --- | --- |
|
||||
| 在已有 storage domain 增加命名 operation,并由 owner 完成事务 | 在 Main 新建同一 SQLite 的第二个连接、同步回退或隐藏 writer |
|
||||
| 先在 Main 完成模型/解析,再把不可变 DTO 交给 owner 原子发布 | 在 SQLite 事务内 `await` 模型、网络、IPC 或文件回调 |
|
||||
| `storage.call('assistant', 'appendRemoteTaskEventsBatch', [batch], { signal })`,提交后再 ACK | 暴露 `executeSql`、任意 repository proxy,或把 live repository/callback 放进 DTO |
|
||||
| 使用同一 output handle 进行预览、分页、历史引用和 release | 每个工具结果写一个临时全文,再复制到 history 或用最终回答代替全文 |
|
||||
| child 退出确认后删除创建者拥有的 launch material | 按目录前缀、年龄或路径位置删除未知归属的用户文件 |
|
||||
| 取消在途请求并等待 settlement,再归还容量 | 调用方停止等待后立即释放槽位,或取消一个请求时杀共享宿主 |
|
||||
| 用真实 owner、隔离数据、确定性模型和生产 UI 路径验证 | 只验证 schema、mock bridge、旧包或 utility ready 就宣称功能完成 |
|
||||
| 更新一个权威文档,其他文档链接它 | 在多个文档复制同一预算、状态机、所有权表并分别维护 |
|
||||
@@ -802,7 +802,7 @@ npx eslint scripts/review-algorithm.mjs scripts/review-algorithm.test.mjs script
|
||||
|
||||
### 2026-09-24 模型阶段超时、并发与设计边界
|
||||
|
||||
- 本次静态核对当前新代码:报告 `heartbeatReportTimeoutSeconds`、监督 `supervisorOrganizeTimeoutSeconds` 已接入共享校验、设置存储、App/设置表单及 Main 摘要器;整数 30..600 秒、默认 240。报告在等待池前冻结值,监督在摘要器入池前读取值,均在获槽及 Runtime 解析后才计时。完整控制及未覆盖环节见[控制清单](./review-scheduling-design.md#当前超时与调度控制清单已实现)。
|
||||
- 本次静态核对当前新代码:报告 `heartbeatReportTimeoutSeconds`、监督 `supervisorOrganizeTimeoutSeconds` 已接入共享校验、设置存储、App/设置表单及 Main 摘要器;整数 30..600 秒、默认 240。报告在等待池前冻结值,监督在摘要器入池前读取值,均在获槽及 Runtime 解析后才计时。该次记录对应的完整控制及未覆盖环节见[初稿控制清单](./review-scheduling-design.md#初稿时超时与调度控制清单历史截面),不代表当前实现。
|
||||
- `supervisorModelConcurrency` 已接入同一设置链路,整数 1..4、默认 1;报告与监督共用 FIFO 池,普通聊天排除,降限不终止在途项。报告获槽后才领取,每次到期领取最多一条;租约为 `max(300, 报告超时 + 60)` 秒,实际请求开始前按运行 ID、owner、attempt、claimed 和未过期条件校验并刷新。报告提交或无变化后先释放槽位,再进入下游监督。尚无提供商全局并发、RPM、TPM 或监督层 Retry-After 控制。
|
||||
- 本轮协作提供的新增验证记录:24 项并发相关测试、33 项租约相关测试及 typecheck、lint 通过。本次文档修订仅核对实现,没有重新执行这些测试;记录未附精确过滤命令,不补造命令或与此前批次相加。
|
||||
- 实现代理报告:87 项测试、App/IPC 聚焦用例、另 19 项取消测试,以及 typecheck、lint 通过。本次文档修订未复跑这些命令,不将数量相加当作独立用例总数,也不推断全量测试已通过。
|
||||
|
||||
@@ -98,9 +98,11 @@ schema 47 增加运行配置、来源清单、成功批次、导航节点四张
|
||||
|
||||
目标是不再静默裁剪,把单次上限改成分页、分块和暂缓派发的依据,直至冻结范围内全部项目、会话、消息及其他入选来源处理完毕。模型提取仍可能遗漏原文含义,但已产出的叶子事实、事件、冲突及来源必须完整保留、可分页读取。摘要只用于导航,不能替代叶子结果;“原文已处理”也不等于模型识别了全部事实。
|
||||
|
||||
### 当前超时与调度控制清单(已实现)
|
||||
### 初稿时超时与调度控制清单(历史截面)
|
||||
|
||||
以下是当前代码事实,不是后文拟议的多层调度预算。设置暴露两个阶段超时和一个监督并发数。
|
||||
以下表格及其后说明保留初稿时的实现截面,不定义当前生产行为或本次存储迁移需要新增的机制。
|
||||
其中“无用户取消入口”“收齐后检查 100KB”和原心跳报告交接已被后续实现替代。
|
||||
现状以本页第 0 节及[监督者技术设计](./technical-design.md)为准,复用既有回顾准入、模型池和暂停/继续机制,不据此再建一套调度器。
|
||||
|
||||
| 控制 | 实际值、读取时点与范围 | 代码依据与边界 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -21,7 +21,7 @@ FR-S1:将现有智能心跳入口整合、升级为监督者,不增加另一
|
||||
|
||||
FR-S2:应用包含“工作回顾 / 故事线图谱 / 自动监督 / 活动记录 / 设置”五个主视图。工作回顾提供统一监督结果阅读、历史选择和手动新回顾,结果显示自身范围与日期;自动监督集中展示计划状态、范围、周期及下次执行时间,以及运行概览、指标、趋势、自动监督报告、待处理记忆与行动建议和心跳审计,创建和编辑使用同一份 Modal 表单;活动记录展示统一实际执行、进度、详情及结果入口,支持按计划 ID 筛选;设置管理模型选择、超时、并发、分页、分批及单次响应容量,分类页签见 UI 设计。计划保存失败保留草稿,关闭未保存修改须确认;编辑已暂停计划保持暂停,创建须显式保存并启用。原有运行记录及应用、存储标识保留。
|
||||
|
||||
当前设置还支持分别调整报告与监督整理的模型超时,以及两者共用的监督并发数。并发设置不限制普通聊天,也不保证避免上游限流;排队不消耗模型阶段时限,降低并发不打断在途请求。取值及生效规则由[当前控制清单](./review-scheduling-design.md#当前超时与调度控制清单已实现)定义。
|
||||
当前设置支持监督整理模型超时及监督模型并发数。并发设置不限制普通聊天,也不保证避免上游限流;排队不消耗模型阶段时限,降低并发不打断在途请求。取值、生效规则及历史报告设置的保留边界由[模型阶段调度](./technical-design.md#模型阶段超时与调度)定义。
|
||||
|
||||
FR-S13:监督者的“设置 → 模型”可选择跟随应用默认文本模型或已有文本模型连接,复用连接及其凭据。回顾执行中修改设置不切换模型;生效时点、继续和重试规则见[监督者模型选择](./logic-design.md#监督者模型选择)。
|
||||
|
||||
|
||||
@@ -129,7 +129,9 @@ Modal 按[共享表单 Modal 规则](../../../UI-DESIGN.md#615-表单-modal)复
|
||||
|
||||
时间螺旋使用所选回顾的时间区间与完整事件集合,半径和区间着色规则见 [3D 密度规则](../story-graph/3d-concept.md)。当前故事成员按精确事件 ID 与结果求交;其余事件按来源确定的项目进入“当前无故事归属”分组,项目未知时显示“项目范围不可用”。图例说明“本次回顾事件按当前故事归属分组;未保存当时的故事归属”。故事与经验列表仍提供该范围的当前完整记录,供查看和调整。
|
||||
|
||||
根视图直接显示事件点,同一主故事的邻近事件按画布密度聚合并显示数量,未归属点使用中性色。事件和经验从左侧列表选择,画布不重复提供这两种选择器。保留一个层级菜单:左侧列表选择记录并打开详情,标题区层级菜单改变画布中的项目、功能或子线索层级。菜单与面包屑均可用键盘操作。默认斜俯视,不显示视角按钮、菜单或页签;自由拖动、平移、滚轮、方向键、数字键和 Escape 返回上层操作保留。
|
||||
根视图直接显示事件点,同一主故事的邻近事件按画布密度聚合并显示数量,未归属点使用中性色。事件和经验可从左侧列表或画布标记选择,画布不重复提供这两种选择器。保留一个层级菜单:左侧列表选择记录并打开详情,标题区层级菜单改变画布中的项目、功能或子线索层级。菜单与面包屑均可用键盘操作。无选择时默认斜俯视,不显示视角按钮、菜单或页签;自由拖动、平移、滚轮、方向键、数字键和 Escape 返回上层操作保留。
|
||||
|
||||
时间螺旋选中故事、事件或经验后,相机沿较短方向旋转并平移到对应位置,保留当前缩放;缩放超出 0.8~1.4 时收回到该区间。列表选择的目标不在当前层级时,切换到能显示该目标的层级;经验须在本次回顾内有跨木片的形成与应用连接才能定位,没有对应几何对象的记录仍可查看详情。选中的经验不受默认六条连接的显示上限限制。鼠标拖动、滚轮和方向键立即接管相机,后续相同选择和同内容刷新不重新定位。启用减少动态效果时直接定位,离开视图时取消未完成动画。
|
||||
|
||||
关联来源详情属于会话时,显示“打开会话”按钮,使用来源保存的会话 ID 跳转并切换到所属项目;不发送消息或启动回顾。另提供“继续讨论”,使用上述可编辑正文预览,点击发送后提交到来源会话并打开它。详情继续展示整理时保存的原文快照。会话已删除或读取失败时,沿用应用会话导航的提示;其他来源类型不显示此按钮。
|
||||
|
||||
|
||||
@@ -116,13 +116,11 @@ confidence 改为可选,保持本地返回原值;多块内容组成逐页 se
|
||||
original.<ext>
|
||||
documents/<attachmentId>/
|
||||
original.<ext>
|
||||
parsed.md
|
||||
manifest.json
|
||||
images/<imageId>.<ext>
|
||||
knowledge/<kbId>/<sourceId>/
|
||||
<managed-original-files>
|
||||
knowledge-assets/<kbId>/<documentId>/
|
||||
parsed.md
|
||||
manifest.json
|
||||
images/<imageId>.<ext>
|
||||
temp/document-parsing/<operationId>/
|
||||
@@ -155,7 +153,7 @@ confidence 改为可选,保持本地返回原值;多块内容组成逐页 se
|
||||
也需验证同一规则。采用暂存、现有事务和按数据库引用核对清理,不新增恢复引擎。
|
||||
|
||||
测试解析使用任务临时目录,结束预览且无有效引用后清理。知识库正文及 chunks 继续存数据库,
|
||||
`knowledge-assets` 的 `parsed.md` 为解析正文文件,随同一次结果提交保持一致,不独立修改
|
||||
`knowledge-assets` 的 `manifest.json` 保存解析正文,随同一次结果提交保持一致,不独立修改
|
||||
或替代数据库中的检索内容。图片归知识库文档所有,不依赖聊天存活;managed 与 reference
|
||||
派生文件均存该目录,reference 外部原件保持原路径,现有 managed 原件目录不迁移。
|
||||
重解析或同步先在本次暂存目录完成新结果与必要资源校验,成功提交替换后清理无引用旧资源;
|
||||
|
||||
@@ -44,17 +44,21 @@ PPTX 回退提示按实际 OCR 来源显示,并在图片处理失败时包含
|
||||
conversation-assets/<conversationId>/
|
||||
images/<resourceId>/original.<ext>
|
||||
documents/<resourceId>/original.<ext>
|
||||
documents/<resourceId>/parsed.md
|
||||
documents/<resourceId>/manifest.json
|
||||
documents/<resourceId>/images/<imageId>
|
||||
knowledge/<libraryId>/<sourceId>/<managed-original>
|
||||
knowledge-assets/<libraryId>/<documentId>/<resultId>/
|
||||
parsed.md
|
||||
manifest.json
|
||||
images/<imageId>
|
||||
temp/document-parsing/<resourceId>/
|
||||
```
|
||||
|
||||
S04 (2026-10-05): new results store parsed content in `manifest.json` only;
|
||||
`parsed.md` is no longer written or copied. Released manifests remain readable,
|
||||
including directories containing the old duplicate. Async storage-host integration
|
||||
and its remaining composition work are specified in
|
||||
[Desktop File Storage Integration](../../architecture/desktop-storage-files.md).
|
||||
|
||||
会话资产的 `request.json` 保存已转换的请求内容,用于恢复内存 context;原件字节单独保存,
|
||||
缩放和 JPEG 请求副本不覆盖原图。提取图片文件使用 UUID 无扩展名,MIME 在清单记录。
|
||||
图片显式 OCR 后生成解析文件。切回原图保留已有结果,并重新进入图片能力校验。
|
||||
|
||||
@@ -10,7 +10,7 @@ Smart Heartbeat 已提供按计划或手动执行的有界回顾,生成报告
|
||||
|
||||
## 文档导航
|
||||
|
||||
当前监督者设置已接入报告与监督整理两项模型阶段超时(30 至 600 秒,默认 240)及共享监督并发数(1 至 4,默认 1),模型超时不包含排队、采集、保存或整次执行期限。报告先获槽再领取,实际请求开始前校验并刷新租约,报告提交或无变化后释放槽位再进入监督。普通聊天不入监督池,尚无跨聊天与监督的提供商全局并发或 RPM/TPM 限制。租约、固定传输时限及重试与用户超时分开,见[控制清单](../conversation-supervision/review-scheduling-design.md#当前超时与调度控制清单已实现)。完整分页与持久化续跑仍为设计,不能把现有增量检查称为完整调度器。
|
||||
当前心跳将到期计划交给监督回顾,不再额外生成独立报告。监督整理使用模型阶段超时和共享监督并发池,普通聊天不入池;模型超时不包含排队、采集和保存,也不是整次回顾期限。分页、批次持久化和暂停/继续已接入生产路径,范围及剩余限制见[生产接线](../conversation-supervision/review-scheduling-design.md#0-生产接线与剩余边界)。超时、并发、重试及保留的历史报告设置由[模型阶段调度](../conversation-supervision/technical-design.md#模型阶段超时与调度)定义,不据初稿控制表另建调度器。
|
||||
|
||||
监督者“活动记录”页签同时显示手动回顾和心跳执行,将新心跳报告与下游监督按运行 ID 合并,保留各阶段真实状态。心跳审计、运行概览、指标、趋势、自动监督报告阅读和建议处理与计划列表同在自动监督页;工作回顾只读取统一监督结果,设置只保留配置。当前布局见[监督者 UI 设计](../conversation-supervision/ui-design.md#应用入口)。心跳审计继续读取报告自身状态,完整执行状态以[活动规则](../conversation-supervision/logic-design.md#活动记录)为准。
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ type HeartbeatScope =
|
||||
- Main 在创建、编辑、列出和执行时验证 Project 归属。
|
||||
- Global 读取所有 Project 的有界会话和任务,但长期记忆仍只读取 Global。
|
||||
- 指定 Project 读取选中项目的有界会话和任务,以及 Global 与选中项目记忆。
|
||||
- 现有输入条数、字符预算、输出大小、超时、重试、租约和工具禁用边界继续生效。报告与监督整理共用监督专用并发池,普通聊天不入池;具体设置、租约和未实现的限流边界见[当前控制清单](../conversation-supervision/review-scheduling-design.md#当前超时与调度控制清单已实现)。
|
||||
- 心跳交接、监督分页与输出容量、模型阶段超时、重试和工具禁用边界以[生产接线](../conversation-supervision/review-scheduling-design.md#0-生产接线与剩余边界)及[模型阶段调度](../conversation-supervision/technical-design.md#模型阶段超时与调度)为准。监督模型池不限制普通聊天,不将历史独立报告阶段作为当前必须执行的路径。
|
||||
- 多项目汇总后统一应用上限,不能按项目倍增预算。
|
||||
- 心跳结果默认不在系统通知中暴露私人正文。
|
||||
- 数据迁移必须使用 SQLite 事务,保留外键、级联删除和现有历史。
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
| 文档 | 职责 |
|
||||
| --- | --- |
|
||||
| [Electron UI 自动化测试规范](./electron-ui-automation.md) | 测试分层、当前源码启动、视觉与原生交互取证、真实调用计数、脱敏报告和清理 |
|
||||
| [桌面并发架构验收](./concurrent-desktop-acceptance.md) | 一次完整架构交付的多项目/会话/子任务负载、计时口径、正确性断言、平台与验收记录要求;当前候选仍有未完成项 |
|
||||
| [复发缺陷与防复发规则](./recurring-defects.md) | 已重复出现的缺陷类型、对应的共享机制和自动发现测试 |
|
||||
| [开发与构建](../../BUILD.md#质量验证) | 依赖、测试、类型检查、Lint 和构建命令 |
|
||||
| [UI 设计系统](../../UI-DESIGN.md#155-验收) | 全局视觉、交互和响应式验收要求 |
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
# 桌面并发架构验收
|
||||
|
||||
## 状态与职责
|
||||
|
||||
2026-10-05,设计基线为 `2be57492`。本文定义一次完整架构交付的验证要求,测试脚本与结果尚未完成。
|
||||
目标架构见[桌面存储与并发执行](../architecture/desktop-storage-direction.md),规范和数值预算以
|
||||
[性能原则](../architecture/performance-principles.md)为唯一依据。既有 KISS 测量只作为基线证据,不能替代本矩阵。
|
||||
|
||||
验收对象是同一候选中的完整产品路径,不单独验收“接口改成 Promise”“Worker 可调用”或“单会话很快”。
|
||||
所有必测场景完成前,不标记 PERF-15/PERF-16 为完成;未测平台及指标明确列出,不以跳过视为通过。
|
||||
|
||||
## 发布门槛与目标差距
|
||||
|
||||
2026-10-06 起按两类判定,沿用既有“历史回归上限”与“性能预算”的分层,不新增判定体系:
|
||||
|
||||
| 类型 | 内容 | 发布判定 |
|
||||
| --- | --- | --- |
|
||||
| 正确性与稳定性 | A01–A10;请求排空、无静默丢弃、提交后 ACK、取消结算、故障不传染、资源回落、进程退出后可恢复 | **必须全部通过**,不能用“比旧版好”替代 |
|
||||
| 性能对照 | 同机、同数据、同负载,与改造前提交交替三轮;含单会话简单操作和后台吞吐 | **不得退化**:候选不差于基线的重复测量波动范围 |
|
||||
| 性能预算 | 性能原则第 1 节的绝对数值 | 未达到时记录为目标差距,不阻断本次发布 |
|
||||
|
||||
“不得退化”同时覆盖单会话路径。存储迁到独立进程后多一次 IPC,单个简单操作最可能变慢,只测并发场景会漏掉这种退化。
|
||||
后台吞吐、回顾发布和事件提交也要对照,界面流畅但后台明显变慢不算改善。
|
||||
|
||||
## 固定负载
|
||||
|
||||
以下为验收档位,不是已验证的容量承诺,也不改变产品并发限制。当前产品若不能达到某档位,记录原因并保留未满足项。
|
||||
使用本地确定性模型与隔离目录,每个输入、事件和工具结果有稳定标识,能够逐项验证顺序及重放结果。
|
||||
不得用真实提供商的网络速度作为应用性能基准。
|
||||
本地临时库、日志和源码测试产物遵守仓库 [AGENTS 临时文件规则](../../AGENTS.md#local-temporary-files),
|
||||
使用仓库根目录 `temp/` 的任务子目录并在关闭资源后清理;可复用夹具提交到 `tests/` 或 `scripts/`,
|
||||
正式验收摘要记录在文档中,不依赖唯一一份临时结果。远程临时目录沿用 Host 专用规则。
|
||||
|
||||
| 场景 | 项目/并行会话/同时活动子任务 | 必须同时执行的用户动作 |
|
||||
| --- | --- | --- |
|
||||
| C01 日常并行 | 3/4/8,子任务为全场景总数 | 持续打字、跨项目切换、查看来源和工具详情、首次进入监督者 |
|
||||
| C02 并发压力 | 4/8/16,子任务为全场景总数 | 滚动长历史、搜索、切换缓存会话、取消其中一个父任务,其他会话继续 |
|
||||
| C03 混合存储 | C01 加 100 批次回顾发布、知识导入和检索 | 前台输入与保存,打开图谱,观察远程事件提交/ACK;各操作必须真实重叠 |
|
||||
| C04 运行时故障隔离 | C01 中挂起一个启动、模型路由准备或清理 | 查询其他任务、取消故障请求、继续健康 peer;覆盖本地及受影响的远程路径 |
|
||||
| C05 存储故障与饱和 | C01 中注入读 Worker 失败、队列达到上限、存储进程退出 | 页面报错与重试,已提交结果可重读,未确认写入不盲目重放,UI 仍可响应 |
|
||||
| C06 持续生命周期 | C02 连续 30 分钟,每轮完成、关闭并重开任务/会话 | 保持草稿、展开、焦点及滚动状态,观察队列、缓存、订阅和所属进程回落 |
|
||||
| C07 冷热进入 | 新进程首次打开及同进程暖返回,分别在空闲和 C01 下测量 | 输入框、监督概览、图谱、历史选择、来源正文分开记录可用时刻 |
|
||||
|
||||
默认数据为 300 个已保存会话、每个 20 条消息,另加 2,000 条消息的长会话;压力数据改为 10,000 条长会话消息。
|
||||
监督数据沿用可复现样本:20 个结果、40,000 条消息、8,000 事件/来源、400 故事、800 经验;消息及来源正文各 2,048 ASCII 字节。
|
||||
回顾发布为 100 批、每批 20 来源,知识检索保留既有 5,000/20,000/50,000 文本块样本。各场景只装入相关数据,不把互不相关的大样本无条件叠加。
|
||||
|
||||
活动流初始使用每会话每 16 ms 一个文本片段、每父会话每秒一个有界工具/子任务更新;工具输出固定为 4 KiB,实际数量、字节数和时间表写入 fixture。
|
||||
每个会话的首次响应、持续流、完成和清理都要经过生产路径。调度抖动时记录实际发送率及迟到分布,不能丢弃片段以保持设定速率。
|
||||
若现有 Runtime 会自然合并事件,分别记录生产与消费数量,按可还原内容验证等价。
|
||||
|
||||
## 计时和判定
|
||||
|
||||
| 指标 | 起止与用途 |
|
||||
| --- | --- |
|
||||
| 输入/点击反馈 | 原生输入事件至目标反馈后的帧机会;记录方法局限,不把两帧等待称为纯渲染时间 |
|
||||
| 内容可用 | 请求发起至所需正文/图谱/来源提交到界面;加载占位出现不能替代该指标 |
|
||||
| Main 阻塞及 p99 延迟 | 连续事件循环采样,排除夹具生成和数据库初始化;与 Renderer 长任务分别记录 |
|
||||
| 存储排队 | 入队至执行开始,记录操作类型、项目/会话归属及队列深度,不记录用户正文 |
|
||||
| 执行/提交 | 存储执行开始至结果或事务提交;完成响应不能早于承诺的提交点 |
|
||||
| 取消/清理 | 分别记录请求取消、执行结算、资源释放;UI 清除等待不等于清理完成 |
|
||||
| 吞吐和积压 | 固定 offered load 下完成数、字节数、队列高水位及输入停止后的排空时间 |
|
||||
| 内存与进程 | 全应用及各实际 PID 的工作集/堆;记录缓存条目、监听器、连接及 worker 数 |
|
||||
|
||||
标准机和 Renderer 4× 降速分别报告,UI 与 Main 的绝对预算引用性能原则第 1 节。
|
||||
Renderer 降速不等于模拟慢磁盘、慢 Main 或真实低配整机,不能从该结果推断它们合格。
|
||||
原有 `perf:check` 回归上限比规范宽松;即使上限检查通过,违反规范仍须报告失败或明确的未满足项。
|
||||
|
||||
存储排队、吞吐、内容就绪和取消清理不得被 UI 流畅掩盖。每份 fixture 在测试实现时固定最大等待/排空期限,
|
||||
使用既有产品期限或改造前重复测量支持的验收值,先记录依据再运行候选;禁止测完候选后放宽期限。
|
||||
没有期限、样本不足或未采集的必需指标不能记为通过。固定输入下吞吐不得低于基线的重复测量波动范围,
|
||||
队列不得持续增长,有限后台任务在持续前台负载下必须完成。容量溢出按设计等待或明确失败,不能静默丢事件。
|
||||
|
||||
每版本至少三轮交替运行,基线从独立 worktree 获得。交互延迟每轮每个指标至少 100 个有效样本;
|
||||
首次进入通过独立启动样本报告各次值,不用三次启动估计 p95。持续负载场景先预热 30 秒,测量至少 120 秒;
|
||||
C06 单独执行完整 30 分钟。记录 OS、CPU、内存、磁盘、Electron/Node、提交、数据校验值、并发、缓存状态和后台负载。
|
||||
|
||||
无新增数据的重复生命周期测试中,逻辑缓存/订阅/连接应回到约定存续范围,队列排空后为零,结束的所属子进程退出。
|
||||
RSS 未立即回落不能单独判为泄漏;需结合存活对象、资源计数和重复轮次趋势。不能通过杀掉健康共享 Runtime 达到清零。
|
||||
|
||||
## 正确性断言
|
||||
|
||||
| ID | 断言 | 对应架构要求 |
|
||||
| --- | --- | --- |
|
||||
| A01 | Main 不直接或间接打开业务 SQLite;正常、失败、饱和、维护及退出路径均无同步回退 | SA-01、SA-06 |
|
||||
| A02 | 同会话事件不丢、不重、不乱序;不同项目/会话/子任务的结果、取消和凭据不串用 | SA-03、SA-05 |
|
||||
| A03 | 远程事件、checkpoint 提交后才 ACK;崩溃后重放去重;发件箱与模型账本不重复执行 | SA-02、SA-05 |
|
||||
| A04 | 事务失败整体回滚,提交后立即读取可见;回顾结果与 checkpoint 同步发布 | SA-02、SA-05 |
|
||||
| A05 | 取消排队请求不执行,取消在途请求等待实际结算;迟到结果不污染新请求,健康 peer 不受影响 | SA-03、SA-06 |
|
||||
| A06 | 关闭前先停止新写入、排空必要写入、关闭连接;替换存储进程前观察旧进程退出 | SA-06 |
|
||||
| A07 | 多项目切换不丢草稿、展开、选区、焦点和滚动;后台任务继续,切回不全量重建历史 | SA-04、SA-05 |
|
||||
| A08 | UI DOM 随视口有界;缓存和队列有清理;未访问正文不随摘要列表整包返回 | SA-04 |
|
||||
| A09 | 已发布数据库升级保留用户内容、ID、顺序、引用、去重与落盘语义,不静默换空库 | SA-02、SA-05、SA-06 |
|
||||
| A10 | GoodBuddy 中转无非必要落盘;完整输出复用唯一正文,分页和释放正确,必要临时文件及历史残留按归属清理 | SA-04、SA-05、SA-08 |
|
||||
|
||||
故障注入覆盖提交前、提交后但响应前、初始化期间和退出期间。使用现有业务 ID 查询实际结果;
|
||||
不得为了让测试容易恢复而引入新的持久化 receipt 或盲目重放。强杀共享存储进程仅用于隔离测试中的服务故障场景。
|
||||
|
||||
## 输出与临时资源验收
|
||||
|
||||
按[源码调查](../architecture/runtime-storage.md#output-and-temporary-file-investigation)选取生产入口,
|
||||
在 C01/C02/C06 中记录 GoodBuddy 创建的文件数、写入字节、峰值保留字节、清理后残留及内存高水位。
|
||||
区分正式保存、必要启动文件和中转写入;结果只计 GoodBuddy 所有的文件,不把第三方内部缓存归为产品临时副本。
|
||||
沿用三轮前后对照,不以目录改名或文件移到数据库目录证明减少写入。
|
||||
|
||||
| 覆盖 | 必须验证的结果 |
|
||||
| --- | --- |
|
||||
| 普通 MCP、短命令输出、空 stderr、短搜索/子任务输出 | 中转不创建输出临时文件;需要保存的正常消息/事件仍持久化,MCP SDK 实际 transport 路径纳入检查 |
|
||||
| 超出预览的输出 | 从现有工具到下一次模型 `output_read` 的生产链逐字节还原,覆盖 UTF-8、JSON 转义、游标、EOF、stdout/stderr、失败与取消;只保留一份所属正文,无 Temp 全文再复制 |
|
||||
| 并发与生命周期 | 保留现有会话归属检查,不能读取其他会话输出;多路输出内存有界,释放调用资源不删除仍被历史/分支引用的内容 |
|
||||
| 数据等价 | 分别核对模型收到的内容、工具事件预览和历史内容;不能用最终回答等价替代工具全文,也不新增整份事件正文副本 |
|
||||
| 文档解析 | 不再新写/复制重复 `parsed.md`,manifest 内容、原文件、图片及附件采用/复制/重开仍正常;旧数据可读 |
|
||||
| 必要启动文件 | 配置/插件加载正常,创建中失败能清理所属半成品,子进程及句柄关闭后再删除;失败不误删预先存在的同名文件 |
|
||||
| 升级与再次启动 | 隔离夹具覆盖旧版失效目录、同版崩溃目录、仍活跃的子进程、归属不明目录和删除失败;只回收确认失效者,失败下次可重试,启动预算不退化 |
|
||||
|
||||
复用 `paged-output-store.test.ts`、`direct-model-process-service.test.ts`、
|
||||
`direct-model-ripgrep.test.ts`、`direct-model-subagent-service.test.ts` 和
|
||||
`model-runtime.test.ts` 中已有分页与模型下一轮请求断言,并扩展文档存储、附件及 Runtime 生命周期测试。
|
||||
远程路径单独验证事件映射和提交后 ACK,不声称本地分页测试覆盖远程全文。
|
||||
无需真实模型即可确定的字节、文件和清理断言使用固定输出;受影响的 Runtime 再按既有 Host 规则验证。
|
||||
|
||||
交付记录列出删除的旧 spool 创建入口、复用的正式内容入口、仍必要的文件及理由、实际残留与未能确认归属的旧目录。
|
||||
本次文档调查未执行上述验收,没有磁盘节省或性能改善的实测结论。
|
||||
|
||||
## 平台与生产路径
|
||||
|
||||
性能主对照使用固定参考机。Windows、macOS、Linux x64/arm64 的原生验证检查进程启动、SQLite 可用性、
|
||||
文件路径及关闭、升级和重启;没有硬件性能基线的平台只报告功能结果,不与参考机数字混算。
|
||||
受影响的远程事件持久化、ACK、binding 和 Runtime 生命周期在共享 Linux x64 Host 开发时验收,
|
||||
采用专用目录和既有固定身份;真实模型调用数单独报告,不混入无外部请求的性能基准。
|
||||
|
||||
正常 UI → Preload → Main → 存储进程 → SQLite 的完整路径必须至少覆盖 C01、C03、C07;
|
||||
仅组件 mock bridge、独立 SQL 或进程握手均不能替代。原生客户端 ledger 的创建、失败、不确定结果及关闭删除也必须覆盖。
|
||||
|
||||
生产构建、各平台包内入口及进程启动通过既有 CI 和[发布手册](../development/release-runbook.md)验证。
|
||||
本规范不授权发布准备期间本地打包或启动 portable 原始用户目录;源码测试使用隔离数据。
|
||||
|
||||
## 自动检查与交付记录
|
||||
|
||||
### 复用既有验收入口
|
||||
|
||||
扩展下表中的脚本和测试,不新增一套并行性能框架。现有能力与缺口均来自源码核对,不能把“已有测试文件”等同于已覆盖新语义。
|
||||
|
||||
| 场景/断言 | 既有入口 | 本次需要补齐的内容 |
|
||||
| --- | --- | --- |
|
||||
| C01/C02/C06/C07 | `build/run-app-perf.cjs`、`tests/support/app-perf-driver.mjs`、`build/run-startup-perf.cjs` | 多项目生产路由、并行流和子任务、冷热功能进入、资源回落;为 30 分钟场景显式设置匹配的运行期限,保留原场景期限 |
|
||||
| C03/A04 | `build/run-main-perf.cjs`、`build/run-knowledge-perf.cjs`、`build/run-write-perf.cjs` | 保留既有数据,新增存储客户端和真实重叠负载;与完整 App 结果对应,不把分开测量冒充同时执行 |
|
||||
| C04/A02/A03 | `remote-event-batcher.test.ts`、`ipc.test.ts`、`runtime-acp-backend.test.ts`、`runtime-controller.test.ts` | 异步提交延迟/拒绝时禁止提前通知及 ACK;覆盖交互、后台和恢复三个入口,open/prepare/close 清理时健康 peer 仍可操作 |
|
||||
| C05/C06/A05/A06 | `readonly-query-reader.test.ts`、`supervision-model-pool.test.ts`、`subagent-scheduler.test.ts`、`shutdown.test.ts` | 取消后真实容量、队列饱和、进程丢失和等待退出;升级预检 SQL 迁出,checkpoint 终止得到确认 |
|
||||
| A04/A07/A09 | `supervision-worker.test.ts`、`supervision-ui-reads.test.ts`、`supervisor-layout.electron.test.ts`、`message-expansion.electron.test.ts` | 保留事务、来源及交互断言;完整 App 扩展 UI 到真实存储链,不能只用组件模拟 bridge |
|
||||
| SA-07 | `eslint.config.js`、`build/check-perf-thresholds.cjs`、`tests/perf-thresholds.test.ts`、已有 Desktop/Agent workflow | 间接打开点、白名单基线、负载元数据、缺失指标及并发场景;预算与历史回归上限分别报告 |
|
||||
|
||||
既有部分脚本默认使用系统临时目录,复用时按 AGENTS 统一到仓库 `temp/`;新增的可重用夹具进入 `tests/` 或 `scripts/`。
|
||||
当前 App 基准默认期限不足 30 分钟,C06 需要独立场景时长参数,不能通过放宽所有测试超时处理。
|
||||
现有阈值检查未验证全部负载规模,需校验项目、会话、子任务、数据量及降速条件,缺少或不匹配时不能产生合格结果。
|
||||
|
||||
### 替代路径核验
|
||||
|
||||
A01/A03/A05/A06 还必须逐项确认:Main 升级预检与间接 ledger 打开已迁出,生产 `readWithFallback` 同步回退已删除,
|
||||
监督清单不再持有独立业务写入权限,远程 `flush` 等待提交且失败可传播,原生客户端先完成 ledger claim 再发模型请求,
|
||||
关闭 ledger 后才删所属目录。检查外部 live repository 工厂、重复客户端、重复队列和旧 shutdown 回调无生产调用。
|
||||
保留合法的仓储同步单测、原子事务内部读取和远端数据库实现;不以删除测试或已发布兼容代码实现“清零”。
|
||||
|
||||
### 最终记录
|
||||
|
||||
- 静态检查覆盖 `DatabaseSync`、同步文件 API、`openPrivateSqliteDatabase` 及桌面间接构造点;
|
||||
允许的存储宿主/测试/远端代码按执行环境列出,不能整片豁免 `src/main`。
|
||||
- 白名单和 App 上限与已提交基线比较;迁出后删除例外,不因换文件名增加例外。
|
||||
- 普通 CI 运行契约、顺序、取消、资源释放和生产路径测试;可复现性能执行环境运行上述关键并发基准,
|
||||
在合并/发布候选检查中提供必需结果。没有合适 runner 时,记录为验收缺口,不能默认通过。
|
||||
- 最终同一候选运行 `npm test`、`npm run typecheck`、`npm run lint`,并核对 native CI/Host 与性能报告对应的源码。
|
||||
- 验收记录列出每个 C/A 编号的结果、命令、日志位置、样本数、原始数据和未测项;单独报告低配、故障注入及真实模型结果。
|
||||
|
||||
截至本文编写,只有历史局部测量可引用,尚无本矩阵的全项通过记录。待实现完成后新增具体日期的验收报告,
|
||||
保留失败结果和复测理由,不把目标架构文档改写成未经验证的进度证明。
|
||||
@@ -0,0 +1,306 @@
|
||||
# 存储架构升级实施与验收记录
|
||||
|
||||
## 交付范围
|
||||
|
||||
本记录跟踪 [架构设计](../architecture/desktop-storage-direction.md) 的 SA-01 至 SA-08,并汇总
|
||||
[Assistant 消费者](../architecture/assistant-storage-consumers.md)、[Knowledge 迁移](../architecture/desktop-storage-knowledge.md)、
|
||||
[文件存储](../architecture/desktop-storage-files.md) 和 [Runtime 存储](../architecture/desktop-storage-runtime-integration.md) 的当前实现。
|
||||
同一候选已完成多项桌面数据库所有权迁移、异步生产调用、输出唯一存储和临时资源清理。
|
||||
内部工作按依赖并行执行,最终整体交付。接口、进程握手或分组测试通过均不代表产品完成。
|
||||
保留现有未提交文档及仓库规则修改,不自动提交或发布。
|
||||
|
||||
## 实施分工
|
||||
|
||||
| 工作包 | 实施内容及替代入口 | 验证要求 | 当前状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| S01 存储宿主 | 复用 reader 请求关联与 utilityProcess;宿主打开 Assistant/Knowledge,负责升级、查询分派、提交和退出 | 真实 Electron 进程、升级失败重试、取消结算、提交后读取、退出排空 | 已实现并接入生产组合;全量验收未完成 |
|
||||
| S02 Assistant 调用 | IPC、心跳、监督、任务、笔记、通道、审计和工具接口改用异步领域操作;回调事务留在宿主 | 先写后读、回顾原子发布、项目隔离、暂停/恢复、错误传播 | 已接入生产组合;完整并发验收未完成 |
|
||||
| S03 Knowledge 调用 | 数据库与 external repository 归宿主;解析、模型与凭据保留原所属;发布 DTO 替代事务回调 | 导入、检索、重建、embedding、删除和取消 | 已接入生产组合;完整 App 和并发验收未完成 |
|
||||
| S04 文件与关联 | 文档结果、附件引用、正文分页由同一存储宿主管理;删除重复 Markdown;保留分支引用 | 采用/复制/释放/重启,取消失败全文,删除项目引用回收 | 已接入生产组合;完整 App 和并发验收未完成 |
|
||||
| S05 绑定与账本 | 桌面远程绑定和原生客户端账本迁入宿主;远端数据库保持原所有权 | claim 后派发、提交后 ACK、响应不确定、关闭后删除目录 | 已接入生产组合;完整 App 和并发验收未完成 |
|
||||
| S06 输出与启动材料 | 短输出有界内存,全文唯一 backing;必要配置文件归属、部分创建清理、启动回收 | 字节等价、内存上限、无多余文件、活跃所有者保护 | 生产 backing 已接入;跨场景文件与生命周期验收未完成 |
|
||||
| S07 应用组合 | Main 初始化、事件通知、远程批处理、shutdown 接入统一存储;删除旧打开和同步回退 | 正常 UI 进入真实宿主,后台/恢复入口,退出失败可见 | 当前 Main 组合已接入;全路径验收未完成 |
|
||||
| S08 守护与验收 | 扩展现有 lint、性能脚本和 Electron 测试;复核旧调用及重复实现 | 全仓检查、C01–C07/A01–A10、真实模型及数据副本 | 待最终候选 |
|
||||
|
||||
S01 的契约供 S02–S06 复用,S07 统一组合。跨包共享文件由指定整合者修改;业务代理提供所需操作和测试。
|
||||
全量测试只对组合后的候选作最终判定。每次失败记录源码版本、原因、修复及复测,不放宽预算。
|
||||
|
||||
## 已发现问题对应关系
|
||||
|
||||
监督者重读/发布 Main 阻塞对应 S01/S02/S07;过大响应、窗口化和摘要缓存保留此前修复并在 C03/C07 回归。
|
||||
跨会话全局等待、取消/迟到路由、超限用量与 Telegram 交付保留已有修复,纳入故障和混合负载验收。
|
||||
输出短内容写后即删、全文引用缺失、重复解析 Markdown、Continue 部分创建和 helper 退出、Python 归档残留对应 S04/S06。
|
||||
Agent 旧包/远程 staging 的失败清理、项目删除附件回收、诊断解析结果归属及笔记原子临时残留需逐项核对现有实现后记录处理结果。
|
||||
有意保留的原生历史、安装缓存、用户导出、模型文件与 WAL 不作为垃圾删除。
|
||||
|
||||
## 真实数据与模型
|
||||
|
||||
已定位 portable:`dist/GoodBuddy-windows-x64/data`。该位置是原始数据,只读取统计并制作一致性备份,禁止直接启动候选写入。
|
||||
数据库存在活动 WAL 时使用 SQLite 一致性备份;备份与工作副本分开,测试材料位于仓库 `temp/goodbuddy-storage-upgrade/`。
|
||||
仅复制测试必需的数据库及所引用文件,记录校验值、schema、行数与完整性;不把凭据或正文写入报告。
|
||||
源库无 WAL 且确认关闭时才允许直接文件复制。升级后逐项比较稳定 ID、顺序、引用、事件还原与重放去重。
|
||||
|
||||
性能采用本地确定性模型,真实模型用于生产功能链:普通会话及工具、子任务、监督发布、本地/远程 Runtime。
|
||||
外部调用有界,记录实际请求数;真实数据内容不会为了性能测试默认发送到外部模型。
|
||||
远程验证沿用共享 Linux x64 Host、现有凭据和专用测试目录,结束确认所属进程退出。
|
||||
|
||||
## 完成条件
|
||||
|
||||
最终运行 `npm test`、`npm run typecheck`、`npm run lint` 和差异检查;构建入口变化验证开发构建。
|
||||
按 [并发验收规范](./concurrent-desktop-acceptance.md) 固定负载和三轮前后对照,包含 30 分钟生命周期。
|
||||
分别报告界面延迟、Main 阻塞、存储等待/提交、吞吐、取消、内存和文件增长,禁止将单测通过替代性能结论。
|
||||
实测超预算、未覆盖平台或外部环境阻断均保留为未满足项,不能宣称全部已解决。
|
||||
|
||||
当前记录确认 S01-S07 已有生产组合和定向证据;完整架构验收、平台包验证和 C01-C07/A01-A10 仍未完成。
|
||||
|
||||
## 2026-10-05 Portable 真实副本验证
|
||||
|
||||
本轮已执行当前源码的 `DesktopStorageClient → utilityProcess → DesktopStorageOwner`,
|
||||
使用生产升级、读取、发布和关闭入口。脚本为
|
||||
`tests/support/portable-storage-upgrade.mjs` 与 `portable-storage-upgrade-fixture.ts`。
|
||||
成功运行始于 `2026-10-05T12:03:32.624Z`,总耗时 265.074 秒;
|
||||
基于提交 `4bb7573b10ecc46f746fbe04cf6b1e2c05647d1a` 的未提交工作树。
|
||||
环境为 Windows `10.0.26200`、Intel Core Ultra X7 358H、33,873,752,064 字节内存、
|
||||
Node `24.21.0`、Electron `44.5.1`。这次未启动完整桌面 UI。
|
||||
|
||||
### 原件、备份与清理
|
||||
|
||||
原件位于 `dist/GoodBuddy-windows-x64/data`。成功运行没有在该目录打开 SQLite 连接,
|
||||
也没有启动以该目录为用户数据目录的候选程序。先复制主文件与 WAL 到隔离 capture 目录,
|
||||
确认源文件的大小、修改时间和 SHA-256 未变,再从 capture 执行 SQLite online backup,
|
||||
由备份生成独立工作副本。本轮四个源 WAL 均为 0 字节;不能据此声称已覆盖持续写入的活动 WAL。
|
||||
各数据库分别备份,文件集合不构成跨数据库事务快照。
|
||||
|
||||
四个源主文件及各自 WAL/SHM 共 12 个文件的前后大小、修改时间和 SHA-256 全部一致。
|
||||
升级后的四个备份主文件 SHA-256 也与升级前一致。73 个附件引用均有可复制内容,
|
||||
包括目录附件,合计 12,720,927 字节;50 个笔记文件合计 431,056 字节。
|
||||
附件及笔记在复制时校验内容摘要,生产写入与重开后再次确认原件及工作副本内容未变。
|
||||
日志只保存表名、计数、摘要和断言结果;没有复制设置或凭据,没有把正文写入报告,外部模型调用为 0。
|
||||
|
||||
被取消的旧探针曾继续占用 `run/work` 的 Assistant 文件。环境中的 `node` 实际由
|
||||
`GoodBuddy.exe` 承载,按可执行文件名查询 Node/Electron 会漏掉该进程。
|
||||
确认其命令行为本任务旧探针后,终止所属进程树并删除残留文件。
|
||||
成功运行自动删除 `fresh-run` 的 capture、backup、work、WAL/SHM 与 Electron 临时目录;
|
||||
旧 `run` 也已删除。保留的 `temp/goodbuddy-storage-upgrade/summary.json` 为无正文统计证据。
|
||||
原取消轮没有保存完整源文件前后报告,因此上述原件不变结论限定于本次有记录的验证区间。
|
||||
|
||||
### 数据与正确性
|
||||
|
||||
| 数据库 | 源主文件/备份字节数 | schema 前 → 后 | 实际数据规模 | 完成写入练习后的主文件字节数 |
|
||||
| --- | ---: | --- | --- | ---: |
|
||||
| Assistant | 2,429,247,488 | 61 → 62 | 11 项目、312 会话、4,836 消息、2,353 任务、1,755,171 事件、233 成果、23,690 活动记录 | 2,432,491,520 |
|
||||
| Knowledge | 1,568,768 | 13 → 13 | 2 知识库、2 文档、27 分块、27 embedding | 1,568,768 |
|
||||
| Attachments | 98,304 | 1 → 1 | 73 附件、73 引用 | 98,304 |
|
||||
| Runtime Bindings | 143,360 | 0 → 0 | 53 绑定 | 143,360 |
|
||||
|
||||
本轮实际覆盖的 Assistant 61 → 62 迁移新增监督来源/结果索引;数据已有历史差量存储,
|
||||
没有重跑 schema 33/34 的正文压缩迁移。
|
||||
|
||||
在追加合成记录之前,对四库全部已有用户表逐行计算规范化摘要,比较行数与表摘要。
|
||||
比较包含稳定 ID、顺序字段、时间、引用和远程来源字段;子任务事件按任务及事件 ID 顺序,
|
||||
调用生产 `restoreSubagentPayload` 还原后比较。未变的不可变过程块复用内容摘要,
|
||||
过程块数量与顺序仍参与每条事件的摘要。所有已有表的 `changed` 与 `added` 列表均为空,
|
||||
没有需要豁免的启动恢复差异。Assistant 完整扫描前/后分别耗时 80.393/107.989 秒。
|
||||
|
||||
备份、升级副本及完成写入练习后的四库 `integrity_check` 均为 `ok`,
|
||||
`foreign_key_check` 均为 0 条。真实监督数据包含 3 个结果、434 个事件、758 个来源;
|
||||
读取其中一个图谱并关闭/重开,内容摘要一致。
|
||||
新增远程事件的首次写入、同内容重放去重、冲突拒绝、已提交序号读取,以及重开后的重放去重均通过。
|
||||
100 批合成监督发布每批包含 20 个来源及 20 个事件;最后一批的事件、来源和 20 条关联均存在,
|
||||
关联端点有效,关闭/重开后图谱摘要一致。
|
||||
|
||||
Assistant 空闲页从 1,643 变为 1,619,Knowledge 维持 90 页;本轮没有证明空间回收收益。
|
||||
表中 Assistant 最终大小包含后续合成发布与去重记录,不能当成仅升级造成的增长。
|
||||
最终工作 WAL 的独立字节数没有写入该轮报告,退出后相关副本已清理。
|
||||
|
||||
### 耗时与边界
|
||||
|
||||
| 项目 | 单轮观测 |
|
||||
| --- | --- |
|
||||
| 首次生产存储就绪/关闭后再次就绪 | 557.778/308.364 ms |
|
||||
| 后续练习初始化/再次就绪 | 333.349/286.487 ms |
|
||||
| 100 次发布与 100 组图谱+结果列表读取重叠执行 | 32,363.016 ms |
|
||||
| 发布 p50/p95/max | 10.830/15.269/27.026 ms |
|
||||
| 读取组 p50/p95/max | 334.647/355.728/368.961 ms |
|
||||
| 夹具 Main 事件循环延迟 p99/max | 16.482/17.121 ms,共 2,117 个采样 |
|
||||
| 结束时 pending/Main RSS | 0/93,634,560 字节 |
|
||||
|
||||
这些数值来自单轮、两个有界生产者的闭环负载,不能替代固定 offered load、三轮前后对照、
|
||||
多项目混合负载或 C01–C07/A01–A10。pending 高水位只在请求完成后采样,观测值 1 不是全程峰值。
|
||||
未测 UI 响应、Renderer、存储进程内的等待与提交分解、30 分钟生命周期、真实模型及远程 Host。
|
||||
Main 延迟采样也不能替代单次阻塞测量,当前证据不足以宣称所有性能预算达标。
|
||||
|
||||
探针设有 15 分钟总期限、每个 Electron 练习 180 秒期限、完整扫描 600 秒检查,
|
||||
扫描每 100,000 行报告进度,练习每 10 秒报告阶段;通过后台运行与有界轮询执行。
|
||||
前一版重复序列化累计过程的扫描超过期限,修正为复用块摘要后才完成全量比较,未减少事件数。
|
||||
另外修复了 `publishedGraph` 的 `unknown` 使用:用共享 `supervisionGraphViewSchema` 校验响应,
|
||||
没有用类型断言跳过检查。依赖目录缺失曾阻断续跑,经用户授权按 lockfile 恢复后完成执行。
|
||||
|
||||
### 检查结果
|
||||
|
||||
`npm run typecheck` 全仓通过,用时 14.312 秒。两个探针文件的 ESLint 通过。
|
||||
定向运行 `desktop-storage.test.ts`、`desktop-storage-files.test.ts`、
|
||||
`desktop-storage-runtime-operations.test.ts` 与 `assistant-storage-upgrade.test.ts`,
|
||||
4 文件、31 测试全部通过,Vitest 报告 8.56 秒;包含真实 Electron 存储及历史迁移合成回归。
|
||||
结束前执行 `--verify-source`,再次确认 12 个源 DB/WAL/SHM 指纹和笔记摘要与本轮基线一致。
|
||||
`git diff --check` 无空白错误。中文审校扫描无阻断项,复核项中的测试边界与实际统计表述保留。
|
||||
|
||||
全仓 `npm run lint` 最后仍有一个范围外错误:
|
||||
`tests/support/story-graph-focus-driver.mjs:122` 的 `console` 未定义;没有修改该探针。
|
||||
全仓 `npm test` 使用 300 秒总期限启动,结束时没有留下最终汇总或退出结果,不能标记为通过。
|
||||
已输出的失败涉及 `MarkdownRenderer.cache.test.tsx` 的 1 个微基准、
|
||||
`image-tool-mcp.test.ts` 的 3 个测试及 `obsidian-electron.test.ts` 的 1 个测试。
|
||||
这些失败保留给对应实现负责者,不以本轮存储验证通过覆盖全仓失败。
|
||||
检查产生的本任务临时目录也在所属进程退出后清理;没有提交代码。
|
||||
|
||||
## 2026-10-05 C01-C07 并发桌面验收(未完成)
|
||||
|
||||
本轮使用现有 `build/run-app-perf.cjs` 与
|
||||
`tests/support/app-perf-driver.mjs`,新增最小并发模式夹具
|
||||
`tests/support/app-perf-concurrent.mjs`。夹具通过真实 UI、Preload、Main、存储宿主和本地回环确定性模型运行;没有启动 portable 原始目录,也没有外部模型请求。基线使用独立 worktree 的提交
|
||||
`4bb7573b10ecc46f746fbe04cf6b1e2c05647d1a`,候选为同一提交的当前未提交工作树。日志和原始 JSON 位于 `temp/goodbuddy-concurrent-upgrade/`。
|
||||
|
||||
本轮没有达到验收规范要求的三轮交替、每项三轮各 100 个有效样本,也没有完成全部 C01-C07。因此不能标记 PERF-15/PERF-16 或本架构验收通过。
|
||||
|
||||
| 场景 | 实际结果 | 证据与主要指标 |
|
||||
| --- | --- | --- |
|
||||
| C01 3/4/8 | 未通过 | 当前候选首轮测量 1,898 个按键样本,p95 输入 16.8 ms、p95 帧 17.0 ms,但 Main 最大事件循环延迟 64.4 ms;请求未排空,驱动超时。基线首轮在第一批真实发送前等待发送按钮超时。 |
|
||||
| C02 4/8/16 | 未通过 | 两个 30 秒测量周期完成;周期 1 有 494 个按键样本,p95 输入 17.4 ms、p95 帧 17.2 ms、4.2% 抖动、Main p99 18.4 ms、最大 25.3 ms。第三周期在请求排空前超时;标准机输入和帧 p95 均超预算。 |
|
||||
| C03 回顾发布+知识重叠 | 未执行 | 现有 App 夹具没有真实重叠的 100 批回顾发布、知识导入/检索和图谱读取生产动作。 |
|
||||
| C04 故障隔离 | 未执行 | 没有挂起启动/模型路由/清理并验证取消及健康 peer 的夹具。 |
|
||||
| C05 存储故障与饱和 | 未通过 | C06 运行期间生产存储路径实际多次返回 `STORAGE_CAPACITY`;错误来自 `ReadonlyQueryReader.call` 的会话队列读取,已保留在 `soak-current/execution.log`。专门读 Worker 失败、存储进程退出和提交前后故障注入尚未完成。 |
|
||||
| C06 30 分钟生命周期 | 未完成 | 已启动完整期限为 1,800,000 ms 的后台运行,实际约 34.5 分钟后因驱动超时退出;首个 30 秒测量有 494 个按键样本,p95 输入 16.6 ms、p95 帧 16.9 ms、Main p99 17.5 ms。由于请求持续在途且存储容量错误,未完成规范要求的完整生命周期、关闭重开和排空,不能算通过。 |
|
||||
| C07 冷/暖进入 | 未通过 | 两个周期完成;周期 1 有 442 个按键样本,p95 输入 17.8 ms、p95 帧 50.0 ms、8.2% 抖动、Main p99 17.7 ms。夹具尚未分别测量监督概览、图谱、历史选择和来源正文的冷/暖内容可用时刻。 |
|
||||
|
||||
资源证据包括每个场景的 Browser、Tab、GPU、Utility PID、CPU 和工作集快照。当前候选的 C02 周期 1 Tab 工作集约 210 MB、Browser 约 200 MB;C07 周期 1 Tab 约 210 MB、Browser 约 212 MB。夹具已记录 3/4 和 4/8 项目/会话档位及 8/16 子任务 offered load,但生产事件中没有形成稳定的 8/16 个同时运行子任务,不能把 offered load 当成实际吞吐。周期性 4 KiB 工具更新、存储排队/执行拆分、缓存/订阅计数、临时文件字节数、C03-C05 故障动作和完整 supervisor 冷/暖内容指标均为未采集项。
|
||||
|
||||
本轮没有修改核心存储或调度实现。`node --check build/run-app-perf.cjs` 和
|
||||
`node --check tests/support/app-perf-concurrent.mjs` 通过;未运行全仓 `npm test`,
|
||||
因此本节只记录局部复测,不改变 C01-C07 的未完成状态。
|
||||
|
||||
### 2026-10-05 C01/C02/C07 实际证据复核与局部修复
|
||||
|
||||
复核了上述原始 JSON、`soak-current/execution.log` 及生产源码。容量错误的实际调用链为
|
||||
`pumpConversationQueue` → `DesktopStorageClient.listConversationQueueItems` →
|
||||
`ReadonlyQueryReader.call`;同一压力段还可见 `tasks:execution-stats`、工作区变化和概览读取竞争同一
|
||||
有界读容量。C01 候选首轮为 1,898 个输入样本,输入 p95 16.8 ms、帧 p95 17.0 ms、Main 最大
|
||||
64.4 ms;C02 周期 1 为 494 个样本,输入 p95 17.4 ms、帧 p95 17.2 ms、Main p99 18.4 ms、
|
||||
最大 25.3 ms;C07 周期 1 为 442 个样本,输入 p95 17.8 ms、帧 p95 50.0 ms、Main p99
|
||||
17.7 ms。C02/C07 的长帧主要出现在持续事件输入和显示刷新期间,采样中没有长任务脚本时间,
|
||||
但有长动画帧的渲染/样式布局时间;因此未减少 offered load、输入样本或阈值。
|
||||
|
||||
本轮应用了三个最小局部调整:隐藏窗口时丢弃已过时的会话显示刷新并在重新可见时重读;侧栏隐藏时
|
||||
不挂载项目切换器、活动菜单及其项目行;队列泵遇到 `STORAGE_CAPACITY` 时按会话只保留一个
|
||||
50 ms 后重试,避免同一队列因容量瞬时不足进入错误风暴。正常事件、持久化和队列项目均保留。
|
||||
定向 `ipc`、刷新和项目切换回归为 248 passed、1 skipped;类型检查和相关 lint 通过。
|
||||
|
||||
重新构建后以相同 C02 档位(4 项目/8 会话/16 子任务、每轮 30 秒输入)执行了四个周期,
|
||||
原始报告位于 `temp/goodbuddy-concurrent-upgrade/profile-1MGfH7/artifacts/result.json`。修复后四轮
|
||||
输入 p95 为 16.9/16.9/17.0/17.3 ms,帧 p95 为 16.9/16.9/17.0/17.1 ms,Main
|
||||
最大事件循环延迟为 46.5/31.1/45.8/292.8 ms;最后一轮仍有 1 个 51 ms 长任务,驱动因
|
||||
请求未排空而超时。日志仍观察到少量其他后台读取的 `STORAGE_CAPACITY`,所以本轮只能证明显示
|
||||
刷新和队列重试的局部改善,不能标记 C02 或容量验收通过。相对原 C02 周期 1,前三轮输入/帧
|
||||
p95 已回到约 17 ms,但 Main 尾延迟和完整排空仍未达预算;C01/C07 尚未取得修复后的同口径复测。
|
||||
|
||||
## 2026-10-05 C03/C04/C07 补充运行(仍未满足)
|
||||
|
||||
本轮只扩展现有 `build/run-app-perf.cjs`、`tests/support/app-perf-driver.mjs` 的
|
||||
并发夹具,并通过已有 Preload/IPC/存储宿主/SQLite 生产入口执行。模型仍为本地
|
||||
回环确定性模型,数据目录和运行目录均由 `run-app-perf` 隔离在
|
||||
`temp/goodbuddy-concurrent-upgrade/`。构建命令为 `npm run build:bundle`;本轮没有
|
||||
修改核心存储或调度实现,也没有提交。
|
||||
|
||||
| 场景 | 实际结果 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| C03 混合存储 | 未满足 | `C03-current-r4/result.json` 记录知识导入耗时 4,307.193 ms,100 个重叠批次,100 次历史读取、100 次知识检索、100 次图谱读取,重叠窗口 3,784.373 ms。当前隔离数据没有已持久化监督结果,回顾发布批次为 0,因此没有把图谱读取冒充回顾发布;运行期间日志出现 `STORAGE_CAPACITY`。结果状态保留为 `not-fulfilled`。 |
|
||||
| C04 故障隔离 | 未满足 | 复核发现原夹具在请求观察器登记前允许点击取消,且 HTTP `request.close` 被误记为模型中止;现已改为等待请求登记、使用响应 `close` 判断异常中止,并等待健康 peer 真正进入和完成。`C04-final-local-r2/result.json`(本地确定性、Windows x64、Electron 44.5.1)记录取消后请求结算、查询和 Runtime status 响应、清理完成,健康 peer 用时 37,142 ms,但 `healthyPeerUnaffected: false`,且循环账本 4 次请求中没有 `fault-hung` 条目,因此不能声称挂起模型路径已完成。外部模型请求数为 0;未授权执行真实模型。 |
|
||||
| C07 冷/暖进入 | 未满足 | `C07-current-r1/result.json` 记录冷进入:输入 0.591 ms、历史 41.889 ms、监督概览 2.532 ms、图谱 2.244 ms、知识快照 2.555 ms;暖返回:输入 18.844 ms、历史 219.868 ms、图谱 10.757 ms、知识快照 2.062 ms。暖监督概览因 `STORAGE_CAPACITY` 无可用时刻;来源正文没有持久化来源,仍未测得。夹具状态为 `measured-with-gaps`,整体未满足。 |
|
||||
|
||||
所有补充运行均保存原始 `result.json` 和执行日志;模型边界仍为本地回环,外部请求数为 0。运行中观察到的存储容量错误及一次存储 Worker 退出均保留为当前候选的未满足项,不能以夹具已采集样本或构建成功覆盖。三轮交替、每项三轮 100 个有效交互样本、C03 回顾发布、C04 健康 peer 完成和 C07 来源正文时刻仍未完成。
|
||||
|
||||
## 2026-10-06 Portable 证据轻量复核
|
||||
|
||||
本次只读复核针对原始 `dist/GoodBuddy-windows-x64/data`、保留的
|
||||
`temp/goodbuddy-storage-upgrade/summary.json` 和当前仓库状态执行;没有启动 portable 原始目录,
|
||||
没有复制或写入原始数据库,也没有提交。保留证据的成功运行时间仍为
|
||||
`2026-10-05T12:03:32.624Z`,记录提交为 `4bb7573b10ecc46f746fbe04cf6b1e2c05647d1a`,
|
||||
且记录的工作树为未提交状态。当前 HEAD 为
|
||||
`edf019aaa728f094a433a2601d149d0ec9b28a21`,工作树仍有大量未提交修改,因此 portable 运行早于当前
|
||||
源码状态,不能把它当作当前 HEAD 的运行结果。
|
||||
|
||||
保留验证器 `node tests/support/portable-storage-upgrade.mjs --verify-source` 本次未通过:
|
||||
当前 `assistant.sqlite` 与保留基线的 SHA-256 和修改时间不同,当前 `knowledge.sqlite` 的修改时间不同,
|
||||
且保留基线中的四个 0 字节 WAL 与四个 SHM 当前已不存在。当前四个主库的字节数仍分别与记录一致;
|
||||
`conversation-attachments.sqlite` 和 `remote-runtime-bindings.sqlite` 的 SHA-256/修改时间仍一致,
|
||||
`knowledge.sqlite` 的 SHA-256 仍一致。当前 `notes` 仍为 50 个文件、431,056 字节,内容指纹与保留证据一致。
|
||||
因此原件相对本轮 portable 执行后的“当时未变”结论有后续漂移,不能宣称当前原始数据仍与 portable 运行基线完全相同。
|
||||
|
||||
对当前四个原始主库执行只读 SQLite `PRAGMA integrity_check` 均为 `ok`,`foreign_key_check` 均为 0 条,
|
||||
版本分别为 Assistant 62、Knowledge 13、Attachments 1、Runtime Bindings 0。这只证明当前文件的轻量结构完整性,
|
||||
不恢复 portable 运行与当前源码的一致性,也不替代一次新的隔离副本迁移验证。
|
||||
|
||||
清理复核确认 portable 验证使用的 `temp/goodbuddy-storage-upgrade/fresh-run`、`run` 目录均不存在,
|
||||
保留目录中只有日志、JSON 结果和源基线等证据文件;进程命令行未发现 portable 数据目录、验证脚本或
|
||||
`goodbuddy-storage-upgrade` 相关进程。机器上发现的已安装 GoodBuddy 进程使用独立用户数据目录,未计入本次
|
||||
portable 验证进程。上述结果只更新本实施记录,未修改原始目录或其他实现文件。
|
||||
|
||||
## 2026-10-06 简化与前后对照验收
|
||||
|
||||
### 简化
|
||||
|
||||
按“出现实测问题才加机制”复核并发修复期间新增的机制,删除以下实现(共约 400 行):
|
||||
|
||||
| 删除项 | 原因 | 替代 |
|
||||
| --- | --- | --- |
|
||||
| 自定义分帧传输 `desktop-storage-wire.ts` | 同机实测原生 `utilityProcess.postMessage` 传 32 MB 文本 126–139 ms、5 万行 71–85 ms,Main 最大延迟在 16 ms 计时精度内;自定义分帧分别为 249–271 ms、448–496 ms | 原生结构化克隆 |
|
||||
| 读/写/后台三类准入预算、交互保留槽 | 容量错误是“满了就拒绝”造成的,分类只是缓解 | 单一在途上限 32,超出按顺序等待,不再拒绝 |
|
||||
| 同键读取合并、存储进程侧重复准入、整值读取串行 | 客户端已限流,宿主再做一遍没有新约束 | 删除 |
|
||||
| 队列泵 `STORAGE_CAPACITY` 50 ms 重试 | 等待替代拒绝后不再出现容量错误 | 删除 |
|
||||
|
||||
另把存储进程启动移到建窗口之前,与窗口创建重叠。取消、提交后才确认、进程丢失时不重放、关闭前排空的语义不变;真实 Electron 存储夹具(含 16 MiB 视频、2,560 万字符历史)通过。
|
||||
|
||||
### 前后对照
|
||||
|
||||
基线为独立 worktree 的 `edf019aa`,候选为同一提交加当前工作树;同机交替运行,本地确定性模型,无外部请求。判定口径见[并发验收的发布门槛](./concurrent-desktop-acceptance.md#发布门槛与目标差距)。
|
||||
|
||||
| 场景 | 正确性门槛 | 对照结果(基线 → 候选,3 轮) |
|
||||
| --- | --- | --- |
|
||||
| 单会话 11 个场景 | — | 输入、切换、长会话打开、流式均在轮间波动内;Main 延迟 p99 在流式场景约 20.6 → 16.8 ms;启动至输入框可用中位数约慢 33 ms(1,300 → 1,333 ms),与轮间波动同量级 |
|
||||
| C01(3/4/8) | 两版均 3/3 通过:全部排空、0 错误 | 排空耗时一致(约 37 s/17.5 s);Main p99 约 21 → 16.7 ms |
|
||||
| C02(4/8/16) | 两版完成的轮次均通过 | 排空耗时一致(约 23 s/53 s);Main p99 约 22 → 16.7 ms |
|
||||
| C03 混合读取 | 两版均无回顾结果可发布,标未满足 | 100 批重叠读取窗口约 1,010 → 912 ms;基线知识导入在该夹具中报错,候选无错误 |
|
||||
| C04 故障隔离 | 两版均 3/3 通过:挂起请求取消结算,健康会话完成 | 取消观测约 135 → 101 ms,健康会话耗时一致 |
|
||||
| C06 5 分钟生命周期 | 两版均通过 | 每轮排空耗时一致;4 轮后堆 18.2 → 17.8 MB;另有候选 30 分钟运行 21 轮全部排空、0 错误,堆每轮约增 0.3 MB,与每轮新增 8 条持久化会话一致,结束时驱动退出出错,未记为通过 |
|
||||
| C07 冷/暖进入 | 无持久化来源,来源正文未测 | 冷进入各阶段 1–7 ms,两版一致;暖返回监督概览 38–45 → 25–38 ms |
|
||||
|
||||
产品子任务调度上限为 3,16 个子任务按设计排队,因此“同时运行子任务”门槛按 `min(负载, 3)` 判定。夹具修正:排空等待改为 180 s(两版相同)、子任务按 `childTaskId` 统计、输入框焦点未就绪时重试输入。
|
||||
|
||||
结论:**正确性门槛在 C01/C02/C04/C06 两版都通过,候选在单会话和并发场景均未退化,Main 尾延迟有改善。** 剩余未满足项:C03 回顾发布与 C07 来源正文需要先有持久化回顾数据的夹具;C05 存储故障注入只有真实 Electron 存储夹具中的进程丢失、发送失败、取消覆盖,未做 App 级故障矩阵。输入 p95 约 8–9 ms,帧 p95 多数 8.5 ms,部分轮出现 16.5/58 ms,两版相同,记为目标差距。
|
||||
|
||||
### 真实数据与真实模型
|
||||
|
||||
portable 新副本(`--fresh`)当前源码复验通过:Assistant 库 175 万条事件、4,848 条消息,schema 62,所有已有表规范化摘要无变化,`integrity_check` 为 ok,外键违规 0;73 个附件、50 个笔记前后一致,原件 DB/WAL/SHM 指纹未变,副本已删除。100 批发布与读取重叠时,发布 p95 19 ms,夹具 Main 延迟 p99 16.4 ms。
|
||||
|
||||
真实模型输出分页首次走通:使用当前默认 `openai-responses` 配置,模型调用 1 次 `process_execute`、2 次 `output_read`,逐页游标连续至 EOF,恢复两个随机标记,**4 次外部请求**。之前两次失败的原因是该手动用例未注入输出正文存储(生产在 `index.ts` 注入),工具在模型分页前就报错;已改为注入与其他单测相同的文件存储。
|
||||
|
||||
### 全仓检查
|
||||
|
||||
`npm run typecheck`、`npm run lint` 通过。`npm test` 三次全量结果:1 失败(`workspace-import` 图片加载超时)、9 失败、1 失败(`local-runtime-reuse` 压缩返回 false),每次失败的用例不同,单独复跑均通过(`local-runtime-reuse` 3/3、`workspace-import` 4/4、其余 3 个文件 481/481)。判定为满载时的时序敏感用例,未放宽断言或超时,但不能记为全仓一次通过。
|
||||
|
||||
## 2026-10-06 有界真实模型复验
|
||||
|
||||
本节记录当前未提交源码的最终有界复验。没有启动 portable 原始数据目录,也没有把其中的会话、附件、笔记、日志或凭据发送给外部模型。所有发送给模型的监督输入均为脚本内合成内容;取消和输出分页场景也只使用脚本生成的短提示、临时工作区和随机标记。请求计数来自生产 Runtime 的 fetch 层或本地计数 relay,不记录请求正文、响应正文、请求头、端点或密钥。
|
||||
|
||||
| 路径 | 实际外部 HTTP 请求 | 结果 | 范围与边界 |
|
||||
| --- | ---: | --- | --- |
|
||||
| 生产存储宿主监督链 | 4 | 通过,4 次 HTTP 200 | `supervision-storage-live.cjs` 使用当前运行时设置读取凭据,在隔离 Electron、Assistant/Knowledge 存储宿主、生产监督工厂和 Heartbeat 入口中运行合成项目;故事、经验、建议发布成功,`no_change` 重跑新增 0 次,设置未改变,pending 为 0 |
|
||||
| 生产监督审核工厂 | 2 | 通过,完成 | `supervision-audit-validation.ts live` 使用当前 `ModelAgentRuntime`、生产监督服务和隔离 SQLite;1 条合成消息完成提取与故事归属,重跑新增 0 次,SQLite 完整性与外键检查通过 |
|
||||
| 直连 Runtime 取消 | 3 | 通过 | 现有 `cancelled-turn-live.test.ts` 的两个 direct-model 取消后续答复用本地计数 relay 执行;取消轮和后续请求均完成,取消内容没有并入下一轮,3 次请求均由 relay 计数 |
|
||||
| 直连 Runtime 输出分页/工具请求 | 2 | 未通过覆盖断言 | 现有 `runtime-e2e.manual.test.ts` 的生产源测试到达模型两次,但模型没有发出 `process_execute` 或 `output_read`,因此没有证明真实模型工具请求和分页闭环;该次仅计入实际请求,不把失败运行写成通过 |
|
||||
| 本地 OpenCode | 0 | 未执行 | 当前源码 checkout 没有 `.runtime-resources/win32-x64/opencode.exe`;没有改用已发布 portable 或其他缓存二进制 |
|
||||
| 本地 Continue | 0 | 未执行 | 当前源码 checkout 没有 `node_modules/@continuedev/cli/dist/cn.js`;没有改用已发布包 |
|
||||
| 共享 Linux Host | 0 | 未执行 | `192.168.0.23` 的 SSH 连接可达,但 BatchMode 连接在认证前由 Host 关闭;没有凭据交换或模型请求 |
|
||||
|
||||
本节合计观察到 **11 次** 外部模型 HTTP 请求:监督存储 4 次、监督审核 2 次、取消 3 次、输出分页失败尝试 2 次。没有重试被计入,也没有远程或 OpenCode/Continue 本地请求。监督链使用的运行时设置仅在隔离进程内解密,原设置字节保持不变。
|
||||
|
||||
验证结束后,隔离数据库、Electron profile、临时工作区、凭据副本、计数 relay 和生成的 probe 目录均已删除;进程检查没有发现属于这些运行的残留进程。该结果证明了当前源码的生产存储监督与直连取消路径,以及一次明确失败的真实模型工具分页尝试;不证明本地 OpenCode、Continue、远程 Linux Host、真实模型工具分页成功、完整桌面 UI、portable 数据副本或 C01–C07/A01–A10 验收已经完成。
|
||||
@@ -1019,34 +1019,38 @@ Renderer 主线程降速 4 倍(Main 不降速,每次 reload 后重新应用
|
||||
|
||||
### PERF-15 存储接口异步化
|
||||
|
||||
2026-10-05 范围澄清:目标职责与迁移边界见[桌面存储方向](../architecture/desktop-storage-direction.md)。
|
||||
当前先实施[监督者及执行路径的 KISS 局部修复](../review/kiss-local-fixes-2026-10-05.md),
|
||||
完成、验证并提交后再讨论全面迁移。局部 Worker 复用不等于 PERF-15/16 已完成。
|
||||
2026-10-05 决策更新:[KISS 局部修复](../review/kiss-local-fixes-2026-10-05.md)已以 `b90ff50f` 提交。
|
||||
用户随后确定 PERF-15/PERF-16 及必要守护按一个完整架构候选交付,不拆成多轮局部产品交付。
|
||||
职责与 KISS 边界以[桌面存储与并发执行](../architecture/desktop-storage-direction.md)为准,
|
||||
多项目、多会话、多子任务的共同验收见[并发规范](../quality/concurrent-desktop-acceptance.md)。
|
||||
当前候选已完成存储宿主及主要生产组合,完整迁移验收尚未完成;以下编号保留为职责索引,不表示分别验收的发布阶段。实现状态汇总见[存储升级实施记录](../quality/storage-upgrade-implementation-2026-10-05.md)。
|
||||
|
||||
- **优先级 / 状态:** P1 / 进行中(热点读走 worker;高频写降为 NORMAL、checkpoint 移出 Main、启动恢复按索引,见 7.2.2;接口异步化未开始)
|
||||
- **范围:** 在原进程内把 `AssistantDatabase`、`KnowledgeDatabase` 等对外接口改为异步,
|
||||
调用方全部 `await`;会话列表只返回摘要,搜索下推到 SQL,活动记录改为增量追加。
|
||||
远程 Runtime 实时事件改用已有的 `appendRemoteTaskEventsBatch` 批量写入,不再逐条同步事务。
|
||||
Story Graph 检索先补测耗时,扫描规模设上限,匹配尽量下推到 SQL。
|
||||
- **验收:** 行为与测试不变;`PERF-11` Main event-loop 延迟不变差。
|
||||
- **优先级 / 状态:** P1 / 进行中(存储域接口和主要生产调用已异步化;完整调用清理、异常路径和 C01-C07/A01-A10 验收仍未完成)
|
||||
- **范围:** 为桌面业务存储建立异步领域接口,保留完整事务和先写后读顺序;包含 Assistant、
|
||||
Knowledge、附件、桌面 Runtime 绑定和本机原生客户端账本。跨边界调用方等待实际完成;同步缓存读取
|
||||
与内部纯计算无需变成 IPC。已完成的摘要、SQL 搜索、活动增量和远程事件批处理继续复用。
|
||||
- **验收:** 与 PERF-16 一起通过 SA-01~SA-07;只增加 Promise 或保留 Main 同步执行不计完成。
|
||||
|
||||
### PERF-16 数据进程
|
||||
|
||||
- **优先级 / 状态:** P1 / 待开始
|
||||
- **实施条件:** `PERF-15` 完成,调用方已全部异步。
|
||||
- **优先级 / 状态:** P1 / 进行中(`utilityProcess` 宿主已接入 Assistant、Knowledge、文件和 Runtime 域;全量生产路径与并发验收仍未完成)
|
||||
- **接口条件:** 每条跨进程业务操作具备异步调用及事务边界;与 PERF-15 在同一候选完成和验收,不单独交付半迁移状态。
|
||||
- **范围:** 目标为统一管理业务 SQLite 的 `utilityProcess`,承接会话、知识库、Story Graph、
|
||||
远程 Runtime 事件持久化和活动记录的业务事务;Main 只做校验与转发。重查询由受管只读 Worker
|
||||
分担,解析、OCR 与 Runtime 执行保持独立,每个数据库明确一个写入所有者;不合并现有数据库。
|
||||
职责、生命周期及 VS Code 对照以[桌面存储方向](../architecture/desktop-storage-direction.md)为准。
|
||||
- **验收:** 大知识库检索、会话列表和文档导入期间 Main event-loop 延迟达到批次目标;取消、
|
||||
崩溃恢复和迁移语义不回退。
|
||||
- **验收:** Main 无目标业务 SQLite 直接或间接访问、无同步回退;多项目/会话/子任务、重查询、
|
||||
导入及发布重叠时满足性能原则。完整矩阵、取消、故障、旧库和平台要求按并发验收执行。
|
||||
|
||||
### PERF-17 架构守护
|
||||
|
||||
- **优先级 / 状态:** P2 / 待开始
|
||||
- **范围:** lint 禁止 `src/main` 使用同步文件 API 和在数据进程外引用 `DatabaseSync`;禁止
|
||||
组件直接订阅 `window.goodbuddy.*.on*`;限制 `App.tsx` 规模;`PERF-11` 关键指标设置回归阈值。
|
||||
- **验收:** 违反规则的改动在 lint 或基准阶段失败。
|
||||
- **优先级 / 状态:** P2 / 部分实施(已有 lint 与历史回归上限;间接访问、单调收紧及并发性能检查待补)
|
||||
- **范围:** 按实际执行环境检查 Main 同步文件操作、直接和间接 SQLite 打开;存储宿主、受管 Worker、
|
||||
测试及远端 Agent 的合法连接按[并发验收的自动检查规则](../quality/concurrent-desktop-acceptance.md#自动检查与交付记录)列明,
|
||||
不按目录一概禁止。禁止组件直接订阅 `window.goodbuddy.*.on*`,限制 `App.tsx` 规模,
|
||||
为 `PERF-11` 及并发场景建立符合性能原则的检查。
|
||||
- **验收:** 违反规则的改动在 lint 或基准阶段失败;本次完整架构所需守护随同一候选交付。
|
||||
历史性能回归上限不能替代规范预算,无 CI/性能环境结果时不标记验收通过。
|
||||
|
||||
## 8. 体验与可访问性
|
||||
|
||||
|
||||
Reference in New Issue
Block a user