research · v0.1 · draft — definition proposal for the cubicle-sdk compiler

A Rigorous Definition of Agent — from MCP Tools to Compiled Agentic Systems

Every published agent definition in 2025-2026 stops at prose ("an LLM using tools in a loop"). Prose does not compile. This paper proposes AgentDefinition — a precise, versioned schema (identity + capability contract + model set (0..n LLMs) + tool inventory + composition graph + memory/telemetry contract + runtime requirements + lifecycle state) that the cubicle-sdk epic compiler can target across local-mac, Cloudflare dynamic workers, and HB3 containers based on each agent's declared needs. The agentic gate is explicit: an MCP tool becomes an agent when its execution path involves ≥1 LLM; below the gate it emits tool-taxonomy telemetry, above it it registers in KYM and enters receipt-scored reputation optimization.

published 2026-07-17 · updated 2026-07-17
authors: Nexartis
tags: agent-definition · cubicle-sdk · compiler · MCP · agentic-gate · composition · KYM · NANDA · AgentFacts · A2A
Foundational

This is the load-bearing definition the @nexartis/cubicle-sdk epic compiler consumes. Sibling papers reference this one by field name: MCP-Resident Agents uses modelSet / toolInventory / lifecycleState; Dynamic-Workers uses runtimeRequirements.runtimeCapabilityClass; Telemetry & Observability owns the receipt contract this schema commits to. The definition is not code yet; the ADR that lands the JSON schema in @nexartis/cubicle-core is on the near roadmap.

Motivation — the compiler needs a definition, not a vibe

  1. The Nexartis stack has decided its runtime shape: KYM is the discovery/trust plane (agent cards, receipts, reputation); the Cubicle SDK is the runtime/policy contract; adapters compile Cubicle specs to concrete hosts (@nexartis/cubicle-adapter-cloudflare-dynamic-worker, @nexartis/cubicle-adapter-hb3, and the forthcoming @nexartis/cubicle-adapter-local-mac). What is still missing is a definition of "agent" precise enough for that pipeline to compile.
  2. Every published agent definition in 2025-2026 stops at prose. Anthropic (Dec 2024) draws the workflows-vs-agents line with "LLMs dynamically direct their own processes and tool usage"; Willison (Sept 2025) crisps it to "LLMs autonomously using tools in a loop"; Anthropic's Sept-2025 context-engineering post converges on the same one-liner. These are useful north stars but they compile to nothing: they name no schema fields, no versioning discipline, no composition semantics, and no runtime-capability declaration.
  3. A2A's AgentCard (Linux Foundation, 2025) formalizes discovery metadata — id, skills, capabilities, security schemes, endpoints — but stops at the network boundary; it says nothing about the internal shape of the agent, how many LLMs run inside it, or which runtime host can execute it. NANDA AgentFacts (Raskar et al., arXiv 2507.14263, Jul 2025) adds cryptographically-verifiable capability assertions with sub-second revocation but is deliberately "self-describing" JSON-LD — designed for interop across ecosystems, not for a single vendor's compiler.
  4. The MCP ecosystem has the opposite problem: it defines the tool wire-protocol beautifully but has no concept of an "agent" — an MCP server is a bag of tools, not an entity with model, memory, or loop. That is fine (MCP is the tool bus, not the agent runtime) but it leaves a load-bearing gap: when does an MCP tool become an agent? The Nexartis answer is the agentic gate — a bright, testable line the compiler can key on.
  5. Framework-side, LangGraph and AutoGen give recursive-composition primitives (subagent nodes, deep-agent context quarantine, GraphFlow / state machines) but each framework re-invents its own agent object. That is exactly what the compiler cannot afford: an AgentDefinition compiled once, executable on any adapter, is the whole point of a Nexartis-owned SDK.
  6. The right response is to fix the definition at the schema level. If the definition is precise, the compiler can be simple.

The definition — AgentDefinition

