VgScript agent guide
The IDE-agnostic reference for AI agents working in a VgScript content repo: the language, the vgs commands, and the conventions to follow.
This is the single, IDE-agnostic guide for AI agents (Claude Code, Cursor,
Copilot, etc.) working in a VgScript content repo — a project full of .vgs
scripts authored with this plugin.
Is the same guide valid in every IDE? Yes. The VS Code extension and the JetBrains plugin are thin wrappers over the same
vgsengine and the same grammar (both live incore/). Everything an agent needs — the language and thevgsCLI — is editor-agnostic, and agents drive the CLI directly rather than clicking menus. Both plugins even expose the identical command set (Fix + Reconcile,Validate,Fix + Reconcile All,Resolve Links,Reorder Scenes) under aVgScriptmenu, so the human-facing steps match too.How to use it: use the public
vgscript-exampleproject as the reference for your content repo'sAGENTS.md(orCLAUDE.md). The Language and Tooling sections below are universal — copy them verbatim. Only the Project context section is per-project; fill it in.
Project context (fill this in for your repo)
This section is a placeholder. Replace the prompts below with your project's own details, but keep the headings so agents always know where to look.
- Premise — one or two sentences on what the game/story is about.
- Authoring language — the natural language the prose is written in (and any
rule, e.g. "keep tags in English, prose in
<language>"). - Conventions — anything non-obvious about tone, naming, or structure an agent should preserve.
Workspace structure (typical — adapt to your repo)
- Scenes are
.vgsfiles, each with a matching<file>.vgs.metasidecar. - Scenes are usually grouped into directories (acts, chapters, etc.) and numbered
sequentially, e.g.
S1_Intro.vgs. Scenes.txtat the project root lists scenes in play order; severalvgscommands read it.
The vgscript language
vgscript is a lightweight bracket-tagged DSL for narrative scenes: dialogue,
named actions, timed/parallel events, and branching decisions. Every tag sits at
the start of its line, in square brackets. The descriptions below match what
the vgs parser actually does (core/tool/src/reconcile.rs); where a feature is
interpreted by the game engine rather than the tool, it is marked
(engine-level).
Entries vs. context tags
The tool parses a script into entry groups. Only four tags open an entry:
| Tag | Entry | Notes |
|---|---|---|
[A] | Action | Engine actions/events. Often a scene's SceneHandler (an action called via events/triggers). |
[T] | Talk | A block of visible narrative / dialogue lines. |
[D] | Decision | A player choice with numbered options. |
[C] | Condition | A condition block that gates flow (engine-level). |
Two more tags carry context but do not open an entry of their own:
[S]— Scene header. Defines the scene key + name. Written as either[S1_Intro](key/name split on the first_) or[S1] Intro(name after the bracket). With neither, the key/name fall back to the filename.[P]— Speaker (persona). ⚠️ This is who is speaking, not the spoken text. The text after[P]names the character; an arrow gives a target:[P] Heroor[P] Hero -> Villain. The current speaker (and optional target) are attached to the following[T]talks ascharacter/directedToin the.meta. An empty[P]clears the speaker. The actual lines of dialogue live in the[T]block beneath it.
Sublines
Numeric sublines [1] [2] [3] … belong to the most recent parent entry above
them. A subline may also be written in long form [D1-2] — reconcile normalizes
it to [2]. [X] (or [x]) is a placeholder/first subline.
[P] Hero
[T1]
[1] So this is where it begins.
[2] Nothing here yet. Only my voice.
Flow control and timing
- Sequential —
->(X): after this line/action finishes, go to entryX. Example:[A1] Intro ->(A2). - Parallel —
||(X): runXconcurrently without waiting. Example:[A1] FadeIn ||(Timer{S}). - Cross-script jump —
->(Sx-Code): jump to an entry in another scene. Runvgs resolve-linksto bind these to stable GUIDs (see Tooling). - Everything after
->or||on a line is flow, not visible text — the tool excludes it when computing a line's displayed text.
The following are engine-level conventions. To the vgs tool, any {…} is an
opaque inline tag that it strips from visible text; the tool does not validate
their meaning:
- Timers —
Timer{XS},Timer{S},Timer{M},Timer{L}: engine-defined durations (extra-small … large) for easy global tuning. - Events — an action that calls a sub-action by name, e.g.
[A2] Event{A1, "UnlockDoor"}("A2 calls theUnlockDoorevent from A1"). - Inline markers — e.g.
{!repeat}: metadata on the line, stripped from the visible/.metatext. - Magnitude suffixes —
XS, S, M, Ldenote size/scale for timers, text blocks, and other magnitude-based parameters.
Decisions and branching
A decision lists numbered options; each option's arrow says what to start when chosen:
[D1]
[1] Accept ->(A_yes)
[2] Decline ->(A_no)
The tool understands two things inside options:
- Copy text from a talk —
=(Tn-1)pulls the visible text of[Tn]line[1]into the option. Reconcile materializes it: a line like[1] {!repeat} =(T1-1) 'Old text' ->(T1)is rewritten to[1] {!repeat} 'Actual talk text' ->(T1), and the.metastores the resolved text. Author-written quoted/plain text that you didn't reference is left alone. - Conditions — an option may start with
if(...); theif(...)is not counted as visible option text. (Broader decision queries such asDecisionsCountLess{...}and=(Dn-x)echoes are engine-level.)
Writing decisions that feel like decisions
A decision's job is to give the player a sensation of agency — the feeling they are authoring their own route — even when every branch reconverges on the same beat. The narrative can (and usually should) funnel back together; what must not collapse is the felt difference between options at the moment of choosing.
The failure mode to avoid is synonym choices: two or more options that say the same thing in different words. They read as a fake decision and break immersion.
✗ Anti-pattern:
[D1] [1] We should leave. [2] It's time to go. [3] There's nothing left for us here.All three are the same intent reworded, and the flow merges regardless. There is no real choice here.
Make options differ in kind, not just wording. Good axes of divergence:
- Attitude / value — pride vs. doubt, tenderness vs. cruelty, awe vs. contempt. The character reveals who they are by what they pick.
- Approach / method — what they'll do next differs, even if it lands in the same place (get past the guard by force vs. by persuasion).
- Tone / register — clinical vs. poetic, defiant vs. resigned.
- What they notice — each option fixes on a different facet of the scene.
When branches must reconverge, let the choice still leave a trace: a changed line later, a remembered stance, or a different transition into the shared beat. Reconvergence is fine; an invisible choice is not.
Quick self-check before committing a [Dn]: if I swapped two options, would the
player notice a different meaning, mood, or consequence? If not, rewrite or cut
until each option is a genuinely distinct stance.
Actions
- Actions are meant to be reusable. Ask the user before adding a new one.
- Parameters are usually key-value pairs separated by colons; keep the syntax consistent with existing cases.
- A scene normally has at least one
SceneHandleraction — an action designed to be called via events and triggers.
Comments
// line comment— to end of line./* block comment */— may span lines.
Both are stripped before parsing, so they never affect entry text or hashes.
Anchors and references (tool-managed — never hand-write)
{#guid:…}— a stable per-entry ID the tool injects so references survive renumbering.{#dst:<guid>/<hash>}— a resolved cross-script destination (written byresolve-links).- A bare 32-char hex hash gets annotated
/*Scene Code*/(or/*MISSING*/if the target is gone). Leave these to the tool.
Meta files
Each .vgs has a <file>.vgs.meta (JSON) holding scene key/name and per-entry
records (currentCode, stable hash, visible lines, and for talks the
character/directedTo). Never hand-edit .vgs.meta — edit the .vgs and
reconcile.
Tooling: the vgs CLI (how to "reconcile")
Do not write your own parser or fix scripts, and never hand-edit .vgs.meta.
All .meta generation, entry renumbering, decision-text filling, cross-script
link resolution, and validation are owned by the bundled vgs CLI. Drive it.
What "reconcile" means
Reconcile = regenerate a script's <file>.vgs.meta so it matches the .vgs
source. One pass:
- Parses the script into entry groups (
[A] [T] [D] [C]with numeric sublines). - Preserves stable per-entry IDs by matching, in order: a
{#guid:…}anchor → the exact entry code → a content hash; a new ID is minted only when nothing matches. This is why renumbering a scene does not break references to it. - Auto-fills decision options that reference a talk via
=(Tn-1). - Annotates cross-script hash references (
/*Scene Code*/or/*MISSING*/). - Writes/updates
<file>.vgs.meta, and may lightly rewrite the.vgsitself (inject{#guid:…}anchors, normalize sublines to numeric).
Workflow is always: edit the .vgs, then reconcile to regenerate the .meta.
How to run it
In the IDE (JetBrains or VS Code, identical): right-click the .vgs (or a
folder, for the project-wide actions) → VgScript → Fix + Reconcile (or
Fix + Reconcile All (Scenes.txt) for the whole project).
From a terminal: vgs is the compiled binary the plugin drives. It is
resolved in this order: the explicit path in Settings → Tools → VgScript
(VS Code: vgscript.rustBinaryPath), the copy bundled inside the plugin, then
core/tool/target/{release,debug}/vgs[.exe] under the open project, then PATH.
The bundled copy is compiled for one platform per release, so on any other OS put
a vgs on your PATH or set the explicit path.
| Goal | Command (alias) |
|---|---|
Reconcile one script (renumber, fill, (re)write its .meta) | vgs fix <file.vgs> |
Reconcile every scene, in Scenes.txt order | vgs fix-all [dir] |
| Find mistakes in one script | vgs find-mistakes <file.vgs> (vgs validate) |
| Find mistakes across all scenes | vgs check-all [dir] |
Resolve cross-script ->(Sx-Code) links to GUIDs | vgs resolve-links <dir> |
Reorder scenes from Scenes.txt (add --rename-files to rename to S<n>_<Name>.vgs) | vgs reorder-scenes <dir> [--dry-run] [--rename-files] |
[dir] defaults to the current directory; pass the folder containing
Scenes.txt. vgs fix is the editor's "Fix + Reconcile" (it enumerates, then
reconciles with subline normalization) — prefer it over the lower-level
enumerate / reconcile / batch subcommands. Run vgs --help,
vgs <command> --help, or vgs help-all for the full spec.
Typical workflow after writing or editing a scene
vgs fix <file.vgs>— normalize entries and (re)generate<file>.vgs.meta.vgs find-mistakes <file.vgs>— confirm it is clean.
For a whole-project pass use vgs fix-all then vgs check-all.
Exit codes (branch on these instead of parsing output)
0— success / no problems found.1— validation problems found (find-mistakes,check-all).2— the script orScenes.txtcould not be found.
Golden rules for agents
- Edit
.vgs, never.vgs.meta— letvgs fixregenerate the sidecar. - Don't reimplement the tool — no hand-rolled parsers or "fix" scripts.
- Reconcile, then validate after any edit (
vgs fix→vgs find-mistakes). - Ask before adding a new action; reuse existing ones.
- Keep style: descriptive action names, readable indentation, consistent tag casing, no trailing spaces on tag lines.
- Make decisions genuine — distinct in kind, not synonyms (see above).