Skip to content

Commit 6de16a8

Browse files
committed
Update
1 parent 94cd6b6 commit 6de16a8

27 files changed

Lines changed: 811 additions & 103 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ jobs:
2929
3030
- name: Smoke-test CLI
3131
run: |
32-
constitution-sim validate --constitution examples/simple_constitution.yaml
33-
constitution-sim validate --constitution examples/advanced_constitution.yaml
34-
constitution-sim validate --constitution examples/strong_executive_constitution.yaml
32+
constitution-sim validate --constitution constitutions/simple_constitution.yaml
33+
constitution-sim validate --constitution constitutions/advanced_constitution.yaml
34+
constitution-sim validate --constitution constitutions/strong_executive_constitution.yaml
3535
3636
- name: Run pytest
3737
run: pytest -q

README.md

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,11 @@ so the project also runs offline / in CI / with zero API keys.
3333
Judiciary, Media, Bureaucracy) gets its own LLM system prompt. The
3434
Executive is ambitious; the Judiciary is reactive; the Media chases a
3535
narrative; the Bureaucracy implements steadily.
36-
- **Agent memory.** Each agent sees its own recent decisions (turn,
37-
action, legal or not) so it can reason about continuity.
38-
- **Schema-driven constitutions.** Strict Pydantic v2 models; YAML in,
39-
typed objects out. Errors are explicit and structured.
40-
- **Rules engine is source of truth.** Agents propose typed actions;
41-
the engine accepts or rejects with a reason. The LLM cannot mutate
42-
state directly.
43-
- **Partial observability.** Each role gets a state view filtered by
44-
its `observation_limits`.
36+
- **Agent memory & shared history.** Each agent remembers its own recent decisions and can see a public history of what other actors just did (if the constitution allows).
37+
- **Inter-agent deliberation.** Each turn features a deliberation phase where agents can negotiate, threaten, or signal intent by sending messages to each other's inboxes.
38+
- **Schema-driven constitutions.** Strict Pydantic v2 models; YAML in, typed objects out. Constitutions can enforce communication limits (e.g. authoritarian gag orders).
39+
- **Rules engine is source of truth.** Agents propose typed actions; the engine accepts or rejects with a reason. The LLM cannot mutate state directly.
40+
- **Partial observability.** Each role gets a state view filtered by its `observation_limits`.
4541
- **Institutional metrics.** Power concentration, deadlock, trust
4642
volatility, legitimacy, corruption pressure, emergency-power drift.
4743
- **Repeated-run evaluation harness.** Multi-seed runs with pandas /
@@ -90,8 +86,8 @@ This exposes a `constitution-sim` console entry point.
9086
```bash
9187
export OPENAI_API_KEY=sk-...
9288
constitution-sim run \
93-
--constitution examples/advanced_constitution.yaml \
94-
--scenario examples/scenario.yaml \
89+
--constitution constitutions/advanced_constitution.yaml \
90+
--scenario constitutions/scenario.yaml \
9591
--turns 20 --seed 42 \
9692
--log /tmp/cs/events.jsonl \
9793
--metrics-out /tmp/cs/metrics.csv
@@ -119,12 +115,12 @@ constitution-sim run --agent-type heuristic ...
119115

120116
```bash
121117
# 1. Validate a constitution YAML against the schema.
122-
constitution-sim validate --constitution examples/advanced_constitution.yaml
118+
constitution-sim validate --constitution constitutions/advanced_constitution.yaml
123119

124120
# 2. Run a simulation (single seed or multi-seed evaluation).
125121
constitution-sim run \
126-
--constitution examples/advanced_constitution.yaml \
127-
--scenario examples/scenario.yaml \
122+
--constitution constitutions/advanced_constitution.yaml \
123+
--scenario constitutions/scenario.yaml \
128124
--turns 30 --runs 5 --seed 42 \
129125
--log /tmp/cs/events.jsonl \
130126
--metrics-out /tmp/cs/metrics.csv \
@@ -145,8 +141,9 @@ For each turn, the LLM agent is prompted with:
145141
- The constitution's name, description, and the list of other roles.
146142
- Its own declared goals and utility weights (from the YAML).
147143
- A partial state view filtered by its `observation_limits`.
148-
- A short memory of its own recent decisions (and whether they were
149-
legal).
144+
- **Public political history**: recent public actions taken by all actors.
145+
- **Inbox messages**: any negotiation/signals received during the turn's deliberation phase.
146+
- A short memory of its own recent decisions (and whether they were legal).
150147
- The exact set of typed actions it's allowed to return.
151148

