Paths into a value, and a handbook the tool can reach - #13
Merged
Conversation
Подстановка читала один уровень, рядом стояло «наблюдаемые случаи плоские».
Наблюдение было верным и перестало быть: ответ инструмента настолько
глубокий, насколько его сделал инструмент. На 16 живых генерациях описания
шага формат отверг шесть, и ПЯТЬ из шести — один случай: число на третьем
уровне ответа kubectl. Список допустимых полей в промпте (4.3 КБ) долю
отказов не сдвинул — не хватало формы, а не документации.
Теперь `var.a.b.c` и `var.items[0].name` — и в подстановке, и слева от
условия. Индекс пишется `[0]`, а не `.0`: так его пишут авторы, и так путь
остаётся однозначным (`.0` — это ещё и поле с именем «0», законный ключ
JSON).
Форма ссылки теперь описана ОДИН раз (RefPattern) и вшита во все регулярки,
которые её читают: подстановка, четыре формы условий, линтер. Раньше каждая
писала её сама, и написания уже разошлись — `{{a.b}}` умело один уровень,
условие принимало сколько угодно точек и точку в конце.
Промах пути — ошибка шага, а не пустая строка: `a.b.c` при отсутствующем `b`
неотличимо от «значение пустое», а по нему ветвятся. В отказе сказано, где
путь оборвался и какие поля у объекта есть на самом деле. Граница проведена
по тому, что старая грамматика умела выразить: голое `var` и одиночное
`var.field` сохраняют молчание — на нём написаны чужие скиллы, лежащие в
пользовательском хранилище, и апгрейд движка не имеет права их ронять.
Молчащую половину сторожит W14.
`[*]` отвергается отдельным сообщением, называющим цикл: автор не опечатался
в синтаксисе, он попросил язык запросов, а нужен ему for_each.
Побочно: lookup+objectOf заменены одним resolve, guard-тест резолвера
переведён на него; expand/payload/callArgs возвращают ошибку — путь умеет
не разрешиться.
1. Комментарий про valueOf утверждал, что подстановка в поле «никогда не работала» на результатах call. Неправда: фолбэк в память стоит в objectOf с первого коммита (afe751d), я перенёс фразу дословно, не проверив. Переписано на «почему обработка здесь нужна», без истории. Та же фраза была в комментарии теста — убрана и там; в репозитории копий не осталось. 2. exit больше не отменяется собственной подписью. Подпись — это caption, а exit — способ скилла сказать «не мой случай, верните ход обычным путём»; потребитель отличает выход от отказа специально (скилл, запущенный поимённо, на отказе ход останавливает, на выходе нет). Непроходящий путь в подписи превращал одно в другое молча. Теперь подстановка там best-effort: что не разрешилось, остаётся как написано, скобками наружу, где это видно читателю. Закрыто двумя тестами — раньше поведение не было закреплено вовсе. 3. set, switch и if при отказе оставляют трассу и слушают on_error. До появления путей эти три рода шага не умели отказывать вовсе, поэтому обе половины у них не были подключены, — при том что on_error лежит в inline Run, откуда его читают все остальные роды. Молча игнорировать разобранное поле — ровно тот класс «объявлено и не действует», который формат и вылавливает. Заодно Flow.Validate проверяет значение on_error у любого рода шага, а не только рядом с инструкцией: неизвестное значение теперь означало бы тихий abort.
Справочник был написан 3 августа и лежал в specs/, то есть в .gitignore:
его не было ни в репозитории, ни в модуле, ни в vendor/ у встраивающего. Он
существовал на одной машине, при том что его собственный README адресует его
в том числе инструменту, который пишет скиллы.
Теперь handbook/ с go:embed и доступом как у схемы:
skillengine.HandbookIndex() // ~1.4 КБ, кладётся в промпт целиком
skillengine.Handbook("flow-shape")
Разделами, а не целиком: 70 КБ в шаг не влезают и не должны. Проверено не
изнутри репы, а из отдельного модуля через replace — то же, что vendor/.
Почему указатель обязан вести туда, куда адресат может пойти: за три дня
сборки скиллов моделью одиннадцать коммитов подряд — один класс, форма,
которой в формате нет. Перечень полей в промпте не помогает (1 из 8 против
0 из 8, в пределах шума), проза не помогает («сделай отбор отдельным шагом»
— 0 из 16), а прежний указатель «позови schema-тул» на пути программы мёртв
дважды: тула нет в радиусе, а у шагов сборки tools: []. Поэтому раздел
теперь начинается с готовой ФОРМЫ, и её можно достать вызовом.
Правило линтера, покрытое разделом, несёт его id: Rule.Handbook и
Finding.Handbook. Полем, а не приклеенной фразой — отказ собирает
встраивающий: человеку «см. также», инструменту id, по которому он сходит.
Справочник не становится вторым источником правды: имена полей живут в
схеме, и тест отвергает раздел, назвавший поле, которого в схеме нет
(отличая поля формата от полей пользовательской response_schema по типу
справа).
Из содержания вычищен словарь чужой установки — протокол вызова, поля
телеметрии, кластеры, имена скиллов и стадии конвейера. За это уже
ретрачено пять версий, поэтому заведены два сторожа: публичный файл не имеет
права называть чужую установку (имена берутся из .githooks/private-names,
в самом тесте их нет — он публичный), а справочник не имеет права звучать
написанным внутри неё.
P2 переписан: «условие читает один уровень» стало ложью через сутки после
написания — пути и числовые сравнения уже в движке.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two embedder tasks, both measured on the same three days of a model writing
skills. Format 2.4.0.
1. A reference may be a PATH
Substitution used to stop at one field, and the comment beside it said the
observed cases were flat. They were, and then they were not: a tool's answer is
as deep as the tool made it. Of sixteen live generations of a step description,
six were refused by the format and five of the six were this one case — and
the same measurement with the list of allowed fields in the prompt (4.3 KB of
it) held the same share of refusals. The format was short of a form, not the
author short of the docs.
An index is
[0], not.0: that is what authors write, and it keeps a pathunambiguous —
.0would be the field named "0", a legal JSON key, and an indexat the same time. Still absent on purpose:
[*], filters, arithmetic. Acondition written with
[*]gets a refusal that names the loop instead.A path that does not resolve is an error, and the message says where the
walk broke and what the object did have. The line is drawn at what the old
grammar could express: a bare
varand a singlevar.fieldkeep their silence,because skills written under that promise live in other people's storage.
The shape of a reference now has ONE definition (
RefPattern), built intosubstitution, all four condition forms and the linter. The spellings had already
drifted apart.
Two behaviour changes came out of review:
exitreason no longer cancels the exit. The reason is acaption;
exitis how a skill hands the turn back, and a consumer tells thatapart from a failure on purpose;
set,switchandifnow leave a trace when they fail and obey the step'son_error— they could not fail at all before, so neither half was wired up,while
on_errorwas already being parsed for them.2. The handbook travels with the module
handbook/, embedded and reachable the way the schema is:It existed and could not be reached: it lived in the ignored spec tree, so it
was in no repository, no module and no
vendor/. Now it also cannot drift fromthe engine — which it already had, within a day: its rule "a condition reads one
level" was made false by the first half of this PR.
Why forms rather than prose: the hint "if you need a subset, make it a separate
step" worked 0 times out of 16, while an example gets copied. Every section now
opens with a piece to copy. Why reachable rather than merely written: the old
pointer said "call the schema tool", which no skill had in its radius and no
skill-writing step could call anyway (
tools: []).Each linter rule that covers a handbook class carries its section id
(
Rule.Handbook,Finding.Handbook) — a field, not a sentence glued to themessage, because whoever assembles the refusal decides what to do with it.
Two things the handbook is not, both guarded by tests: not a second source of
truth (a section naming a field the schema does not have is refused), and
not one installation's vocabulary — the tool names, telemetry fields,
clusters and skill names it arrived with are gone. Five published versions of
this library were retracted for exactly that class. The private names the guard
matches against are NOT in the test: they come from the list the git hooks
share, which is gitignored, and the test skips where it is absent.
The handbook is in Russian, like the failure reports it was written from; the
schema and both READMEs stay bilingual.