Fathym
Menu

Capability Patterns

A Capability is an atomic tool with encapsulated context: it exposes a simple Input/Output contract to callers while hiding internal implementation - services, connections, state. Deterministic by default, composable everywhere.

Simple - data in, data out

The base case: an input schema, an output schema, and execution logic.

const FormatText = Capability('FormatText', 'Formats text in various styles')
  .Input(z.object({
    text: z.string().describe('Text to format'),
    style: z.enum(['upper', 'lower', 'title']).describe('Format style'),
  }))
  .Output(z.object({ formatted: z.string().describe('Formatted result') }))
  .Execute(async (ctx) => {
    const { text, style } = ctx.Input;
    const formatted = style === 'upper'
      ? text.toUpperCase()
      : style === 'lower'
      ? text.toLowerCase()
      : text.replace(/\b\w/g, (c) => c.toUpperCase());
    return { formatted };
  });

With services (IoC)

Declare dependencies with .Services(); they resolve from the IoC container at execution and are reachable as ctx.Services. Declaring services is an act of Integration - the Capability joins the dependency graph without hard-wiring how those services are constructed.

const UserLookup = Capability('UserLookup', 'Looks up a user, cached')
  .Input(z.object({ id: z.string() }))
  .Output(z.object({ name: z.string() }))
  .Services(async (ctx, ioc) => ({
    db: await ioc.Resolve<Database>(ioc.Symbol('Database')),
    cache: await ioc.Resolve<Cache>(ioc.Symbol('Cache')),
  }))
  .Execute(async (ctx) => {
    const cached = await ctx.Services.cache.get(ctx.Input.id);
    return cached ?? await ctx.Services.db.findUser(ctx.Input.id);
  });

With self-governance

Attach a guardrail with .Governance() and it travels with the Capability everywhere - it runs at the Capability level, independently of whatever hosts it (the no-aggregation rule).

const SecretRead = Capability('SecretRead', 'Reads a secret')
  .Input(z.object({ key: z.string() }))
  .Output(z.object({ value: z.string() }))
  .Governance(TrustLevelPolicy('high')) // only high-trust initiators
  .Execute(async (ctx) => ({ value: await vault.get(ctx.Input.key) }));

Output hooks and lifecycle handles

Not every Capability maps input to a value. Some open a resource and return a handle of typed methods - the caller interacts with what's inside without knowing the implementation; the lifecycle stays in the Capability.

const Transaction = Capability('Transaction', 'Managed DB transaction')
  .Input(z.object({ poolKey: z.string() }))
  .Output(z.custom<TransactionHandle>())
  .Execute(async (ctx) => {
    const client = await getPool(ctx.Input.poolKey).connect();
    await client.query('BEGIN');
    return {
      Query: (sql: string, p: unknown[]) => client.query(sql, p),
      Commit: async () => {
        await client.query('COMMIT');
        client.release();
      },
      Rollback: async () => {
        await client.query('ROLLBACK');
        client.release();
      },
    };
  });

The same shape powers long-lived resources - an embedded server with Start/Stop/Send, a deployment with Scale/Pause/Resume/Destroy. The handle IS the contract.

Composition - Capabilities using Capabilities

A Capability can compose other Capabilities (and wrap them with cross-cutting behavior like monitoring) without the inner ones knowing. Each stays whole.

With agents

A Capability can compose Agents via .Agents({}) and reach them through ctx.Agents. Always close agent handles when done.

const TextProcessing = Capability('TextProcessing', 'Extract + summarize')
  .Agents({ Extractor: EntityExtractorAgent, Summarizer: TextSummarizerAgent })
  .Input(z.object({ text: z.string() }))
  .Output(z.object({ entities: z.array(z.string()), summary: z.string() }))
  .Execute(async (ctx) => {
    const ents = await ctx.Agents!.Extractor({ text: ctx.Input.text });
    const sum = await ctx.Agents!.Summarizer({ text: ctx.Input.text });
    const entities = await ents.SendMessage('list entities');
    const summary = await sum.SendMessage('summarize');
    await ents.Close();
    await sum.Close();
    return { entities, summary };
  });

Every Capability is reusable, self-documenting (its schemas + .describe() are read by callers and AI alike), and self-governing where it needs to be.


On this page