152149
It replies with one JSON object describing a single action. If the LLM
@@ -164,7 +161,7 @@ src/constitution_sim/
164161
scenarios/ Shock model + ScenarioEngine
165162
analysis/ MetricsCollector, Evaluator, plot
166163
app/ CLI (validate / run / replay / compare)
167-
examples/
164+
constitutions/
168165
simple_constitution.yaml
169166
advanced_constitution.yaml
170167
strong_executive_constitution.yaml
@@ -219,7 +216,5 @@ like I'm 10" walkthrough.
219216
This is an MVP, not a finished research instrument. The following are
220217
explicit non-goals at this stage:
221218

222-
- Multi-actor coalition formation / strategic communication.
223-
- Persistent economic/demographic simulation (state variables are
224-
scalars, not vector economies).
219+
- Persistent economic/demographic simulation (state variables are scalars, not vector economies).
225220
- Fine-tuned LLMs or RL self-play.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Five-branch constitution exercising every role in the system. Use this
2-
# (paired with examples/scenario.yaml) to see the full simulation surface.
2+
# (paired with constitutions/scenario.yaml) to see the full simulation surface.
33
name: "Advanced Constitution"
44
version: "2.0"
55
description: "Executive + Legislature + Judiciary + Media + Bureaucracy."
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: "Authoritarian Junta"
2+
version: "1.0"
3+
description: >
4+
A highly restrictive constitution where power is concentrated in the Executive,
5+
communication is heavily restricted, and other roles have limited visibility
6+
and influence.
7+
8+
initial_state:
9+
variables:
10+
public_trust: 0.3
11+
budget: 2000.0
12+
state_capacity: 0.8
13+
authoritarian_control: 0.9
14+
15+
allow_emergency_powers: true
16+
17+
roles:
18+
Executive:
19+
name: "Executive"
20+
permissions:
21+
- "ProposeLaw"
22+
- "DeclareEmergency"
23+
- "LiftEmergency"
24+
- "ImplementPolicy"
25+
- "AppointJudge"
26+
- "DoNothing"
27+
goals:
28+
- "Maintain absolute order and stability."
29+
- "Consolidate power and marginalize opposition."
30+
utility_weights:
31+
authoritarian_control: 1.5
32+
state_capacity: 1.0
33+
public_trust: 0.2
34+
observation_limits:
35+
see_variables: true
36+
see_active_laws: true
37+
see_pending_bills: true
38+
see_active_shocks: true
39+
see_others_actions: true
40+
can_send_messages: true
41+
allowed_message_recipients: [] # Can talk to anyone
42+
43+
Judiciary:
44+
name: "Judiciary"
45+
permissions:
46+
- "StrikeDownLaw"
47+
- "DoNothing"
48+
goals:
49+
- "Survive the regime."
50+
utility_weights:
51+
authoritarian_control: -0.5
52+
public_trust: 0.5
53+
observation_limits:
54+
see_variables: true
55+
see_active_laws: true
56+
see_pending_bills: false
57+
see_active_shocks: true
58+
see_others_actions: false # Blind to political history
59+
can_send_messages: false # Gag order on judges
60+
allowed_message_recipients: []
61+
62+
Media:
63+
name: "Media"
64+
permissions:
65+
- "PublishStory"
66+
- "DoNothing"
67+
goals:
68+
- "Publish state-approved narratives."
69+
utility_weights:
70+
authoritarian_control: 1.0
71+
observation_limits:
72+
see_variables: true
73+
variable_allowlist: ["public_trust", "state_capacity"] # Cannot see authoritarian_control
74+
see_active_laws: true
75+
see_pending_bills: false
76+
see_active_shocks: false
77+
see_others_actions: false
78+
can_send_messages: true
79+
allowed_message_recipients: ["Executive"] # Can only talk to Executive censors
80+
81+
rules:
82+
- name: "Executive Supremacy"
83+
description: "The Executive has broad powers and cannot be easily checked."
84+
allowed_actions: ["ProposeLaw", "DeclareEmergency", "LiftEmergency", "ImplementPolicy", "AppointJudge"]
85+
applies_to_roles: ["Executive"]
86+
87+
- name: "Judicial Review (Restricted)"
88+
description: "Judges can strike down laws, but are effectively gagged from communicating."
89+
allowed_actions: ["StrikeDownLaw"]
90+
applies_to_roles: ["Judiciary"]
91+
92+
- name: "State Media"
93+
description: "The Media is an arm of the state."
94+
allowed_actions: ["PublishStory"]
95+
applies_to_roles: ["Media"]
File renamed without changes.

