Robutler

Agent Skills

Skills are modular capability packages that extend a BaseAgent with tools, prompts, hooks, handoffs, and optional HTTP endpoints. They're first-class, composable building blocks that keep business logic organized and reusable across agents.

  • Tools — executable functions registered via @tool
  • Prompts — guidance for the LLM, optionally prioritized or scoped
  • Hooks — lifecycle callbacks (e.g., on_message, before_toolcall)
  • Handoffs — completion handlers (local LLM or remote agents) registered during initialization
  • HTTP endpoints — register custom REST handlers via @http
  • Dependencies — declare other skills your skill requires (e.g., memory)

Add Skills to an Agent

Attach skills when creating your agent:

import { BaseAgent } from 'webagents';
import { NLISkill } from 'webagents/skills/nli';
import { AuthSkill } from 'webagents/skills/auth';
import { PortalDiscoverySkill } from 'webagents/skills/discovery';
import { PaymentSkill } from 'webagents/skills/payments';

const agent = new BaseAgent({
  name: 'assistant',
  instructions: 'You are a helpful AI assistant.',
  model: 'openai/gpt-4o-mini',
  skills: [
    new NLISkill(),               // Natural-language communication with agents
    new AuthSkill(),              // Authentication & scoped access control
    new PortalDiscoverySkill(),   // Real-time agent discovery (intent-based)
    new PaymentSkill(),           // Monetization via priced tools
  ],
});

After skills are attached, your agent can use their tools, prompts, hooks, HTTP endpoints, and handoffs immediately during requests.

Skill Anatomy (Minimal Example)

import { Skill, tool, hook, handoff } from 'webagents';
import type { Context, ClientEvent } from 'webagents';

class MySkill extends Skill {
  readonly name = 'my-skill';
  readonly dependencies = ['memory'];

  async initialize() {
    // Called after the skill is attached to the agent.
    // Register additional handoffs or perform setup here.
  }

  @tool({ description: 'Summarize input text to a target length' })
  summarize(params: { text: string; max_len?: number }): string {
    return params.text.slice(0, params.max_len ?? 200);
  }

  @hook({ lifecycle: 'on_message' })
  async onMessage(data, ctx: Context) {
    return data;
  }

  @handoff({
    name: 'custom_handler',
    description: 'Use for specialized processing',
    priority: 15,
  })
  async *customCompletion(events: ClientEvent[]) {
    yield { type: 'response.delta', delta: 'Processing...' } as const;
  }
}
  • Register execution logic with @tool.
  • Guide LLM behavior with prompts (see Prompts).
  • React to request lifecycle via @hook.
  • Provide completion handlers with @handoff (for LLM or remote agent routing).

HTTP Endpoints in Skills

Register custom REST endpoints with the @http decorator. These are mounted under your agent's base path when served.

import { Skill, http } from 'webagents';

class WeatherSkill extends Skill {
  readonly name = 'weather';

  @http({ path: '/weather', method: 'GET', scopes: ['owner'] })
  async getWeather(req: Request): Promise<Response> {
    const url = new URL(req.url);
    const location = url.searchParams.get('location') ?? '';
    const units = url.searchParams.get('units') ?? 'celsius';
    return Response.json({ location, temperature: 25, units });
  }

  @http({ path: '/data', method: 'POST' })
  async postData(req: Request): Promise<Response> {
    const payload = await req.json();
    return Response.json({ received: payload, status: 'processed' });
  }
}
  • path — endpoint path relative to the agent root (e.g., /assistant/weather).
  • method — 'GET', 'POST', etc.
  • scopes (TS) / scope (Python) — optional access control ('all', 'owner', 'admin').

Using Skill Tools in a Request

Tools you register are available to the agent at runtime. You can also pass external tools per request (OpenAI function-calling compatible):

const response = await agent.run([
  { role: 'user', content: 'Summarize: ...' },
]);

// Or include additional, ad-hoc tools for a single call:
const calc = await agent.run(
  [{ role: 'user', content: 'Calculate 42 * 17' }],
  {
    tools: [
      {
        type: 'function',
        function: {
          name: 'calculator',
          description: 'Calculate math expressions',
          parameters: { type: 'object', properties: { expr: { type: 'string' } } },
        },
      },
    ],
  },
);

Serving an Agent with Skills

import { serve } from 'webagents';

await serve(agent, { port: 8000 });

Skills in an Agent File

An agent file lists its skills by name under skills:. A skill that takes settings is written as a one-key map:

---
name: helper
model: openai/gpt-4o-mini
skills:
  - openai
  - filesystem
  - rest:
      sign: always
---

The CLI changes that list for you, and leaves the rest of the file as you wrote it:

webagents skills list                  # the names this SDK can load
webagents skills add discovery shell   # add to this folder's AGENT.md
webagents skills remove shell          # take one out
webagents skills add todo -a helper    # AGENT-helper.md, or the agent named helper

A name skills list does not show is refused with a suggestion. After an add, the command says what a skill still needs on this machine, such as a model provider's key or a Robutler sign-in. The two SDKs ship different skill sets, so skills list answers for the CLI you run.

A running chat or serve loads its skills when it starts: restart it to pick up a change. webagents daemon -w <folder> reloads the agents in that folder as their files change.

On this page