econagents 0.2.2__tar.gz → 0.2.3__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 (129) hide show
  1. econagents-0.2.3/CHANGELOG.md +63 -0
  2. {econagents-0.2.2 → econagents-0.2.3}/PKG-INFO +3 -2
  3. {econagents-0.2.2 → econagents-0.2.3}/README.md +2 -1
  4. {econagents-0.2.2 → econagents-0.2.3}/econagents/_version.py +2 -2
  5. {econagents-0.2.2 → econagents-0.2.3}/econagents/runtime/game_runner.py +3 -0
  6. econagents-0.2.3/examples/continuous_double_auction/README.md +51 -0
  7. econagents-0.2.3/examples/continuous_double_auction/__init__.py +1 -0
  8. econagents-0.2.3/examples/continuous_double_auction/agents.py +90 -0
  9. econagents-0.2.3/examples/continuous_double_auction/prompts/trader_system.jinja2 +13 -0
  10. econagents-0.2.3/examples/continuous_double_auction/prompts/trader_user_phase_market.jinja2 +28 -0
  11. econagents-0.2.3/examples/continuous_double_auction/run_game.py +55 -0
  12. econagents-0.2.3/examples/continuous_double_auction/server/__init__.py +1 -0
  13. econagents-0.2.3/examples/continuous_double_auction/server/create_game.py +108 -0
  14. econagents-0.2.3/examples/continuous_double_auction/server/server.py +543 -0
  15. econagents-0.2.3/examples/continuous_double_auction/state.py +49 -0
  16. econagents-0.2.3/examples/continuous_double_auction/verify.py +205 -0
  17. econagents-0.2.3/examples/dictator/README.md +54 -0
  18. econagents-0.2.3/examples/dictator/agents.py +99 -0
  19. econagents-0.2.3/examples/dictator/prompts/dictator_system.jinja2 +16 -0
  20. econagents-0.2.3/examples/dictator/prompts/dictator_user_phase_1.jinja2 +11 -0
  21. econagents-0.2.3/examples/dictator/prompts/dictator_user_phase_2.jinja2 +12 -0
  22. econagents-0.2.3/examples/dictator/prompts/receiver_system.jinja2 +4 -0
  23. econagents-0.2.3/examples/dictator/prompts/receiver_user_phase_2.jinja2 +10 -0
  24. econagents-0.2.3/examples/dictator/run_game.py +43 -0
  25. econagents-0.2.3/examples/dictator/server/__init__.py +0 -0
  26. econagents-0.2.3/examples/dictator/server/create_game.py +85 -0
  27. econagents-0.2.3/examples/dictator/server/server.py +389 -0
  28. econagents-0.2.3/examples/dictator/state.py +43 -0
  29. econagents-0.2.3/examples/dictator/verify.py +155 -0
  30. econagents-0.2.3/examples/prisoner/agents.py +72 -0
  31. econagents-0.2.3/examples/prisoner/prisoner.yaml +153 -0
  32. econagents-0.2.3/examples/prisoner/prompts/all_system.jinja2 +12 -0
  33. econagents-0.2.3/examples/prisoner/prompts/all_user.jinja2 +33 -0
  34. econagents-0.2.3/examples/prisoner/run_game.py +39 -0
  35. econagents-0.2.3/examples/prisoner/run_game_from_yaml.py +22 -0
  36. econagents-0.2.3/examples/prisoner/server/__init__.py +0 -0
  37. econagents-0.2.3/examples/prisoner/server/create_game.py +80 -0
  38. econagents-0.2.3/examples/prisoner/server/server.py +358 -0
  39. econagents-0.2.3/examples/prisoner/state.py +36 -0
  40. econagents-0.2.3/examples/prisoner/verify.py +141 -0
  41. econagents-0.2.3/examples/prisoner_personas/__init__.py +0 -0
  42. econagents-0.2.3/examples/prisoner_personas/agents.py +82 -0
  43. econagents-0.2.3/examples/prisoner_personas/personas/marcus-strategic-44.yaml +8 -0
  44. econagents-0.2.3/examples/prisoner_personas/personas/maria-cooperative-37.yaml +8 -0
  45. econagents-0.2.3/examples/prisoner_personas/prisoner.yaml +147 -0
  46. econagents-0.2.3/examples/prisoner_personas/prompts/all_system.jinja2 +12 -0
  47. econagents-0.2.3/examples/prisoner_personas/prompts/all_user.jinja2 +33 -0
  48. econagents-0.2.3/examples/prisoner_personas/run_game.py +125 -0
  49. econagents-0.2.3/examples/prisoner_personas/run_game_from_yaml.py +39 -0
  50. econagents-0.2.3/examples/public_goods/README.md +118 -0
  51. econagents-0.2.3/examples/public_goods/agents.py +79 -0
  52. econagents-0.2.3/examples/public_goods/prompts/_partials/_cooperative.jinja2 +1 -0
  53. econagents-0.2.3/examples/public_goods/prompts/_partials/_selfish.jinja2 +1 -0
  54. econagents-0.2.3/examples/public_goods/prompts/player_system.jinja2 +23 -0
  55. econagents-0.2.3/examples/public_goods/prompts/player_user_phase_1.jinja2 +11 -0
  56. econagents-0.2.3/examples/public_goods/prompts/player_user_phase_2.jinja2 +27 -0
  57. econagents-0.2.3/examples/public_goods/run_game.py +59 -0
  58. econagents-0.2.3/examples/public_goods/server/__init__.py +0 -0
  59. econagents-0.2.3/examples/public_goods/server/create_game.py +87 -0
  60. econagents-0.2.3/examples/public_goods/server/server.py +410 -0
  61. econagents-0.2.3/examples/public_goods/state.py +47 -0
  62. econagents-0.2.3/examples/public_goods/verify.py +150 -0
  63. {econagents-0.2.2 → econagents-0.2.3}/pyproject.toml +6 -0
  64. {econagents-0.2.2 → econagents-0.2.3}/.gitignore +0 -0
  65. {econagents-0.2.2 → econagents-0.2.3}/LICENSE +0 -0
  66. {econagents-0.2.2 → econagents-0.2.3}/econagents/__init__.py +0 -0
  67. {econagents-0.2.2 → econagents-0.2.3}/econagents/_c_extension.pyi +0 -0
  68. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/__init__.py +0 -0
  69. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/config/__init__.py +0 -0
  70. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/config/yaml.py +0 -0
  71. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/llm/__init__.py +0 -0
  72. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/llm/base.py +0 -0
  73. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/llm/observability.py +0 -0
  74. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/llm/ollama.py +0 -0
  75. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/llm/openai.py +0 -0
  76. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/parsing/__init__.py +0 -0
  77. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/parsing/json.py +0 -0
  78. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/prompts/__init__.py +0 -0
  79. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/prompts/jinja.py +0 -0
  80. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/protocol/__init__.py +0 -0
  81. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/protocol/flat.py +0 -0
  82. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/protocol/ibex.py +0 -0
  83. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/state/__init__.py +0 -0
  84. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/state/event_field.py +0 -0
  85. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/tools/__init__.py +0 -0
  86. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/tools/base.py +0 -0
  87. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/tools/calculator.py +0 -0
  88. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/tools/python_exec.py +0 -0
  89. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/tools/registry.py +0 -0
  90. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/transport/__init__.py +0 -0
  91. {econagents-0.2.2 → econagents-0.2.3}/econagents/adapters/transport/websocket.py +0 -0
  92. {econagents-0.2.2 → econagents-0.2.3}/econagents/cli.py +0 -0
  93. {econagents-0.2.2 → econagents-0.2.3}/econagents/domain/__init__.py +0 -0
  94. {econagents-0.2.2 → econagents-0.2.3}/econagents/domain/events.py +0 -0
  95. {econagents-0.2.2 → econagents-0.2.3}/econagents/domain/logging.py +0 -0
  96. {econagents-0.2.2 → econagents-0.2.3}/econagents/domain/messages.py +0 -0
  97. {econagents-0.2.2 → econagents-0.2.3}/econagents/domain/role.py +0 -0
  98. {econagents-0.2.2 → econagents-0.2.3}/econagents/domain/state/__init__.py +0 -0
  99. {econagents-0.2.2 → econagents-0.2.3}/econagents/domain/state/fields.py +0 -0
  100. {econagents-0.2.2 → econagents-0.2.3}/econagents/domain/state/game.py +0 -0
  101. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/__init__.py +0 -0
  102. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/archetypes/altruist.yaml +0 -0
  103. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/archetypes/conditional-cooperator.yaml +0 -0
  104. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/archetypes/free-rider.yaml +0 -0
  105. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/archetypes/inequity-averse.yaml +0 -0
  106. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/archetypes/risk-averse.yaml +0 -0
  107. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/archetypes/risk-seeking.yaml +0 -0
  108. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/archetypes/spiteful.yaml +0 -0
  109. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/archetypes/unconditional-cooperator.yaml +0 -0
  110. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/demographics/entrepreneur-ke-33.yaml +0 -0
  111. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/demographics/parent-br-42.yaml +0 -0
  112. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/demographics/professional-de-35.yaml +0 -0
  113. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/demographics/retiree-jp-68.yaml +0 -0
  114. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/demographics/student-us-21.yaml +0 -0
  115. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/demographics/teacher-no-48.yaml +0 -0
  116. {econagents-0.2.2 → econagents-0.2.3}/econagents/personas/library/demographics/worker-in-29.yaml +0 -0
  117. {econagents-0.2.2 → econagents-0.2.3}/econagents/ports/__init__.py +0 -0
  118. {econagents-0.2.2 → econagents-0.2.3}/econagents/ports/codec.py +0 -0
  119. {econagents-0.2.2 → econagents-0.2.3}/econagents/ports/llm.py +0 -0
  120. {econagents-0.2.2 → econagents-0.2.3}/econagents/ports/parsing.py +0 -0
  121. {econagents-0.2.2 → econagents-0.2.3}/econagents/ports/prompts.py +0 -0
  122. {econagents-0.2.2 → econagents-0.2.3}/econagents/ports/state.py +0 -0
  123. {econagents-0.2.2 → econagents-0.2.3}/econagents/ports/tools.py +0 -0
  124. {econagents-0.2.2 → econagents-0.2.3}/econagents/ports/transport.py +0 -0
  125. {econagents-0.2.2 → econagents-0.2.3}/econagents/py.typed +0 -0
  126. {econagents-0.2.2 → econagents-0.2.3}/econagents/runtime/__init__.py +0 -0
  127. {econagents-0.2.2 → econagents-0.2.3}/econagents/runtime/agent.py +0 -0
  128. {econagents-0.2.2 → econagents-0.2.3}/econagents/runtime/experiment_factory.py +0 -0
  129. {econagents-0.2.2 → econagents-0.2.3}/econagents/runtime/phase_engine.py +0 -0
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ All notable changes to econagents are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
5
+ follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.2.3] - 2026-06-29
8
+
9
+ ### Added
10
+
11
+ - Added a local continuous double auction example with LLM-backed traders,
12
+ structured market actions, a continuous `market` phase, a receive-only
13
+ `summary` phase, local verification, and OpenAI-backed run instructions.
14
+
15
+ ### Changed
16
+
17
+ - Updated game runner logging so an agent's WebSocket transport uses the
18
+ per-agent run logger, capturing transport send/receive events in the same
19
+ per-game log files as agent lifecycle events.
20
+ - Included local examples in source distributions while excluding generated
21
+ local run logs and game specs.
22
+
23
+ ## [0.2.2] - 2026-06-25
24
+
25
+ ### Added
26
+
27
+ - Added local verification scripts for the prisoner, dictator, and public goods
28
+ examples so each example can be exercised end-to-end against its local server
29
+ without making external LLM calls.
30
+
31
+ ### Changed
32
+
33
+ - Organized the package around explicit hexagonal boundaries:
34
+ `domain`, `ports`, `runtime`, and `adapters`.
35
+ - Moved protocol, transport, configuration, prompt, parser, state projection,
36
+ and LLM provider implementations under `econagents.adapters`.
37
+ - Moved roles, state models, events, and stable message types under
38
+ `econagents.domain`.
39
+ - Moved runtime orchestration, phase handling, experiment factories, and game
40
+ supervision under `econagents.runtime`.
41
+ - Renamed the participant runtime and behavior policy concepts to `Agent` and
42
+ `Role`.
43
+ - Renamed the YAML configuration entry point to `YamlExperimentLoader` and the
44
+ loaded YAML models to `ExperimentSpec`, `RoleSpec`, `AgentSpec`,
45
+ `StateSpec`, `RuntimeSpec`, and `RunnerSpec`.
46
+ - Renamed the default response parser to `JsonResponseParser`.
47
+ - Updated local examples and local servers for the refactored runtime,
48
+ transport, prompt rendering, and state projection APIs.
49
+ - Updated OpenAI-backed examples and documentation snippets to use
50
+ `gpt-5.4-mini`.
51
+ - Updated the prisoner YAML examples to use the standard submit-choice envelope
52
+ and explicit `phase` and `round` state fields.
53
+
54
+ ### Fixed
55
+
56
+ - Fixed prompt state resolution in local examples so prompts render the current
57
+ event-projected state, including per-round prisoner prompts, dictator payout
58
+ prompts, and public goods personality and payoff prompts.
59
+ - Fixed dictator local server payout ordering so phase-two prompts receive the
60
+ resolved decision and payout state before the payout phase starts.
61
+
62
+ [0.2.3]: https://github.com/IBEX-TUDelft/econagents/compare/v0.2.2...v0.2.3
63
+ [0.2.2]: https://github.com/IBEX-TUDelft/econagents/compare/v0.2.1...v0.2.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: econagents
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A Python library that lets you use LLM agents in economic experiments.
5
5
  Project-URL: Homepage, https://github.com/IBEX-TUDelft/econagents
