Methodology for instantiating, forking, running, merging, and retiring agents in the a0 platform and its near-identical mirror a0ucns. Load this when adding or changing a sub-agent spawn path, a PCNA instance fork/merge, an agent definition or naming scheme, spawn caps or approval gating, an agent run/log table, a heartbeat-driven agent task, or a checkpoint of agent state. Use it before writing any code that creates, addresses, schedules, or tears down an agent or sub-agent, so the new code follows the platform's existing lifecycle, fork/merge, identity, and gating contracts rather than inventing a parallel one. NOTE: a0-betatest (a0p) has diverged to a different per-user CRUD + native-ZFAE instancing model — this skill's spawn/fork/merge sequence does NOT apply there; see "a0-betatest divergence".
Install
npx skillscat add the-interdependency/stack/agent-instantiation Install via the SkillsCat registry.
agent-instantiation — How a0 brings agents into being
a0 runs one persistent agent (ZFAE) and lets it fork sub-agents
(a0(model)zeta{n}) that run in parallel and merge back. An agent is
not an LLM call: the LLM is an interchangeable energy provider, while the
agent is a PCNAEngine instance (six prime-indexed rings) plus its
identity, memory, run record, and checkpoint. This skill captures the
methodology a coding agent must follow so a new spawn path, ring fork, merge
mode, or agent definition plugs into the existing lifecycle instead of
growing a second, divergent one.
Load this when
- Adding or editing a sub-agent spawn path, or any code that creates an
agent_runsrow. - Adding a fork, merge, or converge of a
PCNAEngineinstance. - Defining a new agent (its name/symbol/slot/directives/tools) or
changing the naming scheme. - Touching spawn caps, approval gating, or the spawn executor.
- Persisting agent state (checkpoints, run logs) or scheduling an
agent task on the heartbeat.
Do not load this for ordinary LLM-call/inference-prompt work that does not
create or reshape an agent instance.
Scope and source boundary
The canonical source is a0 (python/engine/, python/services/,python/agents/, shared/schema.ts). a0ucns is a near-identical mirror and
follows this skill verbatim. a0-betatest (a0p) has diverged to a
different instancing model and does not follow the sequence below — see
"a0-betatest divergence" before touching that repo.
This is repo-specific runtime doctrine, not org-universal math: it
transfers no UCNS / PCNA / PCTA theorem status (see each repo's boundary
notes). Cite the a0 files below; if a mechanism is not in them, mark ithmmm rather than inventing it.
The model: persistent agent vs sub-agents
- Persistent agent (ZFAE). Defined as a plain dict in
python/agents/zfae.py(ZFAE_AGENT_DEF:name,symbol,slot,directives,tools,sentinel_seed_indices,is_persistent). Its
runtime body is a singletonPCNAEnginecreated once at FastAPI
lifespan startup viaget_pcna()inpython/main.py, restored from its
checkpoint. - Sub-agents. Forked children of the persistent instance, held in the
in-memory registry_sub_agents: dict[str, (PCNAEngine, meta)]inpython/services/agent_lifecycle.py. Each carriesparent_id,parent_run_id, andrun_idso it can be capped, found, and retired.
Lifecycle states
A sub-agent run is a row in the agent_runs table (shared/schema.ts) and
moves through:
running → queued by the sub_agent_spawn tool (no worker yet)
executing → claimed by the spawn executor; inference in progress
completed → inference finished, result logged
failed → exception raised; error recorded on the row
merged → terminal; sub_agent_merge absorbed it into the parentIn-memory state (the forked PCNAEngine) lives in _sub_agents; durable
state (status, depth, lineage, summary) lives in agent_runs; the event
stream lives in agent_logs. Keep these three in agreement.
Instantiation sequence (dependency order — follow it top to bottom)
- Define the agent, don't hardcode it. Add/extend an agent-definition
dict inpython/agents/withname,symbol,slot,directives,tools,is_persistent. Address it through the naming helpers, never a
literal string. - Boot the primary as a singleton. One
PCNAEngineper process viaget_pcna()at lifespan startup;await pcna.load_checkpoint()to
restore learned ring state; ensure its row in the agent/instance table.
Do not construct a second primary. - Spawn only through the
sub_agent_spawntool
(python/services/tools/sub_agent_spawn.py). NeverINSERTanagent_runsrow by hand. The tool checks spawn caps, derivesroot_run_id/depthfrom the parent run scope, inserts the rowstatus='running', and returns{ok, agent_id, run_id}immediately
(spawn is non-blocking). - Fork the engine via
InstanceMerge.fork(parent)
(python/engine/merge.py) — returns(child, meta). The child gets
independent tensors with small Gaussian noise (a0: σ≈0.02 on Φ/Ψ/Ω,
≈0.01 on Θ; Memory-L copied deterministically). Register it in_sub_agentswithparent_id+run_id. Never share tensor references
between instances. - Execute via the spawn executor, not inline. The background loop in
python/services/spawn_executor.pyclaims onerunningrow atomically
(SELECT … FOR UPDATE SKIP LOCKEDinspawn_db.py), resolves the
provider, runs one turn, emits toagent_logs, and sets the terminal
status (with the row's retry policy on transient errors). Do not call the
model directly from the spawn path. - Merge with
InstanceMerge.absorb(parent, child)when the child's
work is done — federated averaging blends the rings (a0: donor α≈0.15 on
Φ/Ψ/Ω, ≈0.8 on Memory-L). Then unregister the child, mark its rowmerged, and archive its log stream. Usefork/absorbfor the
parent⇄child path;converge(a, b, α)only for two live peers that both
continue. - Persist on a cadence, validate on restore. Save ring tensors to the
checkpoint store (a0: base64 insystem_toggles) from a heartbeat
task, not ad hoc; on load, validate every ring's shape and assign
nothing if any mismatches (all-or-nothing restore). - Gate every mutation. Manual spawn/merge routes call
require_admin(request)(or are listed in the gating allowlist with a
justification). Tools that cause side effects honor the approval scope
(get_approval_scope_user_id()); spawn caps and parent run scope rideContextVarsso nested spawns inherit the correct lineage and limits.
Fork / merge primitives (the only three)
| Op | Signature | Effect |
|---|---|---|
fork |
InstanceMerge.fork(parent) -> (child, meta) |
Parent continues; child is a noised copy. Spawn path. |
absorb |
InstanceMerge.absorb(parent, donor) -> dict |
Donor blended into parent (fed-avg), donor retired. Merge-back path. |
converge |
InstanceMerge.converge(a, b, alpha=0.5) -> dict |
Two live peers exchange state; both continue. |
Blending uses _fed_avg(a, b, alpha) = clip(alpha*a + (1-alpha)*b, 0, 1).
The constants above are a0's current values, not invariants — read them frommerge.py, don't reproduce them from memory.
Canonical agent nomenclature
The maintainer-defined identity grammar for an a0 agent is:
username( a0( <energy / inference provider> ) <auditor / teacher / …> )- Inside
a0( … )is the energy / inference provider — the LLM that
supplies compute, orzfaewhen the native inference engine is the
source. Read this slot as "what thinks". - The trailing token (optional) is the auditor / teacher / other special
access layered over that energy. The slot is open-ended — "other special
access yet to evolve". - The outer
username( … )names the owning user.
| Identity | Energy / inference | Auditor / teacher |
|---|---|---|
a0(gpt 5.5) |
gpt 5.5 | — |
a0(gemini 3.5)gpt5.5 |
gemini 3.5 | gpt 5.5 (auditor) |
a0(zfae) |
native ZFAE engine | — |
a0(zfae)gpt 5.5 |
native ZFAE engine | gpt 5.5 (teacher / auditor) |
Energy is inside the parens, auditor is outside, the user wraps the whole
thing. zfae inside the parens means native inference is the energy — it
is not an auditor.
Reconciliation note (
hmmm): a0's current code emits a different,
pre-nomenclature form —compose_name(...)→a0({model})zfaeandsub_agent_name(index, ...)→a0({model})zeta{index}— where the trailing
token is a fixed slot / sub-agent index, not the auditor, and there is nousername( … )wrapper. Treat the grammar above as the canonical target and
the code form as the implemented-but-unreconciled state. How the sub-agent
index (zeta{n}) composes with the energy/auditor grammar is not yet
specified — leave ithmmm, do not invent a merged form.
Identity and addressing
- Names follow the canonical nomenclature above; compose them, never
hardcode a literal. The model/energy tag resolvesmodel_id, elseprovider, else?. - The instance address is
engine.theta.instance_id(generated per
Θ tensor). Use it as the canonical handle for a running instance. - Run lineage is
(run_id, parent_run_id, root_run_id, depth)onagent_runs; logs inagent_logscarry the same keys. The human label
(nomenclature), the instance address (instance_id), and the run lineage
are distinct identities — keep them so.
Guardrails to honor
- Spawn caps (depth / fanout / concurrent-live, tier-scoped) are checked
in the spawn tool; raise/return the cap result, never bypass it. - Write-route gating — every
@router.{post,patch,put,delete}on agent
state callsrequire_adminor is allowlisted. - Module-build doctrine — every new Python module opens with a
# === MODULE_BUILD ===block; unknown fields arehmmm, not guessed
(meta-module-build). - 400-line budget and the
N:M C:D I:Ofile annotation
(scripts/annotate.py) apply to new agent modules.
Anti-patterns (refuse these)
- Inserting
agent_runsrows or calling the LLM directly from a spawn path
instead of going throughsub_agent_spawn→ executor. - Sharing or mutating another instance's tensors in place instead of
fork/absorb/converge. - A second primary
PCNAEngine, or addressing an agent by a hardcoded name
string. - Merging without retiring the donor row and archiving its logs (orphaned
executingrows / dangling registry entries). - Skipping
require_admin/ approval-scope checks on a new spawn or merge
surface.
a0-betatest divergence (does not follow this skill)
a0-betatest (a0p) replaced the a0 model wholesale. Do not apply the
spawn/fork/merge sequence there; its instancing is:
- An instance is a per-user CRUD entity, not a forked singleton.
AgentInstance(UUID + editableCharacterSheet) created/read/updated/
archived via routes —backend/agents/{schema.py,store.py,routes.py}.
There is no single persistentPCNAEngine; each agent is its own entity. - Each instance owns a native ZFAE weight bank, not shared ring tensors:
three 157-seed cores[157,53,7,7](1,223,187 scalars),A0ZFAEWeightBank
inbackend/interdependent_lib/zfae/weights.py; the native engine refuses
LLM fallback inzfae_nativemode and trains by teacher distillation. - No
sub_agent_spawn, spawn executor, orInstanceMerge. The only
"spawn/merge" is volatile in-memory sub-context scoping —MemoryCore.spawn_sub/merge_subinbackend/interdependent_lib/pcna/memory_core.py. No ring/weight forking,
no federated averaging, noagent_runsstate machine. - Identity is a UUID + character-sheet name, per
user_id— not thea0(model)zfae/zeta{n}naming convention. - Persistence is filesystem + Mongo + FIQ, not
system_toggles/agent_logs: per-agentstorage/agents/{id}/zfae_core.safetensors(+ meta
JSON), agent metadata in the Mongoagent_instancescollection, and a
hash-chained FIQ audit log for events. - Gating is sentinel + override, not spawn caps + ContextVars: 13
sentinels (S1–S13) with per-agent modes/weights and a pending-override
halt gate, plusMODULE_BUILD/BOUNDARIES/CAPABILITIES/RATIOS
enforced by the vendoreda0p_skillsrunners.
a0-betatest/_legacy_a0/ is a reference copy of canonical a0 (which does
follow this skill). If a0-betatest's per-instance native-ZFAE model needs its
own doctrine, it belongs in a separate skill, not by stretching this one.
hmmm
- a0's
fork()seeds its RNG fromtime.time(); rapid successive forks can
collide (flagged inmerge.py). Prefer a UUID-derived seed if you extend it. - Several
spawn_executorcontracts (stale-sweep, retry-once-on-transient)
are declared but not all implemented; verify against the live file before
relying on them. - The merge blend weights and noise σ are tunable constants, not proven
optima — treat them asrepo-local, not canonical.