wrap-file <файл>Обмазать файл целиком, прямо на месте. Рядом появляется помощник, который и пишет записи; больше ничего не добавляется.
Локально и без аккаунта. Аккаунта нет, регистрация не нужна: введённое в инструменты остаётся в localStorage браузера и на сервер не уходит.
Свой счётчик считает открытия страниц и дочитывания: уезжает адрес и десятая доля текста. Без cookies и чужих счётчиков, IP не хранится, Do Not Track уважается. Как это проверить
Репозиторий портала не выложен, «открытым кодом» мы его не зовём. Открыто это:
Живёт портал на донатах, платных консультациях и разборах по запросу и покупке Workbench.
Планов делать курсы платными нет.
Выкладка от 16 сентября 2026, срез 95043e5
Что изменилось на портале с вашего прошлого захода. Полный список выкладок — в разделе «Что изменилось» на странице «О нас».
Запись всех вызовов работающей программы: какие функции звались, с чем их позвали, что они вернули и что бросили.
from ouroboros_runtime import log as _ouro_log
@_ouro_log
def discount_rate(total, member):
if member:
return 0.15
if total >= 10000:
return 0.10
return 0.0
@_ouro_log
def apply_discount(total, member=False):
rate = discount_rate(total, member)
return round(total * (1 - rate), 2)
@_ouro_log
def main():
for total, member in [(9999, False), (10000, False), (500, True)]:
print(total, member, apply_discount(total, member=member))
apply_discount("free")
main()
9999 False 9999.0
10000 False 9000.0
500 True 425.0
Traceback (most recent call last):
File "/home/user/shop/discount.py", line 24, in <module>
main()
~~~~^^
File "/home/user/shop/ouroboros_runtime.py", line 234, in wrapper
result = fn(*args, **kwargs)
File "/home/user/shop/discount.py", line 21, in main
apply_discount("free")
~~~~~~~~~~~~~~^^^^^^^^
File "/home/user/shop/ouroboros_runtime.py", line 234, in wrapper
result = fn(*args, **kwargs)
File "/home/user/shop/discount.py", line 13, in apply_discount
rate = discount_rate(total, member)
File "/home/user/shop/ouroboros_runtime.py", line 234, in wrapper
result = fn(*args, **kwargs)
File "/home/user/shop/discount.py", line 6, in discount_rate
if total >= 10000:
^^^^^^^^^^^^^^
TypeError: '>=' not supported between instances of 'str' and 'int'
{"p":"in","t":"2026-09-10T19:51:47.402","id":"fb17367a-f596-4198-9565-add097cefa0e","ci":-1,"th":"2.136045302235648","fn":"discount_rate","a":"'free', False","k":""}
{"p":"out","id":"fb17367a-f596-4198-9565-add097cefa0e","fn":"discount_rate","x":"TypeError: '>=' not supported between instances of 'str' and 'int'","d":3e-06}
{
"ok": true,
"path": "debug.info",
"calls_parsed": 9,
"malformed": 0,
"matched": 1,
"returned": 1,
"next_cursor": null,
"in_flight": [],
"in_flight_truncated": false,
"records": [
{
"index": 6,
"started": "2026-09-10T19:51:47.402",
"call_id": "fb17367a-f596-4198-9565-add097cefa0e",
"name": "discount_rate",
"args": "'free', False",
"kwargs": "",
"outcome_kind": "raised",
"outcome": "TypeError: '>=' not supported between instances of 'str' and 'int'",
"duration": 3e-06,
"cpu": null,
"thread": "2.136045302235648"
}
]
}
{
"ok": true,
"path": "debug.info",
"calls_parsed": 9,
"malformed": 0,
"total_calls": 9,
"in_flight": [],
"by_function": [
{
"name": "apply_discount",
"count": 4,
"result": 3,
"raised": 1,
"unknown": 0,
"duration_seconds": {
"min": 7e-05,
"max": 0.000103,
"mean": 8.7e-05,
"total": 0.000347,
"count": 4
}
},
{
"name": "discount_rate",
"count": 4,
"result": 3,
"raised": 1,
"unknown": 0,
"duration_seconds": {
"min": 1e-06,
"max": 3e-06,
"mean": 2e-06,
"total": 7e-06,
"count": 4
}
},
{
"name": "main",
"count": 1,
"result": 0,
"raised": 1,
"unknown": 0,
"duration_seconds": {
"min": 0.000693,
"max": 0.000693,
"mean": 0.000693,
"total": 0.000693,
"count": 1
}
}
],
"by_thread": [
{
"thread": "2.136045302235648",
"count": 9,
"functions": 3,
"cpus": []
}
],
"duration_seconds": {
"min": 1e-06,
"max": 0.000693,
"mean": 0.000116,
"total": 0.001047,
"count": 9
},
"timespan": {
"first": "2026-09-10T19:51:47.402",
"last": "2026-09-10T19:51:47.402",
"seconds": 0.0,
"timestamps_parsed": 9,
"timestamps_unparsed": 0
},
"note": "counts/durations are over completed calls; `duration_seconds` are REAL per-call durations (exit−entry) from each call's `d`. `by_thread` groups calls by the `th` token (CPUs each thread ran on); empty for traces with no thread field. `in_flight` = entered (`p:in`) but never completed. `timespan` is first→last entry time."
}
Это настоящий вывод выпуска v0.6.1, снятый прогоном 2026-09-10, а не набранный руками текст. Знаки — из прогона; добавлены шрифт и подсветка, а длинные строки перенесены по ширине окна. Весь вывод целиком лежит ниже текстом.
Инструмент дописывает в ваш исходник строки, которые ничего не вычисляют, а
только записывают случившееся. Программа после этого запускается как обычно, и
каждый вызов оставляет две записи: одну при входе — имя функции и доводы, —
вторую при выходе: результат или исключение и длительность. Обе связаны общим
номером и копятся в файле debug.info, по объекту JSON в строке.
Восемь языков: Python, JavaScript/TypeScript, C, C++, Elixir, Go, Java, C#. Схема записи у всех одна; различается диалект, которым язык печатает доводы.
Читать записи можно двумя способами. Человеку — командная строка: отобрать вызовы по имени, исходу или длительности и свести весь прогон в счётчики. ИИ-агенту — сервер MCP: те же операции, отданные как инструменты.
Это не профилировщик — записи меняют время прогона. Не отладчик — программа не останавливается. И не покрытие: покрытие говорит, что строка исполнилась, а запись — с чем позвали и что вышло.
Нужен Python 3.12 или новее. Любой из трёх способов кладёт на PATH две команды — ouroboros и ouroboros-mcp.
brew install digitable-lol/tap/ouroboros
asdf plugin add ouroboros https://github.com/digitable-lol/ouroboros.git
uv tool install git+https://github.com/digitable-lol/ouroboros
wrap-file <файл>Обмазать файл целиком, прямо на месте. Рядом появляется помощник, который и пишет записи; больше ничего не добавляется.
wrap-functions <файл> <имена>Обмазать только названные функции. Так поступают, когда трасса всего файла длиннее исходника в сотни раз.
trace <debug.info>Отобрать вызовы: по имени, по исходу — вернул, бросил, не вернулся, — по длительности, по потоку.
trace-stats <debug.info>Свести прогон в счётчики: сколько вызовов у каждой функции, чем кончились, сколько заняли и какие не вернулись.
Команд 17; здесь четыре, с которых начинают. Те же операции отданы ИИ-агенту сервером MCP — инструментов 17, порядок работы в приветствии сервера. Выпуск 0.6.1: проверок 1257, покрытие строк и ветвей 100,00 %.
Курс из семи глав: установка, отказы, отбор вызовов, работа с агентом, восемь языков и границы