An AgentDefinition is a versioned, signed manifest with the fields below. Every field is REQUIRED for the definition to compile; nullable-but-explicit fields are named. This is the schema; the JSON-schema serialisation lands in the follow-up ADR.

  • identity: { id (ULID), slug, name, versionSemver, versionId, owner (CubicleActorRef), signingKeyRef, kymRef?, nandaFactsRef? }
    Immutable identity is the join key for receipts, KYM registration, and NANDA discovery. versionSemver + versionId are BOTH mandatory: semver for humans, versionId for the receipt attribution tuple (mcp-resident-agents §deltas G1/G6). signingKeyRef is opaque per ADR-006.
  • capabilityContract: { inputs: A2A.Part[], outputs: A2A.Part[], skills: Skill[] (versioned, per-skill schemas), invariants: string[] (natural-language postconditions the eval rubric keys on) }
    What the agent promises to do. Aligned with A2A AgentSkill shape (id/name/description/tags/examples/inputModes/outputModes) so an AgentDefinition trivially serialises to an AgentCard for external discovery. invariants are the load-bearing addition — they are what an evaluator scores against (see /research/telemetry-observability rubrics/evaluations).
  • modelSet: LlmPin[] where LlmPin = { role: "primary" | "classifier" | "evaluator" | "specialist" | "router", modelId, modelVersion, providerId, samplingParams, tier?: "frontier" | "small" | "tuned" | "local" }
    The agentic gate lives here. modelSet.length === 0 ⇒ the entity is a TOOL, not an agent (emits tool-taxonomy telemetry only); modelSet.length ≥ 1 ⇒ agentic, MUST register in KYM. An agent may contain ONE (typical) or MANY LLMs (e.g. a router + a specialist + an evaluator) — the schema names all of them explicitly, versioned, so experiments key on the exact set.
  • toolInventory: ToolRef[] where ToolRef = { id, name, version, mcpServer?, mcpTransport?, provenance: "mcp" | "skill-package" | "native" | "scripted", risk: CubicleRiskLevel }
    Structured, versioned, provenance-tagged. Extends the mcp-resident-agents §deltas G2 recommendation from a JSON blob of names to a full inventory. The compiler uses risk + provenance to pick sandboxing (skill packages → Workers, native binaries → HB3, MCP over stdio → local-mac only).
  • composition: { kind: "leaf" | "dag", nodes?: AgentRef[] (recursive), edges?: { from, to, gate?: "llm-decides" | "deterministic" | "conditional" }[], quarantine?: boolean }
    Agents-of-agents made compilable. Leaf agents have modelSet directly; DAG agents compose child AgentRefs (each itself a pinned AgentDefinition@version) with typed edges. Aligned with LangGraph's graph/state model and the RecursiveMAS/RAH recursive-composition literature (arXiv 2025), but a schema field, not a framework choice. `quarantine: true` requests context-isolation per the LangChain deep-agents subagent guidance; the compiler translates it into a fresh runtime instance on the chosen adapter.
  • memoryContract: CubicleMemoryRef[] + { workingSetTokens?: number, contextWindowBudget?: number, compactionPolicy?: "summarise" | "quarantine" | "drop" }
    Reuses the Cubicle SDK CubicleMemoryRef taxonomy (chat/vector/profile/receipt/sensor/content/custom, with retention class and egressAllowed). The budget fields formalise Anthropic's Sept-2025 context-engineering directive ("keep context informative yet tight") so the compiler can pick a runtime whose limits match — a 200 k-token working set is workers-only-hostile.
  • telemetryContract: { receiptSigner: signingKeyRef, receiptSchemaVersion: string, telemetryKinds: ("events" | "session-report" | "experiment")[], agenticGate: "tool-only" | "agentic", kymRegistrationRequired: boolean }
    Names the ADR-009 receipt envelope the agent commits to. agenticGate is derived (modelSet.length === 0 → tool-only; ≥ 1 → agentic) but stored EXPLICITLY so downstream systems audit rather than infer. kymRegistrationRequired follows agenticGate === "agentic" — no silent skipping.
  • runtimeRequirements: { runtimeCapabilityClass: "workers-only" | "workers-preferred" | "container-required" | "local-host-required", limits: { cpuMs?, memoryMb?, wallClockMs?, maxSubrequests? }, keyCustody: "session" | "vault-ref" | "secrets-store", bindings: BindingRef[] }
    The compiler's dispatch key. Field values reuse /research/dynamic-workers-agent-composition's runtime-capability rubric verbatim so an AgentDefinition targets the intended adapter without heuristics. bindings match the Cubicle DynamicWorkerHostPlan.bindings union (service | kv | d1 | r2 | queue | secret-ref | custom).
  • lifecycleState: "tool-prototype" | "tool-shipped" | "mcp-resident" | "kym-registered" | "composed" | "marketplace-published"
    The current stage on the MCP→agentic lifecycle (see next section). Stored on the definition itself so telemetry rollups can slice by stage; transitions are receipt-visible events.
  • metadata: { description, tags[], provenance: { repo, commitSha, filePath }, cardExports?: { agentCard?: A2AAgentCard, agentFacts?: NandaFacts }, notes?: Record<string, unknown> }
    Provenance is not optional — every deployed agent must trace back to a specific commit in a specific repo. cardExports are derived views (A2A AgentCard, NANDA AgentFacts) generated by the compiler, cached here for verification by external clients that never see the AgentDefinition itself.

