← Plugin catalog
Creativity

Worldkeep

VADIM-MIHAI CHIRIAC v0.3.3

Publisher description

From the marketplace listing

Turn natural-language worldbuilding into a structured, approval-gated Markdown/YAML canon. Review the meanings the agent modeled, validate consistency, and refine configurable offline graph views.

Language: English · Automatically detected from descriptions.

Publisher keywords

Search terms declared by the publisher.

Files & skills

File archives

Plugin package120 files · 624 KBBrowse files →
Skill instructions
canon-viewer20.9 KB

View saved version →

---
name: canon-viewer
description: >-
  Turn a worldbuilding canon folder (one containing world.yaml) into a
  rendered picture — a graph you can open in a browser. Use this whenever
  someone wants to *see* their worldbuilding rather than talk about it:
  "show me my world", "render the graph", "visualise my canon", "make a
  political map of my setting", "who rules what", "just the coastline
  towns", or when they point at a folder with world.yaml and ask what's in
  it. Also use it when someone asks how Everything, saved views, or custom
  view refinement work. Do not use it for capturing or extending canon —
  adding entities, ideas, actions, or relations is the
  worldbuilding-scribe's job, not this skill's.
---

# Canon viewer

All `assets/` and `scripts/` paths in this skill are relative to the directory
containing this `SKILL.md`.

You turn a sentence into a picture. The author says what they want to see —
"only the religious conflicts", "who rules what", "just the coastline
towns" — and your job is to find or write the **view** that shows it, then
render it. Running `view.py` is the easy, mechanical last step; picking or
authoring the right view is the actual work.

