Skip to content

demo(mdcode): the commerce demo installs a generated skill, not an agent - #453

Merged
libei merged 4 commits into
GoogleCloudPlatform:mainfrom
libei:skills-demo
Sep 20, 2026
Merged

libei merged 4 commits into
GoogleCloudPlatform:mainfrom
libei:skills-demo

Conversation

@libei

@libei libei commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

The demo/semantic-model/agent/ demo was built around a 72-line ADK agent. PR
#451 made the agent the wrong payload: kcmd skills-generate turns a semantic
model into an Agent Skill, and a skill installs into
any harness that reads the format. So the demo's spine is now generate →
install → hand the harness a support request in English, and there is no agent
in the directory at all.

What changed

demo/semantic-model/agent/demo/semantic-model/skill/. agent.ts,
package.json and the @google/adk dependency are gone, along with every
reference that cited them as proof of a running agent. The model, the two
binding profiles and the two schemas are unchanged apart from the placeholder
database name.

The README is rewritten end to end. It walks the model and its four
word-stated rules, creates the store, shows what kcmd reads out of the
scope, generates the skill, installs it, and then runs four support requests
through a headless coding agent with nothing but the installed skill — no
order numbers, no table names, no SQL. One is refused at the self-service
ceiling, one lands, one lands carrying an advisory warning, and one is an
instruction to split a credit to dodge review. Section 9 diffs the skill
generated under spanner against the one generated under alloydb: the
reference page is byte-identical, and SKILL.md differs only in the
deployment section and the Spanner-only read path.

Every command line, every transcript and every diff in the guide is a live
run against a real Spanner database, not a plausible reconstruction.

Two harnesses, not one claim about a format

"A skill installs into any harness" was asserted from the layout. Section 8
now checks it: the same generated directory, copied unchanged into the path
Gemini CLI reads, with no adapter, no tool registration and no prompt beyond
the request. Gemini CLI discovered the skill on its own — activate_skill
first, then the reference page — wrote its own SELECTs from the schema
block, and walked a customer name and "Labor Day" down to one order. The
first request was refused at the $25 ceiling with nothing written; the second
committed an $8 credit and took order 12347 from $175.00 to $167.00.

The rules held in both harnesses because they are enforced in the runtime
under the action, not by whichever agent happens to be reading.

One generator fix

A generated skill offers writes, and a request names a person and a day where
an action wants a key — so SKILL.md tells an agent to read the store. It gave
it a gcloud line and nothing else. An agent that follows that has to invent
the table and column names, and one did: it wrote o.customerId, got
Name customerId not found inside o, and fell back to INFORMATION_SCHEMA.

So the read section now names the tables, bound through the same profile the
rest of the skill is generated from. The physical name is labelled column
and the model's name follows it for cross-reference, because the first
rendering paired them without saying which was which and was read backwards.
Field descriptions come along, since a coded column's description is what
carries item, tax, fee, or credit — and an agent that guesses those filters
on a value the column never holds and gets an empty answer back, which reads
like the record not existing.

This is Spanner-only, which the guide's Limits section states: under
alloydb the skill describes the action and its rules correctly and the
action still runs, but an agent handed a customer name has nothing telling it
how to reach a key.

What the guide says it does not show

The harness refused the split before running a single command, which is the
harness's own judgment and not the model's. The guide says so, and points at
the command-line case where the same rule refuses the same call in the
runtime with nothing written. Only that one is a guarantee.

Two judgments also misbehaved while this was being written: one returned an
answer that was not JSON, which fails closed; one invented an order total
when it had no read. Both are recorded.

Two harnesses were run, not every harness. The remaining rows in the install
table come from each tool's own documentation.

`kcmd skills-generate` derives an Agent Skill from a semantic model, so the
demo's hand-written ADK agent was the one part of it that had to be maintained
by hand and was true of exactly one framework. Delete it and make the demo
about the thing that replaces it: generate the skill, install it into a
harness, hand the harness a support request in English.

demo/semantic-model/agent/ becomes demo/semantic-model/skill/. agent.ts and its
package.json go, and with them the @google/adk dependency the demo installed on
the side. The model, the two binding profiles and the two schema files are
unchanged except for the placeholder database name, which follows the rename.

