monoid-agent-kernel 0.16.1__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.
- monoid_agent_kernel/__init__.py +11 -0
- monoid_agent_kernel/_policy_util.py +40 -0
- monoid_agent_kernel/_proc.py +86 -0
- monoid_agent_kernel/builder.py +351 -0
- monoid_agent_kernel/cli.py +1244 -0
- monoid_agent_kernel/conformance/__init__.py +32 -0
- monoid_agent_kernel/conformance/harness.py +209 -0
- monoid_agent_kernel/conformance/profiles/__init__.py +55 -0
- monoid_agent_kernel/conformance/profiles/_metadata.py +16 -0
- monoid_agent_kernel/conformance/profiles/capability_security.py +216 -0
- monoid_agent_kernel/conformance/profiles/control_plane.py +41 -0
- monoid_agent_kernel/conformance/profiles/durable_runner.py +42 -0
- monoid_agent_kernel/conformance/profiles/message_fabric.py +42 -0
- monoid_agent_kernel/conformance/profiles/minimal_agent.py +13 -0
- monoid_agent_kernel/conformance/profiles/multi_agent.py +112 -0
- monoid_agent_kernel/conformance/profiles/provider_gateway.py +107 -0
- monoid_agent_kernel/conformance/profiles/reference_full.py +135 -0
- monoid_agent_kernel/conformance/profiles/side_effect_tool_agent.py +36 -0
- monoid_agent_kernel/conformance/profiles/tool_agent.py +39 -0
- monoid_agent_kernel/contracts.py +329 -0
- monoid_agent_kernel/core/__init__.py +1 -0
- monoid_agent_kernel/core/_util.py +145 -0
- monoid_agent_kernel/core/agents.py +883 -0
- monoid_agent_kernel/core/cancellation.py +16 -0
- monoid_agent_kernel/core/capability.py +376 -0
- monoid_agent_kernel/core/capability_revocation.py +92 -0
- monoid_agent_kernel/core/checkpoint.py +350 -0
- monoid_agent_kernel/core/content.py +101 -0
- monoid_agent_kernel/core/context.py +67 -0
- monoid_agent_kernel/core/control.py +124 -0
- monoid_agent_kernel/core/control_audit.py +99 -0
- monoid_agent_kernel/core/durable_metadata.py +116 -0
- monoid_agent_kernel/core/event_sequencing.py +164 -0
- monoid_agent_kernel/core/events.py +204 -0
- monoid_agent_kernel/core/external_agent_envelope.py +338 -0
- monoid_agent_kernel/core/frontmatter.py +140 -0
- monoid_agent_kernel/core/inbox.py +110 -0
- monoid_agent_kernel/core/lease_admission.py +57 -0
- monoid_agent_kernel/core/lifecycle.py +440 -0
- monoid_agent_kernel/core/manifest.py +88 -0
- monoid_agent_kernel/core/media.py +393 -0
- monoid_agent_kernel/core/outbox.py +212 -0
- monoid_agent_kernel/core/output_validator.py +103 -0
- monoid_agent_kernel/core/packages.py +742 -0
- monoid_agent_kernel/core/projections.py +213 -0
- monoid_agent_kernel/core/prompt.py +37 -0
- monoid_agent_kernel/core/proposal_file.py +84 -0
- monoid_agent_kernel/core/result.py +131 -0
- monoid_agent_kernel/core/schemas.py +1146 -0
- monoid_agent_kernel/core/scope.py +185 -0
- monoid_agent_kernel/core/side_effect_policy.py +153 -0
- monoid_agent_kernel/core/spec.py +325 -0
- monoid_agent_kernel/core/streaming.py +208 -0
- monoid_agent_kernel/core/subagent_runtime.py +187 -0
- monoid_agent_kernel/core/tool_approval.py +175 -0
- monoid_agent_kernel/core/tool_surface.py +505 -0
- monoid_agent_kernel/core/trace_context.py +76 -0
- monoid_agent_kernel/core/wire_validation.py +156 -0
- monoid_agent_kernel/core/workspace.py +161 -0
- monoid_agent_kernel/core/workspace_index.py +127 -0
- monoid_agent_kernel/env.py +32 -0
- monoid_agent_kernel/errors.py +105 -0
- monoid_agent_kernel/event_loader.py +47 -0
- monoid_agent_kernel/identifiers.py +50 -0
- monoid_agent_kernel/loop.py +4140 -0
- monoid_agent_kernel/loop_phases.py +561 -0
- monoid_agent_kernel/mcp/__init__.py +10 -0
- monoid_agent_kernel/mcp/client.py +227 -0
- monoid_agent_kernel/mcp/provider.py +407 -0
- monoid_agent_kernel/narration.py +92 -0
- monoid_agent_kernel/observability/__init__.py +9 -0
- monoid_agent_kernel/observability/otel.py +264 -0
- monoid_agent_kernel/permissions.py +74 -0
- monoid_agent_kernel/providers/__init__.py +7 -0
- monoid_agent_kernel/providers/_common.py +122 -0
- monoid_agent_kernel/providers/base.py +312 -0
- monoid_agent_kernel/providers/fake.py +76 -0
- monoid_agent_kernel/providers/gateway.py +474 -0
- monoid_agent_kernel/providers/openai.py +487 -0
- monoid_agent_kernel/public_view.py +173 -0
- monoid_agent_kernel/py.typed +0 -0
- monoid_agent_kernel/recorder.py +421 -0
- monoid_agent_kernel/reference/__init__.py +15 -0
- monoid_agent_kernel/reference/_shared/__init__.py +4 -0
- monoid_agent_kernel/reference/_shared/http_util.py +111 -0
- monoid_agent_kernel/reference/_shared/tokens.py +314 -0
- monoid_agent_kernel/reference/backend/__init__.py +21 -0
- monoid_agent_kernel/reference/backend/commands.py +375 -0
- monoid_agent_kernel/reference/backend/http.py +439 -0
- monoid_agent_kernel/reference/backend/jobs.py +68 -0
- monoid_agent_kernel/reference/backend/loop_factory.py +253 -0
- monoid_agent_kernel/reference/backend/outbox_dispatch.py +130 -0
- monoid_agent_kernel/reference/backend/ports.py +188 -0
- monoid_agent_kernel/reference/backend/projection.py +286 -0
- monoid_agent_kernel/reference/backend/proposal.py +220 -0
- monoid_agent_kernel/reference/backend/proposal_reader.py +16 -0
- monoid_agent_kernel/reference/backend/recovery.py +253 -0
- monoid_agent_kernel/reference/backend/run_execution.py +152 -0
- monoid_agent_kernel/reference/backend/run_preparation.py +197 -0
- monoid_agent_kernel/reference/backend/run_state.py +243 -0
- monoid_agent_kernel/reference/backend/run_types.py +128 -0
- monoid_agent_kernel/reference/backend/runtime_config.py +147 -0
- monoid_agent_kernel/reference/backend/service.py +1664 -0
- monoid_agent_kernel/reference/backend/session.py +280 -0
- monoid_agent_kernel/reference/backend/session_drive.py +231 -0
- monoid_agent_kernel/reference/capability.py +101 -0
- monoid_agent_kernel/reference/conformance.py +1777 -0
- monoid_agent_kernel/reference/llm_gateway/__init__.py +14 -0
- monoid_agent_kernel/reference/llm_gateway/http.py +225 -0
- monoid_agent_kernel/reference/llm_gateway/providers.py +96 -0
- monoid_agent_kernel/reference/llm_gateway/service.py +404 -0
- monoid_agent_kernel/reference/mcp_gateway/__init__.py +26 -0
- monoid_agent_kernel/reference/mcp_gateway/http.py +187 -0
- monoid_agent_kernel/reference/mcp_gateway/service.py +206 -0
- monoid_agent_kernel/reference/outbox.py +135 -0
- monoid_agent_kernel/reference/stores/__init__.py +20 -0
- monoid_agent_kernel/reference/stores/lease.py +116 -0
- monoid_agent_kernel/reference/stores/sqlite.py +295 -0
- monoid_agent_kernel/reference/studio/README.md +97 -0
- monoid_agent_kernel/reference/studio/__init__.py +21 -0
- monoid_agent_kernel/reference/studio/activity.py +53 -0
- monoid_agent_kernel/reference/studio/cli.py +481 -0
- monoid_agent_kernel/reference/studio/sample-skills/code-review-checklist/SKILL.md +25 -0
- monoid_agent_kernel/reference/studio/sample-skills/code-review-checklist/references/review-checklist.md +8 -0
- monoid_agent_kernel/reference/studio/sample-skills/commit-message/SKILL.md +32 -0
- monoid_agent_kernel/reference/studio/sample-skills/incident-summary/SKILL.md +24 -0
- monoid_agent_kernel/reference/studio/sample-skills/incident-summary/references/incident-template.md +32 -0
- monoid_agent_kernel/reference/studio/sample-skills/release-notes/SKILL.md +24 -0
- monoid_agent_kernel/reference/studio/sample-skills/release-notes/references/release-note-template.md +33 -0
- monoid_agent_kernel/reference/studio/sample-skills/release-notes/scripts/collect_changes.py +51 -0
- monoid_agent_kernel/reference/studio/server.py +1922 -0
- monoid_agent_kernel/reference/studio/web/index.html +1877 -0
- monoid_agent_kernel/reference/studio/web/settings.html +108 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/auto-render.min.js +1 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/katex.min.css +1 -0
- monoid_agent_kernel/reference/studio/web/vendor/katex/katex.min.js +1 -0
- monoid_agent_kernel/reference/studio/window.py +72 -0
- monoid_agent_kernel/reference/web_gateway/__init__.py +6 -0
- monoid_agent_kernel/reference/web_gateway/http.py +155 -0
- monoid_agent_kernel/reference/web_gateway/providers.py +807 -0
- monoid_agent_kernel/reference/web_gateway/service.py +559 -0
- monoid_agent_kernel/shell.py +722 -0
- monoid_agent_kernel/skills/__init__.py +20 -0
- monoid_agent_kernel/skills/definition.py +82 -0
- monoid_agent_kernel/skills/loader.py +55 -0
- monoid_agent_kernel/skills/provider.py +378 -0
- monoid_agent_kernel/subagent_loader.py +56 -0
- monoid_agent_kernel/tasks.py +1326 -0
- monoid_agent_kernel/tool_loader.py +51 -0
- monoid_agent_kernel/tool_services/__init__.py +8 -0
- monoid_agent_kernel/tool_services/base.py +25 -0
- monoid_agent_kernel/tool_services/jobs.py +47 -0
- monoid_agent_kernel/tool_services/shell.py +255 -0
- monoid_agent_kernel/tool_services/web.py +356 -0
- monoid_agent_kernel/tools/__init__.py +2 -0
- monoid_agent_kernel/tools/base.py +205 -0
- monoid_agent_kernel/tools/builtin.py +958 -0
- monoid_agent_kernel/tools/decorator.py +132 -0
- monoid_agent_kernel/tools/tool_ids.py +62 -0
- monoid_agent_kernel/web.py +171 -0
- monoid_agent_kernel/workspace/__init__.py +2 -0
- monoid_agent_kernel/workspace/local.py +1004 -0
- monoid_agent_kernel/workspace/paths.py +30 -0
- monoid_agent_kernel-0.16.1.dist-info/METADATA +709 -0
- monoid_agent_kernel-0.16.1.dist-info/RECORD +191 -0
- monoid_agent_kernel-0.16.1.dist-info/WHEEL +4 -0
- monoid_agent_kernel-0.16.1.dist-info/entry_points.txt +3 -0
- monoid_agent_kernel-0.16.1.dist-info/licenses/LICENSE +202 -0
- monoid_agent_kernel-0.16.1.dist-info/licenses/NOTICE +8 -0
- native_agent_runner/__init__.py +103 -0
- native_agent_runner/py.typed +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from collections.abc import Callable, Mapping
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from monoid_agent_kernel.core.agents import AgentRuntimeConfig, RuntimeConfigProvider
|
|
10
|
+
from monoid_agent_kernel.core.capability import CapabilityBroker
|
|
11
|
+
from monoid_agent_kernel.core.checkpoint import CheckpointStore, RunCheckpoint
|
|
12
|
+
from monoid_agent_kernel.core.context import ContextProvider
|
|
13
|
+
from monoid_agent_kernel.core.events import AgentEvent, EventSink
|
|
14
|
+
from monoid_agent_kernel.core.outbox import OutboxSender
|
|
15
|
+
from monoid_agent_kernel.core.output_validator import OutputValidator
|
|
16
|
+
from monoid_agent_kernel.core.spec import AgentRunSpec, ModelConfig, RunLimits
|
|
17
|
+
from monoid_agent_kernel.loop import AgentLoop
|
|
18
|
+
from monoid_agent_kernel.providers.base import ModelAdapter
|
|
19
|
+
from monoid_agent_kernel.providers.gateway import GatewayModelAdapter
|
|
20
|
+
from monoid_agent_kernel.reference._shared.tokens import TokenKind, TokenManager
|
|
21
|
+
from monoid_agent_kernel.reference.backend.ports import MutableRunRecordPort, RunRequestPort
|
|
22
|
+
from monoid_agent_kernel.reference.backend.run_state import BackendRunStateSink
|
|
23
|
+
from monoid_agent_kernel.tools.base import ToolProvider
|
|
24
|
+
from monoid_agent_kernel.web import WebGatewayClient
|
|
25
|
+
|
|
26
|
+
ModelAdapterFactory = Callable[[AgentRunSpec, str], ModelAdapter]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class BackendLoopBuild:
|
|
31
|
+
spec: AgentRunSpec
|
|
32
|
+
model_adapter: ModelAdapter
|
|
33
|
+
web_gateway_client: WebGatewayClient | None
|
|
34
|
+
runtime_config_provider: RuntimeConfigProvider
|
|
35
|
+
capability_broker: CapabilityBroker | None
|
|
36
|
+
outbox_sender: OutboxSender | None
|
|
37
|
+
loop: AgentLoop
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class BackendLoopFactoryContext:
|
|
42
|
+
run_root_provider: Callable[[], Path]
|
|
43
|
+
llm_gateway_url_provider: Callable[[], str]
|
|
44
|
+
web_gateway_url_provider: Callable[[], str | None]
|
|
45
|
+
model_adapter_factory_provider: Callable[[], ModelAdapterFactory | None]
|
|
46
|
+
token_manager_provider: Callable[[], TokenManager]
|
|
47
|
+
llm_gateway_token_ttl_s_provider: Callable[[], int]
|
|
48
|
+
checkpoint_store_provider: Callable[[], CheckpointStore | None]
|
|
49
|
+
emit_output_deltas_provider: Callable[[], bool]
|
|
50
|
+
extra_event_sink_factories_provider: Callable[[], tuple[Callable[[], EventSink], ...]]
|
|
51
|
+
subagent_definitions_provider: Callable[[], Mapping[str, Any] | None]
|
|
52
|
+
tool_providers_provider: Callable[[], tuple[ToolProvider, ...]]
|
|
53
|
+
context_providers_provider: Callable[[], tuple[ContextProvider, ...]]
|
|
54
|
+
output_validators_provider: Callable[[], tuple[OutputValidator, ...]]
|
|
55
|
+
capability_broker_factory_provider: Callable[[], Callable[[RunRequestPort], CapabilityBroker | None] | None]
|
|
56
|
+
outbox_sender_factory_provider: Callable[[], Callable[[RunRequestPort], OutboxSender | None] | None]
|
|
57
|
+
current_runtime_config: Callable[[str], AgentRuntimeConfig | None]
|
|
58
|
+
record: Callable[[str], MutableRunRecordPort]
|
|
59
|
+
record_event: Callable[[str, AgentEvent], None]
|
|
60
|
+
persist_checkpoint_payload: Callable[[MutableRunRecordPort, RunCheckpoint, Mapping[str, bytes]], None]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class BackendRuntimeConfigProvider(RuntimeConfigProvider):
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
current_runtime_config: Callable[[str], AgentRuntimeConfig | None],
|
|
67
|
+
run_id: str,
|
|
68
|
+
) -> None:
|
|
69
|
+
self._current_runtime_config = current_runtime_config
|
|
70
|
+
self._run_id = run_id
|
|
71
|
+
|
|
72
|
+
def current_config(self, run_id: str) -> AgentRuntimeConfig | None:
|
|
73
|
+
del run_id
|
|
74
|
+
return self._current_runtime_config(self._run_id)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class _GatewayTokenSource:
|
|
79
|
+
"""Callable gateway token source that re-mints shortly before expiry for long runs."""
|
|
80
|
+
|
|
81
|
+
token_manager: TokenManager
|
|
82
|
+
kind: TokenKind
|
|
83
|
+
audience: str
|
|
84
|
+
run_id: str
|
|
85
|
+
tenant_id: str
|
|
86
|
+
user_id: str
|
|
87
|
+
ttl_s: int
|
|
88
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
89
|
+
refresh_skew_s: int = 300
|
|
90
|
+
_token: str = ""
|
|
91
|
+
_expires_at: float = 0.0
|
|
92
|
+
|
|
93
|
+
def __call__(self) -> str:
|
|
94
|
+
now = time.time()
|
|
95
|
+
skew = min(self.refresh_skew_s, self.ttl_s // 2)
|
|
96
|
+
if not self._token or now >= self._expires_at - skew:
|
|
97
|
+
self._token = self.token_manager.issue(
|
|
98
|
+
kind=self.kind,
|
|
99
|
+
audience=self.audience,
|
|
100
|
+
run_id=self.run_id,
|
|
101
|
+
tenant_id=self.tenant_id,
|
|
102
|
+
user_id=self.user_id,
|
|
103
|
+
ttl_s=self.ttl_s,
|
|
104
|
+
metadata=dict(self.metadata),
|
|
105
|
+
)
|
|
106
|
+
self._expires_at = now + self.ttl_s
|
|
107
|
+
return self._token
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class BackendLoopFactory:
|
|
111
|
+
"""Builds AgentLoop instances for the Reference backend facade."""
|
|
112
|
+
|
|
113
|
+
def __init__(self, context: BackendLoopFactoryContext) -> None:
|
|
114
|
+
self._context = context
|
|
115
|
+
|
|
116
|
+
def build(
|
|
117
|
+
self,
|
|
118
|
+
run_id: str,
|
|
119
|
+
request: RunRequestPort,
|
|
120
|
+
workspace_root: Path,
|
|
121
|
+
llm_gateway_token: str,
|
|
122
|
+
web_gateway_token: str,
|
|
123
|
+
*,
|
|
124
|
+
include_outbox_sender: bool = True,
|
|
125
|
+
) -> BackendLoopBuild:
|
|
126
|
+
spec = self.run_spec_for_request(run_id, request, workspace_root)
|
|
127
|
+
runtime_config = self._context.current_runtime_config(run_id)
|
|
128
|
+
model_adapter = self.build_model_adapter(
|
|
129
|
+
spec,
|
|
130
|
+
llm_gateway_token,
|
|
131
|
+
runtime_config.model if runtime_config is not None else None,
|
|
132
|
+
token_provider=self.llm_token_source(run_id, request, runtime_config),
|
|
133
|
+
)
|
|
134
|
+
web_gateway_client = self.web_gateway_client(web_gateway_token)
|
|
135
|
+
runtime_config_provider = BackendRuntimeConfigProvider(
|
|
136
|
+
self._context.current_runtime_config,
|
|
137
|
+
run_id,
|
|
138
|
+
)
|
|
139
|
+
capability_broker = self.capability_broker_for(request)
|
|
140
|
+
outbox_sender = self.outbox_sender_for(request) if include_outbox_sender else None
|
|
141
|
+
record = self._context.record(run_id)
|
|
142
|
+
loop = AgentLoop(
|
|
143
|
+
spec=spec,
|
|
144
|
+
model_adapter=model_adapter,
|
|
145
|
+
event_sinks=(
|
|
146
|
+
BackendRunStateSink(self._context.record_event, run_id),
|
|
147
|
+
*(make() for make in self._context.extra_event_sink_factories_provider()),
|
|
148
|
+
),
|
|
149
|
+
permission_policy=request.permission_policy,
|
|
150
|
+
cancellation_token=record.cancellation_token,
|
|
151
|
+
shell_approval_provider=None,
|
|
152
|
+
web_gateway_client=web_gateway_client,
|
|
153
|
+
runtime_config_provider=runtime_config_provider,
|
|
154
|
+
checkpoint_store=self._context.checkpoint_store_provider(),
|
|
155
|
+
emit_output_deltas=self._context.emit_output_deltas_provider(),
|
|
156
|
+
subagent_definitions=self._context.subagent_definitions_provider(),
|
|
157
|
+
tool_providers=self._context.tool_providers_provider(),
|
|
158
|
+
context_providers=self._context.context_providers_provider(),
|
|
159
|
+
output_validators=self._context.output_validators_provider(),
|
|
160
|
+
capability_broker=capability_broker,
|
|
161
|
+
checkpoint_persist_callback=lambda checkpoint, blobs: self._context.persist_checkpoint_payload(
|
|
162
|
+
self._context.record(run_id),
|
|
163
|
+
checkpoint,
|
|
164
|
+
blobs,
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
return BackendLoopBuild(
|
|
168
|
+
spec=spec,
|
|
169
|
+
model_adapter=model_adapter,
|
|
170
|
+
web_gateway_client=web_gateway_client,
|
|
171
|
+
runtime_config_provider=runtime_config_provider,
|
|
172
|
+
capability_broker=capability_broker,
|
|
173
|
+
outbox_sender=outbox_sender,
|
|
174
|
+
loop=loop,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def run_spec_for_request(
|
|
178
|
+
self,
|
|
179
|
+
run_id: str,
|
|
180
|
+
request: RunRequestPort,
|
|
181
|
+
workspace_root: Path,
|
|
182
|
+
) -> AgentRunSpec:
|
|
183
|
+
return AgentRunSpec(
|
|
184
|
+
workspace_root=workspace_root,
|
|
185
|
+
run_root=self._context.run_root_provider(),
|
|
186
|
+
run_id=run_id,
|
|
187
|
+
mode=request.mode,
|
|
188
|
+
workspace_backend=request.workspace_backend,
|
|
189
|
+
limits=RunLimits(
|
|
190
|
+
max_steps=request.max_steps,
|
|
191
|
+
max_tool_calls=request.max_tool_calls,
|
|
192
|
+
max_bytes_read=request.max_bytes_read,
|
|
193
|
+
max_duration_s=request.max_duration_s,
|
|
194
|
+
),
|
|
195
|
+
permission_policy=request.permission_policy,
|
|
196
|
+
metadata={
|
|
197
|
+
**request.metadata,
|
|
198
|
+
"tenant_id": request.tenant_id,
|
|
199
|
+
"user_id": request.user_id,
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def capability_broker_for(self, request: RunRequestPort) -> CapabilityBroker | None:
|
|
204
|
+
factory = self._context.capability_broker_factory_provider()
|
|
205
|
+
if factory is None:
|
|
206
|
+
return None
|
|
207
|
+
return factory(request)
|
|
208
|
+
|
|
209
|
+
def outbox_sender_for(self, request: RunRequestPort) -> OutboxSender | None:
|
|
210
|
+
factory = self._context.outbox_sender_factory_provider()
|
|
211
|
+
if factory is None:
|
|
212
|
+
return None
|
|
213
|
+
return factory(request)
|
|
214
|
+
|
|
215
|
+
def llm_token_source(
|
|
216
|
+
self,
|
|
217
|
+
run_id: str,
|
|
218
|
+
request: RunRequestPort,
|
|
219
|
+
runtime_config: AgentRuntimeConfig | None,
|
|
220
|
+
) -> _GatewayTokenSource:
|
|
221
|
+
return _GatewayTokenSource(
|
|
222
|
+
token_manager=self._context.token_manager_provider(),
|
|
223
|
+
kind="llm_gateway",
|
|
224
|
+
audience="csp.llm-gateway",
|
|
225
|
+
run_id=run_id,
|
|
226
|
+
tenant_id=request.tenant_id,
|
|
227
|
+
user_id=request.user_id,
|
|
228
|
+
ttl_s=self._context.llm_gateway_token_ttl_s_provider(),
|
|
229
|
+
metadata={"agent_config_hash": runtime_config.config_hash} if runtime_config is not None else {},
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def build_model_adapter(
|
|
233
|
+
self,
|
|
234
|
+
spec: AgentRunSpec,
|
|
235
|
+
llm_gateway_token: str,
|
|
236
|
+
model_config: ModelConfig | None,
|
|
237
|
+
token_provider: Callable[[], str | None] | None = None,
|
|
238
|
+
) -> ModelAdapter:
|
|
239
|
+
factory = self._context.model_adapter_factory_provider()
|
|
240
|
+
if factory is not None:
|
|
241
|
+
return factory(spec, llm_gateway_token)
|
|
242
|
+
return GatewayModelAdapter(
|
|
243
|
+
model_config or ModelConfig(),
|
|
244
|
+
gateway_url=self._context.llm_gateway_url_provider(),
|
|
245
|
+
token=llm_gateway_token,
|
|
246
|
+
token_provider=token_provider,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def web_gateway_client(self, token: str) -> WebGatewayClient | None:
|
|
250
|
+
gateway_url = self._context.web_gateway_url_provider()
|
|
251
|
+
if not token or not gateway_url:
|
|
252
|
+
return None
|
|
253
|
+
return WebGatewayClient(gateway_url, token=token)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Callable, Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Protocol
|
|
8
|
+
|
|
9
|
+
from monoid_agent_kernel.core.checkpoint import CheckpointStore, RunCheckpoint
|
|
10
|
+
from monoid_agent_kernel.core.inbox import InboxMessage
|
|
11
|
+
from monoid_agent_kernel.core.outbox import OutboxReceipt, OutboxRequest
|
|
12
|
+
from monoid_agent_kernel.core.trace_context import new_traceparent
|
|
13
|
+
from monoid_agent_kernel.reference.backend.ports import MutableRunRecordPort, queued_message_snapshot
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OutboxLoopPort(Protocol):
|
|
17
|
+
def due_outbox(self, now: float) -> list[OutboxRequest]: ...
|
|
18
|
+
|
|
19
|
+
def record_outbox_result(
|
|
20
|
+
self,
|
|
21
|
+
request_id: str,
|
|
22
|
+
receipt: OutboxReceipt,
|
|
23
|
+
*,
|
|
24
|
+
max_attempts: int = 5,
|
|
25
|
+
next_attempt_at: float = 0.0,
|
|
26
|
+
) -> str: ...
|
|
27
|
+
|
|
28
|
+
def snapshot(self) -> RunCheckpoint | None: ...
|
|
29
|
+
|
|
30
|
+
def collect_checkpoint_blobs(self) -> Mapping[str, bytes]: ...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class OutboxRetryPolicy:
|
|
35
|
+
max_attempts: int
|
|
36
|
+
base_s: float
|
|
37
|
+
factor: float
|
|
38
|
+
cap_s: float
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class OutboxDispatchContext:
|
|
43
|
+
retry_policy_provider: Callable[[], OutboxRetryPolicy]
|
|
44
|
+
max_message_queue_depth_provider: Callable[[], int]
|
|
45
|
+
checkpoint_store_provider: Callable[[], CheckpointStore]
|
|
46
|
+
rng_provider: Callable[[], random.Random]
|
|
47
|
+
live_outbox_runs: Callable[[], list[tuple[MutableRunRecordPort, OutboxLoopPort]]]
|
|
48
|
+
call_soon: Callable[..., None]
|
|
49
|
+
record_terminal: Callable[[MutableRunRecordPort], bool]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OutboxDispatchService:
|
|
53
|
+
"""Reference backend edge dispatcher for staged outbox side effects."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, context: OutboxDispatchContext) -> None:
|
|
56
|
+
self._context = context
|
|
57
|
+
|
|
58
|
+
def backoff_delay(self, attempts: int) -> float:
|
|
59
|
+
"""Capped exponential backoff with full jitter."""
|
|
60
|
+
policy = self._context.retry_policy_provider()
|
|
61
|
+
ceiling = min(policy.cap_s, policy.base_s * (policy.factor**attempts))
|
|
62
|
+
return self._context.rng_provider().uniform(0.0, max(0.0, ceiling))
|
|
63
|
+
|
|
64
|
+
def drain_outbox(self, record: MutableRunRecordPort, loop: OutboxLoopPort) -> None:
|
|
65
|
+
"""Dispatch due staged outbox requests and persist the resulting checkpoint state."""
|
|
66
|
+
sender = record.outbox_sender
|
|
67
|
+
now = time.time()
|
|
68
|
+
due = loop.due_outbox(now)
|
|
69
|
+
if sender is None or not due:
|
|
70
|
+
return
|
|
71
|
+
changed = False
|
|
72
|
+
policy = self._context.retry_policy_provider()
|
|
73
|
+
for request in due:
|
|
74
|
+
if not request.traceparent:
|
|
75
|
+
request.traceparent = new_traceparent()
|
|
76
|
+
try:
|
|
77
|
+
receipt = sender.send(request)
|
|
78
|
+
except Exception as exc: # a sender raising is a retryable transport failure
|
|
79
|
+
receipt = OutboxReceipt(ok=False, error=str(exc), retryable=True)
|
|
80
|
+
next_attempt_at = now + self.backoff_delay(request.attempts + 1)
|
|
81
|
+
status = loop.record_outbox_result(
|
|
82
|
+
request.id,
|
|
83
|
+
receipt,
|
|
84
|
+
max_attempts=policy.max_attempts,
|
|
85
|
+
next_attempt_at=next_attempt_at,
|
|
86
|
+
)
|
|
87
|
+
changed = True
|
|
88
|
+
if request.expect_ack and status in {"dispatched", "failed"}:
|
|
89
|
+
self.stage_outbox_ack(record, request, status, receipt)
|
|
90
|
+
if changed:
|
|
91
|
+
checkpoint = loop.snapshot()
|
|
92
|
+
if checkpoint is not None:
|
|
93
|
+
checkpoint.queued_messages = queued_message_snapshot(record.message_queue)
|
|
94
|
+
checkpoint.inbox_seen_ids = sorted(record.seen_inbox_ids)
|
|
95
|
+
self._context.checkpoint_store_provider().put(checkpoint, loop.collect_checkpoint_blobs())
|
|
96
|
+
|
|
97
|
+
def stage_outbox_ack(
|
|
98
|
+
self, record: MutableRunRecordPort, request: Any, status: str, receipt: OutboxReceipt
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Deliver an outbox send receipt back into the run inbox as a correlated message."""
|
|
101
|
+
ack_id = f"ack_{request.id}"
|
|
102
|
+
if self._context.record_terminal(record):
|
|
103
|
+
return
|
|
104
|
+
if (
|
|
105
|
+
ack_id in record.seen_inbox_ids
|
|
106
|
+
or record.message_queue.qsize() >= self._context.max_message_queue_depth_provider()
|
|
107
|
+
):
|
|
108
|
+
return
|
|
109
|
+
summary = f"[outbox-ack] request {request.id} to {request.destination!r}: {status}"
|
|
110
|
+
if receipt.reference:
|
|
111
|
+
summary += f" (ref={receipt.reference})"
|
|
112
|
+
if receipt.error:
|
|
113
|
+
summary += f" (error={receipt.error})"
|
|
114
|
+
envelope = InboxMessage(
|
|
115
|
+
content=summary,
|
|
116
|
+
id=ack_id,
|
|
117
|
+
source="outbox",
|
|
118
|
+
type="outbox_ack",
|
|
119
|
+
run_id=record.run_id,
|
|
120
|
+
correlation_id=request.correlation_id or request.id,
|
|
121
|
+
causation_id=request.id,
|
|
122
|
+
traceparent=request.traceparent,
|
|
123
|
+
tracestate=request.tracestate,
|
|
124
|
+
)
|
|
125
|
+
record.message_queue.put_nowait(envelope.to_json())
|
|
126
|
+
|
|
127
|
+
def redrive_outbox(self) -> None:
|
|
128
|
+
"""Schedule due outbox drains for live runs with senders."""
|
|
129
|
+
for record, loop in self._context.live_outbox_runs():
|
|
130
|
+
self._context.call_soon(self.drain_outbox, record, loop)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator, Awaitable, Mapping
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Protocol
|
|
6
|
+
|
|
7
|
+
from monoid_agent_kernel.core.agents import AgentRuntimeConfig
|
|
8
|
+
from monoid_agent_kernel.core.checkpoint import RunCheckpoint
|
|
9
|
+
from monoid_agent_kernel.core.lifecycle import SessionState
|
|
10
|
+
from monoid_agent_kernel.core.outbox import OutboxSender
|
|
11
|
+
from monoid_agent_kernel.core.result import AgentRunResult, Suspension
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TokenClaimsPort(Protocol):
|
|
15
|
+
tenant_id: str
|
|
16
|
+
user_id: str
|
|
17
|
+
run_id: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CancellationTokenPort(Protocol):
|
|
21
|
+
requested: bool
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MessageQueuePort(Protocol):
|
|
25
|
+
def qsize(self) -> int: ...
|
|
26
|
+
|
|
27
|
+
def put_nowait(self, item: Any) -> None: ...
|
|
28
|
+
|
|
29
|
+
def get(self) -> Awaitable[Any]: ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def queued_message_snapshot(queue: MessageQueuePort) -> list[str | list[Any] | dict[str, Any]]:
|
|
33
|
+
raw_queue = getattr(queue, "_queue", ())
|
|
34
|
+
return [message for message in list(raw_queue) if isinstance(message, (str, list, dict))]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LoopPort(Protocol):
|
|
38
|
+
def wait_for_pending_tasks(self, timeout_s: float) -> bool: ...
|
|
39
|
+
|
|
40
|
+
def has_pending_tasks(self) -> bool: ...
|
|
41
|
+
|
|
42
|
+
def emit_external_event(
|
|
43
|
+
self,
|
|
44
|
+
event_type: str,
|
|
45
|
+
*,
|
|
46
|
+
data: dict[str, Any] | None = None,
|
|
47
|
+
level: str = "info",
|
|
48
|
+
) -> bool: ...
|
|
49
|
+
|
|
50
|
+
async def arun_until_suspended(self, user_input: Any | None = None) -> Suspension: ...
|
|
51
|
+
|
|
52
|
+
def fail_recoverable(self, error: str, *, error_code: str) -> None: ...
|
|
53
|
+
|
|
54
|
+
def await_user_input(self) -> None: ...
|
|
55
|
+
|
|
56
|
+
async def aclose(self) -> AgentRunResult: ...
|
|
57
|
+
|
|
58
|
+
def snapshot(self) -> RunCheckpoint | None: ...
|
|
59
|
+
|
|
60
|
+
def collect_checkpoint_blobs(self) -> Mapping[str, bytes]: ...
|
|
61
|
+
|
|
62
|
+
def interrupt_turn(self) -> None: ...
|
|
63
|
+
|
|
64
|
+
def pause_turn(self) -> None: ...
|
|
65
|
+
|
|
66
|
+
def revoke_capability(
|
|
67
|
+
self,
|
|
68
|
+
*,
|
|
69
|
+
capability: str | None = None,
|
|
70
|
+
lease_id: str | None = None,
|
|
71
|
+
before: float | None = None,
|
|
72
|
+
reason: str = "",
|
|
73
|
+
) -> dict[str, Any]: ...
|
|
74
|
+
|
|
75
|
+
def report_task_result(
|
|
76
|
+
self,
|
|
77
|
+
task_id: str,
|
|
78
|
+
result: dict[str, Any],
|
|
79
|
+
*,
|
|
80
|
+
status: str,
|
|
81
|
+
persist_checkpoint: bool,
|
|
82
|
+
) -> dict[str, Any]: ...
|
|
83
|
+
|
|
84
|
+
def create_task(self, kind: str, request: dict[str, Any]) -> str: ...
|
|
85
|
+
|
|
86
|
+
def restore(self, checkpoint: RunCheckpoint, *, blobs: Mapping[str, bytes]) -> None: ...
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class RunStreamPort(Protocol):
|
|
90
|
+
suspension: Suspension | None
|
|
91
|
+
|
|
92
|
+
async def __aenter__(self) -> RunStreamPort: ...
|
|
93
|
+
|
|
94
|
+
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> bool | None: ...
|
|
95
|
+
|
|
96
|
+
def __aiter__(self) -> AsyncIterator[Any]: ...
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class RunExecutionLoopPort(LoopPort, Protocol):
|
|
100
|
+
async def aopen(self) -> None: ...
|
|
101
|
+
|
|
102
|
+
def astream(self, user_input: Any) -> RunStreamPort: ...
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class LoopBuildPort(Protocol):
|
|
106
|
+
loop: RunExecutionLoopPort
|
|
107
|
+
outbox_sender: OutboxSender | None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class RunRecordPort(Protocol):
|
|
111
|
+
run_id: str
|
|
112
|
+
tenant_id: str
|
|
113
|
+
user_id: str
|
|
114
|
+
workspace_root: Path
|
|
115
|
+
run_dir: Path
|
|
116
|
+
state: SessionState
|
|
117
|
+
terminal: bool
|
|
118
|
+
created_at: float
|
|
119
|
+
started_at: float
|
|
120
|
+
finished_at: float
|
|
121
|
+
last_event_seq: int
|
|
122
|
+
last_event_type: str
|
|
123
|
+
error: str
|
|
124
|
+
error_code: str
|
|
125
|
+
result: AgentRunResult | None
|
|
126
|
+
last_final_output: Any
|
|
127
|
+
runtime_config: AgentRuntimeConfig | None
|
|
128
|
+
runtime_config_issuer: str
|
|
129
|
+
runtime_config_reason: str
|
|
130
|
+
runtime_config_committed_at: float
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class MutableRunRecordPort(RunRecordPort, Protocol):
|
|
134
|
+
message_queue: MessageQueuePort
|
|
135
|
+
loop: LoopPort | None
|
|
136
|
+
cancellation_token: CancellationTokenPort
|
|
137
|
+
seen_inbox_ids: set[str]
|
|
138
|
+
outbox_sender: OutboxSender | None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class PreparedRunPort(Protocol):
|
|
142
|
+
run_id: str
|
|
143
|
+
record: MutableRunRecordPort
|
|
144
|
+
workspace_root: Path
|
|
145
|
+
run_token: str
|
|
146
|
+
llm_gateway_token: str
|
|
147
|
+
web_gateway_token: str
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class RunRequestPort(Protocol):
|
|
151
|
+
tenant_id: str
|
|
152
|
+
user_id: str
|
|
153
|
+
workspace_root: Path
|
|
154
|
+
instruction: str
|
|
155
|
+
input_parts: tuple[Any, ...]
|
|
156
|
+
mode: str
|
|
157
|
+
workspace_backend: str
|
|
158
|
+
max_steps: int
|
|
159
|
+
max_tool_calls: int
|
|
160
|
+
max_bytes_read: int
|
|
161
|
+
max_duration_s: int | None
|
|
162
|
+
permission_policy: Any
|
|
163
|
+
runtime_config: AgentRuntimeConfig | None
|
|
164
|
+
multi_turn: bool
|
|
165
|
+
metadata: dict[str, Any]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class LeaseStorePort(Protocol):
|
|
169
|
+
def candidate_run_ids(self) -> list[str]: ...
|
|
170
|
+
|
|
171
|
+
def is_stale(self, run_id: str) -> bool: ...
|
|
172
|
+
|
|
173
|
+
def try_claim(self, run_id: str, worker_id: str, ttl_s: float) -> bool: ...
|
|
174
|
+
|
|
175
|
+
def release(self, run_id: str) -> None: ...
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class DriveOpenSessionPort(Protocol):
|
|
179
|
+
def __call__(
|
|
180
|
+
self,
|
|
181
|
+
record: MutableRunRecordPort,
|
|
182
|
+
request: RunRequestPort,
|
|
183
|
+
loop: LoopPort,
|
|
184
|
+
suspension: Suspension,
|
|
185
|
+
*,
|
|
186
|
+
started: float,
|
|
187
|
+
turns: int,
|
|
188
|
+
) -> Awaitable[AgentRunResult]: ...
|