Repository navigation
feat: an operation names the tool it is offered as - #79
Merged
Merged
Conversation
A marked read was offered to a model under its operationId, which this runtime builds as OperationPrefix_Verb_Path. Wiki's first marked artifact (obin-ai/core-solutions#457) therefore published `Wiki_Get_V1_Wiki_Upload-configuration` as a tool name: it identifies the route and describes nothing, and every solution's tools would have read that way. Operation.Tool is the name instead. It is the one part of the marker the owner says rather than core derives, because it is the one part a model reads. Empty keeps today's behaviour exactly — the name falls back to the operationId — so no existing artifact changes until its owner names something. The name is validated, not sanitised: snake_case within 64 characters, refused at Operation.validate. Core's own toolName keeps whatever it is handed (it admits capitals and hyphens by design, being a sanitiser for method names), so a name typed with either would reach a model as typed, and this is the last place that can still say no. The operationId does not move. Generated clients are named from it, so making one tool read better would rename every caller's method. README and CHANGELOG carry the field, the fallback and why the operationId stays put. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Self-review of the field found two ways a declaration could be accepted and then mean nothing — both the kind of quiet wrong this marker exists to avoid. **Two operations could claim one tool name.** Verified by rendering one: both routes published `tool.name: "same_name"` and nothing refused. A tool name is the identity a MODEL addresses, so two of them cannot share one — and nothing downstream collapses them, because a derived package is keyed by the operationId and Robin's catalogue dedupes on the operation, the contract digest and the policy, never on the word. Refused now where a duplicate operationId already was, naming the tool and both routes. **A name on what is never marked was silently dropped.** `Tool` on a mutation validated fine, the marker came back nil, and the name went nowhere — while reading, to whoever wrote it, as an exposure. Only a read is marked, so only a read may be named: a mutation, a stream, an excluded route and an any-checked-caller route are each refused by name, and each says why it is never a tool. The same operation without a name stays valid, which the test pins: this refuses the name, not the operation. Also verified rather than assumed: core's own maxToolName is 64, the same bound this validates, and BoundedName returns a name of exactly 64 unchanged — so a name that passes here is the name a model sees, which was the whole point of validating instead of sanitising. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
antoinetoussaint-byte
enabled auto-merge
October 28, 2026 14:01
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.
Wiki shipped the first marked solution artifact yesterday (obin-ai/core-solutions#457) and published this as a tool name:
That is the operationId, which this runtime builds as
OperationPrefix_Verb_Path. It identifies the route and describes nothing — and since anOperationcould not name itself, every solution's tools would have read that way.The change
Operation.Toolis the model-facing name:It is the one part of the marker the owner says rather than core derives, because it is the one part a model reads. Everything else in
x-codefly-operationstays core's own derivation from the declared policy.Empty keeps today's behaviour exactly — the name falls back to the operationId — so no committed artifact changes until its owner names something.
Validated, not sanitised
Operation.validaterefuses a name that is not snake_case within 64 characters. That is deliberate: core'stoolNameis a sanitiser for method names and admits capitals and hyphens by design (runnable/derive.go:257keeps-and case), soRead-BoardorReadBoardwould reach a model as typed. This is the last place that can still say no, and an owner naming its own tool is told what a name is rather than having one silently rewritten.Refused:
Read_Board,read-board,9read,read board,read_board_,read__board, anything over 64 characters. The rule is^[a-z][a-z0-9]*(_[a-z0-9]+)*$.The operationId does not move
A nicer operationId would have fixed the tool name and the derived binding key in one go, and I deliberately did not do it: generated clients are named from the operationId, so improving one tool's name would rename every caller's method. The identity stays; only the model-facing word is the owner's to choose.
TestAnOperationNamesTheToolItIsOfferedAspins that naming a tool leavesReleases_Get_V1_Releases_Recordeduntouched.Validation
go build ./...,go vet ./...— clean.gofmtclean.go test ./... -count=1— ok, 16.7s, the whole suite.Operation.validateover six rejected forms plus the empty one.Note for reviewers: the test decodes the marker's
toolas a plain document rather than throughrunnablev0.ToolExposure, because the proto enum does not round-tripencoding/json— the first version of the test failed on exactly that, which is worth knowing before writing another assertion over a marker.Follow-up, not in this PR
Wiki's two reads still carry the fallback names until core-solutions sets
Toolon them; that is a one-line change per operation there, once this is released.🤖 Generated with Claude Code