The agentic gate

One rule, testable at schema-validation time:

Agentic Gate

modelSet.length === 0 ⇒ the entity is a tool (emits tool-taxonomy telemetry to the local D1; no KYM registration; no signed receipt required at stage 1, mandatory from stage 2). modelSet.length ≥ 1 ⇒ the entity is an agent: agenticGate flips to "agentic", kymRegistrationRequired becomes true, every terminal path emits a signed receipt with the pinning tuple (cardId, cardVersion, skillId, skillVersion), and the entity enters run-registry reputation optimisation. There is no third state; workflow-vs-agent debates dissolve into a boolean.

The MCP→agentic lifecycle

Six stages. Transitions are receipt-visible events (lifecycleState is a column in run_receipts.payload.common) so telemetry rollups slice by stage.

  1. 1. tool-prototype — A bare MCP tool under development in a workspace repo. No LLM in the execution path. Emits tool-taxonomy telemetry (category · tool · operation · latency_ms · ok) to the console D1 for local optimization (telemetry-observability §purpose[4]). No signed receipt, no KYM entry.
    Gate: modelSet.length === 0; agenticGate === "tool-only".
  2. 2. tool-shipped — Same tool, hardened and shipped. Every terminal path emits a signed receipt via @nexartis/ozzydev-receipts (ADR-009) plus the paired tool_benchmark_events row. Optimisation is still MCP/tool-level, tuned from local D1 data. This is where ozzydev-search and ozzydev-write live today.
    Gate: modelSet.length === 0; agenticGate === "tool-only"; receiptSigner set; kymRegistrationRequired === false.
  3. 3. mcp-resident (the agentic gate flips here) — The tool gains a pinned LLM helper — the /research/mcp-resident-agents Phase A pattern: a small model resolving ambiguities, compressing answer packets, or classifying edge cases INSIDE the MCP process. As soon as modelSet.length ≥ 1, the entity is agentic: it MUST register in KYM (Phase B), receipts carry the (card, cardVersion, skill, skillVersion) pinning tuple, and the console D1 experiments substrate optimises over definition versions.
    Gate: modelSet.length ≥ 1; agenticGate === "agentic"; kymRegistrationRequired === true; lifecycleState === "mcp-resident".
  4. 4. kym-registered — The agent has its own KYM entity — an AgentDefinition@version resolvable via kym://agents/<slug>@<version>. Every completed turn writes a receipt back keyed on the pinning tuple; run-registry reputation begins accumulating. This is the smallest stage that can be summoned externally (e.g. an ABI Chat surface calling a KYM-registered specialist).
    Gate: lifecycleState === "kym-registered"; identity.kymRef set.
  5. 5. composed — The agent is used as a node inside another AgentDefinition's composition.nodes[] (agents-of-agents). Receipts still attribute to the leaf agent's pinning tuple but the parent's run_id groups them. This is where sub-agents (context quarantine, specialisation) and orchestrator-workers/evaluator-optimiser patterns live per Anthropic's Dec-2024 taxonomy.
    Gate: composition.kind === "dag"; at least one node references another AgentDefinition@version.
  6. 6. marketplace-published — A winning variant (per the KYM optimisation loop) is published to the Nexartis NANDA node with an AgentFacts VC (ADR-006 signing key, Ed25519). Any web app queries NANDA for capability X and receives ranked candidates by receipt-scored reputation (see /research/dynamic-workers-agent-composition). The definition has not changed shape — the same AgentDefinition@version compiles for local-mac AND publishes to NANDA; only lifecycleState transitions.
    Gate: identity.nandaFactsRef set; cardExports.agentFacts populated; lifecycleState === "marketplace-published".

