extropy-run 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- extropy_run-0.2.2/.claude/skills/extropy/SKILL.md +152 -0
- extropy_run-0.2.2/.env.example +9 -0
- extropy_run-0.2.2/.github/workflows/claude-code-review.yml +45 -0
- extropy_run-0.2.2/.github/workflows/claude.yml +50 -0
- extropy_run-0.2.2/.github/workflows/publish.yml +28 -0
- extropy_run-0.2.2/.github/workflows/test.yml +35 -0
- extropy_run-0.2.2/.gitignore +73 -0
- extropy_run-0.2.2/CLAUDE.md +196 -0
- extropy_run-0.2.2/LICENSE +21 -0
- extropy_run-0.2.2/PKG-INFO +200 -0
- extropy_run-0.2.2/README.md +163 -0
- extropy_run-0.2.2/docs/architecture.md +245 -0
- extropy_run-0.2.2/docs/commands.md +690 -0
- extropy_run-0.2.2/docs/use-cases.md +192 -0
- extropy_run-0.2.2/extropy/__init__.py +3 -0
- extropy_run-0.2.2/extropy/cli/__init__.py +23 -0
- extropy_run-0.2.2/extropy/cli/app.py +74 -0
- extropy_run-0.2.2/extropy/cli/commands/__init__.py +29 -0
- extropy_run-0.2.2/extropy/cli/commands/config_cmd.py +210 -0
- extropy_run-0.2.2/extropy/cli/commands/estimate.py +216 -0
- extropy_run-0.2.2/extropy/cli/commands/extend.py +271 -0
- extropy_run-0.2.2/extropy/cli/commands/network.py +287 -0
- extropy_run-0.2.2/extropy/cli/commands/persona.py +344 -0
- extropy_run-0.2.2/extropy/cli/commands/results.py +60 -0
- extropy_run-0.2.2/extropy/cli/commands/sample.py +324 -0
- extropy_run-0.2.2/extropy/cli/commands/scenario.py +256 -0
- extropy_run-0.2.2/extropy/cli/commands/simulate.py +339 -0
- extropy_run-0.2.2/extropy/cli/commands/spec.py +253 -0
- extropy_run-0.2.2/extropy/cli/commands/validate.py +287 -0
- extropy_run-0.2.2/extropy/cli/display.py +233 -0
- extropy_run-0.2.2/extropy/cli/utils.py +317 -0
- extropy_run-0.2.2/extropy/config.py +311 -0
- extropy_run-0.2.2/extropy/core/__init__.py +16 -0
- extropy_run-0.2.2/extropy/core/llm.py +178 -0
- extropy_run-0.2.2/extropy/core/models/__init__.py +200 -0
- extropy_run-0.2.2/extropy/core/models/network.py +188 -0
- extropy_run-0.2.2/extropy/core/models/population.py +532 -0
- extropy_run-0.2.2/extropy/core/models/results.py +125 -0
- extropy_run-0.2.2/extropy/core/models/sampling.py +35 -0
- extropy_run-0.2.2/extropy/core/models/scenario.py +326 -0
- extropy_run-0.2.2/extropy/core/models/simulation.py +393 -0
- extropy_run-0.2.2/extropy/core/models/validation.py +219 -0
- extropy_run-0.2.2/extropy/core/pricing.py +78 -0
- extropy_run-0.2.2/extropy/core/providers/__init__.py +97 -0
- extropy_run-0.2.2/extropy/core/providers/base.py +231 -0
- extropy_run-0.2.2/extropy/core/providers/claude.py +370 -0
- extropy_run-0.2.2/extropy/core/providers/logging.py +69 -0
- extropy_run-0.2.2/extropy/core/providers/openai.py +504 -0
- extropy_run-0.2.2/extropy/core/rate_limiter.py +564 -0
- extropy_run-0.2.2/extropy/core/rate_limits.py +125 -0
- extropy_run-0.2.2/extropy/population/__init__.py +79 -0
- extropy_run-0.2.2/extropy/population/network/__init__.py +93 -0
- extropy_run-0.2.2/extropy/population/network/config.py +179 -0
- extropy_run-0.2.2/extropy/population/network/config_generator.py +566 -0
- extropy_run-0.2.2/extropy/population/network/generator.py +1006 -0
- extropy_run-0.2.2/extropy/population/network/metrics.py +236 -0
- extropy_run-0.2.2/extropy/population/network/similarity.py +238 -0
- extropy_run-0.2.2/extropy/population/persona/__init__.py +48 -0
- extropy_run-0.2.2/extropy/population/persona/config.py +234 -0
- extropy_run-0.2.2/extropy/population/persona/generator.py +823 -0
- extropy_run-0.2.2/extropy/population/persona/renderer.py +409 -0
- extropy_run-0.2.2/extropy/population/persona/stats.py +77 -0
- extropy_run-0.2.2/extropy/population/sampler/__init__.py +57 -0
- extropy_run-0.2.2/extropy/population/sampler/core.py +364 -0
- extropy_run-0.2.2/extropy/population/sampler/distributions.py +354 -0
- extropy_run-0.2.2/extropy/population/sampler/modifiers.py +246 -0
- extropy_run-0.2.2/extropy/population/spec_builder/__init__.py +64 -0
- extropy_run-0.2.2/extropy/population/spec_builder/binder.py +221 -0
- extropy_run-0.2.2/extropy/population/spec_builder/hydrator.py +229 -0
- extropy_run-0.2.2/extropy/population/spec_builder/hydrators/__init__.py +18 -0
- extropy_run-0.2.2/extropy/population/spec_builder/hydrators/conditional.py +448 -0
- extropy_run-0.2.2/extropy/population/spec_builder/hydrators/derived.py +161 -0
- extropy_run-0.2.2/extropy/population/spec_builder/hydrators/independent.py +196 -0
- extropy_run-0.2.2/extropy/population/spec_builder/hydrators/prompts.py +250 -0
- extropy_run-0.2.2/extropy/population/spec_builder/parsers.py +194 -0
- extropy_run-0.2.2/extropy/population/spec_builder/schemas.py +293 -0
- extropy_run-0.2.2/extropy/population/spec_builder/selector.py +320 -0
- extropy_run-0.2.2/extropy/population/spec_builder/sufficiency.py +92 -0
- extropy_run-0.2.2/extropy/population/validator/__init__.py +55 -0
- extropy_run-0.2.2/extropy/population/validator/llm_response.py +646 -0
- extropy_run-0.2.2/extropy/population/validator/semantic.py +250 -0
- extropy_run-0.2.2/extropy/population/validator/spec.py +42 -0
- extropy_run-0.2.2/extropy/population/validator/structural.py +762 -0
- extropy_run-0.2.2/extropy/scenario/__init__.py +100 -0
- extropy_run-0.2.2/extropy/scenario/compiler.py +303 -0
- extropy_run-0.2.2/extropy/scenario/exposure.py +285 -0
- extropy_run-0.2.2/extropy/scenario/interaction.py +293 -0
- extropy_run-0.2.2/extropy/scenario/outcomes.py +141 -0
- extropy_run-0.2.2/extropy/scenario/parser.py +210 -0
- extropy_run-0.2.2/extropy/scenario/validator.py +565 -0
- extropy_run-0.2.2/extropy/simulation/__init__.py +147 -0
- extropy_run-0.2.2/extropy/simulation/aggregation.py +298 -0
- extropy_run-0.2.2/extropy/simulation/engine.py +1334 -0
- extropy_run-0.2.2/extropy/simulation/estimator.py +289 -0
- extropy_run-0.2.2/extropy/simulation/persona.py +381 -0
- extropy_run-0.2.2/extropy/simulation/progress.py +105 -0
- extropy_run-0.2.2/extropy/simulation/propagation.py +344 -0
- extropy_run-0.2.2/extropy/simulation/reasoning.py +916 -0
- extropy_run-0.2.2/extropy/simulation/state.py +1268 -0
- extropy_run-0.2.2/extropy/simulation/stopping.py +297 -0
- extropy_run-0.2.2/extropy/simulation/timeline.py +259 -0
- extropy_run-0.2.2/extropy/utils/__init__.py +72 -0
- extropy_run-0.2.2/extropy/utils/callbacks.py +81 -0
- extropy_run-0.2.2/extropy/utils/distributions.py +227 -0
- extropy_run-0.2.2/extropy/utils/eval_safe.py +273 -0
- extropy_run-0.2.2/extropy/utils/expressions.py +339 -0
- extropy_run-0.2.2/extropy/utils/graphs.py +117 -0
- extropy_run-0.2.2/extropy/utils/paths.py +67 -0
- extropy_run-0.2.2/pyproject.toml +65 -0
- extropy_run-0.2.2/tests/__init__.py +1 -0
- extropy_run-0.2.2/tests/conftest.py +447 -0
- extropy_run-0.2.2/tests/test_binder.py +144 -0
- extropy_run-0.2.2/tests/test_cli.py +61 -0
- extropy_run-0.2.2/tests/test_compiler.py +282 -0
- extropy_run-0.2.2/tests/test_conviction.py +124 -0
- extropy_run-0.2.2/tests/test_engine.py +1507 -0
- extropy_run-0.2.2/tests/test_estimator.py +480 -0
- extropy_run-0.2.2/tests/test_integration_timestep.py +646 -0
- extropy_run-0.2.2/tests/test_memory_traces.py +355 -0
- extropy_run-0.2.2/tests/test_models.py +688 -0
- extropy_run-0.2.2/tests/test_network.py +898 -0
- extropy_run-0.2.2/tests/test_network_config_generator.py +88 -0
- extropy_run-0.2.2/tests/test_paths.py +120 -0
- extropy_run-0.2.2/tests/test_persona_generator.py +124 -0
- extropy_run-0.2.2/tests/test_persona_renderer.py +59 -0
- extropy_run-0.2.2/tests/test_progress.py +216 -0
- extropy_run-0.2.2/tests/test_propagation.py +625 -0
- extropy_run-0.2.2/tests/test_providers.py +1144 -0
- extropy_run-0.2.2/tests/test_rate_limiter.py +121 -0
- extropy_run-0.2.2/tests/test_reasoning_prompts.py +590 -0
- extropy_run-0.2.2/tests/test_sampler.py +1248 -0
- extropy_run-0.2.2/tests/test_scenario.py +674 -0
- extropy_run-0.2.2/tests/test_scenario_validator.py +159 -0
- extropy_run-0.2.2/tests/test_simulation.py +678 -0
- extropy_run-0.2.2/tests/test_stopping.py +318 -0
- extropy_run-0.2.2/tests/test_validator.py +755 -0
- extropy_run-0.2.2/uv.lock +825 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: extropy
|
|
3
|
+
description: Help run the extropy pipeline — population creation, scenario compilation, simulation, debugging validation errors, and interpreting results. Use when the user asks about running extropy commands, fixing spec issues, understanding results, or needs guidance on what extropy can simulate.
|
|
4
|
+
allowed-tools: Read, Grep, Glob, Bash, Edit, Write
|
|
5
|
+
argument-hint: "[command or question]"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Extropy Assistant
|
|
9
|
+
|
|
10
|
+
You are an expert assistant for the Extropy predictive intelligence framework. You help users run the full pipeline, debug issues, interpret results, and understand what Extropy can and cannot do.
|
|
11
|
+
|
|
12
|
+
## What Extropy Is
|
|
13
|
+
|
|
14
|
+
Extropy simulates how real human populations respond to scenarios. It creates synthetic populations grounded in real-world statistical data, enriches them with LLM-extrapolated psychographic attributes, connects them via social networks, and runs agent-based simulations where each agent reasons individually via LLM calls. The output is distributional predictions — not a poll, but a simulation of emergent collective behavior.
|
|
15
|
+
|
|
16
|
+
## The Pipeline
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
extropy spec → extropy extend → extropy sample → extropy network → extropy persona → extropy scenario → extropy simulate → extropy results
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Step | Command | What It Does |
|
|
23
|
+
| ---- | ------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
24
|
+
| 1 | `extropy spec "<description>" -o base.yaml` | Build base population spec from natural language |
|
|
25
|
+
| 2 | `extropy extend base.yaml -s "<scenario>" -o population.yaml` | Add scenario-specific attributes |
|
|
26
|
+
| 3 | `extropy sample population.yaml -o agents.json --seed 42` | Sample concrete agents from distributions |
|
|
27
|
+
| 4 | `extropy network agents.json -o network.json -p population.yaml --seed 42` | Generate social network graph |
|
|
28
|
+
| 5 | `extropy persona population.yaml --agents agents.json` | Generate persona rendering config |
|
|
29
|
+
| 6 | `extropy scenario -p population.yaml -a agents.json -n network.json -o scenario.yaml` | Compile executable scenario spec |
|
|
30
|
+
| 7 | `extropy simulate scenario.yaml -o results/ --seed 42` | Run the simulation |
|
|
31
|
+
| 8 | `extropy results results/` | View outcomes |
|
|
32
|
+
|
|
33
|
+
Add `-y` / `--yes` to skip confirmation prompts for scripting.
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Extropy uses a two-zone config system:
|
|
38
|
+
|
|
39
|
+
- **Pipeline zone** (steps 1-6): `extropy config set pipeline.provider claude`
|
|
40
|
+
- **Simulation zone** (step 7): `extropy config set simulation.provider openai`
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
extropy config show # View current config
|
|
44
|
+
extropy config set <key> <value> # Set a value
|
|
45
|
+
extropy config reset # Reset to defaults
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
API keys are always env vars: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`.
|
|
49
|
+
|
|
50
|
+
## When Helping Users Run Commands
|
|
51
|
+
|
|
52
|
+
1. **Check prerequisites first** — does the user have the input files the command needs? Read the directory to verify.
|
|
53
|
+
2. **Use `--seed 42`** on sample, network, and simulate for reproducibility unless the user specifies otherwise.
|
|
54
|
+
3. **Show what happened** — after each command, briefly explain the output (e.g., "Sampled 500 agents with 38 attributes each").
|
|
55
|
+
4. **For `extropy spec` and `extropy extend`** — these are interactive and make LLM calls. Warn the user about API costs for large populations. Use `-y` only if the user asks for non-interactive mode.
|
|
56
|
+
|
|
57
|
+
## Debugging Validation Errors
|
|
58
|
+
|
|
59
|
+
When `extropy validate` fails, common issues and fixes:
|
|
60
|
+
|
|
61
|
+
### Structural Errors (must fix)
|
|
62
|
+
|
|
63
|
+
- **Circular dependency** — Check the `depends_on` fields. Use `extropy validate <spec> --strict` for full details. The error message includes the cycle path.
|
|
64
|
+
- **Invalid distribution params** — e.g., `std: 0` or `min > max`. Fix in the YAML directly.
|
|
65
|
+
- **Missing dependency reference** — A formula or condition references an attribute that doesn't exist. Check spelling.
|
|
66
|
+
- **Duplicate attribute names** — Two attributes with the same `name` field.
|
|
67
|
+
- **Invalid modifier conditions** — Condition syntax uses restricted Python. Only these builtins are allowed: abs, min, max, round, int, float, str, len, sum, all, any, bool.
|
|
68
|
+
|
|
69
|
+
### Semantic Warnings (should fix)
|
|
70
|
+
|
|
71
|
+
- **No-op modifiers** — A modifier that doesn't actually change anything (e.g., `multiply: 1.0, add: 0`).
|
|
72
|
+
- **Categorical option mismatch** — Modifier references an option that doesn't exist in the attribute's options list. Use `extropy fix <spec>` to auto-fix fuzzy matches.
|
|
73
|
+
- **Modifier stacking** — Multiple modifiers that could conflict.
|
|
74
|
+
|
|
75
|
+
### Common Fix Commands
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
extropy validate population.yaml # Check for issues
|
|
79
|
+
extropy validate population.yaml --strict # Treat warnings as errors
|
|
80
|
+
extropy fix population.yaml --dry-run # Preview auto-fixes
|
|
81
|
+
extropy fix population.yaml # Apply auto-fixes
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Interpreting Results
|
|
85
|
+
|
|
86
|
+
When the user asks about simulation results:
|
|
87
|
+
|
|
88
|
+
1. **Read the key files:**
|
|
89
|
+
|
|
90
|
+
- `meta.json` — run config, timing, rate limiter stats
|
|
91
|
+
- `outcome_distributions.json` — final aggregate outcomes
|
|
92
|
+
- `by_timestep.json` — how outcomes evolved over time
|
|
93
|
+
- `agent_states.json` — per-agent final states (for deep dives)
|
|
94
|
+
|
|
95
|
+
2. **Highlight interesting patterns:**
|
|
96
|
+
|
|
97
|
+
- Convergence speed (how many timesteps until exposure saturated)
|
|
98
|
+
- Distribution shape (is it polarized or clustered?)
|
|
99
|
+
- Sentiment vs. position mismatches (agents who chose "comply" but have negative sentiment)
|
|
100
|
+
- Conviction levels (high conviction = stable opinions, low = could shift with more exposure)
|
|
101
|
+
|
|
102
|
+
3. **Suggest segment analysis:**
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
extropy results results/ --segment income
|
|
106
|
+
extropy results results/ --segment age
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
4. **For per-agent deep dives:**
|
|
110
|
+
```bash
|
|
111
|
+
extropy results results/ --agent agent_042
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## What Extropy CAN Simulate
|
|
115
|
+
|
|
116
|
+
Extropy is built for **population-level behavioral prediction** with heterogeneous agents:
|
|
117
|
+
|
|
118
|
+
- **Product/service adoption** — How will surgeons respond to a new AI diagnostic tool? Who adopts early vs. resists, and why?
|
|
119
|
+
- **Policy compliance** — Congestion tax, vaccine mandates, zoning changes. Identifies inequity and friction points across demographics.
|
|
120
|
+
- **Information spread** — How rumors, news, or messaging propagates through social networks. Which groups are most susceptible?
|
|
121
|
+
- **Collective action** — Strike propensity, boycott participation, protest. Predicts "silent" effects like early retirement waves.
|
|
122
|
+
- **Message testing** — Synthetic focus groups at scale. Test political messaging, marketing campaigns, or crisis communications on granular population segments.
|
|
123
|
+
- **Pricing changes** — Subscription price hikes, fee introductions. Predicts churn, downgrade, and complaint patterns by segment.
|
|
124
|
+
|
|
125
|
+
## What Extropy CANNOT Simulate
|
|
126
|
+
|
|
127
|
+
- **Organizational hierarchies** — No org charts, reporting lines, or workflow dependencies. Agents are peers in a social network.
|
|
128
|
+
- **Physical/spatial logistics** — No coordinates, distances, collision, or capacity. Geography is a semantic label, not a map.
|
|
129
|
+
- **High-frequency quantitative models** — LLM reasoning is semantic and probabilistic, not mathematically precise or sub-second.
|
|
130
|
+
- **Multi-event cascades** — Currently single-event scenarios only. Sequential reactive events (event A triggers event B) require running separate simulations.
|
|
131
|
+
|
|
132
|
+
## Simulation Tips
|
|
133
|
+
|
|
134
|
+
- **Population size vs. cost**: Each agent makes 1-2 LLM calls per reasoning round. A 500-agent, 50-timestep simulation could make ~5,000-25,000 API calls. Start small (100 agents, 2-5 timesteps) to validate, then scale up.
|
|
135
|
+
- **Two-pass reasoning**: Pass 1 (pivotal model, e.g., gpt-5) does freeform role-play. Pass 2 (routine model, e.g., gpt-5-mini) classifies into outcomes. This is more accurate but uses 2x the calls.
|
|
136
|
+
- **Seed exposure rules**: The scenario's `seed_exposure.rules` control who learns about the event when. If exposure rate is low after simulation, check these rules — the conditions might be too restrictive.
|
|
137
|
+
- **Multi-touch threshold**: Default is 3 — agents re-reason after 3 new exposures since their last reasoning. Lower it for faster opinion evolution, raise it for more stable opinions.
|
|
138
|
+
- **Stopping conditions**: Simulations stop at max_timesteps OR when stop_conditions are met (e.g., `exposure_rate > 0.95 and no_state_changes_for > 5`).
|
|
139
|
+
|
|
140
|
+
## File Formats Quick Reference
|
|
141
|
+
|
|
142
|
+
| File | Format | Key Fields |
|
|
143
|
+
| ------------------------------------ | ---------- | ------------------------------------------------------------------------- |
|
|
144
|
+
| `population.yaml` | YAML | meta, attributes, sampling_order, grounding |
|
|
145
|
+
| `agents.json` | JSON array | Each object has `_id` + all attribute values |
|
|
146
|
+
| `network.json` | JSON | meta, nodes, edges (bidirectional, typed) |
|
|
147
|
+
| `*.persona.yaml` | YAML | intro_template, treatments, groups, phrasings, population_stats |
|
|
148
|
+
| `scenario.yaml` | YAML | meta, event, seed_exposure, interaction, spread, outcomes, simulation |
|
|
149
|
+
| `results/meta.json` | JSON | scenario_name, population_size, model, completed_at |
|
|
150
|
+
| `results/outcome_distributions.json` | JSON | Per-outcome aggregate distributions |
|
|
151
|
+
| `results/by_timestep.json` | JSON array | Per-timestep: exposure_rate, position_distribution, sentiment, conviction |
|
|
152
|
+
| `results/agent_states.json` | JSON array | Per-agent: position, sentiment, conviction, public_statement, memory |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Extropy API Keys
|
|
2
|
+
# These are the only settings that belong in .env (secrets only).
|
|
3
|
+
# For provider/model config, use: extropy config set <key> <value>
|
|
4
|
+
|
|
5
|
+
# OpenAI (required if using openai as provider)
|
|
6
|
+
OPENAI_API_KEY=sk-...
|
|
7
|
+
|
|
8
|
+
# Anthropic (from https://console.anthropic.com/settings/keys)
|
|
9
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Claude Code Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
pull_request:
|
|
6
|
+
types: [opened, synchronize, ready_for_review, reopened]
|
|
7
|
+
# Optional: Only run on specific file changes
|
|
8
|
+
# paths:
|
|
9
|
+
# - "src/**/*.ts"
|
|
10
|
+
# - "src/**/*.tsx"
|
|
11
|
+
# - "src/**/*.js"
|
|
12
|
+
# - "src/**/*.jsx"
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
claude-review:
|
|
16
|
+
# Optional: Filter by PR author
|
|
17
|
+
# if: |
|
|
18
|
+
# github.event.pull_request.user.login == 'external-contributor' ||
|
|
19
|
+
# github.event.pull_request.user.login == 'new-developer' ||
|
|
20
|
+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
21
|
+
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
pull-requests: read
|
|
26
|
+
issues: read
|
|
27
|
+
id-token: write
|
|
28
|
+
|
|
29
|
+
steps:
|
|
30
|
+
- name: Checkout repository
|
|
31
|
+
uses: actions/checkout@v4
|
|
32
|
+
with:
|
|
33
|
+
fetch-depth: 1
|
|
34
|
+
|
|
35
|
+
- name: Run Claude Code Review
|
|
36
|
+
id: claude-review
|
|
37
|
+
uses: anthropics/claude-code-action@v1
|
|
38
|
+
with:
|
|
39
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
40
|
+
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
|
|
41
|
+
plugins: 'code-review@claude-code-plugins'
|
|
42
|
+
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
|
|
43
|
+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
44
|
+
# or https://code.claude.com/docs/en/cli-reference for available options
|
|
45
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Claude Code
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
pull_request_review_comment:
|
|
7
|
+
types: [created]
|
|
8
|
+
issues:
|
|
9
|
+
types: [opened, assigned]
|
|
10
|
+
pull_request_review:
|
|
11
|
+
types: [submitted]
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude:
|
|
15
|
+
if: |
|
|
16
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
17
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
18
|
+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
|
19
|
+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
pull-requests: read
|
|
24
|
+
issues: read
|
|
25
|
+
id-token: write
|
|
26
|
+
actions: read # Required for Claude to read CI results on PRs
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout repository
|
|
29
|
+
uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 1
|
|
32
|
+
|
|
33
|
+
- name: Run Claude Code
|
|
34
|
+
id: claude
|
|
35
|
+
uses: anthropics/claude-code-action@v1
|
|
36
|
+
with:
|
|
37
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
|
+
|
|
39
|
+
# This is an optional setting that allows Claude to read CI results on PRs
|
|
40
|
+
additional_permissions: |
|
|
41
|
+
actions: read
|
|
42
|
+
|
|
43
|
+
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
|
44
|
+
# prompt: 'Update the pull request description to include a summary of changes.'
|
|
45
|
+
|
|
46
|
+
# Optional: Add claude_args to customize behavior and configuration
|
|
47
|
+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
48
|
+
# or https://code.claude.com/docs/en/cli-reference for available options
|
|
49
|
+
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
|
50
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
|
|
20
|
+
- name: Build package
|
|
21
|
+
run: |
|
|
22
|
+
pip install build
|
|
23
|
+
python -m build
|
|
24
|
+
|
|
25
|
+
- name: Publish to PyPI
|
|
26
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
27
|
+
with:
|
|
28
|
+
skip-existing: true
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main, dev]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main, dev]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lint:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v4
|
|
16
|
+
with:
|
|
17
|
+
enable-cache: true
|
|
18
|
+
- run: uv python install 3.12
|
|
19
|
+
- run: uv sync --group dev
|
|
20
|
+
- run: uv run ruff check .
|
|
21
|
+
- run: uv run ruff format --check .
|
|
22
|
+
|
|
23
|
+
test:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
strategy:
|
|
26
|
+
matrix:
|
|
27
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: astral-sh/setup-uv@v4
|
|
31
|
+
with:
|
|
32
|
+
enable-cache: true
|
|
33
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
34
|
+
- run: uv sync --group dev
|
|
35
|
+
- run: uv run pytest --tb=short -q
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
env/
|
|
25
|
+
venv/
|
|
26
|
+
.venv/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
|
|
34
|
+
# Environment
|
|
35
|
+
.env
|
|
36
|
+
|
|
37
|
+
# Storage (database and populations)
|
|
38
|
+
storage/
|
|
39
|
+
|
|
40
|
+
# Simulation results
|
|
41
|
+
results/
|
|
42
|
+
|
|
43
|
+
# Cache
|
|
44
|
+
data/cache/
|
|
45
|
+
|
|
46
|
+
# Logs
|
|
47
|
+
logs/
|
|
48
|
+
|
|
49
|
+
# OS
|
|
50
|
+
.DS_Store
|
|
51
|
+
Thumbs.db
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
austin/
|
|
55
|
+
examples/
|
|
56
|
+
|
|
57
|
+
# Simulation artifacts
|
|
58
|
+
*.sqlite
|
|
59
|
+
*.db
|
|
60
|
+
*.jsonl
|
|
61
|
+
|
|
62
|
+
# Tool caches
|
|
63
|
+
__pypackages__/
|
|
64
|
+
.mypy_cache/
|
|
65
|
+
.pytest_cache/
|
|
66
|
+
.ruff_cache/
|
|
67
|
+
|
|
68
|
+
# Claude Code
|
|
69
|
+
.claude/agents/
|
|
70
|
+
|
|
71
|
+
# Private (competitive intel, internal planning, todos)
|
|
72
|
+
private/
|
|
73
|
+
todo/
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What Extropy Is
|
|
6
|
+
|
|
7
|
+
Extropy is a predictive intelligence framework that simulates how real human populations respond to scenarios. It creates synthetic populations grounded in real-world statistical data, enriches them with LLM-extrapolated psychographic attributes, connects them via social networks, and runs agent-based simulations where each agent reasons individually via LLM calls. The output is not a poll — it's a simulation of emergent collective behavior.
|
|
8
|
+
|
|
9
|
+
Competitor reference: [Aaru](https://aaru.com) operates in the same space (multi-agent population simulation for predictive intelligence). Extropy differentiates through its grounding pipeline — every attribute distribution is researched from real-world sources with citations, not just LLM-generated.
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -e ".[dev]" # Install with dev deps
|
|
15
|
+
|
|
16
|
+
# Set API keys (secrets only — in .env or env vars)
|
|
17
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
18
|
+
export OPENAI_API_KEY=sk-...
|
|
19
|
+
|
|
20
|
+
# Configure providers and models
|
|
21
|
+
extropy config set pipeline.provider claude # Use Claude for population/scenario building
|
|
22
|
+
extropy config set simulation.provider openai # Use OpenAI for agent reasoning
|
|
23
|
+
extropy config set simulation.model gpt-5-mini # Override simulation model
|
|
24
|
+
extropy config set simulation.routine_model gpt-5-mini # Cheap model for Pass 2 classification
|
|
25
|
+
extropy config set simulation.rate_tier 2 # Rate limit tier (1-4)
|
|
26
|
+
extropy config show # View current config
|
|
27
|
+
|
|
28
|
+
pytest # Run all tests
|
|
29
|
+
pytest tests/test_sampler.py # Single test file
|
|
30
|
+
pytest -k "test_name" # Single test by name
|
|
31
|
+
|
|
32
|
+
ruff check . # Lint
|
|
33
|
+
ruff format . # Format
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
CLI entry point: `extropy` (defined in `pyproject.toml` → `extropy.cli:app`). Python >=3.11.
|
|
37
|
+
|
|
38
|
+
## Pipeline (7 sequential commands)
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
extropy spec → extropy extend → extropy sample → extropy network → extropy persona → extropy scenario → extropy simulate
|
|
42
|
+
│
|
|
43
|
+
extropy results
|
|
44
|
+
extropy estimate
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Each command produces a file consumed by the next. `extropy validate` is a utility runnable at any point. `extropy results` is a viewer for simulation output. `extropy estimate` predicts simulation cost (LLM calls, tokens, USD) without requiring API keys.
|
|
48
|
+
|
|
49
|
+
## Architecture
|
|
50
|
+
|
|
51
|
+
Three phases, each mapping to a package under `extropy/`:
|
|
52
|
+
|
|
53
|
+
### Phase 1: Population Creation (`extropy/population/`)
|
|
54
|
+
|
|
55
|
+
**The validity pipeline.** This is where predictive accuracy is won or lost.
|
|
56
|
+
|
|
57
|
+
1. **Sufficiency check** (`spec_builder/sufficiency.py`) — LLM validates the description has enough context (who, how many, where).
|
|
58
|
+
|
|
59
|
+
2. **Attribute selection** (`spec_builder/selector.py`) — LLM discovers 25-40 attributes across 4 categories: `universal` (age, gender), `population_specific` (specialty, seniority), `context_specific` (scenario-relevant), `personality` (Big Five). Each attribute gets a type (`int`/`float`/`categorical`/`boolean`) and sampling strategy (`independent`/`derived`/`conditional`).
|
|
60
|
+
|
|
61
|
+
3. **Hydration** (`spec_builder/hydrator.py` → `hydrators/`) — The most important step. Four sub-steps, each using different LLM tiers:
|
|
62
|
+
- **2a: Independent** (`hydrators/independent.py`) — `agentic_research()` with web search finds real-world distributions with source URLs. This is the grounding layer.
|
|
63
|
+
- **2b: Derived** (`hydrators/derived.py`) — `reasoning_call()` specifies deterministic formulas (e.g., `years_experience = age - 26`).
|
|
64
|
+
- **2c: Conditional base** (`hydrators/conditional.py`) — `agentic_research()` finds base distributions for attributes that depend on others.
|
|
65
|
+
- **2d: Conditional modifiers** (`hydrators/conditional.py`) — `reasoning_call()` specifies how attribute values shift based on other attributes. Type-specific: numeric gets `multiply`/`add`, categorical gets `weight_overrides`, boolean gets `probability_override`.
|
|
66
|
+
|
|
67
|
+
4. **Constraint binding** (`spec_builder/binder.py`) — Topological sort (Kahn's algorithm, `utils/graphs.py`) resolves attribute dependencies into a valid sampling order. Raises `CircularDependencyError` with cycle path.
|
|
68
|
+
|
|
69
|
+
5. **Sampling** (`sampler/core.py`) — Iterates through `sampling_order`, routing each attribute by strategy. Supports 6 distribution types: normal, lognormal, uniform, beta, categorical, boolean. Hard constraints (min/max) are clamped post-sampling. Formula parameters evaluated via `utils/eval_safe.py` (restricted Python eval, whitelisted builtins only).
|
|
70
|
+
|
|
71
|
+
6. **Network generation** (`network/`) — Hybrid algorithm: similarity-based edge probability with degree correction, calibrated via binary search to hit target avg_degree, then Watts-Strogatz rewiring (5%) for small-world properties. Edge probability: `base_rate * sigmoid(similarity) * degree_factor_a * degree_factor_b`. All network behavior is data-driven via `NetworkConfig`: attribute weights, edge type rules (evaluated by priority via `_eval_edge_condition()`), influence factors (ordinal/boolean/numeric), and degree multipliers. `NetworkConfig` can be generated from a `PopulationSpec` via LLM (`config_generator.py`), loaded from YAML (`--network-config`), or auto-detected as `{population_stem}.network-config.yaml`. Empty config (no `-p` or `-c`) produces a flat network.
|
|
72
|
+
|
|
73
|
+
### Phase 2: Scenario Compilation (`extropy/scenario/`)
|
|
74
|
+
|
|
75
|
+
**Compiler** (`compiler.py`) orchestrates 5 steps: parse event → generate exposure rules → determine interaction model → define outcomes → assemble spec.
|
|
76
|
+
|
|
77
|
+
- **Event types**: product_launch, policy_change, pricing_change, technology_release, organizational_change, market_event, crisis_event
|
|
78
|
+
- **Exposure channels**: broadcast, targeted, organic — with per-timestep rules containing conditions and probabilities
|
|
79
|
+
- **Outcomes**: categorical (enum options), boolean, float (with range), open_ended
|
|
80
|
+
- Auto-configures simulation parameters based on population size (<500: 50 timesteps, ≤5000: 100, >5000: 168)
|
|
81
|
+
|
|
82
|
+
### Phase 3: Simulation (`extropy/simulation/`)
|
|
83
|
+
|
|
84
|
+
**Engine** (`engine.py`) runs per-timestep loop, decomposed into sub-functions:
|
|
85
|
+
1. **`_apply_exposures(timestep)`** — Apply seed exposures from scenario rules (`propagation.py`), then propagate through network via conviction-gated sharing (very_uncertain agents don't share). Uses pre-built adjacency list for O(1) neighbor lookups.
|
|
86
|
+
2. **`_reason_agents(timestep)`** — Select agents to reason (first exposure OR multi-touch threshold exceeded, default: 3 new exposures since last reasoning), filter out already-processed agents (resume support), split into chunks of `chunk_size` (default 50), run two-pass async LLM reasoning per chunk (`reasoning.py`, rate-limiter-controlled), commit per chunk:
|
|
87
|
+
- **Pass 1 (role-play)**: Agent reasons in first person with no categorical enums. Produces reasoning, public_statement, sentiment, conviction (0-100 integer, bucketed post-hoc), will_share. Memory trace (last 3 reasoning summaries) is fed back for re-reasoning agents.
|
|
88
|
+
- **Pass 2 (classification)**: A cheap model classifies the free-text reasoning into scenario-defined categorical/boolean/float outcomes. Position is extracted here — it is output-only, never used in peer influence.
|
|
89
|
+
3. **`_process_reasoning_chunk(timestep, results, old_states)`** — Process a chunk of reasoning results: conviction-based flip resistance (firm+ agents reject flips unless new conviction is moderate+), conviction-gated sharing, state persistence. State updates are batched via `batch_update_states()`.
|
|
90
|
+
4. Compute timestep summary, check stopping conditions (`stopping.py`) — Compound conditions like `"exposure_rate > 0.95 and no_state_changes_for > 10"`, convergence detection via sentiment variance.
|
|
91
|
+
|
|
92
|
+
**Semantic peer influence**: Agents see peers' `public_statement` + sentiment tone, NOT position labels.
|
|
93
|
+
|
|
94
|
+
**Adjacency list**: Built at engine init from network edges (both directions). Stored as `dict[str, list[tuple[str, dict]]]`. Passed to `propagate_through_network()` and used in `_get_peer_opinions()` for O(1) neighbor lookups instead of O(E) scans.
|
|
95
|
+
|
|
96
|
+
**Two-pass reasoning rationale**: Single-pass reasoning caused 83% of agents to pick safe middle options (central tendency bias). Splitting role-play from classification fixes this — agents reason naturally in Pass 1, then a cheap model maps to categories in Pass 2.
|
|
97
|
+
|
|
98
|
+
**Checkpointing** (`state.py`, `engine.py`): Each timestep phase has its own transaction — exposures, each reasoning chunk, and summary are committed separately. The `simulation_metadata` table stores checkpoint state: `mark_timestep_started()` records which timestep is in progress, `mark_timestep_completed()` clears it. On resume (`_get_resume_timestep()`), the engine detects crashed-mid-timestep (checkpoint set) or last-completed-timestep and picks up from there. Already-processed agents within a partial timestep are skipped via `get_agents_already_reasoned_this_timestep()`. Metadata uses immediate commits (not wrapped in `transaction()`). Setup methods (`_create_schema`, `initialize_agents`) also retain their own commits. CLI: `--chunk-size` (default 50).
|
|
99
|
+
|
|
100
|
+
**Live progress** (`progress.py`, `cli/commands/simulate.py`): `SimulationProgress` is a thread-safe dataclass shared between the simulation thread and CLI display thread. The engine calls `record_agent_done(position, sentiment, conviction)` per agent via an `on_agent_done` callback passed to `batch_reason_agents()`. The CLI renders a `Rich.Live` display at 4 Hz showing timestep/agent progress, exposure rate, elapsed time, and unicode distribution bars for each position. Position counts are cumulative across all timesteps. Verbose mode logs periodic summary blocks every 50 agents via `_log_verbose_summary()`.
|
|
101
|
+
|
|
102
|
+
**Conviction system**: Agents output a 0-100 integer score on a free scale (with descriptive anchors: 0=no idea, 25=leaning, 50=clear opinion, 75=quite sure, 100=certain). `score_to_conviction_float()` buckets it immediately: 0-15→0.1 (very_uncertain), 16-35→0.3 (leaning), 36-60→0.5 (moderate), 61-85→0.7 (firm), 86-100→0.9 (absolute). Agents never see categorical labels or float values — only the 0-100 scale. On re-reasoning, memory traces show the bucketed label (e.g. "you felt *moderate* about this") via `float_to_conviction()`. Engine conviction thresholds reference `CONVICTION_MAP[ConvictionLevel.*]` constants, not hardcoded floats.
|
|
103
|
+
|
|
104
|
+
**Cost estimation** (`simulation/estimator.py`): `extropy estimate` runs a simplified SIR-like propagation model to predict LLM calls per timestep without making any API calls. Token counts estimated from persona size + scenario content. Pricing from `core/pricing.py` model database. Supports `--verbose` for per-timestep breakdown.
|
|
105
|
+
|
|
106
|
+
**Rate limiter** (`core/rate_limiter.py`): Token bucket with dual RPM + TPM buckets. Provider-aware defaults from `core/rate_limits.py` (Anthropic/OpenAI, tiers 1-4). Replaces the old hardcoded `Semaphore(50)`. CLI flags: `--rate-tier`, config: `simulation.rate_tier`, `simulation.rpm_override`, `simulation.tpm_override`.
|
|
107
|
+
|
|
108
|
+
**Persona system** (`population/persona/` + `simulation/persona.py`): The `extropy persona` command generates a `PersonaConfig` via 5-step LLM pipeline (structure → boolean → categorical → relative → concrete phrasings). At simulation time, agents are rendered computationally using this config — no per-agent LLM calls. Relative attributes (personality, attitudes) are positioned against population stats via z-scores ("I'm much more price-sensitive than most people"). Concrete attributes use format specs for proper number/time rendering. **Trait salience**: If `decision_relevant_attributes` is set on `OutcomeConfig`, those attributes are grouped first under "Most Relevant to This Decision" in the persona.
|
|
109
|
+
|
|
110
|
+
## LLM Integration (`extropy/core/llm.py`)
|
|
111
|
+
|
|
112
|
+
All LLM calls go through this file — never call providers directly elsewhere. Two-zone routing:
|
|
113
|
+
|
|
114
|
+
**Pipeline zone** (phases 1-2: spec, extend, persona, scenario) — configured via `extropy config set pipeline.*`:
|
|
115
|
+
|
|
116
|
+
| Function | Default Model | Tools | Use |
|
|
117
|
+
|----------|--------------|-------|-----|
|
|
118
|
+
| `simple_call()` | provider default (haiku/gpt-5-mini) | none | Sufficiency checks, simple extractions |
|
|
119
|
+
| `reasoning_call()` | provider default (sonnet/gpt-5) | none | Attribute selection, hydration, scenario compilation. Supports validator callback + retry |
|
|
120
|
+
| `agentic_research()` | provider default (sonnet/gpt-5) | web_search | Distribution hydration with real-world data. Extracts source URLs |
|
|
121
|
+
|
|
122
|
+
**Simulation zone** (phase 3: agent reasoning) — configured via `extropy config set simulation.*`:
|
|
123
|
+
|
|
124
|
+
| Function | Default Model | Tools | Use |
|
|
125
|
+
|----------|--------------|-------|-----|
|
|
126
|
+
| `simple_call_async()` | provider default | none | Pass 1 role-play reasoning + Pass 2 classification (async) |
|
|
127
|
+
|
|
128
|
+
Two-pass model routing: Pass 1 uses `simulation.model` (pivotal reasoning). Pass 2 uses `simulation.routine_model` (cheap classification). Both default to provider default if not set. CLI: `--model`, `--pivotal-model`, `--routine-model`. Standard inference only — no thinking/extended models (no o1, o3, extended thinking).
|
|
129
|
+
|
|
130
|
+
**Provider abstraction** (`extropy/core/providers/`): `LLMProvider` base class with `OpenAIProvider` and `ClaudeProvider` implementations. Factory functions `get_pipeline_provider()` and `get_simulation_provider()` read from `ExtropyConfig`. Base class provides `_retry_with_validation()` — shared validation-retry loop used by both providers' `reasoning_call()` and `agentic_research()`. Both providers implement `_with_retry()` / `_with_retry_async()` for transient API errors (APIConnectionError, InternalServerError, RateLimitError) with exponential backoff (`2^attempt + random(0,1)` seconds, max 3 retries).
|
|
131
|
+
|
|
132
|
+
**Config** (`extropy/config.py`): `ExtropyConfig` with `PipelineConfig` and `SimZoneConfig` zones. Resolution order: env vars > config file (`~/.config/extropy/config.json`) > defaults. API keys always from env vars (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`). For package use: `from extropy.config import configure, ExtropyConfig`.
|
|
133
|
+
|
|
134
|
+
**Default zones**: Pipeline = Claude (population/scenario building). Simulation = OpenAI (agent reasoning). `SimZoneConfig` fields: `provider`, `model`, `pivotal_model`, `routine_model`, `max_concurrent`, `rate_tier`, `rpm_override`, `tpm_override`.
|
|
135
|
+
|
|
136
|
+
All calls use structured output (`response_format: json_schema`). Failed validations are fed back as "PREVIOUS ATTEMPT FAILED" prompts for self-correction.
|
|
137
|
+
|
|
138
|
+
## Data Models (`extropy/core/models/`)
|
|
139
|
+
|
|
140
|
+
All Pydantic v2. Key hierarchy:
|
|
141
|
+
|
|
142
|
+
- `population.py`: `PopulationSpec` → `AttributeSpec` → `SamplingConfig` → `Distribution` / `Modifier` / `Constraint`
|
|
143
|
+
- `scenario.py`: `ScenarioSpec` → `Event`, `SeedExposure` (channels + rules), `InteractionConfig`, `SpreadConfig`, `OutcomeConfig`
|
|
144
|
+
- `simulation.py`: `ConvictionLevel`, `MemoryEntry`, `AgentState` (conviction, public_statement), `PeerOpinion` (public_statement, credibility), `ReasoningContext` (memory_trace), `ReasoningResponse` (conviction, public_statement, reasoning_summary), `SimulationRunConfig` (pivotal_model, routine_model, chunk_size), `TimestepSummary` (average_conviction, sentiment_variance)
|
|
145
|
+
- `network.py`: `Edge`, `NodeMetrics`, `NetworkMetrics`
|
|
146
|
+
- `validation.py`: `ValidationIssue`, `ValidationResult`
|
|
147
|
+
|
|
148
|
+
YAML serialization via `to_yaml()`/`from_yaml()` on `PopulationSpec` and `ScenarioSpec`.
|
|
149
|
+
|
|
150
|
+
## Validation (`extropy/population/validator/`)
|
|
151
|
+
|
|
152
|
+
Two layers for population specs:
|
|
153
|
+
- **Structural** (`structural.py`): ERROR-level — type/modifier compatibility, range violations, distribution params, dependency cycles, condition syntax, formula references, duplicates, strategy consistency
|
|
154
|
+
- **Semantic** (`semantic.py`): WARNING-level — no-op detection, modifier stacking, categorical option reference validity
|
|
155
|
+
|
|
156
|
+
Scenario validation (`extropy/scenario/validator.py`): attribute reference validity, edge type references, probability ranges.
|
|
157
|
+
|
|
158
|
+
## Key Conventions
|
|
159
|
+
|
|
160
|
+
- Conditions and formulas use restricted Python syntax via `eval_safe()` — whitelisted builtins only (abs, min, max, round, int, float, str, len, sum, all, any, bool)
|
|
161
|
+
- Agent IDs use the `_id` field from agent JSON, falling back to string index
|
|
162
|
+
- Network edges are bidirectional (stored as source/target, traversed both ways)
|
|
163
|
+
- Exposure credibility: `event_credibility * channel_credibility` for seed, fixed 0.85 for peer
|
|
164
|
+
- "Position" = first required categorical outcome (extracted in Pass 2, used for aggregation/output only — never used in peer influence)
|
|
165
|
+
- Peer influence is semantic: agents see neighbors' `public_statement` + sentiment tone, not position labels
|
|
166
|
+
- Conviction: agents output 0-100 integer, bucketed to 5 float levels (0.1/0.3/0.5/0.7/0.9) via `score_to_conviction_float()`. Agents see only the 0-100 scale; memory traces show categorical labels. Engine thresholds reference `CONVICTION_MAP[ConvictionLevel.*]`
|
|
167
|
+
- Memory traces: 3-entry sliding window per agent, fed back into reasoning prompts for re-reasoning
|
|
168
|
+
- Progress callbacks use typed `Protocol` classes from `extropy/utils/callbacks.py` (`StepProgressCallback`, `TimestepProgressCallback`, `ItemProgressCallback`, `HydrationProgressCallback`, `NetworkProgressCallback`) — structurally compatible with plain callables via duck typing
|
|
169
|
+
- The `persona` command generates detailed persona configs; `extend` still generates a simpler `persona_template` for backwards compatibility
|
|
170
|
+
- Simulation auto-detects `{population_stem}.persona.yaml` and uses the new rendering if present
|
|
171
|
+
- Network config is data-driven via `NetworkConfig` (YAML-serializable). No built-in reference preset is shipped. CLI: `extropy network -p population.yaml` (LLM-generated), `-c config.yaml` (manual), `--save-config` (export)
|
|
172
|
+
|
|
173
|
+
## Tests
|
|
174
|
+
|
|
175
|
+
pytest + pytest-asyncio. Fixtures in `tests/conftest.py` include seeded RNG (`Random(42)`), minimal/complex population specs, and sample agents. 580+ tests across 15+ test files:
|
|
176
|
+
|
|
177
|
+
- `test_models.py`, `test_network.py`, `test_sampler.py`, `test_scenario.py`, `test_simulation.py`, `test_validator.py` — core logic
|
|
178
|
+
- `test_engine.py` — mock-based engine integration (seed exposure, flip resistance, conviction-gated sharing, chunked reasoning, checkpointing, resume logic, metadata lifecycle, progress state wiring)
|
|
179
|
+
- `test_progress.py` — SimulationProgress thread-safe state (begin_timestep, record_agent_done, position counts, running averages, snapshot isolation)
|
|
180
|
+
- `test_compiler.py` — scenario compiler pipeline with mocked LLM calls, auto-configuration
|
|
181
|
+
- `test_providers.py` — provider response extraction, transient error retry, validation-retry exhaustion, source URL extraction (mocked HTTP)
|
|
182
|
+
- `test_rate_limiter.py` — token bucket, dual-bucket rate limiter, `for_provider` factory
|
|
183
|
+
- `test_cli.py` — CLI smoke tests (`config show/set`, `validate`, `--version`)
|
|
184
|
+
- `test_estimator.py` — cost estimation, pricing lookup, token estimation
|
|
185
|
+
|
|
186
|
+
CI: `.github/workflows/test.yml` — lint (ruff check + format) and test (pytest, matrix: Python 3.11/3.12/3.13) via `astral-sh/setup-uv@v4`.
|
|
187
|
+
|
|
188
|
+
## File Formats
|
|
189
|
+
|
|
190
|
+
- Population/scenario specs: YAML
|
|
191
|
+
- Network config: YAML (`NetworkConfig.to_yaml()`/`from_yaml()`) — attribute weights, edge type rules, influence factors, degree multipliers
|
|
192
|
+
- Agents: JSON (array of objects with `_id`)
|
|
193
|
+
- Network: JSON (`{meta, nodes, edges}`)
|
|
194
|
+
- Simulation state: SQLite (tables: agent_states, exposures, memory_traces, timeline, timestep_summaries, shared_to, simulation_metadata)
|
|
195
|
+
- Timeline: JSONL (streaming, crash-safe)
|
|
196
|
+
- Results: JSON files in output directory (agent_states.json, by_timestep.json, outcome_distributions.json, meta.json)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Devesh Paragiri
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|