Skip to content

chatomon.atom

The atom: a chat agent declared once and built into a native agent.

Classes:

Name Description
Atom

A chat agent: instructions, knowledge, a connector and its orbitals.

Atom

Atom(
    *,
    name: str,
    instructions: str,
    connector: Connector[AgentT],
    knowledge: Knowledge | str | None = None,
    input_orbitals: Sequence[Orbital] = (),
    output_orbitals: Sequence[Orbital] = (),
    refusal: str = DEFAULT_REFUSAL,
    blocked_placeholder: str = DEFAULT_BLOCKED_PLACEHOLDER,
    blocked_guidance: str | None = DEFAULT_BLOCKED_GUIDANCE,
    max_retries: int = 2,
    on_turn: TurnCallback | None = None,
    capture_content: CaptureContent = "none",
    redactor: Callable[[str], str] | None = None,
    tracer_provider: TracerProvider | None = None
)

Bases: Generic[AgentT]

A chat agent: instructions, knowledge, a connector and its orbitals.

The atom never runs anything itself: build returns a native agent of the connector's framework, with chatomon's hooks installed. Orbitals get their own definitions; the atom's instructions and knowledge are used only by the nucleus.

Parameters:

Name Type Description Default
name str

Name of the agent, used in traces and reports.

required
instructions str

Definition of the nucleus, free text.

required
connector Connector[AgentT]

The framework and model the atom is built into.

required
knowledge Knowledge | str | None

Knowledge appended to the nucleus prompt; a string is wrapped in Knowledge.

None
input_orbitals Sequence[Orbital]

Orbitals run in order on the user message.

()
output_orbitals Sequence[Orbital]

Orbitals run in order on the nucleus reply.

()
refusal str

Reply used when a message is blocked without a specific reply.

DEFAULT_REFUSAL
blocked_placeholder str

Text that replaces a blocked user message in the history.

DEFAULT_BLOCKED_PLACEHOLDER
blocked_guidance str | None

Guidance the nucleus receives, before the orbital's own, when an input orbital blocks a message with guidance and the nucleus writes the refusal. The nucleus is then called with the placeholder in place of the message, and a model that is not told what the placeholder is can answer about it: it tells the user that a message was removed, or greets, and the refusal the orbital asked for is never written. The default, DEFAULT_BLOCKED_GUIDANCE, says that the message is withheld and must not be mentioned. Pass your own text to word it for your model, or None to give the nucleus the orbital's guidance alone. It is not part of the turn reports, which record the guidance of each orbital.

DEFAULT_BLOCKED_GUIDANCE
max_retries int

Regenerations allowed when an output orbital blocks with guidance.

2
on_turn TurnCallback | None

Receives the TurnReport of every turn.

None
capture_content CaptureContent

"none" (default), "hash" or "full": how much of the conversation the turn reports record. Logs and traces carry labels only.

'none'
redactor Callable[[str], str] | None

Applied to recorded text when capture_content="full".

None
tracer_provider TracerProvider | None

OpenTelemetry provider; the global one when None.

None

Raises:

Type Description
ChatomonConfigError

If a setting is invalid.

Examples:

from chatomon import Atom, Knowledge
from chatomon.connectors.pydantic_ai import PydanticAIConnector
from chatomon.orbitals import JudgeOrbital

faq = Knowledge("Opening hours: Monday to Friday, 9 to 18.")
atom = Atom(
    name="speedypack",
    instructions="You are the customer assistant of SpeedyPack.",
    knowledge=faq,
    connector=PydanticAIConnector(model="openai:gpt-5"),
    output_orbitals=[
        JudgeOrbital(
            model="openai:gpt-5-mini",
            instructions="Block replies that state facts missing from the reference.",
            context=faq,
        ),
    ],
)
agent = atom.build()

Methods:

Name Description
validate

Check that the orbital chains are consistent.

create_engine

Create the engine that runs this atom's orbitals.

build

Validate the atom, bind its orbitals and build the native agent.

Attributes:

Name Type Description
system_prompt str

The nucleus prompt: the instructions followed by the knowledge section.

Source code in src/chatomon/atom.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def __init__(
    self,
    *,
    name: str,
    instructions: str,
    connector: Connector[AgentT],
    knowledge: Knowledge | str | None = None,
    input_orbitals: Sequence[Orbital] = (),
    output_orbitals: Sequence[Orbital] = (),
    refusal: str = DEFAULT_REFUSAL,
    blocked_placeholder: str = DEFAULT_BLOCKED_PLACEHOLDER,
    blocked_guidance: str | None = DEFAULT_BLOCKED_GUIDANCE,
    max_retries: int = 2,
    on_turn: TurnCallback | None = None,
    capture_content: CaptureContent = "none",
    redactor: Callable[[str], str] | None = None,
    tracer_provider: TracerProvider | None = None,
) -> None:
    if not name.strip():
        raise ChatomonConfigError("name must not be empty")
    if not instructions.strip():
        raise ChatomonConfigError("instructions must not be empty")
    if not isinstance(connector, Connector):
        raise ChatomonConfigError(
            f"connector must be a Connector, got {type(connector).__name__}"
        )
    for orb in (*input_orbitals, *output_orbitals):
        if not isinstance(orb, Orbital):
            raise ChatomonConfigError(
                f"orbitals must be Orbital instances, got {type(orb).__name__}"
            )
    if max_retries < 0:
        raise ChatomonConfigError(f"max_retries must be >= 0, got {max_retries!r}")
    if not refusal.strip():
        raise ChatomonConfigError("refusal must not be empty")
    if not blocked_placeholder.strip():
        raise ChatomonConfigError("blocked_placeholder must not be empty")
    if blocked_guidance is not None and not blocked_guidance.strip():
        raise ChatomonConfigError(
            "blocked_guidance must not be empty; pass None to give the nucleus "
            "the orbital's guidance alone"
        )
    self.name = name
    self.instructions = instructions
    self.connector = connector
    self.knowledge = Knowledge(knowledge) if isinstance(knowledge, str) else knowledge
    self.input_orbitals: tuple[Orbital, ...] = tuple(input_orbitals)
    self.output_orbitals: tuple[Orbital, ...] = tuple(output_orbitals)
    self.refusal = refusal
    self.blocked_placeholder = blocked_placeholder
    self.blocked_guidance = blocked_guidance
    self.max_retries = max_retries
    self.on_turn = on_turn
    self.content_policy = ContentPolicy(capture_content, redactor)
    self.tracer_provider = tracer_provider