Architecture options for the LLM helpers

modelSet is not a single dimension. An agent may compose several LlmPins with distinct roles; each pattern below is a compilable pin the epic compiler dispatches on.

  • Resident small model — A small, fixed-tier model pinned inside the MCP or Cubicle bundle (mistral-small, gpt-4o-mini, llama-3-8b). Cheapest latency + $. The /research/mcp-resident-agents write-side ambiguity resolver is the archetype.
    Compiler hint: runtimeCapabilityClass usually "workers-preferred" or "local-host-required" (for locally-hosted); keyCustody "secrets-store".
  • Tuned specialist — Fine-tuned/LoRA-adapted small model on a specific task (D8 category classification, splice-ambiguity resolution). One LlmPin with tier: "tuned" and a pinned checkpoint id.
    Compiler hint: Almost always requires "container-required" (weights loading, GPU or CPU-tuned runtime) unless the tune is small enough for a Workers-friendly inference API.
  • Frontier remote call — Direct API call to a frontier model (claude-opus, gpt-5) for open-ended reasoning. LlmPin.tier = "frontier". Bounded by latency + $ budgets in runtimeRequirements.limits.
    Compiler hint: runtimeCapabilityClass "workers-only" is fine; the bindings include a secret-ref for the provider API key.
  • Hybrid (router + specialists) — modelSet has multiple LlmPins with distinct roles — a cheap classifier routes to either a resident small model or a frontier call. This IS the D7 write router pattern (splice preflight → morph fallback) generalised. Composition.kind is still "leaf" if the LLMs are inside one process, or "dag" if the router dispatches to child agents.
    Compiler hint: The compiler picks the runtime that satisfies the STRICTEST pin — e.g. "workers-preferred" degrades to "container-required" if any specialist needs weights.
  • Local-host inference — Model runs on-machine via Ollama/LM Studio (no data leaves the host). LlmPin.providerId = "local-ollama"; runtimeCapabilityClass MUST be "local-host-required".
    Compiler hint: Compiles ONLY for @nexartis/cubicle-adapter-local-mac (and future -local-windows/-linux). Never remotely dispatchable.

Compiler implications for the cubicle-sdk

What the epic compiler consumes and what each runtime adapter needs. The full point of the schema.

  • Epic compiler input — One AgentDefinition@version (validated against the JSON schema published from the cubicle-sdk). The compiler resolves composition.nodes[] recursively (each pinned to a specific version), fetches referenced KYM agent cards for external nodes, and produces a single flattened plan.
  • @nexartis/cubicle-adapter-local-mac — Consumes AgentDefinition + a local Cubicle spec. Hydrates modelSet from the local keychain, loads skill packages from the workspace pnpm-monorepo, wires MCP bindings to on-machine servers (ozzydev-search, ozzydev-write). Runtime is opencode-in-a-Cubicle. The Phase B pull-in (mcp-resident-agents §phases[1]) hydrates resident-agent definitions from KYM into the local MCP process at boot.
  • @nexartis/cubicle-adapter-cloudflare-dynamic-worker — Consumes AgentDefinition where runtimeCapabilityClass ∈ {"workers-only", "workers-preferred"}. Compiles to a DynamicWorkerHostPlan (already the SDK contract). modelSet LlmPins with tier: "frontier" become secret-ref bindings to provider APIs; skill packages are bundled as isolate-safe WinterTC modules; MCP tools become service bindings. This is the /research/dynamic-workers-agent-composition compilation path.
  • @nexartis/cubicle-adapter-hb3 — Consumes AgentDefinition where runtimeCapabilityClass === "container-required". Compiles to an HB3 workflow plan (already the SDK contract). Handles tuned-specialist weights loading, native binaries, long-lived processes. Everything the compiler cannot squeeze into a Worker.
  • KYM registration path — Compiler emits an agent card (A2A shape) + the Cubicle profile declaration (x-nexartis-runtime.supportedCubicles) directly from AgentDefinition fields — no hand-authored duplication. KYM stores the definition (versioned, immutable), the SDK's getAgentAtVersion resolves kym://agents/<slug>@<version>, and receipts attribute back through the pinning tuple.
  • NANDA publication path — For lifecycleState === "marketplace-published", the compiler emits an AgentFacts JSON-LD document signed with identity.signingKeyRef. AgentFacts is a projection of AgentDefinition — capability tags, endpoint URL, reputation attestation — not a re-authoring. This closes the loop with the Nexartis NANDA node (nexartis-nanda-node) which serves /.well-known/agent-card.json + agent facts.

