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.
Files changed (137) hide show
  1. extropy_run-0.2.2/.claude/skills/extropy/SKILL.md +152 -0
  2. extropy_run-0.2.2/.env.example +9 -0
  3. extropy_run-0.2.2/.github/workflows/claude-code-review.yml +45 -0
  4. extropy_run-0.2.2/.github/workflows/claude.yml +50 -0
  5. extropy_run-0.2.2/.github/workflows/publish.yml +28 -0
  6. extropy_run-0.2.2/.github/workflows/test.yml +35 -0
  7. extropy_run-0.2.2/.gitignore +73 -0
  8. extropy_run-0.2.2/CLAUDE.md +196 -0
  9. extropy_run-0.2.2/LICENSE +21 -0
  10. extropy_run-0.2.2/PKG-INFO +200 -0
  11. extropy_run-0.2.2/README.md +163 -0
  12. extropy_run-0.2.2/docs/architecture.md +245 -0
  13. extropy_run-0.2.2/docs/commands.md +690 -0
  14. extropy_run-0.2.2/docs/use-cases.md +192 -0
  15. extropy_run-0.2.2/extropy/__init__.py +3 -0
  16. extropy_run-0.2.2/extropy/cli/__init__.py +23 -0
  17. extropy_run-0.2.2/extropy/cli/app.py +74 -0
  18. extropy_run-0.2.2/extropy/cli/commands/__init__.py +29 -0
  19. extropy_run-0.2.2/extropy/cli/commands/config_cmd.py +210 -0
  20. extropy_run-0.2.2/extropy/cli/commands/estimate.py +216 -0
  21. extropy_run-0.2.2/extropy/cli/commands/extend.py +271 -0
  22. extropy_run-0.2.2/extropy/cli/commands/network.py +287 -0
  23. extropy_run-0.2.2/extropy/cli/commands/persona.py +344 -0
  24. extropy_run-0.2.2/extropy/cli/commands/results.py +60 -0
  25. extropy_run-0.2.2/extropy/cli/commands/sample.py +324 -0
  26. extropy_run-0.2.2/extropy/cli/commands/scenario.py +256 -0
  27. extropy_run-0.2.2/extropy/cli/commands/simulate.py +339 -0
  28. extropy_run-0.2.2/extropy/cli/commands/spec.py +253 -0
  29. extropy_run-0.2.2/extropy/cli/commands/validate.py +287 -0
  30. extropy_run-0.2.2/extropy/cli/display.py +233 -0
  31. extropy_run-0.2.2/extropy/cli/utils.py +317 -0
  32. extropy_run-0.2.2/extropy/config.py +311 -0
  33. extropy_run-0.2.2/extropy/core/__init__.py +16 -0
  34. extropy_run-0.2.2/extropy/core/llm.py +178 -0
  35. extropy_run-0.2.2/extropy/core/models/__init__.py +200 -0
  36. extropy_run-0.2.2/extropy/core/models/network.py +188 -0
  37. extropy_run-0.2.2/extropy/core/models/population.py +532 -0
  38. extropy_run-0.2.2/extropy/core/models/results.py +125 -0
  39. extropy_run-0.2.2/extropy/core/models/sampling.py +35 -0
  40. extropy_run-0.2.2/extropy/core/models/scenario.py +326 -0
  41. extropy_run-0.2.2/extropy/core/models/simulation.py +393 -0
  42. extropy_run-0.2.2/extropy/core/models/validation.py +219 -0
  43. extropy_run-0.2.2/extropy/core/pricing.py +78 -0
  44. extropy_run-0.2.2/extropy/core/providers/__init__.py +97 -0
  45. extropy_run-0.2.2/extropy/core/providers/base.py +231 -0
  46. extropy_run-0.2.2/extropy/core/providers/claude.py +370 -0
  47. extropy_run-0.2.2/extropy/core/providers/logging.py +69 -0
  48. extropy_run-0.2.2/extropy/core/providers/openai.py +504 -0
  49. extropy_run-0.2.2/extropy/core/rate_limiter.py +564 -0
  50. extropy_run-0.2.2/extropy/core/rate_limits.py +125 -0
  51. extropy_run-0.2.2/extropy/population/__init__.py +79 -0
  52. extropy_run-0.2.2/extropy/population/network/__init__.py +93 -0
  53. extropy_run-0.2.2/extropy/population/network/config.py +179 -0
  54. extropy_run-0.2.2/extropy/population/network/config_generator.py +566 -0
  55. extropy_run-0.2.2/extropy/population/network/generator.py +1006 -0
  56. extropy_run-0.2.2/extropy/population/network/metrics.py +236 -0
  57. extropy_run-0.2.2/extropy/population/network/similarity.py +238 -0
  58. extropy_run-0.2.2/extropy/population/persona/__init__.py +48 -0
  59. extropy_run-0.2.2/extropy/population/persona/config.py +234 -0
  60. extropy_run-0.2.2/extropy/population/persona/generator.py +823 -0
  61. extropy_run-0.2.2/extropy/population/persona/renderer.py +409 -0
  62. extropy_run-0.2.2/extropy/population/persona/stats.py +77 -0
  63. extropy_run-0.2.2/extropy/population/sampler/__init__.py +57 -0
  64. extropy_run-0.2.2/extropy/population/sampler/core.py +364 -0
  65. extropy_run-0.2.2/extropy/population/sampler/distributions.py +354 -0
  66. extropy_run-0.2.2/extropy/population/sampler/modifiers.py +246 -0
  67. extropy_run-0.2.2/extropy/population/spec_builder/__init__.py +64 -0
  68. extropy_run-0.2.2/extropy/population/spec_builder/binder.py +221 -0
  69. extropy_run-0.2.2/extropy/population/spec_builder/hydrator.py +229 -0
  70. extropy_run-0.2.2/extropy/population/spec_builder/hydrators/__init__.py +18 -0
  71. extropy_run-0.2.2/extropy/population/spec_builder/hydrators/conditional.py +448 -0
  72. extropy_run-0.2.2/extropy/population/spec_builder/hydrators/derived.py +161 -0
  73. extropy_run-0.2.2/extropy/population/spec_builder/hydrators/independent.py +196 -0
  74. extropy_run-0.2.2/extropy/population/spec_builder/hydrators/prompts.py +250 -0
  75. extropy_run-0.2.2/extropy/population/spec_builder/parsers.py +194 -0
  76. extropy_run-0.2.2/extropy/population/spec_builder/schemas.py +293 -0
  77. extropy_run-0.2.2/extropy/population/spec_builder/selector.py +320 -0
  78. extropy_run-0.2.2/extropy/population/spec_builder/sufficiency.py +92 -0
  79. extropy_run-0.2.2/extropy/population/validator/__init__.py +55 -0
  80. extropy_run-0.2.2/extropy/population/validator/llm_response.py +646 -0
  81. extropy_run-0.2.2/extropy/population/validator/semantic.py +250 -0
  82. extropy_run-0.2.2/extropy/population/validator/spec.py +42 -0
  83. extropy_run-0.2.2/extropy/population/validator/structural.py +762 -0
  84. extropy_run-0.2.2/extropy/scenario/__init__.py +100 -0
  85. extropy_run-0.2.2/extropy/scenario/compiler.py +303 -0
  86. extropy_run-0.2.2/extropy/scenario/exposure.py +285 -0
  87. extropy_run-0.2.2/extropy/scenario/interaction.py +293 -0
  88. extropy_run-0.2.2/extropy/scenario/outcomes.py +141 -0
  89. extropy_run-0.2.2/extropy/scenario/parser.py +210 -0
  90. extropy_run-0.2.2/extropy/scenario/validator.py +565 -0
  91. extropy_run-0.2.2/extropy/simulation/__init__.py +147 -0
  92. extropy_run-0.2.2/extropy/simulation/aggregation.py +298 -0
  93. extropy_run-0.2.2/extropy/simulation/engine.py +1334 -0
  94. extropy_run-0.2.2/extropy/simulation/estimator.py +289 -0
  95. extropy_run-0.2.2/extropy/simulation/persona.py +381 -0
  96. extropy_run-0.2.2/extropy/simulation/progress.py +105 -0
  97. extropy_run-0.2.2/extropy/simulation/propagation.py +344 -0
  98. extropy_run-0.2.2/extropy/simulation/reasoning.py +916 -0
  99. extropy_run-0.2.2/extropy/simulation/state.py +1268 -0
  100. extropy_run-0.2.2/extropy/simulation/stopping.py +297 -0
  101. extropy_run-0.2.2/extropy/simulation/timeline.py +259 -0
  102. extropy_run-0.2.2/extropy/utils/__init__.py +72 -0
  103. extropy_run-0.2.2/extropy/utils/callbacks.py +81 -0
  104. extropy_run-0.2.2/extropy/utils/distributions.py +227 -0
  105. extropy_run-0.2.2/extropy/utils/eval_safe.py +273 -0
  106. extropy_run-0.2.2/extropy/utils/expressions.py +339 -0
  107. extropy_run-0.2.2/extropy/utils/graphs.py +117 -0
  108. extropy_run-0.2.2/extropy/utils/paths.py +67 -0
  109. extropy_run-0.2.2/pyproject.toml +65 -0
  110. extropy_run-0.2.2/tests/__init__.py +1 -0
  111. extropy_run-0.2.2/tests/conftest.py +447 -0
  112. extropy_run-0.2.2/tests/test_binder.py +144 -0
  113. extropy_run-0.2.2/tests/test_cli.py +61 -0
  114. extropy_run-0.2.2/tests/test_compiler.py +282 -0
  115. extropy_run-0.2.2/tests/test_conviction.py +124 -0
  116. extropy_run-0.2.2/tests/test_engine.py +1507 -0
  117. extropy_run-0.2.2/tests/test_estimator.py +480 -0
  118. extropy_run-0.2.2/tests/test_integration_timestep.py +646 -0
  119. extropy_run-0.2.2/tests/test_memory_traces.py +355 -0
  120. extropy_run-0.2.2/tests/test_models.py +688 -0
  121. extropy_run-0.2.2/tests/test_network.py +898 -0
  122. extropy_run-0.2.2/tests/test_network_config_generator.py +88 -0
  123. extropy_run-0.2.2/tests/test_paths.py +120 -0
  124. extropy_run-0.2.2/tests/test_persona_generator.py +124 -0
  125. extropy_run-0.2.2/tests/test_persona_renderer.py +59 -0
  126. extropy_run-0.2.2/tests/test_progress.py +216 -0
  127. extropy_run-0.2.2/tests/test_propagation.py +625 -0
  128. extropy_run-0.2.2/tests/test_providers.py +1144 -0
  129. extropy_run-0.2.2/tests/test_rate_limiter.py +121 -0
  130. extropy_run-0.2.2/tests/test_reasoning_prompts.py +590 -0
  131. extropy_run-0.2.2/tests/test_sampler.py +1248 -0
  132. extropy_run-0.2.2/tests/test_scenario.py +674 -0
  133. extropy_run-0.2.2/tests/test_scenario_validator.py +159 -0
  134. extropy_run-0.2.2/tests/test_simulation.py +678 -0
  135. extropy_run-0.2.2/tests/test_stopping.py +318 -0
  136. extropy_run-0.2.2/tests/test_validator.py +755 -0
  137. 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.