AI-Lab-for-Cities-at-Harvard

generalize-skill

Separates a skill written for one organization into two things a portable generalized skill, and a context file holding every value that was specific to that place. Records how far the skill was generalized to travel, since that is what decides which values are local at all. Reports what it was unsure about rather than guessing. Produces drafts for the author to check; decides nothing and publishes nothing.

Resources

3
GitHub

Install

npx skillscat add ai-lab-for-cities-at-harvard/civic-skill-exchange/generalize-skill

Install via the SkillsCat registry.

SKILL.md

Generalize a skill

What this produces

Two separable artifacts, never one. The generalized skill must carry no
trace of the organization it came from, or it is not portable — so the extracted
values live outside it:

<skill-name>/                 the generalized skill. Give this to anyone.
  SKILL.md
  references/…  scripts/…     as before, with local values replaced
  context.template.yml        the slots, all TODO
<org>.context.yml             the extracted values. One organization's copy.
GENERALIZE-NOTES.md           what was extracted, uncertain, and left

Either artifact is useful alone. Together they compose: localize takes the
generalized skill plus a filled context and writes the values back in.

Copy assets/context.template.yml to start both context files. Read
references/contract.md for the format and references/what-is-local.md for
how to sort what is found — read that one before starting, since most mistakes
are sorting mistakes.

The failure that matters

A missed value produces confident wrong output in the next organization: no
error, no warning, a wrong answer. A value flagged unnecessarily costs the
author ten seconds.

Flag freely. Remove nothing that cannot be named.

Steps

1. Read every file first. Including scripts/ — a hardcoded dataset
identifier in a script is the same problem as one in the body, and easier to
miss.

2. Determine the scope, before sorting anything. Two questions: at what tier
of government does this skill operate — international, country, state,
county, city, neighborhood, or other — and how far is it meant to
travel?

The second question sets the boundary, and the sort in step 3 depends on it. A
fact above the reach line is shared context and stays; a fact at or below it is
local and comes out. The same sentence sorts differently depending on the
answer: for a city skill headed to other cities in the same country, national
law is shared and state law is local; headed abroad, both are local, and so is
the assumption that a city council exists. For a skill moving between
neighborhoods of one city, the city itself is shared and must not be
extracted — removing it produces a skill that has lost the ground it stands on,
and asks an adopter to fill in what they already share.

Take the tier from what the skill acts on, not from who wrote it: a regional
authority publishing a template for its member towns has written a city-tier
skill. other is worth using for school districts, transit authorities and
special districts, which do not sit on the municipal ladder.

Ask about the reach if it is not evident. It usually is not — the author never
had to state it. Record both in the context file under scope, so an adopter
can tell at a glance whether the skill is meant for a place like theirs.

3. Sort what is found into local, method, and comparators, per
references/what-is-local.md, against the boundary from step 2. Extract the
first and third. Leave method alone: a generalized skill that has lost its
method is not portable, it is empty.

4. Replace, never delete. Each extracted value becomes a named slot. Use
{{snake_case}} where the value is data-shaped — a field name, an identifier, a
number, a URL. Use prose that points at the context where the value is a name or
a phrase, because {{agency_name}} mid-sentence reads badly both to a person
and to an agent.

A sentence with its specifics removed and nothing put back is worse than the
original: it looks finished and says nothing.

Not every extracted value should become a slot. The context file is the
interview localize will conduct with the next adopter
, not a record of what
was found, and a long interview gets abandoned partway. Before adding a slot,
ask what breaks if it is wrong. If nothing reads the value, if another slot
implies it, or if it sits in one sentence that reads fine without it, it belongs
in the notes instead. Extraction and interrogation are different things.

Then say how exact each answer has to be, with exact: on the entry. Some
values are consumed by machinery and have one right answer: identifiers, field
names, URLs, anything appearing as a {{slot}}. Others are read as prose, and a
high-level answer is genuinely enough — roughly quarterly, our public works
department, whatever it is called this year
, whoever signs off. An adopter
who is not told that approximate is acceptable will invent something
exact-looking rather than admit they do not know.

5. Handle the frontmatter separately. description, civic.use-when and
civic.avoid-when are read by an agent deciding whether to invoke the skill,
before any context file is loaded. A placeholder in them is a bug — the
agent reads {{city}} literally and either never invokes the skill or invokes
it everywhere.

Rewrite them as organization-neutral prose, and put the specific versions in the
context under their own keys. Localizing writes them back.

6. Set the language slot. language is optional and different from the
four above: it does not decide whether the skill gets invoked, it says what
language the localized skill's output should be written in. Fill it with the
source skill's own language — its civic.language if the frontmatter has one,
otherwise whatever the body is actually written in. Write it as a default, not
a question: say in GENERALIZE-NOTES.md that the adopter may change it, and
leave it changeable, but do not leave it blank or TODO — the source always
has an answer. Adding it does not touch contract_version.

7. Stamp the contract version. Both context files open with
contract_version, copied from references/contract.md. A context file without
it cannot be safely read later — a reader finding no version does not know
whether the file is old or merely unmarked, and must stop and ask rather than
assume. Writing the number is the cheapest thing in this skill and the only
thing that makes a future mismatch legible.

8. Record what was uncertain, in GENERALIZE-NOTES.md: everything
extracted; everything that could not be confidently sorted, with which way the
evidence leaned; and anything local-looking that was deliberately kept, with the
reason. Methodology named after its source institution is the common case.

Do not resolve an uncertainty by picking. The author knows; a guess does not.

Before finishing

  • Grep the generalized skill for every extracted value. None may survive
    anywhere, including scripts and references.
  • Every {{slot}} in the body appears in the context, and every context entry
    is used somewhere.
  • No placeholder appears in description, civic.use-when or
    civic.avoid-when.
  • scope records the tier and the reach, and nothing above the reach line was
    extracted.
  • Both context files open with contract_version, matching the contract.
  • language is filled with the source skill's own language, not left blank or
    TODO.
  • Every slot is a question worth asking, and each says how exact its answer
    needs to be.
  • Running this skill again on its own output changes nothing. A second pass that
    finds more means the first was incomplete.

The other half

This skill writes a context file and stops. Filling it in is localize-skill,
which takes a generalized skill plus an organization's answers and produces an
ordinary self-contained skill that runs without reading any context.

/plugin install civic-skills-localize-skill@civic-skill-exchange

They share one contract — references/contract.md here, and the same file in
localize-skill. Read it before changing the shape of what this writes, because
the other side has to be able to read it.

You do not need both. A skill generalized here can be filled in by hand, and for
a small one that is the faster route. What localize-skill buys is the second
adoption: it keeps an organization's answers in a profile so the next skill asks
fewer questions.