@openlucaskaka/kagent 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +353 -0
  2. package/npm/bin/kagent-serve.js +6 -0
  3. package/npm/bin/kagent.js +6 -0
  4. package/npm/lib/App.js +524 -0
  5. package/npm/lib/app-state.js +224 -0
  6. package/npm/lib/approval-choice.js +25 -0
  7. package/npm/lib/commands.js +59 -0
  8. package/npm/lib/editor.js +188 -0
  9. package/npm/lib/ink-runner.js +41 -0
  10. package/npm/lib/kagent-home.js +39 -0
  11. package/npm/lib/launcher.js +221 -0
  12. package/npm/lib/protocol.js +33 -0
  13. package/npm/lib/provider-setup.js +139 -0
  14. package/npm/lib/python-runner.js +892 -0
  15. package/npm/lib/runtime-client.js +390 -0
  16. package/npm/lib/terminal-input.js +127 -0
  17. package/npm/lib/terminal-text.js +19 -0
  18. package/npm/lib/terminal-width.js +14 -0
  19. package/npm/lib/transcript.js +227 -0
  20. package/npm/lib/ui-components.js +247 -0
  21. package/npm/lib/update-manager.js +334 -0
  22. package/package.json +39 -0
  23. package/pyproject.toml +55 -0
  24. package/src/kagent/__init__.py +90 -0
  25. package/src/kagent/cli/__init__.py +5 -0
  26. package/src/kagent/cli/__main__.py +6 -0
  27. package/src/kagent/cli/commands.py +112 -0
  28. package/src/kagent/cli/conversation.py +127 -0
  29. package/src/kagent/cli/interactive.py +841 -0
  30. package/src/kagent/cli/main.py +685 -0
  31. package/src/kagent/cli/memory.py +460 -0
  32. package/src/kagent/cli/pending_approval.py +169 -0
  33. package/src/kagent/cli/provider.py +27 -0
  34. package/src/kagent/cli/session_commands.py +401 -0
  35. package/src/kagent/cli/stdio_runtime.py +784 -0
  36. package/src/kagent/cli/trace.py +67 -0
  37. package/src/kagent/cli/ui.py +931 -0
  38. package/src/kagent/core/__init__.py +0 -0
  39. package/src/kagent/core/agent.py +296 -0
  40. package/src/kagent/core/faults.py +11 -0
  41. package/src/kagent/core/invariants.py +47 -0
  42. package/src/kagent/core/normalization.py +81 -0
  43. package/src/kagent/core/planning.py +48 -0
  44. package/src/kagent/core/state.py +73 -0
  45. package/src/kagent/core/summary.py +64 -0
  46. package/src/kagent/core/tools.py +196 -0
  47. package/src/kagent/core/trace.py +57 -0
  48. package/src/kagent/eval/__init__.py +11 -0
  49. package/src/kagent/eval/cases.py +229 -0
  50. package/src/kagent/eval/evaluator.py +216 -0
  51. package/src/kagent/integrations/__init__.py +3 -0
  52. package/src/kagent/integrations/audit.py +95 -0
  53. package/src/kagent/integrations/backends.py +131 -0
  54. package/src/kagent/integrations/memory.py +301 -0
  55. package/src/kagent/ops/__init__.py +0 -0
  56. package/src/kagent/ops/batch.py +156 -0
  57. package/src/kagent/ops/doctor.py +255 -0
  58. package/src/kagent/ops/metrics.py +214 -0
  59. package/src/kagent/ops/release_evidence.py +877 -0
  60. package/src/kagent/ops/release_manifest.py +142 -0
  61. package/src/kagent/ops/trace_replay.py +285 -0
  62. package/src/kagent/providers/__init__.py +7 -0
  63. package/src/kagent/providers/embeddings.py +187 -0
  64. package/src/kagent/providers/llm.py +770 -0
  65. package/src/kagent/py.typed +1 -0
  66. package/src/kagent/runtime/__init__.py +28 -0
  67. package/src/kagent/runtime/action_graph.py +543 -0
  68. package/src/kagent/runtime/agent.py +2089 -0
  69. package/src/kagent/runtime/approval.py +64 -0
  70. package/src/kagent/runtime/cancellation.py +64 -0
  71. package/src/kagent/runtime/checkpoint_state.py +146 -0
  72. package/src/kagent/runtime/checkpoint_storage.py +270 -0
  73. package/src/kagent/runtime/context.py +65 -0
  74. package/src/kagent/runtime/file_transaction.py +195 -0
  75. package/src/kagent/runtime/hooks.py +74 -0
  76. package/src/kagent/runtime/metadata.py +116 -0
  77. package/src/kagent/runtime/patch_checkpoints.py +385 -0
  78. package/src/kagent/runtime/policy.py +50 -0
  79. package/src/kagent/runtime/presentation.py +205 -0
  80. package/src/kagent/runtime/redaction.py +130 -0
  81. package/src/kagent/runtime/sandbox.py +331 -0
  82. package/src/kagent/runtime/skills.py +66 -0
  83. package/src/kagent/runtime/steering.py +56 -0
  84. package/src/kagent/runtime/steps.py +255 -0
  85. package/src/kagent/runtime/task_state.py +40 -0
  86. package/src/kagent/runtime/tools.py +3532 -0
  87. package/src/kagent/runtime/types.py +240 -0
  88. package/src/kagent/runtime/workspace.py +597 -0
  89. package/src/kagent/service/__init__.py +26 -0
  90. package/src/kagent/service/__main__.py +6 -0
  91. package/src/kagent/service/active_runs.py +178 -0
  92. package/src/kagent/service/cli.py +737 -0
  93. package/src/kagent/service/contract.py +2571 -0
  94. package/src/kagent/service/errors.py +71 -0
  95. package/src/kagent/service/idempotency.py +584 -0
  96. package/src/kagent/service/router.py +884 -0
  97. package/src/kagent/service/run.py +150 -0
  98. package/src/kagent/service/runtime.py +1915 -0
  99. package/src/kagent/service/runtime_approval.py +20 -0
  100. package/src/kagent/service/runtime_cancel.py +192 -0
  101. package/src/kagent/service/runtime_lifecycle.py +229 -0
  102. package/src/kagent/service/runtime_metadata.py +21 -0
  103. package/src/kagent/service/runtime_policy.py +115 -0
  104. package/src/kagent/service/runtime_recovery.py +573 -0
  105. package/src/kagent/service/runtime_resume.py +382 -0
  106. package/src/kagent/service/runtime_resume_claim.py +116 -0
  107. package/src/kagent/service/runtime_run.py +350 -0
  108. package/src/kagent/service/runtime_status.py +2007 -0
  109. package/src/kagent/service/safety.py +139 -0
  110. package/src/kagent/service/server.py +114 -0
  111. package/src/kagent/service/status.py +233 -0
  112. package/src/kagent/service/trace_store.py +322 -0
  113. package/src/kagent/service/transport.py +24 -0
  114. package/src/kagent/utils/__init__.py +0 -0
  115. package/src/kagent/utils/config_validation.py +21 -0
  116. package/src/kagent/utils/json_output.py +23 -0
  117. package/src/kagent/utils/paths.py +473 -0
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any, Dict
5
+
6
+ from kagent.runtime.types import parse_agent_plan
7
+
8
+
9
+ def build_resumable_plan(
10
+ plan: Dict[str, Any],
11
+ pending_approval: Dict[str, Any],
12
+ ) -> Dict[str, Any] | None:
13
+ """Return the pending action and every action that follows it.
14
+
15
+ Actions before the approval boundary already completed in the persisted run.
16
+ Their dependencies are therefore removed while dependencies between remaining
17
+ actions stay intact.
18
+ """
19
+
20
+ try:
21
+ parsed_plan = parse_agent_plan(
22
+ json.dumps(plan, ensure_ascii=False, sort_keys=True)
23
+ )
24
+ except (TypeError, ValueError):
25
+ return None
26
+
27
+ pending_action_id = str(pending_approval.get("id", "")).strip()
28
+ raw_actions = plan.get("actions")
29
+ if not pending_action_id or not isinstance(raw_actions, list):
30
+ return None
31
+
32
+ matching_indexes = [
33
+ index
34
+ for index, action in enumerate(raw_actions)
35
+ if isinstance(action, dict) and action.get("id") == pending_action_id
36
+ ]
37
+ if len(matching_indexes) != 1:
38
+ return None
39
+
40
+ pending_index = matching_indexes[0]
41
+ if raw_actions[pending_index] != pending_approval:
42
+ return None
43
+
44
+ completed_action_ids = {
45
+ action.id for action in parsed_plan.actions[:pending_index]
46
+ }
47
+ remaining_actions = []
48
+ for action in parsed_plan.actions[pending_index:]:
49
+ payload = action.to_dict()
50
+ remaining_dependencies = [
51
+ dependency
52
+ for dependency in action.depends_on
53
+ if dependency not in completed_action_ids
54
+ ]
55
+ if remaining_dependencies:
56
+ payload["depends_on"] = remaining_dependencies
57
+ else:
58
+ payload.pop("depends_on", None)
59
+ remaining_actions.append(payload)
60
+
61
+ resumable_plan: Dict[str, Any] = {"actions": remaining_actions}
62
+ if parsed_plan.final_answer:
63
+ resumable_plan["final_answer"] = parsed_plan.final_answer
64
+ return resumable_plan
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from threading import Event, Lock
5
+ from typing import Any, Callable, Dict, Mapping, Optional
6
+
7
+ ExternalCancellationProbe = Callable[[], Optional[Mapping[str, Any]]]
8
+
9
+
10
+ class RuntimeCancellationToken:
11
+ """Thread-safe cooperative cancellation shared across runtime boundaries."""
12
+
13
+ def __init__(
14
+ self,
15
+ *,
16
+ external_cancellation_probe: ExternalCancellationProbe | None = None,
17
+ ) -> None:
18
+ self._event = Event()
19
+ self._lock = Lock()
20
+ self._reason = ""
21
+ self._cancelled_at = ""
22
+ self._external_cancellation_probe = external_cancellation_probe
23
+
24
+ def cancel(self, reason: str = "", *, cancelled_at: str = "") -> bool:
25
+ normalized_reason = reason.strip()
26
+ normalized_cancelled_at = cancelled_at.strip()
27
+ with self._lock:
28
+ if self._event.is_set():
29
+ return False
30
+ self._reason = normalized_reason
31
+ self._cancelled_at = (
32
+ normalized_cancelled_at or datetime.now(timezone.utc).isoformat()
33
+ )
34
+ self._event.set()
35
+ return True
36
+
37
+ def is_cancelled(self) -> bool:
38
+ self._sync_external_cancellation()
39
+ return self._event.is_set()
40
+
41
+ def snapshot(self) -> Dict[str, str]:
42
+ self._sync_external_cancellation()
43
+ with self._lock:
44
+ return {
45
+ "cancelled": str(self._event.is_set()).lower(),
46
+ "reason": self._reason,
47
+ "cancelled_at": self._cancelled_at,
48
+ }
49
+
50
+ def _sync_external_cancellation(self) -> None:
51
+ if self._event.is_set() or self._external_cancellation_probe is None:
52
+ return
53
+ try:
54
+ cancellation = self._external_cancellation_probe()
55
+ except Exception:
56
+ return
57
+ if not cancellation:
58
+ return
59
+ reason = cancellation.get("reason", cancellation.get("cancel_reason", ""))
60
+ cancelled_at = cancellation.get("cancelled_at", "")
61
+ self.cancel(
62
+ str(reason) if reason is not None else "",
63
+ cancelled_at=str(cancelled_at) if cancelled_at is not None else "",
64
+ )
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from datetime import datetime, timezone
5
+ from typing import Any, Callable, Dict, List, TypedDict
6
+
7
+ from kagent.runtime.cancellation import RuntimeCancellationToken
8
+ from kagent.runtime.policy import RuntimePolicy
9
+ from kagent.runtime.redaction import redact_runtime_text
10
+ from kagent.runtime.steering import RuntimeSteeringBuffer
11
+ from kagent.runtime.tools import RuntimeToolSpec
12
+
13
+ _CHECKPOINT_SECRET_KEY_PARTS = (
14
+ "api_key",
15
+ "apikey",
16
+ "authorization",
17
+ "bearer",
18
+ "password",
19
+ "secret",
20
+ "token",
21
+ )
22
+
23
+ RuntimeEventSink = Callable[[Dict[str, Any]], None]
24
+
25
+
26
+ class RuntimeGraphState(TypedDict, total=False):
27
+ goal: str
28
+ run_id: str
29
+ max_iterations: int
30
+ approved_action_ids: List[str]
31
+ metadata: Dict[str, str]
32
+ tags: List[str]
33
+ started_at: str
34
+ initial_events: List[Dict[str, Any]]
35
+ initial_hook_failure_count: int
36
+ initial_planner: Dict[str, Any]
37
+ initial_progress_events: List[Dict[str, Any]]
38
+ initial_progress_event_sink_failure_count: int
39
+ initial_action_prepared: Dict[str, Any]
40
+ initial_action_phase: Dict[str, Any]
41
+ initial_action_outcome: Dict[str, Any]
42
+ result: Dict[str, Any]
43
+ graph_phases: List[Dict[str, str]]
44
+
45
+
46
+ class RuntimeGraphContext(TypedDict, total=False):
47
+ goal: str
48
+ provider: Any
49
+ policy: RuntimePolicy
50
+ tools: Dict[str, RuntimeToolSpec]
51
+ cancellation_token: RuntimeCancellationToken
52
+ steering_buffer: RuntimeSteeringBuffer
53
+ event_sink: RuntimeEventSink
54
+ hooks: List[Any]
55
+ runtime_workspace_dir: str
56
+ redis_url: str
57
+ milvus_url: str
58
+ embedding_base_url: str
59
+ embedding_api_key: str
60
+ embedding_model: str
61
+ embedding_timeout_seconds: float
62
+ embedding_max_retries: int
63
+ embedding_retry_backoff_seconds: float
64
+ external_backend_timeout_seconds: float
65
+ stream_answers: bool
66
+ planner_plan_cache: Dict[str, Dict[str, Any]]
67
+ prepared_action_cache: Dict[str, Dict[str, Any]]
68
+ executing_action_cache: Dict[str, Dict[str, Any]]
69
+
70
+
71
+ def checkpoint_safe_value(value: Any) -> Any:
72
+ if value is None or isinstance(value, (bool, int, float)):
73
+ return value
74
+ if isinstance(value, str):
75
+ return redact_runtime_text(value)
76
+ if isinstance(value, dict):
77
+ return {
78
+ redact_runtime_text(str(key)): checkpoint_safe_value(item)
79
+ for key, item in value.items()
80
+ }
81
+ if isinstance(value, (list, tuple)):
82
+ return [checkpoint_safe_value(item) for item in value]
83
+ return f"[unsupported {type(value).__name__}]"
84
+
85
+
86
+ def checkpoint_plan_projection(value: Any, *, key: str = "") -> tuple[Any, bool]:
87
+ if key and any(part in key.lower() for part in _CHECKPOINT_SECRET_KEY_PARTS):
88
+ return "[REDACTED]", True
89
+ if value is None or isinstance(value, (bool, int, float)):
90
+ return value, False
91
+ if isinstance(value, str):
92
+ redacted = redact_runtime_text(value)
93
+ return redacted, redacted != value
94
+ if isinstance(value, dict):
95
+ projection: Dict[str, Any] = {}
96
+ changed = False
97
+ for raw_key, item in value.items():
98
+ projected_key = str(raw_key)
99
+ projected_item, item_changed = checkpoint_plan_projection(
100
+ item,
101
+ key=projected_key,
102
+ )
103
+ projection[projected_key] = projected_item
104
+ changed = changed or item_changed
105
+ return projection, changed
106
+ if isinstance(value, (list, tuple)):
107
+ projection = []
108
+ changed = False
109
+ for item in value:
110
+ projected_item, item_changed = checkpoint_plan_projection(item)
111
+ projection.append(projected_item)
112
+ changed = changed or item_changed
113
+ return projection, changed
114
+ return f"[unsupported {type(value).__name__}]", True
115
+
116
+
117
+ def append_graph_phase(
118
+ phases: List[Dict[str, str]] | None,
119
+ node: str,
120
+ started_at: str,
121
+ started_timer: float,
122
+ ) -> List[Dict[str, str]]:
123
+ return [
124
+ *(phases or []),
125
+ {
126
+ "node": node,
127
+ "status": "ok",
128
+ **timing_fields(started_at, started_timer),
129
+ },
130
+ ]
131
+
132
+
133
+ def utc_timestamp() -> str:
134
+ return datetime.now(timezone.utc).isoformat()
135
+
136
+
137
+ def timing_fields(started_at: str, started_timer: float) -> Dict[str, str]:
138
+ return {
139
+ "started_at": started_at,
140
+ "completed_at": utc_timestamp(),
141
+ "duration_seconds": duration_since(started_timer),
142
+ }
143
+
144
+
145
+ def duration_since(started_at: float) -> str:
146
+ return f"{time.perf_counter() - started_at:.4f}"
@@ -0,0 +1,270 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import hmac
5
+ import json
6
+ import os
7
+ import re
8
+ import secrets
9
+ import tempfile
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ DIRECTORY_MODE = 0o700
15
+ FILE_MODE = 0o600
16
+ MANIFEST_VERSION = 1
17
+ _SIGNING_KEY_NAME = ".checkpoint-signing-key"
18
+ _CHECKPOINT_ID_PATTERN = re.compile(
19
+ r"^\d{8}T\d{6}\.\d{6}Z-[0-9a-f]{12}$"
20
+ )
21
+ _SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
22
+
23
+
24
+ def new_checkpoint_id() -> str:
25
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S.%fZ")
26
+ return f"{timestamp}-{secrets.token_hex(6)}"
27
+
28
+
29
+ def validate_checkpoint_id(checkpoint_id: str) -> None:
30
+ if not isinstance(checkpoint_id, str) or not _CHECKPOINT_ID_PATTERN.fullmatch(
31
+ checkpoint_id
32
+ ):
33
+ raise ValueError("invalid checkpoint_id")
34
+
35
+
36
+ def resolve_checkpoint_target(workspace_root: Path, relative_path: Any) -> Path:
37
+ if not isinstance(relative_path, str):
38
+ raise ValueError("checkpoint manifest is invalid")
39
+ candidate = Path(relative_path)
40
+ if candidate.is_absolute() or any(
41
+ part in {"", ".", ".."} for part in candidate.parts
42
+ ):
43
+ raise ValueError("checkpoint path must stay inside the workspace")
44
+ target = workspace_root.joinpath(*candidate.parts)
45
+ try:
46
+ target.relative_to(workspace_root)
47
+ except ValueError as exc:
48
+ raise ValueError("checkpoint path must stay inside the workspace") from exc
49
+ return target
50
+
51
+
52
+ def ensure_store_layout(
53
+ state_root: Path,
54
+ workspace_directory: Path,
55
+ checkpoint_directory: Path,
56
+ ) -> None:
57
+ reject_existing_symlink_chain(state_root)
58
+ directories = (
59
+ state_root,
60
+ workspace_directory,
61
+ workspace_directory / "checkpoints",
62
+ checkpoint_directory,
63
+ )
64
+ for directory in directories:
65
+ if directory.exists() and directory.is_symlink():
66
+ raise ValueError("checkpoint path must not contain symlinks")
67
+ directory.mkdir(parents=True, exist_ok=True)
68
+ directory.chmod(DIRECTORY_MODE)
69
+
70
+
71
+ def reject_existing_symlink_chain(path: Path) -> None:
72
+ absolute_path = path.absolute()
73
+ current = Path(absolute_path.anchor)
74
+ for part in absolute_path.parts[1:]:
75
+ current = current / part
76
+ if current.exists() and current.is_symlink():
77
+ raise ValueError("checkpoint path must not contain symlinks")
78
+
79
+
80
+ def reject_symlink(path: Path) -> None:
81
+ if path.is_symlink():
82
+ raise ValueError("checkpoint path must not contain symlinks")
83
+
84
+
85
+ def remove_checkpoint_directory(checkpoint_directory: Path) -> None:
86
+ reject_symlink(checkpoint_directory)
87
+ for path in checkpoint_directory.iterdir():
88
+ reject_symlink(path)
89
+ if not path.is_file():
90
+ raise ValueError("checkpoint directory contains an unexpected entry")
91
+ path.unlink()
92
+ checkpoint_directory.rmdir()
93
+
94
+
95
+ def atomic_write_text(target: Path, content: str) -> None:
96
+ descriptor, temporary_name = tempfile.mkstemp(
97
+ prefix=f".{target.name}.",
98
+ suffix=".tmp",
99
+ dir=target.parent,
100
+ )
101
+ temporary_path = Path(temporary_name)
102
+ try:
103
+ os.chmod(temporary_path, FILE_MODE)
104
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
105
+ descriptor = -1
106
+ handle.write(content)
107
+ handle.flush()
108
+ os.fsync(handle.fileno())
109
+ temporary_path.replace(target)
110
+ _fsync_directory(target.parent)
111
+ except Exception:
112
+ if descriptor != -1:
113
+ os.close(descriptor)
114
+ temporary_path.unlink(missing_ok=True)
115
+ raise
116
+
117
+
118
+ def write_signed_manifest(
119
+ target: Path,
120
+ manifest: dict[str, Any],
121
+ state_root: Path,
122
+ ) -> None:
123
+ signing_key = load_or_create_signing_key(state_root)
124
+ atomic_write_text(
125
+ target,
126
+ json.dumps(
127
+ signed_manifest_payload(manifest, signing_key),
128
+ ensure_ascii=False,
129
+ separators=(",", ":"),
130
+ ),
131
+ )
132
+
133
+
134
+ def read_signed_manifest(
135
+ manifest_path: Path,
136
+ state_root: Path,
137
+ checkpoint_id: str,
138
+ ) -> dict[str, Any]:
139
+ try:
140
+ envelope = json.loads(manifest_path.read_text(encoding="utf-8"))
141
+ except (OSError, json.JSONDecodeError) as exc:
142
+ raise ValueError("checkpoint manifest is invalid") from exc
143
+ if (
144
+ not isinstance(envelope, dict)
145
+ or set(envelope) != {"manifest", "hmac_sha256"}
146
+ or not isinstance(envelope["manifest"], dict)
147
+ or not isinstance(envelope["hmac_sha256"], str)
148
+ ):
149
+ raise ValueError("checkpoint manifest is invalid")
150
+ manifest = envelope["manifest"]
151
+ expected_hmac = _manifest_hmac(manifest, read_signing_key(state_root))
152
+ if not hmac.compare_digest(envelope["hmac_sha256"], expected_hmac):
153
+ raise ValueError("checkpoint manifest integrity check failed")
154
+ _validate_manifest(manifest, checkpoint_id)
155
+ return manifest
156
+
157
+
158
+ def signed_manifest_payload(
159
+ manifest: dict[str, Any],
160
+ signing_key: bytes,
161
+ ) -> dict[str, Any]:
162
+ return {
163
+ "manifest": manifest,
164
+ "hmac_sha256": _manifest_hmac(manifest, signing_key),
165
+ }
166
+
167
+
168
+ def load_or_create_signing_key(state_root: Path) -> bytes:
169
+ key_path = state_root / _SIGNING_KEY_NAME
170
+ reject_symlink(key_path)
171
+ if key_path.exists():
172
+ return read_signing_key(state_root)
173
+ signing_key = secrets.token_bytes(32)
174
+ try:
175
+ descriptor = os.open(
176
+ key_path,
177
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL,
178
+ FILE_MODE,
179
+ )
180
+ except FileExistsError:
181
+ return read_signing_key(state_root)
182
+ try:
183
+ os.write(descriptor, signing_key)
184
+ os.fsync(descriptor)
185
+ finally:
186
+ os.close(descriptor)
187
+ _fsync_directory(state_root)
188
+ return signing_key
189
+
190
+
191
+ def read_signing_key(state_root: Path) -> bytes:
192
+ key_path = state_root / _SIGNING_KEY_NAME
193
+ reject_existing_symlink_chain(state_root)
194
+ reject_symlink(key_path)
195
+ if not key_path.is_file():
196
+ raise ValueError("checkpoint signing key is missing")
197
+ signing_key = key_path.read_bytes()
198
+ if len(signing_key) != 32:
199
+ raise ValueError("checkpoint signing key is invalid")
200
+ return signing_key
201
+
202
+
203
+ def _manifest_hmac(manifest: dict[str, Any], signing_key: bytes) -> str:
204
+ canonical = json.dumps(
205
+ manifest,
206
+ ensure_ascii=False,
207
+ separators=(",", ":"),
208
+ sort_keys=True,
209
+ ).encode("utf-8")
210
+ return hmac.new(signing_key, canonical, hashlib.sha256).hexdigest()
211
+
212
+
213
+ def _validate_manifest(manifest: Any, checkpoint_id: str) -> None:
214
+ if (
215
+ not isinstance(manifest, dict)
216
+ or manifest.get("version") != MANIFEST_VERSION
217
+ or manifest.get("checkpoint_id") != checkpoint_id
218
+ or manifest.get("status") not in {"prepared", "committed"}
219
+ or not isinstance(manifest.get("created_at"), str)
220
+ or not isinstance(manifest.get("entries"), list)
221
+ or not manifest["entries"]
222
+ ):
223
+ raise ValueError("checkpoint manifest is invalid")
224
+ paths = []
225
+ for entry in manifest["entries"]:
226
+ if not isinstance(entry, dict) or set(entry) != {"path", "before", "after"}:
227
+ raise ValueError("checkpoint manifest is invalid")
228
+ if not isinstance(entry["path"], str):
229
+ raise ValueError("checkpoint manifest is invalid")
230
+ _validate_state(entry["before"])
231
+ _validate_state(entry["after"])
232
+ paths.append(entry["path"])
233
+ if len(paths) != len(set(paths)):
234
+ raise ValueError("checkpoint manifest contains duplicate paths")
235
+
236
+
237
+ def _validate_state(state: Any) -> None:
238
+ if not isinstance(state, dict) or set(state) != {
239
+ "exists",
240
+ "sha256",
241
+ "mode",
242
+ "blob",
243
+ }:
244
+ raise ValueError("checkpoint manifest is invalid")
245
+ exists = state["exists"]
246
+ mode = state["mode"]
247
+ blob = state["blob"]
248
+ sha256 = state["sha256"]
249
+ if not isinstance(exists, bool):
250
+ raise ValueError("checkpoint manifest is invalid")
251
+ if isinstance(mode, bool) or not isinstance(mode, int) or not 0 <= mode <= 0o7777:
252
+ raise ValueError("checkpoint manifest is invalid")
253
+ if not isinstance(blob, str) or not isinstance(sha256, str):
254
+ raise ValueError("checkpoint manifest is invalid")
255
+ if exists:
256
+ if Path(blob).name != blob or not blob or not _SHA256_PATTERN.fullmatch(sha256):
257
+ raise ValueError("checkpoint manifest is invalid")
258
+ elif blob or sha256:
259
+ raise ValueError("checkpoint manifest is invalid")
260
+
261
+
262
+ def _fsync_directory(directory: Path) -> None:
263
+ flags = os.O_RDONLY
264
+ if hasattr(os, "O_DIRECTORY"):
265
+ flags |= os.O_DIRECTORY
266
+ descriptor = os.open(directory, flags)
267
+ try:
268
+ os.fsync(descriptor)
269
+ finally:
270
+ os.close(descriptor)
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict
5
+
6
+
7
+ @dataclass
8
+ class RuntimeContextStats:
9
+ truncated_string_count: int = 0
10
+ truncated_chars: int = 0
11
+ omitted_artifact_content_count: int = 0
12
+
13
+
14
+ class RuntimeContextManager:
15
+ def __init__(self, *, max_string_chars: int = 500) -> None:
16
+ if max_string_chars < 1:
17
+ raise ValueError("max_string_chars must be positive")
18
+ self.max_string_chars = max_string_chars
19
+ self._stats = RuntimeContextStats()
20
+
21
+ def compact_observation_output(self, output: Any) -> Any:
22
+ if isinstance(output, dict) and str(output.get("artifact_id", "")).strip():
23
+ self._stats.omitted_artifact_content_count += 1
24
+ return self._artifact_prompt_metadata(output)
25
+ return self.compact_value(output)
26
+
27
+ def compact_value(self, value: Any) -> Any:
28
+ if isinstance(value, str):
29
+ if len(value) <= self.max_string_chars:
30
+ return value
31
+ self._stats.truncated_string_count += 1
32
+ truncated_chars = len(value) - self.max_string_chars
33
+ self._stats.truncated_chars += truncated_chars
34
+ return {
35
+ "text_prefix": value[: self.max_string_chars],
36
+ "original_chars": len(value),
37
+ "truncated_chars": truncated_chars,
38
+ }
39
+ if isinstance(value, dict):
40
+ return {key: self.compact_value(item) for key, item in value.items()}
41
+ if isinstance(value, list):
42
+ return [self.compact_value(item) for item in value]
43
+ return value
44
+
45
+ def report(self) -> Dict[str, Any]:
46
+ return {
47
+ "strategy": "runtime_context_manager",
48
+ "artifact_content_omitted": True,
49
+ "max_string_chars": str(self.max_string_chars),
50
+ "long_string_shape": "text_prefix/original_chars/truncated_chars",
51
+ "truncated_string_count": str(self._stats.truncated_string_count),
52
+ "truncated_chars": str(self._stats.truncated_chars),
53
+ "omitted_artifact_content_count": str(
54
+ self._stats.omitted_artifact_content_count
55
+ ),
56
+ }
57
+
58
+ def _artifact_prompt_metadata(self, output: Dict[str, Any]) -> Dict[str, Any]:
59
+ metadata = {
60
+ key: output[key]
61
+ for key in ["artifact_id", "title", "kind", "format", "tags", "bytes"]
62
+ if key in output
63
+ }
64
+ metadata["content_omitted"] = True
65
+ return metadata