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.
|
||||
|
||||
Reference in New Issue
Block a user