docs(mdcode): rewrite the actions guide for a human reader - #427
Merged
Merged
Conversation
The guide was clean against the house-style checklist and still read as machine-written. Rewrite the prose against the voice of the BigQuery measures GA post: contractions, the product as the grammatical subject, a problem-led opening, and no epigram closing every paragraph. - Open on what the reader cannot do today, then name the feature, the way the blog spends its first paragraphs on the two-stack problem before naming measures. - Drop the personified "we" for the CLI (51 occurrences to 0). kcmd, push and the judge are now the subjects of their own sentences; "we" no longer narrates the tool's own behaviour. - Use contractions throughout, as the blog does. - Cut the "X rather than Y" antithesis from 30 prose occurrences to 4, varying with "not", "instead of", or a restructured clause. - Add one orienting line naming the running example. Every fenced block is byte-identical to the previous revision: all 564 lines of YAML, SQL, diagrams and captured CLI output are unchanged, so no listing drifts from what the commands actually print. Every heading is unchanged, so the five inbound anchors from reference.md, model_spec.md and the agent demo README still resolve. No technical claim, status note or stated limitation was altered.
…tion
Four statements were wrong against the code a reader would act on:
- "nothing consumes `affects` yet" — `generatedKeys` in run_action.ts walks
`action.affects` and mints a UUID per `create` record, and
`unusableGeneratedKey` refuses the call up front. The guide contradicted
itself twice elsewhere (section 1's `@newTransferKey`, the withheld write
tool).
- `runtime.store.kind` was given as two-valued. `Store` is
`SpannerStore|AlloyDbStore|BigQueryStore`, and `dataClientFor` serves both
spanner and alloydb.
- Section 7 gave the run target as Spanner only; the deployment target may be
a Spanner or an AlloyDB database.
- The identifying-text-field list omitted `fullname` and was stated
case-sensitively; `identifyingColumn` tests
/^(name|full_name|fullname|title|label|display_name)$/i and takes the first
qualifying field in declaration order.
Also states what the guide had left unsaid: the strictest-outcome rule is not
what a run computes, since `askJudges` stops at the first non-advisory guard
that fails in declaration order and discards warnings collected before it; the
judge's four-read budget and what happens when it runs out; and that the
judge's SELECT/WITH wrap bounds neither a writing function under PostgreSQL nor
which tables a statement may name.
Prose changes cut five closing aphorisms, three restatements of the opening
thesis, the page's only blockquote and only Markdown table, and a rebuttal of a
design nobody proposed. Order 12345's total is corrected to its seeded $165.85.
Every fenced block stays byte-identical, every heading is unchanged, and the
five inbound anchors from reference.md, model_spec.md and the demo README still
resolve.
Five passages explained a point by contrast, analogy or abstraction before the
reader had the thing itself.
The worst was a claim that a push "behaves the way it already does for your
metrics", which compared to a feature the reader may not know, four sections
before `default_profile` is introduced. The behaviour it described does not
happen: `actionAspectData` in kc_actions.ts emits an aspect for every action,
and one with no executor publishes with the executor fields absent, which the
recovery path calls a legal published state. Nothing is pruned by binding. The
paragraph also restated the delete-reconciliation rule that section 5 already
states more precisely. Removed from section 1, with the true push behaviour
written into section 5 where the rest of the catalog-entry material lives.
- "What an entity-typed parameter adds" opened by arguing with hand-written
tool schemas. Retitled to name the concept, and it now says what one is
before what it buys.
- "Every entity and field therefore carries two names" restated in the
abstract what the paragraph above had just shown as `Account`/`account`.
Cut.
- "Statements use your database names" led with a discrepancy the reader had
to go back up to the listing to resolve. Reordered to rule, example,
failure mode, and the verbatim-carry rationale is dropped here because the
next passage argues it at length.
- "On its own that's a catalogued rule and no more" said one thing in three
sentences, two of them negative, and "acquires effect" appeared twice
within twenty lines.
Also sweeps the class rather than the instances: fourteen `X, not Y` antithesis
appositions become `rather than` or plain sentences, four cleft constructions
are unwound, and the strictest-outcome sentence is split from sixty words into
three. Prose is reflowed to eighty columns.
Every fenced block stays byte-identical, the five inbound anchors still
resolve, and the one changed heading carries none.
Continues the directness pass through the first two sections, hunting the same
class the previous commit named.
One passage said two things that read as a contradiction. "A judgment has to
state `on_violation`" was followed by "an unmarked constraint rejects", which
implies omitting the key is legal and has a default. It isn't: validate.ts:520
makes omission a push error, and "an unmarked constraint rejects" is the reason
the key is required, quoted from the error text. Rewritten to say the push
fails and give that reason as a reason.
The rest are structural:
- "Rule 3 is named like the rest, because an unnamed rule does nothing"
opened by answering an objection nobody raised. The bold lead now states
what rule 3 does and why.
- "The listing below puts the two things your model has to settle beside each
rule" announced a count whose members arrived only inside the table. It now
names them.
- Three colon splices become sentences, in the opaque-executor paragraph, the
generated-key paragraph, and the second-call walkthrough.
- Two "It's ..." clefts in consecutive sentences about outcome precedence
become one plain causal sentence and one plain subject.
- "Notice what stays computable alongside them:" directed the reader instead
of telling them something.
Also repairs three ragged wraps the previous commit left behind, where a
replacement was rewrapped at its first line and pushed the overflow down.
Fenced blocks remain byte-identical, headings and inbound anchors unchanged,
and no prose line exceeds eighty columns.
Carries the same hunt into the second half of the guide: passages that circle
a point — by analogy, by contrast, by rebuttal, by abstraction — instead of
stating it. Thirty-two passages, each one verified against the source before
the text moved.
The structural ones:
- Section 3 opened on "An executor is opaque", three sentences about what a
reader cannot see, and then said "declare the blast radius" twice. It now
opens on what `affects` names. The scope narrowed too, since a `sql`
executor carries the write and is not opaque.
- "`concept` names an entity or a relationship... Asking you to repeat it
would add a second place to get the answer wrong" argued with a design
nobody proposed. It states what `concept` takes.
- "An agent needs two things... your entities already say what can be looked
at and your actions already say what can be done" ran three clefts in two
sentences. Entities supply the first, actions the second.
- "A store is typed by its backend:" restated the field and its three values
that follow it. The field leads.
- "Drift costs you something in both directions" announced the shape of the
two sentences after it without adding a fact.
- Four more colon splices, five wh-clefts, and six sentences over forty words
in the judge, runtime and agent-tool sections.
Two say something different than before, because the original was wrong about
its own subject. "The gap is loud... and it's still a gap" conceded its own
concession; a refusal being easy to spot and the rule still going unchecked are
two facts, so it now states both. And "The version that would hold regardless
counts the credits already on the order" described in the present tense a
version of the rule that does not exist.
Fenced blocks remain byte-identical to main, the inbound anchors from
reference.md, model_spec.md and the demo README all resolve, and no prose line
exceeds eighty columns.
libei
marked this pull request as ready for review
September 15, 2026 17:15
libei
added a commit
to libei/knowledge-catalog
that referenced
this pull request
Sep 15, 2026
Five are claims about kcmd that the code contradicts. "kcmd can run it rather than publishing it for another system to dispatch" made carrying the write an alternative to publishing. `kc_actions.ts:225-226` copies `sqlStatements` onto the published action verbatim, so a `sql` action is published like any other; what changes is who performs the write. The sentence now says that, and names the `TransferFunds` it is about to redeclare. "kcmd settles every guard on a `sql` action before it opens a transaction" overclaims twice. A guard it cannot settle is refused rather than settled (`run_action.ts:530-535` for an expression), and a `warn` guard left unsettled produces a warning rather than a refusal (`run_action.ts:199-204`). The bullet now claims only the gate property the runtime has: the guards settle before the transaction opens (`run_action.ts:180-204`, ahead of `withSession` at 212-222), and a call that does not clear them never reaches one. "Because `IssueCredit`'s other three guards are expressions, kcmd refuses it outright" names a cause that is never reached. `IssueCredit` has an `mcp` executor, so `whyRefusedWithoutRunning` stops at the executor-kind check (`run_action.ts:429`) before it ever looks at the guards. The sentence now states the outcome without attributing it to the wrong check. "Pass every value as a bound `@parameter` naming a parameter your action declares" omits `@new<Concept>Key`, which `validate.ts:282-286` seeds into `bindable` for every `create` in `affects`. Stated as written, the rule forbids the guide's own create example. The same omission was in `ir.ts`'s `SqlExecutor` comment and is fixed there too. `ir.ts` also still carried the "gate sees the write" bullet that GoogleCloudPlatform#427 removed from the guide: "the statements run where the constraints are probed, so the gate observes the uncommitted result of the write it is gating". Guards settle before the transaction exists, so nothing observes that. The bullet is gone and the count above it drops from three to two. Five are prose defects the restructure introduced or left behind. "Section 1 gave you an action that runs" was true before the DML material became its own section and now skips it. "Four things about an action can be statically wrong" reads as four things about the particular action on the page. "All of these checks are static" reaches back past an intervening list. "Expect no control flow" tells the reader to hold an expectation when the point is a property of `statements` itself. And moving the no-post-write paragraph left "however much it can read" pointing at the store-reading flag twenty lines ahead of it. Five findings were checked and rejected. `warn` guards are evaluated and reported (`run_action.ts:580`, 608-609, 622-623) rather than skipped, so the guide's existing wording holds. Figure 2's lead-in was deleted in GoogleCloudPlatform#427 on purpose and Figure 1 has none either. The unnumbered `## Carrying the write as DML` heading is the requested shape. The Integer-keyed `Transfer` is a known fenced-block defect recorded on the PR. And the semicolon rule is not factually wrong: `validate.ts:299` tests `text.slice(0, -1).includes(';')`, so a trailing `;` passes and an interior one fails, exactly as claimed. It was ambiguous rather than wrong, and now reads "anywhere but the end" to match `reference.md:458`. No fenced line changed. Prose stays inside 80 characters, the five frozen anchors resolve, and no internal or inbound link broke. Every changed line in `ir.ts` is inside a doc comment.
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.
The actions guide passed the house-style checklist — sentence-case headings
within the length caps, spaced em dashes, no nested lists, no SHAG words — and
still read as machine-written. The checklist was never the failing thing. This
rewrites the prose for a reader who wants to declare an action and does not yet
know how.
What changed
The register.
kcmd, push and the judge are the subjects of their ownsentences; the personified "we" is gone, 50 occurrences to 0. That matches the
rest of
docs/semantic-model/, which uses "we" nowhere. Contractions stay,and so do the long compound sentences — chopping those into staccato is itself
a tell.
The opening. The guide starts on what a reader can't do today: an agent
that can ask but not act, reaching writes through hand-written tools the model
never sees. The feature is named after the problem, not before it.
Prose that circled a point instead of stating it. This is the bulk of the
work and the class the review kept landing on — explaining by analogy, by
contrast, by rebuttal, or by abstraction before the reader has the thing
itself. Roughly sixty passages, across every section:
opaque", three sentences on what a reader cannot see, and then "declare the
blast radius" said twice, becomes "
affectsnames the concepts a callwrites to."
add a second place to get the answer wrong" says what
concepttakesinstead.
X, not Yantitheses become "rather than" or a plain sentence. Anearlier pass in this branch introduced them; the construction appears
nowhere in the reference prose it was supposed to match.
gloss, and prose sentences over forty words that carried more than one idea.
by its backend:" led the field and the three values that immediately follow
it; the field leads now.
Five factual corrections, each found by reading the source before moving
the text:
It doesn't —
kc_actions.tspublishes an entry for every action, with theexecutor fields absent when nothing is wired up. The true behaviour is now
in section 5, where publishing is described.
on_violationread as though omitting it were legal with a default. It's apush error (
validate.ts:520), and "an unmarked constraint rejects" is thereason the key is required, not a fallback.
refusal being easy to spot and the rule still going unchecked are two
separate facts; both are stated.
tables a read reaches nor a function that writes. Both limits are now
stated where a reader would look for them.
What did not change
main. All 564 lines of YAML,SQL, diagrams and captured CLI output, verified after every batch by
diffing extracted fences.
### What an entity-typed parameter addsto### Parameters typed by an entity, because the original argued withhand-written tool schemas instead of saying what the thing is. It carries no
inbound anchor. The five inbound anchors from
reference.md,model_spec.mdand the agent demo README all still resolve, as does everyinternal one.
notes and "What is not modeled yet" carry the same content; five of them
now carry more.