The database¶
One Postgres container per machine, chalk-db, holds the telemetry of
every run and the lessons from office hours. The code is in lib/db.sh;
the schema is share/schema.sql.
The container¶
| Image | pgvector/pgvector:pg17 (Postgres 17 with pgvector) |
| Volume | chalk-db-data-17 |
| Reached by | docker exec chalk-db psql … only. No port is published, and the password is random and never leaves the container |
| Started by | The first run, chalk db up, or any command that needs it; one chalk at a time creates it, under a lock |
| Schema | share/schema.sql, applied idempotently every time the database is brought up, so a new column is added in place |
SQL is passed on stdin, with values as psql -v name=value and
referenced as :'name', so psql does the quoting (db_sql).
Tables¶
runs: one row per agent call.
| Column | Meaning |
|---|---|
repo, ticket, branch, loop |
Where the call happened; loop is 0 for calls outside the loop |
kind |
continue, retry, fix-review, spec-check, review or distill |
agent_status |
Loops: ok, blocked or the CLI's error. Checks: pass, fail or none |
rubric_exit, progressed |
The rubric's exit code; whether the call moved the ticket forward |
model, prompts |
The model that answered; a short hash of the prompt set in use |
cost_usd, budget_usd, duration_s |
What it cost, its cap and how long it took |
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, turns |
Usage |
denials, lessons |
Actions refused by permission checks; lessons given in the prompt |
run_id |
One chalk run: TICKET-EPOCHSECONDS |
tests_hash, failing, first_error, tree_id, verdict |
The loop's fingerprint and verdict, when CHALK_FP_RULES is not off |
failing_tests |
The failing test IDs behind tests_hash, one per line and sorted, the first 100 only; NULL when unknown. The report card's "Which tests keep failing?" reads them |
fp_rules |
The CHALK_FP_RULES the call ran under (off, shadow or on); NULL for calls recorded before it was, when shadow was the default. The verdict ledger counts only shadow-mode runs |
events: ticket milestones: detention, submitted,
spec_blocked, or ready (done and reviewed, but not submitted because
CHALK_AUTO_MR is off). The report card shows a ticket's latest one as
its state; chalk status does too, unless an agent call came after it.
lessons: one row per detention. signature is the failure;
resolution is the engineer's office-hours note, and the record of
human intervention; lesson is that note distilled into a rule;
resolved_by and resolved_at say who and when. run_id,
fingerprint and first_error link the lesson to the run and failure it
came from. scope is where the lesson is recalled: repo only in its
own repository, general anywhere; NULL, for lessons from before scopes
or when nothing was distilled, is recalled anywhere. memory_synced_at is no longer written: it belonged to
Hindsight, and is kept so existing databases need no migration.
On Postgres 17, embedding (vector(384)) holds the lesson's embedding
from chalk-embed (BAAI/bge-small-en-v1.5), written when office hours
resolves it. A Postgres 16 database has no such column: the schema adds
it only where pgvector is installed.
The trigram index on signature, the index on (repo, fingerprint) and
the HNSW index on embedding (vector_cosine_ops, for <=>) serve
lesson recall. The semantic query
sets hnsw.ef_search to 100; on a small table the planner reads every
row, which is exact, and the index takes over as the table grows.
decisions: one row per question put to the
decider, or per call that got no
answer.
| Column | Meaning |
|---|---|
call_id |
The runs row of the loop the decision was taken in |
run_id |
The run |
kind |
stuck (is the loop stuck?) or rerank (does a lesson apply?) |
question, lesson_id |
What was asked; for rerank, about which lesson |
answer, confidence, threshold |
yes or no, the confidence from 0 to 1, and CHALK_DECIDER_THRESHOLD at the time; NULL answer when there was none |
error |
Why there was no answer: unreachable, starting, timeout, budget, busy, auth, rejected, server, invalid or version |
mode, acted |
shadow or on, as the decision was taken (on is recorded as shadow while it is held to shadow); whether it changed the run |
latency_ms, model |
How long it took; the model that answered, with the revision chalk decider up resolved |
url |
CHALK_DECIDER_URL, without credentials. With model, the provider the calibration gate judges. Rows from before it was recorded get the local service's URL when their model carries a revision, which only the local service's does |
db_decider_calibration (in lib/db.sh) reads what the
calibration gate
judges each provider by: for every confidence at which it said "stuck" in
shadow mode, how many runs that answer would have stopped, how many of
them it was right about, and how many are not settled yet.
decider_calibration_judge (in lib/decider.sh) applies the gate's rule
to them, for runs, chalk doctor, chalk decider status and the report
card alike.
The report card query¶
share/dashboard.sql returns everything the report card shows as one JSON
document, for a window of days. lib/dashboard.sh puts it into
share/dashboard.html in place of the /*CHALK_DATA*/ line, escaping
</ so no stored text can close the page's script tag. A loop is a call
of kind continue, retry or fix-review, in the query and in
chalk status alike.
Moving to Postgres 17¶
Installs from before v0.6.0 have chalk-db on postgres:16-alpine with
the volume chalk-db-data. They keep working, with a warning at most
once a day (chalk doctor reports it every time), until
chalk db upgrade, which:
- refuses while runs are active;
- dumps the old database with
pg_dumpallinto~/.local/state/chalk/db-upgrade/; - stops the old container and renames it
chalk-db-16; - creates a new
chalk-dbon Postgres 17 with the new volume, restores the dump and compares the row counts ofruns,eventsandlessons; - on any failure, removes the new container and puts the old one back as it was.
The old container and volume stay until chalk db upgrade --cleanup.
CI tests a real upgrade, failures and rollbacks included, with
scripts/ci-db-upgrade.sh (see Testing).
Creating, starting and upgrading the container are serialised by a
machine-wide lock: flock where it exists, otherwise a directory made
with mkdir, whose holder writes its pid. A lock left by a process that
died is cleared by the next process that wants it, one waiter at a time,
so two cannot both take it.
The CI audit table¶
share/ci-audit-schema.sql is a separate, optional table in a database
your organisation runs, written by the
gates, not
by chalk.