Skip to content

CLI Reference

The research CLI for HOURS is utils/eoh_cli.py. Run from the repo root:

python3 utils/eoh_cli.py <command> [options]

Add --no-color to any command to strip ANSI output for piping or logging. Most commands support --format table|csv|json.


arc

Sweep ε from 0 to 0.99 — EOH by domain, registration share, TEH created, basket price, fiscal solvency.

python3 utils/eoh_cli.py arc [--points N] [--format table|csv|json]
python3 utils/eoh_cli.py arc --points 5 --format csv > results/arc.csv

dashboard

Color-coded system health snapshot: Structural Conditions I–IV, EOH health, fiscal health.

python3 utils/eoh_cli.py dashboard [--epsilon ε]

params

Inspect and modify the EohParams calibration. Changes are persisted to utils/_params_state.json and applied to all subsequent commands.

python3 utils/eoh_cli.py params show                                    # all values
python3 utils/eoh_cli.py params set KEY VALUE [--reason TEXT] [--dry-run]
python3 utils/eoh_cli.py params diff                                    # active overrides
python3 utils/eoh_cli.py params reset                                   # clear all

Preview before persisting:

python3 utils/eoh_cli.py params set suff_levy_rate 0.03 --dry-run

Multi-command workflow:

python3 utils/eoh_cli.py params set capital_stock_teh 4000000000 --reason "high-capital scenario"
python3 utils/eoh_cli.py arc --points 20
python3 utils/eoh_cli.py dashboard --epsilon 0.60
python3 utils/eoh_cli.py params reset

scenario

Run or list stress-test scenarios.

python3 utils/eoh_cli.py scenario list
python3 utils/eoh_cli.py scenario run NAME [--format table|csv|json]

scenario list is the authoritative list — it is generated from the registry, prints each scenario's own options, and cannot fall behind. The table below names the families and a representative of each; it is not exhaustive.

Family Representative scenarios
Arc coherence sweep
Shocks automation_failure, demographic_shock, ecological_spike, labor_income_shock, compound_shock, capital_loss
Maintenance & recovery maintenance_crisis, care_delay, recovery
Long run canonical_arc, trust_stress, transition, indust_baseline, indust_recovery
Land / GUF guf_integration, guf_writedown, guf_sweep, guf_magnitude, servicing_census, land_tenure
One collective, end to end collective — the documented institutional entry point
Measurement and reporting obligation_accounts, personal_floor, labour_epsilon, frame, feasibility
The register itself verification_cost, verification_band, register_capture
python3 utils/eoh_cli.py scenario run automation_failure --format csv > results/shock.csv

A flag is read or refused. --epsilon is refused by a scenario whose output is the same at every ε — an arc it sweeps itself, or measured and reference data — rather than accepted and ignored; feasibility reads it only with --adult-capacity or --adult-share. Frame flags follow the same rule.

See Running Scenarios for Python API usage.


frame — running on your own frame

Every scenario that takes a population runs on a frame, as does corridor band: a population, its age mix, its adult labour capacity, its capital, its land, its Trust and its ε. Each prints the inputs it READS, with where each came from, and refuses a flag it does not read — the shocks read the age mix and labour supply; the long-run, Trust and simulation scenarios read population, capital, Trust and ε; indust_* read population and ε; the arcs (canonical_arc, transition, thermal_load) sweep ε themselves and refuse --epsilon. On a named frame, canonical_arc, trust_stress, transition and measured_sim also charge the frame's land fee in every period, capped at what the wage can pay, as stationarity does. Without a frame, collective runs the urban archetype's own parcel inventory. On --frame / --frame-file it runs the frame's land, labour supply and private parcels instead. The parcel-based GUF scenarios (guf_integration, guf_writedown) take only the frame's ε, because their land is their own parcel sample:

Kind Meaning
supplied you gave it — a flag, or your frame file
measured read from a dataset the repo ships for a built-in frame (--frame us: Census ages, MTUS capacity, both ε instruments, BEA stock ages, EIA energy)
derived computed from other inputs, all of them supplied or measured
derived (partly from defaults) computed, but leaning on a framework default (e.g. a per-head intensity)
derived (from defaults) / default the framework's reference value — it says nothing about your frame. A default whose source begins charter choice (the Trust's opening balance, the retirement register, who carries ecological work) is a governance setting, not missing data

