Proposal: centralize daemon retirement and session lifetime ownership
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
- Issue type
- Refactor
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- node.js, typescript
- Domain
- backend-api-design, infrastructure
Research direction
Start by rechecking the cited source inventories against main at c237027737, then read the existing registration module and the proposed src/daemon-registration.ts boundary. Resolve the host-kit lock algorithm and mixed-version policy before implementing the daemon retirement and session-lifetime APIs. Done requires the settled APIs, ownership checks, cleanup sequences, and regression tests described in the issue.
Written by the indexing model from the issue text.
Description
Put identity checks, sequencing and cleanup inside the functions that own the work. Callers should be able to retire a daemon or finish a session resource with one operation, without repeating the safety rules.
Priority: registration and the daemon lock first, session lifetimes second. The payoff is correctness and fewer caller obligations. Changing classes to factories is optional.
Status: needs triage. Two design gates remain before registration integration:
- Specify and prove the hardened host-kit lock algorithm.
- Choose mixed-version exclusion or an unsupported-concurrency policy that the cutover can enforce.
The other decisions below are settled; their APIs and tests still need implementation.
Baseline: main at c237027737, verified 2026-10-02. The cited source files are unchanged from e4716e6f51. Recheck the inventories against each implementation base.
Make each function own the whole operation
The repeated problem is an address passed separately from the resource: registration paths plus a process identity, or a session name plus a record. Bind them at the owning operation.
| Caller | Operation | What the operation owns |
|---|---|---|
| Client lifecycle | stopAndRetireDaemon / recoverAbandonedDaemonRegistration |
Process proof, stop policy, lock acquisition, metadata cleanup and result |
| Daemon lifecycle | Registration functions bound to its acquired lock | Publication, shutdown reports, own metadata retirement and release |
| Routes and session lifecycle | Recording, resource and retirement functions taking SessionRef |
Lifetime check, latest record, field transition and artifact address |
| Device-claim recovery | Artifact path functions | Validation and path layout, without constructing a session store |
flowchart TD
C["Client lifecycle"] --> R["Shared registration jobs"]
D["Daemon lifecycle"] --> R
R --> H["host-kit process identity and lock"]
Q["Routes and session lifecycle"] --> J["Recording and resource jobs"]
J --> S["One session lifetime owner"]
J --> A["Artifact paths and journal operations"]
O["Device-claim recovery"] --> A
Use ordinary functions and composition. Keep private state where ownership needs it, in a small class or factory. Keep adapters that translate records or restrict authority; replacing one broad store with several broad objects does not reduce caller obligations.
Retire a daemon safely
Deepen the existing registration module into a shared root module, such as src/daemon-registration.ts. It already distinguishes matching, replaced, unproven, absent, unreadable and ownerless records. Client runtime imports from daemon internals are forbidden, so this implementation must sit above both.
Move takeover, replay shutdown, failed-startup cleanup and timeout reset to the shared operations. Timeout reset starts a fallback stop without awaiting it, then removes metadata. Awaiting the existing stop function is also insufficient: it can decline signaling or exhaust its exit wait.
Return proof and cleanup outcomes
The proposed boundary is:
type DaemonRetirementInput = Readonly<{
paths: DaemonPaths;
observed: DaemonRegistrationObservation;
mode: 'graceful' | 'force';
ownedStateDir?: OwnedReplayStateDir;
}>;
declare function stopAndRetireDaemon(
input: DaemonRetirementInput,
): Promise<DaemonRetirementResult>;
declare function recoverAbandonedDaemonRegistration(
input: Omit<DaemonRetirementInput, 'mode'>,
): Promise<DaemonRetirementResult>;
type ConfirmedDaemonTermination = Readonly<{
owner: DaemonProcessIdentity;
kind: 'exited' | 'already-dead';
}>;
type DaemonRetirementResult =
| {
status: 'retired';
termination: ConfirmedDaemonTermination;
removedInfo: boolean;
removedStateDir: boolean;
}
| { status: 'absent' }
| {
status: 'retained';
termination?: ConfirmedDaemonTermination;
reason:
| 'ownership-unproven'
| 'registration-replaced'
| 'stop-failed'
| 'exit-unconfirmed'
| 'lock-busy'
| 'metadata-unreadable'
| 'retirement-unconfirmed';
};
Finalize the observation, private-directory capability, partial-failure details and error mapping before implementation. absent means metadata is absent under the lock; it does not prove process exit or authorize directory removal.
The function owns signaling and configured TERM/KILL wait budgets:
| Caller | Mode | Policy |
|---|---|---|
| Timeout reset | force |
Verify identity, send KILL immediately, await exit |
| Takeover and ordinary shutdown cleanup | graceful |
Verify identity, send TERM, wait, escalate to KILL for the same lifetime, await exit |
| Abandoned-registration recovery | No stop mode | Prove abandonment; never signal a live process |
No caller pre-signals or starts a fire-and-forget fallback.
Use one retirement sequence
- Confirm exit. Validate the observed process lifetime before signaling. Proven death skips signaling. Missing process start time, unreadable liveness, failed signaling without independent exit proof, or exhausted waits retain metadata and the owned directory.
- Acquire the startup lock. Use a bounded wait; host-kit alone reclaims proven dead claims. Lock release by a still-running daemon is insufficient.
- Re-read and retire under the lock. Remove
daemon.jsononly if it still names the confirmed dead identity. Retain replaced, unreadable or unproven metadata. - Release this acquisition. Report success only when the postconditions are confirmed. Keep typed partial outcomes for metadata or release failures.
Abandoned-registration recovery uses the same protected core. It never rediscovers and stops a live winner. Even an absent result requires acquisition and inspection under the lock.
sequenceDiagram
participant C as Client retirement job
participant P as Observed daemon lifetime
participant L as Shared registration lock
participant M as Daemon metadata
C->>P: Verified stop and awaited exit
P-->>C: Confirmed termination
C->>L: Acquire; host-kit recovers dead claim
L-->>C: Held acquisition
C->>M: Read current registration
alt Record names the dead identity
C->>M: Remove while lock is held
else Replaced or unproven
Note over C,M: Retain current metadata
end
C->>L: Release this acquisition
Process identity permits signaling and matching a record. Lock possession prevents concurrent mutation. Both are required; checking identity and then unlinking without the lock leaves a race.
Bind daemon writes to its acquired lock
Daemon-side registration functions close over the actual acquisition. They assert it is still held immediately before each metadata/report mutation. Never reconstruct release authority from a path or PID.
Preserve these sequences:
- Startup: acquire → clear shutdown report → publish registration.
- Shutdown: finish teardown → write report → retire own metadata → release.
Report clearing happens before daemon.json exists, so metadata ownership cannot authorize it. A released or lost acquisition cannot write or clear a report. Keep report readers, contents and best-effort reporting, and preserve primary errors plus normalized hint, diagnosticId, logPath and typed details.
Retire only the private replay directory you own
The directory capability comes from private mkdtemp creation and binds to its startup/daemon attempt. Before deletion:
- Confirm that daemon exited and all owned startup attempts completed.
- Acquire the registration lock inside the directory.
- Inspect repair evidence and retain held repair sessions, active close-less replay sessions and unrecovered repair-commit-failure evidence.
- Remove the eligible directory while holding the lock, then release.
Use the same lock for exclusion. This authority does not permit recursive deletion of a shared state directory. The immediate defect is deleting a private replay directory beneath its still-running daemon; normal successor occupancy has not been established.
Host-kit release already handles a missing owner file as not-owner without recreating the path. Add a regression test after guarded directory removal; no new release behavior is needed.
Relaunch startup after a transient holder releases
Failed-startup cleanup stays bound to its own attempt and cannot stop or remove a winning contender. The existing startup wait handles another daemon as holder; the replacement must also handle a client holding the lock during retirement:
- A child that loses nonblocking acquisition reports a dedicated
lock-busydisposition through a shared named exit code and monitored launch result. Generic early exits, clean exits and stderr text are not contention proof. - Join the losing child. Wait while inspection says held/publishing, regardless of the holder's role. Unknown inspection retains state and reports diagnostics.
- Attach if a winner publishes a usable registration, preserving version, code-signature and policy admission.
- If the claim is released or proven dead without a usable registration, launch a fresh attempt. Only acquisition reclaims dead claims.
- Keep the same startup deadline and existing bounded attempt budget across relaunches. Join every loser; exhaustion returns a typed failure and leaves the winner untouched.
Do not add daemon/retirement roles to host-kit's generic owner record.
After migration, delete raw info/lock removal exports, cleanupStaleDaemonLockIfSafe and recoverDaemonLockHolder. Every metadata/report mutation belongs to a held registration owner; every lock reclaim belongs to host-kit acquisition.
Replace the daemon lock
The bespoke daemon lock permits both contenders to succeed:
- A second contender unlinks the first contender's incomplete JSON publication.
- Two contenders read an abandoned claim; the second unlinks the first's replacement claim.
These are demonstrated unsafe interleavings, not frequency measurements. Use one host-kit process lock for the daemon's whole lifetime, with acquisition identity for reclaim/release.
The host-kit primitive needs:
| Requirement | Contract |
|---|---|
| Safe publication and reclaim | A pause beyond publication grace cannot let an old claimant overwrite a successor. A reclaim mutex cannot be stolen solely because its live holder paused. Specify and prove the algorithm. |
| Nonblocking startup acquisition | Make one real attempt with typed acquired/busy/unproven outcomes and no polling or sleep. timeoutMs: 0 makes zero attempts in the existing loop. Bounded client acquisition uses the same protected reclaim mechanism. |
| Read-only holder inspection | Report held, publishing, absent and unreadable/unknown claims, with available liveness facts. A claim may precede metadata. Keep parsing/layout private; inspection does not authorize unlinking. |
| Lock ownership assertion | Supply an acquisition-bound assertion for registration/report mutations alongside the release function. |
| Unknown-liveness handling | Retain the claim and emit a typed reason plus an operator hint: restore inspection or stop a verified owner, then retry. Manual recovery requires external confirmation that all users of the state directory stopped. No automatic age-only or forced-delete fallback. |
isAgentDeviceDaemonProcess(...) === false also covers missing start time, missing command and unreadable identity. Reclaiming on that value is unsafe. Blocking uncertain holders with diagnostics is a deliberate behavior change.
Remove the duplicate server lock type and client lock parser at cutover. Lock version has no reader and startedAt is parsed but unused; do not copy them into host-kit. Preserve daemon.json version/code-signature negotiation.
Cutover remains open. Checking that the legacy path is absent before acquiring a different path cannot exclude an older daemon starting afterward. Choose actual mixed-version exclusion or enforceable refusal under an explicit unsupported-concurrency policy. Test old daemons already running and starting concurrently. Keep legacy files out of host-kit's stray reclamation. Use one steady-state lock owner and base the legacy sunset on the support policy, not a release count. Verify tag/release history before claiming compatibility obligations.
Host-kit hardening and registration design can proceed in parallel. Integration waits for the proven primitive, inspection/assertion APIs and cutover.
Keep session work tied to one lifetime
SessionRef captures a stored address and a mutable record. Lookup/list/find return fresh wrappers; rebuilding a record does not retarget an older wrapper.
Keep three concepts distinct:
| Concept | Meaning |
|---|---|
| Address | The store key, such as cwd:<hash>:default; use it for paths, journals and store operations |
| Lifetime | One occupant of that address, from publication to retirement |
| Record | That lifetime's session state, which can be rebuilt |
Repeated successful open rebuilds the same lifetime. Keep its resources, recording handle, actions, creation time, claims and idle-expiry handling. Preserve record construction and the second-open script-authoring abort rule, which differs from screen recording. Delete followed by publication at the same address creates a new lifetime. No inventoried caller needs a separate occupied-address/new-lifetime replace operation.
Store one stable entry per lifetime
Replace the existing map with stable entries. Each ref carries the entry's opaque identity and its captured record; .session stays a captured record, not a live getter.
type SessionRef = Readonly<{
address: string;
session: SessionState;
lifetime: object;
}>;
type SessionEntry = { current: SessionState };
const entries = new Map<string, SessionEntry>();
function lookup(address: string): SessionRef | undefined {
const entry = entries.get(address);
return entry
? { address, session: entry.current, lifetime: entry }
: undefined;
}
function resolveCurrent(ref: SessionRef): SessionState | undefined {
const entry = entries.get(ref.address);
return entry === ref.lifetime ? entry.current : undefined;
}
Keep entry contents and construction private. This is the canonical map, with no second registry or generated generations.
The store owns three operations:
- Publish: refuse an occupied address and honor daemon shutdown/admission.
- Update: require the captured lifetime; derive from its latest record; never insert a missing entry.
- Retire: check that lifetime before removing the record and its runtime hints.
stateDiagram-v2
[*] --> Draft
Draft --> Live: publish at the existing adoption point
Draft --> [*]: preparation or adoption fails
Live --> Live: rebuild record or reopen app
Live --> Retired: guarded deletion
Retired --> [*]
note right of Retired
Address reuse creates a new lifetime.
Old work gains no authority over it.
end note
Put lifetime checks inside session operations
Recording, resource finish and teardown accept one ref. They resolve the latest matching record, run the owning field transition and use ref.address internally.
- Recording owns recording transitions and journal append.
- Repair finalization owns synthetic-close recording, script publication, failure classification and tombstones.
- Resource functions bind their capture read/write/path adapters internally.
- Retirement keeps hint cleanup, repair outcomes and idle markers tied to the captured lifetime.
Old work cannot gain authority by looking up its address again. Audit refs held by request binding, lock policy, replay, inventory and asynchronous cleanup. Read projections may intentionally use the captured record; durable writers resolve the matching current record inside their owning functions.
Cleaning up an old captured resource can continue after retirement. Guard the store mutation separately: clearing a slot requires both the lifetime and matching resource identity. Preserve durable resource fences. If required capture adoption loses its lifetime, run existing failed-adoption disposal/recovery; do not report success or overwrite successor evidence.
Keep publication points unchanged:
| Session path | Publication point |
|---|---|
| Record-only capture | After successful adoption |
| Sessionless snapshot | After capture |
| Provisional open | Existing before-dispatch point |
Shutdown can overlap canceled handlers and timed-out cleanup. Updates cannot resurrect a retired entry. An unpublished draft also needs the existing shutdown/admission owner's check before first publication; entry identity alone cannot prevent that late publication.
Preserve execution locks, idle-reaper lock order, shutdown order, wire errors, hints and artifact naming. Keep close and teardown sequencing separate: platform close, browser cleanup and materialized paths differ.
Keep field ownership enforceable
Use one lifetime-checked update for published-record replacement. Field owners pass explicit patches; the store merges them into the latest matching record:
updateSession(ref, { appLogFailure: undefined });
Keep this operation private to the owner/adapters. Domain callers use functions such as clearSessionAppLogFailure(ref). Holding a ref does not authorize every field. Existing owner-controlled in-place transitions remain in their domain functions and resolve the matching latest record.
The R7 scanner checks assignments but misses whole-record spread overrides. appLogFailure is declared store-established, yet the app-log owner writes it through spreads. Declare app-log-session-resource.ts as its owner and keep resource-slot transitions in their field owner.
Extend R7 with two syntax rules:
- Check top-level update keys against
SESSION_STATE_FIELD_OWNERS. Accept an explicit patch literal or inline synchronous callback returning explicit named keys. Reject computed keys, patch spreads and opaque patch variables. - Allow whole-
SessionStatespreads from an existing record only insidesession-store.tsand declared draft constructors for open, sessionless snapshot and record-only capture. Existing-session branches use patches. Nested value spreads, such as a lease, remain allowed.
A callback reads the latest record when derivation needs it. Keep it synchronous and non-reentrant: one ownership check is enough. Do not add asynchronous callbacks or general value tracing.
Migrate sessionSlot.replace whole-session copies to explicit slot patches through a ref-bound capability. Capture-kit supplies the resource transition; the daemon adapter supplies its named field update. Do not bypass the update with a constructed whole record.
R7 fixtures must reject the existing app-log spread, unauthorized failure patches, computed keys, spread patches and opaque patches; pass the declared owner's explicit patch; and retain direct-assignment checks.
Migrate the callers together
There are 19 production session-record set sites: 16 in the daemon and three in capture-kit. Seven re-set the same object, eight rebuild it, and four mix creation with rebuilding. There are six direct delete callers. Exclude tests, runtime hints and internal Map operations.
Set inventory: all 19 sites and their migration
| Site(s) | Migration |
|---|---|
| interaction/index.ts:67 | Remove same-object re-set after migrating snapshot mutation and reverse-lookup fallback |
| interaction-runtime.ts:71 | Migrate owner write; remove re-set |
| selector-capture-runtime.ts:335 | Migrate owner write; remove re-set |
| selector-runtime-backend.ts:84 | Migrate owner write; remove re-set |
| selector-runtime.ts:94 | Migrate ref-frame write; remove re-set |
| request-execution-scope.ts:493 | Migrate recording-health write; remove re-set |
| session-replay-command.ts:115 | Migrate coordinator write; remove re-set |
| app-log-session-resource.ts:120, 138 | Same-lifetime patches for failure set/clear |
| session-app-deployment.ts:91 | Same-lifetime Harmony app identity patch |
| session-selector-dispatch.ts:99 | Same-lifetime patch from command result |
| lease-lifecycle.ts:129 | Same-lifetime lease refresh |
| session-perf-runtime.ts:271 | Same-lifetime profile patch; remove address-only fallback |
| capture transitions.ts:193, 232 | Clear only the matching lifetime/resource slot |
| session-open-execution.ts:329, 356 | Separate fresh publication from existing/provisional updates in each branch |
| snapshot-command-runtime.ts:147 | Publish sessionless capture after capture; otherwise update captured lifetime |
| capture adoption.ts:45 | Publish record-only draft; otherwise update after successful adoption |
Delete inventory: all six callers and the policies to preserve
| Site | Retirement policy |
|---|---|
| record-runtime.ts:255 | Retire record-only lifetime after successful recording stop |
| record-runtime.ts:264 | On failed stop, retire only if its manifest is terminal |
| lease-lifecycle.ts:103 | Teardown and claim cleanup, then retire captured address/lifetime |
| daemon-runtime.ts:205 | Bounded teardown finalizes repair and retires even on cleanup timeout; guard later writes |
| daemon-session-idle-expiry.ts:494 | Settle resources/claim before repair, retirement and idle marker; preserve failed-settle retention |
| session-close.ts:343 | Preserve platform/resource teardown, provider/lease/claim handling and error reporting before guarded record/hint removal |
The two Map.delete operations inside SessionStore.delete remove hints and the record; they are not extra callers.
Pair all seven re-set removals with their owner-write migration. A stale re-set loses an intervening rebuild. Removing it alone loses the later mutation on a detached object instead. Route the write through the latest matching record, remove the re-set, and prove both updates survive. Keep all seven in the session migration.
The boundary changes are:
- Capture-kit:
DurableCaptureSessionStoreis only a narrowed type over the full store. Replace that dependency with a ref-bound read/write/path adapter that reports retired/resource-changed outcomes. Give drafts a separately admitted publication capability at the existing adoption point. - Command runtime: bind
createDaemonRuntimeSessionStoregetters and its three write projections to the captured lifetime. Screenshot's no-op setter is a read projection, not a twentieth set site. - Late updates: remove
get(address) ?? oldRecordfrom perf and teardown:34,211,226. It can reinsert a deleted record or adopt a successor. App-log patches must also preserve intervening resource updates. - Lease expiry: teardown and deletion use
session.nameinstead of the lookup's storedparams.sessionName. Useref.addressfor paths, slots and deletion; public name stays for display. Test a scoped leased address plus a separate slot under its public name. This is a helper-contract defect; production reachability with unequal names remains unproved.
Remove unrestricted upsert and resolveStoredSessionName after migration. Its object-identity scan and public-name fallback must not survive as compatibility. A rebuilt cwd-scoped default must use cwd:<hash>:default for journals, without creating a phantom public-name record.
Reduce the store interface in this change. Convert it to a factory only if that helps the resulting composition. Keep runtime projection, ref-frame authorization and capture merging adapters that still own useful behavior.
Land independent cleanups
These do not gate registration or include the seven re-set removals:
- Move canonical session-directory/app-log path functions into
src/daemon/session-artifact-paths.ts. Device-claim recovery constructs a store only for these paths; remove that dependency while preserving validation, stored addresses, layout and errors. - Replace
SessionStore.expandHomewith host-kit'sexpandSessionPathand remove the forwarder. - Optionally replace
SessionScriptWriterwithwriteSessionScript(sessionsDir, session, options). It stores one directory and its only production user is SessionStore. Keep formatting, no-clobber/force behavior, commit transitions, diagnostics, failure routing and public-name-based default filenames together.
Prove the behavior
Use deterministic interleavings and real temporary filesystem/process tests for exclusion and exit guarantees. A Map-only test cannot prove filesystem exclusion.
Registration regression checklist
| Scenario | Required result |
|---|---|
| Partial publication or two stale-lock reclaimers | At most one owner succeeds; loser cannot remove winner |
| Claimant/reclaimer pauses beyond grace | Cannot overwrite/release successor or lose reclaim mutex merely through age |
| Free, held and unproven nonblocking acquisition | One real attempt; no polling/sleep; typed outcome |
| Old daemon running or starting concurrently | Enforced cutover policy; no split exclusion or legacy stray reclamation |
| Replaced/unreadable metadata or unproved process start time | Retain/refuse; no unproved-owner signaling or destructive cleanup |
| Unknown lock identity/liveness | Retain state; typed reason and operator hint; no age-only fallback |
| Force reset versus graceful takeover | Operation owns verified KILL-first versus TERM-then-KILL and awaits exit; callers do not pre-signal |
| Signal failure or exhausted exit wait | Keep metadata/private directory unless independent death is proved |
| Losing startup attempt | Cleanup cannot stop/remove winner |
| Startup meets a client-held retirement lock | Typed busy; join loser; wait regardless of role; attach or relaunch with original deadline/attempt bounds |
| Non-contention early exit | Existing failure policy; no false busy classification or winner cleanup |
| Winner publishes after old daemon exits | Retirement re-reads under the lock and retains winner metadata |
| Startup clears report before metadata publication | Held acquisition authorizes clearing |
| Released/lost acquisition mutates report | Refused; successor report remains intact |
| Guarded private replay-directory deletion | Confirm exit and completion of owned startups; retain repair evidence; release does not recreate path |
Session regression checklist
| Scenario | Required result |
|---|---|
| Rebuild while a resource operation awaits | Latest matching record; preserve unrelated app-log/profile changes |
| Former same-object re-set after rebuild | Both rebuild and later owner mutation survive |
| Delete followed by address reuse | Old work cannot adopt, reinsert, delete successor or clear its hints/slots |
| Old captured resource needs cleanup | Dispose its handle without changing successor/durable evidence |
| Record-only capture fails or loses adoption admission | No premature publication; disposal/recovery evidence retained |
| Repeated open with active recording | Same lifetime/handle; authoring second-open rule preserved |
| Scoped leased address differs from public name | Addressed lifetime retires; public-name slot untouched |
| Rebuilt cwd-scoped session is recorded | Correct journal/address; no phantom public-name record |
| Shutdown overlaps canceled handler continuation | No resurrection or successor adoption; unpublished draft cannot publish after admission closes |
| Bounded cleanup times out, then finishes | Captured handle can finish; late capture/perf/app-log writes cannot reinsert or clear another lifetime |
| Snapshot/find/ref-frame/idle expiry | Issuance, locks, errors and cleanup order preserved |
| Forbidden R7 writer/patch syntax | Unauthorized assignments/keys and forbidden spreads/opaque/computed patches fail; owner's explicit patch passes |
Prove reachable schedules separately from interface hazards. Request locks serialize ordinary open/close. Shutdown is an exception: session teardown takes no execution locks, server close forces closure after five seconds, and socket closure cancels without joining handlers. Exercise that real transport path and a precise handler continuation; existing open cancellation checks can block a particular interleaving.
Regression tests must fail when the relevant ownership check is removed or cleanup is made unconditional. Plant the concrete forbidden app-log spread for R7. Colocate tests with source and retain existing tests for pure moves.
Each slice runs focused checks and pnpm check:affected --run on its exact head, plus the full deterministic gate for a broad refactor as repository guidance requires. Preserve CI-owned provider/native/device checks. Report production additions/deletions separately from tests and moves, and name the mechanism or caller obligation removed.
Deliver in dependency order
- Prove host-kit acquisition/reclaim; add nonblocking acquisition, inspection and lock ownership assertion. Settle mixed-version cutover.
- Finalize retirement inputs/results, stop modes, bounded relaunch and operator diagnostics; migrate registration/report operations. Remove raw cleanup/recovery exports and legacy lock types/parsers.
- Land independent path/forwarder cleanups; optionally simplify the script writer.
- Implement stable entries/captured refs, audit ref holders, and enforce explicit update keys/whole-session spread rules.
- Migrate all 19 set sites, six delete callers, recording/disposal inputs and runtime/capture adapters. Pair the seven re-set removals with owner writes; preserve adoption timing and resource fences. Remove upsert/reverse lookup and shrink the store interface.
- Complete the regression checklists and record exact-head validation, including real shutdown/cancellation and late cleanup.
Related work: #3102 already adds daemon metadata publication/removal fencing; deepen it. Coordinate timeout reset with #3105 and report mutations with #3104. Preserve the #2833 idle-reaper lock seam/order. This follows #3069 without reopening it or closing related issues by association.
Host-kit hardening and registration design can proceed in parallel; integration waits for the two design gates. Session migration follows the lifetime contract. Split issues/PRs must name exact prerequisites and branch bases, rather than depend on an uncommitted sibling fix.
Out of scope: a generic request executor, selector-observation aggregation, global immutable-state migration, actors/manager frameworks and promised line-count/performance gains. Request finalization and diagnostics may be separate bounded follow-ups; they do not gate this work.
- Dominant language
- TypeScript
- Stars
- 4.8k
- Forks
- 315
- Avg merge
- 11h 17m
- Merged PRs (30d)
- 536
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from callstack/agent-device
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
callstack/agent-device#1869 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 20/100
callstack/agent-device#3106 ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 68/100
callstack/agent-device#3105 ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 72/100
callstack/agent-device#3104 ·
Maintainers usually reply within 1 day
-
needs-triage
Difficulty 4/5 3-5 days Newbie friendliness 38/100
callstack/agent-device#3077 ·
Maintainers usually reply within 1 day
All issues in callstack/agent-device
Similar issues
-
check:failed streams:add
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
iptv-org/iptv#53974 · 1 comment ·
Maintainers usually reply within 1 day
-
type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
interledger/rafiki#3986 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Doist/todoist-cli#576 ·
Maintainers usually reply within 1 day
-
feature
Difficulty 1/5 Under an hour Newbie friendliness 72/100
vercel-labs/skills#2370 ·
Maintainers usually reply within 1 day
-
🐛 Bug supabase/cli
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day