sourcebot 0.1.0__py3-none-any.whl

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 (110) hide show
  1. sourcebot/__init__.py +9 -0
  2. sourcebot/__main__.py +17 -0
  3. sourcebot/bus/__init__.py +4 -0
  4. sourcebot/bus/channel_adapter.py +21 -0
  5. sourcebot/bus/event_bus.py +15 -0
  6. sourcebot/bus/message_models.py +33 -0
  7. sourcebot/bus/outbound_dispatcher.py +15 -0
  8. sourcebot/bus/session_manager.py +20 -0
  9. sourcebot/cli/commands/core/__init__.py +3 -0
  10. sourcebot/cli/commands/core/command_line.py +26 -0
  11. sourcebot/cli/commands/init_commands/__init__.py +3 -0
  12. sourcebot/cli/commands/init_commands/init_global_config.py +30 -0
  13. sourcebot/cli/commands/init_commands/init_workspace_config.py +18 -0
  14. sourcebot/cli/commands/run_commands/__init__.py +3 -0
  15. sourcebot/cli/commands/run_commands/command_line_tool.py +345 -0
  16. sourcebot/cli/commands/run_commands/safe_runner.py +47 -0
  17. sourcebot/cli/main.py +28 -0
  18. sourcebot/config/__init__.py +15 -0
  19. sourcebot/config/base.py +13 -0
  20. sourcebot/config/config_manager.py +367 -0
  21. sourcebot/config/exceptions.py +4 -0
  22. sourcebot/config/global_config.py +55 -0
  23. sourcebot/config/provider_config.py +62 -0
  24. sourcebot/config/workspace_config.py +106 -0
  25. sourcebot/context/__init__.py +5 -0
  26. sourcebot/context/context_builder.py +78 -0
  27. sourcebot/context/identity.py +19 -0
  28. sourcebot/context/message_builder.py +154 -0
  29. sourcebot/context/skill/__init__.py +7 -0
  30. sourcebot/context/skill/skill.py +11 -0
  31. sourcebot/context/skill/skill_context.py +10 -0
  32. sourcebot/context/skill/skill_loader.py +57 -0
  33. sourcebot/context/skill/skill_metadata.py +27 -0
  34. sourcebot/context/skill/skill_requirements.py +25 -0
  35. sourcebot/context/skill/skill_summary.py +31 -0
  36. sourcebot/conversation/__init__.py +2 -0
  37. sourcebot/conversation/service.py +191 -0
  38. sourcebot/docker_sandbox/__init__.py +3 -0
  39. sourcebot/docker_sandbox/docker_sandbox.py +113 -0
  40. sourcebot/llm/__init__.py +3 -0
  41. sourcebot/llm/anthropic/__init__.py +2 -0
  42. sourcebot/llm/anthropic/adapter.py +30 -0
  43. sourcebot/llm/anthropic/anthropic_llm_client.py +38 -0
  44. sourcebot/llm/anthropic/converter.py +59 -0
  45. sourcebot/llm/core/adapter.py +16 -0
  46. sourcebot/llm/core/client.py +16 -0
  47. sourcebot/llm/core/delta.py +12 -0
  48. sourcebot/llm/core/message.py +53 -0
  49. sourcebot/llm/core/message_converter.py +33 -0
  50. sourcebot/llm/core/response.py +30 -0
  51. sourcebot/llm/core/tool.py +7 -0
  52. sourcebot/llm/core/tool_converter.py +30 -0
  53. sourcebot/llm/core/tool_delta_aggregator.py +38 -0
  54. sourcebot/llm/llm_client_factory.py +13 -0
  55. sourcebot/llm/openai/__init__.py +2 -0
  56. sourcebot/llm/openai/adapter.py +27 -0
  57. sourcebot/llm/openai/converter.py +53 -0
  58. sourcebot/llm/openai/openai_llm_client.py +47 -0
  59. sourcebot/logging/__init__.py +3 -0
  60. sourcebot/logging/setup.py +33 -0
  61. sourcebot/memory/__init__.py +5 -0
  62. sourcebot/memory/file_store.py +23 -0
  63. sourcebot/memory/llm_consolidator.py +79 -0
  64. sourcebot/memory/service.py +116 -0
  65. sourcebot/memory/window_policy.py +36 -0
  66. sourcebot/prompt/__init__.py +4 -0
  67. sourcebot/prompt/deeomposer_prompt.py +420 -0
  68. sourcebot/prompt/identity_prompt.py +98 -0
  69. sourcebot/prompt/subagent_prompt.py +25 -0
  70. sourcebot/runtime/__init__.py +3 -0
  71. sourcebot/runtime/agent/__init__.py +3 -0
  72. sourcebot/runtime/agent/agent.py +130 -0
  73. sourcebot/runtime/agent/agent_factory.py +83 -0
  74. sourcebot/runtime/dag/planner/__init__.py +3 -0
  75. sourcebot/runtime/dag/planner/dag_planner.py +26 -0
  76. sourcebot/runtime/dag/planner/execution_scheduler.py +35 -0
  77. sourcebot/runtime/dag/planner/parallelism_optimizer.py +44 -0
  78. sourcebot/runtime/dag/planner/task_decomposer.py +37 -0
  79. sourcebot/runtime/dag/scheduler/__init__.py +3 -0
  80. sourcebot/runtime/dag/scheduler/dag_scheduler.py +319 -0
  81. sourcebot/runtime/dag/scheduler/retry_policy.py +27 -0
  82. sourcebot/runtime/dag/scheduler/run_store.py +58 -0
  83. sourcebot/runtime/dag/scheduler/state_store.py +40 -0
  84. sourcebot/runtime/dag/scheduler/task_graph.py +29 -0
  85. sourcebot/runtime/init_system.py +182 -0
  86. sourcebot/runtime/tool_executor.py +30 -0
  87. sourcebot/security/policy.py +23 -0
  88. sourcebot/session/__init__.py +4 -0
  89. sourcebot/session/jsonl_repository.py +142 -0
  90. sourcebot/session/repository.py +19 -0
  91. sourcebot/session/service.py +44 -0
  92. sourcebot/session/session.py +53 -0
  93. sourcebot/storage/__init__.py +3 -0
  94. sourcebot/storage/rules_loader.py +72 -0
  95. sourcebot/storage/skill_storage.py +51 -0
  96. sourcebot/tools/__init__.py +7 -0
  97. sourcebot/tools/base.py +182 -0
  98. sourcebot/tools/registry.py +81 -0
  99. sourcebot/tools/rule_detail.py +70 -0
  100. sourcebot/tools/rule_list.py +57 -0
  101. sourcebot/tools/shell.py +93 -0
  102. sourcebot/tools/skill_detail.py +61 -0
  103. sourcebot/tools/skill_list.py +68 -0
  104. sourcebot/utils/__init__.py +2 -0
  105. sourcebot/utils/output.py +79 -0
  106. sourcebot-0.1.0.dist-info/METADATA +318 -0
  107. sourcebot-0.1.0.dist-info/RECORD +110 -0
  108. sourcebot-0.1.0.dist-info/WHEEL +5 -0
  109. sourcebot-0.1.0.dist-info/entry_points.txt +2 -0
  110. sourcebot-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,319 @@
