Programmable agents & the function plane
Most agents in apomesh are configured: you declare a goal, a loop, a budget, a toolset, and the substrate’s coordinator runs them. Some agents cannot be expressed that way — the logic is genuinely code, with branches, loops, and domain rules no configuration language should try to encode. A programmable agent is that case: bespoke code, authored in whatever language its author chose, published as a container image, and run by the platform as a long-lived confined workload.
The design problem is what such an agent is allowed to touch. Code you did not write, running beside the worker, must not hold a database handle, a provider key, or a session object. So it holds nothing:
A programmable agent holds no substrate handles. Every capability it uses arrives as a typed, family-gated, budget-metered, event-sourced call over one channel.
That sentence is the whole design. The function plane is that channel.
What it governs
Section titled “What it governs”- One channel, both directions typed. The workload speaks
RuntimeFrame; the platform answersHostFrame. A frame sent the wrong way fails to decode rather than being caught by a runtime check somebody has to remember to write. - A closed capability vocabulary. Ten
FunctionFamilyvalues name everything a runtime can ask for. An unrecognised family is a typed denial. - Authorization at both ends. The published contract’s family set is checked by the worker before it serves or relays, and again by the orchestrator at its demux. Neither end trusts the other’s check.
- Identity told, never claimed. A runtime is told which agent it is and cannot assert otherwise; the worker stamps attribution from its own state.
- No credential, by construction. Model calls and tool calls execute worker-side, where the keys already live. The runtime receives outputs.
- Durability as a declared level. A published shape asks for
CheckpointedorReplayable, and the platform holds it to that.
The shape of a run
Section titled “The shape of a run”The split is not arbitrary. Generation, ToolInvoke and ObservedValue are
served by the worker because that is where the session’s provider router and
the tool sandbox already are — relaying them would ship a credential or a
sandbox decision across a hop that exists precisely to avoid that. Everything
else relays to the orchestrator, which owns the session’s durable state.
The ten families
Section titled “The ten families”Each family names one capability, and each carries its own owner, effect class, and metering on the enum — never as an allowlist at the enforcement point, because an allowlist fails open for a family added later.
| Family | What it asks for | Served by |
|---|---|---|
Generation | a model call on the session’s provider router | worker |
ToolInvoke | a tool call through the session’s registry and sandbox | worker |
ObservedValue | record a locally-observed value for replay | worker |
MemoryRead | a durable-memory read | orchestrator |
MemoryWrite | a durable-memory write, attributed to the caller | orchestrator |
Checkpoint | an explicit checkpoint of the calling agent | orchestrator |
ExtensionEmit | a tier-4 extension event on the session log | orchestrator |
Spawn | a child agent through the ordinary spawn path | orchestrator |
Hitl | a human confirmation mid-run | orchestrator |
PositionReport | hand the runtime’s durable position to the coordinator | orchestrator |
A refusal is always an answer, returned on the same correlated result frame a served call would have used. Dropping the frame would leave the runtime waiting forever — a hang wearing a security control’s clothes.
The call sequence is the dedup handle
Section titled “The call sequence is the dedup handle”Every call carries a call_seq: runtime-minted, strictly monotone, naming the
call. Re-sending a sequence names the same call, so a mutating effect the
substrate already applied is answered from what it did rather than applied twice.
call_id is a different thing — it correlates one reply on one attachment, and
the substrate treats it as opaque. The distinction matters across a restart: a
restored runtime legitimately mints a fresh call id from a fresh process
while carrying the same sequence. Any guard keyed on the call id would read
that as a new call and apply the effect a second time.
This is why a durable programmable agent reports its position rather than tracking one in memory, and why resuming with a fresh sequence would be a bug the substrate is right to punish — it would spawn a second child and write a second memory entry.
Three effect classes, not two
Section titled “Three effect classes, not two”FunctionFamilyEffect is Mutating, ReadOnly, or Superseding — three
states because “repeating this is harmful” and “repeating this is harmless” do
not exhaust the cases. For a superseding family the harm runs the other way, and
suppressing the repeat is the defect:
- A position report answered from a record, without writing, would tell a runtime that a newer position is durable when nothing was persisted.
- A HITL call’s pause writes the checkpoint that closes the runtime era — so a dedup record of it is destroyed by the very act that creates the pause, and the re-issued call after restore would find nothing and pause again. Forever.
Identity, and what a runtime may not author
Section titled “Identity, and what a runtime may not author”A runtime never asserts who it is. The Init frame tells it, as a one-way key
it can echo and cannot convert back, and calls carry no agent reference at all.
The same stance governs every field a runtime could otherwise use to author a
fact about the substrate’s own view of it. A Hitl body has no trigger — the
substrate stamps that — because a runtime able to author its own trigger could
report “a budget was exhausted” for a pause it simply asked for. Where a wire
message has a slot the runtime must not fill, the body omits it and the demux
refuses a present one, rather than accepting and ignoring it.
Refusal and cancellation are different events
Section titled “Refusal and cancellation are different events”- A metered call that would exceed the remaining budget is refused:
a
Budgetfailure, nothing stopped, a request denied before it ran. - A runtime whose budget is exhausted is stopped: cancelled, attributed to the budget — policy stopping work already in flight.
Both are needed. The gate alone would let a runtime ask and be refused forever; the stop alone would let one over-large call blow through the allocation. Eviction follows the same rule: a coordinator-initiated stop is never an error, it is a successful outcome carrying “you are being stopped.”
What a published programmable shape declares
Section titled “What a published programmable shape declares”Publishing one is publishing a RuntimeContract alongside the agent’s manifest:
image— the OCI image reference the platform runs.families— the capability set this shape may use. The authorization ceiling, checked at both ends.durability—CheckpointedorReplayable(durability & determinism).resources— the runtime’s resource envelope.protocol_version— which revision of the channel the image speaks.
The shape is locked at publish time, exactly like every other published agent
(published agents), and a
session starts by (name, version). From the operator’s side a programmable
session is an ordinary session: same event log, same budgets, same HITL, same
console.
Related
Section titled “Related”- Durability & determinism — replay, the result journal, and the three determinism tiers.
- Authoring a programmable agent — the protocol from the workload’s side.
- Credential plane — why no key reaches the workload.
- Deployment & isolation — where the workload runs.