"Run an operator-initiated LEGIBILITY CENSUS: an attended first census for a newly-enabled project (run with cost caps and review-before-filing, before the nightly trickle auto-fires an unattended one), or an ad-hoc forced census on demand (e.g. after a big remediation wave). ALWAYS use this skill for: '/census <project-root>', 'run a census', 'run a legibility census', 'first census for <project>', 'force a census now'. Recurring census runs need NO skill — the nightly trickle's evaluate_census_step launches scripts/legibility/census.py automatically when census_trigger fires; this skill covers only the cases where a human kicks it off by hand. NOT in scope: editing scripts/legibility/* code, the nightly trickle, or the census trigger logic itself — this is an operator run-and-verify skill, not a dev skill."
Instal·la
npx skillscat add leodearden/dark-factory/census Instal·la des del registre de SkillsCat.
/census — operator-initiated legibility census
A legibility census (scripts/legibility/census.py) does the following:
- Mines agent session transcripts for confusion sightings, in stratified-random batches, until novelty saturates. The transcripts come from
~/.claude/projectsplus the project'sagent_transcript_roots, matched bycwd_prefixesindocs/legibility/legibility.yaml. - Verifies each novel cluster against the project's current tree. The verifier is the only stage that reads the code.
- Synthesizes the verified clusters into a dated report.
- Updates
docs/legibility/confusion-codebook.yaml. - Files remediation tickets.
What a finding must carry (key, area, anchor, tags, severity, disposition), what a report must pin, and how a run schedules the next are all defined in docs/quality-findings-contract.md. This skill points there and does not restate it.
Statements marked (until Ln) describe today's code and change when that leaf of plans/census-incremental-prd.md lands. The final section lists what changes.
Normally the census runs unattended. Each nightly trickle run ends by evaluating census_trigger.decide_for_project and, on FIRE, launches census.py with the caps from legibility.yaml census.trickle_caps. Run it by hand only in these two cases:
- Attended first census. A first census can auto-fire. When
docs/legibility/census-state.jsonis missing,decide_for_projectanchorslast_census_aton the earliest codebook date and exempts the floor. Condition (c), the novelty spike, needs no anchor. It counts candidates whosefirst_seenfalls in the last 72 h against a threshold of 4, and a running trickle exceeds that on almost every night (until L8a). So a project's first census fires unattended within a night or two of its trickle producing candidates. That run is capped bytrickle_capsbut files tickets for real. Run the first census by hand, before installing the trickle timer, when you want it bounded and reviewed (--dry-run-filing). A project with no trickle data and no codebook does not fire. - Ad-hoc forced census. Run one when you want fresh sightings now, regardless of the trigger's verdict, for example right after a remediation wave.
Both cases run the same command and the same checklists. Only the reason differs, so this is one skill.
Preflight
- Is the trickle healthy? For a project with a running trickle:
Exit 0 means signal is flowing. Any other exit names its own cause. A failed trickle does not block a census, because the census mines transcripts directly, but a forced census on top of a broken trickle is worth knowing about.uv run --directory <factory checkout>/shared python <factory checkout>/scripts/legibility/check_trickle_progress.py <project_id> 3 journalctl --user -u legibility-trickle@<project_id>.service --since "-3 days" | grep -E 'census trigger:|digest|failed' | tail -20 - What would the trigger say? Do not skim the codebook: it is over 2 MB.
It prints FIRE or NO-FIRE and one reason line per condition: days since the last census, tasks landed, the 72 h candidate count, and the floor. It makes one read-onlyPYTHONPATH=<factory checkout>/scripts uv run --directory <factory checkout>/shared python <factory checkout>/scripts/legibility/census_trigger.py evaluate --project-root <target-root>get_statusescall. - Headroom. The run probes this itself (
preflight_headroom, one trickle-model call, which defers the whole run when the account pool is capped), and probes again before verification. Mining usescensus_miner(Sonnet), verificationcensus_verify(Sonnet) and synthesiscensus_synthesis(Fable). Avoid starting an uncapped census while other heavy work holds the pool.
Running
From the factory checkout. <factory checkout> is the dark-factory checkout that ships scripts/legibility/. uv run --directory changes the working directory to shared/, so every path below is absolute. Use --directory, not --project, following the uv run --directory <member> convention in review/briefing.yaml.
uv run --directory <factory checkout>/shared python <factory checkout>/scripts/legibility/census.py \
--project-root <target-root> --force<target-root>is the absolute root of the censused project. It must agree withproject_rootin that project'slegibility.yaml, or the run refuses.--forcebypasses the trigger. Without it, the CLI prints the NO-FIRE reasons and exits 0.--config <path>names a non-defaultlegibility.yaml.--date YYYY-MM-DDstamps the report with another date.--harness-config <path>names thelegibility.yamlof the project that owns harness fix surfaces. It defaults to this checkout's own, which is dark_factory.
Cost-control flags (each optional; omitted means unbounded)
--max-batches Nstops mining after N batches (N ≥ 1). The report marks coverage as partial andstop_reason=capped. (until L1) The run still advanceslast_census_at, and the next window starts there, so capped-away sessions are never mined. To sweep them, rolllast_census_atback first.--max-verify-clusters Nhands the verifier at most N novel clusters, in mining order. The rest merge aspendingcandidates. (until L7) A pending candidate is re-adjudicated only if a later run reproduces its exact title, so in practice it stays pending until someone adjudicates it by hand.--dry-run-filingwrites every would-besubmit_taskpayload toplans/confusion-census-<date>-payloads.json(or…-payloads-2.json, never overwritten) and files nothing. The codebook and census-state still advance, so filing that JSON by hand is the only way those tasks will ever land.
For an attended first census, use all three:
uv run --directory <factory checkout>/shared python <factory checkout>/scripts/legibility/census.py \
--project-root <target-root> --force \
--max-batches 50 --max-verify-clusters 150 --dry-run-filingAgainst an empty codebook, dup_rate only measures "the miner found nothing to match", so saturation cannot bound the run. A bounded first census is a sample, not a sweep. The trickle-launched census passes these same two caps from census.trickle_caps (schema default 50/150; null on a key means uncapped). Flags typed on a manual run are unaffected by that block.
What the run does with verified findings
- Filing is through tickets.
submit_taskreturns atkt_*id. The curator decides create, combine or drop asynchronously, andresolve_ticket(ticket, project_root)returns the task id once it has. Each ticket carriesmetadata.source=legibility_censusandorigin_project_id. - Harness routing. When a cluster's descriptive fields name a harness component, the ticket files into the harness project's tree, with the matched markers under
metadata.x_fix_surface. The rule is inscripts/legibility/filing_policy.py::resolve_target. When the verifier proposed an in-tree remediation, the ticket stays in the observed project. The report lists each cross-project ticket with theproject_rootthatresolve_ticketneeds. - Singleton filing gate (
filing_policy.is_fileable, since 2026-09-27). A verified cluster files only when the verifier proposed an existing in-tree remediation path, or when its title has at leastMIN_UNREMEDIATED_SIGHTINGS(2) distinct-session sightings. Anything else is promoted into the codebook, markedfiling: withheld, and listed under Recorded, Not Filed. A withheld entry files automatically on a later run once its sightings reach 2.
Watch for the final line:
census: done -- report=... filed_tickets=N stop_reason=..., withunresolved_verdicts=Nappended only when the run paid for verdicts that a standing adjudication of the same title overrode.census: deferred -- stage=... unverified_clusters=...when a headroom gate deferred the run. Nothing was persisted, so re-run once capacity returns.
Post-run checklist
- Read the report at
plans/confusion-census-<date>.mdin the censused project. (until L1) A second run on the same date overwrites it, and the earlier one survives only in git history (each run commits its report). Its sections:- Saturation: batch count, stop reason and per-batch
dup_rate. A stop at the two-batch minimum is the common case, and it is not evidence of full coverage. - Verification: present on every run given
--max-verify-clusters, and therefore on every trickle-launched run. The anomaly to look for is the bullet "ALL N … were REJECTED and none survived". It signals a suspected systemic verifier failure. Check the per-clusterverify failedwarnings in the journal before accepting the run. - Unresolved Verdicts: verdicts paid for and dropped against a standing record.
- Origin x Manifestation Matrix: from the verifier's phase stamps.
- Synthesis: prose. (until L10) It is not parsed, and nothing in it is applied. Current reports open the section with the runner's not-applied notice, and the model heads its proposals with
scripts/legibility/census.py::SYNTHESIS_PROPOSALS_HEADING; earlier reports call the same proposals "inputs to the merger", and those were never applied either. To act on one you agree with, put an entry correction or sighting in a §7.3 coding record and runscripts/legibility/codebook.py apply, checking that its printedapplied:stats show it matched or applied (a record reusing the sighting's own session is a silent no-op); promote or reject a candidate through census adjudication; never hand-editdocs/legibility/confusion-codebook.yaml. - Filed Tasks: ticket ids, plus the cross-project list.
- Recorded, Not Filed and Cost.
The report has no per-stratum coverage section.
- Saturation: batch count, stop reason and per-batch
- Resolve the tickets. Call
resolve_ticketfor each id; for cross-project tickets, use the project root the report names. (until L4) The report cannot name task ids, because the curator decides after the report is rendered. Triage or re-prioritise what landed, since every ticket is filed at prioritymedium(until L3). - Check
docs/legibility/census-state.jsonadvanced:last_census_atis this run's date,YYYY-MM-DD(date-only, not a timestamp).last_census_reportnames the new report (written as an absolute path (until L1)).last_census_done_count(until L8b) is three-valued:- an integer matching the project's done-task count: correct.
0on a project that has done tasks: wrong. Theget_statusescall failed and a fabricated baseline was persisted (task 3291). Repair it with a datedget_statusesreadback and investigate the endpoint.0on a project with no done tasks: legitimate.null: the count was unobservable. The run stands, and the tasks-landed condition fails safe until the next successful census.
- If you ran
--dry-run-filing, file the payload JSON by hand. A later census will not re-file it, because those confusions now code as matches and the window has moved.
Anchor seeding — when an equivalent manual survey already exists
To have the trigger treat an earlier hand-run legibility survey as the first census, seed the state file directly:
{
"last_census_at": "<survey-date>",
"last_census_report": "<repo-relative path to the survey report>",
"last_census_done_count": <done-task count at survey time, from get_statuses>
}last_census_at may be a date or an ISO datetime (datetime.fromisoformat). last_census_done_count (until L8b) must be an unquoted non-negative JSON integer, or null if no readback is possible. census_trigger.compute_tasks_landed validates rather than coerces it, so a bad value fails that condition safe and logs a warning naming the value. Every threshold in legibility.yaml's census: block must likewise be an unquoted non-negative integer. CensusConfig.from_mapping falls back per field and names each rejected value in one warning.
Precedent: dark_factory was seeded this way in commit 0b99cf4ca2, from plans/agent-legibility-survey-2026-07-13.md, with last_census_done_count: 2321. Use this only when an equivalent survey really exists.
What changes when plans/census-incremental-prd.md lands
- L1: The run writes a JSON record and renders the report from it under one basename (contract §5), with a
run_idand a## MethodYAML block (the seven §5 keys plusextra). A same-day rerun writes…-2.json/…-2.mdinstead of overwriting.- The trickle and the census record every coded session in a host-local ledger. The census skips ledgered and zero-signal sessions, and a capped run's unmined sessions are picked up by the next run, so rolling
last_census_atback is no longer needed. census-state.jsongainslast_census_run_id,last_census_as_of_shaandsession_watermark, andlast_census_reportbecomes repo-relative. Check the ledger withsession_ledger.py stats --project-id <id>.
- The trickle and the census record every coded session in a host-local ledger. The census skips ledgered and zero-signal sessions, and a capped run's unmined sessions are picked up by the next run, so rolling
- L8a: The novelty spike becomes relative to its trailing baseline, and the floor is measured from the session watermark, so the trigger stops firing every night.
- L8b: The tasks-landed condition, the 10-day
max_interval_daysbackstop andlast_census_done_countare retired; the backstop is dropped by decision (Leo, 2026-10-05), and alegibility.yamlstill carrying the key is ignored with one deprecation warning. The trigger fires on the weighted completion of the tasks the previous run filed (contract §11), or on a relative novelty spike, and either only once the floor has passed. The floor is the minimum transcript window, measured from the session watermark; it never fires a census by itself. The anchor-seeding JSON then needs onlylast_census_atandlast_census_report.- A first census, without the backstop: a never-censused project has no previous run id, so completion is N/A. Its floor is measured from the earliest codebook date, and once that floor has passed it fires only on a novelty spike. Otherwise it fires only when run by hand, which is also the only way a project with no trickle data gets a census.
- L2, L3:
- Verdicts carry anchor, tags, severity with a reason, and route.
- Entries carry
finding_key/finding_area/finding_anchor/finding_tags, and the report gains## Findingsin the contract §1 shape. - Tickets carry
x_finding_key/x_finding_run, at a priority equal to severity.
- L4: Filing follows the contract §8 dedup protocol, and the report names task ids and the step that decided each finding.
- Structural findings are listed, not filed.
- Entries carry
filed_ticketsback-links. A run that cannot reach the task store files nothing and says so.
- L5:
## Dispositionscarries earlier tickets forward to their task outcomes. A done task triggers re-verification (fixedor re-filed), and a cancelled task with a reason makes its entryaccepted. - L6, L7:
## Screenedcounts clusters attached to existing records with no verify spend.## Adjudicationdrains the pending-candidate backlog through a capped, similarity-clustered queue and lists every attach for audit.--max-verify-clustersdeferrals then reach adjudication without recurring.
- L9: The coder is given the project's invariant slugs, and unknown slugs are dropped and counted. It is also given the
## Definitionsection ofdocs/code-quality.md(never the heuristics) as the test a confusion must meet to be minted; tagging stays with the verifier. - L10: Synthesis is rendered from validated JSON as new versus re-observed findings, and its phase refinements and corrections are applied rather than left in prose.
- G: A deterministic gate checks the first automatic report after the batch lands for all of the above.
Out of scope
This skill runs and verifies a census. It does not change scripts/legibility/*, the trickle units, or the trigger logic. Those changes go through /prd (see plans/census-incremental-prd.md) or /do.