dir-core 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.
- dir_core/__init__.py +270 -0
- dir_core/agent_registry.py +209 -0
- dir_core/arbitration.py +53 -0
- dir_core/context_store.py +110 -0
- dir_core/data_types.py +121 -0
- dir_core/dfid.py +27 -0
- dir_core/dim.py +112 -0
- dir_core/escalation.py +178 -0
- dir_core/event_bus.py +272 -0
- dir_core/idempotency.py +81 -0
- dir_core/intent_retry.py +64 -0
- dir_core/jit.py +96 -0
- dir_core/ledger.py +44 -0
- dir_core/lifecycle.py +61 -0
- dir_core/models.py +466 -0
- dir_core/pci.py +98 -0
- dir_core/resource_lock.py +116 -0
- dir_core/runtime.py +135 -0
- dir_core/saga.py +142 -0
- dir_core/storage/__init__.py +171 -0
- dir_core/storage/base.py +369 -0
- dir_core/storage/json_util.py +22 -0
- dir_core/storage/memory.py +365 -0
- dir_core/storage/schema.sql +256 -0
- dir_core/storage/sqlite.py +640 -0
- dir_core/utils/__init__.py +18 -0
- dir_core/utils/llm_client.py +16 -0
- dir_core/utils/logging_utils.py +27 -0
- dir_core/wakeup.py +63 -0
- dir_core-0.1.0.dist-info/METADATA +281 -0
- dir_core-0.1.0.dist-info/RECORD +34 -0
- dir_core-0.1.0.dist-info/WHEEL +5 -0
- dir_core-0.1.0.dist-info/licenses/LICENSE +201 -0
- dir_core-0.1.0.dist-info/top_level.txt +1 -0
dir_core/__init__.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decision Intelligence Runtime (DIR) - core package for ROA/DIR.
|
|
3
|
+
|
|
4
|
+
Core components: DFID, EventBus, DIM, Context Store, models, pluggable storage.
|
|
5
|
+
|
|
6
|
+
Install::
|
|
7
|
+
|
|
8
|
+
pip install dir-runtime
|
|
9
|
+
|
|
10
|
+
Custom storage backend example::
|
|
11
|
+
|
|
12
|
+
from dir_core import AgentRegistry
|
|
13
|
+
from dir_core.storage import AgentRegistryStorage
|
|
14
|
+
|
|
15
|
+
class MyPostgresStorage:
|
|
16
|
+
def init_schema(self) -> None: ...
|
|
17
|
+
def upsert_agent(self, agent_id, contract_json, priority,
|
|
18
|
+
status, agent_version, session_token) -> None: ...
|
|
19
|
+
# implement remaining AgentRegistryStorage methods
|
|
20
|
+
|
|
21
|
+
registry = AgentRegistry(storage=MyPostgresStorage())
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .agent_registry import AgentRegistry, HandshakeResult
|
|
25
|
+
from .arbitration import DEFAULT_PRIORITY_MATRIX, select_winner
|
|
26
|
+
from .dim import validate_proposal
|
|
27
|
+
from .data_types import (
|
|
28
|
+
AgentRegistryStatus,
|
|
29
|
+
ContractRole,
|
|
30
|
+
DecisionFlowStatus,
|
|
31
|
+
DecisionRecordOutcome,
|
|
32
|
+
DimReasonCode,
|
|
33
|
+
EscalationSeverity,
|
|
34
|
+
EventBusBackend,
|
|
35
|
+
EventType,
|
|
36
|
+
FlowTimelineEventType,
|
|
37
|
+
HandshakeRejectionReason,
|
|
38
|
+
HumanDecision,
|
|
39
|
+
ValidationReason,
|
|
40
|
+
ValidationResult,
|
|
41
|
+
ValidationVerdict,
|
|
42
|
+
)
|
|
43
|
+
from .context_store import ContextStore
|
|
44
|
+
from .runtime import DecisionRuntime
|
|
45
|
+
from .dfid import new_dfid, new_dfid_with_parent
|
|
46
|
+
from .event_bus import (
|
|
47
|
+
Event,
|
|
48
|
+
EventBus,
|
|
49
|
+
EventMetadata,
|
|
50
|
+
LoggingEventBus,
|
|
51
|
+
create_event_bus,
|
|
52
|
+
)
|
|
53
|
+
from .escalation import (
|
|
54
|
+
EscalationManager,
|
|
55
|
+
EscalationOutcome,
|
|
56
|
+
ImpactCategory,
|
|
57
|
+
)
|
|
58
|
+
from .idempotency import (
|
|
59
|
+
IdempotencyGuard,
|
|
60
|
+
MemoryBackend,
|
|
61
|
+
SQLiteBackend,
|
|
62
|
+
idempotency_key,
|
|
63
|
+
)
|
|
64
|
+
from .intent_retry import REASONING_EXHAUSTION, IntentRetryGovernor
|
|
65
|
+
from .lifecycle import FlowStatus, transition
|
|
66
|
+
from .resource_lock import LockResult, ResourceLockManager
|
|
67
|
+
from .jit import JITStateVerifier, verify_drift
|
|
68
|
+
from .ledger import DecisionLedger
|
|
69
|
+
from .models import (
|
|
70
|
+
AgentState,
|
|
71
|
+
ContextSnapshot,
|
|
72
|
+
DecisionAtom,
|
|
73
|
+
DecisionFlow,
|
|
74
|
+
DecisionRecord,
|
|
75
|
+
EscalationRequest,
|
|
76
|
+
ExecutionIntent,
|
|
77
|
+
ExplainResult,
|
|
78
|
+
FlowEvent,
|
|
79
|
+
Policy,
|
|
80
|
+
PolicyProposal,
|
|
81
|
+
ProofCarryingIntent,
|
|
82
|
+
CompensationAction,
|
|
83
|
+
ResponsibilityContract,
|
|
84
|
+
SelfCheckResult,
|
|
85
|
+
)
|
|
86
|
+
from .saga import SagaCompensation
|
|
87
|
+
from .pci import (
|
|
88
|
+
ProofChecker,
|
|
89
|
+
compute_evidence_hash,
|
|
90
|
+
hash_content,
|
|
91
|
+
proposal_params_for_hash,
|
|
92
|
+
)
|
|
93
|
+
from .wakeup import (
|
|
94
|
+
WakeupPredicate,
|
|
95
|
+
is_relevant_instrument,
|
|
96
|
+
price_change_significant,
|
|
97
|
+
should_wake,
|
|
98
|
+
volatility_elevated,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Storage layer - protocols and built-in backends
|
|
102
|
+
from .storage import (
|
|
103
|
+
# Protocols (implement these to create a custom backend)
|
|
104
|
+
AgentRegistryStorage,
|
|
105
|
+
AuditStore,
|
|
106
|
+
ContextStorage,
|
|
107
|
+
DecisionAuditStorage,
|
|
108
|
+
IdempotencyStorage,
|
|
109
|
+
SagaStorage,
|
|
110
|
+
ResourceLockStorage,
|
|
111
|
+
IntentRetryStorage,
|
|
112
|
+
EscalationStorage,
|
|
113
|
+
LifecycleStorage,
|
|
114
|
+
# Exceptions
|
|
115
|
+
StorageError,
|
|
116
|
+
ResourceContentionError,
|
|
117
|
+
# SQLite backends (default, no extra deps)
|
|
118
|
+
SqliteAgentRegistryStorage,
|
|
119
|
+
SqliteContextStorage,
|
|
120
|
+
SqliteDecisionAuditStorage,
|
|
121
|
+
SqliteIdempotencyStorage,
|
|
122
|
+
SqliteSagaStorage,
|
|
123
|
+
SqliteResourceLockStorage,
|
|
124
|
+
SqliteIntentRetryStorage,
|
|
125
|
+
SqliteEscalationStorage,
|
|
126
|
+
SqliteLifecycleStorage,
|
|
127
|
+
# Memory backends (testing / ephemeral)
|
|
128
|
+
MemoryAgentRegistryStorage,
|
|
129
|
+
MemoryContextStorage,
|
|
130
|
+
MemoryDecisionAuditStorage,
|
|
131
|
+
MemoryIdempotencyStorage,
|
|
132
|
+
MemorySagaStorage,
|
|
133
|
+
MemoryResourceLockStorage,
|
|
134
|
+
MemoryIntentRetryStorage,
|
|
135
|
+
MemoryEscalationStorage,
|
|
136
|
+
MemoryLifecycleStorage,
|
|
137
|
+
# Bundles & factories
|
|
138
|
+
StorageBundle,
|
|
139
|
+
sqlite_storage,
|
|
140
|
+
memory_storage,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
__all__ = [
|
|
144
|
+
"DEFAULT_PRIORITY_MATRIX",
|
|
145
|
+
# DIM (DIR ?6)
|
|
146
|
+
"validate_proposal",
|
|
147
|
+
"ValidationVerdict",
|
|
148
|
+
"ValidationResult",
|
|
149
|
+
"ValidationReason",
|
|
150
|
+
"DimReasonCode",
|
|
151
|
+
"ContractRole",
|
|
152
|
+
"DecisionRecordOutcome",
|
|
153
|
+
"EscalationSeverity",
|
|
154
|
+
"FlowTimelineEventType",
|
|
155
|
+
"DecisionFlowStatus",
|
|
156
|
+
"HumanDecision",
|
|
157
|
+
"AgentRegistryStatus",
|
|
158
|
+
"HandshakeRejectionReason",
|
|
159
|
+
"EventBusBackend",
|
|
160
|
+
"new_dfid",
|
|
161
|
+
"new_dfid_with_parent",
|
|
162
|
+
"select_winner",
|
|
163
|
+
# EventBus (DIR Topologies ?2)
|
|
164
|
+
"Event",
|
|
165
|
+
"EventBus",
|
|
166
|
+
"EventMetadata",
|
|
167
|
+
"EventType",
|
|
168
|
+
"LoggingEventBus",
|
|
169
|
+
"create_event_bus",
|
|
170
|
+
# EOAM (Topologies ?2)
|
|
171
|
+
"WakeupPredicate",
|
|
172
|
+
"price_change_significant",
|
|
173
|
+
"volatility_elevated",
|
|
174
|
+
"is_relevant_instrument",
|
|
175
|
+
"should_wake",
|
|
176
|
+
# DL+PCI (Topology C)
|
|
177
|
+
"ProofCarryingIntent",
|
|
178
|
+
"DecisionLedger",
|
|
179
|
+
"compute_evidence_hash",
|
|
180
|
+
"hash_content",
|
|
181
|
+
"proposal_params_for_hash",
|
|
182
|
+
"ProofChecker",
|
|
183
|
+
# Intent Retry Governor (DIR ?6.2)
|
|
184
|
+
"IntentRetryGovernor",
|
|
185
|
+
"REASONING_EXHAUSTION",
|
|
186
|
+
# Lifecycle (DIR ?4.3)
|
|
187
|
+
"FlowStatus",
|
|
188
|
+
"transition",
|
|
189
|
+
# Escalation Manager (DIR ?9)
|
|
190
|
+
"EscalationManager",
|
|
191
|
+
"EscalationOutcome",
|
|
192
|
+
"ImpactCategory",
|
|
193
|
+
# Resource Locking (DIR ?6.2)
|
|
194
|
+
"ResourceLockManager",
|
|
195
|
+
"LockResult",
|
|
196
|
+
# Agent Registry (DIR ?2.3)
|
|
197
|
+
"AgentRegistry",
|
|
198
|
+
"HandshakeResult",
|
|
199
|
+
# Context Store (DIR ?8)
|
|
200
|
+
"ContextStore",
|
|
201
|
+
# Facade (DX)
|
|
202
|
+
"DecisionRuntime",
|
|
203
|
+
# Idempotency (DIR ?7)
|
|
204
|
+
"IdempotencyGuard",
|
|
205
|
+
"idempotency_key",
|
|
206
|
+
"SQLiteBackend",
|
|
207
|
+
"MemoryBackend",
|
|
208
|
+
# Saga Compensation (DIR ?7)
|
|
209
|
+
"SagaCompensation",
|
|
210
|
+
"CompensationAction",
|
|
211
|
+
# SDS (Topology B)
|
|
212
|
+
"DecisionAtom",
|
|
213
|
+
"JITStateVerifier",
|
|
214
|
+
"verify_drift",
|
|
215
|
+
# Core models
|
|
216
|
+
"ResponsibilityContract",
|
|
217
|
+
"PolicyProposal",
|
|
218
|
+
"ExecutionIntent",
|
|
219
|
+
# ROA Lifecycle models
|
|
220
|
+
"ExplainResult",
|
|
221
|
+
"Policy",
|
|
222
|
+
"SelfCheckResult",
|
|
223
|
+
# Agent state
|
|
224
|
+
"AgentState",
|
|
225
|
+
"DecisionRecord",
|
|
226
|
+
"EscalationRequest",
|
|
227
|
+
# DecisionFlow (DIR ?5.4)
|
|
228
|
+
"DecisionFlow",
|
|
229
|
+
"ContextSnapshot",
|
|
230
|
+
"FlowEvent",
|
|
231
|
+
# Storage layer - protocols
|
|
232
|
+
"AgentRegistryStorage",
|
|
233
|
+
"AuditStore",
|
|
234
|
+
"ContextStorage",
|
|
235
|
+
"DecisionAuditStorage",
|
|
236
|
+
"IdempotencyStorage",
|
|
237
|
+
"SagaStorage",
|
|
238
|
+
"ResourceLockStorage",
|
|
239
|
+
"IntentRetryStorage",
|
|
240
|
+
"EscalationStorage",
|
|
241
|
+
"LifecycleStorage",
|
|
242
|
+
"StorageError",
|
|
243
|
+
"ResourceContentionError",
|
|
244
|
+
# Storage layer - SQLite backends
|
|
245
|
+
"SqliteAgentRegistryStorage",
|
|
246
|
+
"SqliteContextStorage",
|
|
247
|
+
"SqliteDecisionAuditStorage",
|
|
248
|
+
"SqliteIdempotencyStorage",
|
|
249
|
+
"SqliteSagaStorage",
|
|
250
|
+
"SqliteResourceLockStorage",
|
|
251
|
+
"SqliteIntentRetryStorage",
|
|
252
|
+
"SqliteEscalationStorage",
|
|
253
|
+
"SqliteLifecycleStorage",
|
|
254
|
+
# Storage layer - memory backends
|
|
255
|
+
"MemoryAgentRegistryStorage",
|
|
256
|
+
"MemoryContextStorage",
|
|
257
|
+
"MemoryDecisionAuditStorage",
|
|
258
|
+
"MemoryIdempotencyStorage",
|
|
259
|
+
"MemorySagaStorage",
|
|
260
|
+
"MemoryResourceLockStorage",
|
|
261
|
+
"MemoryIntentRetryStorage",
|
|
262
|
+
"MemoryEscalationStorage",
|
|
263
|
+
"MemoryLifecycleStorage",
|
|
264
|
+
# Storage bundles & factories
|
|
265
|
+
"StorageBundle",
|
|
266
|
+
"sqlite_storage",
|
|
267
|
+
"memory_storage",
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Registry: contract, handshake, lookup by agent_id.
|
|
3
|
+
|
|
4
|
+
DIR §2.3. Maintains a registry of active agents, their capability contracts, and metadata.
|
|
5
|
+
Handshake with SemVer alignment; schema serving for Context compilation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import uuid
|
|
11
|
+
import logging
|
|
12
|
+
import warnings
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from .data_types import AgentRegistryStatus, HandshakeRejectionReason
|
|
17
|
+
from .storage.base import AgentRegistryStorage
|
|
18
|
+
from .storage.sqlite import SqliteAgentRegistryStorage
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
SEMVER_RE = re.compile(r"^(\d+)\.(\d+)(?:\.(\d+))?(?:[-+].*)?$")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class HandshakeResult:
|
|
27
|
+
"""Result of agent handshake (DIR §2.3)."""
|
|
28
|
+
|
|
29
|
+
accepted: bool
|
|
30
|
+
session_token: Optional[str] = None
|
|
31
|
+
reason: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _parse_version(v: str) -> Optional[tuple]:
|
|
35
|
+
"""Parse semver string to (major, minor, patch)."""
|
|
36
|
+
m = SEMVER_RE.match(v.strip())
|
|
37
|
+
if not m:
|
|
38
|
+
return None
|
|
39
|
+
major, minor, patch = int(m.group(1)), int(m.group(2)), int(m.group(3) or 0)
|
|
40
|
+
return (major, minor, patch)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _version_compatible(agent_ver: str, supported: str) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Check if agent_ver is compatible with supported (e.g. "1.x" or "1.2").
|
|
46
|
+
"""
|
|
47
|
+
av = _parse_version(agent_ver)
|
|
48
|
+
if not av:
|
|
49
|
+
return False
|
|
50
|
+
if supported.endswith(".x"):
|
|
51
|
+
prefix = supported[:-2]
|
|
52
|
+
sv = _parse_version(prefix + ".0")
|
|
53
|
+
if not sv:
|
|
54
|
+
return False
|
|
55
|
+
return av[0] == sv[0]
|
|
56
|
+
sv = _parse_version(supported)
|
|
57
|
+
if not sv:
|
|
58
|
+
return False
|
|
59
|
+
return av[0] == sv[0] and av[1] >= sv[1]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AgentRegistry:
|
|
63
|
+
"""Registry of active agents with SemVer handshake (DIR §2.3).
|
|
64
|
+
|
|
65
|
+
Storage backend is pluggable. Pass ``storage=`` for a custom backend, or
|
|
66
|
+
``db_path=`` to use the built-in SQLite backend (default behaviour).
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
db_path: Path to SQLite database. Used when ``storage`` is not provided.
|
|
70
|
+
supported_versions: SemVer constraint for handshake (e.g. ``"1.x"``).
|
|
71
|
+
storage: Custom :class:`~dir_core.storage.AgentRegistryStorage` backend.
|
|
72
|
+
When provided, ``db_path`` is ignored.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ValueError: When neither ``db_path`` nor ``storage`` is supplied.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
db_path: Optional[str] = None,
|
|
81
|
+
supported_versions: str = "1.x",
|
|
82
|
+
*,
|
|
83
|
+
storage: Optional[AgentRegistryStorage] = None,
|
|
84
|
+
):
|
|
85
|
+
self.supported_versions = supported_versions
|
|
86
|
+
if storage is not None:
|
|
87
|
+
self._storage: AgentRegistryStorage = storage
|
|
88
|
+
elif db_path is not None:
|
|
89
|
+
self.db_path = db_path # kept for backward compatibility
|
|
90
|
+
self._storage = SqliteAgentRegistryStorage(db_path)
|
|
91
|
+
else:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"Provide either 'db_path' (SQLite) or 'storage' (custom backend)."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def handshake(
|
|
97
|
+
self,
|
|
98
|
+
agent_id: str,
|
|
99
|
+
contract: Dict[str, Any],
|
|
100
|
+
agent_version: str,
|
|
101
|
+
priority: int = 0,
|
|
102
|
+
) -> HandshakeResult:
|
|
103
|
+
"""
|
|
104
|
+
Handshake with version check. REJECT on VERSION_MISMATCH.
|
|
105
|
+
Returns ACCEPTED with session_token or REJECTED with reason.
|
|
106
|
+
"""
|
|
107
|
+
if not _version_compatible(agent_version, self.supported_versions):
|
|
108
|
+
return HandshakeResult(
|
|
109
|
+
accepted=False,
|
|
110
|
+
reason=HandshakeRejectionReason.VERSION_MISMATCH.value,
|
|
111
|
+
)
|
|
112
|
+
token = str(uuid.uuid4())
|
|
113
|
+
self._storage.upsert_agent(
|
|
114
|
+
agent_id=agent_id,
|
|
115
|
+
contract_json=json.dumps(contract),
|
|
116
|
+
priority=priority,
|
|
117
|
+
status=AgentRegistryStatus.ACTIVE,
|
|
118
|
+
agent_version=agent_version,
|
|
119
|
+
session_token=token,
|
|
120
|
+
)
|
|
121
|
+
logger.info("Handshake: agent_id=%s ver=%s accepted", agent_id, agent_version)
|
|
122
|
+
return HandshakeResult(accepted=True, session_token=token)
|
|
123
|
+
|
|
124
|
+
def get_schema(
|
|
125
|
+
self, agent_id: str, schema_kind: Optional[str] = None
|
|
126
|
+
) -> Optional[dict]:
|
|
127
|
+
"""Return schema from contract: schema_kind=None -> contract['schema'];
|
|
128
|
+
else -> contract['schemas'][kind] or contract['schema']."""
|
|
129
|
+
contract = self.get_agent_contract(agent_id)
|
|
130
|
+
if not contract:
|
|
131
|
+
return None
|
|
132
|
+
if schema_kind is not None and "schemas" in contract and schema_kind in contract["schemas"]:
|
|
133
|
+
return contract["schemas"][schema_kind]
|
|
134
|
+
return contract.get("schema")
|
|
135
|
+
|
|
136
|
+
def register_agent(
|
|
137
|
+
self,
|
|
138
|
+
agent_id: str,
|
|
139
|
+
contract: Dict[str, Any],
|
|
140
|
+
priority: int = 0,
|
|
141
|
+
) -> None:
|
|
142
|
+
"""Register or update an agent with capability contract.
|
|
143
|
+
|
|
144
|
+
Deprecated: Use handshake() for version checking and session tokens (DIR §2.3).
|
|
145
|
+
register_agent does not enforce SemVer compatibility.
|
|
146
|
+
"""
|
|
147
|
+
warnings.warn(
|
|
148
|
+
"register_agent is deprecated; use handshake() for version checking (DIR §2.3)",
|
|
149
|
+
DeprecationWarning,
|
|
150
|
+
stacklevel=2,
|
|
151
|
+
)
|
|
152
|
+
self._storage.upsert_agent(
|
|
153
|
+
agent_id=agent_id,
|
|
154
|
+
contract_json=json.dumps(contract),
|
|
155
|
+
priority=priority,
|
|
156
|
+
status=AgentRegistryStatus.ACTIVE,
|
|
157
|
+
agent_version=None,
|
|
158
|
+
session_token=None,
|
|
159
|
+
)
|
|
160
|
+
logger.info("Registered agent: %s (priority=%d)", agent_id, priority)
|
|
161
|
+
|
|
162
|
+
def get_agent_contract(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
|
163
|
+
"""Retrieve agent capability contract."""
|
|
164
|
+
rec = self._storage.get_agent(agent_id)
|
|
165
|
+
return rec["contract"] if rec else None
|
|
166
|
+
|
|
167
|
+
def get_agent_manifest(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
|
168
|
+
"""Retrieve agent capability contract. Deprecated: use get_agent_contract."""
|
|
169
|
+
return self.get_agent_contract(agent_id)
|
|
170
|
+
|
|
171
|
+
def get_agent_priority(self, agent_id: str) -> int:
|
|
172
|
+
"""Retrieve agent priority (default 0)."""
|
|
173
|
+
rec = self._storage.get_agent(agent_id)
|
|
174
|
+
return rec["priority"] if rec else 0
|
|
175
|
+
|
|
176
|
+
def list_agents(self) -> List[str]:
|
|
177
|
+
"""List all active agent IDs."""
|
|
178
|
+
return self._storage.list_active_agents()
|
|
179
|
+
|
|
180
|
+
def set_agent_status(
|
|
181
|
+
self,
|
|
182
|
+
agent_id: str,
|
|
183
|
+
status: str,
|
|
184
|
+
suspension_reason: Optional[str] = None,
|
|
185
|
+
) -> bool:
|
|
186
|
+
"""
|
|
187
|
+
Transition agent lifecycle status (e.g. ACTIVE -> SUSPENDED).
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
agent_id: Registered agent identifier.
|
|
191
|
+
status: New status value (e.g. 'SUSPENDED', 'ACTIVE').
|
|
192
|
+
suspension_reason: Optional machine-oriented reason (audit / ops).
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
True if a row was updated.
|
|
196
|
+
"""
|
|
197
|
+
updated = self._storage.update_status(agent_id, status, suspension_reason)
|
|
198
|
+
if updated:
|
|
199
|
+
logger.info(
|
|
200
|
+
"Agent status: agent_id=%s status=%s reason=%s",
|
|
201
|
+
agent_id,
|
|
202
|
+
status,
|
|
203
|
+
suspension_reason,
|
|
204
|
+
)
|
|
205
|
+
return updated
|
|
206
|
+
|
|
207
|
+
def get_agent_status(self, agent_id: str) -> Optional[tuple]:
|
|
208
|
+
"""Return (status, suspension_reason) if the agent exists, else None."""
|
|
209
|
+
return self._storage.get_status(agent_id)
|
dir_core/arbitration.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Priority-Based Arbitration (DIR Topologies §2.4).
|
|
3
|
+
|
|
4
|
+
Selects the winning proposal from parallel agents using a priority matrix.
|
|
5
|
+
Lower priority number = higher precedence (e.g. Risk > Strategy).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from .models import PolicyProposal
|
|
11
|
+
|
|
12
|
+
DEFAULT_PRIORITY_MATRIX: Dict[str, int] = {
|
|
13
|
+
"RISK_ALERT": 1,
|
|
14
|
+
"CLOSE_LONG": 2,
|
|
15
|
+
"CLOSE": 2,
|
|
16
|
+
"OPEN_LONG": 3,
|
|
17
|
+
"TAKE_PROFIT": 3,
|
|
18
|
+
"ADJUST_STOP": 4,
|
|
19
|
+
"SENTIMENT_BULLISH": 4,
|
|
20
|
+
"SENTIMENT_BEARISH": 4,
|
|
21
|
+
"RISK_OK": 5,
|
|
22
|
+
"OPEN_POSITION": 5,
|
|
23
|
+
"NEWS_QUALIFIED": 6,
|
|
24
|
+
"SENTIMENT_NEUTRAL": 7,
|
|
25
|
+
"HOLD": 10,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def select_winner(
|
|
30
|
+
proposals: List[PolicyProposal],
|
|
31
|
+
priority_matrix: Optional[Dict[str, int]] = None,
|
|
32
|
+
) -> Optional[PolicyProposal]:
|
|
33
|
+
"""Select winning proposal using Priority Matrix (Topologies §2.4).
|
|
34
|
+
|
|
35
|
+
Lower priority number = higher precedence. If no matrix provided,
|
|
36
|
+
uses DEFAULT_PRIORITY_MATRIX. Unknown policy_kind gets priority 10.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
proposals: List of policy proposals from parallel agents.
|
|
40
|
+
priority_matrix: Optional mapping policy_kind -> priority (lower = higher).
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The winning proposal, or None if proposals list is empty.
|
|
44
|
+
"""
|
|
45
|
+
if not proposals:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
matrix = priority_matrix or DEFAULT_PRIORITY_MATRIX
|
|
49
|
+
|
|
50
|
+
def get_priority(p: PolicyProposal) -> int:
|
|
51
|
+
return matrix.get(p.policy_kind, 10)
|
|
52
|
+
|
|
53
|
+
return min(proposals, key=get_priority)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Store (DIR §8) - Manages multi-layered context for agents.
|
|
3
|
+
|
|
4
|
+
Layers:
|
|
5
|
+
1. Session (Ephemeral): Context specific to the current DecisionFlow (dfid).
|
|
6
|
+
2. State (Authoritative): Long-lived agent state (policy versions, trajectory).
|
|
7
|
+
3. Memory (Long-term): Vector DB or archival storage (Stub for MVP).
|
|
8
|
+
4. Artifacts (Reference): Static docs/rules (Stub).
|
|
9
|
+
|
|
10
|
+
Provides `compile_working_context` to assemble a frozen view for decision making.
|
|
11
|
+
|
|
12
|
+
Implementation note: Memory and Artifacts layers are stubs (return {}).
|
|
13
|
+
Full implementation requires: Memory (vector DB / archival), Artifacts (RAG / static docs).
|
|
14
|
+
See DIR §8.1, ROA §7.2 for layer definitions.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
from typing import Any, Dict, Optional
|
|
20
|
+
|
|
21
|
+
from .storage.base import ContextStorage
|
|
22
|
+
from .storage.sqlite import SqliteContextStorage
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ContextStore:
|
|
28
|
+
"""Multi-layered context store for agent state (DIR §8).
|
|
29
|
+
|
|
30
|
+
Storage backend is pluggable. Pass ``storage=`` for a custom backend, or
|
|
31
|
+
``db_path=`` to use the built-in SQLite backend (default behaviour).
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
db_path: Path to SQLite database. Used when ``storage`` is not provided.
|
|
35
|
+
storage: Custom :class:`~dir_core.storage.ContextStorage` backend.
|
|
36
|
+
When provided, ``db_path`` is ignored.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError: When neither ``db_path`` nor ``storage`` is supplied.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
db_path: Optional[str] = None,
|
|
45
|
+
*,
|
|
46
|
+
storage: Optional[ContextStorage] = None,
|
|
47
|
+
):
|
|
48
|
+
if storage is not None:
|
|
49
|
+
self._storage: ContextStorage = storage
|
|
50
|
+
elif db_path is not None:
|
|
51
|
+
self.db_path = db_path # kept for backward compatibility
|
|
52
|
+
self._storage = SqliteContextStorage(db_path)
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"Provide either 'db_path' (SQLite) or 'storage' (custom backend)."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# -------------------------------------------------------------------------
|
|
59
|
+
# Layer 1: Session (Ephemeral)
|
|
60
|
+
# -------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
def get_session(self, dfid: str) -> Dict[str, Any]:
|
|
63
|
+
raw = self._storage.get_session(dfid)
|
|
64
|
+
return json.loads(raw) if raw else {}
|
|
65
|
+
|
|
66
|
+
def update_session(self, dfid: str, updates: Dict[str, Any]) -> None:
|
|
67
|
+
"""Merge updates into existing session."""
|
|
68
|
+
current = self.get_session(dfid)
|
|
69
|
+
current.update(updates)
|
|
70
|
+
self._storage.set_session(dfid, json.dumps(current))
|
|
71
|
+
|
|
72
|
+
# -------------------------------------------------------------------------
|
|
73
|
+
# Layer 2: State (Authoritative)
|
|
74
|
+
# -------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
def get_state(self, agent_id: str) -> Dict[str, Any]:
|
|
77
|
+
raw = self._storage.get_state(agent_id)
|
|
78
|
+
return json.loads(raw) if raw else {}
|
|
79
|
+
|
|
80
|
+
def update_state(self, agent_id: str, updates: Dict[str, Any]) -> None:
|
|
81
|
+
"""Merge updates into existing state."""
|
|
82
|
+
current = self.get_state(agent_id)
|
|
83
|
+
current.update(updates)
|
|
84
|
+
self._storage.set_state(agent_id, json.dumps(current))
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------------------------------------
|
|
87
|
+
# Compiler
|
|
88
|
+
# -------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def compile_working_context(self, agent_id: str, dfid: str) -> Dict[str, Any]:
|
|
91
|
+
"""
|
|
92
|
+
Assemble all layers into a single Working Context.
|
|
93
|
+
Returns immutable dictionary (snapshot).
|
|
94
|
+
"""
|
|
95
|
+
session_data = self.get_session(dfid)
|
|
96
|
+
state_data = self.get_state(agent_id)
|
|
97
|
+
|
|
98
|
+
# In a real system, we'd fetch Memory and Artifacts here too.
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
"meta": {
|
|
102
|
+
"agent_id": agent_id,
|
|
103
|
+
"dfid": dfid,
|
|
104
|
+
"source": "ContextStore",
|
|
105
|
+
},
|
|
106
|
+
"session": session_data,
|
|
107
|
+
"state": state_data,
|
|
108
|
+
"memory": {}, # Stub
|
|
109
|
+
"artifacts": {} # Stub
|
|
110
|
+
}
|