Skip to main content

s-telemetrykit

SDK учёта вызовов навыков: фиксирует факт и результат каждого вызова супернавыка и кладёт событие в локальный outbox. Доставкой занимается CLI-воркер — кит в сеть не ходит вообще.

Ноль зависимостей, только stdlib. Это требование, а не текущее состояние: кит импортирует каждый супернавык (их 30+), поэтому цена его импорта — часть цены любого вызова. Для сравнения на одной машине: import telemetrykit — 68 мс, import librarykit — 750 мс (корень китов тянет httpx, cryptography, keyring, браузерный слой). Отсюда же собственный минимальный санитайзер секретов вместо librarykit.redaction — см. докстринг telemetrykit/sanitize.py.

Зачем

Событий skill.run / skill.invoke в системе не эмитится вообще — мы не знаем, пользуются навыками или нет. Просить LLM «отчитаться о вызове» ненадёжно: отчёт зависит от того, вспомнит ли модель про него. Факт вызова должен фиксировать КОД навыка.

Подключение

from telemetrykit import track_skill


@track_skill()                    # slug и версия определяются САМИ
def main() -> int:
    ...


# либо как контекст-менеджер
with track_skill():
    do_work()

Аргументы не нужны. track_skill("vk", "1.2.3") тоже работает, но это запасной путь: захардкоженная в 30+ навыках версия гарантированно разъедется с реальной (в этом проекте версия CLI уже жила в двух местах и дала бесконечную петлю самообновления).

Три обещания перед навыком:

  1. исключение проходит насквозь — кит его записывает, но не глотает: навык завершается своим кодом возврата;
  2. кит молчит — ни строки в stdout/stderr, ни настроенного logging;
  3. кит не падает — нет прав, диск полон, битый файл, сломанный резолв: любая внутренняя ошибка гасится и наружу не выходит.

Как определяются slug и версия

Каскад, первый сработавший побеждает (telemetrykit/detect.py):

# Источник Почему он
1 env SKILLERY_SKILL_ID / SKILLERY_SKILL_VERSION явная воля запускающего: тесты, headless, нестандартные раскладки. Половинчатый override допустим — задан только id, версия ищется дальше
2 _skill_meta.json_skill_meta.tomlSKILL.md (frontmatter) канон стора навыков: их пишет skillkit при установке, там лежат slug и version. Ищем вверх по дереву каталогов от файла навыка, максимум 10 уровней
3 importlib.metadata.version(dist) метаданные УСТАНОВЛЕННОГО дистрибутива, а не константа в коде: их проставляет сборка, разъехаться с колесом они не могут
4 имя top-level пакета + "unknown" неопределённость не должна ронять навык

Вызывающий модуль определяется по стеку, а не по cwd. В момент применения декоратора кит идёт вверх по кадрам (sys._getframe) до первого кадра, чей модуль не наш и не служебная обёртка (contextlib/functools), и берёт его __file__ и __name__. Рабочий каталог у агента произвольный и про навык не знает ничего, а sys.argv[0] — это интерпретатор или трамплин. inspect не используется намеренно: он дороже, чем весь остальной кит.

Запуск из исходников (навык не установлен). Обычно срабатывает шаг 2 — рядом с кодом лежит SKILL.md / _skill_meta.* репозитория навыка. Если и их нет, шаг 3 не найдёт дистрибутива, и мы честно отдадим <имя пакета> + "unknown": события всё равно попадут в хаб, просто без версии.

Контракт события (kind="skill_run")

{
  "event_id": "uuid4-hex",
  "skill_id": "vk",
  "skill_version": "1.2.3",
  "installation_id": "uuid4-hex",
  "session_id": "uuid4-hex",
  "timestamp": "2026-07-28T10:00:00.123456+00:00",
  "subcommand": "post create",
  "duration_ms": 42,
  "status": "OK",
  "error_details": null,
  "sys_info": {"os": "Windows", "arch": "AMD64", "python_version": "3.13.5"},
  "arg_names": ["--text", "--dry-run"]
}
  • statusOK | ERROR | INTERRUPTED. KeyboardInterrupt — отдельный статус (иначе прерванные запуски раздуют долю ERROR и спрячут настоящие поломки); SystemExit(0) — это OK, SystemExit(2)ERROR с кодом.
  • error_details (или null): {error_type, error_code, message_template, sanitized_stack_trace}. error_code берётся из атрибута исключения (code / error_code / exit_code / errno / status_code) — по коду, а не по тексту, строят алерты.
  • event_id — ключ дедупликации на бэке (доставка at-least-once).

Приватность

  • Значения аргументов не логируются никогда — только имена флагов (--phone, -v); у --name=Иван берётся левая часть. Подкомандой считается только ведущий позиционный токен, похожий на имя команды (строчная латиница, 2..32 символа) — это отсекает телефоны, пути, e-mail и имена собственные. Разбор останавливается на первом флаге: всё после флага — его значение.
  • Стек и сообщение проходят санитайзер: Bearer …, token=/password=/ api_key=, префиксные токены (ghp_, github_pat_, glpat-, xoxb-, sk-, AKIA), JWT, длинные hex, e-mail, user:pass@host. Абсолютные пути → ~/…, причём не только текущего пользователя (трейс может прийти из чужого venv или CI).
  • message_template — шаблон: длинные числа → <num>, содержимое кавычек → <str> (короткий идентификатор вроде KeyError: 'phone' сохраняется — он нужен для агрегации и значением не является).
  • installation_id — анонимный uuid4 в ~/.skillery/installation_id, не выводится из имени пользователя, hostname или MAC.

Outbox — общий транспорт (не «очередь телеметрии»)

