Cache task events on disk per server; fix performance audit findings

This commit is contained in:
Ian Gulliver
2026-10-23 12:49:43 -07:00
parent dc2fcc6075
commit d6be98f9d8
13 changed files with 448 additions and 250 deletions
+2 -1
View File
@@ -2436,9 +2436,10 @@ components:
Status:
type: object
required: [started_at, db_bytes, last_event_seq, projects, active_tasks, sse_clients, clients, live_agents, running_turns, rate_limits, agent_processes, zombies, stuck_after_seconds, protocol_errors]
required: [started_at, db_bytes, last_event_seq, schema, projects, active_tasks, sse_clients, clients, live_agents, running_turns, rate_limits, agent_processes, zombies, stuck_after_seconds, protocol_errors]
properties:
live_agents: { type: integer, description: Agent processes currently running. }
schema: { type: string, description: "Fingerprint of the database's applied migrations. It changes when a migration lands or the database is recreated; the app then drops the events it cached from this server." }
agent_processes:
type: array
description: Every agent process byre is tracking, idle sessions included.
+1
View File
@@ -30,6 +30,7 @@ NDJSON both ways: one JSON object per line on stdin and stdout. Non-JSON stdout
- Newer CLIs also send `command_lifecycle` lines (`command_uuid`, `state` such as `completed`). byre drops them, as it does `keep_alive`.
- The `init` event arrives with the first turn, not at launch, so the session ID is unknown until the first message is sent.
- While a tool runs, `tool_progress` lines with `"heartbeat": true`, the tool's `elapsed_time_seconds`, and a `parent_tool_use_id` naming the tool call arrive every 30 seconds. A Bash call also brings `system` `task_started` and `task_notification` lines around it.
- `system` lines with subtype `status` (`"status": "requesting"` as each model request starts) and `thinking_tokens` (a running `estimated_tokens` while the model thinks, several a second) are progress only. byre stores neither them nor `tool_progress`; the other lines it doesn't handle are kept as `agent.vendor` events.
- Subagents (the `Agent` tool, listed in `init` tools as `Task`): `system` `task_started` (`task_type` `local_agent`, `tool_use_id` naming the `Agent` call, `subagent_type`, `description`, `prompt`, `is_backgrounded`), `task_progress` (`description` of what it is doing), `task_updated` (by `task_id` only), and `task_notification` (`status`, `summary`). `assistant` and `user` lines from inside the subagent carry `parent_tool_use_id` set to the `Agent` call's ID, plus `agent_id`, `subagent_type` and `task_description`. A foreground subagent (`run_in_background: false`) sends only its prompt this way, as a `user` text block, and its final report arrives as the `Agent` call's `tool_result`. A background one (the default) streams all its assistant and tool-result lines; the `Agent` call's `tool_result` comes back at once saying it launched, the turn can end with its own `result` while the subagent runs, and when the subagent finishes the CLI starts another turn by itself, ending in a second `result` carrying `origin.kind` `task-notification`.
## Control channel
+2
View File
@@ -48,6 +48,8 @@ The update UI must not offer Skip or turning automatic updates off: on a Mac run
CloudKit has no unique constraints, so two Macs can each create a record for the same thing; duplicate records are expected. See `docs/operations.md`, "CloudKit schema", before changing the SwiftData models. The store is named `byre.store` because the app isn't sandboxed, so SwiftData's default `default.store` would be shared with every other unsandboxed app. Deleting the local files while the app isn't running makes the next launch download the records again.
Each task's events are cached on disk per server, under `run.fc.byre.mac/events/<server host>/` in Application Support, so a launch fetches only the events newer than the cached ones. Events never change, so the cache is keyed on the one thing that rewrites them: the `schema` fingerprint in `/api/status` (the server's applied migrations), and the whole server's cache is dropped when it differs. A task's file goes when the task closes, and a file that stops decoding is cut at its last good line, with the rest fetched again. A server restart replaces the Store while the old one still shows the transcripts, so each connection's cache opens with a generation and takes appends only from feeds loaded under the current one; a file is only ever extended by a writer that read it first.
## Gaps in SwiftUI
Places where SwiftUI alone may fall short, each checked side by side against its fallback; the fallback is used only where the check failed:
+6 -2
View File
@@ -35,6 +35,8 @@ const agentActor = "agent"
const restartNotice = "byre restarted while you were mid-turn, so your last turn was cut off. Re-check the working tree (git status and the files you were editing) before continuing where you left off."
var progressSubtypes = map[string]bool{"status": true, "thinking_tokens": true}
type Manager struct {
store *store.Store
worktreeRoot string
@@ -1143,9 +1145,12 @@ func (m *Manager) handle(ctx context.Context, l *live, ev claude.Event) error {
m.setVendorUsage(l.vendor, claudeUsage(info.RateLimitInfo))
case "system":
if ev.Subtype == "init" {
if ev.Subtype == "init" || progressSubtypes[ev.Subtype] {
return nil
}
case "tool_progress":
return nil
}
raw := map[string]any{}
@@ -1261,7 +1266,6 @@ func (m *Manager) turnEnded(ctx context.Context, l *live, res endResult) error {
}
}
}
m.refreshFacts(ctx, l.taskID)
handoff := compacting && finished
if sha != "" {
payload["commit"] = sha
-14
View File
@@ -83,20 +83,6 @@ func (m *Manager) settleFacts(ctx context.Context, dirty map[string]bool) error
return nil
}
func (m *Manager) refreshFacts(ctx context.Context, taskID string) {
t, err := m.store.GetTask(ctx, taskID)
if err == nil {
var p api.Project
p, err = m.store.GetProject(ctx, t.ProjectId)
if err == nil {
err = m.settleTaskFacts(ctx, t, p)
}
}
if err != nil {
slog.Error("refresh merge facts", "task", taskID, "err", err)
}
}
func (m *Manager) settleTaskFacts(ctx context.Context, t api.Task, p api.Project) error {
m.factsRun.Lock()
defer m.factsRun.Unlock()
+229 -225
View File
@@ -1487,8 +1487,11 @@ type Status struct {
// RunningTurns Turns running now, oldest session first.
RunningTurns []TurnRef `json:"running_turns"`
SseClients int `json:"sse_clients"`
StartedAt time.Time `json:"started_at"`
// Schema Fingerprint of the database's applied migrations. It changes when a migration lands or the database is recreated; the app then drops the events it cached from this server.
Schema string `json:"schema"`
SseClients int `json:"sse_clients"`
StartedAt time.Time `json:"started_at"`
// StuckAfterSeconds How long a running turn may go without a stream event before it counts as stuck.
StuckAfterSeconds int `json:"stuck_after_seconds"`
@@ -5303,229 +5306,230 @@ var swaggerSpec = []string{
"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",
}
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
+1
View File
@@ -762,6 +762,7 @@ func (s *Server) status(ctx context.Context) (api.Status, error) {
StartedAt: s.startedAt,
DbBytes: dbBytes,
LastEventSeq: c.LastEventSeq,
Schema: s.store.Schema(),
Projects: c.Projects,
ActiveTasks: c.ActiveTasks,
SseClients: s.hub.count(),
@@ -0,0 +1,3 @@
DELETE FROM events WHERE kind = 'agent.vendor'
AND (json_extract(payload, '$.type') = 'tool_progress'
OR (json_extract(payload, '$.type') = 'system' AND json_extract(payload, '$.subtype') IN ('status', 'thinking_tokens')));
+33 -1
View File
@@ -2,8 +2,10 @@ package store
import (
"context"
"crypto/sha256"
"database/sql"
"embed"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@@ -74,6 +76,7 @@ const TaskColors = 11
type Store struct {
db *sql.DB
path string
schema string
writeMu sync.Mutex
onEvent func([]api.Event)
}
@@ -92,7 +95,36 @@ func Open(path string, onEvent func([]api.Event)) (*Store, error) {
return nil, err
}
return &Store{db: db, path: path, onEvent: onEvent}, nil
schema, err := schemaFingerprint(db)
if err != nil {
db.Close()
return nil, err
}
return &Store{db: db, path: path, schema: schema, onEvent: onEvent}, nil
}
func schemaFingerprint(db *sql.DB) (string, error) {
rows, err := db.Query(`SELECT name, applied_at FROM migrations ORDER BY name`)
if err != nil {
return "", err
}
defer rows.Close()
h := sha256.New()
for rows.Next() {
name, appliedAt := "", ""
err = rows.Scan(&name, &appliedAt)
if err != nil {
return "", err
}
fmt.Fprintf(h, "%s %s\n", name, appliedAt)
}
return hex.EncodeToString(h.Sum(nil))[:16], rows.Err()
}
func (s *Store) Schema() string {
return s.schema
}
func migrate(db *sql.DB, dataDir string) error {
+139
View File
@@ -0,0 +1,139 @@
import Foundation
private let encoder: JSONEncoder = {
let e = JSONEncoder()
e.dateEncodingStrategy = .secondsSince1970
return e
}()
private let decoder: JSONDecoder = {
let d = JSONDecoder()
d.dateDecodingStrategy = .secondsSince1970
return d
}()
final class EventCache: @unchecked Sendable {
private let dir: URL
private let queue = DispatchQueue(label: "run.fc.byre.events")
private var lastSeqs: [String: Int64] = [:]
private var generation = 0
init(host: String) {
dir = URL.applicationSupportDirectory.appending(path: "run.fc.byre.mac/events/\(host)", directoryHint: .isDirectory)
}
func open(schema: String) {
queue.async {
self.generation += 1
let fm = FileManager.default
let marker = self.dir.appending(path: "schema")
if (try? String(contentsOf: marker, encoding: .utf8)) == schema {
return
}
self.lastSeqs = [:]
try? fm.removeItem(at: self.dir)
do {
try fm.createDirectory(at: self.dir, withIntermediateDirectories: true)
try schema.write(to: marker, atomically: true, encoding: .utf8)
} catch {
log.error("open event cache: \(errorText(error), privacy: .public)")
}
}
}
func load(_ taskId: String) async -> (events: [ByreEvent], generation: Int) {
await withCheckedContinuation { c in
queue.async {
let events = self.read(taskId)
self.lastSeqs[taskId] = events.last?.seq ?? 0
c.resume(returning: (events, self.generation))
}
}
}
func append(_ taskId: String, _ events: [ByreEvent], generation: Int) {
queue.async {
guard generation == self.generation, let last = self.lastSeqs[taskId] else {
return
}
let fresh = events.filter { $0.seq > last }
guard let newest = fresh.last else {
return
}
do {
try self.write(taskId, fresh)
self.lastSeqs[taskId] = newest.seq
} catch {
log.error("cache events of \(taskId, privacy: .public): \(errorText(error), privacy: .public)")
self.forget(taskId)
}
}
}
func drop(_ taskId: String) {
queue.async {
self.forget(taskId)
}
}
func keep(_ taskIds: Set<String>) {
queue.async {
let files = (try? FileManager.default.contentsOfDirectory(at: self.dir, includingPropertiesForKeys: nil)) ?? []
for url in files where url.pathExtension == "ndjson" {
let taskId = url.deletingPathExtension().lastPathComponent
if !taskIds.contains(taskId) {
self.forget(taskId)
}
}
}
}
private func file(_ taskId: String) -> URL {
dir.appending(path: "\(taskId).ndjson")
}
private func forget(_ taskId: String) {
lastSeqs[taskId] = nil
try? FileManager.default.removeItem(at: file(taskId))
}
private func read(_ taskId: String) -> [ByreEvent] {
let url = file(taskId)
guard let data = try? Data(contentsOf: url) else {
return []
}
var events: [ByreEvent] = []
var kept = 0
for line in data.split(separator: 0x0a, omittingEmptySubsequences: false) where !line.isEmpty {
guard let ev = try? decoder.decode(ByreEvent.self, from: line) else {
break
}
events.append(ev)
kept = min(line.endIndex + 1, data.endIndex) - data.startIndex
}
if kept < data.count, let h = try? FileHandle(forWritingTo: url) {
try? h.truncate(atOffset: UInt64(kept))
try? h.close()
}
return events
}
private func write(_ taskId: String, _ events: [ByreEvent]) throws {
var data = Data()
for ev in events {
data.append(try encoder.encode(ev))
data.append(0x0a)
}
let url = file(taskId)
let fm = FileManager.default
if !fm.fileExists(atPath: url.path), !fm.createFile(atPath: url.path, contents: nil) {
throw ApiError(status: 0, message: "create \(url.path)")
}
let h = try FileHandle(forWritingTo: url)
defer {
try? h.close()
}
try h.seekToEnd()
try h.write(contentsOf: data)
}
}
+4 -1
View File
@@ -42,6 +42,7 @@ final class Feed {
@ObservationIgnored private var scheduled = false
@ObservationIgnored private var toolNames: [String: String] = [:]
@ObservationIgnored private let shown: () -> Bool
@ObservationIgnored private var persist: ([ByreEvent]) -> Void = { _ in }
init(shown: @escaping () -> Bool) {
self.shown = shown
@@ -60,12 +61,13 @@ final class Feed {
}
}
func load(_ history: [ByreEvent], inherited: Int = 0) {
func load(_ history: [ByreEvent], inherited: Int = 0, persist: @escaping ([ByreEvent]) -> Void = { _ in }) {
if ready {
return
}
ready = true
self.inherited = inherited
self.persist = persist
buffer = history + buffer
flush()
}
@@ -131,6 +133,7 @@ final class Feed {
return
}
events.append(contentsOf: fresh)
persist(fresh)
let found = findCaptures(fresh)
if !found.isEmpty {
captures.append(contentsOf: found)
+5 -1
View File
@@ -173,6 +173,7 @@ final class Connection {
let overlays = Overlays()
let forwards: Forwards
let pages: Pages
let cache: EventCache
var phase: Phase = .starting
@ObservationIgnored private var stopped = false
@ObservationIgnored private var restarting = false
@@ -181,6 +182,7 @@ final class Connection {
self.origin = origin
forwards = Forwards(host: origin.host()!)
pages = Pages(api: API(origin: origin))
cache = EventCache(host: origin.host()!)
Task {
await start()
}
@@ -231,9 +233,10 @@ final class Connection {
}
private func attempt(_ old: Store?) async -> Bool {
let store = Store(api: API(origin: origin), images: old?.images ?? ImageDrafts(), forwards: forwards, pages: pages)
let store = Store(api: API(origin: origin), images: old?.images ?? ImageDrafts(), forwards: forwards, pages: pages, cache: cache)
do {
let status = try await store.api.client.getStatus().ok.body.json
cache.open(schema: status.schema)
async let me: Void = store.loadMe()
async let projects: Void = store.loadProjects()
async let inbox: Void = store.loadInbox()
@@ -254,6 +257,7 @@ final class Connection {
store.startGlobalStream(after: status.lastEventSeq) { [weak self] in
self?.reconnected(status.startedAt)
}
store.startFeeds()
store.remote.start()
forwards.sync(store.api, Set(store.activeTasks().map(\.task.id)))
Notifier.shared.deliver()
+23 -5
View File
@@ -92,9 +92,11 @@ final class Store {
let remote: Remote
let forwards: Forwards
let pages: Pages
let cache: EventCache
private var taskFeeds: [String: Feed] = [:]
@ObservationIgnored private var historyLoads: Set<ObjectIdentifier> = []
@ObservationIgnored private var feedsStarted = false
@ObservationIgnored private var requestSeqs: [String: Int] = [:]
@ObservationIgnored private var taskReloads: Set<String> = []
@ObservationIgnored private var listReloads: Set<String> = []
@@ -109,11 +111,12 @@ final class Store {
let changesMemory = ChangesMemory()
@ObservationIgnored private var localSends: [String: LocalSends] = [:]
init(api: API, images: ImageDrafts, forwards: Forwards, pages: Pages) {
init(api: API, images: ImageDrafts, forwards: Forwards, pages: Pages, cache: EventCache) {
self.api = api
self.images = images
self.forwards = forwards
self.pages = pages
self.cache = cache
leadFeed = Feed(shown: { true })
paneFocus = PaneFocus(api: api)
remote = Remote(api: api)
@@ -443,8 +446,10 @@ final class Store {
private func applyTasks(_ projectId: String, _ list: [ByreTask]) async throws {
tasks[projectId] = list.sorted { $0.createdAt > $1.createdAt }
for t in list where t.status == .active && !t.background {
_ = taskFeed(t.id)
if feedsStarted {
for t in list where t.status == .active && !t.background {
_ = taskFeed(t.id)
}
}
dropClosedFeeds()
drafts?.prune(self)
@@ -622,7 +627,8 @@ final class Store {
}
private func loadHistory(_ taskId: String, _ feed: Feed) async throws {
var got: [ByreEvent] = []
let (cached, generation) = await cache.load(taskId)
var got = cached
while true {
let page = try await api.client.listTaskEvents(path: .init(taskId: taskId), query: .init(after: got.last?.seq ?? 0, limit: historyPage)).ok.body.json
got += page
@@ -630,7 +636,18 @@ final class Store {
break
}
}
feed.load(got, inherited: got.prefix { $0.taskId != taskId }.count)
feed.load(got, inherited: got.prefix { $0.taskId != taskId }.count, persist: { [cache] events in
cache.append(taskId, events, generation: generation)
})
}
func startFeeds() {
feedsStarted = true
let active = activeTasks()
cache.keep(Set(active.map(\.task.id)))
for (_, task) in active where !task.background {
_ = taskFeed(task.id)
}
}
private func taskFeed(_ taskId: String) -> Feed {
@@ -658,6 +675,7 @@ final class Store {
for taskId in taskFeeds.keys {
if taskId != selectedTaskId, let task = findTask(taskId), task.status != .active {
taskFeeds[taskId] = nil
cache.drop(taskId)
}
}
}