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 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)
@@ -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
+ }