Skip to content

Case Law: A Governed Citator On Hard State

The case-law-monitoring kit builds a citator you can audit, composed over the agent-operation base. The corpus layer is real public law — opinions, courts, judges, statutes, and the citation graph, CourtListener-sourced and digest-pinned (the Chevron-deference cluster). The firm layer is the practice — clients, matters, arguments, filings, deadlines — plus the governed edges an attorney actually reviews: holdings, interpretations, citation treatment, argument support and risk, matter impact.

The demo runs in two acts: act one builds the pre-2024 world and the matter is quiet; act two lands Loper Bright, and the payoff query — supporting_authority_now_bad_law — surfaces the decision that overruled this matter's supporting authority, with a receipt for the traversal. Every judgment-bearing edge on that path was proposed by an extractor and admitted by a reviewer: a rule declared in config is an invariant; an instruction followed by a model is a probability, drawn fresh every call. The opinions are real public law; the firm layer — clients, matters, filings — is synthetic demo data. Run everything from a clone of the repo (seed paths are repo-relative), with jq installed.

1. Start an identity-on instance

Run the daemon with auth on so writes are credential-backed identities rather than one shared operator. In production the extractor and the reviewer hold separately minted credentials; this walkthrough runs under the admin credential for brevity.

CRUXIBLE_SERVER_AUTH=true \
CRUXIBLE_RUNTIME_BOOTSTRAP_SECRET=change-me-once \
CRUXIBLE_SERVER_STATE_DIR="$HOME/.cruxible/case-law" cruxible server start   # shell 1

# shell 2 — init with the bootstrap secret as bearer, then claim the admin credential
CRUXIBLE_SERVER_BEARER_TOKEN=change-me-once \
  cruxible --server-url http://127.0.0.1:8100 init \
  --kit agent-operation --kit case-law-monitoring --bootstrap

CRUXIBLE_RUNTIME_BOOTSTRAP_SECRET=change-me-once cruxible credential claim-bootstrap
export CRUXIBLE_SERVER_BEARER_TOKEN=<printed-admin-token>

2. Register the opinion texts

Every holding and treatment judgment then cites a verbatim, hash-verified passage of the real opinion:

jq -r '.opinions[] | [.opinion_id, .source_url] | @tsv' \
  kits/case-law-monitoring/data/seed/opinions/manifest.json |
while IFS=$'\t' read -r opinion_id source_url; do
  cruxible source register \
    --path "kits/case-law-monitoring/data/seed/opinions/${opinion_id}.txt" \
    --id "opinion_text_${opinion_id}" --kind markdown --retention manifest_only \
    --original-uri "$source_url" --label "${opinion_id} opinion text" --json
done

3. Act one — build the world, then judge the holdings

Canonical ingest previews first, then commits; one extractor payload then seeds the inert Holding entities and the governed opinion_has_holding proposal:

cruxible run --workflow build_corpus
cruxible apply --workflow build_corpus --from-last-preview

cruxible run --workflow analyze_opinions_for_holdings --json \
  | jq '{items: .output.items}' > holding-candidates.json

cruxible run --workflow apply_candidate_holdings \
  --input-file holding-candidates.json --save-preview holdings-preview.json
cruxible apply --preview-file holdings-preview.json

cruxible propose --workflow propose_holdings_from_opinion \
  --input-file holding-candidates.json

The proposal lands as a pending group — review and resolve, the pattern for every propose below:

cruxible group list --status pending_review
cruxible group get --group <group-id>
cruxible group resolve --group <group-id> --action approve \
  --rationale "Verified holdings against the registered opinion text." \
  --expected-pending-version <pending-version>

4. Wire holdings to statutes, issues, and arguments

Each layer reads the one before it, so resolve each group before running the next; the last sets the baseline citator state (act one is all follows):

cruxible propose --workflow propose_statute_interpretations   # then resolve
cruxible propose --workflow propose_holding_issue_links       # then resolve
cruxible propose --workflow propose_argument_support          # then resolve
cruxible propose --workflow propose_opinion_treatment         # then resolve

cruxible query run negative_treatment_for_cited_authorities \
  --param matter_id=matter_greengrid_epa

Zero results is the point — the doctrine is standing.

5. Act two — Loper Bright arrives

The treatment proposal after the corpus update contains exactly three new members — overrules Chevron, abrogates Brand X, limits Mead — each with quote-and-offset evidence. Negative treatment always requires review:

cruxible run --workflow refresh_corpus --json | jq '.output' > corpus-update.json
cruxible run --workflow sync_corpus_update \
  --input-file corpus-update.json --save-preview sync-preview.json
cruxible apply --preview-file sync-preview.json

cruxible propose --workflow propose_opinion_treatment         # review, then resolve

6. The payoff

cruxible query run supporting_authority_now_bad_law \
  --param matter_id=matter_greengrid_epa --json

cruxible explain --receipt <receipt-id> --format markdown

Three results: one path through each supporting authority Loper Bright took down, newest treatment first, with a receipt_id for the whole derivation — explain renders it.

Staying current

Fresh opinions arrive outside the workflow boundary: kits/case-law-monitoring/scripts/fetch_courtlistener.py pulls from CourtListener (set COURTLISTENER_API_TOKEN) and the reviewed rows feed sync_corpus_update exactly like act two; analyze_review_work + apply_review_work_items then route the obligations as base WorkItems that close only through the review gate. The bundled extractors are the zero-LLM floor, not the ceiling: an agent supplies smarter judgment as the same contract rows, through the same gates — see the kit README.