Structured tokens

Structured/agentic tokens are first-class entities: a token (a "project") carries typed attributes (its phase, npv, cost-to-date), custom behavior, and its own history, and can be instantiated, selected by predicate, and advanced through lifecycle phases. A structured species is declared with @structured_token and registered against a network before tokens are added to the initial marking.

ReactiveDynamics.register_structured_species!Function
register_structured_species!(net, type)

Register the structured-token kind type (a Symbol) as a species of the static network net, adding a :S row named type if one does not already exist and flagging it specStructured = true. This is what tells the engine that occupants of that place are first-class token agents (counted from the "structured" container), not a plain scalar count. Returns nothing. The @register sugar and @structured_token (which defines the host struct) are the usual companions; a kind must be registered before instances can be added to a ReactionNetworkProblem.

source
ReactiveDynamics.add_structured_token!Function
add_structured_token!(problem, agent) -> agent

Add a structured-token instance agent to the live problem: entangle it under the "structured" AA container and assign it a per-species monotonic creation index (ADR 0006 §E), so the deterministic (species, creation_index) selection order every token operation relies on is well-defined. Returns the added agent. This is the imperative counterpart to declaring the token in the PopulationEntry initial marking; both funnel through here so creation indices are contiguous and reproducible under (model, seed).

source
ReactiveDynamics.AbstractStructuredTokenType
AbstractStructuredToken <: AbstractAlgebraicAgent

Abstract supertype of every structured (agentic) token kind (ADR 0006/0008). A structured token is a first-class entity — it carries its own attributes, a stable identity, and a history, and moves through a lifecycle (created → bound to a transition as a consumed resource → advanced/retired) rather than being an anonymous unit of a plain-species count. Each concrete kind is an AlgebraicAgents.@aagent, so a token is a genuine node in the AA hierarchy under the problem's "structured" container. Define a kind with @structured_token, register it with register_structured_species!, and add instances with add_structured_token! or the declarative PopulationEntry initial marking. Kinds get their behavior for free (no evolution rule by default — the orchestrator advances them); override log_token_fields to record fields into the per-token trajectory log.

source
ReactiveDynamics.BaseStructuredTokenType
BaseStructuredToken <: AbstractStructuredToken

The base structured-token layout every @structured_token kind inherits (via @aagent FreeAgent). It supplies the protocol fields the engine relies on: species (the :S row the token currently occupies, or :removed once soft-retired), bound_transition (the Transition instance the token is currently committed to as a consumed resource, or nothing when free), and past_bonds (the token's audit history — the (species, t, transition) triples of every transition it has been used in). A concrete kind adds its own modeling attributes (e.g. phase, npv) on top of these; the base layout is what makes a token bindable, selectable, and auditable without per-kind boilerplate.

source
ReactiveDynamics.PopulationEntryType
PopulationEntry(species, kind; count = 1, attributes = Dict{Symbol, Any}())

A declarative initial-marking entry (ADR 0007 §B): count instances of the structured kind (a registry key resolved to a host constructor), each token placed on species and built from attributes — a Dict of field => Expr-or-literal evaluated once at t=0 through the state's seeded rng (§4 D5). It is the structured-token analogue of specInitVal for plain species: put PopulationEntrys in the population= vector of ReactionNetworkProblem to declare the t=0 marking declaratively, so it re-instantiates cleanly on reinit! / across ensemble members. The alternative form — an already-constructed host token agent — is placed in the population vector directly (no PopulationEntry, and the registry is not consulted).

source
ReactiveDynamics.log_token_fieldsFunction
log_token_fields(token) -> NamedTuple

Per-token trajectory-log hook (ADR 0013 §A1 / CONTRACT §14.1). A host token KIND overrides this to declare WHICH fields the orchestrator records into state.token_trajectory each tick, e.g.

ReactiveDynamics.log_token_fields(t::ProjectToken) = (; t.phase, t.npv_peak, t.pos_remaining)

The default logs NOTHING — the trajectory log is bounded by per-kind opt-in (Invariant 2), so it does not grow for kinds that don't opt in (load-bearing because retired tokens are KEPT under the Milestone-1 soft-:removed decision). The hook MUST be 𝓕ₜ-measurable: a pure field read, no RNG, no future (Invariant 1) — it returns the snapshot rather than holding it, keeping storage central.

source

Runtime token instances

A Transition is a live in-flight instance of a transition recipe; the tokens it occupies are its bound structured agents. ReactantSpec is the promoted transition↔species incidence row — the typed, foreign-key-exact form of a reaction line that structured-token binding and species merges repoint against.

ReactiveDynamics.TransitionType

One in-flight transition instance — an AlgebraicAgents @aagent, so a live transition is itself a node in the AA hierarchy. Spawned when a transition fires and held in the state's ongoing_transitions until its cycle time elapses, whereupon it completes (with its terminal probability-of-success) and emits its RHS products. i is the originating :T row; trans is the per-instance attribute dict (cycle time, priority, …); bound_structured_agents/nonblock_structured_agents/structured_to_agents hold the tokens this instance occupies; t is its spawn time, q its allocated quantity, and state its progress through the cycle.

source
ReactiveDynamics.ReactantSpecType
ReactantSpec

One row of the promoted transition↔species incidence relation (ADR 0003 Phase 2): a transition trans (FK → a :T row) consumes/produces species (FK → an :S row) with stoich stoichiometry on side (:lhs or :rhs), under a modality set. Promoting the relation from the re-parsed :trans Expr into a typed record with INTEGER foreign keys makes the model's defining relation FK-checkable and — the headline win — lets equalize! merge species by structurally REPOINTING the species FK instead of doing string surgery on names. A legitimately dynamic reactant (a @choose/@move/@structured/@advance/@select term, or expression-valued stoichiometry) carries the sentinel species = 0 and stashes its original term in expr; a static reactant has species ≥ 1 and expr === nothing. The table is DERIVED from the authoritative :trans column (see populate_reactant_specs!) and is additive/behavior-preserving — the runtime still parses :trans per tick. Read it via reactant_specs.

source
ReactiveDynamics.reactant_specsFunction
reactant_specs(net) -> Vector{ReactantSpec}

The promoted transition↔species incidence table of net (ADR 0003 Phase 2) — the FK-exact ReactantSpec rows. populate_reactant_specs! fills it from the :trans column and equalize! keeps it FK-exact across a species merge; it is empty for a freshly-constructed or not-yet-promoted model, so a caller wanting the table on such a model calls populate_reactant_specs!(net) first (as equalize! does).

source
ReactiveDynamics.specnameFunction
specname(net, i) -> Symbol

The :S species name at row index i — the inverse of find_index, used to resolve/check a ReactantSpec FK target back to a name.

source