A prompt is on its twentieth iteration. Is the first version still around? For most tools, the answer depends on whether you happened to back things up. Syzygy 1.2.0 treats prompts and skills as assets, with separate actions for saving work in progress and publishing a version. This piece unpacks five design decisions behind the prompts and skills workbench, each answering a specific way things rot.
Save a draft, publish a version, restore working content
Storage is one TOML file per prompt, capped at 1 MiB, with identity taken from the document's name field. Discovery accepts any *.toml at any depth under the root, so moving or renaming prompt files in your file manager is a legal operation. That decision forces one hard rule: a resource's identity is its user-visible name, never a filesystem path.
The prompts directory has a git layer, with .git sitting next to your TOML files. Saving writes the working draft atomically. Explicit publication allocates the next published version and creates a commit with a message such as "publish prompt: name (v7)". A failed commit rolls back the publication change in the working document. Creating a draft or moving a prompt to trash does not stand in for publication.
Legacy data gets the same treatment. Earlier revisions were inlined in each TOML as a history array; on startup, each file's old versions are rewritten into real git commits in chronological order ("legacy v1: name"), followed by the current version. Migration is idempotent and skips files that already have git history.
The implementation chose gitoxide (gix 0.87) over libgit2 (git2-rs), and the reason is a packaging ledger. Syzygy ships to five targets: macOS (notarized, with auto-update), Windows, Linux (signed AppImage), iOS (in App Store review), and Android. git2 drags a C library into all five build and signing pipelines; gix is pure Rust and compiles along with the workspace toolchain. The workspace declares it with default-features = false plus a short list of features (blob-diff, revision, status), skipping networking and worktree checkout entirely. Prompt versioning needs blobs, trees, commits, and line diffs, and gix covers those.
The history interface exposes three commands: prompt_git_history_v1 lists commits for a file or the whole library, prompt_git_diff_v1 produces a unified diff, and prompt_git_restore_v1 atomically writes a chosen commit's content back. Restoring changes the working content without creating a new commit. Inspect the result, continue editing if needed, then explicitly publish if you want a new recorded version.
Published versions and working drafts have distinct roles. Editing the body, variables, or attachments and saving does not allocate a published version. Publication advances the publication sequence and records the version in Git; deleting a published version does not reuse its number or discard the working draft. Git history records publications rather than every draft save.

Save as often as needed while editing, and publish the milestones you want to keep in version history.
Attachments are content-addressed; references travel inside the document
Prompts carry images and files. The obvious design is a per-prompt attachments directory, and Syzygy explicitly declines it: a per-prompt directory needs a name, and the name would either be the prompt's name (which can be strings like ../.., and distinct names can sanitize to the same directory) or a derivative of it (broken by rename). Remove that layer and the whole problem disappears.
Instead, attachment bytes are stored in one flat shared directory, <data_dir>/prompt-assets/<hash>.<ext>, and each prompt records which attachments it uses in its own document, referencing them from the body as {{@attachment name}}. Three properties fall out.
First, cross-prompt deduplication: one screenshot referenced by ten prompts is one copy on disk, and hand-copying a prompt file to "save as" naturally shares the same assets. Second, rename immunity: renaming a prompt moves no attachments, and renaming an attachment force-rewrites the references in the body. The link lives inside the document, not on a path, so however you shuffle prompt files in a file manager, the attachments follow. Third, reference-reclaiming deletion: permanent delete reclaims only bytes that no prompt references anymore. The prompt catalog is memory-resident, so the pre-delete scan is an in-memory pass, and it has to actually happen, because skipping it deletes bytes another prompt still uses.
The trade-offs are stated plainly: a hash directory cannot be browsed per prompt in a file manager (the attachment entry keeps a "reveal in file manager" action, and export materializes readable filenames); the manifest does not enter version history, so an old version referencing a deleted attachment shows "reference no longer valid" rather than fabricating a replacement. Per-attachment cap is 64 MiB, and same-name uploads auto-deduplicate (image.png becomes image-2.png), because pasted screenshots keep producing identical names. Cross-device sharing goes through a verified ZIP round-trip: per-file CRC and BLAKE3 checks, rejection of symlinks, encrypted entries, and path traversal, limits of 128 attachments and 256 MiB total expanded size, and create-only import that never overwrites an existing prompt.
If you share prompt packs with colleagues, the round-trip guarantees they receive the complete document plus every attachment, and importing creates copies rather than stomping their same-named prompts.
Variables have five tiers, and every value carries its provenance
Where does {{name}} in a prompt body get its value? Syzygy's resolver merges five tiers by precedence: asset defaults lowest, then workspace defaults, the selected environment's values, host overrides, and finally explicit per-call values. Above all five sits a read-only system context assembled from the actual context, transcript, date, time, language, and locale, and any tier attempting to override it fails loudly. That hole is welded shut.
Every resolved value carries a provenance tag. When you are debugging why a wrong team name went out, you can see whether the value came from a workspace default or an environment override instead of excavating five config layers. A variable declared required that resolves to nothing at any tier fails the parse outright, with an error naming the variable; rendering never continues on an empty string.
Value types are enforced too: a secret is its own value type, and the type system guarantees it cannot be concatenated into rendering contexts as an ordinary string. Preview and requests share one rendering path: render against current bindings first, then run the privacy check on the rendered result. The source text may be innocuous while the resolved value is an email address, so the preview must treat it as sensitive. The Source view always shows the raw text, braces included, uninterpolated; Preview and Source are two views, and switching to Source is not an unlock.
Variable names have a grammar: 1 to 64 bytes of ASCII identifier, starting with a letter or underscore. Substitution runs exactly once, so {{other}} inside a variable's value is never recursively expanded, and missing variables or unclosed braces stay in the text as written. Environment-tier values come from environments configured in the app; the resolver cannot read arbitrary OS environment variables and executes no code to produce a value.
If you maintain a shared prompt library for a team, the tier structure keeps "the library's defaults" and "my personal environment overrides" in their own lanes, and editing a default never tramples anyone's local override.
The slash menu encodes consequences into the prefix
Typing / in the AI composer opens a menu with two kinds of entries: builtin commands (/clear, /search, /thinking, /tools, /help, which execute on pick) and three qualifier prefixes: /prompt:, /skill:, /file:.
The design lives in the prefix. After /prompt:, fuzzy matching runs only over the prompt pool, and picking a result swaps the prompt body into the composer. /skill: filters skills, and picking one inserts a skill reference token, with the activation determined separately from the message text. /file: inserts a file. The prefix encodes what picking does: before pressing enter, you know whether you are inserting a full text, a reference, or a file path. The menu never explains behavior in copy; the prefix is the behavior.
Builtins lead the list on purpose: one keystroke reaches /clear, and qualifiers follow. The grammar is deliberately tight: the composer value must remain a single token, and a space or newline closes the menu, because a space means the user has started writing prose. Filter text after the qualifier is free-form fuzzy input where a title match always outranks a description-only match. A full-width colon is equivalent to an ASCII one, qualifiers accept case-insensitive aliases, and adding a new asset kind is a registry entry, not new grammar.