docs/architecture.md

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ src/constitution_sim/
4343
state.py WorldState (canonical) and StateView (per-role projection)
4444
actions.py Typed action classes
4545
events.py EventRecord (one row per logged decision)
46+
messages.py Message and DealProposal models for inter-agent communication
4647
core/
4748
engine.py SimulationEngine + EventLogger
49+
message_bus.py MessageBus (handles message routing and filters)
4850
rules.py RulesEngine (legality decisions + reasons)
4951
scheduler.py Round-robin scheduler
5052
agents/
@@ -76,31 +78,32 @@ src/constitution_sim/
7678
\ /
7779
\ /
7880
SimulationEngine ----> EventLogger (.jsonl)
79-
/ | \
80-
/ | \
81-
Scheduler Agents MetricsCollector
82-
| \
83-
StateView DataFrame ----> plots + compare
81+
/ | | \
82+
/ | | \
83+
Scheduler Messages Agents MetricsCollector
84+
| \
85+
StateView DataFrame ----> plots + compare
8486
```
8587

8688
Per turn:
8789

88-
1. `Scheduler.get_next_actor()` returns an `actor_id`.
89-
2. `SimulationEngine.get_state_view(role_name)` builds a partial view
90-
that respects the role's `ObservationLimits`.
91-
3. The agent's `decide(state_view)` returns a typed `Action`.
92-
4. `RulesEngine.is_legal(role, action, state)` returns
90+
1. **Deliberation Phase**: All agents generate messages (`communicate()`) which are routed via the `MessageBus`.
91+
2. `Scheduler.get_next_actor()` returns an `actor_id`.
92+
3. `SimulationEngine.get_state_view(role_name)` builds a partial view
93+
that respects the role's `ObservationLimits`, including the public political history.
94+
4. The agent's `decide_with_messages(state_view, inbox)` returns a typed `Action`.
95+
5. `RulesEngine.is_legal(role, action, state)` returns
9396
`(bool, reason)`. The reason is always stored.
94-
5. `EventLogger.log(EventRecord)` records the attempt — legal or not.
95-
6. If legal, `SimulationEngine.apply_action(actor_id, action, state)`
96-
mutates the `WorldState`.
97-
7. The engine calls `agent.remember(turn, action_type, is_legal)` so
97+
6. `EventLogger.log(EventRecord)` records the attempt — legal or not.
98+
7. If legal, `SimulationEngine.apply_action(actor_id, action, state)`
99+
mutates the `WorldState` and records the public action in `state.recent_actions`.
100+
8. The engine calls `agent.remember(turn, action_type, is_legal)` so
98101
agents that maintain memory (LLM) can see their own history next
99102
turn.
100-
8. `ScenarioEngine.tick(state)` ages active shocks and triggers new
103+
9. `ScenarioEngine.tick(state)` ages active shocks and triggers new
101104
ones.
102-
9. `MetricsCollector.collect(state)` snapshots metrics.
103-
10. Turn counter increments.
105+
10. `MetricsCollector.collect(state)` snapshots metrics.
106+
11. Turn counter increments.
104107

105108
## Agent cognition
106109

@@ -115,6 +118,8 @@ a prompt that includes:
115118
- The **constitution context**: name, description, list of other roles.
116119
- The agent's declared **goals** and **utility weights** from the YAML.
117120
- A **filtered state view** (per-role observation limits applied).
121+
- **Public political history**: recently executed actions by all actors (if permitted).
122+
- **Inbox**: messages sent by other actors during the current turn's deliberation phase.
118123
- A **rolling memory** of the agent's own recent decisions and whether
119124
they were legal.
120125
- The **exact set of typed actions** the role is allowed to return.
@@ -144,7 +149,7 @@ All randomness flows through a single seeded `random.Random` per agent
144149

145150
`Role.observation_limits` (`ObservationLimits`) controls what fields of
146151
the `WorldState` flow into the agent's `StateView`. Examples in
147-
`examples/advanced_constitution.yaml`:
152+
`constitutions/advanced_constitution.yaml`:
148153

149154
- Bureaucracy: `see_pending_bills: false` — bureaucrats don't see
150155
drafts.
@@ -167,6 +172,8 @@ The `MetricsCollector` snapshots per turn:
167172
| corruption_proxy | total illegal-action attempts |
168173
| emergency_active | 1 if a state of emergency is currently active |
169174
| emergency_turns | cumulative turns spent under emergency powers |
175+
| communication_volume| number of messages sent over the message bus |
176+
| active_coalitions | number of formally declared coalitions |
170177

171178
Plus the raw `state.variables` and counts of laws / bills / shocks.
172179

docs/tutorial.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ works in both modes.
5454

5555
```bash
5656
constitution-sim run \
57-
--constitution examples/simple_constitution.yaml \
57+
--constitution constitutions/simple_constitution.yaml \
5858
--turns 6 \
5959
--log /tmp/my_first_run.jsonl
6060
```
@@ -95,8 +95,8 @@ First events:
9595

9696
```bash
9797
constitution-sim run \
98-
--constitution examples/advanced_constitution.yaml \
99-
--scenario examples/scenario.yaml \
98+
--constitution constitutions/advanced_constitution.yaml \
99+
--scenario constitutions/scenario.yaml \
100100
--turns 30 --runs 5 --seed 42 \
101101
--log /tmp/cs/events.jsonl \
102102
--metrics-out /tmp/cs/metrics.csv \
@@ -112,14 +112,14 @@ shocks firing in the middle, and `.png` plots written to
112112

