Add next-steps graphs: dynamic context after every appstore call#89
Merged
Conversation
added 4 commits
July 15, 2026 14:19
Agents install apps and stop caring, because nothing in a call's output says
where the flow goes. The product demo covers install->first-call, once. A
next-steps graph covers every call after it: each call ends by naming the small
set of recommended next commands for where the agent now stands.
Authored in submission.json beside product_demo, carried verbatim into the
catalogue's metadata.json, cached by pilotctl at install. Recommended, not
exhaustive: primitive exposes 111 methods and its graph names four.
- internal/nextsteps: the frozen contract (Graph/Edge/Step), Resolve, Validate,
and renderers. A flat edge list, not a node tree: the graph is implicit in
from->then, which keeps authoring, diffing and testing to one list.
- publish/scaffold wiring, mirroring product_demo's four touchpoints.
- CI gates: a graph can never recommend a method the app does not expose; a
named gateway must be reachable from a wildcard edge; cross-app steps resolve.
- 19 submissions backfilled (91 edges), every graph derived from live output
and every authored command actually run.
- docs/NEXT-STEPS-GRAPHS.md; publish-rich-from-r2.sh carries next_steps into
metadata, so a content fix ships as a metadata republish with no app rebuild.
Two things the ground truth changed, both found by running the real system:
Match is over the outcome PAYLOAD, not the outcome. The most important case is
not an error: the scaffold's requireKey soft-fails an unauthenticated call with
exit 0 and {"needs_signup":true}, and the CLI adapter likewise wraps a non-zero
tool exit into a successful call with {"exit":1,"stderr":...}. Server-not-started
for postgres/mysql/redis is exit 0. An outcome-only matcher would have been
silently inert on nearly every app.
A discriminated edge beats a bare exact-from edge. Scoring originally ranked
from-exact above a match, so for any wrapped method that also had a flow edge,
the flow edge shadowed the gateway: a cold agent calling primitive.send_email
was told to read its inbox instead of to sign up. Regression-tested.
Also fixes io.pilot.aegis, whose cli.args repeated the binary name as argv[0]
("aegis aegis scan") and left 6 of 7 methods returning a usage banner.
The graph is authored in submission.json but consumed from the catalogue's metadata.json — different structs, different packages, joined by ToConfig + BuildMetadata. A field that validates and then silently fails to travel would be invisible to every other gate and would ship as 'the feature does nothing'.
It was labelled 'recovery' only to satisfy the old gateway-reachability gate, which accepted a wildcard route to a gateway only on an err edge. Miren never errors — a no-cluster host returns exit 0 with the message in the payload — so the honest kind was unreachable. The gate now accepts a wildcard edge of either outcome, so the label can match reality.
The argv fix (dropping the duplicated binary name) ships on its own branch off main, so it is not held hostage to this feature and this PR stays purely next-steps. The aegis graph here deliberately routes via aegis.exec, which is correct for the app as it is published TODAY.
Alexgodoroja
force-pushed
the
feat/next-steps-graph
branch
from
July 15, 2026 21:38
4c6b186 to
8a32846
Compare
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.
What & why
Autonomous agents install an app and stop caring, because nothing in a call's output tells them where the flow goes. Product demos fixed that at install time — one banner, once. This fixes it at call time, continuously: every
pilotctl appstore callnow ends by naming the small set of recommended next commands for where the agent stands — the next step in the flow on success, and the specific fix on failure.Authored in
submission.jsonbesideproduct_demo, carried verbatim into the catalogue'smetadata.json, cached by pilotctl at install. The client half is pilot-protocol/pilotprotocol#396.What's here
internal/nextsteps— the frozen contract:Graph/Edge/Step,Resolve,Validate, renderers. A flat edge list, not a node tree: the graph is implicit infrom → then, which keeps authoring, diffing and testing to one list, and avoids a second thing to keep in sync withexposes.next_stepsflows submission → config →metadata.json, mirroringproduct_demo's four touchpoints. Additive and optional — older clients ignore it.ToConfig+BuildMetadata.<ns>.help+ real execution.docs/NEXT-STEPS-GRAPHS.md;publish-rich-from-r2.shcarriesnext_stepsinto metadata, so a content fix is a metadata republish with no app rebuild.Two things the ground truth changed
Both were found by running all 19 apps, and both invalidate the obvious design.
1.
matchis over the outcome PAYLOAD, not the outcome. The most important case is not an error: the scaffold'srequireKeysoft-fails an unauthenticated call with exit 0 and{"needs_signup":true}. The CLI adapter likewise wraps a non-zero tool exit into a successful call with{"exit":1,"stderr":...}— server-not-started for postgres/mysql/redis is exit 0, an aegis block is exit 0, ideon-free'sunknown jobIdis exit 0. An outcome-only matcher would have been silently inert on nearly every app. Somatchtests the error text on failure and the result body on success.2. A discriminated edge beats a bare exact-
fromedge. Scoring originally rankedfrom-exact above a match, so for any wrapped method that also had a flow edge, the flow edge shadowed the gateway: a cold agent callingprimitive.send_emailwas told to read its inbox instead of to sign up — the exact failure this feature exists to prevent, and invisible to CI because both edges are individually valid. The rule is now: an edge that matched the actual situation beats one that merely matched the method name. Regression-tested.Recommended, not exhaustive
A graph is not
<ns>.help.io.pilot.primitiveexposes 111 methods; its graph names four. Utility methods that advance the flow only — no getters, no listers. ≤3 steps per edge.Deliberately not here
io.pilot.sixtyfour— externally owned, same call PR Add product demos: example-driven, skill-file usage guides #88 made (19/20 coverage).aegis.exec, which is correct for the app as published today.Verification
go test ./internal/...— 11/11 packages green;gofmt/go vetclean.Known limitation
pilotctl caches the graph at install and refreshes on
upgrade(which re-runsinstall --force), butoutdatedcompares app versions — a graph-only fix doesn't bump one, so existing installs won't see it until the app's next version bump. Explicit refresh today ispilotctl appstore install <id> --force. Documented; the fix is to teachoutdatedto comparemetadata_sha256, deliberately left out of this cut.