Constraints#
What each execution unit may read, may write, and may not do. This
document is normative; it restates the rules of eb-semantics.rst in
the axis a modeler actually asks about (“can I do this here?”) and adds
no new ones. Every clause cites the rule it comes from.
The five units are pi, event, agent, fn, and init,
plus the exec section, which is not an execution unit but does admit
statements.
Periodic items (pi)#
May read — earlier pis’ current-period values, in declaration order
(HIST-60); any pi’s history via [-k] (HIST-10); module-level
variables; context values :p/:i/:t/:r (CTX-10..30); fn
calls (FN-40).
May write or do — its own value, and only through the mandatory
terminating expression (PI-20); ephemeral locals (PI-30); stop_r
(SCHED-20); :capacity (RC-60); trailing keyword statements after the
value is committed (PI-25).
May not — be assigned to (PI-40); mutate state variables (STATE-20);
spawn agents (AG-25); timeout or claim (AG-80, CLAIM-40).
Events (event)#
May read — state variables; module-level variables; the arguments
bound at schedule time (SCHED-12); context values; fn calls.
May write or do — mutate state variables (STATE-20); sched()
further events (SCHED-10); spawn agents (AG-25); stop_r (SCHED-20);
:capacity (RC-60); ephemeral locals (EV-40). Executes atomically when
dequeued (EV-10).
May not — suspend: no timeout, no claim (AG-80, CLAIM-40);
write module-level variables; read a pi’s value (EV-35); be invoked
directly by name (EV-25).
Agents (agent)#
May read — everything an event may read, plus the arguments bound at
spawn time and rsc parameters (RC-40).
May write or do — everything an event may do, plus timeout
(AG-80), claim and release (CLAIM-10, CLAIM-15). Ephemeral locals
(AG-40).
May not — write module-level variables (AG-50); read a pi’s value (AG-55); return a value — an agent invocation is a statement, not an expression (AG-60).
Suspension is not restricted to the top level
An earlier specification asserted that suspension could occur only at
the top level of an agent body, and used that to argue implementations
need no coroutines. That restriction is not part of this language.
timeout and claim are valid wherever a statement is valid
inside an agent, including within if, match, and for. The
segment model accommodates this directly: an agent is a cyclic graph
of segments cut at suspension points, not a straight-line sequence, so
a suspension inside a loop is an ordinary back edge.
Functions (fn)#
May read — its parameters and module-level variables. Nothing else.
May write or do — compute and return a value of its declared type (FN-25). Draw from the random stream (INTR-31). Nothing observable outside its own frame; locals are ephemeral (FN-50).
May not — mutate state variables (FN-30) or read them (FN-32);
reference a pi by name (FN-35); call sched(), stop_r(),
stop_s(), or halt() (FN-30); timeout or claim (AG-80,
CLAIM-40); spawn agents (AG-25); change resource capacity (RC-60).
Init block (init)#
Has the same powers as an agent body (INIT-30): spawn agents, call
functions, timeout, claim, release, mutate state, set
capacity. It is a singleton (INIT-10), runs once per run at t = 0
(INIT-20), and runs after state and resources are reset (INIT-40).
Exec section#
Statements — including run(), sched() registrations, and
:print — are valid only after exec (EXEC-05). Assignment at exec
level reaches exec locals and module globals only (EXEC-45).
:randseed is valid only here, at most once (EXEC-35). :capacity
is not valid here: resources exist only for the duration of a run()
(RC-60).
After a run() completes, exec statements may read a pi’s or state
variable’s final value directly, in addition to the observation-based
intrinsics (EXEC-45, INTR-40).
Cross-cutting rules#
These are the rules that close the system — the ones that make the table above exhaustive rather than indicative.
STATE-20 |
State variables are mutable only inside |
STATE-30 |
State variables are the Markov state; pure pi models have none. |
SCEN-10 |
Scenarios contain only assignments to module-level variables, applied once per run before period 1 (SCEN-05) — the only sanctioned way module-level values change between runs. |
SCOPE-40 |
No shadowing, anywhere. |
GUARD-50 |
Guard expressions are side-effect free by construction. |
GRP-60 |
Group declarations apply to |
SCHED-20/30 |
|
MOD-40 |
Imported non-state symbols are read-only; imported state
variables are mutable only from an |
Why the constraints exist#
A bug in a Monte Carlo model does not crash. It produces a plausible
wrong distribution — the worst failure mode, because randomized output
cannot be verified by inspection. Nearly every constraint above converts
some source of silent bias into a compile-time error, and
eb-errors.rst shows each one surfacing as a specific, positioned
message.
All causality is findable. State changes happen only in
event/agent/init bodies (STATE-20) — a small, syntactically
identifiable set of sites. Function calls, pi references, and guard
expressions can never move the simulation behind your back (FN-30,
PI-40, GUARD-50).
Pis are spreadsheet rows with the race conditions outlawed. Once per period, in declaration order, immutable once produced (PI-10, PI-50). Same-period references must point backward in declaration order (HIST-60), so same-period cycles cannot be written; feedback must go through explicit history, making every recurrence visible in the source.
Function dependencies are visible at the call site. A function that could reach into simulation state would have dependencies invisible where it is called. FN-32 and FN-35 make the answer to “what state does this touch?” always “none”.
An agent body is the whole story of its process. Suspension is
syntactically visible — timeout, claim — and no fn can hide a
wait, because functions cannot suspend at all (FN-30, CLAIM-40). Note
that explicit release (CLAIM-15) reintroduces one bug class the older
block form excluded: a held unit can now be forgotten. CLAIM-70 bounds
the damage by reclaiming held units when an agent terminates, so a leak
costs capacity for the agent’s remaining lifetime rather than for the
rest of the run.
Reproducibility is a theorem, not a hope. DET-10 — same code, inputs, scenario, and seed give identical observable results — is only promisable because of the constraint system: single-threaded atomic handlers (EV-10, DET-20), FIFO ordering in both the event queue (TIME-30) and resource wait queues (CLAIM-30), exact IEEE 754 time comparison (TIME-50), and no mutation channel outside the sequenced units. EXEC-30’s total per-run reset makes runs independent, which is what licenses parallel run execution (DET-30) with no change in results.