trinity-agent 0.2.2__tar.gz → 0.3.0__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.
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/PKG-INFO +1 -1
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/pyproject.toml +1 -1
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/__init__.py +1 -1
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/deliberation/protocol.py +291 -218
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/orchestrator.py +11 -0
- trinity_agent-0.3.0/src/trinity/tui/app.py +584 -0
- trinity_agent-0.3.0/src/trinity/tui/events.py +76 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/tui/session.py +39 -54
- trinity_agent-0.3.0/src/trinity/tui/theme.py +88 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_tui.py +194 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_tui_session.py +45 -0
- trinity_agent-0.2.2/src/trinity/tui/app.py +0 -396
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/.gitignore +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/LICENSE +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/README.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/docs/checkpoint.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/docs/phase-6-plan.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/docs/reference-architecture.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/docs/test-results/phase-1-T.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/docs/test-results/phase-2-T.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/docs/test-results/phase-3-T.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/docs/test-results/phase-4-T.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/docs/test-results/phase-5-T.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/docs/test-results/phase-6-T.md +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/__main__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/agents/__init__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/agents/base.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/agents/claude_agent.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/agents/codex_agent.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/agents/factory.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/agents/gemini_agent.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/cli.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/completion/__init__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/completion/base.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/completion/hook.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/completion/idle.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/completion/prompt.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/config.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/context/__init__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/context/monitor.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/context/rotator.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/context/shared.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/deliberation/__init__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/deliberation/consensus.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/deliberation/distributor.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/error_handler.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/health/__init__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/health/checker.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/logging.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/models.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/retry.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/setup/__init__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/setup/detector.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/setup/wizard.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/tmux/__init__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/tmux/layout.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/tmux/pane.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/tmux/session.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/tui/__init__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/workspace/__init__.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/workspace/isolation.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/src/trinity/workspace/managed_home.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/templates/trinity.config.example +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/conftest.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_agent_factory.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_claude_agent.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_cli.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_cli_detector.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_cli_v2.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_codex_agent.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_completion.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_config.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_consensus_v2.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_context_monitor.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_deliberation.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_e2e.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_error_handling.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_gemini_agent.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_health_checker.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_interactive_claude.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_logging.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_managed_home.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_models.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_multi_provider.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_orchestrator.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_protocol.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_protocol_v2.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_retry.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_session_handoff.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_session_rotator.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_setup_wizard.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_shared_context.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_tmux.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_tmux_integration.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_tmux_layout.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/tests/test_workspace.py +0 -0
- {trinity_agent-0.2.2 → trinity_agent-0.3.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trinity-agent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Three minds, one context — Multi-agent AI orchestrator for Claude Code, Codex, and Gemini CLI.
|
|
5
5
|
Project-URL: Homepage, https://github.com/hongdangmoo49/Trinity
|
|
6
6
|
Project-URL: Repository, https://github.com/hongdangmoo49/Trinity
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "trinity-agent"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Three minds, one context — Multi-agent AI orchestrator for Claude Code, Codex, and Gemini CLI."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -1,218 +1,291 @@
|
|
|
1
|
-
"""Deliberation protocol — round-based deliberation loop."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import logging
|
|
7
|
-
import time
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
from trinity.
|
|
11
|
-
from trinity.
|
|
12
|
-
from trinity.deliberation.
|
|
13
|
-
from trinity.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
1
|
+
"""Deliberation protocol — round-based deliberation loop."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from typing import Callable
|
|
9
|
+
|
|
10
|
+
from trinity.agents.base import AgentWrapper
|
|
11
|
+
from trinity.context.shared import SharedContextEngine
|
|
12
|
+
from trinity.deliberation.consensus import ConsensusEngine
|
|
13
|
+
from trinity.deliberation.distributor import TaskDistributor
|
|
14
|
+
from trinity.models import (
|
|
15
|
+
ConsensusResult,
|
|
16
|
+
DeliberationMessage,
|
|
17
|
+
DeliberationResult,
|
|
18
|
+
MessageRole,
|
|
19
|
+
TaskAssignment,
|
|
20
|
+
)
|
|
21
|
+
from trinity.tui.events import TUIEvent, TUIEventType
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DeliberationProtocol:
|
|
27
|
+
"""Round-based deliberation: opinions → counter → consensus → tasks.
|
|
28
|
+
|
|
29
|
+
Each round:
|
|
30
|
+
1. Build a round-specific prompt for all agents
|
|
31
|
+
2. Send to all agents in parallel (with per-agent completion streaming)
|
|
32
|
+
3. Collect responses → write to shared.md
|
|
33
|
+
4. Check for consensus
|
|
34
|
+
5. If reached, distribute tasks. Otherwise, next round.
|
|
35
|
+
|
|
36
|
+
When an event_callback is provided, events are emitted for each
|
|
37
|
+
agent completion, round transition, and consensus evaluation,
|
|
38
|
+
enabling real-time TUI updates.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
agents: dict[str, AgentWrapper],
|
|
44
|
+
shared: SharedContextEngine,
|
|
45
|
+
consensus_engine: ConsensusEngine | None = None,
|
|
46
|
+
distributor: TaskDistributor | None = None,
|
|
47
|
+
max_rounds: int = 5,
|
|
48
|
+
round_timeout: float = 120.0,
|
|
49
|
+
tmux_manager=None,
|
|
50
|
+
event_callback: Callable[[TUIEvent], None] | None = None,
|
|
51
|
+
):
|
|
52
|
+
self.agents = agents
|
|
53
|
+
self.shared = shared
|
|
54
|
+
self.consensus_engine = consensus_engine or ConsensusEngine()
|
|
55
|
+
self.distributor = distributor or TaskDistributor()
|
|
56
|
+
self.max_rounds = max_rounds
|
|
57
|
+
self.round_timeout = round_timeout
|
|
58
|
+
self.tmux_manager = tmux_manager
|
|
59
|
+
self._event_callback = event_callback
|
|
60
|
+
|
|
61
|
+
def _emit(self, event_type: TUIEventType, **kwargs) -> None:
|
|
62
|
+
"""Emit a TUI event if callback is registered."""
|
|
63
|
+
if self._event_callback:
|
|
64
|
+
self._event_callback(TUIEvent(type=event_type, data=kwargs))
|
|
65
|
+
|
|
66
|
+
async def run(self, user_prompt: str) -> DeliberationResult:
|
|
67
|
+
"""Execute full deliberation loop."""
|
|
68
|
+
start_time = time.time()
|
|
69
|
+
agent_names = list(self.agents.keys())
|
|
70
|
+
|
|
71
|
+
# Initialize shared.md
|
|
72
|
+
self.shared.initialize(goal=user_prompt, agent_names=agent_names)
|
|
73
|
+
|
|
74
|
+
consensus: ConsensusResult | None = None
|
|
75
|
+
round_num = 0
|
|
76
|
+
|
|
77
|
+
for round_num in range(1, self.max_rounds + 1):
|
|
78
|
+
logger.info(f"=== Round {round_num}/{self.max_rounds} ===")
|
|
79
|
+
|
|
80
|
+
# Emit round start
|
|
81
|
+
self._emit(TUIEventType.ROUND_START, round_num=round_num)
|
|
82
|
+
|
|
83
|
+
# Update tmux pane titles to show round progress
|
|
84
|
+
self._update_pane_titles(f"Round {round_num}/{self.max_rounds}")
|
|
85
|
+
|
|
86
|
+
# Build prompt for this round
|
|
87
|
+
round_prompt = self._build_round_prompt(round_num, user_prompt)
|
|
88
|
+
|
|
89
|
+
# Collect opinions from all agents (with per-agent streaming)
|
|
90
|
+
opinions = await self._collect_opinions(round_num, round_prompt)
|
|
91
|
+
|
|
92
|
+
# Write opinions to shared.md
|
|
93
|
+
for name, msg in opinions.items():
|
|
94
|
+
self.shared.append_opinion(name, round_num, msg.content)
|
|
95
|
+
|
|
96
|
+
# Update message round_num (it was set to 0 in agent)
|
|
97
|
+
for name, msg in opinions.items():
|
|
98
|
+
msg.round_num = round_num
|
|
99
|
+
|
|
100
|
+
# Check consensus
|
|
101
|
+
self._emit(TUIEventType.CONSENSUS_CHECKING, round_num=round_num)
|
|
102
|
+
|
|
103
|
+
opinion_texts = {name: msg.content for name, msg in opinions.items()}
|
|
104
|
+
consensus = self.consensus_engine.evaluate(opinion_texts)
|
|
105
|
+
|
|
106
|
+
if consensus.reached:
|
|
107
|
+
logger.info(f"Consensus reached at round {round_num}!")
|
|
108
|
+
self.shared.update_consensus(consensus.summary)
|
|
109
|
+
self._update_pane_titles("✓ Consensus!")
|
|
110
|
+
|
|
111
|
+
self._emit(
|
|
112
|
+
TUIEventType.CONSENSUS_RESULT,
|
|
113
|
+
reached=True,
|
|
114
|
+
agreement_count=consensus.agreement_count,
|
|
115
|
+
total_agents=consensus.total_agents,
|
|
116
|
+
summary=consensus.summary,
|
|
117
|
+
round_num=round_num,
|
|
118
|
+
)
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
logger.info(f"No consensus yet. Continuing to round {round_num + 1}.")
|
|
122
|
+
|
|
123
|
+
self._emit(
|
|
124
|
+
TUIEventType.CONSENSUS_RESULT,
|
|
125
|
+
reached=False,
|
|
126
|
+
agreement_count=consensus.agreement_count,
|
|
127
|
+
total_agents=consensus.total_agents,
|
|
128
|
+
summary="",
|
|
129
|
+
round_num=round_num,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Update pane titles for task distribution phase
|
|
133
|
+
self._update_pane_titles("Distributing tasks...")
|
|
134
|
+
|
|
135
|
+
# If no consensus after all rounds, force conclusion
|
|
136
|
+
if consensus and not consensus.reached:
|
|
137
|
+
logger.warning(f"Max rounds ({self.max_rounds}) reached. Forcing conclusion.")
|
|
138
|
+
consensus = ConsensusResult(
|
|
139
|
+
reached=True, # Force it
|
|
140
|
+
agreement_count=consensus.agreement_count,
|
|
141
|
+
total_agents=consensus.total_agents,
|
|
142
|
+
opinions=consensus.opinions,
|
|
143
|
+
summary=f"Forced conclusion after {self.max_rounds} rounds. "
|
|
144
|
+
f"Majority opinion selected.",
|
|
145
|
+
)
|
|
146
|
+
self.shared.update_consensus(consensus.summary)
|
|
147
|
+
|
|
148
|
+
# Distribute tasks
|
|
149
|
+
tasks = self.distributor.distribute(
|
|
150
|
+
consensus_text=consensus.summary if consensus else user_prompt,
|
|
151
|
+
agents={name: ag.spec for name, ag in self.agents.items()},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Write tasks to shared.md
|
|
155
|
+
task_dict = {t.agent_name: t.task_description for t in tasks}
|
|
156
|
+
self.shared.update_tasks(task_dict)
|
|
157
|
+
|
|
158
|
+
# Calculate totals
|
|
159
|
+
total_tokens = sum(
|
|
160
|
+
ag.context_usage.used for ag in self.agents.values()
|
|
161
|
+
)
|
|
162
|
+
elapsed = time.time() - start_time
|
|
163
|
+
|
|
164
|
+
self._emit(TUIEventType.DELIBERATION_DONE)
|
|
165
|
+
|
|
166
|
+
return DeliberationResult(
|
|
167
|
+
user_prompt=user_prompt,
|
|
168
|
+
rounds_completed=round_num,
|
|
169
|
+
consensus=consensus,
|
|
170
|
+
tasks=tasks,
|
|
171
|
+
total_tokens_used=total_tokens,
|
|
172
|
+
duration_seconds=elapsed,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
async def _collect_opinions(
|
|
176
|
+
self, round_num: int, prompt: str
|
|
177
|
+
) -> dict[str, DeliberationMessage]:
|
|
178
|
+
"""Send prompt to all agents in parallel and collect responses.
|
|
179
|
+
|
|
180
|
+
Uses asyncio.wait(FIRST_COMPLETED) instead of asyncio.gather
|
|
181
|
+
to enable per-agent completion streaming via events.
|
|
182
|
+
"""
|
|
183
|
+
# Create tasks with agent names attached
|
|
184
|
+
pending: set[asyncio.Task] = set()
|
|
185
|
+
task_to_name: dict[asyncio.Task, str] = {}
|
|
186
|
+
|
|
187
|
+
for name, agent in self.agents.items():
|
|
188
|
+
coro = agent.send_and_wait(prompt, timeout=self.round_timeout)
|
|
189
|
+
task = asyncio.ensure_future(coro)
|
|
190
|
+
task_to_name[task] = name
|
|
191
|
+
pending.add(task)
|
|
192
|
+
self._emit(TUIEventType.AGENT_THINKING, agent=name, round_num=round_num)
|
|
193
|
+
|
|
194
|
+
opinions: dict[str, DeliberationMessage] = {}
|
|
195
|
+
|
|
196
|
+
while pending:
|
|
197
|
+
done, pending = await asyncio.wait(
|
|
198
|
+
pending, return_when=asyncio.FIRST_COMPLETED
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
for task in done:
|
|
202
|
+
name = task_to_name[task]
|
|
203
|
+
try:
|
|
204
|
+
result = task.result()
|
|
205
|
+
except Exception as exc:
|
|
206
|
+
logger.error(f"[{name}] Error in round {round_num}: {exc}")
|
|
207
|
+
opinions[name] = DeliberationMessage(
|
|
208
|
+
source=name,
|
|
209
|
+
target="all",
|
|
210
|
+
round_num=round_num,
|
|
211
|
+
role=MessageRole.OPINION,
|
|
212
|
+
content=f"[Error: {exc}]",
|
|
213
|
+
)
|
|
214
|
+
self._emit(
|
|
215
|
+
TUIEventType.AGENT_ERROR,
|
|
216
|
+
agent=name,
|
|
217
|
+
error=str(exc),
|
|
218
|
+
round_num=round_num,
|
|
219
|
+
)
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
if isinstance(result, DeliberationMessage):
|
|
223
|
+
result.round_num = round_num
|
|
224
|
+
opinions[name] = result
|
|
225
|
+
self._emit(
|
|
226
|
+
TUIEventType.AGENT_RESPONDED,
|
|
227
|
+
agent=name,
|
|
228
|
+
content=result.content,
|
|
229
|
+
round_num=round_num,
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
logger.warning(f"[{name}] Unexpected result type: {type(result)}")
|
|
233
|
+
self._emit(
|
|
234
|
+
TUIEventType.AGENT_ERROR,
|
|
235
|
+
agent=name,
|
|
236
|
+
error=f"Unexpected result type: {type(result)}",
|
|
237
|
+
round_num=round_num,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return opinions
|
|
241
|
+
|
|
242
|
+
def _build_round_prompt(self, round_num: int, user_prompt: str) -> str:
|
|
243
|
+
"""Build the prompt for a specific deliberation round."""
|
|
244
|
+
if round_num == 1:
|
|
245
|
+
return (
|
|
246
|
+
f"Read the shared context below for background.\n\n"
|
|
247
|
+
f"User's request: {user_prompt}\n\n"
|
|
248
|
+
f"Share your initial opinion. Be specific and concise.\n"
|
|
249
|
+
f"State your recommendation and key reasoning.\n"
|
|
250
|
+
f"Keep your response under 500 words."
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
# Read previous round opinions from shared.md
|
|
254
|
+
prev_section = self.shared.read_section(f"Round {round_num - 1} Opinions")
|
|
255
|
+
prev_context = prev_section or "(previous round opinions not available)"
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
f"Previous round opinions:\n\n"
|
|
259
|
+
f"{prev_context}\n\n"
|
|
260
|
+
f"---\n\n"
|
|
261
|
+
f"For each other agent's opinion above, state whether you AGREE or DISAGREE "
|
|
262
|
+
f"and explain why. If you disagree, propose an alternative.\n"
|
|
263
|
+
f"End your response with either 'I AGREE with [name]' or your counter-proposal.\n"
|
|
264
|
+
f"Keep your response under 300 words."
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def _update_pane_titles(self, status_text: str) -> None:
|
|
268
|
+
"""Update tmux pane titles to show round progress (Phase 2 feature)."""
|
|
269
|
+
if not self.tmux_manager:
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
import subprocess
|
|
273
|
+
|
|
274
|
+
for name in self.agents:
|
|
275
|
+
pane = self.tmux_manager.get_pane(name)
|
|
276
|
+
if pane:
|
|
277
|
+
try:
|
|
278
|
+
subprocess.run(
|
|
279
|
+
[
|
|
280
|
+
"tmux",
|
|
281
|
+
"select-pane",
|
|
282
|
+
"-t",
|
|
283
|
+
pane.pane_id,
|
|
284
|
+
"-T",
|
|
285
|
+
f"{name}: {status_text}",
|
|
286
|
+
],
|
|
287
|
+
capture_output=True,
|
|
288
|
+
timeout=5,
|
|
289
|
+
)
|
|
290
|
+
except Exception:
|
|
291
|
+
pass # Non-critical — don't fail deliberation for title update
|
|
@@ -40,6 +40,15 @@ class TrinityOrchestrator:
|
|
|
40
40
|
self.context_monitor: ContextMonitor | None = None
|
|
41
41
|
self.session_rotator: SessionRotator | None = None
|
|
42
42
|
self.health_checker: HealthChecker | None = None
|
|
43
|
+
self._event_bus = None
|
|
44
|
+
|
|
45
|
+
def set_event_bus(self, bus) -> None:
|
|
46
|
+
"""Set the TUI event bus for real-time deliberation updates.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
bus: A TUIEventBus instance from trinity.tui.events.
|
|
50
|
+
"""
|
|
51
|
+
self._event_bus = bus
|
|
43
52
|
|
|
44
53
|
def _ensure_initialized(self) -> None:
|
|
45
54
|
"""Lazy initialization: create agents, shared context, protocol."""
|
|
@@ -70,6 +79,7 @@ class TrinityOrchestrator:
|
|
|
70
79
|
)
|
|
71
80
|
|
|
72
81
|
# Create deliberation protocol
|
|
82
|
+
event_callback = self._event_bus.emit if self._event_bus else None
|
|
73
83
|
self.protocol = DeliberationProtocol(
|
|
74
84
|
agents=self.agents,
|
|
75
85
|
shared=self.shared,
|
|
@@ -80,6 +90,7 @@ class TrinityOrchestrator:
|
|
|
80
90
|
max_rounds=self.config.max_deliberation_rounds,
|
|
81
91
|
round_timeout=self.config.round_timeout_seconds,
|
|
82
92
|
tmux_manager=self.tmux_manager if self.interactive else None,
|
|
93
|
+
event_callback=event_callback,
|
|
83
94
|
)
|
|
84
95
|
|
|
85
96
|
# Create context monitor and session rotator
|