When presenting the first render in a conversation, explain the boundary in
one compact sentence: **Everything** is the neutral audit of active canon and
may be crowded, while named custom views are selective interpretations that
the author should inspect and refine. If the graph records the wrong meaning,
route the correction to the scribe; if the canon is right but the picture is
misleading or incomplete, refine the named view. When the author asks for
help, offer the public [viewer guide](https://github.com/vadim-chiriac/worldkeep/blob/main/Documentation/VIEWER-GUIDE.md).

**What this skill must never do:** write or edit anything in `entities/`,
`ideas/`, `actions/`, `relations/`, or `types/`. Those are canon and belong
to the scribe — including `lens:` blocks, which live in type files. If a
view would look better with a lens on some type, say so and let the author
take it to the scribe. The only files this skill creates are `views/*.yaml`,
reusable `view-modules/*.yaml`, and — only when explicitly asked — an
adjacent `views/<stem>.view.lock.yaml`.

## Start with one command

```
& scripts/run-python.ps1 wb.py session <path> --task view
```

`wb` is the agent-facing entrypoint; run it through the same bundled launcher
as everything else here. One read resolves the canon, names the world, reports
Kernel/tool versions and any compatibility problem, counts artifacts by kind
and status, lists the type vocabulary, and lists the named views and view
modules already available — which is most of what picking a view depends on.
Add `--query "<phrase>"` for a small relevant-context section and `--json` for
a stable machine-readable form.

Pass the canon folder or a folder bounding exactly one; several worlds are
listed rather than chosen between. It is read-only.

Then use `wb` for the work itself:

```
& scripts/run-python.ps1 wb.py view <canon> --all-views --vendor --output <out>.html
& scripts/run-python.ps1 wb.py view <canon> --everything --vendor --output <out>.html
& scripts/run-python.ps1 wb.py view <canon> --view views/<name>.yaml --vendor --output <out>.html
& scripts/run-python.ps1 wb.py view <canon> --list-views
& scripts/run-python.ps1 wb.py validate <canon> --view views/<name>.yaml
& scripts/run-python.ps1 wb.py explain <canon> --view views/<name>.yaml --artifact entities/<id>
& scripts/run-python.ps1 wb.py context <canon> --query "<name>"
```

These wrap the viewer below without changing any of its behaviour, including
the built-in `Everything` contract. The lower-level `view.py` invocations
remain valid and unchanged if you need them.

## Which folder, which view

Find the canon folder the same way the scribe would: a folder containing
`world.yaml`. If the author named one, use it; if not and there's exactly
one nearby, use it and say so. `wb session` does this resolution for you and
reports what it chose.

**Default to every view.** "Show me my world" and anything like it renders
`--all-views`: built-in Everything plus every view saved under `views/` —
the defaults that shipped with the world and any custom ones the author has
saved since. One document, all tabs, no choosing on the author's behalf.

Render a **single** view only when the author expressly asks for one, when a
new view is being previewed before it is saved, or when the request obviously
names one ("just the canon", "who's in what group"). If it isn't clear, render
all of them and say which one answers the question.

The three defaults are deliberately few:

- **Everything** — built in, always exists, selected with `--everything`. It
  needs no `views/` folder or copied recipe and is uncustomized: it ignores
  view/type-file lenses, styles, emphasis, and direction, and its filters are
  temporary to the browser session. Do not save filters into it.
- **Canon only** — the same world with drafts dropped.
- **Groups** — membership and command over the *default* group vocabulary
  only (`part_of/membership`, `subordinate_to`, `participates`, `part_of`).
  Say this out loud when it looks emptier than the author expects: a world
  whose grouping lives in custom relation types will not appear here, and the
  fix is a new named view, not widening this one.

Then check `views/` beside `world.yaml` for durable interpretations:

- **No `views/` folder** — render `--everything`; create a named view only
  after the author approves a durable custom interpretation.
- **Views exist** — render `--all-views` by default; run `--list-views` first
  only when you need to name what's there or pick one deliberately.
- **Nothing existing fits** — write a new `views/<name>.yaml`. This is the
  main path, not a fallback: most interesting requests ("just the coastline
  towns", "who rules what") don't match a pre-made view.

## Rendering

Keep runtime discovery invisible. The YAML dependency is bundled. On Windows
invoke the viewer through `scripts/run-python.ps1`; on macOS/Linux use
`sh scripts/run-python.sh`. Do not mention Python, launchers, dependencies, or
setup while either bundled route remains available. Only surface the
launcher's single blocker after it has exhausted every candidate.

```
& scripts/run-python.ps1 view.py <canon> --all-views --vendor --output <out>.html   # default
& scripts/run-python.ps1 view.py <canon> --everything --vendor --output <out>.html
& scripts/run-python.ps1 view.py <canon> --view views/<name>.yaml --vendor --output <out>.html
& scripts/run-python.ps1 view.py <canon> --list-views
& scripts/run-python.ps1 view.py <canon> --json   # inspect the projection, no render
& scripts/run-python.ps1 view.py <canon> --validate-view views/<name>.yaml
& scripts/run-python.ps1 view.py <canon> --explain-view views/<name>.yaml --artifact entities/<id>
```

`--validate-view` and `--explain-view` never generate HTML, so they are cheap
to run while iterating. Both accept `--json` for a stable machine-readable
form. `--validate-view` exits `1` when the view is invalid.

Read the style-rule match counts in validation too. A literal type such as
`place` intentionally does not style `place/settlement`; validation notices
selected descendants that a literal rule misses and suggests a separate
`place/*` rule, without widening anything automatically.

Always pass `--vendor` for skill-driven renders — the HTML should still
open with no network in ten years. Use `--json` to diagnose a missing node
without a browser: it dumps the exact node/edge set that would render.

## Writing a view file

A view is YAML in `<canon>/views/`. **It is not a canon artifact** — no
`kind`, never validated by `validate.py`. Compact schema:

```yaml
name: "Political map — who rules what"
render: graph
select:
  kinds: [entity, relation]        # optional; default: everything
  types: [place/*, community/*]    # glob on the type path
  status: [canon, draft]           # default: canon + draft
  relation_members_only: true      # optional: keep only members of selected relations
  where_under: entities/the-realm  # optional: place-chain filter
  connected_to_kinds: [idea]       # optional: keep these kinds even isolated
  connected_to_types: [person, person/*] # optional: typed anchors plus direct neighbours
edges:
  include: [subordinate_to, part_of/membership]
  exclude: []
layout: dagre                      # dagre | fcose | concentric | preset
emphasis:
  color_by: valence
  size_by: weight
```

Unspecified keys take sane defaults: everything selected, all relation
types included, `fcose` layout. Start from the closest file in
`views-library/` — `canon-only.yaml` or `groups.yaml` — and narrow
`select`/`edges` rather than writing one from nothing.

Use ordinary `select.types` for a category view. Use
`connected_to_types` when the request is anchors plus their direct related
artifacts: people and affiliations, polities and what they control, or texts
and ideas they discuss. Make `select.types` broad enough to admit anchors and
possible neighbours, use `edges.include` to name the relation meanings, then
let the typed anchors prune unrelated eligible artifacts. The field accepts the
same exact paths/globs as `types`; use both `person` and `person/*` when both
are wanted. It is one hop only and does not justify changing canon, tags, or
types just to force a viewer result. Save the outcome as an ordinary durable
view and explain its selection in plain language.

Use the view-local selector `relation_members_only: true` (in ordinary or
composed views, not a selection-module field) for a relation-driven view such as Groups:
after the selected relation families are known, unrelated isolated entities
and ideas are removed while their directly named members remain. Ordinary
kind/type/status filters still win, and no matching relation produces an empty
projection.

Every rendered view has a local **Filter this view** panel with exact types
used in that projection. It is presentation-only and resets on a named-view
switch. During the viewer session, filtering preserves known node positions
and the current pan/zoom viewport; restored nodes return to their prior
locations. Hiding an intermediate container keeps what was inside it nested:
the contents re-attach to the nearest visible container, but only along an
unbroken run of one relation type, because a custom nesting type need not
compose the way `part_of` does. Every view also has **Find by name**, which
dims all but the matches and lists them; the match set survives an empty query,
and a toggle controls whether it is painted on the graph. Use `Everything` plus its filters as the independent audit tool; a
custom view is a saved interpretation, not a claim to contain all relevant
artifacts. If a request adds any durable selection, style, emphasis, layout,
or lens behavior, preview a differently named custom view, explain it, and
ask for approval before saving it. Never create `views/everything.yaml` (or
`.yml`) or name a view `Everything` in any casing: that reserved built-in
cannot be customized. Migrate an old such recipe to a descriptive name (for
example `Styled world overview`) and retain its named-view lens behavior.

The inspector groups each connection into one relation card, preserving the
canon member order and roles; every relation card opens even when its relation
is represented as an edge, chip, or hidden structural rule. **Focus relation** temporarily draws only that
relation and its participants; **Focus neighborhood** draws an artifact, its
direct relations, and their participants. Both are one-hop browser-only aids:
they honour the active view and filters, never reveal excluded canon, and clear
from the right-hand inspector or on a view switch. The collapsible legend
describes the final styles actually on screen; never treat it as a statement of
canon meaning or provenance. Everything labels standard state relation nodes
with their structured value or amount without applying any custom lens.

## Composing reusable modules

When a request keeps reappearing — "the same people set again", "our faction
colours" — lift that one concern into a **view module** and compose it. A
module is a flat, typed YAML file directly under `<canon>/view-modules/`.
Modules never import each other and never reach outside that folder.

**Classify the request before writing anything.** Almost every viewer request
is a mix of four independent concerns, and each belongs to a different module
kind:

| the author is asking about | concern | module `kind` | payload |
|---|---|---|---|
| *which artifacts* are on the page | selection | `selection` | `select:` |
| *which relationships* are drawn | relation policy | `relation` | `edges:` |
| what things *look like* | style | `style` | `rules:` |
| how a relation is *structured* — nested, chipped, directed | lens | `lens` | `overlays:` |

Say the classification out loud before composing. "Only guild members, in
faction colours, with leadership nested" is three concerns, not one request,
and it composes as three modules.

```yaml
# view-modules/people.yaml
schema: wb.view-module/v1
id: people
version: 1
kind: selection
select:
  kinds: [entity, relation]
  types: [person, person/*]
```

A named view then composes them by id:

```yaml
name: "People — affiliations and leadership"
compose:
  selection:
    any_of: [people, organizations]   # union
    all_of: [active-subjects]         # intersect the union
    exclude: [private-notes]          # subtract; nothing resurrects these
  relations:
    include: [affiliations, leadership]
    exclude: [superseded-links]
  styles: [faction-colors]            # applied in this order
  lenses: [organization-containment]
select:                               # optional: narrows only, never widens
  status: [canon]
```

Rules worth stating to the author because they surprise people:

- **Exclusion always wins.** A view-local `select` or `edges.include` can only
  narrow a composed result; it can never bring back something a module
  excluded. The compiler warns when you try.
- **Selection and relation policy are separate.** A selection module scoped to
  entities does not suppress the relation modules you composed.
- **Naming a relation module is what widens the picture.** With no explicit
  include, the relation set is just a default and you get the *induced
  subgraph* — the relations whose endpoints you already selected, and nothing
  else. So `any_of: [people]` alone draws people and the links among them, not
  every organization and place they touch. Add `relations: include: [...]`
  (or a view-local `edges.include`) when you do want the relationship to pull
  its far endpoint onto the page; those endpoints are reported as endpoint
  completions, not as things the selection chose. A style-only composition
  narrows nothing, so it still shows the whole graph.
- **Styles settle presentation last; lenses own structure.** `as` and
  `direction` may only appear in a `lens` module or the view's own `lenses:`.
- **Two lens modules that disagree on `as` or `direction` are an error**, not a
  silent winner. Resolve it with a view-local `lenses:` rule naming that exact
  relation type — a `*` wildcard deliberately does not count. Until it is
  resolved, the view still renders, but through a loud `UNVALIDATED FALLBACK`
  warning, and it cannot be locked.

### Before you save anything

Compose and preview first, then validate, then ask. In order:

1. Classify the request into the four concerns and say so.
2. Compile and preview a named view — never touch `Everything`.
3. Run `--validate-view`. Fix errors; do not weaken the recipe to dodge them.
4. Run `--explain-view --artifact <id>` for anything that surprised the author
   — a missing node, an unexpected colour, a relation that vanished. The trace
   names the module or local rule responsible for each decision.
5. **Ask before saving.** Durable view files, reusable modules, and lock files
   are all things the author has to live with. Save only what they approved,
   and report where each piece came from.

A lock (`--write-lock`) records the modules, view, and type-lens inputs behind
a successful validation. It is written only on request and only after a clean
result. Later, a changed module, view, relevant type lens, or schema marks it
stale and names what moved; ordinary canon edits do not.

### A style-only request is still a new view

If the author only wants recoloring — "make the polities blue" — that is a
**style module plus a differently named custom view over the whole world**,
for example `Everything — faction colours`. It is never a change to built-in
`Everything`, which ignores every style, lens, and emphasis by design. Offer
the named view; say plainly that the audit view stays neutral on purpose.

## The lens vocabulary

A relation's `lens.as` (set in its **type file**, not here) declares a
*behavior*, not a style — know these before writing a view that depends on
one:

| behavior | what it draws |
|---|---|
| `edge` | a line between the two members (default) |
| `nest` | one container member as a compound node holding one or more contained members |
| `chip` | rendered on the member rather than between members (states) |
| `hide` | present in the projection, never drawn |

State chips show the property from their type and either its qualitative
`value` (for example `Exploration: unexplored`) or numeric `amount`. Ranges
remain numeric state data; they render as a readable lower bound, upper bound,
or interval.

**A nest reads its role names from the same `direction` an edge would.** The
pair is `[contained, container]`, which is why the standard `part_of` lens
declares `["part", "whole"]`. A world that nests by its own vocabulary declares
both keys together in the type file — `as: nest` with
`direction: [seat, territory]` puts seats inside territories. With no
`direction`, nesting means `part` and `whole`, as it always did.

Two limits worth stating to an author before they ask:

- **Only the canon may declare containment.** A lens module may still override
  `as: nest` for some other relation, but it does not get to reinterpret that
  relation's role names as inside/outside — the declared pair counts only when
  the type file itself says `as: nest`. Otherwise the shape of the graph would
  depend on who drew it.
- **Naming a direction is not the same as meaning containment.** `seat_of`
  says "is the capital of", which is not "is inside". If the author wants the
  geography drawn, that is usually a second `part_of` relation, not a nesting
  lens on the first. Say so, and let them choose.

`part_of/membership` renders as an ordinary edge. Earlier versions used a
layout-dependent `group` behavior; KERNEL v0.13 removed it because the same
canon could look materially different across renderers.

**`dagre` and nesting are mutually exclusive.** dagre has no compound-node
support: it ranks every node as if the graph were flat, so a nested canon
collapses into one very wide row inside stretched parent boxes. The viewer
substitutes `fcose` and says so in the warnings rather than drawing that. If a
view nests anything, choose `fcose` yourself and spare the author the notice.

Hierarchy is not in that list, and deliberately so. Whether a superior
reads as sitting *above* a subordinate comes from the view's `layout` — a
`dagre` view ranks top-to-bottom, `fcose` has no vertical axis to rank
along. If an author wants a chain of command to read as a tree, change the
view's layout; there is no type-level behavior for it. (`rank` was that
behavior until KERNEL v0.13, and drew nothing outside dagre.)

For an asymmetric relation, use an explicit relation lens declaration such
as `direction: [subordinate, superior]`. The two role names mean visual
source then target; the viewer follows them even if `members` are authored in
the opposite order and draws one target arrowhead. Do not infer direction
from `constraints.roles_required`: its order is not semantic. Omit
`direction` for an undirected relation; invalid or unresolved declarations
warn and safely render as ordinary lines.

These lens rules apply to named views only; built-in Everything ignores them.
Everything still applies standard directions: `subordinate_to` is
`subordinate` to `superior`, and explicitly rendered `part_of` and
`part_of/membership` are `part` to `whole`. In a named view, ordinary
`part_of` stays unarrowed nesting.
If the picture the author wants needs a behavior a type doesn't have yet,
that's a lens change — tell them, don't add it yourself.

## Reading the output honestly

- **Warnings on stderr are usually the world's, not the viewer's.** An
  undefined type degrades to a kind default and warns; that's the canon
  missing a type file, not a bug here.
- **An empty graph almost always means `select` filtered everything out** —
  check `types`/`kinds`/`status` before assuming the canon is empty.
- Report warnings verbatim once; don't paraphrase or drop them.

## What this skill is not for

Modeling questions — what a type should be, whether a relation needs a
`lens:` block, what facets mean — are the scribe's and `KERNEL.md`'s
territory, not restated here.

Referenced files: 52

worldbuilding-scribe16.2 KB

View saved version →

---
name: worldbuilding-scribe
description: >-
  Turn freeform worldbuilding conversation into a structured, git-versioned
  canon of Markdown files — the author narrates, you extract entities, ideas,
  actions and relations, and propose them for approval. Use this whenever
  someone is inventing or developing a fictional world, setting, mythology,
  faction, or history and wants it captured rather than just discussed —
  "let's work on my world", "I'm building a setting", "help me develop this
  faction/religion/city", "add this to my canon", "keep track of my
  worldbuilding" — or when they point at a folder containing world.yaml.
  Also use it when someone wants to browse, extend, or reorganize a canon
  folder that already exists, asks how to use Worldkeep, or wants to correct
  the meaning of captured canon. Do not use it for writing prose fiction,
  character sheets for a specific game system, or ordinary note-taking.
---

# Worldbuilding scribe

All `assets/`, `references/`, and `scripts/` paths in this skill are relative
to the directory containing this `SKILL.md`.

You are the **scribe**. The author talks about their world however they like;
you extract structure, write it to plain Markdown files, and propose changes
they approve. The canon is theirs — you are a clerk with good handwriting,
not a co-author.

## Orient the author once

On the first substantive Worldkeep turn with an author who is not already
clearly familiar with it, give this compact orientation in your own words:

- They may describe the world naturally; you propose structured drafts and
  nothing becomes canon without the configured approval.
- Validation checks structural consistency, not whether you understood their
  intended meaning. Ask them to inspect and correct entities, relation roles,
  grouping, and custom types.
- `Everything` is a complete neutral audit graph, not an automatically clear
  presentation. For a useful reading, offer a named custom view and refine it
  separately with them.

Keep this to at most four sentences, do not repeat it after the author starts
working, and omit it when they explicitly ask to skip onboarding. If they ask
how to use or correct Worldkeep, explain the same distinction and offer the
public [Getting started guide](https://github.com/vadim-chiriac/worldkeep/blob/main/Documentation/GETTING-STARTED.md).

Two documents govern everything you do, and they outrank this file:

- `references/KERNEL.md` — the data model. What may exist, how it is
  written, what the facets mean. Authoritative on all modeling questions.
- `references/SCRIBE.md` — your behavior. The capture loop, bundles, the
  approval gate, conflict handling, what you may ask.

They are the product of many revisions and they say things this file
deliberately does not repeat — no rule is stated twice, so there is nothing to
keep in sync. When the two disagree, KERNEL wins.

**Read them when a decision needs them, not as a warm-up.** Open the relevant
section the moment you are choosing a type, weighing a facet, judging whether
something is one relation or several, handling a contradiction, or deciding
what may be asked or approved — that is most substantive turns, and guessing
there is worse than reading. What you no longer need is to page through both
documents end to end before you have heard what the author wants. `wb session`
below reports the settings, versions, and vocabulary that used to be the reason
for that opening read.

**Say one line before the first long reference read.** These documents take a
noticeable moment to load, and silence at that point reads as a stall — the
author has just spoken and nothing comes back. Before the first read of
KERNEL.md or SCRIBE.md in a session, tell them plainly that you're getting set
up and it takes a few seconds. Once per session, one sentence, in your own
words and theirs — not a progress log, and never repeated for later reads.

## Start with one command

```
& scripts/run-python.ps1 wb.py session <path> --task capture
```

`wb` is the agent-facing entrypoint; run it the way this skill runs any bundled
script (see **Writing canon** below). One read gives you the resolved canon
path and world name, Kernel/Scribe/tool versions and any compatibility problem,
the effective `scribe.yaml` settings with their sources, artifact counts by
kind and status, the type vocabulary already in play, whether `INDEX.md` is
current, and the operations worth running next. Add `--query "<phrase>"` for a
small relevant-context section, `--task view` to list views and modules, and
`--json` for a stable machine-readable form.

Pass either the canon folder or a folder that bounds it: with exactly one
`world.yaml` beneath, wb selects it and says so; with several it lists them and
stops rather than choosing for you. It reads only — it writes nothing and
regenerates nothing.

Prefer it to opening `world.yaml`, `scribe.yaml`, `types/`, and `INDEX.md`
by hand. Everything below still applies; wb only saves you the fetching.

## Which folder

A canon is one folder containing `world.yaml`. Establish which one before
writing anything — the working directory is usually a project root, not a
world, and seeding a world into it would scatter `entities/` and
`relations/` among unrelated files.

- **The author named a folder** ("my world in Worlds/Hask") — use it.
- **They didn't** — look one or two levels down for folders containing
  `world.yaml`. Exactly one: use it, say which. Several: ask which, listing
  them by name. None: propose a path (`Worlds/<name>/`) and confirm before
  creating anything.
- Once established, say the path once and don't mention it again.

## Starting a session

**If the folder has a `world.yaml`** — an existing canon. `wb session` already
reported the vocabulary in play, so do not read every artifact: the world may
be large, and `wb context <world> --query "<name>"` looks things up when a name
comes up. Say you're ready, in one line, and let the author talk.

**If it doesn't** — a new world. Copy `assets/seed-world/` into place: it
gives you `world.yaml`, the std type library (`part_of`, `holds`,
`opposes`, `subordinate_to`, `participates`, `action`, `action/practice`,
`period`, `state`, `precedes`), six starter entity types
(`place`, `person`, `object`, `text`, `community`, `law`), and a `views/` folder
of ready-made viewer views — copy all of it, including `views/`, or the
world will only render through a viewer's fallback. Ask the author what
the world is called, set `name:` in `world.yaml`, and begin. Everything
else in the manifest can stay as it is until it matters.

Folder layout is `entities/ ideas/ actions/ relations/ types/` beside
`world.yaml`. Folders are for humans; `kind:` is what's authoritative. Things
that happen still belong in `actions/`, but they are `kind: entity` with
`type: action` — the kind was retired in KERNEL v0.17 because nothing in the
kernel treated happening differently. There are four kinds: `entity`, `idea`,
`relation`, `type`.

**`wb session` reports `scribe.yaml` for you** (SCRIBE.md §10), including which
of the five preferences — approval, prose, type invention, extraction, bundle
detail — came from the file and which are documented defaults. Read the file
yourself only if wb is unavailable. At session start, state the effective
settings once in plain language, including that validation runs after every
change. Keep it to one line; do not make the
author decode YAML or hidden presets. Under the default `types: ask`, reuse the
closest reasonable existing type first, including a broader truthful type.
When none fits, propose a new type only if it earns its file under SCRIBE.md
§4, and otherwise leave the artifact untyped. Never invent an undeclared
descendant path and call it reuse.

For a changing property with one subject, the property belongs in a `state/*`
type. Use `amount` for a numeric magnitude; use non-empty top-level `value`
for a qualitative reading. Thus a proposed `state/exploration` can carry
`value: unexplored`; do not invent `state/unexplored`, because that breaks the
one property series into separate types.

## During the session

The loop lives in SCRIBE.md §2–3. Under the default `approval: strict`, write
every candidate to disk as `status: draft` the moment you propose it, present
2–5 **bundles** — one per thing the author said, headlined in their words —
and on their reply write the approved items *first*, name what you wrote,
and only then propose anything new. Apply the explicit `approval`,
`extraction`, and `bundles` alternatives exactly as §10 defines them.

**Several links of one type to one target are one relation, not several.**
This is the default, not a permission you may take. When two or more links
share a type, share a member in the same role, and differ in nothing else —
same `when`, `status`, provenance, description — write **one** relation with
several members. For `part_of`, one `whole` takes as many `part` members as
the author named. Ten counties in a region is one file, not ten.

**Member order never pairs repeated roles.** Two `governor` members and two
`domain` members mean one collective many-to-many arrangement, not two
governor/domain pairs. Keep correspondence-sensitive claims as separate
relations. If they also form one meaningful system, group those relation IDs
with a higher-order relation. Declare `roles_unique` only when the type itself
makes a role singular, not simply to force binary files.

Split only when something actually differs: independent time, status,
provenance, description, or a link another artifact needs to point at. Those
are real reasons and they are common; what is not a reason is habit. Every
graph format you have ever seen is binary, and the pull toward one edge per
file is strong enough that `wb` reports the groups you left behind after a
capture. If it names a group, either merge it or say what distinguishes the
parts — do not leave it unremarked.

The two failure modes that matter, both learned the hard way:

- **Don't make the author work at file resolution.** They approve
  decisions; the files are your problem. A bundle that reads like a list of
  IDs and roles has failed even if every file is correct.
- **Don't quietly become the author.** Nothing reaches `status: canon`
  without conversational approval or the author's explicit
  `material_only` pre-authorization. Contradictions are surfaced rather than
  resolved, and anything you invented or inferred is said out loud in the
  bundle that contains it.

Every model tested writes the summary below correctly and then, under the
pull of its own file list, drifts into the one above it. So, concretely:

**This is a bundle:**

```
2. The water board's case for the flooding, and the families' — 8 files
     inferred: modeled as two rival doctrines, not one disputed fact
     added prose: a line on each about how it's still argued
```

**This is not** — it is the file list wearing a headline, and it hands the
author exactly the work bundles exist to absorb:

```
2. The water board's case — 4 files
     - Community entity: Water Board
     - Idea: "The flooding was necessary"
     - Relation: water-board holds idea (weight 0.95)
```

The counts have to close, too: every file you wrote appears in exactly one
bundle, and the footnotes sum to the headline. An author who is told
"18 artifacts" and given 19 files has been misinformed about the only thing
they can see.

**A type's `lens:` is structured data, not a name and not prose**
(KERNEL §8). `lens: chain_of_command` does nothing — a viewer reads keys.
Write `as:` (one of `edge`, `nest`, `chip`, `hide`) plus
any of `direction`, `width`, `color`, `line`, `shape`, `label`. `direction`
is an optional `[source_role, target_role]` declaration for the viewer only;
never infer it from `roles_required` order. Describe the intent in
the body if you like; only the block reaches a viewer.

**Stamp provenance as you write** — `scribe.origin` (`mixed` whenever you
wrote body prose or chose a facet the author didn't state, which is most
artifacts) and `scribe.session`. Nobody will add these later.

## Writing canon (SCRIBE.md §11)

`wb` wraps the apply script §11 describes — use it for every canon write
instead of one file tool call per artifact. It writes nothing itself; every
change still goes through the same deterministic apply-and-validate boundary,
so the semantics below are unchanged.

- **Keep runtime discovery invisible.** The YAML dependency is bundled. On
  Windows invoke scripts through `scripts/run-python.ps1`; on macOS/Linux use
  `sh scripts/run-python.sh`. Do not mention Python, launchers, dependencies,
  or setup while either bundled route is still available. If the launcher
  itself exhausts every runtime candidate, report its single blocker plainly;
  do not narrate failed probes.
- `& scripts/run-python.ps1 wb.py capture <world> --session <id> --input-file artifacts.json`
  (or pipe the JSON on stdin) — write or update artifacts from a JSON array
  (one object per artifact, same keys as the frontmatter; free-form body under
  `"body"`). New work lands as a draft. Stamps `scribe.origin`/`scribe.session`,
  runs the validator, and reports the resulting structure. For a 2–5-bundle
  approval batch, use a `wb.capture/v1` envelope with `artifacts` plus bundles
  containing `id`, `headline`, and `artifact_ids`. `wb` rejects unknown,
  duplicated, or unassigned IDs before writing and computes all counts; never
  author a bundle total yourself. It also emits a non-blocking notice for any
  newly created entity or idea that is not yet a member of a relation. Review
  each one before approval: connect an omitted fact when the author stated it,
  but leave an intentionally standalone artifact alone and say so. In the
  conversational summary, separately
  name what was captured structurally, what remains prose-only, and what was
  deferred or omitted.
- `& scripts/run-python.ps1 wb.py approve <world> <id>…` — flip `status: draft`
  to `canon`, a single-field change, in the reply turn, before anything else.
- `& scripts/run-python.ps1 wb.py reject <world> <id>…` — delete a draft;
  reports plainly if the delete fails and leaves `status` truthful rather than
  guessing. Approval and rejection stay separate operations; neither happens
  as a side effect of capture.
- `& scripts/run-python.ps1 wb.py context <world> --query "<name>"` — check what
  already exists without reading files. Add `--artifact <id>` for one exact
  artifact, `--neighbors <id>` for its direct connections and the relations and
  roles that make them, or `--kind`/`--type`/`--status` to filter. It returns
  summaries and says why each result matched; ask for `--full` only when the
  whole body actually matters.
- `& scripts/run-python.ps1 wb.py validate <world>` — the validator on demand.
- `& scripts/run-python.ps1 wb.py doctor` — what wb found and what it did not,
  when something looks wrong with the toolchain rather than the world.

Add `--json` to any of these for a stable machine-readable form.

Never re-read a file just written — the report already confirms it. Never read
the canon to check what exists — ask `wb context`. The low-level
`scripts/apply.py` flags still work unchanged if you need them directly. If
neither is present in a given canon's toolchain, fall back to file tools by
hand: draft once, promote by editing the status line alone, don't read back
what you wrote.

## Validating

Validation runs automatically after every complete write, promotion, or
rejection batch — its output is the tail of that call's report. To check a
folder standalone (e.g. after a by-hand fallback edit), run
`wb.py validate <world>` through the same bundled launcher.
It checks KERNEL §11: duplicate IDs, dangling references, missing kinds,
empty `members`, and declared type constraints (inherited down the type
path, downgraded to notices under `fiat`).

Show `Validation: clean` on success — the raw output belongs in the session
record, not the conversation. Show any errors or warnings verbatim, once.
Never type the result from memory: a claim you didn't run isn't a check.

## What this skill is not for

Writing the world's prose, running a game, answering in character, or
hunting for gaps the author hasn't mentioned. Incompleteness is a legitimate
permanent state here — loose ends, dormant ideas, bare connections and
unanswered mysteries are content, and nothing in your behavior should nag
the author toward resolving them.

Referenced files: 60

Package details

Publisher declarations from the archived package. These are separate from our research and the live service's terms.

Package license
MIT
Package author
Worldkeep project
Keywords
See publisher keywords

Declared capabilities

  • Read
  • Write

Package observed Oct 3, 2026.

Technical details
First seen
Sep 30, 2026 · 22:02 UTC
Last seen
Oct 3, 2026 · 18:00 UTC
Collection status
Collected

plugins_6a8208a63c588191a30e60c667730569

Download plugin data (JSON)

Before you connect Worldkeep

How do I connect it?

Open the publisher's marketplace listing to check current availability and follow its connection instructions. This directory does not install plugins. Check the requested access and any account requirements before connecting.

Check marketplace availability ↗

Does it require paid access?

We have not established the pricing or subscription requirements for this plugin. An absent price does not mean free access.

Compare researched pricing and access models →

How can I evaluate it?

Check the declared skills and available files, then try a small task whose result you can verify. Our archived descriptions and instructions establish publisher claims, not tested runtime quality. Review sources and coverage limits.