cordis-runtime 0.1.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.
@@ -0,0 +1,7 @@
1
+ """Minimal host-facing runtime for Cordis Core."""
2
+
3
+ from .runtime import CordisHostRuntime, RuntimeValidationError
4
+
5
+ __version__ = "0.1.1"
6
+
7
+ __all__ = ["CordisHostRuntime", "RuntimeValidationError", "__version__"]
@@ -0,0 +1,374 @@
1
+ """Thin deterministic bridge from a host task to Cordis Core and a model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ import re
7
+ from typing import Any, Dict, Iterable, Mapping, Optional
8
+
9
+ from cordis_core import CordisRuntime
10
+ from cordis_memory import CognitiveStore
11
+
12
+
13
+ RUNTIME_SCHEMA = "cordis.runtime.v1"
14
+ VALID_MODES = {"fast", "advisory", "high_intervention", "takeover"}
15
+ DESTRUCTIVE_TERMS = {"delete", "drop", "destroy", "wipe", "truncate", "remove", "format", "生产", "删除", "清空", "销毁"}
16
+
17
+
18
+ class RuntimeValidationError(ValueError):
19
+ """Raised when a host breaks the minimal Runtime contract."""
20
+
21
+
22
+ def _tokens(value: str) -> set[str]:
23
+ return set(re.findall(r"[a-z0-9_]{2,}|[\u4e00-\u9fff]{2}", value.lower()))
24
+
25
+
26
+ def _string_list(value: Any, field: str) -> list[str]:
27
+ if value is None:
28
+ return []
29
+ if not isinstance(value, list) or any(not isinstance(item, str) or not item.strip() for item in value):
30
+ raise RuntimeValidationError(f"{field} must be a list of non-empty strings")
31
+ return [item.strip()[:1000] for item in value[:20]]
32
+
33
+
34
+ class CordisHostRuntime:
35
+ """Expose Core as compact context and deterministic host control signals."""
36
+
37
+ def __init__(self, core: CordisRuntime, memory: Optional[CognitiveStore] = None):
38
+ if not isinstance(core, CordisRuntime):
39
+ raise RuntimeValidationError("core must be a CordisRuntime")
40
+ if memory is not None and not isinstance(memory, CognitiveStore):
41
+ raise RuntimeValidationError("memory must be a CognitiveStore or None")
42
+ self.core = core
43
+ self.memory = memory
44
+ self._focus: Dict[str, Dict[str, Any]] = {}
45
+
46
+ @staticmethod
47
+ def _control_mode(ir: Mapping[str, Any], complexity: float) -> str:
48
+ task = ir["task"]
49
+ prediction = ir["prediction"]
50
+ strategy = ir["strategy"]
51
+ if task["stakes"] == "critical" or (
52
+ strategy["status"] == "avoid_until_revalidated" and prediction["risk_score"] >= 0.62
53
+ ):
54
+ return "takeover"
55
+ if ir["escalation"]["advisor_required"]:
56
+ return "high_intervention"
57
+ if (
58
+ task["stakes"] == "low"
59
+ and complexity <= 0.25
60
+ and not ir["state"]["relevant_memory"]
61
+ and not ir["state"]["relevant_world_patterns"]
62
+ ):
63
+ return "fast"
64
+ return "advisory"
65
+
66
+ @staticmethod
67
+ def _exploration_policy(ir: Mapping[str, Any]) -> Dict[str, Any]:
68
+ """Choose exploit, explore, or revalidate from explicit evidence only."""
69
+ evidence = ir["prediction"]["strategy_evidence"]
70
+ entropy = ir["prediction"]["strategy_entropy"]
71
+ uses = int(evidence["uses"])
72
+ failures = int(evidence["failures"])
73
+ if failures >= 1 and ir["strategy"]["status"] == "avoid_until_revalidated":
74
+ return {
75
+ "mode": "revalidate",
76
+ "reason": "the selected strategy has failed more often than it has succeeded",
77
+ "requirements": ["do_not_repeat_failed_strategy", "compare_alternative_strategy", "verify_assumption"],
78
+ }
79
+ if uses < 3:
80
+ return {
81
+ "mode": "explore",
82
+ "reason": "the selected strategy has insufficient evidence",
83
+ "requirements": ["treat_success_as_tentative", "record_observable_evidence"],
84
+ }
85
+ if entropy is not None and entropy < 0.35:
86
+ return {
87
+ "mode": "explore",
88
+ "reason": "strategy selection is overly concentrated",
89
+ "requirements": ["propose_alternative_strategy", "do_not_randomly_explore_high_risk_work"],
90
+ }
91
+ return {
92
+ "mode": "exploit",
93
+ "reason": "current strategy has sufficient non-concentrated evidence",
94
+ "requirements": ["continue_to_collect_evidence"],
95
+ }
96
+
97
+ @staticmethod
98
+ def _prompt(
99
+ ir: Mapping[str, Any], focus: Mapping[str, Any], mode: str, cognition: Iterable[Mapping[str, Any]] = ()
100
+ ) -> str:
101
+ memory = [item.get("lesson") for item in ir["state"]["relevant_memory"] if item.get("lesson")]
102
+ patterns = [item["statement"] for item in ir["state"]["relevant_world_patterns"]]
103
+ acceptance = [
104
+ f"{item['id']}: {item['description']}" for item in ir["verification"]["acceptance_evidence"]
105
+ ]
106
+
107
+ sections = [
108
+ "[CORDIS CONTEXT]",
109
+ f"Task ID: {ir['task']['id']}",
110
+ f"Goal: {ir['task']['goal']}",
111
+ f"Control mode: {mode}",
112
+ f"Current step: {focus['current_step']}",
113
+ f"Risk: {ir['prediction']['risk_score']}",
114
+ ]
115
+ if focus["constraints"]:
116
+ sections.append("Constraints: " + " | ".join(focus["constraints"]))
117
+ if memory:
118
+ sections.append("Relevant experience: " + " | ".join(memory[:3]))
119
+ if patterns:
120
+ sections.append("World patterns: " + " | ".join(patterns[:3]))
121
+ cognition = list(cognition)
122
+ if cognition:
123
+ items = [f"[{item['id']}] {item['content']}" for item in cognition[:3]]
124
+ sections.append("Cognitive store: " + " | ".join(items))
125
+ sections.extend(
126
+ [
127
+ "Prefer: " + " | ".join(ir["strategy"]["prefer"]),
128
+ "Avoid: " + " | ".join(ir["strategy"]["avoid"]),
129
+ ]
130
+ )
131
+ if acceptance:
132
+ sections.append("Acceptance: " + " | ".join(acceptance))
133
+ if mode == "fast":
134
+ sections.append("Act on the smallest reversible next step; return observable evidence.")
135
+ elif mode == "advisory":
136
+ sections.append("Use this context while choosing and executing the next step.")
137
+ elif mode == "high_intervention":
138
+ sections.append("State the next step and its verification before acting.")
139
+ else:
140
+ sections.append("Do not act until a reviewed plan and verification path are available.")
141
+ return "\n".join(sections)
142
+
143
+ def _memory_snapshot(self, task: Mapping[str, Any]) -> list[Dict[str, Any]]:
144
+ if not self.memory:
145
+ return []
146
+ result = self.memory.query(
147
+ intent=str(task["goal"]), project_id=str(task["project_id"]), scopes=("project", "global"), limit=3
148
+ )
149
+ return [
150
+ item for item in result["items"]
151
+ if item["kind"] != "pattern" or item["status"] == "active"
152
+ ]
153
+
154
+ def export_focus(self) -> Dict[str, Dict[str, Any]]:
155
+ """Return a serializable snapshot of active host-task focus.
156
+
157
+ Core state already survives restarts. Hosts such as a CLI or MCP server
158
+ may need this separate snapshot because they receive later task events
159
+ in a new process.
160
+ """
161
+ return copy.deepcopy(self._focus)
162
+
163
+ def restore_focus(self, value: Mapping[str, Mapping[str, Any]]) -> None:
164
+ """Restore a snapshot produced by :meth:`export_focus`.
165
+
166
+ This restores host control metadata only; it neither opens Core tasks
167
+ nor changes learning state.
168
+ """
169
+ if not isinstance(value, Mapping):
170
+ raise RuntimeValidationError("focus must be an object")
171
+ restored: Dict[str, Dict[str, Any]] = {}
172
+ for task_id, focus in value.items():
173
+ if not isinstance(task_id, str) or not task_id.strip() or not isinstance(focus, Mapping):
174
+ raise RuntimeValidationError("focus must map task IDs to focus objects")
175
+ required = ("task_id", "goal", "current_step", "project_id", "control_mode", "constraints", "seen_cognition_ids")
176
+ if any(field not in focus for field in required):
177
+ raise RuntimeValidationError("focus object is missing required fields")
178
+ if focus["task_id"] != task_id:
179
+ raise RuntimeValidationError("focus task_id must match its map key")
180
+ if focus["control_mode"] not in VALID_MODES:
181
+ raise RuntimeValidationError("focus control_mode is invalid")
182
+ for field in ("goal", "current_step", "project_id"):
183
+ if not isinstance(focus[field], str) or not focus[field].strip():
184
+ raise RuntimeValidationError(f"focus.{field} must be a non-empty string")
185
+ _string_list(focus["constraints"], "focus.constraints")
186
+ _string_list(focus["seen_cognition_ids"], "focus.seen_cognition_ids")
187
+ restored[task_id] = copy.deepcopy(dict(focus))
188
+ self._focus = restored
189
+
190
+ def begin(self, payload: Mapping[str, Any]) -> Dict[str, Any]:
191
+ """Run Core Preflight and return host control plus compact model context."""
192
+ if not isinstance(payload, Mapping):
193
+ raise RuntimeValidationError("payload must be an object")
194
+ complexity = payload.get("complexity", 0.5)
195
+ if not isinstance(complexity, (int, float)):
196
+ raise RuntimeValidationError("complexity must be numeric")
197
+ constraints = _string_list(payload.get("constraints"), "constraints")
198
+ current_step = payload.get("current_step")
199
+ if current_step is not None and (not isinstance(current_step, str) or not current_step.strip()):
200
+ raise RuntimeValidationError("current_step must be a non-empty string")
201
+
202
+ ir = self.core.preflight(payload)
203
+ task_id = ir["task"]["id"]
204
+ cognition = self._memory_snapshot(ir["task"])
205
+ focus = {
206
+ "task_id": task_id,
207
+ "goal": ir["task"]["goal"],
208
+ "current_step": current_step.strip()[:1000] if current_step else ir["task"]["goal"],
209
+ "constraints": constraints,
210
+ "project_id": ir["task"]["project_id"],
211
+ "seen_cognition_ids": [item["id"] for item in cognition],
212
+ }
213
+ mode = self._control_mode(ir, max(0.0, min(1.0, float(complexity))))
214
+ exploration = self._exploration_policy(ir)
215
+ focus["control_mode"] = mode
216
+ self._focus[task_id] = focus
217
+ return {
218
+ "schema": RUNTIME_SCHEMA,
219
+ "task_id": task_id,
220
+ "control": {
221
+ "mode": mode,
222
+ "advisor_required": ir["escalation"]["advisor_required"],
223
+ "execution_allowed": mode != "takeover",
224
+ },
225
+ "exploration": exploration,
226
+ "focus": copy.deepcopy(focus),
227
+ "cognitive_ir": ir,
228
+ "cognition": cognition,
229
+ "model_context": self._prompt(ir, focus, mode, cognition)
230
+ + "\nExploration policy: "
231
+ + exploration["mode"]
232
+ + ". "
233
+ + " | ".join(exploration["requirements"]),
234
+ }
235
+
236
+ def query(
237
+ self,
238
+ task_id: str,
239
+ *,
240
+ intent: str,
241
+ scopes: Iterable[str] = ("project", "global"),
242
+ kinds: Iterable[str] = ("episode", "knowledge", "pattern", "capability", "principle"),
243
+ limit: int = 3,
244
+ ) -> Dict[str, Any]:
245
+ """Return novel cognition for an active task; never repeat seen item IDs."""
246
+ focus = self._focus.get(task_id)
247
+ if not focus:
248
+ raise RuntimeValidationError("unknown active task_id")
249
+ if not self.memory:
250
+ return {"items": [], "status": "memory_not_configured", "excluded_ids": []}
251
+ result = self.memory.query(
252
+ intent=intent,
253
+ project_id=focus["project_id"],
254
+ scopes=scopes,
255
+ kinds=kinds,
256
+ exclude_ids=focus["seen_cognition_ids"],
257
+ limit=limit,
258
+ )
259
+ focus["seen_cognition_ids"].extend(item["id"] for item in result["items"])
260
+ return result
261
+
262
+ def check_action(self, task_id: str, action: Mapping[str, Any]) -> Dict[str, Any]:
263
+ """Return a cheap drift signal; the host decides whether to enforce it."""
264
+ focus = self._focus.get(task_id)
265
+ if not focus:
266
+ raise RuntimeValidationError("unknown active task_id")
267
+ if not isinstance(action, Mapping):
268
+ raise RuntimeValidationError("action must be an object")
269
+ description = action.get("description")
270
+ purpose = action.get("purpose")
271
+ if not isinstance(description, str) or not description.strip():
272
+ raise RuntimeValidationError("action.description is required")
273
+ if not isinstance(purpose, str) or not purpose.strip():
274
+ raise RuntimeValidationError("action.purpose is required")
275
+ focus_tokens = _tokens(f"{focus['goal']} {focus['current_step']}")
276
+ action_tokens = _tokens(f"{description} {purpose}")
277
+ overlap = sorted(focus_tokens & action_tokens)
278
+ aligned = bool(overlap) or not focus_tokens
279
+ dangerous_terms = sorted(DESTRUCTIVE_TERMS & action_tokens)
280
+ requires_review = bool(dangerous_terms)
281
+ result = {
282
+ "task_id": task_id,
283
+ "aligned": aligned,
284
+ "signal": "destructive_action_review" if requires_review else ("on_focus" if aligned else "possible_drift"),
285
+ "heuristic": True,
286
+ "reason": (
287
+ "destructive action term detected: " + ", ".join(dangerous_terms)
288
+ if requires_review
289
+ else ("shared lexical terms: " + ", ".join(overlap) if aligned else "no shared lexical terms")
290
+ ),
291
+ "shared_terms": overlap[:10],
292
+ "dangerous_terms": dangerous_terms,
293
+ "requires_review": requires_review,
294
+ "control_mode": focus["control_mode"],
295
+ }
296
+ if self.memory:
297
+ self.memory.record_event(
298
+ {
299
+ "event_type": "action_checked" if aligned else "possible_drift",
300
+ "scope": "workflow",
301
+ "project_id": focus["project_id"],
302
+ "task_id": task_id,
303
+ "subject": description,
304
+ "actual": purpose,
305
+ "expected": focus["current_step"],
306
+ "error_class": None if aligned else "attention_drift",
307
+ }
308
+ )
309
+ return result
310
+
311
+ def observe(self, task_id: str, event: Mapping[str, Any]) -> Dict[str, Any]:
312
+ """Record any plan, tool, artifact, test, or verification event for an active task.
313
+
314
+ Hosts use this one method instead of maintaining a separate taxonomy for
315
+ shell errors, test failures, model mistakes, timeouts, or tool results.
316
+ """
317
+ focus = self._focus.get(task_id)
318
+ if not focus:
319
+ raise RuntimeValidationError("unknown active task_id")
320
+ if not isinstance(event, Mapping):
321
+ raise RuntimeValidationError("event must be an object")
322
+ if not self.memory:
323
+ return {"status": "memory_not_configured"}
324
+ normalized = dict(event)
325
+ normalized["task_id"] = task_id
326
+ normalized["project_id"] = focus["project_id"]
327
+ normalized.setdefault("scope", "workflow")
328
+ return self.memory.record_event(normalized)
329
+
330
+ def finish(self, payload: Mapping[str, Any]) -> Dict[str, Any]:
331
+ """Finalize a Runtime task through Core's evidence-bound feedback."""
332
+ task_id = payload.get("task_id") if isinstance(payload, Mapping) else None
333
+ if not isinstance(task_id, str) or task_id not in self._focus:
334
+ raise RuntimeValidationError("unknown active task_id")
335
+ focus = self._focus[task_id]
336
+ result = self.core.feedback(payload)
337
+ if self.memory:
338
+ outcome = result["learning"]["event"]["outcome"] if "learning" in result else result["event"]["outcome"]
339
+ event = result["event"]
340
+ self.memory.record_event(
341
+ {
342
+ "event_type": f"task_{outcome}",
343
+ "scope": "workflow",
344
+ "project_id": focus["project_id"],
345
+ "task_id": task_id,
346
+ "subject": focus["goal"],
347
+ "actual": event["lesson"] or outcome,
348
+ "expected": "task acceptance criteria",
349
+ "error_class": event["attribution"] if outcome != "success" else None,
350
+ }
351
+ )
352
+ self.memory.remember(
353
+ kind="episode",
354
+ subject=focus["goal"],
355
+ content=event["lesson"] or f"Task ended with {outcome}.",
356
+ scope="project",
357
+ project_id=focus["project_id"],
358
+ task_id=task_id,
359
+ source_id=event["id"],
360
+ evidence={"outcome": outcome, "attribution": event["attribution"]},
361
+ confidence=0.7 if outcome == "success" else 0.5,
362
+ metadata={"strategy_id": result["event"].get("strategy_id"), "outcome": outcome},
363
+ )
364
+ if event["attribution"] == "world" and event.get("lesson"):
365
+ self.memory.observe_pattern(
366
+ statement=event["lesson"],
367
+ subject=f"world:{focus['project_id']}",
368
+ scope="project",
369
+ project_id=focus["project_id"],
370
+ source_id=event["id"],
371
+ evidence={"task_id": task_id, "outcome": outcome},
372
+ )
373
+ del self._focus[task_id]
374
+ return {"schema": "cordis.runtime-result.v1", "learning": result}
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: cordis-runtime
3
+ Version: 0.1.1
4
+ Summary: Minimal host runtime for Cordis Core.
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: cordis-core<0.2,>=0.1.1
10
+ Requires-Dist: cordis-memory<0.2,>=0.1.1
11
+ Dynamic: license-file
12
+
13
+ # Cordis Runtime
14
+
15
+ Cordis Runtime is the minimal host bridge for Cordis Core. It does not call a
16
+ model and it does not split tasks.
17
+
18
+ The semantic boundary is explicit:
19
+
20
+ ```text
21
+ user language
22
+ -> host/main model interprets intent
23
+ -> structured Task Contract
24
+ -> Cordis Runtime + Core
25
+ -> compact model context and control signal
26
+ ```
27
+
28
+ For an agent with tool calling, the main model fills the `begin` arguments as
29
+ part of its normal turn. This does not require a second model call. A non-agent
30
+ application that accepts raw natural language must provide an optional Task
31
+ Interpreter. Task decomposition remains the responsibility of a Planner.
32
+
33
+ ## Minimal use
34
+
35
+ ```python
36
+ from cordis_core import CordisRuntime
37
+ from cordis_runtime import CordisHostRuntime
38
+
39
+ runtime = CordisHostRuntime(CordisRuntime("cordis-state.json"))
40
+
41
+ turn = runtime.begin({
42
+ "task": {
43
+ "goal": "Fix the login integration test",
44
+ "domain": "software",
45
+ "project_id": "my-app",
46
+ "strategy_id": "inspect_logs_first",
47
+ "stakes": "medium",
48
+ },
49
+ "complexity": 0.4,
50
+ "current_step": "Inspect the failing test and logs",
51
+ "constraints": ["Do not change the database schema"],
52
+ "acceptance_evidence": ["login integration test passes"],
53
+ })
54
+
55
+ # Send turn["model_context"] to the host's existing planner or main model.
56
+ ```
57
+
58
+ The host calls `check_action(...)` before an action, `observe(...)` for any
59
+ plan/tool/artifact/test/error event, and `finish(...)` with observable evidence
60
+ after execution. When constructed with a `CognitiveStore`, Runtime retrieves a
61
+ small project-safe snapshot on `begin`, supports deduplicated `query(...)`, and
62
+ persists the final episode through `finish(...)`.
63
+
64
+ ## v0.1 boundary
65
+
66
+ Runtime provides:
67
+
68
+ - structured Task Contract validation through Core;
69
+ - task-local Focus State;
70
+ - deterministic control modes;
71
+ - compact model context;
72
+ - an experimental lexical drift heuristic, never a safety or attention guarantee;
73
+ - evidence-bound feedback delegation.
74
+ - optional `CognitiveStore` retrieval, event capture, and episode write-back.
75
+
76
+ Runtime does not provide natural-language interpretation, planning, model
77
+ calls, tool execution, durable conversation memory, or an HTTP service.
78
+
79
+ `check_action` is deliberately conservative metadata: it reports lexical
80
+ overlap and obvious destructive terms. Hosts must not treat it as semantic
81
+ understanding, an authorization decision, or a complete attention-drift guard.
@@ -0,0 +1,7 @@
1
+ cordis_runtime/__init__.py,sha256=YmP3qYlZmqXWyQsk_do5_0jAS4UXMBmSOxmiiOc5EPo,212
2
+ cordis_runtime/runtime.py,sha256=bLFhRHYX1sm5nXsaXy-gTvNzGTf__foMomw6TQVQOSQ,17201
3
+ cordis_runtime-0.1.1.dist-info/licenses/LICENSE,sha256=loBxr-Xd1Q86pUdsN4__1DtAre2dUcrKUIhv3jQH_dc,1076
4
+ cordis_runtime-0.1.1.dist-info/METADATA,sha256=zMKpVB2aD_lOJrH7VwMb7ObrF-Ueg5eGu_wX4iy-ZDI,2758
5
+ cordis_runtime-0.1.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
6
+ cordis_runtime-0.1.1.dist-info/top_level.txt,sha256=yfi7NMJSO_8vidnZbTs9wqSCAWMKQNn1UNTenPimFbo,15
7
+ cordis_runtime-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cordis Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ cordis_runtime