@@ -82,10 +82,73 @@ updated, err := service.UpdateQueuedAssignment(ctx, projectID, assignment.ID,
8282The update is compare-and-set: it preserves lifecycle/execution fields and
8383returns ` ErrConflict ` if another writer changed or claimed the assignment.
8484This prevents a stale editor from releasing a claim that may already have
85- started execution. After a claim, use ` Service.PrepareAssignment ` to attach a
86- host execution reference or context snapshot while verifying the expected
87- claim owner. Snapshot import remains an administrative/offline workflow and is
88- not exposed as a live whole-assignment mutation.
85+ started execution. Snapshot import remains an administrative/offline workflow
86+ and is not exposed as a live whole-assignment mutation.
87+
88+ ### Portable worker claims and host authority
89+
90+ Portable MCP workers use a fenced pre-start claim rather than relying on the
91+ caller-chosen ` claimed_by ` label:
92+
93+ ``` text
94+ queued ── claim ──> claimed (lease active) ── start ──> running (fence retained)
95+ │
96+ └── expiry + explicit recover ──> queued
97+ ```
98+
99+ 1 . ` Service.ClaimAssignmentWithLease ` returns an assignment whose ` claim `
100+ contains a server-generated ` id ` , ` acquired_at ` , and ` expires_at ` .
101+ 2 . While status is ` claimed ` , call ` Service.RenewAssignmentClaim ` before the
102+ expiry when provisioning or context preparation may take longer than the
103+ configured TTL. ` WithAssignmentClaimLeaseTTL ` changes the default five
104+ minutes for an embedding server; ` coordination.capabilities ` reports the
105+ effective value. Positive custom values are rounded up to whole seconds
106+ with a one-second minimum.
107+ 3 . Pass the exact claim id to ` PrepareAssignmentWithClaim ` ,
108+ ` ReleaseAssignmentWithClaim ` , ` UpdateAssignmentStatusWithClaim ` , and
109+ ` CompleteAssignmentWithClaim ` . Missing ids are invalid; expired or
110+ superseded ids conflict.
111+ 4 . When work advances out of ` claimed ` , Cairnline retires the reservation
112+ expiry but retains its id as a fencing generation for later worker writes.
113+
114+ Claim ids are concurrency values, not authentication credentials. They may be
115+ stored in portable snapshots and returned in assignment reads. Hosts still
116+ decide which principals may invoke these methods or their MCP tools. For
117+ content-only MCP clients, ` assignments.get ` and ` assignments.list ` include
118+ ` claim_id ` plus ` claim_expires_at ` while the reservation is active, or
119+ ` claim_fence ` after work starts.
120+
121+ If a pre-start claim expires, it stays ` claimed ` until an authorized host or
122+ operator calls ` RecoverAssignmentClaim ` with the exact expired id. Recovery
123+ requeues it and clears prepared execution/context references. This operation is
124+ explicit so the host can reconcile any resources it created during preparation.
125+ ` projects.operations_brief ` and ` projects.health ` expose the stable
126+ ` recover_assignment_claim ` action hint for this state.
127+ It is never valid for ` running ` , ` awaiting_approval ` , or ` awaiting_review ` work:
128+ Cairnline cannot determine that host execution is dead and never cancels or
129+ requeues it automatically.
130+
131+ The original ` ClaimAssignment ` , ` PrepareAssignment ` , ` ReleaseAssignment ` ,
132+ ` UpdateAssignmentStatus ` , and ` CompleteAssignment ` methods remain
133+ embedding-host authority surfaces for trusted reconciliation and existing
134+ Hecate integration. They bypass worker fencing by design and must not be
135+ exposed directly as agent tools. Embedders with custom ` Store `
136+ implementations must implement the new claim-lease methods before upgrading.
137+
138+ Breaking (alpha): the portable worker claim contract is now fenced.
139+ ` assignments.prepare ` and ` assignments.release ` take ` claim_id ` instead of
140+ ` claimed_by ` ; ` assignments.update_status ` and ` assignments.complete ` also
141+ require the current ` claim_id ` . ` assignments.claim ` returns that id in both its
142+ text and structured result. The public ` Store ` interface adds the seven leased
143+ claim/renew/recover and fenced mutation methods, so custom stores must implement
144+ them. Snapshot version 2 persists claim generations; version 1 imports remain
145+ supported only as unleased host-authoritative history.
146+
147+ SQLite migration and v1 snapshot import preserve existing claimed rows as
148+ unleased host-authoritative state; they do not invent an expiry or silently
149+ make old work stealable. Before handing one of those rows to an MCP worker, a
150+ trusted embedding host must reconcile/release it and let the worker claim it
151+ again under the leased contract.
89152
90153Handoff editors follow the same rule. Use ` Service.PatchHandoff ` ,
91154` Service.UpdateHandoffStatus ` , or ` Service.DeleteHandoff ` with the exact
@@ -131,6 +194,7 @@ Hosts that want agent-neutral interoperability should start here:
131194coordination.capabilities
132195assignments.next
133196assignments.claim
197+ assignments.renew_claim (only while still claimed and nearing expiry)
134198assignments.context
135199assignments.launch_packet
136200evidence.record
@@ -140,15 +204,22 @@ assignments.complete
1402041 . Call ` coordination.capabilities ` once during setup or health checks to learn
141205 the server contract and boundaries.
1422062 . Poll ` assignments.next ` with the host's available kind and skill ids.
143- 3 . Claim one assignment with ` assignments.claim ` .
207+ 3 . Claim one assignment with ` assignments.claim ` and retain the returned
208+ ` structuredContent.claim.id ` fencing value.
1442094 . Read ` assignments.context ` and/or ` assignments.launch_packet ` . Both include
145210 the project's enabled durable memory entries.
146- 5 . Build the host-native prompt/run packet from the structured metadata.
147- 6 . Record evidence as the run produces useful proof.
148- 7 . Complete, fail, or cancel the assignment explicitly.
149-
150- If the host crashes after claim, it should either resume by ` execution_ref ` or
151- release the claim when it knows work will not continue.
211+ 5 . Renew with ` assignments.renew_claim ` if the assignment remains ` claimed ` as
212+ expiry approaches. Pass the claim id to prepare, progress, and completion
213+ mutations.
214+ 6 . Build the host-native prompt/run packet from the structured metadata.
215+ 7 . Record evidence as the run produces useful proof.
216+ 8 . Complete, fail, or cancel the assignment explicitly.
217+
218+ If the host crashes before work starts, another authorized host may explicitly
219+ call ` assignments.recover_claim ` once the reservation expires, then claim the
220+ queued assignment again. If it crashes after work starts, the executing host
221+ must reconcile or resume through its ` execution_ref ` ; claim expiry never makes
222+ runtime work stealable.
152223
153224## Execution Ref And Approval Signal
154225
@@ -410,11 +481,14 @@ Before exposing Cairnline tools to an agent, a host should decide:
410481
411482- Which mutating tools the agent may call.
412483- Whether assignments can be claimed automatically or require operator review.
484+ - Which component renews pre-start claims, how it retains the non-secret claim
485+ id, and how it stops stale workers after a conflict.
413486- Whether evidence locators are only stored, rendered as text, or opened as
414487 links after scheme validation.
415488- Whether local roots are readable by the host, and under what path boundary.
416489- Whether skill metadata may trigger host-native instruction loading.
417- - How to recover or release claimed assignments after crashes.
490+ - How to reconcile prepared host resources before recovering an expired claim,
491+ and how to handle running work separately through host supervision.
418492- How to show operator confirmation for memory promotion and destructive
419493 project changes.
420494- Whether to render ` ui:// ` app views, and if so, only inside a sandboxed
@@ -428,6 +502,8 @@ A minimal useful integration does not need orchestration. It only needs:
428502- ` projects.list `
429503- ` assignments.next `
430504- ` assignments.claim `
505+ - ` assignments.renew_claim ` when pre-start work approaches expiry
506+ - ` assignments.recover_claim ` for explicit expired-claim recovery
431507- ` assignments.context `
432508- ` assignments.launch_packet `
433509- ` evidence.record `
0 commit comments