trinity-lite 0.2.0__tar.gz → 0.2.2__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 (35) hide show
  1. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/PKG-INFO +3 -1
  2. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/pyproject.toml +2 -1
  3. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/tests/test_mcp_server.py +137 -3
  4. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/__init__.py +1 -1
  5. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/mcp_server.py +154 -1
  6. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite.egg-info/PKG-INFO +3 -1
  7. trinity_lite-0.2.2/trinity_lite.egg-info/requires.txt +6 -0
  8. trinity_lite-0.2.0/trinity_lite.egg-info/requires.txt +0 -3
  9. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/LICENSE +0 -0
  10. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/README.md +0 -0
  11. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/setup.cfg +0 -0
  12. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/tests/test_bus.py +0 -0
  13. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/tests/test_cli.py +0 -0
  14. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/tests/test_doctor.py +0 -0
  15. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/tests/test_guard.py +0 -0
  16. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/tests/test_orchestrator.py +0 -0
  17. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/tests/test_router.py +0 -0
  18. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/tests/test_worker.py +0 -0
  19. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/__main__.py +0 -0
  20. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/adapters.py +0 -0
  21. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/bus.py +0 -0
  22. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/cli.py +0 -0
  23. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/config.py +0 -0
  24. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/doctor.py +0 -0
  25. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/guard.py +0 -0
  26. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/orchestrator.py +0 -0
  27. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/paths.py +0 -0
  28. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/py.typed +0 -0
  29. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/router.py +0 -0
  30. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/validation.py +0 -0
  31. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite/worker.py +0 -0
  32. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite.egg-info/SOURCES.txt +0 -0
  33. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite.egg-info/dependency_links.txt +0 -0
  34. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite.egg-info/entry_points.txt +0 -0
  35. {trinity_lite-0.2.0 → trinity_lite-0.2.2}/trinity_lite.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trinity-lite
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: A minimal public multi-agent task bus with routing, workers, and safety checks.
5
5
  Author: Yomiracle
6
6
  License-Expression: MIT
@@ -19,6 +19,8 @@ Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Provides-Extra: mcp
21
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"
22
24
  Dynamic: license-file
23
25
 
24
26
  # Trinity Lite
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trinity-lite"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "A minimal public multi-agent task bus with routing, workers, and safety checks."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -26,6 +26,7 @@ dependencies = []
26
26
 
27
27
  [project.optional-dependencies]
28
28
  mcp = ["mcp>=1.0"]
29
+ agent-skill = ["agent-skill-system>=1.0.6"]
29
30
 
30
31
  [project.scripts]
31
32
  trinity-lite = "trinity_lite.cli:main"
@@ -1,5 +1,6 @@
1
1
  import io
2
2
  import json
3
+ import os
3
4
  import sys
4
5
  import tempfile
5
6
  import unittest
@@ -22,6 +23,19 @@ from trinity_lite.mcp_server import (
22
23
  _get_known_agents,
23
24
  )
24
25
 
26
+ try:
27
+ from engine.bank import SkillBank # noqa: F401
28
+
29
+ HAS_AGENT_SKILL = True
30
+ except ImportError:
31
+ HAS_AGENT_SKILL = False
32
+
33
+ # Detect agent-skill-system skills directory
34
+ SKILLS_DIR = os.environ.get(
35
+ "TRINITY_SKILLS_DIR",
36
+ os.path.expanduser("~/agent-skill-system/skills"),
37
+ )
38
+
25
39
 
26
40
  class McpServerTest(unittest.TestCase):
27
41
  def setUp(self):
@@ -54,7 +68,7 @@ class McpServerTest(unittest.TestCase):
54
68
  self.assertIn("resources", resp["result"]["capabilities"])
55
69
  self.assertIn("serverInfo", resp["result"])
56
70
  self.assertEqual(resp["result"]["serverInfo"]["name"], "trinity-lite-mcp")
57
- self.assertEqual(resp["result"]["serverInfo"]["version"], "0.2.0")
71
+ self.assertEqual(resp["result"]["serverInfo"]["version"], "0.2.2")
58
72
 
59
73
  def test_initialized_returns_none(self):
60
74
  resp = self._call(self._msg("initialized"))
