soev_agents.domain
Record what happens in a thread as events, and read the events back from the thread's trace.
An event is one immutable fact: an input, a model's output, a tool's result, a failure. A thread keeps nothing else, so a stored thread reads the same in each process and after each deploy. Elements are what the model is shown and can refer to again by id. Citations record the numbers that the model cited.
Traces
A trace is a thread's whole record, in tree order. A step reads the part of the trace that the runner gives it.
Trace
classTrace(events=())A thread's whole record: every event of every stream, in tree order.
A trace is a value: it holds events and has no identity of its own. A step, a router and a context builder each get a view of the thread as a Trace, never the thread itself. The query methods select events
and apply no rule.
Example
from soev_agents.domain import ModelOutput, ToolCall, Trace
def tool_calls(view: Trace) -> tuple[ToolCall, ...]:
output = view.latest(ModelOutput)
if output is not None and output.calls_tools:
return output.tool_calls
return ()Make a trace of events.
parameventsIterable[Event]= ()The events, in tree order. The trace keeps them in the order given and does not sort them.
methodlast() -> Event | Nonemethodlatest[T: Event](event_type) -> T | Nonemethodby_type[T: Event](event_type) -> tuple[T, ...]methodstream(name) -> tuple[Event, ...]methodscope_of(name) -> StreamReturn the stream that heads the scope of the stream name.
A scope is the part of the thread that one graph run produced: the agent's, or one subagent's. The root
stream heads the agent's scope, and each stream whose first event is an Input heads a subagent's scope.
paramnameStreamThe name of a stream.
Returns
StreamThe nearest of name and its ancestors that is the root stream or starts with an Input in this trace.
methodview(name) -> TraceReturn the events that a step in the stream name can read.
These are its own stream, everything forked below it, and its ancestors up to the point it forked from. The view excludes a sibling stream, because its events can arrive at any time, and nothing computed from a view can depend on timing.
The view also stays inside the scope of name. A subagent's view holds nothing of its parent before
the hand-off, and a parent's view holds nothing of a subagent's stream.
paramnameStreamThe name of the stream that reads.
Stream
type aliasStream = strThe name of a stream, which gives the place of the stream in the tree of streams of the thread.
Each thread starts with one stream, root. A branch that a fan-out starts at the node search, after event 3 of
the root, is root/search@3. A subagent's stream carries its index after #, as in root/research#0@7.
Events
An event is one fact in a trace. Subclass Event for an event type of your own.
Event
modelEventOne recorded fact. Subclasses add their payload fields.
An event never changes. A step creates it with its payload fields only. The runner stamps stream,
origin and version, and the store stamps seq and ts when it appends the event. Validation fails for an
event with only one of seq and ts.
For an event type of your own, subclass Event with a type field whose default is the tag. Then add the class
to the event_types of the agent whose threads record it.
Example
from typing import Literal
from soev_agents.domain import Actor, Event
class Escalated(Event):
type: Literal["escalated"] = "escalated"
actor: Actor = "system"
reason: strattributeseqint | NoneattributeactorActorWhich kind of party produced the event.
attributeoriginNodeName | Noneattributetsdatetime | None= NoneWhen the store appended the event, or None before then.
attributeversionstr | NoneattributesealedboolWhether the store appended the event: true once seq and ts are stamped.
Actor
type aliasActor = Literal['user', 'model', 'tool', 'system']Which kind of party produced an event: user, model, tool or system.
user is the party outside the system, whoever it is. The framework sets user on each Input and Cancelled
that a caller produces. The runtime records actor but does not read it to decide anything.
NodeName
type aliasNodeName = strtag
functiontag(model_type) -> strReturn the type tag of an event class or an element class.
The tag is the default of the class's type field. A store keeps an event or an element under its tag. To read
it back, the framework looks the class up by the tag.
Returns
strThe tag, such as model_output for ModelOutput.
Raises:
TypeError: The class has notypefield with a string default.
Opened
modelOpenedInput
modelInputSomething that arrives from outside: a user message, a task spec, an approval decision.
It starts or continues a thread. Its elements are recorded when it is accepted, so numbering reads the thread alone.
A fresh input opens a turn. An input that responds to a Request continues the turn that the request
was made in. Subclass it for a typed input of your own, with a type of its own.
attributeactorActor= 'user'user by default: the party outside the system.
attributepayloadJsonValueThe message or value as it arrived: any JSON.
attributemodelstr | None= NoneThe model that this input asks for, or None for the deployment's default.
Request
modelRequestThe agent halted because it needs input from outside.
A thread whose last event is a Request is waiting.
The Input that responds to a request is its response. The response continues the turn at the edge of
the node that asked. A request has no default actor: the model for a clarification, system for a gate.
attributetypeLiteral['request']= 'request'The tag, request.
attributekindstrWhat kind of request this is, in the agent's own words, such as a clarification or an approval.
attributeaboutstr | None= NoneWhat the agent asks, written for a person to read, or None.
attributeexpectsdict[str, JsonValue] | None= NoneThe JSON Schema that the payload of the response must satisfy, or None to accept any
JSON.
Finished
modelFinishedCancelled
modelCancelledFailure
modelFailureA step raised instead of returning: no result exists for the node named in origin.
The run halted there. An error the agent can react to (a tool 404, an empty retrieval) is not a Failure: it is
recorded as data on its own event type.
attributetypeLiteral['failure']= 'failure'The tag, failure.
attributeactorActor= 'system'system by default.
attributeerrorstrWhat went wrong, as the runtime saw it.
attributetransientbool= FalseTrue if the same step can succeed when it runs again, such as after a timeout or an outage. False if it fails again until something changes.
BudgetExceeded
modelBudgetExceededThe turn made more model calls that call tools than its budget allows.
The tool calls of the last model output did not run, and the runtime told the model to give its final answer. The event records no setting of the budget.
attributetypeLiteral['budget_exceeded']= 'budget_exceeded'The tag, budget_exceeded.
attributeactorActor= 'system'system by default.
Model calls and tool calls
A ModelOutput records what a model call gave, and a ToolOutput records the result of a tool call.
ModelOutput
modelModelOutputOne model output: the text, the tool calls it asks for, its reasoning, why it stopped and what it cost.
Whether it is a message or a tool call is read off tool_calls. A model step records it with the citation
markers out of content and real ids in its tool calls. markers and each call's translated give back what
the model wrote.
attributetypeLiteral['model_output']= 'model_output'The tag, model_output.
attributeactorActor= 'model'model by default.
attributecontentstrattributemarkerstuple[Marker, ...]attributemodelstr | Noneattributetool_callstuple[ToolCall, ...]= ()The tool calls that the model asked for. The ToolOutput with the result of each call carries
the same call_id.
attributereasoningstr | None= NoneThe model's reasoning text, or None if it produced none.
attributefinish_reasonFinishReason | None= NoneWhy the model stopped, or None if the adapter did not say.
attributeusageTokenUsage | None= NoneThe token counts of the call, or None.
attributeprompt_charactersint | None= NoneThe characters of the ChatRequest of the call: its messages, the tool calls in them
and the tools offered. With usage.prompt_tokens, the next call estimates only what was added. None
if not counted.
attributerequest_hashstr | None= NoneThe SHA-256 of the ChatRequest that the framework built for the call, as canonical JSON.
A rebuild of the ChatRequest from the trace before the call gives the same hash, unless the code
changed. None if not recorded.
attributecalls_toolsboolWhether the model asked for at least one tool call.
FinishReason
type aliasFinishReason = Literal['stop', 'length', 'tool_calls', 'content_filter']TokenUsage
modelTokenUsageToken counts for one model call.
attributeprompt_tokensintThe tokens in what the model was given.
attributecompletion_tokensintThe tokens that the model produced.
attributetotal_tokensintThe total, as the provider reported it.
ToolCall
modelToolCallOne tool call that the model asked for, with real ids for its element numbers and result handles.
Put each translated value back to get the call as the model wrote it.
attributeidstrThe call's id. The ToolOutput with the result of the call carries it as call_id.
attributenamestrThe tool's name, as the model saw it.
attributeargumentsdict[str, JsonValue]attributetranslatedtuple[Translation, ...]= ()Each value of arguments that replaced a number or a handle that the model wrote.
Translation
modelTranslationOne argument value that the model wrote as an element number or a result handle.
The call's arguments hold the real id or the call id that the value named instead.
attributepathstrWhere the value sits in the arguments: document_id, or documents.1 for an item of a list.
attributewrittenJsonValueWhat the model wrote there.
ToolOutputBase
modelToolOutputBaseThe result of one tool call, linked to the call by call_id.
Each result has a result handle. The base is never recorded itself: ToolOutput carries what a tool returned,
and Reopened names what is shown again.
attributeactorActor= 'tool'tool by default.
attributecall_idstr | None= NoneThe id of the tool call that this is the result of.
ToolOutput
modelToolOutputThe result of one tool call, linked by call_id. error is data the model reacts to.
Its elements are shown before text.
A tool error is never a Failure: the model reads error and continues. Subclass it for a tool that
records typed fields, with a type of its own.
attributedataJsonValue= NoneThe tool's result, as JSON.
attributetextstr | None= NoneThe tool's result as text that the model reads as written, before data. It never holds an
id.
attributecitationstuple[Citation, ...]attributeerrorstr | None= NoneWhy the tool could not give a result, or None if it could. The model reacts to it as data.
Image
modelImageA picture that a tool shows the model with its result, such as a rendered page.
attributedatabytesThe image's bytes. JSON holds them as base64 text, and validation accepts both encodings.
attributemimestr= 'image/png'The image's media type, image/png by default.
Reopened
modelReopenedContent shown again that was shortened or removed earlier, named by its ids only.
What the model sees is drawn from the earlier results when the context is rendered, so nothing is stored twice.
attributetypestr= 'reopened'The tag, reopened.
attributereopened_elementstuple[str, ...]= ()The real ids of the elements shown again.
attributereopened_resultstuple[str, ...]= ()The call ids of the tool calls whose whole results are shown again.
Compaction
modelCompactionA summary that replaces the events before a kept event in every later request to the model.
The kept event is the one at kept_from_seq in kept_from_stream. The thread keeps the events that the summary
replaces.
attributetypeLiteral['compaction']= 'compaction'The tag, compaction.
attributeactorActor= 'model'model by default.
attributesummarystrattributekept_from_seqintattributekept_from_streamStreamattributemodelstr | None= NoneThe model that wrote the summary.
attributeusageTokenUsage | None= NoneThe token counts of the call that wrote the summary.
attributerequest_hashstr | None= NoneThe SHA-256 of the ChatRequest that the framework built for the call that wrote the summary,
as canonical JSON. None if not recorded.
methodfirst_kept(events) -> intEffects
An EffectIntent records an action outside the thread, and an EffectResult records its result.
EffectIntent
modelEffectIntentThe declaration, sealed before anything happens, that the agent will now act outside the thread.
kind is the declared verb, args the sealed argument.
For an intent without an EffectResult in the log, it is unknown whether the world changed.
attributetypeLiteral['effect_intent']= 'effect_intent'The tag, effect_intent.
attributeactorActor= 'system'system by default.
attributekindstrThe declared verb, such as refund or send_mail.
EffectResult
modelEffectResultThe result of one intent, linked by intent_seq. error is data the graph routes on.
The runner stamps intent_seq. An effect result has no default actor: system if the runtime saw
the result, user if a party outside the system reported the result.
attributetypeLiteral['effect_result']= 'effect_result'The tag, effect_result.
attributeintent_seqint | None= NoneThe seq of the EffectIntent that this is the result of.
attributedataJsonValue= NoneWhat the world gave back, as JSON.
attributeerrorstr | None= NoneA definitive refusal from the world, or None if there was none.
Elements
An element is something the model is shown and can refer to again by id: in a tool argument or in a citation.
Element
modelElementSomething the model is shown and can refer to again. Subclass Reference or Source, never this.
The class of an element tells what the element is. A class is stored under its name in kebab case
(KnowledgeBase as knowledge-base) unless it declares type itself. So when you rename a class, its stored
name changes too, and stored threads no longer read back. Declare the old name as type to rename safely.
An element is immutable. Add each subclass to the element_types of the agent whose threads record it.
attributetypestrattributeidstrThe real id. The model never sees it, only the number that the element is shown under.
attributerefstr | None= NoneThe real id of the element that this one is part of, such as a passage's document. None if
it is part of nothing.
Reference
modelReferenceSource
modelSourceAn element whose content the model saw, and so the only kind it can cite.
Subclass it once for each kind of thing, such as a passage or a web page. A source must name the
element that it is part of in ref: validation fails without it.
attributetextstrThe material that the tool retrieved, a verbatim quote of the element that ref names. The
model sees it as the agent's context policy renders it, which can be shortened.
ElementEvent
modelElementEventAn event that shows elements to the model.
The elements can be an input's attachments, a tool's documents and passages, or the retrieved sources of an agent's own event type. Every element shown is numbered by first appearance in the thread, so the model can cite it and name it to tools. Subclass it for an event type of your own that shows elements.
ElementTypes
classElementTypes(*types)The element classes stored elements are read back into, by their type tag.
A set never changes: including returns a new one.
Make the set of element classes.
paramtypestype[Element]The element classes to read.
Raises:
methodincluding(*types) -> ElementTypesmethoddecode(data) -> ElementUnknownElementTypeError
errorUnknownElementTypeErrorCitations
A citation is one number that the model cited, and the source that the number named.
Citation
modelCitationOne number that a model output cited, at an offset in its cleaned text, and what it resolved to.
A marker that cites several numbers gives one citation for each number.
attributeatintattributestatusLiteral['resolved', 'invalid']resolved if the number named a source, invalid if it named none.
attributesourcestr | None= NoneThe real id of the cited source, or None if the citation is invalid.
attributedocumentstr | Noneattributereasonstr | None= NoneWhy an invalid citation did not resolve, or None if it resolved.
Marker
modelMarkerA citation marker that the model wrote, taken out of its text.
A marker records where it stood, the exact characters cut out there, and the numbers that it cited. Put each
text back at its at to get the text as the model wrote it (see inserted).
attributetextstrThe characters cut out there: the marker and the spaces before it.
attributecitedtuple[CitedNumber, ...]The numbers that the marker cited, in the order written.
CitedNumber
modelCitedNumberOne number inside a marker: what the model wrote, and the real id of the Source it named then.
attributewrittenintThe number as the model wrote it.
attributesourcestr | None= NoneThe real id of the source that the number named when the model wrote it, or None if it named none.
inserted
functioninserted(text, pieces) -> strReturn text with each piece put in at its offset.
inserted(text, [(m.at, m.text) for m in markers]) gives back the text as the model wrote it.
paramtextstrThe text without the pieces.
parampiecesSequence[tuple[int, str]]Pairs of an offset in text, in characters, and the string to put in there. The pairs can come in
any order. Pieces at one offset keep their order.
Returns
strThe text with every piece put in.