# behavior

Behavior is the policy this runtime actually runs under: the hook slots that decide whether
an input gets attention and whether a message is delivered. Changing it is a
content-addressed publish plus a compare-and-swap activation, so the change is a revision
you can name, verify, and roll back - not an edit that takes effect because it is on disk.

```python
from klbr import behavior

draft = behavior.draft()
```

## the trail

Draft, test, publish, activate. Each step is a typestate: you cannot activate what did not
validate, and you cannot validate what is not a draft.

```python
draft.write(
    "manifest.json",
    '{"version":1,"hooks":{"attention.plan":"klbr_hooks.attention:plan",'
    '"delivery.review":"klbr_hooks.delivery:review"}}',
)
draft.write("klbr_hooks/attention.py", attention_source)
candidate = draft.test()
```

`test()` is not a lint. It starts the hook role in a temporary process, hands it synthetic
requests, and validates the decisions that come back against the wire vocabulary
(`wake`/`defer`/`ignore`, `allow`/`hold`). A hook that answers something else fails here,
where nothing is live and nobody was reached.

```python
release = candidate.publish("/path/to/releases")
print(release.id, release.verify())
```

A release is a directory named by the digest of its own bytes. `verify()` reloads it and
re-hashes what is on disk, so a release that was edited after publication fails rather than
being activated.

```python
active = await release.activate(expected_epoch=epoch)
```

Activation is a compare-and-swap on the session's hook epoch. Pass the epoch you read; a
stale one is refused instead of silently overwriting whoever won the race. Inside a cell this
goes through the host, which re-verifies the bytes, re-runs the dry validation, and swaps
the live policy. Going back is the same call with the previous release.

## what it does not do

There are two hook slots, and a manifest that names others is refused. Activation does not
rewrite what already ran: it takes effect at the next cell's pin, and a cell that is already
running keeps the revision it started with.

## refusals

`CASConflictError` is a lost race, not a bug - read the epoch again and decide whether you
still want the change. `ValidationError` at publish time means the dry run failed; fix the
hook rather than the manifest.
