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

# ci.scope

> Open a named span on the current thread.

Open a named span. Becomes the innermost scope on the current thread
until the context exits. Use it for regions that framework hooks and
loop wrappers don't already cover: augmentation, beam search, custom
schedulers, preprocessing passes.

## Signature

```python theme={null}
def scope(name: str, index: int | None = None, **attrs) -> Scope
```

## Parameters

| Name      | Type   | Default | Purpose                                           |
| --------- | ------ | ------- | ------------------------------------------------- |
| `name`    | `str`  | -       | Scope name; appears as `span.name` in the spool   |
| `index`   | `int?` | `None`  | Optional index (e.g. epoch or batch number)       |
| `**attrs` | any    | -       | Arbitrary keyword attributes attached to the span |

Attribute values are stored on `span.attrs` as JSON. The platform
indexes them and lets you filter by them in the dashboard.

## Behavior

* Nests arbitrarily under whatever scope is already open. Max depth
  **64**; scopes beyond that log a warning and the span record is
  dropped, but the `with` block still runs normally: `__enter__`
  and `__exit__` fire, and the code inside executes. The drop is
  reported in `ci.health()` so you can spot it.
* Thread-local. Each thread maintains its own scope stack, so parallel
  DataLoader workers and async handlers don't contaminate each other.
* On `__exit__`, end time, CPU time, and (when hooks populate it) GPU
  time are recorded. The closed span is handed to the flush thread.
* Overhead is tracked and surfaced as a mark on every scope.

## Examples

### Simple wrap

```python theme={null}
with ci.scope("augmentation"):
    batch = augment(batch)
```

### With attributes

```python theme={null}
with ci.scope("postprocess", variant="beam-search", beam_width=4):
    output = beam_search(logits)
```

### Nested

```python theme={null}
with ci.scope("preprocess"):
    with ci.scope("tokenize"):
        ids = tokenizer(text)
    with ci.scope("pad"):
        ids = pad(ids, max_len=512)
```

### Indexed

```python theme={null}
for i, shard in enumerate(shards):
    with ci.scope("shard", index=i):
        process(shard)
```

## Returns

The yielded `Scope` carries the span's ID and start time. Most callers
ignore it; `ci.mark()` automatically attaches to whatever scope is
currently innermost.

## Overhead

Open + close measures \~4.4 μs per cycle on x86\_64 and \~2.7 μs on
arm64. The hot path is lock-free: a thread-local scope stack, a
slotted `Scope` instance, a 32-char hex id, and inlined ContextVar +
thread-local lookups. Cost scales through the push/pop pair itself,
not with the number of sibling scopes already closed, so nesting
doesn't compound. See [Overhead](/sdk/core-concepts#overhead) for the
full picture.

## Related

<CardGroup cols={2}>
  <Card title="ci.mark" icon="tag" href="/sdk/reference/mark">
    Attach a value to the innermost open scope.
  </Card>

  <Card title="Loop wrappers" icon="repeat" href="/sdk/reference/loop-wrappers">
    `ci.epochs` and `ci.batches`: scopes without a `with` block.
  </Card>
</CardGroup>
