> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tai42.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Park index and park seams (tai42_kit.interactions)

> The shared park index, the terminal chain notice, and the give-up handler seam a driver that parks registers so an abandoned park's waiter is told.

The durable park index a driver keeps for the runs it parks on an async `ask`.

When a driver parks a run, the platform keeps only the interaction id; the driver reverses it
to its own run through this index. One consumer namespace holds, on one Redis:

* an ENTRY per resume key (an interaction id, or any key the consumer resumes by), carrying the
  consumer's own opaque rebuild payload;
* a BARRIER per parked super-step (a thread's set of asks resolved together): the consumer's
  opaque `expected` map, keyed by member id, the answers buffered so far, and any opaque
  consumer fields;
* a DRIVE LEASE per super-step, so exactly one worker drives a completed barrier, with a
  heartbeat and a token-checked release;
* a RESOLUTION RECORD per resolved super-step and a TOMBSTONE in place of every member entry,
  so a lapped redelivery replays what the first drive returned;
* a RUN-RESOLUTION INDEX per thread (every resolved super-step and its members), so a kill can
  reach every stored outcome of a run;
* a LIVE SET per thread (its super-steps whose barrier is live), so a checkpoint sweep can tell
  a parked thread from an abandoned one.

The module imports no Redis client at import time: the client is reached lazily, so a consumer
whose import graph excludes Redis may import this module. Every Redis error propagates.

## BARRIER\_TTL\_MARGIN\_SECONDS

`tai42_kit.interactions.park_index.BARRIER_TTL_MARGIN_SECONDS`

```python theme={null}
BARRIER_TTL_MARGIN_SECONDS: Final[int] = 3600
```

## DRIVE\_LEASE\_HEARTBEAT\_SECONDS

`tai42_kit.interactions.park_index.DRIVE_LEASE_HEARTBEAT_SECONDS`

```python theme={null}
DRIVE_LEASE_HEARTBEAT_SECONDS: Final[float] = 20.0
```

## DRIVE\_LEASE\_SECONDS

`tai42_kit.interactions.park_index.DRIVE_LEASE_SECONDS`

```python theme={null}
DRIVE_LEASE_SECONDS: Final[int] = 60
```

## ENTRY\_TTL\_FLOOR\_SECONDS

`tai42_kit.interactions.park_index.ENTRY_TTL_FLOOR_SECONDS`

```python theme={null}
ENTRY_TTL_FLOOR_SECONDS: Final[int] = 30 * 24 * 3600
```

## RESOLVED\_FIELD

`tai42_kit.interactions.park_index.RESOLVED_FIELD`

```python theme={null}
RESOLVED_FIELD: Final[str] = '__park_resolved__'
```

## Barrier

`tai42_kit.interactions._park_types.Barrier`

```python theme={null}
class Barrier
```

A super-step barrier: the consumer's `expected` map, the answers buffered so far, its own fields.

**Attributes**

| Attribute | Type |
| - | - |
| `expected` | `dict[str, Any]` |
| `outputs` | `dict[str, Any]` |
| `fields` | `dict[str, str]` |

## BarrierNotFoundError

`tai42_kit.interactions._park_types.BarrierNotFoundError`

```python theme={null}
class BarrierNotFoundError(ParkIndexError)
```

The super-step barrier an answer converges on is gone (expired or never written).

**Attributes**

| Attribute | Type |
| - | - |
| `thread_id` | — |
| `superstep` | — |

## BufferResult

`tai42_kit.interactions._park_types.BufferResult`

```python theme={null}
class BufferResult
```

Progress after an answer was buffered: answered members, all members, the still-open members in order.

**Attributes**

| Attribute | Type |
| - | - |
| `present` | `int` |
| `total` | `int` |
| `remaining` | `list[str]` |

## DriveInProgressError

`tai42_kit.interactions._park_types.DriveInProgressError`

```python theme={null}
class DriveInProgressError(ParkIndexError)
```

Another worker holds the super-step's drive lease.

Raised (never a benign return) so the platform keeps the caller's durable retry ticket and
redelivers until the live drive completes or its lease expires.

**Attributes**

| Attribute | Type |
| - | - |
| `thread_id` | — |
| `superstep` | — |

## DriveLease

`tai42_kit.interactions.park_index.DriveLease`

```python theme={null}
class DriveLease
```

A super-step's drive lease: `SET NX EX` to win it, a heartbeat while held, a token-checked release.

Win it by entering (`async with`) or `acquire`; leave it by exiting or `close`,
which stops the heartbeat and releases it unless `ParkIndex.finalize` consumed it.

**Attributes**

| Attribute | Type |
| - | - |
| `index` | — |
| `thread_id` | — |
| `superstep` | — |
| `token` | — |
| `acquired` | — |
| `consumed` | — |
| `lost` | — |
| `closed` | — |

### Members

#### acquire

`tai42_kit.interactions.park_index.DriveLease.acquire`

```python theme={null}
async acquire(self) -> DriveLease
```

Win the lease and start its heartbeat.

#### holds

`tai42_kit.interactions.park_index.DriveLease.holds`

```python theme={null}
async holds(self) -> bool
```

Whether this lease's token still holds the super-step right now.

#### release

`tai42_kit.interactions.park_index.DriveLease.release`

```python theme={null}
async release(self) -> None
```

Drop the lease if this token still holds it (a reclaimer's lease is never dropped).

#### close

`tai42_kit.interactions.park_index.DriveLease.close`

```python theme={null}
async close(self) -> None
```

Stop the heartbeat and release the lease once; a consumed lease is left as finalize left it.

A cancel of the calling task delivered while the heartbeat stops propagates, after the release.

#### mark\_consumed

`tai42_kit.interactions.park_index.DriveLease.mark_consumed`

```python theme={null}
async mark_consumed(self) -> None
```

Record that a finalize dropped the lease key; stops the heartbeat so no renew reports it lost.

## LeaseLostError

`tai42_kit.interactions._park_types.LeaseLostError`

```python theme={null}
class LeaseLostError(ParkIndexError)
```

The caller no longer holds the drive lease its write is guarded by; another writer owns the super-step.

**Attributes**

| Attribute | Type |
| - | - |
| `thread_id` | — |
| `superstep` | — |

## ParkIndex

`tai42_kit.interactions.park_index.ParkIndex`

```python theme={null}
class ParkIndex
```

One consumer's park index under its own key namespace on its own Redis.

**Attributes**

| Attribute | Type |
| - | - |
| `namespace` | — |

### Members

#### entry\_key

`tai42_kit.interactions.park_index.ParkIndex.entry_key`

```python theme={null}
entry_key(self, entry_id: str) -> str
```

The key of the entry stored for `entry_id`.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `entry_id` | `str` | — | — |

#### barrier\_key

`tai42_kit.interactions.park_index.ParkIndex.barrier_key`

```python theme={null}
barrier_key(self, thread_id: str, superstep: str) -> str
```

The key of a super-step's barrier hash.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |

#### lease\_key

`tai42_kit.interactions.park_index.ParkIndex.lease_key`

```python theme={null}
lease_key(self, thread_id: str, superstep: str) -> str
```

The key of a super-step's drive lease.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |

#### resolution\_key

`tai42_kit.interactions.park_index.ParkIndex.resolution_key`

```python theme={null}
resolution_key(self, thread_id: str, superstep: str) -> str
```

The key of a resolved super-step's resolution record.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |

#### run\_resolutions\_key

`tai42_kit.interactions.park_index.ParkIndex.run_resolutions_key`

```python theme={null}
run_resolutions_key(self, thread_id: str) -> str
```

The key of a thread's run-resolution index.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |

#### live\_set\_key

`tai42_kit.interactions.park_index.ParkIndex.live_set_key`

```python theme={null}
live_set_key(self, thread_id: str) -> str
```

The key of a thread's set of super-steps whose barrier is live.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |

#### persist

`tai42_kit.interactions.park_index.ParkIndex.persist`

```python theme={null}
async persist(
    self,
    *,
    thread_id: str,
    superstep: str,
    entries: Mapping[str, Mapping[str, Any]],
    expected: Mapping[str, Any],
    barrier_fields: Mapping[str, str] | None = None,
    entry_ttl: Mapping[str, int],
    barrier_ttl: int,
) -> None
```

Write a parked super-step's entries, its barrier and its live-set membership, all or nothing.

Each entry is written whole under its own TTL; the barrier holds `expected` (keyed by
member id) and `barrier_fields`; the thread's live set lasts as long as its longest-lived
barrier.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |
| `entries` | `Mapping[str, Mapping[str, Any]]` | — | — |
| `expected` | `Mapping[str, Any]` | — | — |
| `barrier_fields` | `Mapping[str, str] \| None` | `None` | — |
| `entry_ttl` | `Mapping[str, int]` | — | — |
| `barrier_ttl` | `int` | — | — |

#### threads\_with\_live\_barriers

`tai42_kit.interactions.park_index.ParkIndex.threads_with_live_barriers`

```python theme={null}
async threads_with_live_barriers(self, thread_ids: Sequence[str]) -> set[str]
```

The subset of `thread_ids` with at least one live barrier.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_ids` | `Sequence[str]` | — | — |

#### read\_entry

`tai42_kit.interactions.park_index.ParkIndex.read_entry`

```python theme={null}
async read_entry(self, entry_id: str) -> dict[str, Any] | None
```

The entry or tombstone stored for `entry_id`, or `None` when there is none.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `entry_id` | `str` | — | — |

#### tombstone\_kind

`tai42_kit.interactions.park_index.ParkIndex.tombstone_kind`

```python theme={null}
tombstone_kind(entry: Mapping[str, Any]) -> TombstoneKind
```

Whether a read entry is a live entry, a resolved tombstone, or a detached tombstone.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `entry` | `Mapping[str, Any]` | — | — |

#### tombstone\_coordinates

`tai42_kit.interactions.park_index.ParkIndex.tombstone_coordinates`

```python theme={null}
tombstone_coordinates(entry: Mapping[str, Any]) -> tuple[str, str]
```

The `(thread_id, superstep)` a resolved tombstone points at.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `entry` | `Mapping[str, Any]` | — | — |

#### read\_tombstone\_resolution

`tai42_kit.interactions.park_index.ParkIndex.read_tombstone_resolution`

```python theme={null}
async read_tombstone_resolution(self, entry: Mapping[str, Any]) -> ResolutionRecord | None
```

The record a tombstone resolves to: the stored record of a resolved one, `None` for a detached one.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `entry` | `Mapping[str, Any]` | — | — |

#### read\_barrier

`tai42_kit.interactions.park_index.ParkIndex.read_barrier`

```python theme={null}
async read_barrier(self, thread_id: str, superstep: str) -> Barrier | None
```

A super-step's barrier, or `None` when it is gone; buffered answers are decoded.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |

#### set\_barrier\_fields

`tai42_kit.interactions.park_index.ParkIndex.set_barrier_fields`

```python theme={null}
async set_barrier_fields(self, thread_id: str, superstep: str, fields: Mapping[str, str]) -> bool
```

Write consumer fields onto a live barrier; `False` (nothing written) when the barrier is gone.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |
| `fields` | `Mapping[str, str]` | — | — |

#### buffer

`tai42_kit.interactions.park_index.ParkIndex.buffer`

```python theme={null}
async buffer(self, thread_id: str, superstep: str, member_id: str, answer: Any) -> BufferResult
```

Buffer one member's answer into its barrier, idempotently, and report progress.

A redelivered answer for a member already answered keeps the first one.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |
| `member_id` | `str` | — | — |
| `answer` | `Any` | — | — |

#### claim

`tai42_kit.interactions.park_index.ParkIndex.claim`

```python theme={null}
claim(self, thread_id: str, superstep: str) -> DriveLease
```

A drive lease on a super-step, won by entering it (`async with` or `DriveLease.acquire`).

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |

#### finalize

`tai42_kit.interactions.park_index.ParkIndex.finalize`

```python theme={null}
async finalize(
    self,
    lease: DriveLease,
    *,
    member_ids: Sequence[str],
    resolution: Resolution,
    value: Any,
) -> None
```

Resolve a super-step under `lease`: tombstone its members, store its record, drop its barrier and lease.

One guarded step: it lands only while `lease` still holds the super-step, and it also
records the super-step in its thread's run-resolution index and leaves the thread's live
set. On success the lease is consumed and its heartbeat stopped.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `lease` | `DriveLease` | — | — |
| `member_ids` | `Sequence[str]` | — | — |
| `resolution` | `Resolution` | — | — |
| `value` | `Any` | — | — |

#### read\_resolution

`tai42_kit.interactions.park_index.ParkIndex.read_resolution`

```python theme={null}
async read_resolution(self, thread_id: str, superstep: str) -> ResolutionRecord | None
```

A resolved super-step's record, or `None` when it has none.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |

#### run\_resolutions

`tai42_kit.interactions.park_index.ParkIndex.run_resolutions`

```python theme={null}
async run_resolutions(self, thread_id: str) -> dict[str, list[str]]
```

Every resolved super-step of a thread with its members, `{}` when none.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |

#### drop\_run\_resolutions

`tai42_kit.interactions.park_index.ParkIndex.drop_run_resolutions`

```python theme={null}
async drop_run_resolutions(self, thread_id: str, *, keep: str | None = None) -> None
```

Drop every resolved super-step of a thread but `keep` (member tombstones and record), then its index.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `keep` | `str \| None` | `None` | — |

#### drop\_resolution

`tai42_kit.interactions.park_index.ParkIndex.drop_resolution`

```python theme={null}
async drop_resolution(self, thread_id: str, superstep: str, *, member_ids: Iterable[str] = ()) -> None
```

Drop one resolved super-step: its record, its members' tombstones (and `member_ids`), its index member.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `thread_id` | `str` | — | — |
| `superstep` | `str` | — | — |
| `member_ids` | `Iterable[str]` | `()` | — |

#### detach

`tai42_kit.interactions.park_index.ParkIndex.detach`

```python theme={null}
async detach(self, entry_ids: Sequence[str]) -> None
```

Tombstone keys that were claimed but never parked on, never overwriting an existing key.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `entry_ids` | `Sequence[str]` | — | — |

#### extend\_horizon

`tai42_kit.interactions.park_index.ParkIndex.extend_horizon`

```python theme={null}
async extend_horizon(self, entry_id: str, expiry_at: datetime | None) -> bool
```

Grow a live entry's, its barrier's and its live set's TTLs to outlast `expiry_at`; never shrink one.

Returns `False` when no live entry backs `entry_id` (absent or a tombstone).

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `entry_id` | `str` | — | — |
| `expiry_at` | `datetime \| None` | — | — |

## ParkIndexCorruptError

`tai42_kit.interactions._park_types.ParkIndexCorruptError`

```python theme={null}
class ParkIndexCorruptError(ParkIndexError)
```

A stored park index value does not have the shape the index writes.

## ParkIndexError

`tai42_kit.interactions._park_types.ParkIndexError`

```python theme={null}
class ParkIndexError(RuntimeError)
```

Base of every park index failure.

## ParkValueCodecError

`tai42_kit.interactions._park_types.ParkValueCodecError`

```python theme={null}
class ParkValueCodecError(ParkIndexError)
```

A value cannot be stored in, or read back from, the park index.

## Resolution

`tai42_kit.interactions._park_types.Resolution`

```python theme={null}
Resolution = Literal['terminal', 'suspended', 'aborted']
```

## ResolutionMissingError

`tai42_kit.interactions._park_types.ResolutionMissingError`

```python theme={null}
class ResolutionMissingError(ParkIndexError)
```

A resolved tombstone points at a resolution record that is gone.

**Attributes**

| Attribute | Type |
| - | - |
| `thread_id` | — |
| `superstep` | — |

## ResolutionRecord

`tai42_kit.interactions._park_types.ResolutionRecord`

```python theme={null}
class ResolutionRecord
```

What a resolved super-step stored: how it resolved and the value a redelivery replays.

**Attributes**

| Attribute | Type |
| - | - |
| `resolution` | `Resolution` |
| `value` | `Any` |

## SuperstepAlreadyResolvedError

`tai42_kit.interactions._park_types.SuperstepAlreadyResolvedError`

```python theme={null}
class SuperstepAlreadyResolvedError(ParkIndexError)
```

The barrier was finalized while an answer was being buffered into it; the super-step already resolved.

**Attributes**

| Attribute | Type |
| - | - |
| `thread_id` | — |
| `superstep` | — |

## TombstoneKind

`tai42_kit.interactions.park_index.TombstoneKind`

```python theme={null}
TombstoneKind = Literal['live', 'resolved', 'detached']
```

## barrier\_ttl\_seconds

`tai42_kit.interactions.park_index.barrier_ttl_seconds`

```python theme={null}
barrier_ttl_seconds(expiries: Iterable[datetime | None]) -> int
```

A barrier's TTL: the entry floor, extended to outlast the LATEST deadline it coordinates plus the margin.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `expiries` | `Iterable[datetime \| None]` | — | — |

## decode\_value

`tai42_kit.interactions._park_types.decode_value`

```python theme={null}
decode_value(encoded: Any) -> Any
```

Rebuild a value from its tagged mapping (`encode_value`'s inverse).

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `encoded` | `Any` | — | — |

## encode\_value

`tai42_kit.interactions._park_types.encode_value`

```python theme={null}
encode_value(value: Any) -> dict[str, Any]
```

Encode a resolution value or a buffered answer as a tagged JSON mapping.

A contract outcome type keeps its type across the store; any other pydantic model is refused,
and a plain value must be JSON-serializable.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `value` | `Any` | — | — |

## entry\_ttl\_seconds

`tai42_kit.interactions.park_index.entry_ttl_seconds`

```python theme={null}
entry_ttl_seconds(expiry_at: datetime | None) -> int
```

An entry's TTL: the floor, extended to outlast its ask's deadline plus the margin.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `expiry_at` | `datetime \| None` | — | — |

## resolution\_ttl\_seconds

`tai42_kit.interactions.park_index.resolution_ttl_seconds`

```python theme={null}
resolution_ttl_seconds() -> int
```

The TTL of a resolution record and its tombstones: twice the platform's redelivery horizon.

They must outlive the last redelivery the reaper can fire, plus reaper-cycle and clock-skew
margin, and no longer (they hold a run's outcome).

## superstep\_id

`tai42_kit.interactions.park_index.superstep_id`

```python theme={null}
superstep_id(member_ids: Iterable[str]) -> str
```

A deterministic super-step id: a sha256 over the sorted member ids, insensitive to their order.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `member_ids` | `Iterable[str]` | — | — |

Park adoption and the chained-call notices: which park a run records for a returned sentinel, and what it fires.

A park belongs to exactly ONE resume continuation — the one bound when it was raised — and
carries that owner BOTH on its sentinel and on its wire-form marker. `assert_park_adoptable`
is the guard every seam that turns a park into the CALLER's own suspended state applies: the
object seams that adopt a returned sentinel, and the CLAIM point where a driver records resume
state against a marker read off a serialized tool result. So a nested run's park is never adopted
by its caller (which would then wait for a resume fired only at the nested run).

The owner is the continuation NAME, not a per-run token, because that is the whole of what the
platform dispatches: the name is what the interaction stores and what a later answer invokes, so
a scoped owner would have to be unscoped again before dispatch and would fork the meaning of the
resume binding. Name equality alone is not what makes the guard sound — two runs of the same
driver share a name — the `None` rule is: a park crossing a nested RUN's face carries no owner
at all, so a same-named sibling is refused on that, never on a name comparison happening to
differ.

Refusing is not the only answer to a park this caller does not own. `resolve_park_adoption`
is the OBJECT seams' form of the guard, and it answers adopt-your-own or CHAIN: a nested dispatch
a CHAIN routing (`ChainedResume`) was bound around parks on
the nested CALL — a key of its own, owned by its own resume continuation — while the nested run
keeps its park and its resume, and its terminal re-enters the caller through the routing's
delivery tool. A dispatch nothing chained still gets the loud refusal. The CLAIM point cannot
chain: a park reaching it as content belongs to no dispatch it could attribute the call to, so
there it is adopt-or-refuse.

Every driver builds the chain fires here too — `repark_notice` when a chained run re-parks,
`terminal_chain_notice` at its terminal — so each payload has one shape.

## assert\_park\_adoptable

`tai42_kit.interactions.park_adoption.assert_park_adoptable`

```python theme={null}
assert_park_adoptable(resume_owner: str | None, *, interaction_id: str, tool_name: str) -> None
```

Guard the seam where a caller ADOPTS a park as its OWN.

Applies to a returned sentinel or the wire-form marker, where the caller records resume
state of its own against that interaction.

`resume_owner` is the park's `SuspendedInteraction.resume_owner`, and the guard's
role is a NESTING DISCRIMINATOR, not an authenticity token: it separates a park a run raised
UNDER ITS OWN resume binding (adoptable) from one that crossed a NESTED run's face on the way
up (not — that nested run is resumed on its own path, so adopting it would wait for a resume
fired elsewhere and hang forever). A park a run raises for itself carries the continuation it
was raised under; a park crossing a nested RUN's face carries no owner at all. So the guard is
sound on the `None` rule, not on name equality: a same-named sibling run is refused because
its park reaches here ownerless, never because two names happened to differ.

`None` is never adoptable, by ANY caller: it is the signal a park crossed a nested run's
face (or is content shaped to look like a park), neither of which this caller may turn into
its own suspended state. An unbound caller is refused too — it has no continuation the
platform could fire, so a park it claimed could only hang. A BLANK owner reads as no owner:
it names no registered tool the platform could dispatch.

This name check does NOT authenticate the marker against any store — the resume owner is a
public continuation name, so a well-formed marker naming the bound continuation is treated as
adoptable here. Structural validation of the marker shape lives in
`read_suspended_interaction_marker`;
store-authoritative existence is a separate concern the claim seams that hold a store handle
apply.

Raises `NestedParkOwnershipError` BEFORE any state of the
caller's own is recorded, so the owning run's park stays untouched and resumable on its own
path. A seam holding the sentinel a nested CALL returned applies `resolve_park_adoption`
instead, which answers this same question but can also CHAIN the call rather than refuse it.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `resume_owner` | `str \| None` | — | — |
| `interaction_id` | `str` | — | — |
| `tool_name` | `str` | — | — |

## attach\_chained\_park

`tai42_kit.interactions.park_adoption.attach_chained_park`

```python theme={null}
attach_chained_park(key: str) -> None
```

Mark a claimed chained `key` as ATTACHED so the ledger drops it.

The driver recorded a park on it, so it is no longer a dead chain. A no-op when no
`chained_park_claims` ledger is open, or when the key was never claimed inside it.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `key` | `str` | — | — |

## chained\_park\_claims

`tai42_kit.interactions.park_adoption.chained_park_claims`

```python theme={null}
chained_park_claims(claims: set[str] | None = None) -> Generator[set[str]]
```

Bind the per-drive ledger of chained keys `resolve_park_adoption` CLAIMED, yield the set.

A driver opens one around each drive so it can tell, when the drive stops, which chained
calls it actually parked on: it drops each key it persists, and whatever REMAINS is a
claim the drive never turned into a park (the run errored, or moved on without parking).
Those are dead chains — the nested run will still fire its terminal at them — so the
driver detaches them rather than leaving the fire to hunt a park that will never exist.

The yielded set is mutated in place, so a claim recorded inside a task the drive spawns
(every nested dispatch runs in a child context) is visible to the opener.

`claims` lets a caller pass an EXISTING set to re-bind rather than a fresh one. A driver
that binds per drive-step — the resume binding cannot span a generator's yields, so it is
re-entered around each `__anext__` — owns one accumulating set for the whole drive and
re-binds it here each step, so a claim recorded in an early step survives to the reconcile
at the end. Omit it (the default) to open a fresh ledger for the whole drive at once.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `claims` | `set[str] \| None` | `None` | — |

## is\_chained\_park\_key

`tai42_kit.interactions.park_adoption.is_chained_park_key`

```python theme={null}
is_chained_park_key(key: str) -> bool
```

Whether `key` names a chained CALL rather than a platform interaction.

The discriminator a driver indexing both key kinds reads before applying any policy that
only makes sense for one of them (a chained key has no interaction, so nothing expires
it and no answer is ever delivered against it directly).

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `key` | `str` | — | — |

## new\_chained\_park\_key

`tai42_kit.interactions.park_adoption.new_chained_park_key`

```python theme={null}
new_chained_park_key() -> str
```

Mint a fresh chained resume key for ONE nested dispatch.

Namespaced and unique per call, so two nested calls never converge on one key.

## repark\_notice

`tai42_kit.interactions.park_adoption.repark_notice`

```python theme={null}
repark_notice(expiry_at: datetime | None) -> tuple[str, dict[str, Any]] | None
```

The `(tool, payload)` fired when a park is raised under a bound chain routing.

Returns `None` when no chain routing is bound around the current dispatch.

A chained caller's own suspension horizon is INHERITED from the nested run's current ask,
so when that run re-parks on a new ask the caller's horizon must move with it. The notice
is that signal: the routing's `delivery_tool` paired with `{CHAINED_PARK_TOKEN_KEY:
routing.chain_key, "expiry_at": <iso>, "status": PARK_COMPLETION_REPARKED}` — the one
non-terminal status, carrying the new deadline in place of a result and the chain key the
delivery tool reverses back to the caller's park. It resolves nothing; the completion still
fires later under a terminal status.

Only a bound chain routing is notified, so no other delivery tool ever sees this fire.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `expiry_at` | `datetime \| None` | — | — |

## resolve\_park\_adoption

`tai42_kit.interactions.park_adoption.resolve_park_adoption`

```python theme={null}
resolve_park_adoption(resume_owner: str | None, *, interaction_id: str, tool_name: str) -> tuple[str, str | None]
```

The park THIS run records for a returned park sentinel — ADOPT-your-own, or CHAIN.

Returned as the `(key, resume_owner)` its own park is keyed and owned by.

The OBJECT seams' form of `assert_park_adoptable`, for a seam holding the sentinel a
nested call returned. Three outcomes, in this order:

* the park is adoptable here (raised under the continuation bound HERE) — it is this run's
  own park, returned unchanged, and the platform resumes it directly;
* otherwise, if this dispatch was CHAINED (a chain routing was bound around it,
  `get_chained_resume`), the run parks on the chained resume
  KEY instead, owned by this run's OWN resume continuation — because that park is genuinely
  this run's: it waits on the CALL, while the nested run keeps its own park and its own resume
  and re-enters here through the chain's delivery tool when it terminates. The key is recorded
  in the open `chained_park_claims` ledger, so a drive that never turns the claim into a
  park can detach it;
* otherwise `NestedParkOwnershipError`, exactly as
  `assert_park_adoptable` raises it: waiting on a foreign park with nothing chained would
  leave this run suspended behind a resume fired only at the nested run.

Never returns a foreign interaction id, and never returns a key owned by anything but the
continuation bound here — so the claim point downstream reaches the same verdict.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `resume_owner` | `str \| None` | — | — |
| `interaction_id` | `str` | — | — |
| `tool_name` | `str` | — | — |

## terminal\_chain\_notice

`tai42_kit.interactions.park_adoption.terminal_chain_notice`

```python theme={null}
terminal_chain_notice(routing: ChainedResume, result: Any) -> tuple[str, dict[str, Any]]
```

The `(delivery tool, payload)` a run's terminal fires along its captured chain routing.

A `RunFailed` result is a failed terminal: its opaque
`outcome` rides as the `result` under the `failed` status. Any other result is a
success, carried whole. The payload keys are exactly the chain key, `result` and
`status`.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `routing` | `ChainedResume` | — | — |
| `result` | `Any` | — | — |

The give-up handler seam: the driver that parked a run tells its waiter when the platform abandons the park.

When the platform gives up redelivering an answered park (its continuation-due record outlived
the redelivery horizon), the waiter to tell is the one the parking DRIVER captured for that
park. The platform names the interaction only; each driver that parks registers one handler,
reads its own record of that interaction, runs its own failed-terminal epilogue and returns the
outermost outcome for the platform to deliver to the run's address. A handler returns `None`
for an interaction that is not its park.

## ParkGiveUpHandler

`tai42_kit.interactions.park_giveup.ParkGiveUpHandler`

```python theme={null}
ParkGiveUpHandler: TypeAlias = Callable[[str, Mapping[str, Any]], Awaitable[ParkGiveUpOutcome | None]]
```

## ParkGiveUpOutcome

`tai42_kit.interactions.park_giveup.ParkGiveUpOutcome`

```python theme={null}
class ParkGiveUpOutcome(NamedTuple)
```

The outermost outcome of a run whose abandoned park its driver gave up, for the platform to deliver.

**Attributes**

| Attribute | Type |
| - | - |
| `result` | `Any` |

## fire\_park\_giveup

`tai42_kit.interactions.park_giveup.fire_park_giveup`

```python theme={null}
async fire_park_giveup(interaction_id: str, failed_outcome: Mapping[str, Any]) -> ParkGiveUpOutcome | None
```

Ask each registered handler in turn to give up `interaction_id`'s park; return the first one's outcome.

A handler that owns the park returns its outcome and later handlers are not called; `None`
when no handler owns it. A handler's raise propagates.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `interaction_id` | `str` | — | — |
| `failed_outcome` | `Mapping[str, Any]` | — | — |

## register\_park\_giveup\_handler

`tai42_kit.interactions.park_giveup.register_park_giveup_handler`

```python theme={null}
register_park_giveup_handler(handler: ParkGiveUpHandler) -> None
```

Register a driver's give-up handler; handlers run in registration order. A driver registers once at import.

**Parameters**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `handler` | `ParkGiveUpHandler` | — | — |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.