nexus-coding-agent 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 (131) hide show
  1. nexus/__init__.py +4 -0
  2. nexus/application/__init__.py +2 -0
  3. nexus/application/approval_service.py +130 -0
  4. nexus/application/diff_service.py +177 -0
  5. nexus/application/event_publisher.py +180 -0
  6. nexus/application/execution_context.py +29 -0
  7. nexus/application/execution_ledger.py +126 -0
  8. nexus/application/model_call_context.py +29 -0
  9. nexus/application/observed_model_gateway.py +194 -0
  10. nexus/application/plan_approval_service.py +250 -0
  11. nexus/application/planning.py +668 -0
  12. nexus/application/runtime.py +624 -0
  13. nexus/application/session_service.py +411 -0
  14. nexus/application/structured_output.py +96 -0
  15. nexus/application/telemetry.py +396 -0
  16. nexus/application/telemetry_subscriber.py +216 -0
  17. nexus/application/tool_runtime.py +496 -0
  18. nexus/application/tracing_dispatcher.py +283 -0
  19. nexus/application/validation.py +293 -0
  20. nexus/config/__init__.py +17 -0
  21. nexus/config/loader.py +253 -0
  22. nexus/config/models.py +310 -0
  23. nexus/context/__init__.py +6 -0
  24. nexus/context/budget.py +24 -0
  25. nexus/context/builder.py +92 -0
  26. nexus/context/chunking.py +65 -0
  27. nexus/context/explorer.py +246 -0
  28. nexus/context/integration.py +63 -0
  29. nexus/context/manager.py +328 -0
  30. nexus/context/retrieval.py +261 -0
  31. nexus/domain/__init__.py +2 -0
  32. nexus/domain/agent_decision.py +113 -0
  33. nexus/domain/agent_state.py +49 -0
  34. nexus/domain/approvals.py +64 -0
  35. nexus/domain/context.py +108 -0
  36. nexus/domain/exploration.py +86 -0
  37. nexus/domain/mcp.py +51 -0
  38. nexus/domain/model.py +173 -0
  39. nexus/domain/observability.py +326 -0
  40. nexus/domain/persistence.py +122 -0
  41. nexus/domain/planning.py +329 -0
  42. nexus/domain/ports/__init__.py +61 -0
  43. nexus/domain/ports/agent_decision.py +9 -0
  44. nexus/domain/ports/approval_unit_of_work.py +27 -0
  45. nexus/domain/ports/checkpoint_provider.py +11 -0
  46. nexus/domain/ports/context.py +71 -0
  47. nexus/domain/ports/events.py +21 -0
  48. nexus/domain/ports/graph_runtime.py +30 -0
  49. nexus/domain/ports/mcp.py +23 -0
  50. nexus/domain/ports/model_gateway.py +21 -0
  51. nexus/domain/ports/model_input_budget.py +15 -0
  52. nexus/domain/ports/planning.py +62 -0
  53. nexus/domain/ports/repositories.py +54 -0
  54. nexus/domain/ports/repository_context.py +18 -0
  55. nexus/domain/ports/session_unit_of_work.py +33 -0
  56. nexus/domain/ports/skills.py +41 -0
  57. nexus/domain/ports/tooling.py +39 -0
  58. nexus/domain/ports/tracer.py +13 -0
  59. nexus/domain/ports/validation.py +32 -0
  60. nexus/domain/runtime_events.py +445 -0
  61. nexus/domain/skills.py +129 -0
  62. nexus/domain/tooling.py +163 -0
  63. nexus/domain/validation.py +161 -0
  64. nexus/errors/__init__.py +82 -0
  65. nexus/evaluation/__init__.py +7 -0
  66. nexus/evaluation/assertions.py +4 -0
  67. nexus/evaluation/evaluator.py +263 -0
  68. nexus/evaluation/fixtures.py +36 -0
  69. nexus/evaluation/harness.py +606 -0
  70. nexus/evaluation/loader.py +415 -0
  71. nexus/evaluation/metrics.py +27 -0
  72. nexus/evaluation/models.py +362 -0
  73. nexus/evaluation/report.py +43 -0
  74. nexus/evaluation/runner.py +480 -0
  75. nexus/infrastructure/__init__.py +2 -0
  76. nexus/infrastructure/asyncio_compat.py +13 -0
  77. nexus/infrastructure/bootstrap/__init__.py +17 -0
  78. nexus/infrastructure/bootstrap/composition.py +663 -0
  79. nexus/infrastructure/checkpoint/__init__.py +5 -0
  80. nexus/infrastructure/checkpoint/postgres.py +62 -0
  81. nexus/infrastructure/database.py +39 -0
  82. nexus/infrastructure/embedding.py +61 -0
  83. nexus/infrastructure/graph/__init__.py +6 -0
  84. nexus/infrastructure/graph/day4_runtime.py +966 -0
  85. nexus/infrastructure/graph/langgraph_runtime.py +172 -0
  86. nexus/infrastructure/mcp/__init__.py +5 -0
  87. nexus/infrastructure/mcp/manager.py +380 -0
  88. nexus/infrastructure/model_gateway/__init__.py +6 -0
  89. nexus/infrastructure/model_gateway/openai_compatible.py +345 -0
  90. nexus/infrastructure/observability/__init__.py +7 -0
  91. nexus/infrastructure/observability/console.py +31 -0
  92. nexus/infrastructure/observability/langsmith.py +236 -0
  93. nexus/infrastructure/observability/structured_logger.py +70 -0
  94. nexus/infrastructure/persistence/__init__.py +13 -0
  95. nexus/infrastructure/persistence/approval_unit_of_work.py +74 -0
  96. nexus/infrastructure/persistence/models.py +146 -0
  97. nexus/infrastructure/persistence/repositories.py +326 -0
  98. nexus/infrastructure/persistence/unit_of_work.py +82 -0
  99. nexus/infrastructure/repository_files.py +117 -0
  100. nexus/infrastructure/sandbox/__init__.py +5 -0
  101. nexus/infrastructure/sandbox/local_process.py +353 -0
  102. nexus/infrastructure/semantic.py +276 -0
  103. nexus/infrastructure/semantic_models.py +56 -0
  104. nexus/interfaces/__init__.py +2 -0
  105. nexus/interfaces/cli/__init__.py +2 -0
  106. nexus/interfaces/cli/__main__.py +6 -0
  107. nexus/interfaces/cli/app.py +266 -0
  108. nexus/interfaces/cli/renderer.py +118 -0
  109. nexus/security/__init__.py +14 -0
  110. nexus/security/approval_policies.py +73 -0
  111. nexus/security/command_policy.py +117 -0
  112. nexus/security/executables.py +107 -0
  113. nexus/security/git_commands.py +65 -0
  114. nexus/security/workspace.py +93 -0
  115. nexus/skills/__init__.py +7 -0
  116. nexus/skills/builtin/__init__.py +1 -0
  117. nexus/skills/builtin/debug-python/SKILL.md +26 -0
  118. nexus/skills/builtin/review-repository/SKILL.md +26 -0
  119. nexus/skills/builtin/write-tests/SKILL.md +26 -0
  120. nexus/skills/loader.py +439 -0
  121. nexus/skills/registry.py +146 -0
  122. nexus/skills/selector.py +152 -0
  123. nexus/tools/__init__.py +6 -0
  124. nexus/tools/editing.py +312 -0
  125. nexus/tools/mcp.py +160 -0
  126. nexus/tools/native.py +654 -0
  127. nexus/tools/registry.py +27 -0
  128. nexus_coding_agent-0.1.0.dist-info/METADATA +142 -0
  129. nexus_coding_agent-0.1.0.dist-info/RECORD +131 -0
  130. nexus_coding_agent-0.1.0.dist-info/WHEEL +4 -0
  131. nexus_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,663 @@