6
6
  Project-URL: Repository, https://github.com/IBEX-TUDelft/econagents
@@ -81,11 +81,12 @@ econagents consists of these main components:
81
81
 
82
82
  ## Example Experiments
83
83
 
84
- The repository includes three example games:
84
+ The repository includes four example games:
85
85
 
86
86
  1. **`prisoner`**: An iterated Prisoner's Dilemma game with 5 rounds and 2 LLM agents, runs on a local python server (included).
87
87
  2. **`dictator`**: A modified Dictator game with 2 LLM agents that runs on a local python server (included).
88
88
  3. **`public_goods`**: A public goods game with 4 players that runs on a local python server (included).
89
+ 4. **`continuous_double_auction`**: A classical continuous double auction with LLM-backed traders using a continuous market phase.
89
90
 
90
91
  ### Running the Prisoner's Dilemma game
91
92
 
@@ -48,11 +48,12 @@ econagents consists of these main components:
48
48
 
49
49
  ## Example Experiments
50
50
 
51
- The repository includes three example games:
51
+ The repository includes four example games:
52
52
 
53
53
  1. **`prisoner`**: An iterated Prisoner's Dilemma game with 5 rounds and 2 LLM agents, runs on a local python server (included).
54
54
  2. **`dictator`**: A modified Dictator game with 2 LLM agents that runs on a local python server (included).