Read a result only as far as its inputs go: an outcome built on default rows is a statement about the reference collective, not about yours.

On --frame us the capital is the US stock: BEA's inventory at the midpoint of conversion_band(), the derived dollars-per-hour band. Each scenario that reads capital also reports its verdict at both ends of that band ("at both ends of the conversion band"). --bea-usd-per-teh or --capital-stock states your own.

# What a frame resolves to, every input labelled
python3 utils/eoh_cli.py frame show --frame us

# Start your own frame from the US one, then replace what you have measured
python3 utils/eoh_cli.py frame show --frame us --format json > my_frame.json

# Run a shock and the corridor on it
python3 utils/eoh_cli.py scenario run demographic_shock --frame-file my_frame.json --shock-type aging --shock-magnitude 0.2
python3 utils/eoh_cli.py corridor band --frame-file my_frame.json

The frame file is JSON with any of: name, population, age_fractions (every AGE_GROUPS group, summing to 1), epsilon (a number, or {"low": a, "high": b}), capital_teh, land_hectares, adult_capacity_h_yr, utilization, trust_balance, retirement_age, retired_share, years_in_collective. Anything else is refused, never patched. What you leave out is derived or defaulted — and labelled so. ε is never imputed: without a reading it runs at the reference value, labelled default.

Shipped and constructed frames. A frame file is the one intake — a country's, an institution's, or a constructed scenario's. eoh frame shipped lists the files that ship with the repo (--write regenerates them from the module that owns each), and --frame-file NAME runs one. indust_overbuilt is CONSTRUCTED, not measured: an overbuilt industrial collective (10× capital per head, an aged stock, ecosystem health below the spike threshold) whose ecological work its people carry rather than the GUF. Constructed frames are fictitious on purpose — some extreme, to test the framework's bounds and how it handles collapse; a combination that looks unworkable and still reads stable is itself a finding. A frame file may also state ecosystem_health, capital_age_ratio and ecological_carried_by_people; declared carried, the health deficit over the frame's land is priced as a restoration obligation and carried as labour with the recurring ecological work, in the shocks, the band and the overbuild floor.

python3 utils/eoh_cli.py frame shipped
python3 utils/eoh_cli.py corridor band --frame-file indust_overbuilt
python3 utils/eoh_cli.py scenario run capital_loss --frame-file indust_overbuilt --capital-fraction-lost 0.2

Retirement as a register event is a governance choice, so it is off unless you ask for it. With --retirement-age (bare: the charter default, or give an age within the elderly band), members past that age join the guarantee at the elderly obligation. --years-in-collective sets their claim through the framework's vesting curve — 0 is no claim, the same as leaving it off. The retired share is measured from Census ages where the frame has them, and labelled as leaning on the Census age profile where it does not.

python3 utils/eoh_cli.py scenario run demographic_shock --frame us --shock-type aging --shock-magnitude 0.04 --retirement-age
python3 utils/eoh_cli.py scenario run demographic_shock --frame us --retirement-age 70 --years-in-collective 0

