trinity-lite 0.2.2__tar.gz → 0.3.1__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_lite-0.3.1/PKG-INFO +167 -0
- trinity_lite-0.3.1/README.md +142 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/pyproject.toml +2 -2
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/tests/test_mcp_server.py +3 -3
- trinity_lite-0.3.1/tests/test_pipeline.py +185 -0
- trinity_lite-0.3.1/tests/test_worker.py +161 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/__init__.py +1 -1
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/cli.py +35 -14
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/mcp_server.py +160 -2
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/orchestrator.py +11 -0
- trinity_lite-0.3.1/trinity_lite/pipeline.py +158 -0
- trinity_lite-0.3.1/trinity_lite/worker.py +206 -0
- trinity_lite-0.3.1/trinity_lite.egg-info/PKG-INFO +167 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite.egg-info/SOURCES.txt +2 -0
- trinity_lite-0.2.2/PKG-INFO +0 -269
- trinity_lite-0.2.2/README.md +0 -244
- trinity_lite-0.2.2/tests/test_worker.py +0 -67
- trinity_lite-0.2.2/trinity_lite/worker.py +0 -45
- trinity_lite-0.2.2/trinity_lite.egg-info/PKG-INFO +0 -269
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/LICENSE +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/setup.cfg +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/tests/test_bus.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/tests/test_cli.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/tests/test_doctor.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/tests/test_guard.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/tests/test_orchestrator.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/tests/test_router.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/__main__.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/adapters.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/bus.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/config.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/doctor.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/guard.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/paths.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/py.typed +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/router.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite/validation.py +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite.egg-info/dependency_links.txt +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite.egg-info/entry_points.txt +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite.egg-info/requires.txt +0 -0
- {trinity_lite-0.2.2 → trinity_lite-0.3.1}/trinity_lite.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trinity-lite
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: The bus that lets your AI agents talk to each other.
|
|
5
|
+
Author: Yomiracle
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Yomiracle/trinity-lite
|
|
8
|
+
Project-URL: Repository, https://github.com/Yomiracle/trinity-lite
|
|
9
|
+
Project-URL: Issues, https://github.com/Yomiracle/trinity-lite/issues
|
|
10
|
+
Keywords: ai-agent,multi-agent,agent-orchestration,codex,claude-code
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Provides-Extra: mcp
|
|
21
|
+
Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
22
|
+
Provides-Extra: agent-skill
|
|
23
|
+
Requires-Dist: agent-skill-system>=1.0.6; extra == "agent-skill"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Trinity Lite
|
|
27
|
+
|
|
28
|
+
[](https://github.com/Yomiracle/trinity-lite/actions/workflows/test.yml)
|
|
29
|
+
[](pyproject.toml)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
[](https://pypi.org/project/trinity-lite/)
|
|
32
|
+
|
|
33
|
+
**The bus that lets your AI agents talk to each other.**
|
|
34
|
+
|
|
35
|
+
[中文 README](README_zh.md)
|
|
36
|
+
|
|
37
|
+
## The problem
|
|
38
|
+
|
|
39
|
+
You already use Claude Code. Maybe you just installed Codex. You want them to collaborate — but there's no built-in way to route tasks between them, remember who did what, or stop them from stepping on each other. Trinity Lite is the missing layer.
|
|
40
|
+
|
|
41
|
+
## What it does
|
|
42
|
+
|
|
43
|
+
- **Route by capability, not name.** You describe the task. The router matches it to the right agent — no hardcoded agent names, no fragile dispatch logic. *"Implement a rate limiter" lands on the agent you tagged `implement`. "Review the auth module" goes to the agent tagged `review`.*
|
|
44
|
+
- **Give every agent a pull queue.** Workers read pending tasks from the shared bus, execute them via CLI, and write results back. Each agent polls on its own schedule. You never copy-paste an output between terminals again.
|
|
45
|
+
- **Remember every decision.** Every task, status change, result, error, and inter-agent message lands in a local SQLite database. Query who did what, when, and what happened — without setting up a logging pipeline.
|
|
46
|
+
- **Block footguns before they fire.** Self-delegation loops are rejected. Delegation depth has a hard cap. Working directories must be in the allowlist. You ship features, not incident reports.
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
30 seconds, no agents required:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install trinity-lite
|
|
54
|
+
trinity-lite doctor
|
|
55
|
+
trinity-lite dispatch-auto "implement a hello-world function"
|
|
56
|
+
trinity-lite worker codex --once
|
|
57
|
+
trinity-lite tasks
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Mock agents are built in. You see the full dispatch → execute → result cycle before you wire up anything real.
|
|
61
|
+
|
|
62
|
+
## Not another framework
|
|
63
|
+
|
|
64
|
+
Trinity Lite doesn't build agents. It connects the agents you already have.
|
|
65
|
+
|
|
66
|
+
LangGraph and CrewAI give you primitives for building agents from scratch — graph definitions, role abstractions, tool wrappers. Trinity Lite starts from the opposite end: Claude Code is running in one terminal, Codex is running in another, and they need a shared task bus, durable state, and safety boundaries. No SDK to learn. No new agent abstraction. Just a bus that works with the CLIs you already use.
|
|
67
|
+
|
|
68
|
+
## Who this is for
|
|
69
|
+
|
|
70
|
+
| You are... | Trinity Lite helps you... |
|
|
71
|
+
|------------|---------------------------|
|
|
72
|
+
| Copy-pasting prompts and outputs between two agent terminals all day | Dispatch once to the bus and let agents pull their own work |
|
|
73
|
+
| Prototyping a multi-agent pipeline before committing infrastructure | Run the full flow with mock agents — no API keys, no provisioning |
|
|
74
|
+
| Running everything on a single machine with zero server setup | Keep your state in SQLite, your runtime in stdlib, your daemon count at zero |
|
|
75
|
+
| Showing a colleague how multi-agent collaboration works | `pip install` → five commands → they see it run. No explanation needed. |
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
- **Route by capability.** Tag agents with `implement`, `review`, `audit` — the router matches tasks to the agent that can do them. No agent names in your dispatch logic.
|
|
80
|
+
- **Dispatch directly when you need control.** Bypass the router and send a task straight to `claude_code` or `codex`. Best of both worlds.
|
|
81
|
+
- **Persist everything in SQLite.** Tasks, statuses, results, errors, and messages in one local file. Query it with `sqlite3` or any tool that speaks SQL.
|
|
82
|
+
- **Run CLI workers on demand.** `trinity-lite worker codex --once` pulls one queued task, executes the agent's command, and writes the result. Run it in a loop, in cron, or by hand.
|
|
83
|
+
- **Execute safely, no shell injection.** Agent commands are JSON arrays run with `shell=False`. No string interpolation into a shell. No surprises.
|
|
84
|
+
- **Test with mock agents.** Mock agents simulate the full cycle without real CLIs. Prototype routing, persistence, and review handoffs first. Wire up real agents later.
|
|
85
|
+
- **Guard against runaway delegation.** Self-delegation is blocked. Delegation depth is capped. Working directories are allowlisted. Safe by default.
|
|
86
|
+
- **Check health in one pass.** `trinity-lite doctor` verifies Python, SQLite, route config, agent config, and publish readiness.
|
|
87
|
+
- **Zero dependencies.** Runtime is Python standard library only. Nothing to install but Python 3.10+.
|
|
88
|
+
- **109 tests guarding the surface area.** Mock workflows, safety checks, routing, persistence — all covered.
|
|
89
|
+
|
|
90
|
+
## Install
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install trinity-lite
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Python 3.10+. Zero runtime dependencies. Standard library only.
|
|
97
|
+
|
|
98
|
+
### Optional extras
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install trinity-lite[mcp] # MCP server — 10 tools + 3 resources
|
|
102
|
+
pip install trinity-lite[agent-skill] # agent-skill-system integration
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Workflow example
|
|
106
|
+
|
|
107
|
+
Hermes routes → Codex implements → Claude Code reviews. Five commands, one audit trail.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Hermes sends the task. The router picks Codex automatically.
|
|
111
|
+
trinity-lite dispatch-auto "implement a rate limiter for the API" --source-agent hermes
|
|
112
|
+
|
|
113
|
+
# Codex pulls the task and runs it.
|
|
114
|
+
trinity-lite worker codex --once --agents agents.local.json
|
|
115
|
+
|
|
116
|
+
# Claude Code reviews the result.
|
|
117
|
+
trinity-lite send claude_code "please review task <task_id>" --source-agent hermes
|
|
118
|
+
trinity-lite worker claude_code --once --agents agents.local.json
|
|
119
|
+
|
|
120
|
+
# Read the full trail.
|
|
121
|
+
trinity-lite status <task_id>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Works immediately with mock agents. Ready for real CLIs when you are:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
cp examples/agents.command.example.json agents.local.json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## MCP server
|
|
131
|
+
|
|
132
|
+
Turn the task bus into an MCP server. Let any MCP client dispatch, query, and route tasks.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install trinity-lite[mcp]
|
|
136
|
+
trinity-lite mcp serve
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**10 tools:**
|
|
140
|
+
|
|
141
|
+
| Tool | What it does |
|
|
142
|
+
|------|--------------|
|
|
143
|
+
| `trinity_dispatch` | Dispatch a task to a specific agent |
|
|
144
|
+
| `trinity_dispatch_auto` | Dispatch and let the capability router pick the agent |
|
|
145
|
+
| `trinity_status` | Get the state and result of any task by ID |
|
|
146
|
+
| `trinity_tasks` | List recent tasks, filterable by agent |
|
|
147
|
+
| `trinity_worker` | Run one worker cycle for an agent |
|
|
148
|
+
| `trinity_doctor` | Run health and diagnostic checks |
|
|
149
|
+
| `trinity_inbox` | Read durable messages for an agent |
|
|
150
|
+
| `trinity_send` | Send a message from one agent to another |
|
|
151
|
+
| `trinity_skill_search` | Search agent-skill-system for relevant skills |
|
|
152
|
+
| `trinity_skill_load` | Load the full content of a named skill |
|
|
153
|
+
|
|
154
|
+
**3 resources:** `trinity://health`, `trinity://tasks/recent`, `trinity://tasks/{task_id}`
|
|
155
|
+
|
|
156
|
+
## Links
|
|
157
|
+
|
|
158
|
+
- [PyPI](https://pypi.org/project/trinity-lite/)
|
|
159
|
+
- [Documentation](docs/)
|
|
160
|
+
- [Changelog](CHANGELOG.md)
|
|
161
|
+
- [Contributing](CONTRIBUTING.md)
|
|
162
|
+
- [Architecture](docs/ARCHITECTURE.md)
|
|
163
|
+
- [Real agent setup](docs/REAL_AGENTS.md)
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Trinity Lite
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Yomiracle/trinity-lite/actions/workflows/test.yml)
|
|
4
|
+
[](pyproject.toml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://pypi.org/project/trinity-lite/)
|
|
7
|
+
|
|
8
|
+
**The bus that lets your AI agents talk to each other.**
|
|
9
|
+
|
|
10
|
+
[中文 README](README_zh.md)
|
|
11
|
+
|
|
12
|
+
## The problem
|
|
13
|
+
|
|
14
|
+
You already use Claude Code. Maybe you just installed Codex. You want them to collaborate — but there's no built-in way to route tasks between them, remember who did what, or stop them from stepping on each other. Trinity Lite is the missing layer.
|
|
15
|
+
|
|
16
|
+
## What it does
|
|
17
|
+
|
|
18
|
+
- **Route by capability, not name.** You describe the task. The router matches it to the right agent — no hardcoded agent names, no fragile dispatch logic. *"Implement a rate limiter" lands on the agent you tagged `implement`. "Review the auth module" goes to the agent tagged `review`.*
|
|
19
|
+
- **Give every agent a pull queue.** Workers read pending tasks from the shared bus, execute them via CLI, and write results back. Each agent polls on its own schedule. You never copy-paste an output between terminals again.
|
|
20
|
+
- **Remember every decision.** Every task, status change, result, error, and inter-agent message lands in a local SQLite database. Query who did what, when, and what happened — without setting up a logging pipeline.
|
|
21
|
+
- **Block footguns before they fire.** Self-delegation loops are rejected. Delegation depth has a hard cap. Working directories must be in the allowlist. You ship features, not incident reports.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
30 seconds, no agents required:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install trinity-lite
|
|
29
|
+
trinity-lite doctor
|
|
30
|
+
trinity-lite dispatch-auto "implement a hello-world function"
|
|
31
|
+
trinity-lite worker codex --once
|
|
32
|
+
trinity-lite tasks
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Mock agents are built in. You see the full dispatch → execute → result cycle before you wire up anything real.
|
|
36
|
+
|
|
37
|
+
## Not another framework
|
|
38
|
+
|
|
39
|
+
Trinity Lite doesn't build agents. It connects the agents you already have.
|
|
40
|
+
|
|
41
|
+
LangGraph and CrewAI give you primitives for building agents from scratch — graph definitions, role abstractions, tool wrappers. Trinity Lite starts from the opposite end: Claude Code is running in one terminal, Codex is running in another, and they need a shared task bus, durable state, and safety boundaries. No SDK to learn. No new agent abstraction. Just a bus that works with the CLIs you already use.
|
|
42
|
+
|
|
43
|
+
## Who this is for
|
|
44
|
+
|
|
45
|
+
| You are... | Trinity Lite helps you... |
|
|
46
|
+
|------------|---------------------------|
|
|
47
|
+
| Copy-pasting prompts and outputs between two agent terminals all day | Dispatch once to the bus and let agents pull their own work |
|
|
48
|
+
| Prototyping a multi-agent pipeline before committing infrastructure | Run the full flow with mock agents — no API keys, no provisioning |
|
|
49
|
+
| Running everything on a single machine with zero server setup | Keep your state in SQLite, your runtime in stdlib, your daemon count at zero |
|
|
50
|
+
| Showing a colleague how multi-agent collaboration works | `pip install` → five commands → they see it run. No explanation needed. |
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Route by capability.** Tag agents with `implement`, `review`, `audit` — the router matches tasks to the agent that can do them. No agent names in your dispatch logic.
|
|
55
|
+
- **Dispatch directly when you need control.** Bypass the router and send a task straight to `claude_code` or `codex`. Best of both worlds.
|
|
56
|
+
- **Persist everything in SQLite.** Tasks, statuses, results, errors, and messages in one local file. Query it with `sqlite3` or any tool that speaks SQL.
|
|
57
|
+
- **Run CLI workers on demand.** `trinity-lite worker codex --once` pulls one queued task, executes the agent's command, and writes the result. Run it in a loop, in cron, or by hand.
|
|
58
|
+
- **Execute safely, no shell injection.** Agent commands are JSON arrays run with `shell=False`. No string interpolation into a shell. No surprises.
|
|
59
|
+
- **Test with mock agents.** Mock agents simulate the full cycle without real CLIs. Prototype routing, persistence, and review handoffs first. Wire up real agents later.
|
|
60
|
+
- **Guard against runaway delegation.** Self-delegation is blocked. Delegation depth is capped. Working directories are allowlisted. Safe by default.
|
|
61
|
+
- **Check health in one pass.** `trinity-lite doctor` verifies Python, SQLite, route config, agent config, and publish readiness.
|
|
62
|
+
- **Zero dependencies.** Runtime is Python standard library only. Nothing to install but Python 3.10+.
|
|
63
|
+
- **109 tests guarding the surface area.** Mock workflows, safety checks, routing, persistence — all covered.
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install trinity-lite
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Python 3.10+. Zero runtime dependencies. Standard library only.
|
|
72
|
+
|
|
73
|
+
### Optional extras
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install trinity-lite[mcp] # MCP server — 10 tools + 3 resources
|
|
77
|
+
pip install trinity-lite[agent-skill] # agent-skill-system integration
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Workflow example
|
|
81
|
+
|
|
82
|
+
Hermes routes → Codex implements → Claude Code reviews. Five commands, one audit trail.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Hermes sends the task. The router picks Codex automatically.
|
|
86
|
+
trinity-lite dispatch-auto "implement a rate limiter for the API" --source-agent hermes
|
|
87
|
+
|
|
88
|
+
# Codex pulls the task and runs it.
|
|
89
|
+
trinity-lite worker codex --once --agents agents.local.json
|
|
90
|
+
|
|
91
|
+
# Claude Code reviews the result.
|
|
92
|
+
trinity-lite send claude_code "please review task <task_id>" --source-agent hermes
|
|
93
|
+
trinity-lite worker claude_code --once --agents agents.local.json
|
|
94
|
+
|
|
95
|
+
# Read the full trail.
|
|
96
|
+
trinity-lite status <task_id>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Works immediately with mock agents. Ready for real CLIs when you are:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cp examples/agents.command.example.json agents.local.json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## MCP server
|
|
106
|
+
|
|
107
|
+
Turn the task bus into an MCP server. Let any MCP client dispatch, query, and route tasks.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install trinity-lite[mcp]
|
|
111
|
+
trinity-lite mcp serve
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**10 tools:**
|
|
115
|
+
|
|
116
|
+
| Tool | What it does |
|
|
117
|
+
|------|--------------|
|
|
118
|
+
| `trinity_dispatch` | Dispatch a task to a specific agent |
|
|
119
|
+
| `trinity_dispatch_auto` | Dispatch and let the capability router pick the agent |
|
|
120
|
+
| `trinity_status` | Get the state and result of any task by ID |
|
|
121
|
+
| `trinity_tasks` | List recent tasks, filterable by agent |
|
|
122
|
+
| `trinity_worker` | Run one worker cycle for an agent |
|
|
123
|
+
| `trinity_doctor` | Run health and diagnostic checks |
|
|
124
|
+
| `trinity_inbox` | Read durable messages for an agent |
|
|
125
|
+
| `trinity_send` | Send a message from one agent to another |
|
|
126
|
+
| `trinity_skill_search` | Search agent-skill-system for relevant skills |
|
|
127
|
+
| `trinity_skill_load` | Load the full content of a named skill |
|
|
128
|
+
|
|
129
|
+
**3 resources:** `trinity://health`, `trinity://tasks/recent`, `trinity://tasks/{task_id}`
|
|
130
|
+
|
|
131
|
+
## Links
|
|
132
|
+
|
|
133
|
+
- [PyPI](https://pypi.org/project/trinity-lite/)
|
|
134
|
+
- [Documentation](docs/)
|
|
135
|
+
- [Changelog](CHANGELOG.md)
|
|
136
|
+
- [Contributing](CONTRIBUTING.md)
|
|
137
|
+
- [Architecture](docs/ARCHITECTURE.md)
|
|
138
|
+
- [Real agent setup](docs/REAL_AGENTS.md)
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "trinity-lite"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.3.1"
|
|
8
|
+
description = "The bus that lets your AI agents talk to each other."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = "MIT"
|
|
@@ -68,7 +68,7 @@ class McpServerTest(unittest.TestCase):
|
|
|
68
68
|
self.assertIn("resources", resp["result"]["capabilities"])
|
|
69
69
|
self.assertIn("serverInfo", resp["result"])
|
|
70
70
|
self.assertEqual(resp["result"]["serverInfo"]["name"], "trinity-lite-mcp")
|
|
71
|
-
self.assertEqual(resp["result"]["serverInfo"]["version"], "0.
|
|
71
|
+
self.assertEqual(resp["result"]["serverInfo"]["version"], "0.3.1")
|
|
72
72
|
|
|
73
73
|
def test_initialized_returns_none(self):
|
|
74
74
|
resp = self._call(self._msg("initialized"))
|
|
@@ -90,7 +90,7 @@ class McpServerTest(unittest.TestCase):
|
|
|
90
90
|
resp = self._call(self._msg("tools/list", 1))
|
|
91
91
|
tools = resp["result"]["tools"]
|
|
92
92
|
self.assertIsInstance(tools, list)
|
|
93
|
-
self.assertEqual(len(tools),
|
|
93
|
+
self.assertEqual(len(tools), 12)
|
|
94
94
|
names = [t["name"] for t in tools]
|
|
95
95
|
self.assertIn("trinity_dispatch", names)
|
|
96
96
|
self.assertIn("trinity_dispatch_auto", names)
|
|
@@ -133,7 +133,7 @@ class McpServerTest(unittest.TestCase):
|
|
|
133
133
|
data = json.loads(contents[0]["text"])
|
|
134
134
|
self.assertEqual(data["status"], "ok")
|
|
135
135
|
self.assertEqual(data["bus"], "connected")
|
|
136
|
-
self.assertEqual(data["tools"],
|
|
136
|
+
self.assertEqual(data["tools"], 12)
|
|
137
137
|
self.assertEqual(data["resources"], 3)
|
|
138
138
|
|
|
139
139
|
def test_resource_health_degraded(self):
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Tests for pipeline orchestration."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import tempfile
|
|
5
|
+
import unittest
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from trinity_lite.bus import TrinityBus
|
|
9
|
+
from trinity_lite.pipeline import (
|
|
10
|
+
load_pipeline,
|
|
11
|
+
resolve_step_prompt,
|
|
12
|
+
run_pipeline,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PipelineTest(unittest.TestCase):
|
|
17
|
+
def setUp(self):
|
|
18
|
+
self.tmp = tempfile.TemporaryDirectory(dir=str(Path.home()))
|
|
19
|
+
self.root = Path(self.tmp.name)
|
|
20
|
+
|
|
21
|
+
def tearDown(self):
|
|
22
|
+
self.tmp.cleanup()
|
|
23
|
+
|
|
24
|
+
def _write_yaml(self, name, content):
|
|
25
|
+
path = self.root / name
|
|
26
|
+
path.write_text(content, encoding="utf-8")
|
|
27
|
+
return str(path)
|
|
28
|
+
|
|
29
|
+
def test_load_valid_pipeline(self):
|
|
30
|
+
yaml_text = "name: test-pipeline\nsteps:\n - id: step1\n agent: codex\n task_type: implementation\n prompt_template: 'Implement: {task}'\n"
|
|
31
|
+
yaml_path = self._write_yaml("test.yaml", yaml_text)
|
|
32
|
+
p = load_pipeline(yaml_path)
|
|
33
|
+
self.assertEqual(p["name"], "test-pipeline")
|
|
34
|
+
self.assertEqual(len(p["steps"]), 1)
|
|
35
|
+
self.assertEqual(p["steps"][0]["id"], "step1")
|
|
36
|
+
|
|
37
|
+
def test_load_invalid_yaml_raises(self):
|
|
38
|
+
json_path = self._write_yaml("bad.json", "not valid json{{{")
|
|
39
|
+
with self.assertRaises(ValueError):
|
|
40
|
+
load_pipeline(json_path)
|
|
41
|
+
|
|
42
|
+
def test_missing_required_field_raises(self):
|
|
43
|
+
yaml_text = "name: bad\nsteps:\n - id: step1\n task_type: implementation\n prompt_template: 'ok'\n"
|
|
44
|
+
yaml_path = self._write_yaml("bad.yaml", yaml_text)
|
|
45
|
+
with self.assertRaises(ValueError):
|
|
46
|
+
load_pipeline(yaml_path)
|
|
47
|
+
|
|
48
|
+
def test_empty_steps_raises(self):
|
|
49
|
+
yaml_path = self._write_yaml("empty.yaml", "name: empty\nsteps: []")
|
|
50
|
+
with self.assertRaises(ValueError):
|
|
51
|
+
load_pipeline(yaml_path)
|
|
52
|
+
|
|
53
|
+
def test_duplicate_step_ids_raises(self):
|
|
54
|
+
yaml_text = "name: dup\nsteps:\n - id: step1\n agent: codex\n task_type: implementation\n prompt_template: 'ok'\n - id: step1\n agent: codex\n task_type: implementation\n prompt_template: 'ok'\n"
|
|
55
|
+
yaml_path = self._write_yaml("dup.yaml", yaml_text)
|
|
56
|
+
with self.assertRaises(ValueError):
|
|
57
|
+
load_pipeline(yaml_path)
|
|
58
|
+
|
|
59
|
+
def test_unknown_step_reference_raises(self):
|
|
60
|
+
yaml_text = "name: badref\nsteps:\n - id: step1\n agent: codex\n task_type: implementation\n prompt_template: 'Result: {steps.nonexistent.result}'\n"
|
|
61
|
+
yaml_path = self._write_yaml("badref.yaml", yaml_text)
|
|
62
|
+
with self.assertRaises(ValueError):
|
|
63
|
+
load_pipeline(yaml_path)
|
|
64
|
+
|
|
65
|
+
def test_prompt_template_rendering_task_only(self):
|
|
66
|
+
step = {"prompt_template": "Do: {task}"}
|
|
67
|
+
result = resolve_step_prompt(step, "write tests", {})
|
|
68
|
+
self.assertEqual(result, "Do: write tests")
|
|
69
|
+
|
|
70
|
+
def test_prompt_template_rendering_with_step_result(self):
|
|
71
|
+
step = {"prompt_template": "Result was: {steps.step1.result}"}
|
|
72
|
+
step_results = {"step1": {"result": "all done", "status": "completed"}}
|
|
73
|
+
result = resolve_step_prompt(step, "task", step_results)
|
|
74
|
+
self.assertEqual(result, "Result was: all done")
|
|
75
|
+
|
|
76
|
+
def test_prompt_template_rendering_with_missing_step(self):
|
|
77
|
+
step = {"prompt_template": "Got: {steps.missing.result}"}
|
|
78
|
+
result = resolve_step_prompt(step, "task", {})
|
|
79
|
+
self.assertEqual(result, "Got: ")
|
|
80
|
+
|
|
81
|
+
def test_prompt_template_rendering_with_error(self):
|
|
82
|
+
step = {"prompt_template": "Error: {steps.step1.result}"}
|
|
83
|
+
step_results = {"step1": {"error": "failed hard", "status": "failed"}}
|
|
84
|
+
result = resolve_step_prompt(step, "task", step_results)
|
|
85
|
+
self.assertEqual(result, "Error: failed hard")
|
|
86
|
+
|
|
87
|
+
def test_run_single_step_pipeline(self):
|
|
88
|
+
bus = TrinityBus(self.root / "bus.db", allowed_roots=[self.root])
|
|
89
|
+
pipeline = {
|
|
90
|
+
"name": "test-single",
|
|
91
|
+
"steps": [
|
|
92
|
+
{"id": "implement", "agent": "codex", "task_type": "implementation",
|
|
93
|
+
"prompt_template": "Implement: {task}"}
|
|
94
|
+
],
|
|
95
|
+
}
|
|
96
|
+
result = run_pipeline(pipeline, "hello world", bus, cwd=str(self.root))
|
|
97
|
+
self.assertEqual(result["pipeline_name"], "test-single")
|
|
98
|
+
self.assertEqual(result["overall_status"], "completed")
|
|
99
|
+
self.assertEqual(len(result["steps"]), 1)
|
|
100
|
+
self.assertEqual(result["steps"][0]["status"], "completed")
|
|
101
|
+
|
|
102
|
+
def test_run_two_step_pipeline(self):
|
|
103
|
+
bus = TrinityBus(self.root / "bus.db", allowed_roots=[self.root])
|
|
104
|
+
pipeline = {
|
|
105
|
+
"name": "test-two",
|
|
106
|
+
"steps": [
|
|
107
|
+
{"id": "implement", "agent": "codex", "task_type": "implementation",
|
|
108
|
+
"prompt_template": "Implement: {task}"},
|
|
109
|
+
{"id": "review", "agent": "claude_code", "task_type": "code_review",
|
|
110
|
+
"prompt_template": "Review: {task}\nResult: {steps.implement.result}"},
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
result = run_pipeline(pipeline, "hello", bus, cwd=str(self.root))
|
|
114
|
+
self.assertEqual(result["overall_status"], "completed")
|
|
115
|
+
self.assertEqual(len(result["steps"]), 2)
|
|
116
|
+
self.assertEqual(result["steps"][0]["status"], "completed")
|
|
117
|
+
self.assertEqual(result["steps"][1]["status"], "completed")
|
|
118
|
+
|
|
119
|
+
def test_pipeline_step_results_accessible(self):
|
|
120
|
+
bus = TrinityBus(self.root / "bus.db", allowed_roots=[self.root])
|
|
121
|
+
pipeline = {
|
|
122
|
+
"name": "test-ref",
|
|
123
|
+
"steps": [
|
|
124
|
+
{"id": "step1", "agent": "codex", "task_type": "implementation",
|
|
125
|
+
"prompt_template": "Task: {task}"},
|
|
126
|
+
{"id": "step2", "agent": "claude_code", "task_type": "code_review",
|
|
127
|
+
"prompt_template": "Previous result was: {steps.step1.result}"},
|
|
128
|
+
],
|
|
129
|
+
}
|
|
130
|
+
result = run_pipeline(pipeline, "check refs", bus, cwd=str(self.root))
|
|
131
|
+
self.assertEqual(result["overall_status"], "completed")
|
|
132
|
+
step2 = bus.get_task(result["steps"][1]["task_id"])
|
|
133
|
+
self.assertIn("Previous result was:", step2["prompt"])
|
|
134
|
+
|
|
135
|
+
def test_pipeline_no_run_workers(self):
|
|
136
|
+
bus = TrinityBus(self.root / "bus.db", allowed_roots=[self.root])
|
|
137
|
+
pipeline = {
|
|
138
|
+
"name": "test-no-run",
|
|
139
|
+
"steps": [
|
|
140
|
+
{"id": "step1", "agent": "codex", "task_type": "implementation",
|
|
141
|
+
"prompt_template": "{task}"}
|
|
142
|
+
],
|
|
143
|
+
}
|
|
144
|
+
result = run_pipeline(pipeline, "stuff", bus, cwd=str(self.root), run_workers=False)
|
|
145
|
+
self.assertEqual(result["overall_status"], "queued")
|
|
146
|
+
|
|
147
|
+
def test_pipeline_fail_fast(self):
|
|
148
|
+
bus = TrinityBus(self.root / "bus.db", allowed_roots=[self.root])
|
|
149
|
+
pipeline = {
|
|
150
|
+
"name": "test-fail",
|
|
151
|
+
"steps": [
|
|
152
|
+
{"id": "step1", "agent": "no_such_agent", "task_type": "implementation",
|
|
153
|
+
"prompt_template": "{task}"},
|
|
154
|
+
{"id": "step2", "agent": "codex", "task_type": "implementation",
|
|
155
|
+
"prompt_template": "{task}"},
|
|
156
|
+
],
|
|
157
|
+
}
|
|
158
|
+
result = run_pipeline(pipeline, "stuff", bus, cwd=str(self.root))
|
|
159
|
+
self.assertEqual(result["overall_status"], "failed")
|
|
160
|
+
self.assertEqual(len(result["steps"]), 1)
|
|
161
|
+
|
|
162
|
+
def test_cli_orchestrate_with_pipeline_flag(self):
|
|
163
|
+
import io as _io
|
|
164
|
+
import json as _json
|
|
165
|
+
from contextlib import redirect_stdout as _redirect_stdout
|
|
166
|
+
from trinity_lite.cli import main
|
|
167
|
+
yaml_text = "name: cli-pipe\nsteps:\n - id: step1\n agent: codex\n task_type: implementation\n prompt_template: 'ok'\n"
|
|
168
|
+
pipeline_path = self._write_yaml("cli_test.yaml", yaml_text)
|
|
169
|
+
output = _io.StringIO()
|
|
170
|
+
with _redirect_stdout(output):
|
|
171
|
+
code = main([
|
|
172
|
+
"orchestrate",
|
|
173
|
+
"test task",
|
|
174
|
+
"--pipeline", pipeline_path,
|
|
175
|
+
"--db", str(self.root / "cli_bus.db"),
|
|
176
|
+
"--cwd", str(self.root),
|
|
177
|
+
])
|
|
178
|
+
self.assertEqual(code, 0)
|
|
179
|
+
data = _json.loads(output.getvalue())
|
|
180
|
+
self.assertEqual(data["pipeline_name"], "cli-pipe")
|
|
181
|
+
self.assertEqual(data["overall_status"], "completed")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
unittest.main()
|