akernel-runtime 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.
- akernel_runtime-0.1.0.dist-info/METADATA +270 -0
- akernel_runtime-0.1.0.dist-info/RECORD +40 -0
- akernel_runtime-0.1.0.dist-info/WHEEL +5 -0
- akernel_runtime-0.1.0.dist-info/entry_points.txt +2 -0
- akernel_runtime-0.1.0.dist-info/licenses/LICENSE +201 -0
- akernel_runtime-0.1.0.dist-info/licenses/NOTICE +4 -0
- akernel_runtime-0.1.0.dist-info/top_level.txt +1 -0
- context_kernel/__init__.py +4 -0
- context_kernel/__main__.py +5 -0
- context_kernel/agent_reports.py +188 -0
- context_kernel/benchmarks.py +493 -0
- context_kernel/budget.py +72 -0
- context_kernel/cli.py +2953 -0
- context_kernel/context.py +161 -0
- context_kernel/evals.py +347 -0
- context_kernel/global_memory.py +126 -0
- context_kernel/loop.py +1617 -0
- context_kernel/marketplace.py +194 -0
- context_kernel/marketplace_data/skills/context_budget.json +27 -0
- context_kernel/marketplace_data/skills/context_compaction.json +27 -0
- context_kernel/marketplace_data/skills/edit_file.json +27 -0
- context_kernel/marketplace_data/skills/index.json +66 -0
- context_kernel/marketplace_data/skills/long_task_planning.json +27 -0
- context_kernel/marketplace_data/skills/multi_file_bugfix.json +28 -0
- context_kernel/memory.py +515 -0
- context_kernel/models.py +144 -0
- context_kernel/planner.py +155 -0
- context_kernel/policy.py +271 -0
- context_kernel/project.py +317 -0
- context_kernel/providers.py +1264 -0
- context_kernel/report_costs.py +375 -0
- context_kernel/runner.py +78 -0
- context_kernel/skills.py +318 -0
- context_kernel/state_writer.py +108 -0
- context_kernel/storage.py +171 -0
- context_kernel/tasks.py +549 -0
- context_kernel/text.py +42 -0
- context_kernel/tokenizer.py +22 -0
- context_kernel/tools.py +544 -0
- context_kernel/verifier.py +77 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .context import ContextBuilder
|
|
6
|
+
from .policy import assess_request_policy
|
|
7
|
+
from .storage import Workspace
|
|
8
|
+
from .tokenizer import estimate_tokens
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
CODE_TERMS = {
|
|
12
|
+
"code",
|
|
13
|
+
"cli",
|
|
14
|
+
"debug",
|
|
15
|
+
"edit",
|
|
16
|
+
"file",
|
|
17
|
+
"fix",
|
|
18
|
+
"implement",
|
|
19
|
+
"patch",
|
|
20
|
+
"refactor",
|
|
21
|
+
"test",
|
|
22
|
+
}
|
|
23
|
+
RESEARCH_TERMS = {"compare", "current", "latest", "news", "price", "research", "today", "verify"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ExecutionPlanner:
|
|
27
|
+
def __init__(self, workspace: Workspace):
|
|
28
|
+
self.workspace = workspace
|
|
29
|
+
|
|
30
|
+
def plan(
|
|
31
|
+
self,
|
|
32
|
+
request: str,
|
|
33
|
+
total_budget: int | None,
|
|
34
|
+
profile: str = "balanced",
|
|
35
|
+
task_id: str | None = None,
|
|
36
|
+
resume: bool = False,
|
|
37
|
+
) -> dict[str, Any]:
|
|
38
|
+
comparison = ContextBuilder(self.workspace).compare(
|
|
39
|
+
request,
|
|
40
|
+
total_budget,
|
|
41
|
+
profile,
|
|
42
|
+
task_id=task_id,
|
|
43
|
+
resume=resume,
|
|
44
|
+
)
|
|
45
|
+
packet = comparison["kernel"]["packet"]
|
|
46
|
+
route = classify_route(request, packet)
|
|
47
|
+
policy = assess_request_policy(self.workspace, request)
|
|
48
|
+
warnings = list(packet.get("omissions", []))
|
|
49
|
+
if packet["budget"]["over_budget"]:
|
|
50
|
+
warnings.append("Do not execute provider call until context is reduced or budget is raised.")
|
|
51
|
+
warnings.extend(policy["warnings"])
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"request": request,
|
|
55
|
+
"profile": comparison["profile"],
|
|
56
|
+
"route": route,
|
|
57
|
+
"task": summarize_task(packet),
|
|
58
|
+
"policy": policy,
|
|
59
|
+
"budget": packet["budget"],
|
|
60
|
+
"selection": {
|
|
61
|
+
"memory": summarize_memory(packet),
|
|
62
|
+
"skills": summarize_skills(packet),
|
|
63
|
+
},
|
|
64
|
+
"savings": comparison["savings"],
|
|
65
|
+
"actions": planned_actions(packet),
|
|
66
|
+
"warnings": warnings,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def classify_route(request: str, packet: dict[str, Any]) -> dict[str, Any]:
|
|
71
|
+
request_terms = {term.strip(".,:;!?()[]{}\"'").casefold() for term in request.split()}
|
|
72
|
+
has_code = bool(request_terms.intersection(CODE_TERMS))
|
|
73
|
+
has_research = bool(request_terms.intersection(RESEARCH_TERMS))
|
|
74
|
+
selected_items = len(packet.get("memory", [])) + len(packet.get("skills", []))
|
|
75
|
+
request_tokens = estimate_tokens(request)
|
|
76
|
+
|
|
77
|
+
if has_research:
|
|
78
|
+
mode = "research_or_verification"
|
|
79
|
+
elif has_code:
|
|
80
|
+
mode = "code_or_file_work"
|
|
81
|
+
else:
|
|
82
|
+
mode = "direct_answer"
|
|
83
|
+
|
|
84
|
+
if request_tokens > 180 or selected_items >= 5:
|
|
85
|
+
complexity = "high"
|
|
86
|
+
elif request_tokens > 80 or selected_items >= 3:
|
|
87
|
+
complexity = "medium"
|
|
88
|
+
else:
|
|
89
|
+
complexity = "low"
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"mode": mode,
|
|
93
|
+
"complexity": complexity,
|
|
94
|
+
"reason": route_reason(mode, complexity, selected_items),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def summarize_memory(packet: dict[str, Any]) -> list[dict[str, Any]]:
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
"id": item["record"]["id"],
|
|
102
|
+
"kind": item["record"]["kind"],
|
|
103
|
+
"score": item["score"],
|
|
104
|
+
"reason": item["reason"],
|
|
105
|
+
"estimated_tokens": item["estimated_tokens"],
|
|
106
|
+
}
|
|
107
|
+
for item in packet.get("memory", [])
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def summarize_skills(packet: dict[str, Any]) -> list[dict[str, Any]]:
|
|
112
|
+
return [
|
|
113
|
+
{
|
|
114
|
+
"id": item["contract"]["id"],
|
|
115
|
+
"name": item["contract"]["name"],
|
|
116
|
+
"level": item["level"],
|
|
117
|
+
"score": item["score"],
|
|
118
|
+
"reason": item["reason"],
|
|
119
|
+
"estimated_tokens": item["estimated_tokens"],
|
|
120
|
+
}
|
|
121
|
+
for item in packet.get("skills", [])
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def summarize_task(packet: dict[str, Any]) -> dict[str, Any]:
|
|
126
|
+
task = packet.get("task", {})
|
|
127
|
+
brief = task.get("brief")
|
|
128
|
+
if not brief:
|
|
129
|
+
return {"resume": False}
|
|
130
|
+
return {
|
|
131
|
+
"resume": True,
|
|
132
|
+
"id": brief["task"]["id"],
|
|
133
|
+
"title": brief["task"]["title"],
|
|
134
|
+
"status": brief["task"]["status"],
|
|
135
|
+
"estimated_tokens": brief.get("estimated_tokens", 0),
|
|
136
|
+
"plan": brief.get("plan"),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def planned_actions(packet: dict[str, Any]) -> list[str]:
|
|
141
|
+
actions = [
|
|
142
|
+
"Assemble the minimal context packet shown in this plan.",
|
|
143
|
+
"Call the selected provider only after budget checks pass.",
|
|
144
|
+
"Write a trace with selected memory, selected skills, provider usage, and verifier checks.",
|
|
145
|
+
]
|
|
146
|
+
if packet["budget"]["over_budget"]:
|
|
147
|
+
return [
|
|
148
|
+
"Stop before provider execution because the packet is over budget.",
|
|
149
|
+
"Reduce selected memory or skill levels, or choose a larger profile.",
|
|
150
|
+
]
|
|
151
|
+
return actions
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def route_reason(mode: str, complexity: str, selected_items: int) -> str:
|
|
155
|
+
return f"mode={mode}; complexity={complexity}; selected_context_items={selected_items}"
|
context_kernel/policy.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shlex
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .storage import DEFAULT_COMMAND_POLICY, Workspace
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
FILE_OPERATIONS = {"read", "write", "delete"}
|
|
11
|
+
DESTRUCTIVE_FILE_OPERATIONS = {"delete"}
|
|
12
|
+
SENSITIVE_FILENAMES = {".env", ".env.example"}
|
|
13
|
+
PROTECTED_DIRECTORIES = {".venv", "__pycache__"}
|
|
14
|
+
PROTECTED_STATE_FILES = {"memory.sqlite3", "config.json"}
|
|
15
|
+
DESTRUCTIVE_COMMAND_TERMS = {
|
|
16
|
+
"del",
|
|
17
|
+
"erase",
|
|
18
|
+
"format",
|
|
19
|
+
"git checkout",
|
|
20
|
+
"git clean",
|
|
21
|
+
"git reset",
|
|
22
|
+
"move-item",
|
|
23
|
+
"rd",
|
|
24
|
+
"remove-item",
|
|
25
|
+
"rm",
|
|
26
|
+
"rmdir",
|
|
27
|
+
}
|
|
28
|
+
SAFE_COMMAND_ROOTS = set(DEFAULT_COMMAND_POLICY["allowed_roots"])
|
|
29
|
+
DANGEROUS_REQUEST_TERMS = {"delete", "remove", "reset", "wipe", "destroy", "清空", "删除", "重置"}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def check_file_policy(
|
|
33
|
+
workspace: Workspace,
|
|
34
|
+
operation: str,
|
|
35
|
+
target: Path | str,
|
|
36
|
+
*,
|
|
37
|
+
allow_destructive: bool = False,
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
operation = operation.lower()
|
|
40
|
+
if operation not in FILE_OPERATIONS:
|
|
41
|
+
raise ValueError(f"Unsupported file operation: {operation}. Expected one of: {', '.join(sorted(FILE_OPERATIONS))}")
|
|
42
|
+
|
|
43
|
+
target_path = Path(target)
|
|
44
|
+
resolved = resolve_target(workspace, target_path)
|
|
45
|
+
reasons: list[str] = []
|
|
46
|
+
allowed = True
|
|
47
|
+
|
|
48
|
+
if not is_relative_to(resolved, workspace.root):
|
|
49
|
+
allowed = False
|
|
50
|
+
reasons.append("target is outside the workspace root")
|
|
51
|
+
|
|
52
|
+
if target_path.name.lower() in SENSITIVE_FILENAMES or resolved.name.lower() in SENSITIVE_FILENAMES:
|
|
53
|
+
allowed = False
|
|
54
|
+
reasons.append("target is a sensitive environment file")
|
|
55
|
+
|
|
56
|
+
relative_parts = {part.lower() for part in safe_relative_parts(workspace, resolved)}
|
|
57
|
+
if relative_parts.intersection(PROTECTED_DIRECTORIES):
|
|
58
|
+
allowed = False
|
|
59
|
+
reasons.append("target is inside a protected generated directory")
|
|
60
|
+
|
|
61
|
+
if ".akernel" in relative_parts and (operation != "read" or resolved.name in PROTECTED_STATE_FILES):
|
|
62
|
+
allowed = False
|
|
63
|
+
reasons.append("target touches protected Context Kernel state")
|
|
64
|
+
|
|
65
|
+
if operation in DESTRUCTIVE_FILE_OPERATIONS and not allow_destructive:
|
|
66
|
+
allowed = False
|
|
67
|
+
reasons.append("destructive file operation requires --allow-destructive")
|
|
68
|
+
|
|
69
|
+
return policy_result(
|
|
70
|
+
kind="file",
|
|
71
|
+
allowed=allowed,
|
|
72
|
+
subject=str(resolved),
|
|
73
|
+
operation=operation,
|
|
74
|
+
reasons=reasons,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def check_batch_file_policy(
|
|
79
|
+
workspace: Workspace,
|
|
80
|
+
edits: list[dict[str, Any]],
|
|
81
|
+
*,
|
|
82
|
+
allow_destructive: bool = False,
|
|
83
|
+
) -> dict[str, Any]:
|
|
84
|
+
if not edits:
|
|
85
|
+
raise ValueError("Batch file policy requires at least one edit.")
|
|
86
|
+
|
|
87
|
+
allowed = True
|
|
88
|
+
reasons: list[str] = []
|
|
89
|
+
items: list[dict[str, Any]] = []
|
|
90
|
+
subjects: list[str] = []
|
|
91
|
+
for index, edit in enumerate(edits, start=1):
|
|
92
|
+
path = edit.get("path")
|
|
93
|
+
if not isinstance(path, str) or not path.strip():
|
|
94
|
+
raise ValueError(f"Batch edit {index} is missing a valid path.")
|
|
95
|
+
policy = check_file_policy(
|
|
96
|
+
workspace,
|
|
97
|
+
"write",
|
|
98
|
+
path,
|
|
99
|
+
allow_destructive=allow_destructive,
|
|
100
|
+
)
|
|
101
|
+
items.append(policy)
|
|
102
|
+
subjects.append(str(policy["subject"]))
|
|
103
|
+
if not policy["allowed"]:
|
|
104
|
+
allowed = False
|
|
105
|
+
reasons.extend([f"edit {index}: {reason}" for reason in policy["reasons"]])
|
|
106
|
+
|
|
107
|
+
result = policy_result(
|
|
108
|
+
kind="batch_file",
|
|
109
|
+
allowed=allowed,
|
|
110
|
+
subject=", ".join(subjects),
|
|
111
|
+
operation="batch_patch",
|
|
112
|
+
reasons=reasons,
|
|
113
|
+
)
|
|
114
|
+
result["items"] = items
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def check_command_policy(
|
|
119
|
+
command: str,
|
|
120
|
+
*,
|
|
121
|
+
workspace: Workspace | None = None,
|
|
122
|
+
allow_destructive: bool = False,
|
|
123
|
+
) -> dict[str, Any]:
|
|
124
|
+
normalized = " ".join(command.strip().split())
|
|
125
|
+
if not normalized:
|
|
126
|
+
raise ValueError("Command cannot be empty.")
|
|
127
|
+
|
|
128
|
+
lower = normalized.casefold()
|
|
129
|
+
reasons: list[str] = []
|
|
130
|
+
allowed = True
|
|
131
|
+
root = command_root(normalized)
|
|
132
|
+
root_candidates = command_root_candidates(normalized)
|
|
133
|
+
command_policy = command_policy_settings(workspace)
|
|
134
|
+
allowed_roots = set(command_policy["allowed_roots"])
|
|
135
|
+
blocked_terms = set(command_policy["blocked_terms"])
|
|
136
|
+
|
|
137
|
+
if root and not allowed_roots.intersection(root_candidates):
|
|
138
|
+
allowed = False
|
|
139
|
+
reasons.append(f"command root is not in the workspace safe list: {root}")
|
|
140
|
+
|
|
141
|
+
matched_destructive = matched_destructive_terms(lower, extra_terms=blocked_terms)
|
|
142
|
+
if matched_destructive and not allow_destructive:
|
|
143
|
+
allowed = False
|
|
144
|
+
reasons.append("destructive command term requires --allow-destructive: " + ", ".join(matched_destructive))
|
|
145
|
+
|
|
146
|
+
if root == "git" and ("--hard" in lower or "-f" in lower) and not allow_destructive:
|
|
147
|
+
allowed = False
|
|
148
|
+
reasons.append("forceful git operations are blocked by default")
|
|
149
|
+
|
|
150
|
+
result = policy_result(
|
|
151
|
+
kind="command",
|
|
152
|
+
allowed=allowed,
|
|
153
|
+
subject=normalized,
|
|
154
|
+
operation="execute",
|
|
155
|
+
reasons=reasons,
|
|
156
|
+
)
|
|
157
|
+
result["policy_config"] = {
|
|
158
|
+
"allowed_roots": command_policy["allowed_roots"],
|
|
159
|
+
"blocked_terms": command_policy["blocked_terms"],
|
|
160
|
+
"workspace": str(workspace.root) if workspace else None,
|
|
161
|
+
}
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def assess_request_policy(workspace: Workspace, request: str) -> dict[str, Any]:
|
|
166
|
+
lower = request.casefold()
|
|
167
|
+
warnings: list[str] = []
|
|
168
|
+
if any(term in lower for term in DANGEROUS_REQUEST_TERMS):
|
|
169
|
+
warnings.append("request contains destructive language; require an explicit policy check before file or command execution")
|
|
170
|
+
if ".env" in lower:
|
|
171
|
+
warnings.append("request mentions environment files; avoid exposing secrets and require policy review")
|
|
172
|
+
if "git reset" in lower or "reset --hard" in lower:
|
|
173
|
+
warnings.append("request mentions forceful git reset; default command policy blocks this")
|
|
174
|
+
return {
|
|
175
|
+
"workspace": str(workspace.root),
|
|
176
|
+
"warnings": warnings,
|
|
177
|
+
"requires_policy_check": bool(warnings),
|
|
178
|
+
"command_policy": summarize_command_policy(workspace),
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def resolve_target(workspace: Workspace, target: Path) -> Path:
|
|
183
|
+
if target.is_absolute():
|
|
184
|
+
return target.resolve()
|
|
185
|
+
return (workspace.root / target).resolve()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def is_relative_to(path: Path, parent: Path) -> bool:
|
|
189
|
+
try:
|
|
190
|
+
path.relative_to(parent)
|
|
191
|
+
except ValueError:
|
|
192
|
+
return False
|
|
193
|
+
return True
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def safe_relative_parts(workspace: Workspace, path: Path) -> list[str]:
|
|
197
|
+
if not is_relative_to(path, workspace.root):
|
|
198
|
+
return []
|
|
199
|
+
return list(path.relative_to(workspace.root).parts)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def command_root(command: str) -> str:
|
|
203
|
+
try:
|
|
204
|
+
parts = shlex.split(command, posix=False)
|
|
205
|
+
except ValueError:
|
|
206
|
+
parts = command.split()
|
|
207
|
+
if not parts:
|
|
208
|
+
return ""
|
|
209
|
+
return parts[0].strip('"').strip("'").casefold()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def command_root_candidates(command: str) -> set[str]:
|
|
213
|
+
root = command_root(command)
|
|
214
|
+
if not root:
|
|
215
|
+
return set()
|
|
216
|
+
candidates = {root}
|
|
217
|
+
basename = Path(root).name.casefold()
|
|
218
|
+
if basename:
|
|
219
|
+
candidates.add(basename)
|
|
220
|
+
if basename.endswith(".exe"):
|
|
221
|
+
candidates.add(basename[:-4])
|
|
222
|
+
return candidates
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def matched_destructive_terms(lower_command: str, *, extra_terms: set[str] | None = None) -> list[str]:
|
|
226
|
+
tokens = {part.strip('"').strip("'").casefold() for part in lower_command.split()}
|
|
227
|
+
destructive_terms = set(DESTRUCTIVE_COMMAND_TERMS)
|
|
228
|
+
destructive_terms.update(extra_terms or set())
|
|
229
|
+
matches: list[str] = []
|
|
230
|
+
for term in sorted(destructive_terms):
|
|
231
|
+
if " " in term:
|
|
232
|
+
if term in lower_command:
|
|
233
|
+
matches.append(term)
|
|
234
|
+
continue
|
|
235
|
+
if term in tokens:
|
|
236
|
+
matches.append(term)
|
|
237
|
+
return matches
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def command_policy_settings(workspace: Workspace | None = None) -> dict[str, list[str]]:
|
|
241
|
+
if workspace is None:
|
|
242
|
+
return {
|
|
243
|
+
"allowed_roots": sorted(SAFE_COMMAND_ROOTS),
|
|
244
|
+
"blocked_terms": [],
|
|
245
|
+
}
|
|
246
|
+
config = workspace.load_config()
|
|
247
|
+
command_policy = config.get("command_policy", {})
|
|
248
|
+
return {
|
|
249
|
+
"allowed_roots": list(command_policy.get("allowed_roots", [])),
|
|
250
|
+
"blocked_terms": list(command_policy.get("blocked_terms", [])),
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def summarize_command_policy(workspace: Workspace | None = None) -> dict[str, Any]:
|
|
255
|
+
settings = command_policy_settings(workspace)
|
|
256
|
+
return {
|
|
257
|
+
"allowed_roots": settings["allowed_roots"],
|
|
258
|
+
"blocked_terms": settings["blocked_terms"],
|
|
259
|
+
"allowed_root_count": len(settings["allowed_roots"]),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def policy_result(kind: str, allowed: bool, subject: str, operation: str, reasons: list[str]) -> dict[str, Any]:
|
|
264
|
+
return {
|
|
265
|
+
"kind": kind,
|
|
266
|
+
"operation": operation,
|
|
267
|
+
"subject": subject,
|
|
268
|
+
"allowed": allowed,
|
|
269
|
+
"status": "allowed" if allowed else "blocked",
|
|
270
|
+
"reasons": reasons or ["policy checks passed"],
|
|
271
|
+
}
|