55
55
  3. **`public_goods`**: A public goods game with 4 players that runs on a local python server (included).
56
+ 4. **`continuous_double_auction`**: A classical continuous double auction with LLM-backed traders using a continuous market phase.
56
57
 
57
58
  ### Running the Prisoner's Dilemma game
58
59
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.2'
22
- __version_tuple__ = version_tuple = (0, 2, 2)
21
+ __version__ = version = '0.2.3'
22
+ __version_tuple__ = version_tuple = (0, 2, 3)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -289,6 +289,9 @@ class GameRunner:
289
289
  """
290
290
  agent_logger = self.get_agent_logger(agent_id, self.config.game_id)
291
291
  agent.logger = agent_logger
292
+ transport = getattr(agent, "transport", None)
293
+ if transport is not None and hasattr(transport, "logger"):
294
+ transport.logger = agent_logger
292
295
 
293
296
  async def spawn_agent(self, agent: Agent, agent_id: int) -> None:
294
297
  """
@@ -0,0 +1,51 @@
1
+ # Continuous Double Auction
2
+
3
+ This local example implements a continuous double auction, a standard market institution in experimental economics. Buyers have induced private values and cash endowments, sellers have induced private costs, and LLM-backed agents submit bids or asks while the market phase remains open.
4
+
5
+ The important runtime feature is the continuous phase:
6
+
7
+ - The server emits `phase-transition` with `phase="market"`.
8
+ - Each agent is configured with `PhaseEngine(continuous_phases={"market"}, min_action_delay=8, max_action_delay=8)`.
9
+ - The agent acts once on entry, then keeps calling the role's LLM-backed market phase until the server moves out of `market`.
10
+ - The default induced schedules give each of the two buyers and two sellers five marginal units. That allows up to ten total unit trades, with several more surplus-preserving trades than the original two-unit schedule.
11
+ - The default local run keeps the market open for 60 seconds, unless all possible units trade first.
12
+ - After the market closes, the server emits a receive-only `summary` phase with final trades, cash balances, and surplus. Agents do not send an action in this phase.
13
+
14
+ The traders use `ChatOpenAI` and structured output for market orders, so set `OPENAI_API_KEY` before running the LLM-backed experiment. You can set `OPENAI_MODEL` to override the default model.
15
+
16
+ ## Running The Example
17
+
18
+ Start the local WebSocket server:
19
+
20
+ ```bash
21
+ uv run python examples/continuous_double_auction/server/server.py
22
+ ```
23
+
24
+ In a separate terminal, run the agents:
25
+
26
+ ```bash
27
+ uv run python examples/continuous_double_auction/run_game.py
28
+ ```
29
+
30
+ The runner writes per-game and per-agent logs under `examples/continuous_double_auction/logs/game_<game_id>/`.
31
+ The local server writes submitted orders and trades to `examples/continuous_double_auction/logs/server.log`.
32
+
33
+ You can also run the end-to-end verification, which starts the server and agents in the same process:
34
+
35
+ ```bash
36
+ uv run python examples/continuous_double_auction/verify.py
37
+ ```
38
+
39
+ ## Files
40
+
41
+ ```text
42
+ continuous_double_auction/
43
+ ├── agents.py # LLM-backed trader role and structured action schema
44
+ ├── run_game.py # Local runner using HybridGameRunnerConfig
45
+ ├── prompts/ # Trader prompts used by the LLM role
46
+ ├── state.py # EventField-backed market state
47
+ ├── verify.py # Local end-to-end check with a deterministic LLM stub
48
+ └── server/
49
+ ├── create_game.py # Game spec and induced value/cost schedule
50
+ └── server.py # WebSocket CDA server
51
+ ```
@@ -0,0 +1 @@
1
+ """Continuous double auction example."""
@@ -0,0 +1,90 @@
1
+ from collections.abc import Iterable
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Literal
5
+
6
+ from dotenv import load_dotenv
7
+ from pydantic import BaseModel, Field
8
+
9
+ from econagents import Role
10
+ from econagents.adapters.llm import ChatOpenAI
11
+ from econagents.runtime import Agent, PhaseEngine, create_game_state
12
+ from econagents.runtime.game_runner import GameRunnerConfig, HybridGameRunnerConfig
13
+
14
+ from examples.continuous_double_auction.state import CDAGameState
15
+
16
+ REPO_ROOT = Path(__file__).resolve().parents[2]
17
+ load_dotenv(REPO_ROOT / ".env", override=True)
18
+
19
+ MARKET_PHASE = "market"
20
+ SUMMARY_PHASE = "summary"
21
+
22
+
23
+ class OrderComponent(BaseModel):
24
+ """Component marker for CDA order actions."""
25
+
26
+ type: Literal["continuous-double-auction:order"] = "continuous-double-auction:order"
27
+
28
+
29
+ class OrderMeta(BaseModel):
30
+ """Outbound action metadata emitted by the trader LLM."""
31
+
32
+ type: Literal["submit-order", "hold"]
33
+ component: OrderComponent = Field(default_factory=OrderComponent)
34
+
35
+
36
+ class OrderPayload(BaseModel):
37
+ """Outbound action payload emitted by the trader LLM."""
38
+
39
+ gameId: int
40
+ side: Literal["buy", "sell"] | None = None
41
+ price: float | None = Field(default=None, description="Bid or ask price. Required for submit-order.")
42
+ reason: str | None = Field(default=None, description="Short reason when holding instead of quoting.")
43
+
44
+
45
+ class MarketAction(BaseModel):
46
+ """Structured action emitted by an LLM trader during the continuous market."""
47
+
48
+ meta: OrderMeta
49
+ payload: OrderPayload
50
+
51
+
52
+ class Trader(Role[CDAGameState]):
53
+ """LLM-backed trader role for the local continuous double auction."""
54
+
55
+ role = 1
56
+ name = "trader"
57
+ llm = ChatOpenAI(
58
+ model_name=os.getenv("OPENAI_MODEL", "gpt-5.4-mini"),
59
+ response_kwargs={"max_output_tokens": 300},
60
+ )
61
+ task_phases = [MARKET_PHASE]
62
+ response_schemas = {MARKET_PHASE: MarketAction}
63
+
64
+
65
+ def create_cda_agent(*, config: GameRunnerConfig, recovery_code: str) -> Agent:
66
+ """Create one LLM-backed continuous double auction trader."""
67
+ if not isinstance(config, HybridGameRunnerConfig):
68
+ raise TypeError("The continuous double auction example requires HybridGameRunnerConfig.")
69
+
70
+ return Agent(
71
+ url=config.server_url(),
72
+ auth_mechanism=config.auth_mechanism,
73
+ auth_mechanism_kwargs={"gameId": config.game_id, "recovery": recovery_code},
74
+ state=create_game_state(CDAGameState, game_id=config.game_id),
75
+ role=Trader(),
76
+ prompts_dir=config.prompts_dir,
77
+ phase_transition_event=config.phase_transition_event,
78
+ phase_identifier_key=config.phase_identifier_key,
79
+ phase_engine=PhaseEngine(
80
+ continuous_phases=set(config.continuous_phases),
81
+ min_action_delay=config.min_action_delay,
82
+ max_action_delay=config.max_action_delay,
83
+ ),
84
+ end_game_event=config.end_game_event,
85
+ )
86
+
87
+
88
+ def create_cda_agents(config: GameRunnerConfig, recovery_codes: Iterable[str]) -> list[Agent]:
89
+ """Create all traders for a runner config."""
90
+ return [create_cda_agent(config=config, recovery_code=code) for code in recovery_codes]
@@ -0,0 +1,13 @@
1
+ You are a trader in a continuous double auction, a standard experimental economics market.
2
+
3
+ You have a private role:
4
+ - If you are a buyer, your limit price is your current private value. Submit buy bids at or below both that value and your cash balance.
5
+ - If you are a seller, your limit price is your current private cost. Submit sell asks at or above that cost.
6
+
7
+ Your goal is to earn surplus on each unit:
8
+ - Buyer surplus = value - transaction price.
9
+ - Seller surplus = transaction price - cost.
10
+
11
+ Cash balance is a real budget constraint for buyers. Cash flow is the net payments or receipts from trades. Realized surplus is your payoff so far.
12
+
13
+ Quote one order at a time. Keep prices valid for your role. Hold only if you have no remaining units, if your role is not assigned, or if every possible quote would violate your private limit.
@@ -0,0 +1,28 @@
1
+ Current market state:
2
+ - Game ID: {{ meta.game_id }}
3
+ - Trader role: {{ private_information.trader_type }}
4
+ - Current limit price: {{ private_information.limit_price }}
5
+ - Remaining units: {{ private_information.remaining_units }}
6
+ - Cash balance: {{ private_information.cash_balance }}
7
+ - Net cash flow: {{ private_information.cash_flow }}
8
+ - Realized surplus: {{ private_information.surplus }}
9
+ - Inventory: {{ private_information.inventory }}
10
+ - Your actions submitted so far: {{ private_information.actions_submitted }}
11
+ - Best bid: {{ public_information.best_bid }}
12
+ - Best ask: {{ public_information.best_ask }}
13
+ - Last transaction price: {{ public_information.last_price }}
14
+ - Total orders submitted: {{ public_information.order_count }}
15
+ - Transactions so far: {{ public_information.transactions }}
16
+
17
+ Return one structured action.
18
+
19
+ If you submit an order:
20
+ - Use meta.type = "submit-order".
21
+ - Use payload.gameId = {{ meta.game_id }}.
22
+ - Use payload.side = "buy" if you are a buyer, or "sell" if you are a seller.
23
+ - Use payload.price as your bid or ask.
24
+
25
+ If you hold:
26
+ - Use meta.type = "hold".
27
+ - Use payload.gameId = {{ meta.game_id }}.
28
+ - Use payload.reason to explain briefly.
@@ -0,0 +1,55 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from dotenv import load_dotenv
7
+
8
+ from econagents.runtime.game_runner import GameRunner, HybridGameRunnerConfig
9
+ from examples.continuous_double_auction.agents import MARKET_PHASE, create_cda_agents
10
+ from examples.continuous_double_auction.server.create_game import create_game_from_specs
11
+
12
+ logger = logging.getLogger("continuous_double_auction")
13
+
14
+ MARKET_DURATION_SECONDS = 60
15
+ SUMMARY_DURATION_SECONDS = 2
16
+ ACTION_DELAY_SECONDS = 8
17
+
18
+
19
+ async def main() -> None:
20
+ """Run a local continuous double auction with LLM-backed traders."""
21
+ logger.info("Starting Continuous Double Auction example")
22
+ env_path = Path(__file__).resolve().parents[2] / ".env"
23
+ load_dotenv(env_path, override=True)
24
+ logger.info(
25
+ "Loaded environment from %s; OPENAI_API_KEY is %s",
26
+ env_path,
27
+ "set" if os.getenv("OPENAI_API_KEY") else "not set",
28
+ )
29
+
30
+ game_specs = create_game_from_specs(
31
+ market_duration=MARKET_DURATION_SECONDS,
32
+ summary_duration=SUMMARY_DURATION_SECONDS,
33
+ )
34
+
35
+ config = HybridGameRunnerConfig(
36
+ game_id=game_specs["game_id"],
37
+ logs_dir=Path(__file__).parent / "logs",
38
+ prompts_dir=Path(__file__).parent / "prompts",
39
+ log_level=logging.DEBUG,
40
+ hostname="localhost",
41
+ port=8766,
42
+ path="",
43
+ continuous_phases=[MARKET_PHASE],
44
+ min_action_delay=ACTION_DELAY_SECONDS,
45
+ max_action_delay=ACTION_DELAY_SECONDS,
46
+ max_game_duration=game_specs["market_duration"] + game_specs["summary_duration"] + 45,
47
+ )
48
+ agents = create_cda_agents(config, game_specs["recovery_codes"])
49
+ runner = GameRunner(config=config, agents=agents)
50
+
51
+ await runner.run_game()
52
+
53
+
54
+ if __name__ == "__main__":
55
+ asyncio.run(main())
@@ -0,0 +1 @@
1
+ """Server for the continuous double auction example."""
@@ -0,0 +1,108 @@
1
+ import json
2
+ import logging
3
+ import uuid
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from dotenv import load_dotenv
9
+
10
+ load_dotenv()
11
+
12
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def default_trader_specs() -> list[dict[str, Any]]:
17
+ """Return a small induced-value schedule for a classic double auction."""
18
+ return [
19
+ {
20
+ "name": "Buyer 1",
21
+ "trader_type": "buyer",
22
+ "limit_prices": [110.0, 100.0, 90.0, 80.0, 70.0],
23
+ "cash_endowment": 450.0,
24
+ },
25
+ {
26
+ "name": "Buyer 2",
27
+ "trader_type": "buyer",
28
+ "limit_prices": [105.0, 95.0, 85.0, 75.0, 65.0],
29
+ "cash_endowment": 425.0,
30
+ },
31
+ {"name": "Seller 1", "trader_type": "seller", "limit_prices": [35.0, 45.0, 55.0, 65.0, 75.0]},
32
+ {"name": "Seller 2", "trader_type": "seller", "limit_prices": [40.0, 50.0, 60.0, 70.0, 80.0]},
33
+ ]
34
+
35
+
36
+ def generate_recovery_codes(num_players: int) -> list[str]:
37
+ """Generate recovery codes for all traders."""
38
+ return [str(uuid.uuid4()) for _ in range(num_players)]
39
+
40
+
41
+ def save_game_data(
42
+ specs_path: Path,
43
+ game_id: int,
44
+ game_name: str,
45
+ trader_specs: list[dict[str, Any]],
46
+ market_duration: int,
47
+ summary_duration: int,
48
+ ) -> Path:
49
+ """Save game data to the server specs directory."""
50
+ specs_dir = specs_path.parent / "games"
51
+ specs_dir.mkdir(parents=True, exist_ok=True)
52
+
53
+ recovery_codes = generate_recovery_codes(len(trader_specs))
54
+ traders = []
55
+ for spec, recovery in zip(trader_specs, recovery_codes):
56
+ traders.append({**spec, "recovery": recovery})
57
+
58
+ game_data = {
59
+ "game_id": game_id,
60
+ "game_name": game_name,
61
+ "num_players": len(traders),
62
+ "recovery_codes": recovery_codes,
63
+ "traders": traders,
64
+ "market_duration": market_duration,
65
+ "summary_duration": summary_duration,
66
+ "created_at": datetime.now().isoformat(),
67
+ }
68
+
69
+ output_file = specs_dir / f"game_{game_id}.json"
70
+ with output_file.open("w") as f:
71
+ json.dump(game_data, f, indent=2)
72
+ logger.info(f"Game data saved to {output_file}")
73
+ return output_file
74
+
75
+
76
+ def create_game_from_specs(
77
+ trader_specs: list[dict[str, Any]] | None = None,
78
+ market_duration: int = 60,
79
+ summary_duration: int = 2,
80
+ ) -> dict[str, Any]:
81
+ """Create a local continuous double auction game spec."""
82
+ game_id = int(datetime.now().timestamp())
83
+ game_name = f"Continuous Double Auction {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
84
+ trader_specs = trader_specs or default_trader_specs()
85
+
86
+ save_game_data(
87
+ specs_path=Path(__file__).parent / "games",
88
+ game_id=game_id,
89
+ game_name=game_name,
90
+ trader_specs=trader_specs,
91
+ market_duration=market_duration,
92
+ summary_duration=summary_duration,
93
+ )
94
+
95
+ recovery_codes = []
96
+ game_file = Path(__file__).parent / "games" / f"game_{game_id}.json"
97
+ with game_file.open("r") as f:
98
+ recovery_codes = json.load(f)["recovery_codes"]
99
+
100
+ return {
101
+ "game_id": game_id,
102
+ "game_name": game_name,
103
+ "num_players": len(trader_specs),
104
+ "recovery_codes": recovery_codes,
105
+ "market_duration": market_duration,
106
+ "summary_duration": summary_duration,
107
+ "created_at": datetime.now().isoformat(),
108
+ }