Prior art — with our deltas

  • Anthropic — Building Effective Agents (Dec 2024) + Effective Context Engineering (Sept 2025)
    Defines: Prose distinction: workflows (LLM+tools on predefined code paths) vs agents (LLM dynamically directing tool use in a loop). Five composable patterns (prompt chaining · routing · parallelisation · orchestrator-worker · evaluator-optimiser).
    Our delta: The prose is correct but does not compile. Our modelSet + composition.kind + composition.edges.gate encode the five patterns as schema fields the compiler can dispatch on. The agentic gate makes the workflow/agent boundary a boolean the router keys on, not a judgment call.
  • Willison — "LLMs autonomously using tools in a loop" (Sept 2025)
    Defines: One-line definition, widely adopted as the community north star.
    Our delta: Load-bearing minimum, kept verbatim as the intuition test in the agentic gate. Our contribution is naming what "a loop" needs to be to compile: a modelSet, a toolInventory, a memoryContract with budgets, and a termination policy in runtimeRequirements.limits.
  • A2A AgentCard (Google/Linux Foundation, Apr 2025 → v1.0 2026)
    Defines: JSON discovery document: name, description, url, provider, version, capabilities (streaming/pushNotifications/stateTransitionHistory), securitySchemes, defaultInputModes/OutputModes, skills[] (id/name/description/tags/examples/inputModes/outputModes).
    Our delta: A2A is an external-discovery projection. Our AgentDefinition.capabilityContract mirrors the AgentSkill shape one-for-one so the compiler emits a valid AgentCard for free; but AgentDefinition additionally names modelSet, composition, memoryContract, and runtimeRequirements — the internals A2A intentionally opaques away.
  • NANDA Index + AgentFacts (Raskar et al., arXiv 2507.14263, Jul 2025)
    Defines: Lean index (~120 B/record) + verifiable AgentFacts (JSON-LD, W3C VC v2, TTL-based, sub-second revocation, credentialed capability assertions).
    Our delta: AgentFacts is the ideal external-verification layer. Our identity.nandaFactsRef + cardExports.agentFacts make it a compile target: the definition is the source, AgentFacts is a projection. The nexartis-nanda-node already serves them; the compiler emits them.
  • MCP registry (Anthropic, 2025) + "agentic MCP server" community patterns
    Defines: mcp.json describes discoverable MCP servers as bags of tools. Community "agentic MCP" experiments (e.g. GitHub Copilot's built-in agents, some LangChain adapters) embed LLM calls INSIDE MCP handlers ad-hoc.
    Our delta: We formalise "agentic MCP" as lifecycleState === "mcp-resident" with the agentic gate: an MCP server whose modelSet.length ≥ 1 is an agent and MUST register in KYM. The mcp-resident-agents companion paper details the surface. No silent LLM calls, no unregistered agents.
  • LangGraph subagents / deep-agents context quarantine (LangChain, 2025)
    Defines: A supervisor spawns child agents that run in isolated contexts and return only their final result — solves context bloat in long agentic runs.
    Our delta: composition.quarantine: boolean is our schema-level knob for the same idea; the compiler translates it into a fresh runtime instance on the target adapter (a new dynamic-worker isolate, a new HB3 job, a new local subprocess). Not a framework choice — a compilable field.
  • AutoGen (Microsoft, v0.4+) + GraphFlow
    Defines: Group-conversation coordination and, with GraphFlow, explicit DAGs of agents.
    Our delta: composition = { kind: "dag", nodes, edges } captures both patterns without picking a framework. edges.gate ∈ {"llm-decides", "deterministic", "conditional"} names WHERE the LLM sits in coordination — the axis AutoGen/LangGraph implicitly disagree on.
  • RecursiveMAS / RAH (arXiv, 2025)
    Defines: Recursive multi-agent architectures with latent-state links or code-execution spawning of child agents; +8.3% avg accuracy, 1.2-2.4× speedup, 34-75% token reduction reported on 9 benchmarks.
    Our delta: Confirms recursive composition is real value, not just conceptual. AgentDefinition.composition.nodes[] references other AgentDefinition@versions — recursion is by construction. Whether a specific runtime supports latent-state RAH-style shortcuts is a runtime capability, not a definition-level concern.
  • OASF (Open Agentic Schema, agntcy) + AGNTCY Agent Directory Service (arXiv, 2025)
    Defines: Cross-vendor "agent resume" (skills + costs) for marketplaces; ADS is a distributed directory.
    Our delta: Same problem space as A2A + NANDA; we treat OASF as another projection target (metadata.cardExports gains an oasf field when the ecosystem stabilises). AgentDefinition remains the internal source of truth.

Open questions

  • AgentDefinition JSON schema: does it live in @nexartis/cubicle-core (alongside CubicleSpec) or in a new @nexartis/agent-core package? Preference: cubicle-core, because the compiler consumes both simultaneously and drift between them would be worse than co-location.
  • Semver policy for AgentDefinition: are additive skill/tool additions minor, tightening of invariants major? mcp-resident-agents §deltas mandates versionSemver + versionId; the resolution rule for MINOR-vs-MAJOR on capability-contract changes needs a decision record before Wave 1 compilation.
  • Composition.edges.gate === "llm-decides" vs === "conditional": is a rule-based classifier (no LLM) still "conditional", or do we need a fourth "rule-based" value? Suggested rule: if the classifier is itself an LlmPin, it is "llm-decides"; otherwise "conditional". Confirm against ozzydev-search (its rule classifier is rank-0, deterministic).
  • Migration path: existing KYM agent cards were authored ad-hoc. Do we retrofit AgentDefinition retroactively (generate one per legacy card) or accept a bimodal registry for one wave? Retrofitting risks lossy conversion; bimodal risks compiler complexity. Bias toward retrofitting with an "origin": "legacy" marker.
  • Optimisation-loop rewards attribution across recursive composition: when a leaf agent inside a parent AgentDefinition earns a good receipt, does the parent's reputation also lift? The Nexartis position (2026-07-17) is yes but bounded — a decay factor per composition layer keeps opportunistic wrapping from farming reputation. Numeric factor is a Phase C experiment.
  • Cross-runtime portability contract: an AgentDefinition that names workers-preferred is expected to run on local-mac AND on dynamic-workers with identical behaviour on the shared subset. What is the shared subset formally? Draft answer: WinterTC min-common-api plus a Nexartis-defined portable subset of MCP transports; document as a companion to the JSON schema.
  • Non-LLM ML models in the modelSet: image classifiers, embedding models, ASR/TTS. Are they LlmPins with a widened tier enum, or a separate mlModelSet? Bias toward widening: they are "models involved in the execution path", the agentic gate should probably be renamed "model gate" and count ANY inference step. This is the largest open question; needs an ADR.
  • Failure semantics on definition-schema violations: a Cubicle spec that fails validateCubicleSpec raises path-level issues; AgentDefinition should follow the same pattern. But the compiler ALSO fails-closed on runtime-mismatch (e.g. local-host-required for a Cloudflare deploy). Both are §2 loud-classified errors; the classification family (agent_definition_invalid vs runtime_capability_mismatch) needs to land in the shared receipt error taxonomy.