Where did you hear about us?
1 of 6
스킬

스킬 - 라우터가 고릅니다.

AI는 Wos 하나에만 연결하고, 그 연결로 기억과 직접 더한 스킬에 닿습니다.

라우터

ask는 기억 밖의 요청을 라우터에 넘깁니다. 라우터는 저장소의 스킬 목록, 이 저장소와 최근 주고받은 것, 그 사람 시계의 지금 시각을 읽고, 스킬 하나를 골라 실행하거나, 스킬로 할 수 있는 것을 설명하거나, 못 한다고 말합니다. 스스로 답을 지어내지 않고 기억도 읽지 않습니다. 기억은 recall의 일입니다. 라우터와 스킬은 앱의 모델과 키로 돕니다. 키는 그 모델의 제공자에게만 가고 Wontopos로 오지 않으며, 모델을 정하지 않으면 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?;

설정

  • key 기본 모델 연결에 쓰는 Anthropic 키.
  • router_model · skill_model 라우터의 모델과 스킬의 모델.
  • handlers code 스킬의 함수, 스킬 이름별.
  • time_zone 그 사람의 시간대(IANA). 이것도 클라이언트의 tz도 없으면 ask가 거절됩니다.
  • limits 기본은 답 하나당 3번, 60초에 3번, 답 하나에 75초, 저장소당 하루 200번이고, 이 프로세스 안에서 셉니다.

묻기

ask는 실행된 뒤에는 예외를 내지 않습니다. 실패도 ok: false와 reason, 그리고 모델에게 줄 text로 옵니다. 모델에게는 text만(ask_for_model) 주고, 앱은 action(skill, explain, cannot), skill, input, reason을 봅니다. can_ask는 지금 ask가 돌 수 있는지(라우터 설정, 시간대, 저장소의 스킬)를 모델을 부르지 않고 알려 줍니다. 그때만 모델에게 ask 도구를 주고, 그 도구가 router_tools입니다.

스킬 더하기

스킬은 저장소마다, 앱의 프로세스 안에 있습니다. add_skill에 이름(소문자·숫자·밑줄), 라우터가 읽는 설명, 종류(llm: 스킬 모델이 지시문을 따름, code: handlers의 같은 이름 함수가 실행됨), 입력의 JSON Schema를 줍니다. confirm=True인 스킬은 사람이 동의한 뒤 run_skill로만 실행됩니다. 돈이 들거나 다른 사람에게 닿는 일에 씁니다. list_skills·remove_skill·copy_skill로 보고, 지우고, 다른 저장소에 복사하고, 다시 시작해도 남기려면 export_skills·import_skills를 씁니다. import_skill_md는 Agent Skills 형식의 SKILL.md에서 llm 스킬을 만듭니다. 그 글만 읽고, 폴더의 스크립트나 다른 파일은 실행하지도, 읽지도, 보내지도 않습니다.

모델 연결

anthropic_caller는 Anthropic Messages API로 보냅니다. openai_caller는 OpenAI의 chat completions 형식을 쓰는 서버라면 어디로든 보냅니다(OpenAI, DeepSeek, Qwen, Llama 호스트, Gemini의 호환 주소, 키가 있든 없든 로컬 서버). 직접 만든 함수도 됩니다. 잠도 같은 연결을 씁니다.