krnl-code 1.0.4__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.
- krnl_agent/__init__.py +9 -0
- krnl_agent/__main__.py +7 -0
- krnl_agent/agent_registry.py +95 -0
- krnl_agent/agent_selector.py +69 -0
- krnl_agent/audit_log.py +155 -0
- krnl_agent/background.py +94 -0
- krnl_agent/checkpoints.py +67 -0
- krnl_agent/ci.py +73 -0
- krnl_agent/cli.py +1458 -0
- krnl_agent/commands.py +42 -0
- krnl_agent/config.py +425 -0
- krnl_agent/context.py +352 -0
- krnl_agent/depaudit.py +63 -0
- krnl_agent/deploy.py +245 -0
- krnl_agent/doctor.py +106 -0
- krnl_agent/events.py +141 -0
- krnl_agent/gitignore.py +47 -0
- krnl_agent/graph.py +928 -0
- krnl_agent/guardrails.py +70 -0
- krnl_agent/headless.py +60 -0
- krnl_agent/history.py +49 -0
- krnl_agent/hooks.py +72 -0
- krnl_agent/ingest.py +129 -0
- krnl_agent/llm.py +456 -0
- krnl_agent/loop.py +779 -0
- krnl_agent/mcp_client.py +128 -0
- krnl_agent/memory.py +61 -0
- krnl_agent/modelrouter.py +151 -0
- krnl_agent/monitor.py +112 -0
- krnl_agent/notify.py +119 -0
- krnl_agent/parallel_executor.py +139 -0
- krnl_agent/permissions.py +128 -0
- krnl_agent/plugins.py +105 -0
- krnl_agent/pricing.py +85 -0
- krnl_agent/prompts.py +60 -0
- krnl_agent/repomap.py +133 -0
- krnl_agent/sandbox.py +69 -0
- krnl_agent/scaffold.py +167 -0
- krnl_agent/schedules.py +137 -0
- krnl_agent/secrets.py +100 -0
- krnl_agent/selfheal.py +87 -0
- krnl_agent/server.py +302 -0
- krnl_agent/sessions.py +258 -0
- krnl_agent/settings.py +59 -0
- krnl_agent/skills.py +73 -0
- krnl_agent/teams.py +38 -0
- krnl_agent/tool_schemas.py +431 -0
- krnl_agent/tools.py +694 -0
- krnl_agent/webtools.py +139 -0
- krnl_code-1.0.4.dist-info/METADATA +214 -0
- krnl_code-1.0.4.dist-info/RECORD +56 -0
- krnl_code-1.0.4.dist-info/WHEEL +5 -0
- krnl_code-1.0.4.dist-info/entry_points.txt +2 -0
- krnl_code-1.0.4.dist-info/licenses/LICENSE +147 -0
- krnl_code-1.0.4.dist-info/licenses/NOTICE +4 -0
- krnl_code-1.0.4.dist-info/top_level.txt +1 -0
krnl_agent/commands.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Custom slash commands loaded from markdown files.
|
|
2
|
+
|
|
3
|
+
Files in `.krnl/commands/*.md` (project) or `~/.krnl-agent/commands/*.md`
|
|
4
|
+
(global) become `/name` commands. The file body is the prompt template;
|
|
5
|
+
`$ARGUMENTS` is replaced with everything after the command, and `$1`, `$2`, … with
|
|
6
|
+
individual whitespace-separated args.
|
|
7
|
+
|
|
8
|
+
Example `.krnl/commands/review.md`:
|
|
9
|
+
Review the changes in $ARGUMENTS for bugs and suggest fixes.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .settings import SETTINGS_DIR
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _dirs(workspace: str) -> list[Path]:
|
|
19
|
+
from . import plugins
|
|
20
|
+
|
|
21
|
+
return [SETTINGS_DIR / "commands", *plugins.plugin_command_dirs(),
|
|
22
|
+
Path(workspace) / ".krnl" / "commands"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_commands(workspace: str) -> dict[str, str]:
|
|
26
|
+
cmds: dict[str, str] = {}
|
|
27
|
+
for d in _dirs(workspace):
|
|
28
|
+
if d.is_dir():
|
|
29
|
+
for f in sorted(d.glob("*.md")):
|
|
30
|
+
try:
|
|
31
|
+
cmds[f.stem] = f.read_text(encoding="utf-8")
|
|
32
|
+
except Exception:
|
|
33
|
+
continue
|
|
34
|
+
return cmds
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def expand_command(body: str, args: str) -> str:
|
|
38
|
+
parts = args.split()
|
|
39
|
+
out = body.replace("$ARGUMENTS", args)
|
|
40
|
+
for i, p in enumerate(parts, 1):
|
|
41
|
+
out = out.replace(f"${i}", p)
|
|
42
|
+
return out
|
krnl_agent/config.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"""Configuration loading and resolution.
|
|
2
|
+
|
|
3
|
+
Config is layered, in order of precedence (later wins):
|
|
4
|
+
1. built-in defaults
|
|
5
|
+
2. config.yaml (next to the package, the cwd, or $KRNL_AGENT_CONFIG)
|
|
6
|
+
3. environment variables (.env is loaded automatically)
|
|
7
|
+
4. explicit overrides passed on the CLI / API call
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from dataclasses import dataclass, field, replace
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
from dotenv import load_dotenv
|
|
18
|
+
|
|
19
|
+
load_dotenv() # pull .env into os.environ if present
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# --------------------------------------------------------------------------- #
|
|
23
|
+
# Dataclasses
|
|
24
|
+
# --------------------------------------------------------------------------- #
|
|
25
|
+
@dataclass
|
|
26
|
+
class ProviderConfig:
|
|
27
|
+
name: str
|
|
28
|
+
type: str = "openai" # "openai" (OpenAI-compatible) or "anthropic"
|
|
29
|
+
base_url: Optional[str] = None
|
|
30
|
+
api_key_env: Optional[str] = None
|
|
31
|
+
api_key: Optional[str] = None # resolved from api_key_env at load time
|
|
32
|
+
model: str = "gpt-4o-mini"
|
|
33
|
+
temperature: float = 0.2
|
|
34
|
+
max_tokens: int = 4096
|
|
35
|
+
extra_headers: dict = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class AgentConfig:
|
|
40
|
+
max_steps: int = 30
|
|
41
|
+
stream: bool = True
|
|
42
|
+
auto_approve_reads: bool = True
|
|
43
|
+
auto_approve_writes: bool = False
|
|
44
|
+
auto_approve_commands: bool = False
|
|
45
|
+
max_file_bytes: int = 120_000
|
|
46
|
+
max_command_seconds: int = 60
|
|
47
|
+
# context-window management
|
|
48
|
+
max_context_tokens: int = 24_000 # compact history above this estimate
|
|
49
|
+
compact_history: bool = True
|
|
50
|
+
# resilience
|
|
51
|
+
retry_attempts: int = 3
|
|
52
|
+
retry_backoff: float = 1.5
|
|
53
|
+
# ignore .gitignore-listed paths in addition to the configured globs
|
|
54
|
+
use_gitignore: bool = True
|
|
55
|
+
# auto-scaffold a .krnl/ wrapper (memory + skill + project doc) on first run
|
|
56
|
+
auto_onboard: bool = True
|
|
57
|
+
# tamper-evident append-only audit log of tool actions (.krnl/audit/)
|
|
58
|
+
audit_log: bool = True
|
|
59
|
+
# post-edit verifier (critic) sub-agent that reviews the turn's diff
|
|
60
|
+
verify_edits: bool = False
|
|
61
|
+
# self-heal: auto-retry a failed test/command via a fix-it sub-agent (N times)
|
|
62
|
+
self_heal: int = 0
|
|
63
|
+
# nested sub-agent budgets (agent-of-agent guardrails)
|
|
64
|
+
subagent_max_calls: int = 12
|
|
65
|
+
subagent_token_budget: int = 400_000
|
|
66
|
+
# extended thinking / reasoning (best-effort, provider-dependent)
|
|
67
|
+
thinking: bool = False
|
|
68
|
+
reasoning_effort: Optional[str] = None # "low" | "medium" | "high"
|
|
69
|
+
# try these models (same provider) in order if the primary call keeps failing
|
|
70
|
+
fallback_models: list = field(default_factory=list)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class GraphConfig:
|
|
75
|
+
enabled: bool = False
|
|
76
|
+
backend: str = "networkx"
|
|
77
|
+
db_path: str = ".krnl/graph.db"
|
|
78
|
+
languages: list[str] = field(default_factory=list)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class ContextConfig:
|
|
83
|
+
graph_aware: bool = False
|
|
84
|
+
graph_hop_limit: int = 1
|
|
85
|
+
differential_updates: bool = False
|
|
86
|
+
compaction_summarization: bool = False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class MemoryConfig:
|
|
91
|
+
per_session: bool = False
|
|
92
|
+
staleness_check_against_graph: bool = True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class AgentsConfig:
|
|
97
|
+
specialization: bool = False
|
|
98
|
+
archetypes: list[str] = field(default_factory=lambda: ["code_reviewer", "test_generator", "debugger"])
|
|
99
|
+
parallel_execution: bool = False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class Config:
|
|
104
|
+
provider: ProviderConfig
|
|
105
|
+
agent: AgentConfig
|
|
106
|
+
ignore: list[str] = field(default_factory=list)
|
|
107
|
+
all_providers: dict[str, ProviderConfig] = field(default_factory=dict)
|
|
108
|
+
mcp_servers: dict = field(default_factory=dict)
|
|
109
|
+
web: dict = field(default_factory=dict)
|
|
110
|
+
subagent_model: Optional[str] = None
|
|
111
|
+
permissions: dict = field(default_factory=dict)
|
|
112
|
+
hooks: dict = field(default_factory=dict)
|
|
113
|
+
notifications: dict = field(default_factory=dict)
|
|
114
|
+
pricing: dict = field(default_factory=dict) # model -> {input, output} per 1M tokens
|
|
115
|
+
# model routing: {"cheap": "<model>", "heavy": "<model>"} — cheap is used for
|
|
116
|
+
# sub-agents / verifier / self-heal when set (auto-downshift).
|
|
117
|
+
router: dict = field(default_factory=dict)
|
|
118
|
+
# multi-model: named model roles, each may target a different provider, e.g.
|
|
119
|
+
# {"planner": {"provider": "anthropic", "model": "claude-opus-4-8"}, ...}
|
|
120
|
+
models: dict = field(default_factory=dict)
|
|
121
|
+
# phase -> role mapping + strategy: {"strategy": "auto", "plan": "planner",
|
|
122
|
+
# "execute": "executor", "subagent": "cheap", "escalate": "planner", ...}
|
|
123
|
+
routing: dict = field(default_factory=dict)
|
|
124
|
+
# run_command sandbox/egress policy: deny_commands (regex), allow_commands
|
|
125
|
+
# (allowlist; if non-empty everything else is denied), block_network (bool).
|
|
126
|
+
sandbox: dict = field(default_factory=dict)
|
|
127
|
+
# auto-deploy policy: {"default_target": "cloudrun", "free_tier_only": true,
|
|
128
|
+
# "allow_billable": false, "cost_ceiling_usd": 0}
|
|
129
|
+
deploy: dict = field(default_factory=dict)
|
|
130
|
+
# monitoring providers / endpoints (mostly read from env; see monitor.py)
|
|
131
|
+
monitoring: dict = field(default_factory=dict)
|
|
132
|
+
# self-heal policy: {"strategy": "rollback-auto-fix-pr", "auto_merge_low_risk": false}
|
|
133
|
+
selfheal: dict = field(default_factory=dict)
|
|
134
|
+
# Phase 1: Code Knowledge Graph
|
|
135
|
+
graph: GraphConfig = field(default_factory=GraphConfig)
|
|
136
|
+
# Phase 2: Context Selection
|
|
137
|
+
context: ContextConfig = field(default_factory=ContextConfig)
|
|
138
|
+
# Phase 3: Per-Session Memory
|
|
139
|
+
memory: MemoryConfig = field(default_factory=MemoryConfig)
|
|
140
|
+
# Phase 4: Agent Specialization
|
|
141
|
+
agents: AgentsConfig = field(default_factory=AgentsConfig)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
DEFAULT_IGNORE = [
|
|
145
|
+
".git/**", "node_modules/**", "__pycache__/**", ".venv/**", "venv/**",
|
|
146
|
+
"dist/**", "build/**", "*.lock", ".env", ".env.*",
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
# Built-in provider profiles so the extension (and a fresh install with no
|
|
150
|
+
# config.yaml) works out of the box: the user just picks a provider and supplies
|
|
151
|
+
# a key. Any of these can be overridden by a user-defined provider of the same
|
|
152
|
+
# name in config.yaml.
|
|
153
|
+
BUILTIN_PROVIDERS: dict[str, dict] = {
|
|
154
|
+
"krnl": {
|
|
155
|
+
"type": "openai",
|
|
156
|
+
"base_url": "https://api.krnl.one/api/v1",
|
|
157
|
+
"api_key_env": "KRL_API_KEY",
|
|
158
|
+
"model": "gpt-4.1-mini",
|
|
159
|
+
},
|
|
160
|
+
"openai": {
|
|
161
|
+
"type": "openai",
|
|
162
|
+
"base_url": "https://api.openai.com/v1",
|
|
163
|
+
"api_key_env": "OPENAI_API_KEY",
|
|
164
|
+
"model": "gpt-4o-mini",
|
|
165
|
+
},
|
|
166
|
+
"openrouter": {
|
|
167
|
+
"type": "openai",
|
|
168
|
+
"base_url": "https://openrouter.ai/api/v1",
|
|
169
|
+
"api_key_env": "OPENROUTER_API_KEY",
|
|
170
|
+
"model": "nex-agi/nex-n2-pro:free",
|
|
171
|
+
},
|
|
172
|
+
"ollama": {
|
|
173
|
+
"type": "openai",
|
|
174
|
+
"base_url": "http://localhost:11434/v1",
|
|
175
|
+
"api_key_env": None,
|
|
176
|
+
"model": "qwen3.5-coder:7b",
|
|
177
|
+
},
|
|
178
|
+
"anthropic": {
|
|
179
|
+
"type": "anthropic",
|
|
180
|
+
"base_url": None,
|
|
181
|
+
"api_key_env": "ANTHROPIC_API_KEY",
|
|
182
|
+
"model": "claude-sonnet-4-6",
|
|
183
|
+
},
|
|
184
|
+
"gemini": {
|
|
185
|
+
"type": "openai", # Google's OpenAI-compatible endpoint
|
|
186
|
+
"base_url": "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
187
|
+
"api_key_env": "GEMINI_API_KEY",
|
|
188
|
+
"model": "gemini-3.5-flash",
|
|
189
|
+
},
|
|
190
|
+
"groq": {
|
|
191
|
+
"type": "openai",
|
|
192
|
+
"base_url": "https://api.groq.com/openai/v1",
|
|
193
|
+
"api_key_env": "GROQ_API_KEY",
|
|
194
|
+
"model": "llama-3.3-70b-versatile",
|
|
195
|
+
},
|
|
196
|
+
"cerebras": {
|
|
197
|
+
"type": "openai",
|
|
198
|
+
"base_url": "https://api.cerebras.ai/v1",
|
|
199
|
+
"api_key_env": "CEREBRAS_API_KEY",
|
|
200
|
+
"model": "llama-3.3-70b",
|
|
201
|
+
},
|
|
202
|
+
"deepseek": {
|
|
203
|
+
"type": "openai",
|
|
204
|
+
"base_url": "https://api.deepseek.com",
|
|
205
|
+
"api_key_env": "DEEPSEEK_API_KEY",
|
|
206
|
+
"model": "deepseek-chat",
|
|
207
|
+
},
|
|
208
|
+
"together": {
|
|
209
|
+
"type": "openai",
|
|
210
|
+
"base_url": "https://api.together.xyz/v1",
|
|
211
|
+
"api_key_env": "TOGETHER_API_KEY",
|
|
212
|
+
"model": "moonshotai/Kimi-K2-Instruct",
|
|
213
|
+
},
|
|
214
|
+
"mistral": {
|
|
215
|
+
"type": "openai",
|
|
216
|
+
"base_url": "https://api.mistral.ai/v1",
|
|
217
|
+
"api_key_env": "MISTRAL_API_KEY",
|
|
218
|
+
"model": "mistral-large-latest",
|
|
219
|
+
},
|
|
220
|
+
"xai": {
|
|
221
|
+
"type": "openai",
|
|
222
|
+
"base_url": "https://api.x.ai/v1",
|
|
223
|
+
"api_key_env": "XAI_API_KEY",
|
|
224
|
+
"model": "grok-2-latest",
|
|
225
|
+
},
|
|
226
|
+
"vercel": {
|
|
227
|
+
"type": "openai", # Vercel AI Gateway
|
|
228
|
+
"base_url": "https://ai-gateway.vercel.sh/v1",
|
|
229
|
+
"api_key_env": "AI_GATEWAY_API_KEY",
|
|
230
|
+
"model": "anthropic/claude-sonnet-4.5",
|
|
231
|
+
},
|
|
232
|
+
"lmstudio": {
|
|
233
|
+
"type": "openai",
|
|
234
|
+
"base_url": "http://localhost:1234/v1",
|
|
235
|
+
"api_key_env": None,
|
|
236
|
+
"model": "local-model",
|
|
237
|
+
},
|
|
238
|
+
# Generic OpenAI-compatible endpoint; base_url/model supplied at runtime.
|
|
239
|
+
# Covers AWS Bedrock / Azure / GCP Vertex via an OpenAI-compatible proxy.
|
|
240
|
+
"custom": {
|
|
241
|
+
"type": "openai",
|
|
242
|
+
"base_url": "http://localhost:8001/v1",
|
|
243
|
+
"api_key_env": None,
|
|
244
|
+
"model": "local-model",
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# --------------------------------------------------------------------------- #
|
|
250
|
+
# Loading
|
|
251
|
+
# --------------------------------------------------------------------------- #
|
|
252
|
+
def _candidate_paths(explicit: Optional[str]) -> list[Path]:
|
|
253
|
+
paths = []
|
|
254
|
+
if explicit:
|
|
255
|
+
paths.append(Path(explicit))
|
|
256
|
+
env = os.getenv("KRNL_AGENT_CONFIG")
|
|
257
|
+
if env:
|
|
258
|
+
paths.append(Path(env))
|
|
259
|
+
paths.append(Path.cwd() / "config.yaml")
|
|
260
|
+
paths.append(Path(__file__).resolve().parent.parent / "config.yaml")
|
|
261
|
+
return paths
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _find_config_file(explicit: Optional[str]) -> Optional[Path]:
|
|
265
|
+
for p in _candidate_paths(explicit):
|
|
266
|
+
if p and p.is_file():
|
|
267
|
+
return p
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _provider_from_dict(name: str, d: dict) -> ProviderConfig:
|
|
272
|
+
api_key_env = d.get("api_key_env")
|
|
273
|
+
api_key = os.getenv(api_key_env) if api_key_env else None
|
|
274
|
+
return ProviderConfig(
|
|
275
|
+
name=name,
|
|
276
|
+
type=d.get("type", "openai"),
|
|
277
|
+
base_url=d.get("base_url"),
|
|
278
|
+
api_key_env=api_key_env,
|
|
279
|
+
api_key=api_key,
|
|
280
|
+
model=d.get("model", "gpt-4o-mini"),
|
|
281
|
+
temperature=float(d.get("temperature", 0.2)),
|
|
282
|
+
max_tokens=int(d.get("max_tokens", 4096)),
|
|
283
|
+
extra_headers=d.get("extra_headers", {}) or {},
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def load_config(
|
|
288
|
+
config_path: Optional[str] = None,
|
|
289
|
+
*,
|
|
290
|
+
provider: Optional[str] = None,
|
|
291
|
+
model: Optional[str] = None,
|
|
292
|
+
api_key: Optional[str] = None,
|
|
293
|
+
base_url: Optional[str] = None,
|
|
294
|
+
) -> Config:
|
|
295
|
+
"""Load configuration, applying optional overrides.
|
|
296
|
+
|
|
297
|
+
`api_key` / `base_url` let a caller (e.g. the VS Code extension) inject a
|
|
298
|
+
secret and endpoint at runtime so nothing has to live in a file.
|
|
299
|
+
"""
|
|
300
|
+
raw: dict[str, Any] = {}
|
|
301
|
+
cfg_file = _find_config_file(config_path)
|
|
302
|
+
if cfg_file:
|
|
303
|
+
raw = yaml.safe_load(cfg_file.read_text(encoding="utf-8")) or {}
|
|
304
|
+
|
|
305
|
+
# Start from the built-in profiles, then let config.yaml override/extend them.
|
|
306
|
+
merged: dict[str, dict] = {k: dict(v) for k, v in BUILTIN_PROVIDERS.items()}
|
|
307
|
+
for name, d in (raw.get("providers", {}) or {}).items():
|
|
308
|
+
merged[name] = {**merged.get(name, {}), **d}
|
|
309
|
+
all_providers = {name: _provider_from_dict(name, d) for name, d in merged.items()}
|
|
310
|
+
|
|
311
|
+
active = provider or raw.get("active") or "openai"
|
|
312
|
+
if active not in all_providers:
|
|
313
|
+
raise ValueError(
|
|
314
|
+
f"Provider '{active}' not found. Available: {', '.join(all_providers)}"
|
|
315
|
+
)
|
|
316
|
+
selected = all_providers[active]
|
|
317
|
+
overrides: dict[str, Any] = {}
|
|
318
|
+
if model:
|
|
319
|
+
overrides["model"] = model
|
|
320
|
+
if api_key: # non-empty only
|
|
321
|
+
overrides["api_key"] = api_key
|
|
322
|
+
if base_url:
|
|
323
|
+
overrides["base_url"] = base_url
|
|
324
|
+
if overrides:
|
|
325
|
+
selected = replace(selected, **overrides)
|
|
326
|
+
|
|
327
|
+
agent_raw = raw.get("agent", {}) or {}
|
|
328
|
+
agent = AgentConfig(
|
|
329
|
+
max_steps=int(agent_raw.get("max_steps", 30)),
|
|
330
|
+
stream=bool(agent_raw.get("stream", True)),
|
|
331
|
+
auto_approve_reads=bool(agent_raw.get("auto_approve_reads", True)),
|
|
332
|
+
auto_approve_writes=bool(agent_raw.get("auto_approve_writes", False)),
|
|
333
|
+
auto_approve_commands=bool(agent_raw.get("auto_approve_commands", False)),
|
|
334
|
+
max_file_bytes=int(agent_raw.get("max_file_bytes", 120_000)),
|
|
335
|
+
max_command_seconds=int(agent_raw.get("max_command_seconds", 60)),
|
|
336
|
+
max_context_tokens=int(agent_raw.get("max_context_tokens", 24_000)),
|
|
337
|
+
compact_history=bool(agent_raw.get("compact_history", True)),
|
|
338
|
+
retry_attempts=int(agent_raw.get("retry_attempts", 3)),
|
|
339
|
+
retry_backoff=float(agent_raw.get("retry_backoff", 1.5)),
|
|
340
|
+
use_gitignore=bool(agent_raw.get("use_gitignore", True)),
|
|
341
|
+
auto_onboard=bool(agent_raw.get("auto_onboard", True)),
|
|
342
|
+
audit_log=bool(agent_raw.get("audit_log", True)),
|
|
343
|
+
verify_edits=bool(agent_raw.get("verify_edits", False)),
|
|
344
|
+
self_heal=int(agent_raw.get("self_heal", 0)),
|
|
345
|
+
subagent_max_calls=int(agent_raw.get("subagent_max_calls", 12)),
|
|
346
|
+
subagent_token_budget=int(agent_raw.get("subagent_token_budget", 400_000)),
|
|
347
|
+
thinking=bool(agent_raw.get("thinking", False)),
|
|
348
|
+
reasoning_effort=agent_raw.get("reasoning_effort"),
|
|
349
|
+
fallback_models=list(agent_raw.get("fallback_models", []) or []),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Load Phase 1-4 configs
|
|
353
|
+
graph_raw = raw.get("graph", {}) or {}
|
|
354
|
+
graph = GraphConfig(
|
|
355
|
+
enabled=bool(graph_raw.get("enabled", False)),
|
|
356
|
+
backend=graph_raw.get("backend", "networkx"),
|
|
357
|
+
db_path=graph_raw.get("db_path", ".krnl/graph.db"),
|
|
358
|
+
languages=list(graph_raw.get("languages", []) or []),
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
context_raw = raw.get("context", {}) or {}
|
|
362
|
+
context_cfg = ContextConfig(
|
|
363
|
+
graph_aware=bool(context_raw.get("graph_aware", False)),
|
|
364
|
+
graph_hop_limit=int(context_raw.get("graph_hop_limit", 1)),
|
|
365
|
+
differential_updates=bool(context_raw.get("differential_updates", False)),
|
|
366
|
+
compaction_summarization=bool(context_raw.get("compaction_summarization", False)),
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
memory_raw = raw.get("memory", {}) or {}
|
|
370
|
+
memory_cfg = MemoryConfig(
|
|
371
|
+
per_session=bool(memory_raw.get("per_session", False)),
|
|
372
|
+
staleness_check_against_graph=bool(memory_raw.get("staleness_check_against_graph", True)),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
agents_raw = raw.get("agents", {}) or {}
|
|
376
|
+
agents_cfg = AgentsConfig(
|
|
377
|
+
specialization=bool(agents_raw.get("specialization", False)),
|
|
378
|
+
archetypes=list(agents_raw.get("archetypes", ["code_reviewer", "test_generator", "debugger"]) or []),
|
|
379
|
+
parallel_execution=bool(agents_raw.get("parallel_execution", False)),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
ignore = raw.get("ignore") or DEFAULT_IGNORE
|
|
383
|
+
|
|
384
|
+
mcp_servers = (raw.get("mcp", {}) or {}).get("servers", {}) or {}
|
|
385
|
+
|
|
386
|
+
web = dict(raw.get("web", {}) or {})
|
|
387
|
+
if not web.get("provider"):
|
|
388
|
+
# auto-detect a search backend from env (TAVILY/BRAVE/SERPAPI), else keyless
|
|
389
|
+
for env_key, name in (
|
|
390
|
+
("TAVILY_API_KEY", "tavily"),
|
|
391
|
+
("BRAVE_API_KEY", "brave"),
|
|
392
|
+
("SERPAPI_API_KEY", "serpapi"),
|
|
393
|
+
):
|
|
394
|
+
if os.getenv(env_key):
|
|
395
|
+
web = {"provider": name, "api_key": os.environ[env_key]}
|
|
396
|
+
break
|
|
397
|
+
else:
|
|
398
|
+
web = {"provider": "duckduckgo", "api_key": ""}
|
|
399
|
+
elif web.get("api_key_env"):
|
|
400
|
+
web["api_key"] = os.getenv(web["api_key_env"], "")
|
|
401
|
+
|
|
402
|
+
return Config(
|
|
403
|
+
provider=selected,
|
|
404
|
+
agent=agent,
|
|
405
|
+
ignore=list(ignore),
|
|
406
|
+
all_providers=all_providers,
|
|
407
|
+
mcp_servers=mcp_servers,
|
|
408
|
+
web=web,
|
|
409
|
+
subagent_model=raw.get("subagent_model"),
|
|
410
|
+
permissions=raw.get("permissions", {}) or {},
|
|
411
|
+
hooks=raw.get("hooks", {}) or {},
|
|
412
|
+
notifications=raw.get("notifications", {}) or {},
|
|
413
|
+
pricing=raw.get("pricing", {}) or {},
|
|
414
|
+
router=raw.get("router", {}) or {},
|
|
415
|
+
models=raw.get("models", {}) or {},
|
|
416
|
+
routing=raw.get("routing", {}) or {},
|
|
417
|
+
sandbox=raw.get("sandbox", {}) or {},
|
|
418
|
+
deploy=raw.get("deploy", {}) or {},
|
|
419
|
+
monitoring=raw.get("monitoring", {}) or {},
|
|
420
|
+
selfheal=raw.get("selfheal", {}) or {},
|
|
421
|
+
graph=graph,
|
|
422
|
+
context=context_cfg,
|
|
423
|
+
memory=memory_cfg,
|
|
424
|
+
agents=agents_cfg,
|
|
425
|
+
)
|