@@ -76,7 +90,7 @@ class McpServerTest(unittest.TestCase):
76
90
  resp = self._call(self._msg("tools/list", 1))
77
91
  tools = resp["result"]["tools"]
78
92
  self.assertIsInstance(tools, list)
79
- self.assertEqual(len(tools), 8)
93
+ self.assertEqual(len(tools), 10)
80
94
  names = [t["name"] for t in tools]
81
95
  self.assertIn("trinity_dispatch", names)
82
96
  self.assertIn("trinity_dispatch_auto", names)
@@ -86,6 +100,8 @@ class McpServerTest(unittest.TestCase):
86
100
  self.assertIn("trinity_doctor", names)
87
101
  self.assertIn("trinity_inbox", names)
88
102
  self.assertIn("trinity_send", names)
103
+ self.assertIn("trinity_skill_search", names)
104
+ self.assertIn("trinity_skill_load", names)
89
105
  # Check schema structure
90
106
  for t in tools:
91
107
  self.assertIn("name", t)
@@ -117,7 +133,7 @@ class McpServerTest(unittest.TestCase):
117
133
  data = json.loads(contents[0]["text"])
118
134
  self.assertEqual(data["status"], "ok")
119
135
  self.assertEqual(data["bus"], "connected")
120
- self.assertEqual(data["tools"], 8)
136
+ self.assertEqual(data["tools"], 10)
121
137
  self.assertEqual(data["resources"], 3)
122
138
 
123
139
  def test_resource_health_degraded(self):
@@ -451,5 +467,123 @@ class McpServerTest(unittest.TestCase):
451
467
  self.assertEqual(compact["id"], "abc123def456")
452
468
 
453
469
 
