Voice home file formats
The voice home is a private folder, ideally a private git repo linked to a private GitHub repo. Git keeps the history, so there is no version field in any file. To undo a change, revert the commit. vf.py setup creates it, vf.py sync and vf.py save keep it in sync; see setup.md.
Its location is saved per computer in ~/.config/voice-forge/config.json:
{ "home": "/Users/me/.voice-forge", "verified_private": ["https://github.com/me/voice-forge-private"] }
verified_private lists remotes confirmed private on this computer. vf.py pushes only to those, or to a GitHub repo it can check is private.
<voice home>/
base.md the few rules true in every profile
profiles/<name>/
rules.md the brief and the approved rules, in the ten section schema
rules.migrated.md only during a migration from the old layout; see migrate.md
rules.update.md only while bringing in changes from the profile it was derived from; see derive.md
lessons.md candidate rules waiting for evidence or approval
topics.md topics for training rounds
rounds/ one file per training or learning round
analyses/ one file per analysis of past texts
samples/ short examples the user confirmed as their voice
holdout/ texts for tests only; never read them
evals/ test results, blind comparisons and fidelity-log.md
feedback.md problems with the skill, one dated line each, under "## Log"
voice-forge.json settings shared by all computers, for example {"auto_push": true}
Profile names are lowercase with a language suffix: economics-en, explainer-es. default-en and default-es are the fallback profiles for everyday text.
Profiles are independent. An agent loads base.md and one profile, never two profiles at once.
Keep the formats below exactly. scripts/vf.py parses them.
rules.md
rules.md is the profile: the brief and every approved rule. It is the one source of truth for writers and reviewers. Evidence lives elsewhere (rounds, analyses, lessons); rules only cite it by ID.
A nonfiction profile follows the schema in schema-nonfiction.md: the ten sections below, in this order, with these exact headings. vf.py validate --profile <name> checks it. A file that breaks the format fails validation, even if its rules read well.
---
profile: economics-en
type: nonfiction # nonfiction, fiction or unclassified
schema: nf-1
language: en
register: academic
audience: economists, referees and editors
---
# economics-en
## 1. Identity and context
- Language and register: English, academic
- Genre and document types: journal articles and working papers
- Purpose: report and defend empirical findings in development economics
- Readers: economists, referees and editors
- Field and venues: development economics journals
- Conventions: author year citations; methods in passive voice are fine
- Topics: poverty, inequality, household surveys
- Scope: research papers and reports; not emails or blog posts
## 2. Document architecture
Status: established
- R03 State the main finding in the first paragraph of the introduction.
- strength: preferred
- kind: constraint
- applies: introductions of papers and reports
- except: none
- check: Read the first paragraph of the introduction. PASS if it states the main finding with its direction.
- evidence: 20261005-1142, 20261007-0930, 20261009-1015
- status: approved 2026-10-09
## 3. Argumentation and reasoning
Status: unknown
## 4. Paragraphs
Status: unknown
## 5. Discourse and coherence
Status: unknown
## 6. Sentences and rhythm
Status: established
- R01 Keep most sentences short; use a long one only to hold a list or a chain of steps.
- strength: preferred
- kind: tendency
- applies: all prose
- except: quoted text
- check: Look at the text as a whole. PASS if long sentences are rare and each one holds a list or a chain of steps.
- auto: share-sentences-over 30 15
- evidence: 20261005-01, 20261007-02, 20261009-01
- status: approved 2026-10-09
- from: L003
## 7. Vocabulary and register
Status: unknown
## 8. Voice and metadiscourse
Status: unknown
## 9. Evidence and sources
Status: unknown
## 10. Anti-patterns and constraints
Status: established
- N01 Never use em dashes or en dashes as punctuation.
- strength: prohibited
- kind: constraint
- applies: all prose
- except: number ranges such as 1990–2000
- check: PASS if no dash is used as punctuation.
- auto: no-dashes
- evidence: stated 2026-10-03
- status: approved 2026-10-03
## Notes
Free text the user wrote. Not rules.
Format rules, all checked by vf.py validate:
- Front matter has
profile(the folder name),type,schema: nf-1andlanguage. - Headings are exactly the ten above, in order, then optionally
## Notes. No other##headings. - Section 1 has one line per field:
Language and register,Genre and document types,Purpose,Readers,Field and venues,Conventions,Scope.Topicsis optional. An empty field is a warning, not an error. It has no status line and no rules. - Sections 2 to 10 start with
Status: established,Status: unknownorStatus: not_applicable.not_applicableneeds aReason:line under it.establishedneeds one or more rules; the others need none. - Rules have every field in the rule contract:
strength,kind,applies,except,check,evidence,status.autoandfromare optional. Allowed values and meanings are in schema-nonfiction.md. - IDs:
R01for profile rules,N01for "never" rules, which must bestrength: prohibited. Each ID appears once. Never reuse an ID, even after a rule is dropped. - No
TODOin any rule field.vf.py migrateleaves TODO markers for the user to fill. - No
adaptfield.vf.py deriveadds one to each copied rule; delete it once the rule is adapted and approved. - Optional fields:
auto,fromandtransfer(any,adapt,en-only,es-only). - Retired IDs:
vf.py rule dropremoves a rule and adds- Retired R07 (date): reason. Was: ...to Notes.vf.py next-idnever hands out an ID that appears anywhere in the profile, its rounds or its lessons. - Each rule is one checkable action. "Be clear" is not a rule. "Put the claim in the first sentence of the paragraph" is.
evidencelists round IDs,analysis:<id>,stated YYYY-MM-DDwhen the user stated the rule, oradopted YYYY-MM-DD (analysis:<id>)for a suggestion the user adopted.
Derived profiles add two front matter fields: derived_from: explainer-es@3f2a9c1 (the source and its version) and stage: provisional. A copied rule's evidence reads transferred from explainer-es R07@1a2b3c4d, where the last part is a fingerprint of the source rule, so vf.py derive --update can see later changes. Not copied rules are listed in Notes as - not transferred: explainer-es R04 (es-only, ...): .... See derive.md.
Profiles with type: fiction or type: unclassified keep any layout; validation checks only their rule fields. A file in the older layout (## Brief, ## Rules, ## Never) fails validation with a hint to run vf.py migrate.
vf.py export --profile <name> prints the profile as JSON for scripts. The JSON is always generated from rules.md; never edit or keep it as a second source.
base.md
base.md holds the few rules true in every profile. It uses the same rule contract and the same section headings, but only the sections that hold rules, and no status lines or section 1.
---
profile: base
type: base
schema: nf-1
language: any
---
# Base voice
## 10. Anti-patterns and constraints
- B01 Never use em dashes or en dashes as punctuation.
- strength: prohibited
- kind: constraint
- applies: all prose
- except: number ranges
- check: PASS if no dash is used as punctuation.
- auto: no-dashes
- evidence: stated 2026-10-03
- status: approved 2026-10-03
- B02 Use «» for every first level quotation.
- strength: mandatory
- kind: constraint
- applies: all prose
- except: quotes inside a quote, which use “”
- check: PASS if every first level quotation uses «».
- auto: angle-quotes
- transfer: es-only
- evidence: stated 2026-10-03
- status: approved 2026-10-03
## Notes
transfer: es-only makes B02 apply to Spanish profiles only. IDs in base.md are B01 (or N01 in files made before version 2.7). In review reports, base rules are written base:B01 so they never clash with profile IDs.
lessons.md
# Lessons: economics-en
## L003 Put the claim first, then the data
- status: candidate
- category: paragraphs
- rule: Open the paragraph with the claim. Give the number after it.
- strength: preferred
- kind: tendency
- applies: analytical body paragraphs
- except: paragraphs that describe data or methods
- check: In each applicable paragraph, PASS if the claim comes before the first number.
- evidence: 20261005-01 (poverty-lines), 20261007-02 (ppp-revisions), analysis:20261008-ppp-paper
- against:
- examples:
- 20261005-01: "Over the past decades, poverty..." → "Extreme poverty fell by half..."
statusis one ofcandidate,promoted,rejected.evidenceholds unique round IDs, each with its topic slug, andanalysis:<id>entries from Analyze mode. Never list the same round twice.againstlists training rounds where the user did the opposite. Counterexamples found by Analyze mode stay in the analysis record (and may shapeexcept); they never go inagainst.categoryis one of the ten section slugs in schema-nonfiction.md: identity, architecture, argument, paragraphs, discourse, sentences, vocabulary, voice, evidence, constraints. Lessons written before version 2.7 may use older names (tone, rhythm, openings...);vf.pymaps them, and migration asks about unclear ones.- Optional fields
strength,kind,applies,except,checkandautohold the draft rule contract. At promotion they are copied intorules.mdafter the user approves the whole block. - A lesson is ready to promote when its rounds cover 2 or more topics and it has either 3 or more rounds, or 2 rounds plus an analysis. Analysis alone never promotes a lesson, because old or coauthored texts may not reflect the user's voice today.
python3 scripts/vf.py statuschecks this. It counts analysis IDs, not the texts behind them: one analysis is enough, and more analyses never replace rounds or topics.
topics.md
# Topics: economics-en
- poverty-lines: Why the international poverty line moved from $2.15 to $3.00
- survey-weights: What survey weights do and when they mislead
One topic per line: a slug, a colon, a short description. The user can add or remove topics at any time.
rounds/
File name: <id>-<topic-slug>.md. The ID is the date and time, so two computers never pick the same one: 20261009-1142 (or 20261009-114233 if that minute is taken). Older rounds may use a two digit counter, 20261005-01; both count. Get the next ID with python3 scripts/vf.py next-round --profile <name>.
---
id: 20261009-1142
profile: economics-en
mode: train # train or learn
topic: poverty-lines
focus: mixed # mixed, openings, closings, transitions, data, hard or own
language: en
source: paper.md@3f2a9c1e0b7d..8d41aa07c2e9 # learn rounds from git only; printed by vf.py pair
---
## Passage
The text the agent wrote (train) or the AI draft (learn).
## Rewrite
The user's version, exactly as given.
## Findings
- NEW [paragraphs] ...
- CONFIRMED R01 ...
- CONTRADICTION R04 ...
## Decisions
- L003 yes (new)
- L001 yes (added evidence)
- "Avoid passive voice" no
samples/
Finished texts the user wrote or fully approved, one per file: <slug>.md.
---
language: es
register: explanatory
topic: sesgo-de-confirmacion
source: blog post, 2025
---
Text.
Rewrites in rounds/ count as samples too. Do not copy them into samples/.
analyses/
File name: <YYYYMMDD>-<slug>.md, where the slug names the main text. This ID is what lessons cite as analysis:<YYYYMMDD>-<slug>.
The record holds the full analysis; the conversation only shows a summary. It is written for people and agents: no script parses it, and vf.py validate checks profiles, not analysis records. vf.py holdout check reads it as text, and vf.py next-id notices rule IDs mentioned in it. Item IDs use F, P, Q and S, never R, N, B or L. Sections, in this order:
---
id: 20261016-stats-book
profile: stats-explainer-en
---
## Sources
- ch1-averages.md and ch2-surveys.md: chapters 1 and 2 of one book project; dates unknown; written alone; still likes them: yes
- magazine-2019.md: 2019, written alone, the magazine required endnotes; still likes it: mixed
- column-2024.md: 2024, written alone; still likes it: yes
- Reanalysis: none. (Write here when a source was analyzed before, or two files are versions of one text.)
## Coverage
- ch1-averages.md: read in full, including Table 1 and its caption.
- magazine-2019.md: read in full; the pull quote image could not be read.
## Measurements
Output of `measure.py stats`, per source text: sentence and paragraph lengths, lexical diversity, stance markers.
## Findings
- F01 [architecture] stable: gives an everyday analogy before the technical term
- where: ch1 §2 "Think of a queue at the bank..."; magazine-2019 ¶3; column-2024 ¶2
- function: the reader has a picture before the label
- variation: longer analogies in the book, one sentence in the column
- conditions: the first time a technical term appears
- exceptions: column-2024 glossary box gives definitions first (a list of terms, no explanation)
- alternatives: not a venue requirement; the magazine's other authors define first
- scope: time (2019 and 2024; the book is undated)
- profile: refines R02 (adds the order: analogy, then term)
- F02 [paragraphs] unsupported: three rhetorical questions in a row
- where: ch2 ¶4, ¶9, ¶15; nowhere else
- scope: text
- profile: new; local, so a question (Q01), not a lesson
## Across sources
What each source adds, confirms or contradicts, written once across sources.
## Confirmations
- Confirms R01 in all four sources (opens with a concrete scene).
## Inconsistent passages
- P01 magazine-2019, ¶7, "It may perhaps be argued...": one long, heavily hedged sentence; nothing in its job explains the change.
## Decisions
- P01: someone else shaped it (the editor), 2026-10-16
- F01: keep → L012, wording approved 2026-10-16
- F04: dropped
- Q01: answered: "only in that chapter, on purpose"
- S01: no
## Pending
- F03: unsure
- F05, F06, S02: not shown yet
- Findings use the section slugs and the source labels:
stable(across separate works),stated(the user said it),genre(an external requirement; goes to the brief),coauthor(only in texts others shaped),old-habit(only in older texts),unsupported(too little evidence, including local patterns; never a lesson),suggestion(a field convention the user does not follow yet). - Scope is one of
passage,text,project,works,time. - Profile relation is
confirms,refines,tension,neworcovered by Rxx. - Decisions record what the answer means: a fact,
recognized(with the user's scope),keep → L012only after the user approved the exact wording,dropped,pending,deferred,adopted as R07. Pending items stay listed until decided; nothing disappears. - Records written before version 2.9 have only Sources, Measurements, Findings, Inconsistent paragraphs and Decisions. They stay valid as they are.