telemetrykit/outbox.pyпубличная библиотека для любых исходящих сущностей: сегодня запуски навыков (skill_run) и логи CLI (log), завтра что-то ещё. Второй такой механизм заводить нельзя: разъехавшиеся очереди — это разъехавшиеся гарантии доставки.

Файл один — ~/.skillery/outbox.jsonl. Конверт:

{"id": "…", "kind": "skill_run", "ts": "…", "schema_version": 1, "payload": {}}

kind — обычная строка: новый тип не требует правки модуля, валидация payload лежит на продюсере, который один знает форму своих данных. schema_version — версия КОНВЕРТА, не payload'а.

from telemetrykit import outbox

ident = outbox.append("log", {"level": "ERROR", "message": "boom"})  # id или None
batch = outbox.read_batch(100)      # читаем, НЕ удаляя
...                                  # отправили
outbox.remove([e["id"] for e in batch])   # ack
outbox.path()                        # где лежит файл

Порядок «прочитал → отправил → подтвердил» даёт at-least-once: перезапуск между отправкой и ack приведёт к повтору, поэтому в конверте и есть id.

Параллельная запись

Пишущих процессов много (навыки запускаются одновременно), читающий один (CLI-воркер).

  • Запись — одна строка за один системный вызов. На POSIX достаточно O_APPEND (стандарт требует неделимости «сдвиг в конец + запись»). На Windows O_APPEND этого не даёт: CRT реализует его как «seek, потом write» двумя вызовами, и между ними вклинивается другой процесс. Замерено на живой машине: 6 процессов × 200 строк дали 1048 строк из 1200 — 13% событий пропали молча. Настоящий атомарный append в Windows — файл, открытый с правом FILE_APPEND_DATA (и без FILE_WRITE_DATA), тогда позицию двигает ядро; тот же замер с ним — 1200 из 1200. Биндинги через ctypes собираются лениво, при первой записи; если не сложилось — откат на обычный os.write.
  • Перезапись (ротация и ack) — под lock-файлом и через os.replace, а дозаписанный конкурентами хвост переносится в новый файл по смещению: событие, приехавшее во время ack, не теряется.

Ротация

Порог 5 МБ (SKILLERY_OUTBOX_MAX_BYTES). Сверху него самая старая половина строк выбрасывается, а на их месте остаётся конверт kind="outbox.rotated" со счётчиком dropped — потеря становится ВИДИМОЙ на бэке, а не молча случившейся. Ротирует ровно один процесс (lock-файл), остальные в этот момент просто дописывают.

Переменные окружения

Переменная Что делает
SKILLERY_TELEMETRY_DISABLED=1 выключает учёт вызовов навыков
SKILLERY_OUTBOX_DISABLED=1 выключает исходящий транспорт целиком
SKILLERY_OUTBOX_PATH полный путь к файлу outbox'а
SKILLERY_TELEMETRY_DIR каталог outbox'а (историческое имя)
SKILLERY_OUTBOX_MAX_BYTES порог ротации (по умолчанию 5 МБ)
SKILLERY_HOME каталог-дом (по умолчанию ~/.skillery; учитывается и SKILLERY_CONFIG_DIR, чтобы не появилось второго понятия «дома»)
SKILLERY_SKILL_ID / SKILLERY_SKILL_VERSION override идентичности навыка
SKILLERY_SESSION_ID общий id сессии для нескольких процессов

Выключателя два намеренно: пользователь вправе отказаться от статистики запусков, не отключая доставку остального (например логов, которые сам же попросил собрать для разбора инцидента).

Публичный API

from telemetrykit import track_skill        # главное
from telemetrykit import TelemetryEvent     # контракт события
from telemetrykit import outbox             # общий транспорт (нужен воркеру)
from telemetrykit import outbox_path        # алиас outbox.path

Остальное — ErrorDetails, SysInfo, SkillTracker, STATUS_*, KIND_SKILL_RUN, telemetry_disabled и под-модули args / detect / identity / sanitize / paths.

Разработка

uv venv && uv pip install pytest ruff
python -m pytest -q
python -m ruff check telemetrykit tests

Публикация на PyPI — по семвер-тегу vX.Y.Z через GitLab Trusted Publishing (OIDC, токены нигде не хранятся), см. .gitlab-ci.yml. Версии поднимаются только патчами от PyPI-latest.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

s_telemetrykit-0.1.3.tar.gz (44.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

s_telemetrykit-0.1.3-py3-none-any.whl (37.4 kB view details)

Uploaded Python 3

File details

Details for the file s_telemetrykit-0.1.3.tar.gz.

File metadata

  • Download URL: s_telemetrykit-0.1.3.tar.gz
  • Upload date:
  • Size: 44.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for s_telemetrykit-0.1.3.tar.gz
Algorithm Hash digest
SHA256 17bc7f43b06792a3a706ee07f27b2e6b4f328cc01967ff8e8a51b756a3b53542
MD5 763b6437b3e40ecdd6788b13d5fd4d7a
BLAKE2b-256 f57ef3d6e0e4a4a0fa10dc54e66d7f83d68c681ccc15d1b9655765b1c40b7746

See more details on using hashes here.

File details

Details for the file s_telemetrykit-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: s_telemetrykit-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 37.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for s_telemetrykit-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 42fee1dc7dea2cf8911f56dac39c05b53783e660c666d51f1455156b7a20278b
MD5 ddaf8481eba40dc807df04b8304daf2e
BLAKE2b-256 9b90cabd7681b765544143e1a835fb8d6c1882658587708fdeae63dbc1d9343a

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.0

2 files

This release

0.1.3 This release

2 files

0.1.2

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page