Skip to content

Bound the orchestrator's process-lifetime collections - #534

Merged
TheGreatAxios merged 3 commits into
mainfrom
cl-7229-bound-orchestrator-maps
Aug 31, 2026
Merged

TheGreatAxios merged 3 commits into
mainfrom
cl-7229-bound-orchestrator-maps

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Summary

CL-7229: postedApprovalIds and pendingDelegationThreads in
packages/chat/src/chat-orchestrator.ts grew one entry per approval /
delegation for the life of the hub process, never pruned. Bounds both
with @corbits/collections' createExpiringMap (already consumed by
crypto-cache.ts and workflow-routes.ts), per the ruling: event-driven
delete plus a TTL backstop, not TTL-only, not unbounded.

pendingDelegationThreads

Already deleted on the event that matters — threadDelegatedReply
consumes and deletes the entry the moment the delegated specialist's
first reply lands. What was missing is a bound for the case where that
event never fires (the specialist never wakes, crashes, or was mentioned
by mistake). Now TTL-bounded at AGENT_TURN_STALE_MS, the same
threshold agent-turns.ts already uses to call a running turn no
longer believable. A delegation nobody answers within that window is
already considered abandoned elsewhere in the system, so evicting it
can't drop a reply a running turn still needs — a very late reply still
posts, it just lands unthreaded instead of nested under the delegating
message. Cosmetic degrade, never a lost message.

postedApprovalIds

Every gate the reactor blocks on — approval gates included — resolves
out of "pending" on its own within DEFAULT_GATE_TIMEOUT_MS (one
hour, vendor/intx/inference/src/reactor.ts), either by a human's
decision or by timing out to a terminal "timeout"/"expired" status.
So a guard entry older than that bound is guaranteed to belong to an
approval that's no longer pending. TTL is set to double the gate timeout
plus a margin. Eviction can never reopen the duplicate-card hole this
guard exists to close: postApproveBlock re-reads the approval's live
status on every call, independent of the in-memory guard, and bails out
on anything but "pending" regardless of whether the guard remembers
it — the guard is only ever an optimization on top of that authoritative
check, never the sole source of correctness.

Test plan

  • bun run typecheck
  • HUB_DATA_DIR=$(mktemp -d) bun test packages/chat/ — 757 pass, 0 fail
  • New red/green tests prove both eviction (entry actually ages out)
    and safety at the boundary (an evicted, still-pending-looking entry
    never causes a duplicate post or a lost delegation reply)
  • bunx prettier --check .
  • bun run check:structural (ignoring check:report-error, which
    fails on main independent of this change)

https://linear.app/abklabs/issue/CL-7229/bound-the-orchestrators-process-lifetime-collections

Proves eviction actually happens for postedApprovalIds and
pendingDelegationThreads, and that eviction is safe: a redelivered
gate-blocked event for a resolved approval still posts nothing once its
guard entry expires, and an abandoned delegation's reply still posts —
just unthreaded — once its entry expires.
Both grew one entry per approval/delegation for the life of the hub
process, never pruned. Bound each with @corbits/collections'
createExpiringMap rather than a plain Set/Map:

- pendingDelegationThreads: already deleted on the event that matters
  (the specialist's first reply consumes it); now also TTL-bounded at
  AGENT_TURN_STALE_MS, the same threshold agent-turns.ts already uses
  to call a running turn no longer believable. A delegation nobody
  answers within that window is already considered abandoned elsewhere
  in the system, so evicting it can't drop a live reply — a very late
  reply still posts, just unthreaded instead of nested under the
  delegating message.
- postedApprovalIds: every gate the reactor blocks on resolves out of
  "pending" on its own within one hour (DEFAULT_GATE_TIMEOUT_MS), so a
  guard entry older than that is guaranteed to belong to a
  non-pending approval. TTL is set well past that bound; eviction can
  never reopen the duplicate-card hole this guard exists to close,
  because postApproveBlock re-reads the approval's live status
  independently of the guard on every call.
@TheGreatAxios
TheGreatAxios merged commit cbfc601 into main Aug 31, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant