ark-audio-agent 0.1.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.
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.3
2
+ Name: ark-audio-agent
3
+ Version: 0.1.3
4
+ Summary: Autonomous text-to-music composer driven by a multi-agent ReAct harness (Claude API, with an offline deterministic fallback).
5
+ Keywords: music,generative,agents,react,claude,midi,synthesizer
6
+ Author: Immanuel R
7
+ Author-email: Immanuel R <raj@immanuel.co>
8
+ License: MIT
9
+ Requires-Dist: numpy>=1.24
10
+ Requires-Dist: mido>=1.3
11
+ Requires-Dist: rich>=13.7
12
+ Requires-Dist: anthropic>=0.107.1 ; extra == 'llm'
13
+ Requires-Python: >=3.10
14
+ Project-URL: Homepage, https://github.com/ir-dev/ark-audio-agent
15
+ Project-URL: Repository, https://github.com/ir-dev/ark-audio-agent
16
+ Project-URL: Issues, https://github.com/ir-dev/ark-audio-agent/issues
17
+ Provides-Extra: llm
18
+ Description-Content-Type: text/markdown
19
+
20
+ # ark-audio-agent
21
+
22
+ **Autonomous text → music.** Give it a sentence; it reasons about the feeling,
23
+ composes a structured piece, and renders it to MIDI + audio — with no human in
24
+ the loop.
25
+
26
+ The interesting part is the *harness*: composition is driven by a team of
27
+ specialist agents running a **ReAct loop** (reason → act → observe) over a shared
28
+ musical blackboard. It runs on the **Claude API** when a key is available, and on
29
+ a **fully-offline deterministic music-theory brain** otherwise, so it always
30
+ produces a result.
31
+
32
+ ```bash
33
+ ark-audio "a rainy night in a neon city, lonely but hopeful"
34
+ # → renders/a-rainy-night-in-a-neon-city-lonely-but-hopeful/song.mp3 (+ .wav, .mid, report.md, trace.jsonl)
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Why it's built this way
40
+
41
+ Rather than ask one model to emit raw notes (which drifts out of key and is hard
42
+ to steer), the system separates **musical intent** from **musical realization**:
43
+
44
+ - **Agents decide intent** — key, mode, tempo, song form, chord progressions, a
45
+ melody's contour/register/density, instrument roles, drum and bass styles.
46
+ - **Deterministic engines realize it** — pure music-theory functions turn that
47
+ intent into in-key notes, which a `mido` writer turns into MIDI and a
48
+ from-scratch `numpy` synthesizer turns into audio.
49
+
50
+ Because every decision an agent can make is a **tool**, both brains (Claude and
51
+ the offline planner) drive the exact same action surface and produce the exact
52
+ same trace format.
53
+
54
+ ### Architecture
55
+
56
+ ```
57
+ text prompt
58
+ │
59
+ ┌──────────▼───────────────────────────────────────────────┐
60
+ │ Orchestrator (the harness) │
61
+ │ │
62
+ │ Interpreter → Composer → Melodist → Arranger │ each agent runs a
63
+ │ │ │ ReAct loop:
64
+ │ ▼ │ reason (thinking)
65
+ │ Critic ──reject──┐ │ → act (tool call)
66
+ │ │approve │ one targeted │ → observe (result)
67
+ │ │ revision (bounded) │
68
+ └───────────────────────────────┼────────────┘ │
69
+ │ │
70
+ ┌───────────────────────────▼───────────────────────┐ │
71
+ │ Tools (the ReAct action space) │ │
72
+ │ set_brief · get_theory · set_global · │ │
73
+ │ set_structure · set_progression · design_melody · │ │
74
+ │ set_instruments · set_bass · set_drums · │ │
75
+ │ analyze_score · finish / approve / request_revision│ │
76
+ └───────────────────────────┬───────────────────────┘ │
77
+ ▼ │
78
+ Score blackboard (shared state) │
79
+ └──────────────────────────────┬─────────────────────────────┘
80
+ ▼
81
+ performance.py → { melody, harmony, bass, drums } notes
82
+ │
83
+ ┌───────────────────┴────────────────────┐
84
+ ▼ ▼
85
+ midi_render.py (mido) synth.py (numpy software synth)
86
+ │ │
87
+ song.mid song.wav ──(ffmpeg)──► song.mp3
88
+ ```
89
+
90
+ ### The agents
91
+
92
+ | Agent | Decides | Tools |
93
+ |---|---|---|
94
+ | **Interpreter** | mood, imagery, genre, key/mode/tempo, emotional arc | `set_brief`, `set_title` |
95
+ | **Composer** | time signature, section form + dynamic arc, chord progressions | `get_theory`, `set_global`, `set_structure`, `set_progression` |
96
+ | **Melodist** | per-section melody contour, register, density, syncopation | `design_melody`, `get_theory` |
97
+ | **Arranger** | instruments, per-section bass & drum styles | `set_instruments`, `set_bass`, `set_drums` |
98
+ | **Critic** | judges the whole piece; approves or bounces back one revision | `analyze_score`, `approve`, `request_revision` |
99
+
100
+ Every agent also has `analyze_score` (a sensing action) and a terminal `finish`.
101
+
102
+ ---
103
+
104
+ ## Install
105
+
106
+ Uses [uv](https://docs.astral.sh/uv/).
107
+
108
+ ```bash
109
+ uv sync --extra llm # installs the optional anthropic SDK for the Claude brain
110
+ # or, without the Claude brain (offline only):
111
+ uv sync
112
+ ```
113
+
114
+ `ffmpeg` is optional — if present, an MP3 is exported alongside the WAV.
115
+
116
+ ---
117
+
118
+ ## Usage
119
+
120
+ ### CLI
121
+
122
+ ```bash
123
+ ark-audio "triumphant sunrise over the mountains"
124
+ ark-audio "a quiet dawn over still water" --offline --seed 7
125
+ ark-audio "lonely synthwave drive at 2am" --out renders/drive --no-mp3
126
+ echo "a storm breaking over the sea" | ark-audio # reads stdin
127
+ ```
128
+
129
+ Flags: `--offline` (force the deterministic brain), `--seed`, `--out`,
130
+ `--model`, `--effort low|medium|high|xhigh|max`, `--max-revisions`, `--reverb`,
131
+ `--no-mp3`.
132
+
133
+ ### Library
134
+
135
+ ```python
136
+ from ark_audio_agent import compose
137
+
138
+ result = compose("a melancholy rainy afternoon", out_dir="renders/rain", offline=True)
139
+ print(result.outputs["wav"], "-", result.score.title)
140
+ ```
141
+
142
+ ### Outputs
143
+
144
+ Each run writes, into the output directory:
145
+
146
+ | File | What |
147
+ |---|---|
148
+ | `song.mid` | Standard MIDI file (open in any DAW / soundfont player) |
149
+ | `song.wav` | 44.1 kHz stereo audio from the built-in synth |
150
+ | `song.mp3` | 192 kbps MP3 (if `ffmpeg` is installed) |
151
+ | `score.json` | the full declarative Score (reload / tweak / re-render) |
152
+ | `brief.json` | compact human/LLM summary of the piece |
153
+ | `trace.jsonl` | every agent step: thought · action · observation |
154
+ | `report.md` | a readable write-up of the brief, structure, and agent trace |
155
+
156
+ ---
157
+
158
+ ## The two brains
159
+
160
+ **Claude multi-agent** (default when an API key is set). Each agent runs
161
+ the canonical Messages-API tool-use loop with adaptive thinking; the model
162
+ reasons, calls a tool, reads the observation, and continues until it finishes.
163
+ Defaults to `claude-opus-5-5`. The API call degrades gracefully across
164
+ SDK/account differences (server-side refusal fallback → plain streaming → drop
165
+ effort → drop thinking), so the run never hard-fails on a capability mismatch.
166
+
167
+ ```bash
168
+ export ANTHROPIC_API_KEY=sk-ant-...
169
+ ark-audio "a cathedral of light at the edge of the world" --effort high
170
+ ```
171
+
172
+ **Claude on Microsoft Foundry (Azure).** Point the brain at a Foundry resource
173
+ instead and it uses the SDK's `AnthropicFoundry` client with API-key auth. A
174
+ Foundry endpoint takes precedence over `ANTHROPIC_API_KEY`:
175
+
176
+ ```bash
177
+ export ANTHROPIC_FOUNDRY_API_KEY=...
178
+ export ANTHROPIC_FOUNDRY_RESOURCE=my-resource # or ANTHROPIC_FOUNDRY_BASE_URL=https://my-resource.services.ai.azure.com/anthropic/
179
+ export ARK_MODEL=claude-opus-5-5 # your Foundry deployment name
180
+ ark-audio "a cathedral of light at the edge of the world"
181
+ ```
182
+
183
+ Server-side refusal fallback is a first-party API feature, so it is skipped on
184
+ Foundry. Adaptive thinking and effort still apply.
185
+
186
+ **Deterministic** (offline, always available). A rule-based planner maps the
187
+ text to a feeling via a lexicon, then applies music-theory heuristics to make the
188
+ same tool calls an LLM would. Reproducible given a `--seed`. This is what runs in
189
+ CI and what powers `--offline`.
190
+
191
+ > If no key is found (and `--offline` isn't set), the CLI automatically uses the
192
+ > deterministic brain and says so.
193
+
194
+ ---
195
+
196
+ ## How the music is realized
197
+
198
+ - **Theory** (`theory.py`): scales/modes, diatonic chords, Roman-numeral
199
+ resolution, scale-degree → MIDI, equal-temperament frequencies.
200
+ - **Blackboard** (`blackboard.py`): the declarative `Score` the agents edit.
201
+ - **Performance** (`performance.py`): realizes the Score into notes — a
202
+ contour-driven melody that snaps to chord tones on strong beats, chord comping
203
+ or sustained pads, several bass styles (roots, root-fifth, pulse, walking, arp),
204
+ and synthesized drum patterns (four-on-floor, backbeat, breakbeat, …), all
205
+ seeded and reproducible.
206
+ - **Synth** (`synth.py`): additive (band-limited) oscillator voices with ADSR,
207
+ per-instrument patches, synthesized percussion, an FFT-convolution reverb, and
208
+ a soft-clipping master bus.
209
+
210
+ ---
211
+
212
+ ## Development
213
+
214
+ ```bash
215
+ uv run pytest # theory + pipeline tests (offline, no key needed)
216
+ uv run ark-audio "..." --offline
217
+ ```
218
+
219
+ ## License
220
+
221
+ MIT.
@@ -0,0 +1,202 @@
1
+ # ark-audio-agent
2
+
3
+ **Autonomous text → music.** Give it a sentence; it reasons about the feeling,
4
+ composes a structured piece, and renders it to MIDI + audio — with no human in
5
+ the loop.
6
+
7
+ The interesting part is the *harness*: composition is driven by a team of
8
+ specialist agents running a **ReAct loop** (reason → act → observe) over a shared
9
+ musical blackboard. It runs on the **Claude API** when a key is available, and on
10
+ a **fully-offline deterministic music-theory brain** otherwise, so it always
11
+ produces a result.
12
+
13
+ ```bash
14
+ ark-audio "a rainy night in a neon city, lonely but hopeful"
15
+ # → renders/a-rainy-night-in-a-neon-city-lonely-but-hopeful/song.mp3 (+ .wav, .mid, report.md, trace.jsonl)
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Why it's built this way
21
+
22
+ Rather than ask one model to emit raw notes (which drifts out of key and is hard
23
+ to steer), the system separates **musical intent** from **musical realization**:
24
+
25
+ - **Agents decide intent** — key, mode, tempo, song form, chord progressions, a
26
+ melody's contour/register/density, instrument roles, drum and bass styles.
27
+ - **Deterministic engines realize it** — pure music-theory functions turn that
28
+ intent into in-key notes, which a `mido` writer turns into MIDI and a
29
+ from-scratch `numpy` synthesizer turns into audio.
30
+
31
+ Because every decision an agent can make is a **tool**, both brains (Claude and
32
+ the offline planner) drive the exact same action surface and produce the exact
33
+ same trace format.
34
+
35
+ ### Architecture
36
+
37
+ ```
38
+ text prompt
39
+ │
40
+ ┌──────────▼───────────────────────────────────────────────┐
41
+ │ Orchestrator (the harness) │
42
+ │ │
43
+ │ Interpreter → Composer → Melodist → Arranger │ each agent runs a
44
+ │ │ │ ReAct loop:
45
+ │ ▼ │ reason (thinking)
46
+ │ Critic ──reject──┐ │ → act (tool call)
47
+ │ │approve │ one targeted │ → observe (result)
48
+ │ │ revision (bounded) │
49
+ └───────────────────────────────┼────────────┘ │
50
+ │ │
51
+ ┌───────────────────────────▼───────────────────────┐ │
52
+ │ Tools (the ReAct action space) │ │
53
+ │ set_brief · get_theory · set_global · │ │
54
+ │ set_structure · set_progression · design_melody · │ │
55
+ │ set_instruments · set_bass · set_drums · │ │
56
+ │ analyze_score · finish / approve / request_revision│ │
57
+ └───────────────────────────┬───────────────────────┘ │
58
+ ▼ │
59
+ Score blackboard (shared state) │
60
+ └──────────────────────────────┬─────────────────────────────┘
61
+ ▼
62
+ performance.py → { melody, harmony, bass, drums } notes
63
+ │
64
+ ┌───────────────────┴────────────────────┐
65
+ ▼ ▼
66
+ midi_render.py (mido) synth.py (numpy software synth)
67
+ │ │
68
+ song.mid song.wav ──(ffmpeg)──► song.mp3
69
+ ```
70
+
71
+ ### The agents
72
+
73
+ | Agent | Decides | Tools |
74
+ |---|---|---|
75
+ | **Interpreter** | mood, imagery, genre, key/mode/tempo, emotional arc | `set_brief`, `set_title` |
76
+ | **Composer** | time signature, section form + dynamic arc, chord progressions | `get_theory`, `set_global`, `set_structure`, `set_progression` |
77
+ | **Melodist** | per-section melody contour, register, density, syncopation | `design_melody`, `get_theory` |
78
+ | **Arranger** | instruments, per-section bass & drum styles | `set_instruments`, `set_bass`, `set_drums` |
79
+ | **Critic** | judges the whole piece; approves or bounces back one revision | `analyze_score`, `approve`, `request_revision` |
80
+
81
+ Every agent also has `analyze_score` (a sensing action) and a terminal `finish`.
82
+
83
+ ---
84
+
85
+ ## Install
86
+
87
+ Uses [uv](https://docs.astral.sh/uv/).
88
+
89
+ ```bash
90
+ uv sync --extra llm # installs the optional anthropic SDK for the Claude brain
91
+ # or, without the Claude brain (offline only):
92
+ uv sync
93
+ ```
94
+
95
+ `ffmpeg` is optional — if present, an MP3 is exported alongside the WAV.
96
+
97
+ ---
98
+
99
+ ## Usage
100
+
101
+ ### CLI
102
+
103
+ ```bash
104
+ ark-audio "triumphant sunrise over the mountains"
105
+ ark-audio "a quiet dawn over still water" --offline --seed 7
106
+ ark-audio "lonely synthwave drive at 2am" --out renders/drive --no-mp3
107
+ echo "a storm breaking over the sea" | ark-audio # reads stdin
108
+ ```
109
+
110
+ Flags: `--offline` (force the deterministic brain), `--seed`, `--out`,
111
+ `--model`, `--effort low|medium|high|xhigh|max`, `--max-revisions`, `--reverb`,
112
+ `--no-mp3`.
113
+
114
+ ### Library
115
+
116
+ ```python
117
+ from ark_audio_agent import compose
118
+
119
+ result = compose("a melancholy rainy afternoon", out_dir="renders/rain", offline=True)
120
+ print(result.outputs["wav"], "-", result.score.title)
121
+ ```
122
+
123
+ ### Outputs
124
+
125
+ Each run writes, into the output directory:
126
+
127
+ | File | What |
128
+ |---|---|
129
+ | `song.mid` | Standard MIDI file (open in any DAW / soundfont player) |
130
+ | `song.wav` | 44.1 kHz stereo audio from the built-in synth |
131
+ | `song.mp3` | 192 kbps MP3 (if `ffmpeg` is installed) |
132
+ | `score.json` | the full declarative Score (reload / tweak / re-render) |
133
+ | `brief.json` | compact human/LLM summary of the piece |
134
+ | `trace.jsonl` | every agent step: thought · action · observation |
135
+ | `report.md` | a readable write-up of the brief, structure, and agent trace |
136
+
137
+ ---
138
+
139
+ ## The two brains
140
+
141
+ **Claude multi-agent** (default when an API key is set). Each agent runs
142
+ the canonical Messages-API tool-use loop with adaptive thinking; the model
143
+ reasons, calls a tool, reads the observation, and continues until it finishes.
144
+ Defaults to `claude-opus-5-5`. The API call degrades gracefully across
145
+ SDK/account differences (server-side refusal fallback → plain streaming → drop
146
+ effort → drop thinking), so the run never hard-fails on a capability mismatch.
147
+
148
+ ```bash
149
+ export ANTHROPIC_API_KEY=sk-ant-...
150
+ ark-audio "a cathedral of light at the edge of the world" --effort high
151
+ ```
152
+
153
+ **Claude on Microsoft Foundry (Azure).** Point the brain at a Foundry resource
154
+ instead and it uses the SDK's `AnthropicFoundry` client with API-key auth. A
155
+ Foundry endpoint takes precedence over `ANTHROPIC_API_KEY`:
156
+
157
+ ```bash
158
+ export ANTHROPIC_FOUNDRY_API_KEY=...
159
+ export ANTHROPIC_FOUNDRY_RESOURCE=my-resource # or ANTHROPIC_FOUNDRY_BASE_URL=https://my-resource.services.ai.azure.com/anthropic/
160
+ export ARK_MODEL=claude-opus-5-5 # your Foundry deployment name
161
+ ark-audio "a cathedral of light at the edge of the world"
162
+ ```
163
+
164
+ Server-side refusal fallback is a first-party API feature, so it is skipped on
165
+ Foundry. Adaptive thinking and effort still apply.
166
+
167
+ **Deterministic** (offline, always available). A rule-based planner maps the
168
+ text to a feeling via a lexicon, then applies music-theory heuristics to make the
169
+ same tool calls an LLM would. Reproducible given a `--seed`. This is what runs in
170
+ CI and what powers `--offline`.
171
+
172
+ > If no key is found (and `--offline` isn't set), the CLI automatically uses the
173
+ > deterministic brain and says so.
174
+
175
+ ---
176
+
177
+ ## How the music is realized
178
+
179
+ - **Theory** (`theory.py`): scales/modes, diatonic chords, Roman-numeral
180
+ resolution, scale-degree → MIDI, equal-temperament frequencies.
181
+ - **Blackboard** (`blackboard.py`): the declarative `Score` the agents edit.
182
+ - **Performance** (`performance.py`): realizes the Score into notes — a
183
+ contour-driven melody that snaps to chord tones on strong beats, chord comping
184
+ or sustained pads, several bass styles (roots, root-fifth, pulse, walking, arp),
185
+ and synthesized drum patterns (four-on-floor, backbeat, breakbeat, …), all
186
+ seeded and reproducible.
187
+ - **Synth** (`synth.py`): additive (band-limited) oscillator voices with ADSR,
188
+ per-instrument patches, synthesized percussion, an FFT-convolution reverb, and
189
+ a soft-clipping master bus.
190
+
191
+ ---
192
+
193
+ ## Development
194
+
195
+ ```bash
196
+ uv run pytest # theory + pipeline tests (offline, no key needed)
197
+ uv run ark-audio "..." --offline
198
+ ```
199
+
200
+ ## License
201
+
202
+ MIT.
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "ark-audio-agent"
3
+ version = "0.1.3"
4
+ description = "Autonomous text-to-music composer driven by a multi-agent ReAct harness (Claude API, with an offline deterministic fallback)."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ keywords = [
8
+ "music",
9
+ "generative",
10
+ "agents",
11
+ "react",
12
+ "claude",
13
+ "midi",
14
+ "synthesizer",
15
+ ]
16
+ dependencies = [
17
+ "numpy>=1.24",
18
+ "mido>=1.3",
19
+ "rich>=13.7",
20
+ ]
21
+
22
+ [[project.authors]]
23
+ name = "Immanuel R"
24
+ email = "raj@immanuel.co"
25
+
26
+ [project.license]
27
+ text = "MIT"
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/ir-dev/ark-audio-agent"
31
+ Repository = "https://github.com/ir-dev/ark-audio-agent"
32
+ Issues = "https://github.com/ir-dev/ark-audio-agent/issues"
33
+
34
+ [project.optional-dependencies]
35
+ llm = ["anthropic>=0.107.1"]
36
+
37
+ [project.scripts]
38
+ ark-audio = "ark_audio_agent.cli:main"
39
+
40
+ [dependency-groups]
41
+ dev = ["pytest>=8.0"]
42
+
43
+ [build-system]
44
+ requires = ["uv_build>=0.12.7,<0.13.0"]
45
+ build-backend = "uv_build"
@@ -0,0 +1,35 @@
1
+ [project]
2
+ name = "ark-audio-agent"
3
+ version = "0.1.3"
4
+ description = "Autonomous text-to-music composer driven by a multi-agent ReAct harness (Claude API, with an offline deterministic fallback)."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [{ name = "Immanuel R", email = "raj@immanuel.co" }]
8
+ license = { text = "MIT" }
9
+ keywords = ["music", "generative", "agents", "react", "claude", "midi", "synthesizer"]
10
+ dependencies = [
11
+ "numpy>=1.24",
12
+ "mido>=1.3",
13
+ "rich>=13.7",
14
+ ]
15
+
16
+ [project.urls]
17
+ Homepage = "https://github.com/ir-dev/ark-audio-agent"
18
+ Repository = "https://github.com/ir-dev/ark-audio-agent"
19
+ Issues = "https://github.com/ir-dev/ark-audio-agent/issues"
20
+
21
+ [project.optional-dependencies]
22
+ # Claude-powered multi-agent brain. Without this (or without an API key),
23
+ # the app runs fully offline on the deterministic music-theory brain.
24
+ # 0.107.1 is the first release whose AnthropicFoundry sends API-key auth correctly.
25
+ llm = ["anthropic>=0.107.1"]
26
+
27
+ [project.scripts]
28
+ ark-audio = "ark_audio_agent.cli:main"
29
+
30
+ [dependency-groups]
31
+ dev = ["pytest>=8.0"]
32
+
33
+ [build-system]
34
+ requires = ["uv_build>=0.12.7,<0.13.0"]
35
+ build-backend = "uv_build"
@@ -0,0 +1,48 @@
1
+ """ark-audio-agent — autonomous text-to-music via a multi-agent ReAct harness.
2
+
3
+ Quick start (library):
4
+
5
+ from ark_audio_agent import compose
6
+ result = compose("a quiet dawn over still water", out_dir="renders/dawn")
7
+ print(result.outputs["wav"])
8
+
9
+ By default ``compose`` uses the Claude multi-agent brain when an
10
+ ``ANTHROPIC_API_KEY`` (or a Microsoft Foundry endpoint and
11
+ ``ANTHROPIC_FOUNDRY_API_KEY``) is set, and the fully-offline deterministic
12
+ brain otherwise. Pass ``offline=True`` to force the latter.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ __version__ = "0.1.3"
18
+
19
+ __all__ = ["compose", "Orchestrator", "DeterministicBrain", "RunResult", "__version__"]
20
+
21
+
22
+ def compose(source_text: str, out_dir: str = "renders/piece", *, seed: int = 42,
23
+ offline: bool = False, max_revisions: int = 2, make_mp3: bool = True,
24
+ on_event=None):
25
+ """Compose a piece from text and render it. Returns a ``RunResult``."""
26
+ from .orchestrator import Orchestrator
27
+
28
+ brain = _default_brain(offline=offline, seed=seed)
29
+ orch = Orchestrator(brain, max_revisions=max_revisions, make_mp3=make_mp3)
30
+ return orch.compose(source_text, out_dir, seed=seed, on_event=on_event)
31
+
32
+
33
+ def _default_brain(*, offline: bool, seed: int):
34
+ from .brain import DeterministicBrain
35
+ if offline:
36
+ return DeterministicBrain(seed=seed)
37
+ try:
38
+ from .llm import LLMBrain, anthropic_available
39
+ if anthropic_available():
40
+ return LLMBrain()
41
+ except Exception: # noqa: BLE001
42
+ pass
43
+ return DeterministicBrain(seed=seed)
44
+
45
+
46
+ # re-exports (kept lazy-friendly but convenient)
47
+ from .brain import DeterministicBrain # noqa: E402
48
+ from .orchestrator import Orchestrator, RunResult # noqa: E402