470
+ # ---- Skills: agent-skill-system integration ----
471
+
472
+ def test_skill_search_without_agent_skill_system(self):
473
+ """Graceful error when agent-skill-system not installed."""
474
+ import trinity_lite.mcp_server as mcp
475
+ from unittest.mock import patch
476
+
477
+ with patch.object(mcp, "_init_skill_engine", return_value=False):
478
+ params = {
479
+ "name": "trinity_skill_search",
480
+ "arguments": {"query": "parse a contract"},
481
+ }
482
+ resp = self._call(self._msg("tools/call", 1, params))
483
+ self.assertIn("result", resp)
484
+ self.assertIn("error", resp["result"])
485
+ self.assertEqual(resp["result"]["error"], "agent-skill-system not installed")
486
+ self.assertIn("hint", resp["result"])
487
+
488
+ @unittest.skipUnless(HAS_AGENT_SKILL and Path(SKILLS_DIR).exists(),
489
+ "agent-skill-system not installed or skills dir not found")
490
+ def test_skill_search_returns_matching_skills(self):
491
+ """Search returns matching skills when engine is available."""
492
+ import trinity_lite.mcp_server as mcp
493
+
494
+ old_available = mcp._SKILL_ENGINE_AVAILABLE
495
+ old_bank = mcp._SKILL_BANK
496
+ old_bank_dir = mcp._SKILL_BANK_DIR
497
+ try:
498
+ from engine.bank import SkillBank
499
+ mcp._SKILL_ENGINE_AVAILABLE = True
500
+ mcp._SKILL_BANK_DIR = SKILLS_DIR
501
+ mcp._SKILL_BANK = SkillBank(mcp._SKILL_BANK_DIR)
502
+ mcp._SKILL_BANK.scan_directory()
503
+
504
+ params = {
505
+ "name": "trinity_skill_search",
506
+ "arguments": {"query": "generate a weekly report", "limit": 3},
507
+ }
508
+ resp = self._call(self._msg("tools/call", 1, params))
509
+ self.assertIn("result", resp)
510
+ self.assertIn("skills", resp["result"])
511
+ skills = resp["result"]["skills"]
512
+ self.assertIsInstance(skills, list)
513
+ if skills:
514
+ self.assertIn("name", skills[0])
515
+ self.assertIn("description", skills[0])
516
+ self.assertIn("score", skills[0])
517
+ finally:
518
+ mcp._SKILL_ENGINE_AVAILABLE = old_available
519
+ mcp._SKILL_BANK = old_bank
520
+ mcp._SKILL_BANK_DIR = old_bank_dir
521
+
522
+ @unittest.skipUnless(HAS_AGENT_SKILL and Path(SKILLS_DIR).exists(),
523
+ "agent-skill-system not installed or skills dir not found")
524
+ def test_skill_load_returns_content(self):
525
+ """Load a skill's full content."""
526
+ import trinity_lite.mcp_server as mcp
527
+
528
+ old_available = mcp._SKILL_ENGINE_AVAILABLE
529
+ old_bank = mcp._SKILL_BANK
530
+ old_bank_dir = mcp._SKILL_BANK_DIR
531
+ try:
532
+ from engine.bank import SkillBank
533
+ mcp._SKILL_ENGINE_AVAILABLE = True
534
+ mcp._SKILL_BANK_DIR = SKILLS_DIR
535
+ mcp._SKILL_BANK = SkillBank(mcp._SKILL_BANK_DIR)
536
+ mcp._SKILL_BANK.scan_directory()
537
+
538
+ # Pick an active skill from the bank
539
+ active = mcp._SKILL_BANK.list_active()
540
+ if active:
541
+ skill_name = active[0].name
542
+ params = {
543
+ "name": "trinity_skill_load",
544
+ "arguments": {"skill_name": skill_name},
545
+ }
546
+ resp = self._call(self._msg("tools/call", 1, params))
547
+ self.assertIn("result", resp)
548
+ result = resp["result"]
549
+ self.assertIn("name", result)
550
+ self.assertIn("skill_md", result)
551
+ self.assertIn("system_prompt", result)
552
+ finally:
553
+ mcp._SKILL_ENGINE_AVAILABLE = old_available
554
+ mcp._SKILL_BANK = old_bank
555
+ mcp._SKILL_BANK_DIR = old_bank_dir
556
+
557
+ @unittest.skipUnless(HAS_AGENT_SKILL and Path(SKILLS_DIR).exists(),
558
+ "agent-skill-system not installed or skills dir not found")
559
+ def test_skill_load_not_found(self):
560
+ """Graceful error when skill name is not in the bank."""
561
+ import trinity_lite.mcp_server as mcp
562
+
563
+ old_available = mcp._SKILL_ENGINE_AVAILABLE
564
+ old_bank = mcp._SKILL_BANK
565
+ old_bank_dir = mcp._SKILL_BANK_DIR
566
+ try:
567
+ from engine.bank import SkillBank
568
+ mcp._SKILL_ENGINE_AVAILABLE = True
569
+ mcp._SKILL_BANK_DIR = SKILLS_DIR
570
+ mcp._SKILL_BANK = SkillBank(mcp._SKILL_BANK_DIR)
571
+ mcp._SKILL_BANK.scan_directory()
572
+
573
+ params = {
574
+ "name": "trinity_skill_load",
575
+ "arguments": {"skill_name": "nonexistent-skill-xyz"},
576
+ }
577
+ resp = self._call(self._msg("tools/call", 1, params))
578
+ self.assertIn("result", resp)
579
+ self.assertIn("error", resp["result"])
580
+ self.assertIn("skill not found", resp["result"]["error"])
581
+ self.assertIn("available_skills", resp["result"])
582
+ finally:
583
+ mcp._SKILL_ENGINE_AVAILABLE = old_available
584
+ mcp._SKILL_BANK = old_bank
585
+ mcp._SKILL_BANK_DIR = old_bank_dir
586
+
587
+
454
588
  if __name__ == "__main__":
455
589
  unittest.main()
