Repository navigation
generate runnables: a solution's REST routes become Runnables, and so tools, as a module's methods already do #1184
Description
What this is for
Every gRPC method a module serves behind the authority hop already becomes a
Runnable, and so a tool, with nothing hand-written: codefly generate runnables
("Derive … Runnable packages, and so tools, from a module's served methods …
Every Runnable is a tool"), from core/runnable/derive.go, which takes the
method's authority.hop.v1.policy and the message descriptors and derives the
tool's name (the snake_cased method), its description (the method's leading doc
comment), its effect and its schemas. generate runnable-bindings then prepares
one value per operation per environment, keyed MODULE__OPERATION, and Robin
opens a binding's own tool exposure as a chat tool (obin-ai/module-robin#520).
A solution's REST routes get none of this, although a solution declares
everything the derivation needs. The goal is the same sentence for both sides:
every gRPC method of a module and every REST route of a solution becomes a tool
by becoming a Runnable.
What already exists
- core can derive a Runnable from a REST operation:
runnable/operation_openapi.go→PackageFromOpenAPIOperation, marked with
the vendor extensionx-codefly-operation(runnable/openapi_marker.go),
whose value is "the proto3 JSON form of codefly.runnable.v0.Operation — the
same field names … so the marker and the method option are one schema read two
ways rather than two schemas that drift". - a solution already declares the inputs.
solution-runtime-go's
operations.go,type Operation:Path,Method, a requiredSummary, the
Request/Responsezero values the artifact's schemas are derived from, and a
requiredPolicy— which isauthority.hop.v1.Policywritten per route and
published asx-codefly-authority-policy. The same declaration the module
side derives everything from.
The three gaps
- No command calls core's REST derivation.
git grep PackageFromOpenAPIOperation/ParseOpenAPIDocument/OpenAPIOperationMarker
over this repo'smainreturns nothing.generate runnablestakes
[module]and reads only the gRPC catalog and FileDescriptorSet. - The artifact a solution publishes is the wrong dialect and lacks the
marker.solutions/proposals/contracts/api/backend/http/openapi.jsonon
obin-ai/core-solutions#451 is Swagger 2.0 (swagger,definitions,
consumes/produces), 16 paths, and its only per-operation extension is
x-codefly-authority-policy. Core's parser wants OpenAPI major 3 and
#/components/schemas/. - A read route cannot be an operation at all today.
openAPIOperationMethodsadmits only POST and PUT, because "an operation
applies an effect and is retried under a declared policy, which GET and
DELETE do not describe". But the module side derives read-only operations
happily — "a policy requiring only 'read' derives a read-only tool that is
safe to re-run and carries no receipt" — and a read is more retry-safe over
GET, not less. The asymmetry is what bites: in obin-ai/core-solutions'
releases solution the tool-worthy routes are exactly the GETs (/recorded,
/history), while its POSTs are the ones a tool must never call
(/record,/link/begin,/chat/install).
Proposed work, by owner
- codefly-dev/solution-runtime-go: emit the interface artifact as OpenAPI
3.1, and writex-codefly-operationbeside thex-codefly-authority-policy
it already writes, for every operation that declares aPolicy. The tool's
description is theSummarythe operation is already required to carry, and
the execution bounds are core's own defaults, so the runtime invents no
vocabulary. - codefly-dev/core: admit GET for an operation whose declared effect is
read-only and whose policy requires only reads, so a solution's reads can be
operations at all (gap 3). If that is refused, say so and solutions will have
to publish their reads as POST, which changes every page's fetch. - this repo: let
generate runnablestake a solution and write the same
contracts/runnablestree from its document, and letgenerate runnable-bindingsprepare them keyedSOLUTION__OPERATION. Same two
commands, no new concept. - obin-ai/module-robin and obin-ai/module-runtime: corrected 2026-10-26,
after feat(runnable): a REST read over GET is an operation, and Swagger 2.0 is read as itself core#852 was written. I first wrote that nothing in Robin
changes. That is true for a module's Connect operation and false for a
solution's REST route: Robin'srunnable_catalog.go:94skips every binding
whose call is not Connect,toolowner/operation.goalways POSTs a Connect
procedure, and module-runtime'spkg/runnable/transport.gobodiedMethods
admits only POST/PUT/PATCH/DELETE. So a solution's read is derivable and
preparable after the three PRs below, and still not callable until those
two owners admit a GET over arestcall. What stays true: the bundle side
needs nothing — tools are declared by reference ({name, contract: runnable, service}) and resolved from the composed binding at import.
Why it matters now
A solution's only tool surface today is MCP (solution-runtime-go/mcp.go,
ServeMCP), which Robin does not read: obin-ai/core-solutions' releases
solution serves read_board and component_history there, any MCP client the
host admits can call them, and its own Robin chat cannot. With this, the chat
reads the solution's own routes the same way it reads a module's methods.
Audited 2026-10-26 against cli main (e4de4a8cd), core main (89bf6c4a),
solution-runtime-go main (e53e044) and robin main (cf25e8bf).
Activity
feat(generate): a solution's routes become Runnables, and so tools, a…
feat(generate): a solution's routes become Runnables, and so tools, a…
antoinetoussaint-byte commentedon Oct 28, 2026
A lazybox agent is working on this.
claude(Opus 5.5) started at 2026-10-26 17:26 UTC, last renewed the claim at 2026-10-29 09:14 UTC, and the claim lapses at 2026-10-29 10:14 UTC unless it is renewed again. A claim is an assertion with an expiry, not proof that a process is alive.claim detail