1
+ # sourcebot/runtime/dag/scheduler/dag_scheduler.py
2
+ import asyncio
3
+ import json
4
+ import logging
5
+ from pathlib import Path
6
+ from collections import deque
7
+ from datetime import datetime
8
+ from sourcebot.runtime.dag.scheduler.task_graph import TaskGraph
9
+ from sourcebot.runtime.dag.scheduler.state_store import StateStore
10
+ from sourcebot.runtime.dag.scheduler.retry_policy import RetryPolicy
11
+ from sourcebot.runtime.dag.scheduler.run_store import RunStore
12
+ from sourcebot.llm.core.message import Message
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class DAGScheduler:
18
+
19
+ def __init__(
20
+ self,
21
+ agent_factory,
22
+ workspace,
23
+ runs_dir="runs",
24
+ max_concurrent=4,
25
+ retry_policy=None,
26
+
27
+ ):
28
+
29
+ self.agent_factory = agent_factory
30
+
31
+ self.runs_dir = Path(workspace)/runs_dir
32
+
33
+ self.max_concurrent = max_concurrent
34
+
35
+ self.retry_policy = retry_policy or RetryPolicy()
36
+
37
+ self.semaphore = asyncio.Semaphore(max_concurrent)
38
+
39
+ self.run_store = RunStore(self.runs_dir)
40
+
41
+ # Run DAG
42
+ async def run(self, tasks):
43
+
44
+ self.run_id, self.run_dir = self.run_store.create_run(tasks)
45
+
46
+ logger.info(f"DAG RUN {self.run_id}")
47
+
48
+ self.state = StateStore(self.run_dir)
49
+
50
+ graph = TaskGraph(tasks)
51
+
52
+ result = await self._execute(graph)
53
+
54
+ if result["failed"]:
55
+ self.run_store.update_status(self.run_dir, "failed")
56
+ else:
57
+ self.run_store.update_status(self.run_dir, "completed")
58
+
59
+ return result
60
+
61
+ # Resume run
62
+ async def resume(self, run_id):
63
+
64
+ self.run_id = run_id
65
+ self.run_dir = self.runs_dir / run_id
66
+
67
+ tasks = self.run_store.load_dag(self.run_dir)
68
+
69
+ self.state = StateStore(self.run_dir)
70
+
71
+ graph = TaskGraph(tasks)
72
+
73
+ logger.info(f"Resuming {run_id}")
74
+
75
+ return await self._execute(graph)
76
+
77
+
78
+ # Replay failed tasks
79
+ async def replay_failed(self, run_id):
80
+
81
+ self.run_id = run_id
82
+ self.run_dir = self.runs_dir / run_id
83
+
84
+ tasks = self.run_store.load_dag(self.run_dir)
85
+
86
+ self.state = StateStore(self.run_dir)
87
+
88
+ for t in list(self.state.state.keys()):
89
+
90
+ if self.state.state[t]["status"] == "failed":
91
+ self.state.state[t]["status"] = "pending"
92
+
93
+ self.state.save()
94
+
95
+ graph = TaskGraph(tasks)
96
+
97
+ logger.info(f"Replay failed tasks {run_id}")
98
+
99
+ return await self._execute(graph)
100
+
101
+
102
+ # Core execution
103
+ async def _execute(self, graph):
104
+
105
+ ready = deque()
106
+
107
+ running = {}
108
+
109
+ future_map = {}
110
+
111
+ completed = set()
112
+ failed = set()
113
+
114
+ for tid, deg in graph.in_degree.items():
115
+
116
+ state = self.state.status(tid)
117
+
118
+ if state == "completed":
119
+ completed.add(tid)
120
+ continue
121
+
122
+ if deg == 0:
123
+ ready.append(tid)
124
+
125
+ while ready or running:
126
+
127
+ while ready and len(running) < self.max_concurrent:
128
+
129
+ tid = ready.popleft()
130
+
131
+ task = graph.tasks[tid]
132
+
133
+ fut = asyncio.create_task(
134
+ self._execute_task(task)
135
+ )
136
+
137
+ running[tid] = fut
138
+ future_map[fut] = tid
139
+
140
+ if not running:
141
+ break
142
+
143
+ done, _ = await asyncio.wait(
144
+ running.values(),
145
+ return_when=asyncio.FIRST_COMPLETED
146
+ )
147
+
148
+ for fut in done:
149
+
150
+ tid = future_map[fut]
151
+
152
+ try:
153
+
154
+ result = fut.result()
155
+
156
+ if result["success"]:
157
+
158
+ completed.add(tid)
159
+
160
+ for child in graph.children[tid]:
161
+
162
+ graph.in_degree[child] -= 1
163
+
164
+ if graph.in_degree[child] == 0:
165
+ ready.append(child)
166
+
167
+ else:
168
+ failed.add(tid)
169
+
170
+ except Exception as e:
171
+
172
+ failed.add(tid)
173
+ logger.error(f"{tid} crashed: {e}")
174
+
175
+ del running[tid]
176
+
177
+ return {
178
+ "completed": list(completed),
179
+ "failed": list(failed)
180
+ }
181
+
182
+
183
+ # Task execution
184
+ async def _execute_task(self, task):
185
+
186
+ task_id = task["id"]
187
+
188
+ log_file = self.run_dir / "tasks" / f"{task_id}.log"
189
+
190
+ attempt = self.state.attempts(task_id) + 1
191
+
192
+ while True:
193
+
194
+ try:
195
+
196
+ self.state.update(
197
+ task_id,
198
+ status = "running",
199
+ attempts = attempt,
200
+ started = str(datetime.utcnow())
201
+ )
202
+
203
+ async with self.semaphore:
204
+
205
+ agent = self.agent_factory.build_sub_agent(
206
+ task_id = task_id,
207
+ task_description = task["description"]
208
+ )
209
+ messages = self._build_messages(task)
210
+ result, _, _ = await agent.run(messages)
211
+
212
+ self._write_log(log_file, result)
213
+
214
+ if self._is_success(result):
215
+
216
+ self.state.update(
217
+ task_id,
218
+ status = "completed",
219
+ result = result
220
+ )
221
+
222
+ return {"success": True}
223
+
224
+ raise RuntimeError("task failed")
225
+
226
+ except Exception as e:
227
+
228
+ self._write_log(log_file, str(e))
229
+
230
+ if not self.retry_policy.should_retry(attempt):
231
+
232
+ self.state.update(
233
+ task_id,
234
+ status = "failed",
235
+ error = str(e)
236
+ )
237
+
238
+ return {"success": False}
239
+
240
+ delay = self.retry_policy.get_delay(attempt)
241
+
242
+ logger.warning(
243
+ f"{task_id} retry in {delay}s"
244
+ )
245
+
246
+ await asyncio.sleep(delay)
247
+
248
+ attempt += 1
249
+
250
+ # =================================
251
+ # Helpers
252
+ # =================================
253
+
254
+ def _write_log(self, file: Path, data):
255
+
256
+ file.parent.mkdir(parents=True, exist_ok=True)
257
+
258
+ with open(file, "a") as f:
259
+
260
+ f.write(
261
+ f"\n[{datetime.utcnow()}]\n"
262
+ )
263
+
264
+ if isinstance(data, str):
265
+ f.write(data + "\n")
266
+ else:
267
+ f.write(json.dumps(data, indent=2) + "\n")
268
+
269
+ def _is_success(self, result):
270
+
271
+ if isinstance(result, dict):
272
+ return result.get("success", True)
273
+
274
+ return bool(result)
275
+
276
+ def _build_messages(self, task):
277
+ return [
278
+ Message(role = "system", content = f"You are executing task {task['id']}"),
279
+ Message(role = "user", content = self.build_task_description(task)),
280
+ ]
281
+
282
+ def build_task_description(self, task):
283
+ """Combine the task and context into a complete description."""
284
+ description = task.get("description", "")
285
+ context = task.get("context", {})
286
+
287
+ context_parts = []
288
+
289
+ if context.get("rules"):
290
+ rules = "\n • ".join(context["rules"])
291
+ context_parts.append(f"Rules:\n • {rules}")
292
+
293
+ if context.get("skills"):
294
+ skills = ", ".join(context["skills"])
295
+ context_parts.append(f"Required skills: {skills}")
296
+
297
+ if context.get("environment"):
298
+ env = context["environment"]
299
+ if env.get("required_tools"):
300
+ tools = ", ".join(env["required_tools"])
301
+ context_parts.append(f"Required tools: {tools}")
302
+ if env.get("working_dir"):
303
+ context_parts.append(f"Working directory: {env['working_dir']}")
304
+
305
+ if context.get("inherited_context"):
306
+ inherited = context["inherited_context"]
307
+ inherited_items = [f"{k}: {v}" for k, v in inherited.items()]
308
+ context_parts.append(f"Context: {', '.join(inherited_items)}")
309
+
310
+ if context_parts:
311
+ full_description = f"{description}\n\nContext Information:\n" + "\n".join(context_parts)
312
+ else:
313
+ full_description = description
314
+
315
+ return full_description
316
+
317
+
318
+ def save_dag(self, plan_tasks):
319
+ return self.run_store.create_run(plan_tasks)
@@ -0,0 +1,27 @@
1
+ # sourcebot/runtime/dag/scheduler/retry_policy.py
2
+ import random
3
+ class RetryPolicy:
4
+
5
+ def __init__(
6
+ self,
7
+ max_attempts: int = 3,
8
+ base_delay: int = 2,
9
+ max_delay: int = 30,
10
+ jitter: bool = True,
11
+ ):
12
+ self.max_attempts = max_attempts
13
+ self.base_delay = base_delay
14
+ self.max_delay = max_delay
15
+ self.jitter = jitter
16
+
17
+ def should_retry(self, attempt: int) -> bool:
18
+ return attempt < self.max_attempts
19
+
20
+ def get_delay(self, attempt: int) -> float:
21
+
22
+ delay = min(self.base_delay * (2 ** (attempt - 1)), self.max_delay)
23
+
24
+ if self.jitter:
25
+ delay = delay * (0.5 + random.random())
26
+
27
+ return delay
@@ -0,0 +1,58 @@
1
+ # sourcebot/runtime/dag/scheduler/run_store.py
2
+ import json
3
+ import uuid
4
+ from pathlib import Path
5
+ from datetime import datetime
6
+
7
+
8
+ class RunStore:
9
+
10
+ def __init__(self, runs_dir: Path):
11
+
12
+ self.runs_dir = runs_dir
13
+ self.runs_dir.mkdir(parents=True, exist_ok=True)
14
+
15
+ def create_run(self, tasks):
16
+
17
+ run_id = f"run_{uuid.uuid4().hex[:8]}"
18
+
19
+ run_dir = self.runs_dir / run_id
20
+ run_dir.mkdir(parents=True)
21
+
22
+ (run_dir / "tasks").mkdir()
23
+
24
+ (run_dir / "dag.json").write_text(
25
+ json.dumps(tasks, indent=2)
26
+ )
27
+
28
+ meta = {
29
+ "run_id": run_id,
30
+ "created_at": str(datetime.utcnow()),
31
+ "status": "running"
32
+ }
33
+
34
+ (run_dir / "run_meta.json").write_text(
35
+ json.dumps(meta, indent=2)
36
+ )
37
+
38
+ return run_id, run_dir
39
+
40
+ def load_dag(self, run_dir):
41
+
42
+ file = run_dir / "dag.json"
43
+
44
+ if not file.exists():
45
+ raise RuntimeError("dag.json missing")
46
+
47
+ return json.loads(file.read_text())
48
+
49
+ def update_status(self, run_dir, status):
50
+
51
+ meta_file = run_dir / "run_meta.json"
52
+
53
+ meta = json.loads(meta_file.read_text())
54
+
55
+ meta["status"] = status
56
+ meta["updated_at"] = str(datetime.utcnow())
57
+
58
+ meta_file.write_text(json.dumps(meta, indent=2))
@@ -0,0 +1,40 @@
1
+ # sourcebot/runtime/dag/scheduler/state_store.py
2
+ import json
3
+ from pathlib import Path
4
+ from datetime import datetime
5
+
6
+
7
+ class StateStore:
8
+
9
+ def __init__(self, run_dir: Path):
10
+
11
+ self.file = run_dir / "state.json"
12
+
13
+ if self.file.exists():
14
+ self.state = json.loads(self.file.read_text())
15
+ else:
16
+ self.state = {}
17
+
18
+ def save(self):
19
+
20
+ self.file.write_text(
21
+ json.dumps(self.state, indent=2)
22
+ )
23
+
24
+ def status(self, task_id):
25
+
26
+ return self.state.get(task_id, {}).get("status")
27
+
28
+ def attempts(self, task_id):
29
+
30
+ return self.state.get(task_id, {}).get("attempts", 0)
31
+
32
+ def update(self, task_id, **fields):
33
+
34
+ entry = self.state.setdefault(task_id, {})
35
+
36
+ entry.update(fields)
37
+
38
+ entry["updated_at"] = str(datetime.utcnow())
39
+
40
+ self.save()
@@ -0,0 +1,29 @@
1
+ # sourcebot/runtime/dag/scheduler/task_graph.py
2
+ from collections import defaultdict
3
+
4
+ class TaskGraph:
5
+
6
+ def __init__(self, tasks):
7
+
8
+ self.tasks = {t["id"]: t for t in tasks}
9
+
10
+ self.children = defaultdict(list)
11
+ self.in_degree = {}
12
+
13
+ for t in tasks:
14
+
15
+ tid = t["id"]
16
+ deps = t.get("depends_on", [])
17
+
18
+ self.in_degree[tid] = len(deps)
19
+
20
+ for d in deps:
21
+ self.children[d].append(tid)
22
+
23
+ def roots(self):
24
+
25
+ return [
26
+ tid
27
+ for tid, deg in self.in_degree.items()
28
+ if deg == 0
29
+ ]
@@ -0,0 +1,182 @@
1
+ # sourcebot/runtime/init_system.py
2
+ from rich.console import Console
3
+ import sys
4
+ from pathlib import Path
5
+ from sourcebot.context import ContextBuilder
6
+ from sourcebot.config import ConfigManager
7
+ from sourcebot.security.policy import SecurityPolicy
8
+ from sourcebot.bus import EventBus
9
+ from sourcebot.context import MessageBuilder
10
+ from sourcebot.conversation.service import ConversationService
11
+ from sourcebot.session import JsonlSessionRepository, SessionService
12
+ from sourcebot.runtime.dag.scheduler import DAGScheduler
13
+ from sourcebot.runtime.dag.planner import DAGPlanner
14
+ from sourcebot.memory import FileMemoryStore, LLMConsolidator, MemoryService, WindowMemoryPolicy
15
+ from sourcebot.runtime.agent import AgentFactory
16
+ from sourcebot.tools import ToolRegistry, ShellTool, SkillListTool, SkillDetailTool, RuleListTool, RuleDetailTool
17
+ from sourcebot.storage import SkillStorage, RulesLoader
18
+ from sourcebot.docker_sandbox import DockerSandbox
19
+ from sourcebot.llm import LLMClientFactory
20
+ import logging
21
+ logger = logging.getLogger(__name__)
22
+
23
+ class InitSystem:
24
+ def __init__(self):
25
+ self.console = Console(
26
+ force_terminal=sys.stdout.isatty(),
27
+ soft_wrap=True
28
+ )
29
+
30
+ config_manager = ConfigManager()
31
+
32
+ # Config
33
+ app_root_path = config_manager.app_root_path
34
+ skill_dir_path = app_root_path/"skills"
35
+
36
+ # Workspace config
37
+ workspace_config = config_manager.load_workspace_config()
38
+
39
+ # Workspace config error
40
+ if workspace_config is None:
41
+ self.console.print("❌ Error: Workspace configuration not found")
42
+ self.console.print("Please run the initialization command in the root directory of the workspace first:")
43
+ self.console.print("[cyan]sourcebot init_workspace[/cyan]")
44
+ self.console.print("Or ensure the current directory is a valid Sourcebot workspace"
45
+ )
46
+
47
+ sys.exit(1)
48
+ # Agent config
49
+ main_provider_name = workspace_config.models["main_agent"].provider
50
+ main_model_name = workspace_config.models["main_agent"].model
51
+ sub_provider_name = workspace_config.models["sub_agent"].provider
52
+ sub_model_name = workspace_config.models["sub_agent"].model
53
+
54
+ # Host working path
55
+ host_workspace = Path(workspace_config.project_root)
56
+
57
+ # Docker working path
58
+ # The Docker working directory remains unchanged
59
+ docker_workspace = Path("/workspace")
60
+ # Skill storage
61
+ skill_storage = SkillStorage(
62
+ root_skills = skill_dir_path,
63
+ builtin_skills = host_workspace/"skills" # The work directory skill path should not appear in the prompt.
64
+ )
65
+
66
+ # Rules loader
67
+ rules_loader = RulesLoader(app_root_path)
68
+
69
+ # Context builder
70
+ # 🔴 Note that the host machine's workspace address should not appear in the prompt; it should always be docker_workspace.
71
+ context_builder = ContextBuilder(
72
+ workspace = docker_workspace,
73
+ skill_storage = skill_storage,
74
+ rules_loader = rules_loader,
75
+ )
76
+
77
+ # Message builder
78
+ message_builder = MessageBuilder(context_builder)
79
+
80
+ # Security policy
81
+ # TODO: Security policy not yet implemented
82
+ security_policy = SecurityPolicy()
83
+
84
+ # Docker sandbox
85
+ self.docker_sandbox = DockerSandbox(
86
+ image = workspace_config.docker_image,
87
+ host_workspace = str(host_workspace)
88
+ )
89
+ # ====================
90
+ # Tool registry
91
+ # ====================
92
+ tools = ToolRegistry()
93
+ # Skill
94
+ tools.register(
95
+ SkillListTool(
96
+ skill_storage = skill_storage,
97
+ )
98
+ )
99
+ tools.register(
100
+ SkillDetailTool(
101
+ skill_storage = skill_storage,
102
+ host_workspace = host_workspace,
103
+ )
104
+ )
105
+ # Rule
106
+ tools.register(
107
+ RuleListTool(
108
+ rules_loader = rules_loader,
109
+ )
110
+ )
111
+ tools.register(
112
+ RuleDetailTool(
113
+ rules_loader = rules_loader,
114
+ )
115
+ )
116
+ # Shell
117
+ tools.register(
118
+ ShellTool(
119
+ sandbox = self.docker_sandbox,
120
+ timeout = 100,
121
+ )
122
+ )
123
+
124
+ # Message bus
125
+ # TODO: Not yet enabled
126
+ self.bus = EventBus()
127
+
128
+ # Agent factory
129
+ agent_factory = AgentFactory(
130
+ bus = self.bus,
131
+ tools = tools,
132
+ message_builder = message_builder,
133
+ main_provider_name = main_provider_name,
134
+ main_model_name = main_model_name,
135
+ config_manager = config_manager,
136
+ policy = security_policy,
137
+ max_iterations = 40,
138
+ sub_provider_name = sub_provider_name,
139
+ sub_model_name = sub_model_name,
140
+ )
141
+ # DAG planner
142
+ main_llm = LLMClientFactory.create_client(
143
+ config_manager = config_manager,
144
+ provider_name = main_provider_name,
145
+ model_name = main_model_name
146
+ )
147
+ self.dag_planner = DAGPlanner(main_llm, context_builder)
148
+ # DAG scheduler
149
+ self.dag_scheduler = DAGScheduler(
150
+ agent_factory = agent_factory,
151
+ workspace = host_workspace
152
+ )
153
+
154
+
155
+ # Main agent runtime
156
+ main_agent = agent_factory.build_main_agent()
157
+
158
+ # Memory service
159
+ file_memory_store = FileMemoryStore(host_workspace)
160
+ window_memory_policy = WindowMemoryPolicy()
161
+ llm_consolidator = LLMConsolidator(main_llm)
162
+ memory_service = MemoryService(
163
+ store = file_memory_store,
164
+ policy = window_memory_policy,
165
+ consolidator = llm_consolidator
166
+ )
167
+
168
+ # Session service
169
+ session_repository = JsonlSessionRepository(host_workspace)
170
+ session_service = SessionService(session_repository)
171
+
172
+ # Conversation service
173
+ self.conversation_service = ConversationService(
174
+ session_service = session_service,
175
+ memory_service = memory_service,
176
+ agent = main_agent,
177
+ message_builder = message_builder,
178
+ bus = self.bus
179
+ )
180
+
181
+
182
+
@@ -0,0 +1,30 @@
1
+ # sourcebot/runtime/tool_executor.py
2
+
3
+ import asyncio
4
+ class ToolExecutor:
5
+
6
+ def __init__(self, tools, timeout = 60, retries = 2):
7
+ self.tools = tools
8
+ self.timeout = timeout
9
+ self.retries = retries
10
+
11
+ async def execute(self, name: str, args):
12
+
13
+ last_error = None
14
+
15
+ for attempt in range(self.retries + 1):
16
+
17
+ try:
18
+
19
+ return await asyncio.wait_for(
20
+ self.tools.execute(name, args),
21
+ timeout = self.timeout
22
+ )
23
+
24
+ except asyncio.TimeoutError:
25
+ last_error = f"Tool {name} timed out"
26
+
27
+ except Exception as e:
28
+ last_error = str(e)
29
+
30
+ return f"Tool failed after retries: {last_error}"
@@ -0,0 +1,23 @@
1
+ # domain/security/policy.py
2
+ from typing import Any, Dict, List
3
+ RULES_DB = [
4
+ {"pattern": "eval(", "safe_replacement": "# eval removed for safety"}
5
+ ]
6
+
7
+ class SecurityPolicy:
8
+ # TODO: To be implemented
9
+ def apply_policy(self, code: str) -> str:
10
+ safe_code = code
11
+ for rule in RULES_DB:
12
+ if rule["pattern"] in code:
13
+ safe_code = safe_code.replace(rule["pattern"], rule["safe_replacement"])
14
+ return safe_code
15
+ async def before_llm(self, messages: List[Dict]) -> None:
16
+ pass
17
+
18
+ async def before_tool(self, tool_name: str, args: Dict[str, Any]) -> None:
19
+ pass
20
+
21
+ async def after_tool(self, tool_name: str, result: str) -> None:
22
+ pass
23
+