# `Cyclium.AtomGuard`
[🔗](https://github.com/Cyclium/cyclium_ex/blob/main/lib/cyclium/atom_guard.ex#L1)

Bounded string-to-atom conversion for data sourced from outside the codebase
(DB-defined dynamic actor/workflow definitions, persisted workflow results,
and LLM-produced tool/output names).

Atoms are never garbage-collected and the BEAM atom table is bounded
(`:erlang.system_info(:atom_limit)`, ~1M by default). Converting
high-cardinality external strings with `String.to_atom/1` — actor ids,
expectation/step ids, subject keys, JSON keys, LLM-chosen tool names — leaks
atoms permanently and can crash every node in the cluster with
`system_limit`.

`intern!/1` is a drop-in replacement for `String.to_atom/1` that:

  * returns an **existing** atom without minting (so reloading the same
    definition, or any value that matches a compiled atom, never grows the
    table), and
  * mints a new atom only while the atom table has headroom; once usage
    crosses the configured threshold it raises `Cyclium.AtomGuard.LimitError`.

The net effect: an abusive or runaway set of dynamic definitions fails to
load (a localized, catchable error) instead of taking down the node.

## Configuration

    # Refuse to mint new atoms once the atom table is this full (0.0–1.0).
    # Default: 0.8
    config :cyclium, :atom_guard_max_usage, 0.8

# `existing_atom`

```elixir
@spec existing_atom(String.t()) :: {:ok, atom()} | :error
```

Returns `{:ok, atom}` if `string` already corresponds to an atom, otherwise
`:error`. Never mints a new atom.

# `intern!`

```elixir
@spec intern!(term()) :: atom()
```

Convert `value` to an atom, bounded by atom-table headroom.

  * Atoms (and `nil`) pass through unchanged.
  * Binaries that already correspond to an existing atom are returned without
    minting — no table growth.
  * A genuinely new binary is minted only while atom-table usage is below the
    configured threshold; past it, raises `LimitError`.
  * Other terms are stringified first.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