A disaster that destroys capital — a wildfire, flood or earthquake — is capital_loss. The share destroyed is written down (D1: the TEH it embodied is reported, not charged to the Trust), its upkeep leaves the obligation, and the machine work it did falls to people. By default machine capability falls by the same share as the capital, which assumes the loss is spread across the stock; --capability-fraction-lost sets it apart (a fire that takes houses and spares the grid). Rebuilding is a choice, off unless --rebuild-years is given; when on, it is booked as the reconstruction obligation (reconstruction_obligation in total_eoh and the pipeline) and the result says rebuild: on. The result separates capacity (the machine stock that can still run — only a rebuild restores it) from efficiency (the share of people's work actually done — labour restores it), so a broken plant and an unstaffed one do not read alike. A wildfire also burns ecosystem: compound_shock --ecology-collapse --capital-fraction-lost F applies both to one state.

python3 utils/eoh_cli.py scenario run capital_loss --frame us --capital-fraction-lost 0.05 --rebuild-years 5

Deferred upkeep against the overbuild floor. On a frame, maintenance_crisis reads every year against the overbuild floor — whether the apparatus still saves more hours than it costs its members against autarky. Deferred upkeep pushes toward the floor two ways: the compounding backlog adds upkeep, and the neglected stock, its condition falling, abates less. The table shows the condition ratio and the margin each year, and the year the stock is past saving: from then on catching up the neglect costs more labour than rebuilding. That year is derived, not set — an older or more upkeep-intensive stock reaches it sooner — and the frame's capital age sets where the stock starts. On --frame us that age is measured: BEA's average ages over BEA's service lives (frame show --frame us gives the band and what is excluded).

python3 utils/eoh_cli.py scenario run maintenance_crisis --frame us --fulfilment-fraction 0.5 --periods 30

When ε is a range (a frame-file range, or the US instruments), the scenario also runs at both ends and prints each outcome. Where they differ, the difference is the result; when the instruments DISAGREE, the midpoint is labelled as not a reading. Precedence: a flag, then --frame-file, then --frame, then the default. A scenario that reads no frame refuses frame flags rather than ignoring them.


simulate

Multi-period simulation with period-by-period state table.

python3 utils/eoh_cli.py simulate [--periods N] [--epsilon ε] [--epsilon-delta Δε] \
    [--workforce-decay] [--guf-inflow TEH]
python3 utils/eoh_cli.py simulate --periods 20 --epsilon 0.30 --epsilon-delta 0.02

# With workforce decay and GUF land-fee injection:
python3 utils/eoh_cli.py simulate --periods 20 --epsilon 0.30 --epsilon-delta 0.02 \
    --workforce-decay --guf-inflow 50000.0

--workforce-decay — when set, workforce_fraction shrinks proportionally as ε rises each period (models labor displacement).

--guf-inflow TEH — inject a fixed GUF land-fee revenue (TEH) into the Trust each period.


sensitivity

Parameter and arc sensitivity sweeps.

python3 utils/eoh_cli.py sensitivity fiscal --parameter PARAM --values V1,V2,... [--epsilon ε]
python3 utils/eoh_cli.py sensitivity arc [--start ε] [--end ε] [--points N] [--format table|csv|json]
python3 utils/eoh_cli.py sensitivity delta [--epsilon ε] [--delta Δε]
python3 utils/eoh_cli.py sensitivity fiscal --parameter dep_rate --values 0.03,0.045,0.06

guf — Land Use Fee Commands

Ground Use Fee calculations. See GUF Framework for the mathematical specification.

guf calculate

python3 utils/eoh_cli.py guf calculate \
    --epsilon 0.40 \
    --area-slu 3.5 \
    --use-category residential_primary \
    --location-value 0.629 \
    [--format table|json]

Use categories: residential_primary, residential_secondary, agricultural_active, agricultural_fallow, commercial_retail, commercial_office, industrial_light, industrial_heavy, institutional, conservation.

guf trust

python3 utils/eoh_cli.py guf trust \
    --revenues 1.5,2.3,0.8 \
    [--subsidies 0.5] \
    [--format table|json]

guf writedown

Compute modified GUF during an ecological write-down event (NLSA Eq. 29).

# Restoration pathway (default)
python3 utils/eoh_cli.py guf writedown \
    --epsilon 0.40 --area-slu 3.5 --pathway restoration \
    --services-reset-json '[{"label":"water","volume":0.4,"kappa_ref":1.65,"beta":0.8,"retained":0.3}]'

# Abandonment pathway
python3 utils/eoh_cli.py guf writedown \
    --epsilon 0.40 --area-slu 3.5 --pathway abandonment \
    --services-reset-json '[{"label":"water","volume":0.2,"kappa_ref":1.65,"beta":0.8,"retained":0.3}]' \
    --services-lost-json '[{"label":"biodiversity","volume_lost":5.0,"kappa_ref":0.35,"beta":0.7}]'

Services JSON formats:

Reset services (--services-reset-json):

[{"label":"water","volume":0.4,"kappa_ref":1.65,"beta":0.8,"retained":0.3}]

Lost services (--services-lost-json):

[{"label":"biodiversity","volume_lost":5.0,"kappa_ref":0.35,"beta":0.7}]

Options: --pathway restoration|abandonment, --amortization-years N (default 50).

guf rebuilding-surcharge

python3 utils/eoh_cli.py guf rebuilding-surcharge \
    --epsilon 0.40 \
    --services-json '[{"label":"biodiversity","volume_lost":5.0,"kappa_ref":0.35,"beta":0.7}]' \
    [--amortization-years 50] \
    [--format table|json]

guf accumulation-warning

python3 utils/eoh_cli.py guf accumulation-warning \
    --unfulfilled 450000 \
    --total 1200000 \
    [--threshold 0.30] \
    [--format table|json]

Warns when unfulfilled / total > threshold (default 0.30). Triggers accelerated ρ_s review and ecology fund priority.


guf inventory — Collective Land Inventory

Batch GUF operations over a collective's full parcel inventory. Parcel data is provided as a JSON file — a list of objects following the standard parcel schema.

guf inventory calculate

Compute aggregate GUF for a parcel inventory at a given ε.

python3 utils/eoh_cli.py guf inventory calculate \
    --parcels parcels.json \
    [--epsilon 0.40] \
    [--median-income 350.0] \
    [--format table|json]

guf inventory sweep

Sweep aggregate GUF across the ε arc.

python3 utils/eoh_cli.py guf inventory sweep \
    --parcels parcels.json \
    [--epsilon-start 0.0] \
    [--epsilon-end 0.99] \
    [--steps 20] \
    [--format table|json]

guf inventory stress

Multi-period automation→levy→GUF stress test.

python3 utils/eoh_cli.py guf inventory stress \
    --parcels parcels.json \
    [--epsilon-start 0.20] \
    [--epsilon-end 0.80] \
    [--periods 20] \
    [--population 1000000] \
    [--format table|json]

Omit --parcels to use the default 1 000-parcel synthetic urban inventory.

Parcel JSON format (list of parcel dicts; only the three required fields needed for a minimal inventory):

[
  {"area_slu": 3.5, "location_value": 0.72, "use_category": "residential_primary"},
  {"area_slu": 5.0, "location_value": 0.85, "use_category": "commercial_retail",
   "ecosystem_services": [{"label": "water", "volume": 0.4, "kappa_ref": 1.65, "beta": 0.8, "retained": 0.3}]}
]

See Land — Collective Inventory for the full parcel schema.


contestability — Contestability Invariant (§8)

Arc table, stress sweep, derived levy schedule, the §8.9 recalibrated arc, and the §8.7e membership-terms audit.

python3 utils/eoh_cli.py contestability arc    [--regime increasing_returns|replicable] [--points N]
python3 utils/eoh_cli.py contestability stress [--points N]
python3 utils/eoh_cli.py contestability levy   [--chi-target 1.0] [--levy-base capital_yield|machine_output]
python3 utils/eoh_cli.py contestability recal  [--regime ...] [--points N] [--capital-output-ratio 4.0]
python3 utils/eoh_cli.py contestability audit  [--terms-json PATH|-] [inline flags]

--levy-base machine_output uses the physically-consistent base ε·total_EOH (the pipeline's own measure of automated production) instead of the static ε·K·yield, which understates it ~12× at high ε (proposed §8.8 M3).

contestability recal

The proposed-§8.9/§8.9b recalibrated arc: the commons owns share φ(ε) of an ε-consistent capital stock (τ = φ ≤ 1, dτ/dε ≥ 0 structural), and exit is financed by own labor (low ε), commons underwriting (mid ε), or dividend savings (high ε) — t_exit ≤ horizon replaces the retired flow/stock χ.

--phi-policy selects the doctrine: dilution (default, §8.9b charter formation — the commons' share attaches to new capital at commissioning; private capital is never sold down, so φ caps at ≈ 0.66 by ε = 0.99, marked * in the table), target (§8.9a purchase model, regression anchor), or escalated (dilution + the charter escalation clause — full generational conversion via capital-estate escheat if observed concentration threatens exit; never fires at canonical defaults, rows marked ! when active). --estate-escheat sets the baseline capital-estate share (default 0.15 = ESTATE_LEVY_FRACTION, the D5 doctrine extended); raise --min-viable-population to stress the escalation trigger.

# The doctrine arc (dilution default)
python3 utils/eoh_cli.py contestability recal

# §8.9a purchase model for comparison
python3 utils/eoh_cli.py contestability recal --phi-policy target

# Force the escalation clause to fire (40× founding cohort)
python3 utils/eoh_cli.py contestability recal --phi-policy escalated \
    --min-viable-population 200000

contestability formation

The §8.9c formation-feedback simulation: formation is financed or it does not happen (private supply falls as the charter share rises), the commons co-funds from net income per --priority (share-first holds the canonical pace at the dividend's expense; dividend-first crawls and never completes), and ε derives from the capital actually formed. --charter-share 0 is the null anchor; --hurdle/--full-supply toward fiat-like returns quantify the Condition III finding (zero interest makes the charter affordable: s* = 0.50 vs ≈ 0.10 fiat-like).

# The doctrine run (share-first)
python3 utils/eoh_cli.py contestability formation

# The crawling counterfactual
python3 utils/eoh_cli.py contestability formation --priority dividend --years 120

# The fiat-world counterfactual (dividend driven to zero mid-arc)
python3 utils/eoh_cli.py contestability formation --hurdle 0.06 --full-supply 0.18

contestability audit

Checks proposed membership terms against the χ invariant (the code audits the contract; it does not legislate it). Terms come from a JSON file, stdin (-), or inline flags (--vesting-years, --admission-cost, --exit-notice-years, --minimum-hours, --dividend-fraction); inline flags override JSON keys.

# Inline: an admission fee at high ε breaches the invariant
python3 utils/eoh_cli.py contestability audit --admission-cost 800 --epsilon 0.9

# From a terms file
python3 utils/eoh_cli.py contestability audit --terms-json terms.json --epsilon 0.4

# §8.8 closure flags: same admission fee, but a funded commons finances exit
python3 utils/eoh_cli.py contestability audit --admission-cost 800 --epsilon 0.9 \
    --commons-balance 1e10 --commons-dividend --underwriting-policy

Terms JSON format (all fields optional; absent fields keep canonical defaults):

{
  "vesting_years": 5.0,
  "admission_cost_teh": 250.0,
  "exit_notice_years": 0.5,
  "minimum_hours_annual": 400.0,
  "dividend_policy_fraction": 1.0
}

coasean — Collective Federation (§§6–7, §8.7)

Research-tier federation mechanics. --format table|json goes before the subcommand.

python3 utils/eoh_cli.py coasean n1-check                  # N=1 regression anchor
python3 utils/eoh_cli.py coasean count                     # emergent N(ε) across the arc
python3 utils/eoh_cli.py coasean federation --epsilon 0.4  # per-collective snapshot
python3 utils/eoh_cli.py coasean federation --frame us --split us_states  # the US frame by state
python3 utils/eoh_cli.py coasean simulate --periods 10     # multi-period arc

coasean federation on a frame

Without a frame, federation divides the reference economy into N(ε) equal collectives and charges no land fee. On --frame / --frame-file it divides the FRAME instead. Each collective runs the same path as scenario run collective on that frame: its land, its labour supply, and the per-parcel land fee, capped at what its own mint can pay. A total row and the whole frame run as one collective are printed beside the collectives.

--split NAME|PATH names how the frame divides. us_states ships, holding the 48 states and DC. Without --split, the frame divides into N(ε) equal shares, or --n of them. A split file is JSON. Each entry in its collectives list gives an id, an optional label, and positive population, land_hectares and parcel_count. Those three are WEIGHTS: each collective gets its share of the frame's totals, and its population share of the frame's capital and Trust. An entry may also state its own age_fractions, which replace the frame's mix; us_states does, from Census 2025 single-year ages. Every other input (labour capacity, stock age, ε) is the frame's. Because the fee cap applies collective by collective, the collected fees need not add up to the whole frame's.

coasean simulate flags

Flag Effect
--dynamics Phase 3 Trust/capital evolution (adds T, τ, piketty columns)
--g-priv F private capital growth per period (with --dynamics)
--levy-rate F common-fund levy on automated output (with --dynamics)
--commons Phase 4 two-tier commons: escheat, tithe, per-collective χ (adds commons, escheat, χ_marg_min, χ_status columns)
--commons-tithe F levy fraction routed to the federation commons (default 0.03)
--regime R K_entry regime for per-collective χ
--commons-dividend §8.8 M1: commons pays a universal unvested dividend that feeds χ (adds entry_cap, exit_fin semantics)
--commons-start F initial commons balance in TEH (seed; commons_seed_required() ≈ 1.8e7 at defaults)
# The full two-tier arc: consolidation escheat, tithe, and the χ invariant
python3 utils/eoh_cli.py coasean simulate --periods 10 --dynamics --commons --levy-rate 0.3

# §8.8 closure: seeded commons + universal dividend → exit financeable across the arc
python3 utils/eoh_cli.py coasean simulate --periods 21 --epsilon-start 0.0 --dynamics \
    --g-priv 0.02 --levy-rate 0.2 --commons --commons-dividend --commons-start 1.8e7