TS · 03

Axon - agent-side tool

The Axon wraps a neuronFn with the metadata and validation needed to put it on the bus. It never touches the Synapse; attach it to a Dendrite to participate.

classAxon

Turns raw Neuron output into protocol-valid signals: a normal return becomes AGENT_OUTPUT, a clarify() return becomes CLARIFICATION, a permissionRequest() return becomes PERMISSION, and a thrown error becomes ERROR.

axon.ts
interface AxonOptions {
  neuronId:        string;
  neuronFn:        NeuronFn;
  capabilities?:   string[];
  version?:        string;
  neuronKind?:     string;          // REGISTER directed.type; default "neuron"
  contextFetcher?: ContextFetcher;
  outputParser?:   OutputParser;    // recognise CLARIFICATION / PERMISSION / ERROR
  engrams?:        EngramBinding[]; // memory the Neuron may address by name
}

class Axon {
  readonly neuronId: string;
  readonly capabilities: string[];
  readonly version: string | undefined;

  constructor(opts: AxonOptions);

  // Source-paired factories  -  create the Neuron and the Axon in one call:
  static openai(neuronId, opts, extra?): Axon;
  static anthropic(neuronId, opts, extra?): Axon;
  static ollama(neuronId, opts, extra?): Axon;
  static huggingface(neuronId, opts, extra?): Axon;
  static mcp(neuronId, opts, extra?): Axon;

  // Called by the Dendrite for each inbound TASK. Resolves
  // contextRef, runs neuronFn, returns AGENT_OUTPUT / CLARIFICATION / PERMISSION / ERROR.
  handleTask(task: Signal): Promise<Signal>;

  // Pre-task hook  -  transform / validate / reject the TASK input.
  beforeTask(fn: (input: Json) => unknown | Promise<unknown>);

  // Detectors over the Neuron's RAW output  -  named detects* to stay
  // distinct from the Dendrite's on* (which consume inbound Signals).
  // Return the intent's fields to match, null to fall through.
  // Precedence: error -> clarification -> permission -> output.
  detectsOutput(fn: Recogniser): Recogniser;
  detectsClarification(fn: Recogniser): Recogniser;
  detectsPermission(fn: Recogniser): Recogniser;
  detectsError(fn: Recogniser): Recogniser;

  // Inherited from LifecycleHooks:
  onConnect(fn);                  // after the hosting Dendrite emits REGISTER
  onRefresh(fn);                  // each heartbeat tick
  onSchedule(everyMs, fn);        // periodic background task
}

// A Neuron is a plain function  -  sync or async. The optional third argument
// carries the Engram helpers (recall / imprint) when engrams are bound:
type NeuronFn = (input: Json, context: unknown[], helpers?: NeuronHelpers)
  => Promise<Json> | Json;

Constructor options

OptionTypeDescription
neuronIdstringThe address other processes use to reach this Neuron. Unique within a namespace.
neuronFnNeuronFnThe Neuron itself: (input, context) => Json | Promise<Json>. May return clarify(...).
capabilities?string[]Tags advertised in REGISTER for routing. Defaults to [].
version?stringOptional version string surfaced in REGISTER. Defaults to undefined.
contextFetcher?ContextFetcherResolver for payload.context_ref. Defaults to a no-op returning [].

Source-paired factories

The second way to build an Axon. The static factories - Axon.ollama(), Axon.huggingface(), Axon.openai(), Axon.anthropic(), Axon.mcp() - create the provider-backed Neuron and the Axon in one call, mirroring Python’s classmethods. The signature is Axon.source(neuronId, sourceOpts, axonExtra?): source options go in the second argument, Axon options (capabilities, version, engrams, ...) in the third.

factories.ts
import { Axon } from "@cosmonapse/sdk";

// One call: Neuron factory + Axon wiring + recogniser. The third argument
// (AxonExtra) carries the Axon-side options: capabilities, version,
// neuronKind, contextFetcher, outputParser, engrams.
const chat = Axon.huggingface("llama",
  { endpoint: "https://router.huggingface.co",
    model: "meta-llama/Llama-3.1-8B-Instruct",
    apiKey: process.env.HF_TOKEN, useChatApi: true },
  { capabilities: ["chat"] });

const cloud = Axon.openai("gpt", { model: "gpt-4o" });        // apiKey falls back to OPENAI_API_KEY
const local = Axon.ollama("chat", { model: "llama3" });
const claude = Axon.anthropic("claude", { model: "claude-sonnet-4-6" });
const files = Axon.mcp("files", { server: "filesystem", args: ["/data"] });

// Each factory wires the source family's recogniser (parseLlmIntents /
// parseMcpIntents), so the model can signal CLARIFICATION / PERMISSION /
// ERROR via a {"cosmo": ...} intent block  -  pass an explicit
// outputParser (or none) in AxonExtra to override.

Example

answerer.ts
import { Axon, clarify } from "@cosmonapse/sdk";

const axon = new Axon({
  neuronId: "answerer",
  neuronFn: async (input, context) => {
    if (!input.q) return clarify("What should I answer?");
    return { answer: String(input.q).toUpperCase() };
  },
  capabilities: ["text", "qa"],
  version: "1.2.0",
});

Have a feature in mind?

The protocol, SDKs, and CLI are still pre-1.0. If something here is missing, ambiguous, or wrong - open an issue and propose a change. Every breaking change is debated in DECISIONS.md first.