@@ -1,3 +1,3 @@
1
1
  """Trinity Lite: a minimal public multi-agent task bus."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.2"
@@ -20,10 +20,54 @@ from .guard import GuardError
20
20
  from .router import resolve_route
21
21
  from .worker import run_once as worker_run_once
22
22
 
23
+ # ---------------------------------------------------------------------------
24
+ # agent-skill-system integration (optional, graceful fallback)
25
+ # ---------------------------------------------------------------------------
26
+
27
+ _SKILL_ENGINE_AVAILABLE = False
28
+ _SKILL_BANK = None
29
+ _SKILL_BANK_DIR = None
30
+
31
+
32
+ def _find_skills_dir() -> str:
33
+ """Locate the agent-skill-system skills/ bank directory."""
34
+ # Env override
35
+ env_dir = os.environ.get("TRINITY_SKILLS_DIR", "")
36
+ if env_dir and Path(env_dir).is_dir():
37
+ return env_dir
38
+ # Check ~/.trinity/skills
39
+ home_skills = Path.home() / ".trinity" / "skills"
40
+ if home_skills.is_dir():
41
+ return str(home_skills)
42
+ # Check ~/agent-skill-system/skills (git-clone default)
43
+ dev_skills = Path.home() / "agent-skill-system" / "skills"
44
+ if dev_skills.is_dir():
45
+ return str(dev_skills)
46
+ # Default (will be auto-created by SkillBank)
47
+ return str(home_skills)
48
+
49
+
50
+ def _init_skill_engine() -> bool:
51
+ """Lazy-init the agent-skill-system engine. Cached after first call."""
52
+ global _SKILL_ENGINE_AVAILABLE, _SKILL_BANK, _SKILL_BANK_DIR
53
+ if _SKILL_ENGINE_AVAILABLE:
54
+ return True
55
+ try:
56
+ from engine.bank import SkillBank # type: ignore[import-untyped]
57
+ _SKILL_BANK_DIR = _find_skills_dir()
58
+ _SKILL_BANK = SkillBank(_SKILL_BANK_DIR)
59
+ _SKILL_BANK.scan_directory()
60
+ _SKILL_ENGINE_AVAILABLE = True
61
+ return True
62
+ except ImportError:
63
+ _SKILL_ENGINE_AVAILABLE = False
64
+ return False
65
+
66
+
23
67
  JSONRPC_VERSION = "2.0"
24
68
  PROTOCOL_VERSION = "2024-11-05"
25
69
  SERVER_NAME = "trinity-lite-mcp"
26
- SERVER_VERSION = "0.2.0"
70
+ SERVER_VERSION = "0.2.2"
27
71
 
28
72
  # ---------------------------------------------------------------------------
29
73
  # Tool definitions (MCP schema)
@@ -133,6 +177,23 @@ TOOL_DEFINITIONS = [
133
177
  },
134
178
  ["target_agent", "message"],
135
179
  ),
180
+ _tool_def(
181
+ "trinity_skill_search",
182
+ "Search agent-skill-system for relevant skills matching a task description.",
183
+ {
184
+ "query": {"type": "string", "description": "Task description or keyword query"},
185
+ "limit": {"type": "integer", "description": "Maximum results (default: 5, max: 20)"},
186
+ },
187
+ ["query"],
188
+ ),
189
+ _tool_def(
190
+ "trinity_skill_load",
191
+ "Load the full content (SKILL.md + memory) of a named skill.",
192
+ {
193
+ "skill_name": {"type": "string", "description": "Exact name of the skill to load"},
194
+ },
195
+ ["skill_name"],
196
+ ),
136
197
  ]
137
198
 
138
199
  TOOLS_BY_NAME = {t["name"]: t for t in TOOL_DEFINITIONS}
@@ -470,6 +531,96 @@ def _handle_trinity_send(params, bus, agents_path, routes_path):
470
531
  return jsonrpc_response(1, msg)
471
532
 
472
533
 
534
+ def _handle_trinity_skill_search(params, bus, agents_path, routes_path):
535
+ query = _validate_text(params.get("query", ""), max_len=1000)
536
+ if not query.strip():
537
+ return jsonrpc_error(1, -32602, "query must not be empty")
538
+ limit = _validate_limit(params.get("limit"), default=5, maximum=20)
539
+
540
+ if not _init_skill_engine():
541
+ return jsonrpc_response(1, {
542
+ "error": "agent-skill-system not installed",
543
+ "hint": "pip install agent-skill-system",
544
+ "skills": [],
545
+ })
546
+
547
+ try:
548
+ from engine.searcher import SkillSearcher # type: ignore[import-untyped]
549
+ searcher = SkillSearcher(_SKILL_BANK_DIR)
550
+ results = searcher.search(query, _SKILL_BANK.index, top_n=limit)
551
+ except Exception as exc:
552
+ return jsonrpc_response(1, {
553
+ "error": "skill search failed: {}".format(exc),
554
+ "skills": [],
555
+ })
556
+
557
+ skills = []
558
+ for entry, cfg, score in results:
559
+ skills.append({
560
+ "name": entry.name,
561
+ "description": cfg.description,
562
+ "version": entry.version,
563
+ "success_rate": entry.success_rate,
564
+ "use_count": entry.use_count,
565
+ "score": score,
566
+ "tags": cfg.tags,
567
+ "trigger_keywords": cfg.trigger_keywords[:8],
568
+ })
569
+
570
+ return jsonrpc_response(1, {"skills": skills})
571
+
572
+
573
+ def _handle_trinity_skill_load(params, bus, agents_path, routes_path):
574
+ skill_name = _validate_text(params.get("skill_name", ""), max_len=128)
575
+ if not skill_name.strip():
576
+ return jsonrpc_error(1, -32602, "skill_name must not be empty")
577
+
578
+ if not _init_skill_engine():
579
+ return jsonrpc_response(1, {
580
+ "error": "agent-skill-system not installed",
581
+ "hint": "pip install agent-skill-system",
582
+ })
583
+
584
+ try:
585
+ from engine.loader import SkillLoader # type: ignore[import-untyped]
586
+ entry = _SKILL_BANK.get(skill_name)
587
+ if entry is None:
588
+ return jsonrpc_response(1, {
589
+ "error": "skill not found: {}".format(skill_name),
590
+ "available_skills": [s.name for s in _SKILL_BANK.list_active()],
591
+ })
592
+ loader = SkillLoader(_SKILL_BANK_DIR)
593
+ bundle = loader.load(entry)
594
+ if bundle is None:
595
+ return jsonrpc_error(1, -32002, "failed to load skill: {}".format(skill_name))
596
+
597
+ memory_entries_serialised = []
598
+ for mem in bundle.memory_entries:
599
+ memory_entries_serialised.append({
600
+ "date": mem.date,
601
+ "type": mem.type.value,
602
+ "title": mem.title,
603
+ "scene": mem.scene,
604
+ "detail": mem.detail,
605
+ })
606
+
607
+ return jsonrpc_response(1, {
608
+ "name": bundle.config.name,
609
+ "version": bundle.config.version,
610
+ "description": bundle.config.description,
611
+ "skill_md": bundle.skill_md,
612
+ "memory_md": bundle.memory_md,
613
+ "memory_entries": memory_entries_serialised,
614
+ "input_schema": bundle.config.input_schema,
615
+ "trigger_keywords": bundle.config.trigger_keywords,
616
+ "system_prompt": loader.build_system_prompt(bundle),
617
+ })
618
+ except Exception as exc:
619
+ return jsonrpc_response(1, {
620
+ "error": "skill load failed: {}".format(exc),
621
+ })
622
+
623
+
473
624
  TOOL_HANDLERS = {
474
625
  "trinity_dispatch": _handle_trinity_dispatch,
475
626
  "trinity_dispatch_auto": _handle_trinity_dispatch_auto,
@@ -479,6 +630,8 @@ TOOL_HANDLERS = {
479
630
  "trinity_doctor": _handle_trinity_doctor,
480
631
  "trinity_inbox": _handle_trinity_inbox,
481
632
  "trinity_send": _handle_trinity_send,
633
+ "trinity_skill_search": _handle_trinity_skill_search,
634
+ "trinity_skill_load": _handle_trinity_skill_load,
482
635
  }
483
636
 
484
637
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trinity-lite
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: A minimal public multi-agent task bus with routing, workers, and safety checks.
5
5
  Author: Yomiracle
6
6
  License-Expression: MIT
@@ -19,6 +19,8 @@ Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Provides-Extra: mcp
21
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"
22
24
  Dynamic: license-file
23
25
 
24
26
  # Trinity Lite
@@ -0,0 +1,6 @@
1
+
2
+ [agent-skill]
3
+ agent-skill-system>=1.0.6
4
+
5
+ [mcp]
6
+ mcp>=1.0
@@ -1,3 +0,0 @@
1
-
2
- [mcp]
3
- mcp>=1.0
File without changes
File without changes
File without changes