skald/docs/authors.md
Cobb Hayes 346cea515d Public-flip audit: env-driven paths, scrub audit-ticket prefixes, terser README
Lucy bind paths + LAN host pins replaced with env defaults. Repository URLs
→ git.sulkta.com. Audit-changelog scaffolding stripped from inline comments
(technical reasoning preserved). README sheds marketing scaffolding. AI-speak
in load-bearing prompts/SOULs left alone — that IS the product.
2026-05-27 11:42:58 -07:00

5.3 KiB

Authors as personas with souls

Each story has a named author with a soul. The author's voice bleeds through every generation pass — not as instruction stapled to the prompt, but as the substrate the prose grows from. The reader should feel "a person wrote this."

Authors have memory across their corpus when the per-story cross_story_memory toggle is on. An author writing a Chernobyl piece can quietly echo a phrase from an earlier mining-strike story. Default is off — most stories stand alone.

Schema

Authors live in the database, not on disk. The soul is a markdown blob in author_revisions.soul. Authors are immutable; new soul revisions create a new author_revisions row marked is_current and the previous one is demoted.

CREATE TABLE authors (
    id              UUID PRIMARY KEY,
    slug            TEXT NOT NULL UNIQUE,
    display_name    TEXT NOT NULL,
    persona_tagline TEXT,
    model           TEXT NOT NULL DEFAULT 'opus',
    is_synthetic    BOOLEAN NOT NULL DEFAULT true,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE author_revisions (
    id              UUID PRIMARY KEY,
    author_id       UUID NOT NULL REFERENCES authors(id),
    n               INT NOT NULL,
    soul            TEXT NOT NULL,
    system_template TEXT,
    tools           TEXT[] NOT NULL DEFAULT '{}',
    note            TEXT,
    is_current      BOOLEAN NOT NULL DEFAULT false,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE stories
    ADD COLUMN author_id           UUID REFERENCES authors(id),
    ADD COLUMN author_revision_id  UUID REFERENCES author_revisions(id),
    ADD COLUMN cross_story_memory  BOOLEAN NOT NULL DEFAULT false;

CREATE TABLE author_corpus (
    author_id   UUID NOT NULL REFERENCES authors(id) ON DELETE CASCADE,
    story_id    UUID NOT NULL REFERENCES stories(id) ON DELETE CASCADE,
    role        TEXT NOT NULL CHECK (role IN ('authored', 'read')),
    added_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (author_id, story_id)
);

Per-pass author roles

  • gen — full author voice. They are writing.
  • cleanup — full author voice. Polishing their own draft, not a neutral editor.
  • rewrite — full author voice. Re-authoring another hand's prose; canon preserved, prose reworked.
  • dedup — full author voice. Surgical fix of audit-flagged repetitions only.
  • narrate_prep — author voice if bound; the author's beat placement carries.
  • audit / prose_audit — neutral. The audit checks the author's work with fresh eyes; no author bound.
  • summarize — neutral. Continuity utility, not prose.

When an author is bound, the soul replaces the model's base system prompt (SystemMode::Replace). Without an author, a neutral house scaffold is appended (SystemMode::Append).

Soul template

Following the SOUL.md shape, recalibrated for authorial identity. Free-form prose under each section.

# Author: {{display_name}}

_Tagline: {{persona_tagline}}_

## Voice

Sentence rhythm. Vocabulary register. Paragraph length tendencies.
Dialogue density. Punctuation habits (dashes, semicolons, sentence
fragments). What your prose SOUNDS like read aloud.

## Worldview

What you believe about people. Power. Money. Labor. Cities. Nature.
What makes you angry. What makes you tender. Whose side you're on
in the implicit moral architecture of any scene.

## Specifics over abstractions

The concrete details you reach for. The five senses you favor.
Smells? Cold? Texture of cloth? Sound of machines?

## Pet peeves

Words you refuse to write. Tropes you avoid. Sentimentalities you
gut.

## Sense of humor

Dry? Dark? Absent? Bitter? Self-deprecating? Where in a paragraph
does humor live — end of a sentence, mid-clause, or never?

## Biography (real or invented)

A few biographical facts that EXPLAIN the voice — the formative
cuts, not a CV.

## Anchor authors

Living or dead authors the prose draws from. Useful for the model
to triangulate voice.

## Do / Don't

Concrete prose moves to reach for and to avoid.

The default scaffold (DEFAULT_AUTHOR_SCAFFOLD in skald-core::forge) wraps the soul in a system prompt that:

  • declares the model IS the author, not playing one
  • pins canon as non-negotiable (names, dates, established events)
  • forbids preamble, meta-commentary, fourth-wall breaks
  • substitutes the per-pass directive (GEN_DIRECTIVE, CLEANUP_DIRECTIVE, REWRITE_DIRECTIVE, DEDUP_DIRECTIVE, NARRATE_PREP_DIRECTIVE)

A per-author system_template overrides the default scaffold when set; otherwise the default is used.

Cross-story memory

When stories.cross_story_memory = true, the continuation context pulls characters / canon_facts / passages from every story the author has authored or marked-read, not just the parent chain.

To keep token budget sane, cross-corpus pulls are summary-only by default. Embeddings-similarity (once wired) can surface direct callbacks.

Seeding an author

skald authors seed \
    --slug orson-black \
    --display-name "Orson Black" \
    --tagline "Orwell but more rebel and pissed off" \
    --file seeds/authors/orson-black.md

This creates the author + first revision (or adds a new revision to an existing author, which becomes current).