Skip to content

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.

  • One channel, both directions typed. The workload speaks RuntimeFrame; the platform answers HostFrame. 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 FunctionFamily values 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 Checkpointed or Replayable, and the platform holds it to that.

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.

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.

FamilyWhat it asks forServed by
Generationa model call on the session’s provider routerworker
ToolInvokea tool call through the session’s registry and sandboxworker
ObservedValuerecord a locally-observed value for replayworker
MemoryReada durable-memory readorchestrator
MemoryWritea durable-memory write, attributed to the callerorchestrator
Checkpointan explicit checkpoint of the calling agentorchestrator
ExtensionEmita tier-4 extension event on the session logorchestrator
Spawna child agent through the ordinary spawn pathorchestrator
Hitla human confirmation mid-runorchestrator
PositionReporthand the runtime’s durable position to the coordinatororchestrator

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.

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.

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 Budget failure, 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.
  • durabilityCheckpointed or Replayable (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.