113113
```bash
114114
# Run A: balanced (advanced)
115-
constitution-sim run --constitution examples/advanced_constitution.yaml \
116-
--scenario examples/scenario.yaml --turns 12 --runs 3 --seed 11 \
115+
constitution-sim run --constitution constitutions/advanced_constitution.yaml \
116+
--scenario constitutions/scenario.yaml --turns 12 --runs 3 --seed 11 \
117117
--log /tmp/A/events.jsonl --metrics-out /tmp/A/metrics.csv \
118118
--plot-dir /tmp/A/plots
119119

120120
# Run B: power-grab (strong executive)
121-
constitution-sim run --constitution examples/strong_executive_constitution.yaml \
122-
--scenario examples/scenario.yaml --turns 12 --runs 3 --seed 11 \
121+
constitution-sim run --constitution constitutions/strong_executive_constitution.yaml \
122+
--scenario constitutions/scenario.yaml --turns 12 --runs 3 --seed 11 \
123123
--log /tmp/B/events.jsonl --metrics-out /tmp/B/metrics.csv \
124124
--plot-dir /tmp/B/plots
125125

@@ -146,7 +146,7 @@ back. That's the framework working.
146146

147147
## 8. Editing a constitution
148148

149-
Open `examples/simple_constitution.yaml`. The structure:
149+
Open `constitutions/simple_constitution.yaml`. The structure:
150150

151151
```yaml
152152
name: "My Constitution"
@@ -203,12 +203,12 @@ Knobs you can turn:
203203
After editing, validate:
204204

205205
```bash
206-
constitution-sim validate --constitution examples/my_constitution.yaml
206+
constitution-sim validate --constitution constitutions/my_constitution.yaml
207207
```
208208

209209
## 9. Editing a scenario
210210

211-
`examples/scenario.yaml` lists *shocks* — sudden events that nudge the
211+
`constitutions/scenario.yaml` lists *shocks* — sudden events that nudge the
212212
world's variables:
213213

214214
```yaml
@@ -266,7 +266,7 @@ columns:
266266

267267
## 12. Five experiments to try this weekend
268268

269-
1. **The dictator test.** Use `examples/strong_executive_constitution.yaml`.
269+
1. **The dictator test.** Use `constitutions/strong_executive_constitution.yaml`.
270270
Watch `power_concentration` climb above 0.9. Then in the YAML, add
271271
`StrikeDownLaw` back to the Judiciary's `permissions` — re-run and
272272
watch it drop.
@@ -307,12 +307,12 @@ exactly why the rules engine rejected it.
307307

308308
```bash
309309
# Validate
310-
constitution-sim validate --constitution examples/advanced_constitution.yaml
310+
constitution-sim validate --constitution constitutions/advanced_constitution.yaml
311311
312312
# One quick AI-powered simulation (auto-picks LLM if a key is set)
313313
constitution-sim run \
314-
--constitution examples/advanced_constitution.yaml \
315-
--scenario examples/scenario.yaml \
314+
--constitution constitutions/advanced_constitution.yaml \
315+
--scenario constitutions/scenario.yaml \
316316
--turns 10 --log /tmp/quick.jsonl
317317
318318
# Force the heuristic agent (deterministic, no API needed)

src/constitution_sim/agents/base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,25 @@
22
from constitution_sim.models.state import StateView
33
from constitution_sim.models.actions import Action
44

5+
from typing import List
6+
from constitution_sim.models.messages import Message
7+
58
class BaseAgent(ABC):
69
def __init__(self, agent_id: str, role_name: str):
710
self.agent_id = agent_id
811
self.role_name = role_name
912

13+
def communicate(self, state_view: StateView, inbox: List[Message]) -> List[Message]:
14+
"""Optional deliberation phase: return messages to send to others."""
15+
return []
16+
17+
def decide_with_messages(self, state_view: StateView, inbox: List[Message]) -> Action:
18+
"""Decide on the next action, optionally using received messages.
19+
20+
Default implementation ignores messages and calls decide().
21+
"""
22+
return self.decide(state_view)
23+
1024
@abstractmethod
1125
def decide(self, state_view: StateView) -> Action:
1226
"""Decide on the next action based on the partial state view."""

0 commit comments

Comments
 (0)