1
+ """Assemble and dispose approved concrete application dependencies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from collections.abc import AsyncIterator, Callable
8
+ from contextlib import asynccontextmanager
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any, cast
12
+
13
+ from langgraph.checkpoint.base import BaseCheckpointSaver
14
+ from langsmith import Client
15
+
16
+ from nexus.application.approval_service import ApprovalService
17
+ from nexus.application.diff_service import FinalDiffCollector
18
+ from nexus.application.event_publisher import (
19
+ InProcessEventPublisher,
20
+ LiveRuntimeEventBridge,
21
+ )
22
+ from nexus.application.execution_context import current_execution_context
23
+ from nexus.application.execution_ledger import RuntimeEventBuffer, ToolExecutionLedger
24
+ from nexus.application.observed_model_gateway import ObservedModelGateway
25
+ from nexus.application.plan_approval_service import PlanApprovalService
26
+ from nexus.application.planning import JsonAgentDecisionAdapter, ModelPlanner
27
+ from nexus.application.runtime import NexusRuntime
28
+ from nexus.application.session_service import SessionService
29
+ from nexus.application.telemetry import EventEnricher, TelemetryRedactor
30
+ from nexus.application.telemetry_subscriber import TelemetrySubscriber
31
+ from nexus.application.tool_runtime import RuntimeEventEmitter, ToolRuntime
32
+ from nexus.application.tracing_dispatcher import (
33
+ SafeTracerDispatcher,
34
+ TracerSink,
35
+ )
36
+ from nexus.application.validation import (
37
+ DeterministicValidationPlanner,
38
+ ToolValidationRunner,
39
+ )
40
+ from nexus.config.models import ApprovalMode, MCPServerConfig, RuntimeConfig
41
+ from nexus.context import SelectiveRepositoryExplorer
42
+ from nexus.context.budget import Day5ModelInputBudgetGuard
43
+ from nexus.context.chunking import LineWindowChunker
44
+ from nexus.context.integration import ManagedContextBuilder
45
+ from nexus.context.manager import BoundedContextManager
46
+ from nexus.context.retrieval import (
47
+ HybridContextProvider,
48
+ ToolLexicalSearchProvider,
49
+ ToolRepositoryAccess,
50
+ )
51
+ from nexus.domain.context import ContextBudget, IndexRequest, IndexResult
52
+ from nexus.domain.mcp import MCPToolDescriptor
53
+ from nexus.domain.persistence import SessionSummary
54
+ from nexus.domain.ports.checkpoint_provider import CheckpointProvider
55
+ from nexus.domain.ports.context import ContextProvider, EmbeddingGateway
56
+ from nexus.domain.ports.graph_runtime import GraphRuntime
57
+ from nexus.domain.ports.mcp import MCPManager
58
+ from nexus.domain.ports.model_gateway import ModelGateway
59
+ from nexus.domain.ports.tooling import ApprovalPolicy
60
+ from nexus.domain.runtime_events import (
61
+ ObservabilityWarning,
62
+ RuntimeEvent,
63
+ TraceFallback,
64
+ TraceOperation,
65
+ TraceSink,
66
+ )
67
+ from nexus.domain.tooling import JsonObject, RiskLevel
68
+ from nexus.errors import ConfigurationError, ContextError
69
+ from nexus.evaluation.models import EvalTraceCollector
70
+ from nexus.evaluation.runner import EvalRunner
71
+ from nexus.infrastructure.asyncio_compat import configure_asyncio_policy
72
+ from nexus.infrastructure.checkpoint import PostgresCheckpointProvider
73
+ from nexus.infrastructure.database import DatabaseBootstrap
74
+ from nexus.infrastructure.embedding import OpenAICompatibleEmbeddingGateway
75
+ from nexus.infrastructure.graph.day4_runtime import Day4LangGraphRuntime
76
+ from nexus.infrastructure.graph.langgraph_runtime import LangGraphRuntime
77
+ from nexus.infrastructure.mcp import SDKMCPManager
78
+ from nexus.infrastructure.model_gateway.openai_compatible import OpenAICompatibleModelGateway
79
+ from nexus.infrastructure.observability import (
80
+ ConsoleTracer,
81
+ LangSmithTracer,
82
+ StructuredLogger,
83
+ )
84
+ from nexus.infrastructure.observability.langsmith import LangSmithClient
85
+ from nexus.infrastructure.persistence import (
86
+ SqlAlchemyApprovalUnitOfWorkFactory,
87
+ SqlAlchemySessionUnitOfWorkFactory,
88
+ )
89
+ from nexus.infrastructure.repository_files import RepositoryFiles
90
+ from nexus.infrastructure.sandbox import LocalProcessSandbox
91
+ from nexus.infrastructure.semantic import PgVectorRepositoryIndexer, PgVectorSemanticSearchProvider
92
+ from nexus.security import (
93
+ AutoApprovalPolicy,
94
+ DefaultCommandPolicy,
95
+ TrustedExecutables,
96
+ WorkspaceGuard,
97
+ )
98
+ from nexus.skills import DefaultSkillRegistry, FileSkillLoader, ModelSkillSelector
99
+ from nexus.tools import MCPToolAdapter, ToolRegistry
100
+ from nexus.tools.native import build_native_tools
101
+
102
+ configure_asyncio_policy()
103
+
104
+
105
+ def build_evaluation_runner(config: RuntimeConfig, cases_root: Path) -> EvalRunner:
106
+ """Assemble Day 9 evaluation without exposing Harness authority to the Agent."""
107
+
108
+ evaluation_config = config.model_copy(update={"approval_mode": ApprovalMode.AUTO})
109
+
110
+ @asynccontextmanager
111
+ async def runtime_factory(
112
+ workspace: Path, collector: EvalTraceCollector
113
+ ) -> AsyncIterator[NexusRuntime]:
114
+ async with bootstrap_application(
115
+ evaluation_config,
116
+ workspace_path=workspace,
117
+ approval_policy=AutoApprovalPolicy(),
118
+ eval_trace_collector=collector,
119
+ ) as application:
120
+ yield application.runtime
121
+
122
+ return EvalRunner(cases_root, runtime_factory)
123
+
124
+
125
+ @dataclass(frozen=True, slots=True)
126
+ class BootstrappedApplication:
127
+ """Application dependencies exposed to entry-point adapters."""
128
+
129
+ runtime: NexusRuntime
130
+ session_service: SessionService
131
+ database: DatabaseBootstrap
132
+ tool_runtime: ToolRuntime
133
+
134
+
135
+ @dataclass(frozen=True, slots=True)
136
+ class BootstrappedToolApplication:
137
+ """Day 3 Tool Runtime dependencies without model/graph construction."""
138
+
139
+ tool_runtime: ToolRuntime
140
+ database: DatabaseBootstrap
141
+
142
+
143
+ @dataclass(frozen=True, slots=True)
144
+ class _Day7ContextServices:
145
+ context_manager: BoundedContextManager
146
+ budget_guard: Day5ModelInputBudgetGuard
147
+ skill_registry: DefaultSkillRegistry
148
+ skill_selector: ModelSkillSelector
149
+
150
+
151
+ def _build_day7_context_services(
152
+ *,
153
+ config: RuntimeConfig,
154
+ gateway: ModelGateway,
155
+ workspace: Path,
156
+ provider: ContextProvider,
157
+ access: ToolRepositoryAccess,
158
+ chunker: LineWindowChunker,
159
+ ) -> _Day7ContextServices:
160
+ context_manager = BoundedContextManager(
161
+ provider,
162
+ access,
163
+ chunker,
164
+ ContextBudget(
165
+ max_retrieved_chunks=config.max_retrieved_chunks,
166
+ max_exploration_seed_chunks=config.max_exploration_seed_chunks,
167
+ max_code_context_tokens=config.max_code_context_tokens,
168
+ max_recent_observations=config.max_recent_observations,
169
+ ),
170
+ config.max_model_input_tokens,
171
+ )
172
+ repository_skill_root = workspace / ".nexus" / "skills"
173
+ user_skill_root = Path.home() / ".nexus" / "skills"
174
+ skill_loader = FileSkillLoader(repository_skill_root, user_skill_root)
175
+ skill_registry = DefaultSkillRegistry(
176
+ skill_loader,
177
+ repository_skill_root,
178
+ user_skill_root,
179
+ )
180
+ budget_guard = Day5ModelInputBudgetGuard(config.max_model_input_tokens)
181
+ skill_selector = ModelSkillSelector(
182
+ gateway,
183
+ config.max_selected_skills,
184
+ budget_guard,
185
+ )
186
+ return _Day7ContextServices(
187
+ context_manager,
188
+ budget_guard,
189
+ skill_registry,
190
+ skill_selector,
191
+ )
192
+
193
+
194
+ @asynccontextmanager
195
+ async def bootstrap_application(
196
+ config: RuntimeConfig,
197
+ *,
198
+ model_gateway: ModelGateway | None = None,
199
+ workspace_path: Path | None = None,
200
+ interrupt_before_model_response: bool = False,
201
+ approval_policy: ApprovalPolicy | None = None,
202
+ tool_event_emitter: RuntimeEventEmitter | None = None,
203
+ embedding_gateway: EmbeddingGateway | None = None,
204
+ eval_trace_collector: EvalTraceCollector | None = None,
205
+ ) -> AsyncIterator[BootstrappedApplication]:
206
+ """Build the approved object graph and reliably release infrastructure resources."""
207
+
208
+ database = DatabaseBootstrap(config.database_url)
209
+ checkpoint_provider: CheckpointProvider = PostgresCheckpointProvider(config.database_url)
210
+ mcp_manager: MCPManager | None = None
211
+ telemetry_subscriber: TelemetrySubscriber | None = None
212
+ telemetry_subscription: object | None = None
213
+ langsmith_tracer: LangSmithTracer | None = None
214
+ try:
215
+ await checkpoint_provider.setup()
216
+ raw_gateway = (
217
+ model_gateway if model_gateway is not None else OpenAICompatibleModelGateway(config)
218
+ )
219
+ unit_of_work_factory = SqlAlchemySessionUnitOfWorkFactory(database.session_factory)
220
+ workspace = workspace_path or Path.cwd()
221
+ session_service = SessionService(unit_of_work_factory, workspace)
222
+ event_publisher = InProcessEventPublisher()
223
+ event_buffer = LiveRuntimeEventBridge(event_publisher)
224
+ ledger = ToolExecutionLedger()
225
+ approval_factory = SqlAlchemyApprovalUnitOfWorkFactory(database.session_factory)
226
+ plan_approval_service = PlanApprovalService(approval_factory)
227
+ combined_emitter = _combined_emitter(event_buffer, tool_event_emitter)
228
+ tool_runtime, executables, mcp_manager, tool_metadata, tool_sources = (
229
+ await _build_tool_runtime(
230
+ config,
231
+ database,
232
+ workspace,
233
+ approval_policy or AutoApprovalPolicy(),
234
+ combined_emitter,
235
+ plan_approval_service=plan_approval_service,
236
+ ledger=ledger,
237
+ allow_lexical_path=RepositoryFiles(
238
+ workspace,
239
+ config.max_file_size_bytes,
240
+ ).allowed,
241
+ )
242
+ )
243
+ sinks: list[TracerSink] = []
244
+ if config.console_tracing_enabled:
245
+ structured_logger = StructuredLogger()
246
+ sinks.append(
247
+ TracerSink(TraceSink.CONSOLE, ConsoleTracer(structured_logger))
248
+ )
249
+ if config.langsmith_tracing_enabled:
250
+ if config.langsmith_api_key is None:
251
+ raise ConfigurationError(
252
+ "Enabled LangSmith tracing requires a key.",
253
+ code="TRACE_CONFIGURATION_INVALID",
254
+ )
255
+ client = Client(
256
+ api_url=config.langsmith_endpoint,
257
+ api_key=config.langsmith_api_key.get_secret_value(),
258
+ workspace_id=config.langsmith_workspace_id,
259
+ hide_inputs=True,
260
+ hide_outputs=_safe_langsmith_mapping,
261
+ hide_metadata=_safe_langsmith_mapping,
262
+ auto_batch_tracing=True,
263
+ )
264
+ langsmith_tracer = LangSmithTracer(
265
+ cast(LangSmithClient, client), project=config.langsmith_project
266
+ )
267
+ sinks.append(TracerSink(TraceSink.LANGSMITH, langsmith_tracer))
268
+
269
+ async def emit_trace_warning(
270
+ code: str,
271
+ sink: TraceSink,
272
+ operation: TraceOperation,
273
+ fallback: TraceFallback,
274
+ ) -> None:
275
+ context = current_execution_context()
276
+ warning = ObservabilityWarning(
277
+ run_id=context.run_id,
278
+ session_id=context.session_id,
279
+ code=code,
280
+ sink=sink,
281
+ operation=operation,
282
+ fallback=fallback,
283
+ )
284
+ try:
285
+ await event_publisher.publish(warning)
286
+ except RuntimeError:
287
+ print(
288
+ json.dumps(
289
+ {
290
+ "type": "ObservabilityWarning",
291
+ "code": code,
292
+ "sink": sink.value,
293
+ "operation": operation.value,
294
+ "fallback": fallback.value,
295
+ },
296
+ separators=(",", ":"),
297
+ ),
298
+ file=sys.stderr,
299
+ )
300
+
301
+ dispatcher = SafeTracerDispatcher(
302
+ tuple(sinks), warning_emitter=emit_trace_warning
303
+ )
304
+ telemetry_subscriber = TelemetrySubscriber(
305
+ EventEnricher(workspace, tool_sources),
306
+ dispatcher,
307
+ warning_emitter=emit_trace_warning,
308
+ ledger=ledger,
309
+ redactor=TelemetryRedactor(),
310
+ finish_observer=(
311
+ None if eval_trace_collector is None else eval_trace_collector.record_finish
312
+ ),
313
+ )
314
+ telemetry_subscription = event_publisher.subscribe(telemetry_subscriber)
315
+ gateway = ObservedModelGateway(
316
+ raw_gateway,
317
+ emit=event_publisher.publish,
318
+ ledger=ledger,
319
+ provider=config.model_provider,
320
+ model=config.model_name,
321
+ )
322
+ checkpointer = cast(BaseCheckpointSaver[Any], checkpoint_provider.get_checkpointer())
323
+ legacy_runtime = LangGraphRuntime(
324
+ gateway,
325
+ checkpointer=checkpointer,
326
+ interrupt_before_model_response=interrupt_before_model_response,
327
+ )
328
+ graph_runtime: GraphRuntime
329
+ if interrupt_before_model_response:
330
+ graph_runtime = legacy_runtime
331
+ else:
332
+ access = ToolRepositoryAccess(tool_runtime)
333
+ chunker = LineWindowChunker()
334
+ semantic = None
335
+ if config.semantic_enabled:
336
+ embedding = embedding_gateway or OpenAICompatibleEmbeddingGateway(config)
337
+ semantic = PgVectorSemanticSearchProvider(database.session_factory, embedding)
338
+ context_services = _build_day7_context_services(
339
+ config=config,
340
+ gateway=gateway,
341
+ workspace=workspace,
342
+ provider=HybridContextProvider(
343
+ ToolLexicalSearchProvider(access, chunker), semantic
344
+ ),
345
+ access=access,
346
+ chunker=chunker,
347
+ )
348
+ context_manager = context_services.context_manager
349
+ graph_runtime = Day4LangGraphRuntime(
350
+ explorer=SelectiveRepositoryExplorer(tool_runtime),
351
+ context_builder=ManagedContextBuilder(
352
+ context_manager,
353
+ session_service.context_repository_id,
354
+ str(workspace.resolve()),
355
+ session_service.context_turns,
356
+ context_services.skill_registry,
357
+ context_services.skill_selector,
358
+ ),
359
+ planner=ModelPlanner(
360
+ gateway,
361
+ normalize_argv=executables.normalize_argv,
362
+ prepare_input=context_manager.fit_model_input,
363
+ tool_metadata=tool_metadata,
364
+ ),
365
+ agent=JsonAgentDecisionAdapter(
366
+ gateway,
367
+ prepare_input=context_manager.fit_model_input,
368
+ tool_metadata=tool_metadata,
369
+ ),
370
+ tool_runtime=tool_runtime,
371
+ validation_planner=DeterministicValidationPlanner(),
372
+ validation_runner=ToolValidationRunner(
373
+ tool_runtime,
374
+ emit=combined_emitter,
375
+ ),
376
+ plan_approval_service=plan_approval_service,
377
+ diff_collector=FinalDiffCollector(tool_runtime),
378
+ event_buffer=event_buffer,
379
+ ledger=ledger,
380
+ approval_mode=config.approval_mode,
381
+ max_steps=config.max_steps,
382
+ max_repair_attempts=config.max_repair_attempts,
383
+ max_replans=config.max_replans,
384
+ checkpointer=checkpointer,
385
+ legacy_runtime=legacy_runtime,
386
+ context_manager=context_manager,
387
+ conversation_turns=session_service.context_turns,
388
+ model_call_id=gateway.last_model_call_id,
389
+ )
390
+ application = BootstrappedApplication(
391
+ runtime=NexusRuntime(
392
+ graph_runtime,
393
+ session_service=session_service,
394
+ model_metadata={
395
+ "provider": config.model_provider,
396
+ "model": config.model_name,
397
+ },
398
+ event_buffer=event_buffer,
399
+ event_publisher=event_publisher,
400
+ telemetry_subscriber=telemetry_subscriber,
401
+ ledger=ledger,
402
+ ),
403
+ session_service=session_service,
404
+ database=database,
405
+ tool_runtime=tool_runtime,
406
+ )
407
+ yield application
408
+ finally:
409
+ if telemetry_subscription is not None:
410
+ await cast(Any, telemetry_subscription).aclose()
411
+ if telemetry_subscriber is not None:
412
+ await telemetry_subscriber.close()
413
+ if langsmith_tracer is not None:
414
+ try:
415
+ await langsmith_tracer.close()
416
+ except Exception:
417
+ pass
418
+ if mcp_manager is not None:
419
+ await mcp_manager.close()
420
+ await checkpoint_provider.close()
421
+ await database.close()
422
+
423
+
424
+ @asynccontextmanager
425
+ async def bootstrap_tool_application(
426
+ config: RuntimeConfig,
427
+ *,
428
+ workspace_path: Path | None = None,
429
+ approval_policy: ApprovalPolicy | None = None,
430
+ tool_event_emitter: RuntimeEventEmitter | None = None,
431
+ day5_file_filtering: bool = False,
432
+ ) -> AsyncIterator[BootstrappedToolApplication]:
433
+ """Build only the approved Day 3 Tool Runtime dependency graph."""
434
+
435
+ database = DatabaseBootstrap(config.database_url)
436
+ mcp_manager: MCPManager | None = None
437
+ try:
438
+ tool_runtime, _, mcp_manager, _, _ = await _build_tool_runtime(
439
+ config,
440
+ database,
441
+ workspace := workspace_path or Path.cwd(),
442
+ approval_policy or AutoApprovalPolicy(),
443
+ tool_event_emitter,
444
+ allow_lexical_path=(
445
+ RepositoryFiles(workspace, config.max_file_size_bytes).allowed
446
+ if day5_file_filtering
447
+ else None
448
+ ),
449
+ )
450
+ yield BootstrappedToolApplication(tool_runtime=tool_runtime, database=database)
451
+ finally:
452
+ if mcp_manager is not None:
453
+ await mcp_manager.close()
454
+ await database.close()
455
+
456
+
457
+ async def _build_tool_runtime(
458
+ config: RuntimeConfig,
459
+ database: DatabaseBootstrap,
460
+ workspace: Path,
461
+ approval_policy: ApprovalPolicy,
462
+ event_emitter: RuntimeEventEmitter | None,
463
+ *,
464
+ plan_approval_service: PlanApprovalService | None = None,
465
+ ledger: ToolExecutionLedger | None = None,
466
+ allow_lexical_path: Callable[[str], bool] | None = None,
467
+ ) -> tuple[
468
+ ToolRuntime,
469
+ TrustedExecutables,
470
+ MCPManager | None,
471
+ tuple[JsonObject, ...],
472
+ dict[str, str],
473
+ ]:
474
+ guard = WorkspaceGuard(workspace)
475
+ executables = TrustedExecutables.resolve(guard.root)
476
+ manager: MCPManager | None = None
477
+ adapters: list[MCPToolAdapter] = []
478
+ mcp_risks: dict[str, RiskLevel] = {}
479
+ unsupported_writes: set[str] = set()
480
+ tool_metadata: list[JsonObject] = []
481
+ try:
482
+ if config.mcp_enabled:
483
+ manager = SDKMCPManager(config.mcp_servers)
484
+ await manager.connect()
485
+ descriptors = await manager.list_tools()
486
+ adapters, mcp_risks, unsupported_writes, tool_metadata = _adapt_mcp_tools(
487
+ descriptors,
488
+ config.mcp_servers,
489
+ manager,
490
+ )
491
+ command_policy = DefaultCommandPolicy(executables, mcp_risks)
492
+ sandbox = LocalProcessSandbox(guard, command_policy, executables)
493
+ tools = [
494
+ *build_native_tools(
495
+ guard,
496
+ sandbox,
497
+ executables,
498
+ allow_lexical_path=allow_lexical_path,
499
+ ),
500
+ *adapters,
501
+ ]
502
+ try:
503
+ registry = ToolRegistry(tools)
504
+ except ValueError as exc:
505
+ if not adapters:
506
+ raise
507
+ raise ConfigurationError(
508
+ "MCP/native Tool registration contains a duplicate name.",
509
+ code="MCP_TOOL_COLLISION",
510
+ ) from exc
511
+ except Exception:
512
+ if manager is not None:
513
+ await manager.close()
514
+ raise
515
+ approval_factory = SqlAlchemyApprovalUnitOfWorkFactory(database.session_factory)
516
+ return (
517
+ ToolRuntime(
518
+ registry,
519
+ command_policy,
520
+ approval_policy,
521
+ ApprovalService(approval_factory),
522
+ emit=event_emitter,
523
+ plan_approval_service=plan_approval_service,
524
+ normalize_argv=executables.normalize_argv,
525
+ ledger=ledger,
526
+ unsupported_write_operations=frozenset(unsupported_writes),
527
+ ),
528
+ executables,
529
+ manager,
530
+ tuple(tool_metadata),
531
+ {
532
+ tool.name: "MCP" if isinstance(tool, MCPToolAdapter) else "NATIVE"
533
+ for tool in tools
534
+ },
535
+ )
536
+
537
+
538
+ def _mcp_risk(server: MCPServerConfig, remote_name: str) -> RiskLevel:
539
+ return next(
540
+ (
541
+ configured.risk_level
542
+ for configured in server.tool_risks
543
+ if configured.tool_name == remote_name
544
+ ),
545
+ RiskLevel.DANGEROUS,
546
+ )
547
+
548
+
549
+ def _adapt_mcp_tools(
550
+ descriptors: tuple[MCPToolDescriptor, ...],
551
+ server_configs: tuple[MCPServerConfig, ...],
552
+ manager: MCPManager,
553
+ ) -> tuple[
554
+ list[MCPToolAdapter],
555
+ dict[str, RiskLevel],
556
+ set[str],
557
+ list[JsonObject],
558
+ ]:
559
+ servers = {server.server_id: server for server in server_configs if server.enabled}
560
+ adapters: list[MCPToolAdapter] = []
561
+ risks: dict[str, RiskLevel] = {}
562
+ unsupported_writes: set[str] = set()
563
+ metadata: list[JsonObject] = []
564
+ for descriptor in descriptors:
565
+ server = servers.get(descriptor.server_id)
566
+ if server is None:
567
+ raise ConfigurationError(
568
+ "MCP discovery returned an unknown server identity.",
569
+ code="MCP_CONFIGURATION_INVALID",
570
+ )
571
+ risk = _mcp_risk(server, descriptor.remote_name)
572
+ adapter = MCPToolAdapter(
573
+ descriptor,
574
+ manager,
575
+ risk_level=risk,
576
+ timeout_seconds=server.tool_timeout_seconds,
577
+ )
578
+ adapters.append(adapter)
579
+ risks[adapter.name] = risk
580
+ if risk is RiskLevel.WRITE:
581
+ unsupported_writes.add(adapter.name)
582
+ if risk is RiskLevel.SAFE:
583
+ metadata.append(
584
+ {
585
+ "registry_name": descriptor.registry_name,
586
+ "description": descriptor.description,
587
+ "input_schema": descriptor.input_schema,
588
+ "risk_level": risk.value,
589
+ "source": "mcp",
590
+ "server_id": descriptor.server_id,
591
+ }
592
+ )
593
+ return adapters, risks, unsupported_writes, metadata
594
+
595
+
596
+ async def index_repository(
597
+ config: RuntimeConfig,
598
+ *,
599
+ rebuild: bool = False,
600
+ workspace_path: Path | None = None,
601
+ embedding_gateway: EmbeddingGateway | None = None,
602
+ ) -> IndexResult:
603
+ """Standalone index entry: no chat model, checkpointer, or synthetic coding Run."""
604
+ workspace = workspace_path or Path.cwd()
605
+ embedding = embedding_gateway or OpenAICompatibleEmbeddingGateway(config)
606
+ database = DatabaseBootstrap(config.database_url)
607
+ try:
608
+ service = SessionService(
609
+ SqlAlchemySessionUnitOfWorkFactory(database.session_factory),
610
+ workspace,
611
+ )
612
+ indexer = PgVectorRepositoryIndexer(
613
+ database.session_factory,
614
+ embedding,
615
+ LineWindowChunker(),
616
+ RepositoryFiles(workspace, config.max_file_size_bytes),
617
+ )
618
+ return await indexer.index(
619
+ IndexRequest(
620
+ await service.context_repository_id(),
621
+ str(workspace.resolve()),
622
+ rebuild,
623
+ )
624
+ )
625
+ except ContextError:
626
+ raise
627
+ except Exception as exc:
628
+ raise ContextError(
629
+ "Repository index initialization failed.", code="INDEX_BUILD_FAILED", retryable=True
630
+ ) from exc
631
+ finally:
632
+ await database.close()
633
+
634
+
635
+ async def list_repository_sessions(config: RuntimeConfig) -> list[SessionSummary]:
636
+ """Business history listing does not request a model or semantic capability."""
637
+ database = DatabaseBootstrap(config.database_url)
638
+ try:
639
+ service = SessionService(
640
+ SqlAlchemySessionUnitOfWorkFactory(database.session_factory),
641
+ Path.cwd(),
642
+ )
643
+ return await service.list_sessions()
644
+ finally:
645
+ await database.close()
646
+
647
+
648
+ def _combined_emitter(
649
+ buffer: RuntimeEventBuffer | LiveRuntimeEventBridge,
650
+ external: RuntimeEventEmitter | None,
651
+ ) -> RuntimeEventEmitter:
652
+ async def emit(event: RuntimeEvent) -> None:
653
+ await buffer.emit(event)
654
+ if external is not None:
655
+ await external(event)
656
+
657
+ return emit
658
+
659
+
660
+ def _safe_langsmith_mapping(value: dict[str, Any]) -> dict[str, Any]:
661
+ """Defense in depth: the adapter has already constructed the only safe mapping."""
662
+
663
+ return dict(value)
@@ -0,0 +1,5 @@
1
+ """Official LangGraph checkpoint provider adapter."""
2
+
3
+ from nexus.infrastructure.checkpoint.postgres import PostgresCheckpointProvider
4
+
5
+ __all__ = ["PostgresCheckpointProvider"]