The README is rewritten around the new spine, and every listing in it comes
from a run today against the seed it publishes: the four command-line cases
that show what a judge with and without store access settles, what happens with
no judge, and the reject consequence; then four Claude Code runs against the
installed skill reaching escalate, applied, applied-with-warning, and a refusal
to break a rule it was told to break. A fifth outcome turned up by accident --
the judge returned non-JSON and the runtime refused rather than run unchecked --
and is transcribed as it happened.

It also states three limits it did not before: the AlloyDB profile generates no
read path, so an agent under it cannot find a key; kcmd takes its scope from the
working directory, so the skill's command line only works from the scope
directory; and a judgment that returns well-formed nonsense is not caught.

docs/semantic-model/actions.md loses its claim that the directory holds "an
agent built this way" with a 72-line agent.ts, and the agent_tools.ts header
loses its pointer to that file.
… read

"Finding a record" gave a Spanner-backed skill the gcloud line to read the
store and stopped there, so a harness given a customer's name and an action
wanting a key had a command and no schema. Driving Claude Code against the
commerce demo, it spent two turns querying INFORMATION_SCHEMA before it could
read a row.

Emit the tables under that snippet, reusing readableEntities() -- the same
listing the reading judge is given, so the two cannot drift.

Rendering it as `customer_id is Customer.customerId` was read backwards on the
next run: the agent wrote `o.customerId`, got INVALID_ARGUMENT: Name customerId
not found inside o, and fell back to INFORMATION_SCHEMA anyway. The rest of the
skill is written in the model's names and a statement has to contain the
store's, so a pairing that does not say which side goes in the SQL is worse
than ambiguous. The physical name is labelled `column` and the sentence above
the block says the quoted name is the one to write. With that, the next run
read two rows and issued the action with no schema discovery at all.

Field descriptions come along, because a coded column's description carries
what its values are -- `item, tax, fee, or credit` -- and an agent that guesses
filters on a value the column never holds and gets an empty answer back, which
reads like the record not existing.

The section is Spanner-only, as the snippet above it already was. A model bound
to AlloyDB still gets no read path; docs/semantic-model/skills.md now says so
under what it doesn't generate yet, and its cross-profile diff is refreshed
from a live run.
…output

Parameter projection changed both the demo model and what the generator
emits, so the README's quoted strings and its transcripts no longer matched
a run. Re-ran every command in the guide and replaced them with what came
back.

What moved:

  * The model excerpt now shows `{name: order, concept: Order, field:
    orderId}` and the `orderId` description it projects, and the prose says
    the parameter is projected rather than typed as an entity.

  * `kcmd action list` prints `order (Integer from Order.orderId)` and
    `--arg order=<Integer>`.

  * SKILL.md's "Finding a record" opens on what a wrong key costs rather
    than on an argument that accepted text, and the schema block carries
    `order_id`'s description. Both quoted here and in docs/skills.md.

  * All four harness transcripts are new runs. Three land where they did
    before; the fourth no longer attempts the honest credit after refusing
    to split, and the section now says plainly that refusing to structure
    around a control is the harness's own behaviour and only the
    command-line case in section 4 is a guarantee.

  * The judge-without-a-store case answered that it could not tell the
    order total rather than inventing one, as it had before. Both outcomes
    are recorded, because which one you get is not predictable.

  * The non-JSON judge answer did not recur; it is kept as a note about an
    earlier run rather than presented as part of this transcript.

