On this page

Writing-style store

The writing-style store is Skryba's record of the text its user actually wrote: the passages and preferences tables of its fleet database, with the samples the person rewrites and the harvesters that collect their writing beside them. It is the only durable state in the product that keeps prose rather than a measurement of prose.

Why this store keeps the text

The detector cache deliberately forgets what it scored: it holds (detector, sha256(text)) -> probability and nothing else, because a probability is all it ever has to answer again, and a hash is enough to know the question repeated. That design is correct there and useless here. A hash cannot be trained on. An adapter that writes like a person needs the person's sentences, so this store keeps them, in rows the person reads and exports with skryba writing-style.

Everything else in Skryba is about machine prose: the input is AI text, the candidates are model completions, the judge scores a rewrite. This store is the other half, and only that half.

Location

The schema skryba, or the one SKRYBA_DATABASE_SCHEMA names. There is no store file.

Schema

CREATE TABLE skryba.passages (
  text_hash TEXT PRIMARY KEY,
  source TEXT NOT NULL,
  words BIGINT NOT NULL,
  text TEXT NOT NULL,
  sequence BIGSERIAL NOT NULL,
  kind TEXT NOT NULL DEFAULT 'files'
);

CREATE TABLE skryba.preferences (
  pair_hash TEXT PRIMARY KEY,
  kept TEXT NOT NULL,
  machine TEXT NOT NULL,
  strategy TEXT,
  sequence BIGSERIAL NOT NULL,
  sample TEXT
);

CREATE TABLE skryba.samples (
  id TEXT PRIMARY KEY,
  seed TEXT NOT NULL,
  kind TEXT NOT NULL,
  words BIGINT NOT NULL,
  machine TEXT NOT NULL,
  model TEXT NOT NULL,
  created_at BIGINT NOT NULL,
  sequence BIGSERIAL NOT NULL
);

CREATE TABLE skryba.harvesters (
  name TEXT PRIMARY KEY,
  kind TEXT NOT NULL,
  path TEXT,
  min_words BIGINT NOT NULL,
  max_words BIGINT NOT NULL,
  cursor TEXT,
  last_run_at BIGINT,
  last_added BIGINT,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL
);

CREATE TABLE skryba.harvest_schedule (
  owner TEXT PRIMARY KEY,
  schedule_id TEXT NOT NULL
);

A store an older Skryba wrote gains kind and sample the first time this one opens it; every passage it already held is of kind files, which is where add read it from.

Passages are prose the person wrote, ingested from their own files by writing-style add or by a harvester. source is the path the passage came from, or <kind>:<harvester>/<item> for a harvested one; words is its whitespace word count, and kind is files, messages, notes or transcripts.

Preferences are pairs the person themselves resolved, recorded by writing-style keep: kept is what they kept or wrote, machine is what the model produced for the same passage, strategy is the rewrite strategy that produced it when a rewrite named one, and sample is the sample it rewrote when it rewrote one.

Samples are machine-written passages the person rewrites, written by writing-style samples generate: id is the same zero-terminated digest as text_hash, over the model's text; seed is the text_hash of the passage that set its subject and length, kind and words the seed's, and model the Brama model that wrote it. Harvesters are the places a harvest reads, with the cursor its last run stopped at, and harvest_schedule holds the one Stado schedule id that runs them.

Identity is the text itself

text_hash is a lowercase hexadecimal SHA-256 over the passage text followed by one zero byte; pair_hash is the same digest over the kept text, a zero byte, the machine text, and a zero byte. Both are primary keys written with ON CONFLICT DO NOTHING, which makes re-ingestion cheap and honest: run add again over a directory you edited and it adds what changed and nothing else, counting the rest in skipped. The same hash is why a preference cannot be recorded twice.

The zero byte is a terminator, not decoration: without it the pair ("ab", "c") and the pair ("a", "bc") would hash identically and the second one recorded would be silently dropped as a duplicate. It also means this digest is not the detector cache's text_hash, which is a plain SHA-256 over the exact UTF-8 input — the two tables answer different questions and their keys are not interchangeable.

Reads are oldest first

passages and preferences are read ORDER BY sequence, the order rows were added, so export --limit N takes the same first N rows before and after the store grows.

Invariants and failures

  • add, keep, export and status call neither Brama nor a detector, so no model or detector credential is read. train reads none either: it spawns the local ster binary.
  • Skryba runs no gradient over these rows itself. It writes the file Ster reads and then calls Ster; see the training boundary.
  • Both texts of a preference are trimmed before storage, and a pair whose sides are equal is refused rather than stored.
  • creating the writing-style tables in …, adding a passage from <SOURCE> to …, keeping a preference in … — the database refused; the cause is Postgres's own sentence. A connection failure is described under skryba database check.

Deleting rows from these tables deletes the person's captured writing and nothing else: their original files are untouched, and no rewrite, score, or policy depends on it.

Maintained as part of the website-owned Skryba documentation corpus.