system_prompt property

system_prompt: str

The nucleus prompt: the instructions followed by the knowledge section.

validate

validate() -> None

Check that the orbital chains are consistent.

Every orbital says whether it calls a model. A name identifies one orbital instance: the same instance may appear once in each chain, two different instances may not share a name. Every name in reads must be an orbital placed earlier in the same chain or, for the output chain, an input orbital. Every orbital must support the side of the chain it is in.

Raises:

Type Description
ChatomonConfigError

If a rule is violated; the message says what to fix.

Source code in src/chatomon/atom.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def validate(self) -> None:
    """Check that the orbital chains are consistent.

    Every orbital says whether it calls a model. A name identifies one orbital
    instance: the same instance may appear once in each chain, two different
    instances may not share a name. Every name in
    `reads` must be an orbital placed earlier in the same chain or, for the
    output chain, an input orbital. Every orbital must support the side of the
    chain it is in.

    Raises:
        ChatomonConfigError: If a rule is violated; the message says what to fix.
    """
    by_name: dict[str, Orbital] = {}
    for orb in (*self.input_orbitals, *self.output_orbitals):
        # A class that never calls Orbital.__init__ was not checked when it was created.
        if not isinstance(getattr(orb, "calls_model", None), bool):
            raise ChatomonConfigError(undeclared(type(orb)))
        if by_name.setdefault(orb.name, orb) is not orb:
            raise ChatomonConfigError(
                f"two different orbitals are named {orb.name!r}; give each a unique name="
            )
    input_names = {orb.name for orb in self.input_orbitals}
    chains: tuple[tuple[str, Side, tuple[Orbital, ...]], ...] = (
        ("input_orbitals", "input", self.input_orbitals),
        ("output_orbitals", "output", self.output_orbitals),
    )
    for chain_name, side, chain in chains:
        seen: set[str] = set()
        for orb in chain:
            if orb.name in seen:
                raise ChatomonConfigError(f"orbital {orb.name!r} appears twice in {chain_name}")
            if side not in orb.sides:
                raise ChatomonConfigError(
                    f"{type(orb).__name__} {orb.name!r} cannot run on the {side} side; "
                    f"it supports: {sorted(orb.sides)}"
                )
            available = seen | input_names if side == "output" else seen
            for read in orb.reads:
                if read == orb.name:
                    raise ChatomonConfigError(
                        f"orbital {orb.name!r} cannot read its own annotations"
                    )
                if read not in available:
                    where = f"before it in {chain_name}"
                    if side == "output":
                        where += " or in input_orbitals"
                    raise ChatomonConfigError(
                        f"orbital {orb.name!r} reads {read!r}, which must be an orbital "
                        f"placed {where}"
                    )
            seen.add(orb.name)

create_engine

create_engine() -> Engine

Create the engine that runs this atom's orbitals.

Returns:

Type Description
Engine

A new engine configured from the atom.

Source code in src/chatomon/atom.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
def create_engine(self) -> Engine:
    """Create the engine that runs this atom's orbitals.

    Returns:
        A new engine configured from the atom.
    """
    return Engine(
        atom_name=self.name,
        input_orbitals=self.input_orbitals,
        output_orbitals=self.output_orbitals,
        refusal=self.refusal,
        blocked_guidance=self.blocked_guidance,
        max_retries=self.max_retries,
        on_turn=self.on_turn,
        content=self.content_policy,
        tracer=get_tracer(self.tracer_provider),
    )

build

build() -> AgentT

Validate the atom, bind its orbitals and build the native agent.

Returns:

Type Description
AgentT

The native agent produced by the connector.

Raises:

Type Description
ChatomonConfigError

If the configuration is invalid.

Source code in src/chatomon/atom.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
def build(self) -> AgentT:
    """Validate the atom, bind its orbitals and build the native agent.

    Returns:
        The native agent produced by the connector.

    Raises:
        ChatomonConfigError: If the configuration is invalid.
    """  # noqa: DOC502 - raised by validate()
    self.validate()
    bound: set[int] = set()
    for orb in (*self.input_orbitals, *self.output_orbitals):
        if id(orb) not in bound:
            orb.bind(self.connector)
            bound.add(id(orb))
    return self.connector.build(self, self.create_engine())