Where did you hear about us?
1 of 6
Skills

Skills - the router picks one.

Your AI connects to Wos alone, and through that one connection it reaches its memory and any skill you add.

The router

ask hands a request outside memory to the router. The router reads the store's skills, its last exchanges with the store and the time on the person's clock, then runs one skill, says what the skills can do, or says it cannot. It never makes up an answer and never reads memory; memory is recall's job. The router and the skills run on your app's model and key. The key goes to that model's provider and never to Wontopos, and with no model named they use Claude Haiku 5.5.

from wontopos import Client

def weather(input, ctx):                                 # a code skill is your function
    return {"city": input["city"], "forecast": "sunny"}

mem = Client(api_key, model="scroll-2", user_id="jimin",
             router={"key": model_key, "time_zone": "Asia/Seoul",
                     "handlers": {"weather": weather}})
mem.add_skill({
    "name": "weather", "kind": "code",
    "description": "Tomorrow's weather for a city.",
    "input": {"type": "object", "properties": {"city": {"type": "string"}},
              "required": ["city"]},
})
r = mem.ask("tomorrow's weather in Busan", turn=message_id)
reply_for_model = r["text"]
import { Client } from "wontopos";

const mem = new Client({
  apiKey, model: "scroll-2", userId: "jimin",
  router: {
    key: modelKey, timeZone: "Asia/Seoul",
    handlers: { weather: (input) => ({ city: input.city, forecast: "sunny" }) },   // a code skill
  },
});
await mem.addSkill({
  name: "weather", kind: "code",
  description: "Tomorrow's weather for a city.",
  input: { type: "object", properties: { city: { type: "string" } }, required: ["city"] },
});
const r = await mem.ask("tomorrow's weather in Busan", { turn: messageId });
const replyForModel = r.text;
use std::collections::HashMap;
use serde_json::json;
use wontopos::{skill_handler, AskOpts, Client, RouterOptions, SkillDefinition, SkillKind};

let weather = skill_handler(|input, _ctx| async move {          // a code skill is your function
    Ok(json!({ "city": input["city"], "forecast": "sunny" }))
});
let mem = Client::new(&api_key).with_model("scroll-2").with_user("jimin")
    .with_router(RouterOptions {
        key: Some(model_key), time_zone: Some("Asia/Seoul".into()),
        handlers: HashMap::from([("weather".to_string(), weather)]),
        ..Default::default()
    })?;
mem.add_skill(SkillDefinition {
    name: "weather".into(), kind: SkillKind::Code,
    description: "Tomorrow's weather for a city.".into(),
    input: json!({ "type": "object", "properties": { "city": { "type": "string" } },
                   "required": ["city"] }),
    ..Default::default()
}, None).await?;
let r = mem.ask("tomorrow's weather in Busan", None, &AskOpts {
    turn: Some(message_id.clone()), ..Default::default()
}).await?;

Setting it up

  • key the Anthropic key for the default model connection.
  • router_model · skill_model the router's model and the skills' model.
  • handlers the functions of code skills, by skill name.
  • time_zone the person's time zone (IANA). Without it, or a client tz, ask is refused.
  • limits by default 3 asks per answer, 3 per 60 seconds, 75 seconds per answer and 200 per store a day, counted in this process.

Asking

Once it runs, ask never raises: a failure comes back as ok: false with a reason and a text for the model. Give the model the text (ask_for_model); your app reads action (skill, explain or cannot), skill, input and reason. can_ask says, with no model call, whether ask can run now: a router set up, a time zone known, a skill in the store. Offer the model the ask tool only then; router_tools is that tool.

Adding skills

Skills live per store, in your process. add_skill takes a name (lowercase letters, digits and underscores), the description the router reads, a kind (llm: the skill model follows its instructions; code: the function of that name in handlers runs) and a JSON Schema for its input. A skill with confirm=True runs only through run_skill, after a person agrees; use it for anything that costs money or reaches someone else. list_skills, remove_skill and copy_skill list, remove and copy them to other stores, and export_skills and import_skills keep them across a restart. import_skill_md makes an llm skill from an Agent Skills SKILL.md; it reads that text only, and runs, reads or sends no script or other file from the folder.

Connecting a model

anthropic_caller sends to Anthropic's Messages API. openai_caller sends to any server that speaks OpenAI's chat completions: OpenAI, DeepSeek, Qwen, Llama hosts, Gemini's compatible endpoint, and local servers with or without a key. A function of your own works too. Sleep uses the same connections.