---
title: prefactor_core.managers.span module
editUrl: true
head: []
template: doc
sidebar:
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

# prefactor_core.managers.span module

Manager for span lifecycle operations.

The SpanManager handles high-level operations for spans, converting user
calls into Operation objects that are queued for processing. It also manages
the span stack for automatic parent detection.

### *class* prefactor_core.managers.span.SpanManager(http_client: [PrefactorHttpClient](../../http/reference/prefactor_http.md#prefactor_http.PrefactorHttpClient), enqueue: Callable[[[Operation](prefactor_core.operations.md#prefactor_core.operations.Operation)], Awaitable[None]])

Bases: `object`

Manages span lifecycle operations.

Spans follow a three-phase lifecycle that maps to the API’s state machine:

1. `prepare()`  — synchronous; allocates a local temp ID and pushes it
   onto the SpanContextStack so nested spans can auto-detect their parent.
   No HTTP call is made.
2. `start()`    — async; POSTs the span to the API with status
   `"active"` and the params payload, then re-keys local state under
   the API-generated ID. The span is now running.
3. `finish()`   — async; queues a `FINISH_SPAN` operation that calls
   `POST /agent_spans/{id}/finish` with the desired terminal status
   (`complete`, `failed`, or `cancelled`).

API state machine:
: pending  → cancelled            (cancel_unstarted: POST pending, finish cancelled)
  active   → complete / failed / cancelled  (start then finish)

`cancel_unstarted()` handles the case where the span is cancelled
before `start()` is ever called — it POSTs the span as `pending`
then immediately cancels it, which is the only valid pre-active
cancellation path the API supports.

### Example

manager = SpanManager(http_client, enqueue_func)

temp_id = manager.prepare(instance_id=”inst-123”, schema_name=”agent:llm”)
api_id  = await manager.start(temp_id, payload={“model”: “gpt-4”})
await manager.finish(api_id, status=”complete”, result_payload={…})

#### *async* cancel_unstarted(temp_id: str) → None

Cancel a span that was never started.

When `cancel()` is called before `start()`, the span has not yet
been posted to the API. The API state machine only allows
`pending → cancelled`, so this method creates the span as
`pending` then immediately cancels it via the finish endpoint.

* **Parameters:**
  **temp_id** – The temporary ID returned by `prepare()`.
* **Raises:**
  **KeyError** – If temp_id is not a known pending span.

#### *async* create(instance_id: str, schema_name: str, parent_span_id: str | None = None, payload: dict[str, Any] | None = None, span_id: str | None = None) → str

Create a span in one step (prepare + start).

Convenience method that combines `prepare()` and `start()` for
callers that don’t need the two-phase lifecycle.

* **Parameters:**
  * **instance_id** – ID of the agent instance this span belongs to.
  * **schema_name** – Name of the schema for this span.
  * **parent_span_id** – Optional parent span ID (auto-detected if None).
  * **payload** – Optional initial payload data.
  * **span_id** – Ignored (API generates IDs).
* **Returns:**
  The API-generated span ID.

#### *async* finish(span_id: str, result_payload: dict[str, Any] | None = None, status: FinishStatus = 'complete', idempotency_key: str | None = None) → None

Mark a span as finished.

Queues a finish operation and removes the span from the stack.

* **Parameters:**
  * **span_id** – The ID of the span to finish.
  * **result_payload** – Optional result data to store on the span.
  * **status** – Terminal status — `"complete"`, `"failed"`, or
    `"cancelled"` (default: `"complete"`). The span must be
    `active` for this to succeed; use `cancel_unstarted()`
    to cancel a span that was never started.
  * **idempotency_key** – Optional key to make repeated finish requests
    duplicate-safe. When omitted, a new key is generated.
* **Raises:**
  **KeyError** – If the span ID is not known.

#### get_span(span_id: str) → [Span](prefactor_core.models.md#prefactor_core.models.Span) | None

Get a span by ID.

* **Parameters:**
  **span_id** – The span ID to look up.
* **Returns:**
  The span if known, None otherwise.

#### prepare(instance_id: str, schema_name: str, parent_span_id: str | None = None) → str

Reserve a local span slot and push it onto the context stack.

Allocates a temporary local ID and pushes it onto the
SpanContextStack so that nested `prepare()` calls can auto-detect
their parent. The actual HTTP POST is deferred to `start()`.

* **Parameters:**
  * **instance_id** – ID of the agent instance this span belongs to.
  * **schema_name** – Name of the schema for this span.
  * **parent_span_id** – Optional parent span ID (auto-detected from
    stack if None).
* **Returns:**
  A temporary span ID (replaced by the API-generated ID in
  `start()`).

#### *async* start(temp_id: str, payload: dict[str, Any] | None = None) → str

Post the span to the API as `active` and return the API-generated ID.

POSTs the span with `status="active"`, which allows it to be
finished via the finish endpoint with any terminal status
(`complete`, `failed`, or `cancelled`). Replaces the temporary
local ID with the API-generated ID in local state and on the context
stack.

* **Parameters:**
  * **temp_id** – The temporary ID returned by `prepare()`.
  * **payload** – Optional params/inputs to send with the span.
* **Returns:**
  The API-generated span ID.
* **Raises:**
  **KeyError** – If temp_id is not a known pending span.