If you live on the keyboard, the practical effect is that /clear is one keystroke away and three characters of /pro are usually enough to locate a prompt by fuzzy title.
The skills registry: dedup stops at presentation, files belong to disk
A skill is a package with a SKILL.md at its entrance. The registry is driven by a source list: you add directory sources (path templates resolving home, environment variables, and app-data locations), toggle, remove, and reorder them, and a filesystem watcher keeps catalog snapshots in step with external edits.
Scanning is restrained. A normal scan reads only SKILL.md; file trees and attachments load lazily. Budgets are hardcoded: SKILL.md capped at 256 KiB, editable files at 1 MiB, scan depth 8, and at most 10,000 directories and 2,000 packages per source. Scanning is not takeover: indexed directories never become app-reclaimable space, and removing a source deletes nothing.
Deduplication happens only at the presentation layer: when two sources contain a skill whose SKILL.md bytes are identical, the list deduplicates on the raw-byte SHA-256, keeps the first valid instance, and records the duplicate sources. Packages with the same name but different content are all kept; identity, content version, package version, and display name are four separate concepts. The boundary means the app never pretends to know that two same-named packages are "really one thing." Different bytes, different assets.
Activation binds to content. Every valid catalog entry carries an instructionDigest, a digest of the skill's instruction content, and an AI activation carries that digest, tying the request to the bytes that were scanned. When a catalog snapshot goes stale, entries remain inspectable but nothing can become an activation until a refresh produces a fresh snapshot. New skills start from a complete, standard SKILL.md template; frontmatter is parsed with typed keys and serialized back in standard key order with the body untouched. There is no "simplified view" that comments the frontmatter out.

If you collect agent skill packages from GitHub, pointing a source at the directory makes them usable; removing the source leaves the directory untouched; and same-named packages are never silently merged.
Where this ends
Three limits, stated as such. First, prompt git is local history; it does not sync across devices, and prompts with their attachments stay on-machine like clipboard entries. Second, skills are currently instruction packages: the registry covers discovery, dedup, and content binding, while execution authorization and MCP lifecycle are separate boundaries, and the file API does not run skill scripts. Third, native cross-platform file behavior testing is still in progress; the completed verification is 1,066 Rust tests, fmt/check/clippy gates, and Windows GNU/Linux target builds, numbers from the September 23, 2026 implementation record, not a standing guarantee for every platform. One structural fact worth adding: file-change events for prompts, skills, and memory are forwarded by a single event runtime, and the three domains keep their own business rules behind it, sharing the pipe, not ownership.
Everything has a visible entry point: the prompts page manages templates, variables, and history; the skills page manages sources and packages; the slash menu in the AI composer moves assets into conversations. Prompts can be tagged, carry attachments, and export as verified ZIPs, which turns "I have a stash of prompts" from a habit into a portable asset.
The prompts and skills workbench is not a separate product. It is the asset layer of the same AI workspace: the local models piece covers where compute comes from, and this one covers managing what you feed it. The decision records (ADRs 0003, 0010, 0059, 0061) live in the repository.
Frequently asked questions
How is Syzygy's prompt version history implemented?
Each prompt is a TOML file, and the prompts directory itself is a git repository. Saving updates the working draft; explicitly publishing allocates a version and records a git commit through the pure-Rust gitoxide (gix) stack. History and diff commands inspect commits, while restore writes a selected revision back as working content. You can review it before deciding whether to publish again.
Where are prompt attachments stored, and do they waste space?
Attachment bytes live in a flat shared directory, prompt-assets/, named by content hash, with no per-prompt subdirectories. The same screenshot referenced by ten prompts occupies one copy on disk, and permanent deletion reclaims only bytes no prompt references anymore.
Does removing a skill source delete my skill files?
No. Removing a source only takes it out of the registry. No files are deleted, and indexed directories never become app-reclaimable space. Packages with the same name but different content are all kept; deduplication only affects presentation.