Goldens pick up main's fixture change to `c_custkey`. `kcmd
skills-generate` is in main now, so the limit saying it is on a branch is
gone.
The guide claimed a skill installs into any harness on the strength of the
file format alone. Copy the same generated directory into the place Gemini
CLI looks and run two of section 7's requests through it: one refused at the
$25 ceiling, one committed a $8 credit against order 12347. No adapter, no
tool registration, no prompt beyond the request.

Record what was actually run rather than what the layout implies, and say in
Limits that two harnesses were exercised and the rest of the install table
is from each tool's documentation.
@libei
libei marked this pull request as ready for review September 20, 2026 22:12
@libei
libei merged commit 71033c8 into GoogleCloudPlatform:main Sep 20, 2026
7 checks passed
libei added a commit to libei/knowledge-catalog that referenced this pull request Sep 21, 2026
GoogleCloudPlatform#453 shipped this demo hours before the trim removed the flags it runs
on. Twelve command lines and nineteen judge flags in one README, and two
sections whose entire subject was a flag pair that no longer exists.

Section 4 is rewritten around what the command line actually does now,
with two live runs: a credit that commits under the NOT CHECKED banner,
and the same command asked for twenty dollars against an eighteen dollar
order -- the case CreditWithinOrderTotal exists to stop -- committing and
leaving the order at a total of negative five dollars. That is the cost
of the trim, shown rather than asserted, and it is a better argument for
where guards belong than the prose it replaces.

The guarded transcripts are kept and marked. They are the only record of
one action carrying four rules through all three on_violation outcomes,
and they are pasted as they were printed, two-word verb and all, because
a transcript nobody ever saw is not evidence. The agent runs in sections
7 through 9 get the same treatment: those calls were refused because the
generated command line carried a judge at the time, and the skill as it
generates today would commit them.

Three claims the demo made are now false and say so instead of quietly
standing: that the rules are enforced underneath whatever reads the
skill, that an agent ignoring the description cannot get past them, and
that the split is impossible because of the runtime rather than because
the harness happened to behave. Limits leads with the honest version --
no caller in this repository settles a guard today, because the CLI
declares them unchecked and the one application that hired a judge was
the ADK agent GoogleCloudPlatform#453 removed. The library is untouched and still tested;
what is missing is something wired to it.
libei added a commit that referenced this pull request Sep 21, 2026
…454)

`kcmd` is for curating a semantic model, not for dispatching its actions. The
action command lines had drifted into the second job.

Every flag that made `kcmd` behave like a runtime is gone -- `--judge`,
`--judge-location`, `--judge-reads-store` -- while `Judge`, `GeminiJudge`,
`modelJudgeStore` and `runAction`'s `judge` option are untouched and still
tested. Only the command lines stopped pretending to be the application that
hires them. What is left is two flags over four command-flag pairs, with no
combinations: `action-list` (`--profile`), `action-run` (`--arg`, `--profile`),
`agent-tools` (`--profile`).

`action <command>` and `agent <command>` become `action-list`, `action-run` and
`agent-tools`. cac cannot match a nested subcommand, so the space form was a
positional dispatcher that accepted either verb's flags and printed their union
under `--help`. `action-list` takes no positional at all; it lists everything,
and says so.

`action-list --store` becomes `kcmd profiles --print-store`. It was a mode flag
wearing a target flag's name -- it suppressed the listing entirely and only
printed, while nothing on any `kcmd` command line ever selects a store.
`profiles` gains `--profile [name]` so what `action-list --store --profile
alloydb` did survives, and gains a `reference.md` section it never had.

`action-run` binds the arguments, opens one transaction and applies the
statements. It settles no guard, and the outcome says so: `runAction` names
every guard a `skipGuards` run passed over in `ActionOutcome.warnings`, so the
command line, `describeOutcome` and whatever calls the runtime next all get it
without having to remember. An agent handed the action as a tool reads that
sentence in the result rather than a bare `applied: true`.

`agent-tools` derives with `skipGuards`, so a guarded action is offered rather
than marked `[NOT RUNNABLE]` -- who settles a rule belongs to whoever dispatches
the call, which a listing cannot know. A tool is still withheld for what no
judge would repair: no executor under the binding, an executor no handler runs,
or a guard naming a rule the model never declares.

The skills generator emits the flat verbs and no judge flag, and keys its
"this command line settles no guard" caveat on the model rather than on the
action it picked for the example.

Docs: `actions.md` §7, `reference.md` and `model_spec.md` stop saying this
repository settles a guard -- true only while the demo's command line carried
`--judge --judge-reads-store`. The skill demo's section 4 is rewritten against a
live store, where a $20.00 credit against an $18.00 order commits and leaves the
order at -$5.00 with `CreditWithinOrderTotal` sitting in the model. The
transcripts recorded while a judge was hired are kept and marked historical.

Between this and #453 no caller in the repository settles a guard. The judge,
the `on_violation` routing and the constraint library are all still there and
still tested; nothing is wired to them. That is the demo's top limit.

Replaces #448, which could not be updated in place: a rebased head cannot be
published over an existing PR without a force-push.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant