minima-cli 0.4.9__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.
- minima/__init__.py +5 -0
- minima/api/__init__.py +1 -0
- minima/api/auth.py +39 -0
- minima/api/errors.py +40 -0
- minima/api/routers/__init__.py +1 -0
- minima/api/routers/calibration.py +50 -0
- minima/api/routers/feedback.py +279 -0
- minima/api/routers/health.py +50 -0
- minima/api/routers/models.py +42 -0
- minima/api/routers/recommend.py +66 -0
- minima/api/routers/savings.py +55 -0
- minima/api/routers/strategies.py +33 -0
- minima/catalog/__init__.py +1 -0
- minima/catalog/data/capability_priors.json +210 -0
- minima/catalog/data/model_aliases.json +12 -0
- minima/catalog/merge.py +69 -0
- minima/catalog/refresh.py +54 -0
- minima/catalog/sources/__init__.py +1 -0
- minima/catalog/sources/litellm.py +19 -0
- minima/catalog/sources/openrouter.py +25 -0
- minima/catalog/store.py +86 -0
- minima/config.py +288 -0
- minima/deps.py +35 -0
- minima/llm/__init__.py +1 -0
- minima/llm/anthropic.py +106 -0
- minima/llm/base.py +196 -0
- minima/llm/gemini.py +124 -0
- minima/llm/registry.py +54 -0
- minima/logging.py +28 -0
- minima/main.py +109 -0
- minima/memory/__init__.py +1 -0
- minima/memory/adapter.py +572 -0
- minima/memory/keys.py +83 -0
- minima/memory/records.py +190 -0
- minima/memory/threadpool.py +41 -0
- minima/metrics/__init__.py +1 -0
- minima/metrics/calibration.py +415 -0
- minima/metrics/report.py +116 -0
- minima/metrics/savings.py +98 -0
- minima/recommender/__init__.py +1 -0
- minima/recommender/_pg_pool.py +38 -0
- minima/recommender/_redis_client.py +32 -0
- minima/recommender/aggregate.py +157 -0
- minima/recommender/classify.py +165 -0
- minima/recommender/decisionlog.py +505 -0
- minima/recommender/durablerefs.py +312 -0
- minima/recommender/engine.py +997 -0
- minima/recommender/escalation.py +83 -0
- minima/recommender/propensity.py +189 -0
- minima/recommender/recstore.py +368 -0
- minima/recommender/score.py +318 -0
- minima/recommender/types.py +166 -0
- minima/schemas/__init__.py +1 -0
- minima/schemas/common.py +73 -0
- minima/schemas/feedback.py +34 -0
- minima/schemas/models_catalog.py +36 -0
- minima/schemas/recommend.py +104 -0
- minima/schemas/savings.py +39 -0
- minima/schemas/strategies.py +57 -0
- minima/schemas/workflow.py +43 -0
- minima/seeding/__init__.py +1 -0
- minima/seeding/items.py +42 -0
- minima/seeding/llmrouterbench.py +232 -0
- minima/seeding/routerbench.py +141 -0
- minima/seeding/run_seed.py +56 -0
- minima/seeding/synthetic.py +70 -0
- minima/tenancy/__init__.py +8 -0
- minima/tenancy/context.py +37 -0
- minima/tenancy/passthrough.py +110 -0
- minima/version.py +3 -0
- minima_cli-0.4.9.dist-info/METADATA +275 -0
- minima_cli-0.4.9.dist-info/RECORD +161 -0
- minima_cli-0.4.9.dist-info/WHEEL +4 -0
- minima_cli-0.4.9.dist-info/entry_points.txt +5 -0
- minima_cli-0.4.9.dist-info/licenses/LICENSE +295 -0
- minima_client/__init__.py +19 -0
- minima_client/autocapture.py +101 -0
- minima_client/client.py +301 -0
- minima_client/errors.py +23 -0
- minima_harness/LICENSE_PI +32 -0
- minima_harness/__init__.py +16 -0
- minima_harness/agent/__init__.py +72 -0
- minima_harness/agent/agent.py +276 -0
- minima_harness/agent/events.py +124 -0
- minima_harness/agent/loop.py +311 -0
- minima_harness/agent/state.py +79 -0
- minima_harness/agent/tools.py +97 -0
- minima_harness/ai/__init__.py +66 -0
- minima_harness/ai/compat.py +71 -0
- minima_harness/ai/errors.py +96 -0
- minima_harness/ai/events.py +117 -0
- minima_harness/ai/openrouter_catalog.py +153 -0
- minima_harness/ai/provider_catalog.py +299 -0
- minima_harness/ai/provider_quirks.py +37 -0
- minima_harness/ai/providers/__init__.py +75 -0
- minima_harness/ai/providers/_common.py +48 -0
- minima_harness/ai/providers/anthropic.py +290 -0
- minima_harness/ai/providers/base.py +65 -0
- minima_harness/ai/providers/faux.py +173 -0
- minima_harness/ai/providers/google.py +221 -0
- minima_harness/ai/providers/openai_compat.py +278 -0
- minima_harness/ai/registry.py +184 -0
- minima_harness/ai/stream.py +82 -0
- minima_harness/ai/tools.py +51 -0
- minima_harness/ai/types.py +204 -0
- minima_harness/ai/usage.py +41 -0
- minima_harness/minima/__init__.py +40 -0
- minima_harness/minima/cache.py +102 -0
- minima_harness/minima/config.py +85 -0
- minima_harness/minima/goals.py +226 -0
- minima_harness/minima/judge.py +144 -0
- minima_harness/minima/mapping.py +147 -0
- minima_harness/minima/meter.py +143 -0
- minima_harness/minima/router.py +220 -0
- minima_harness/minima/runtime.py +544 -0
- minima_harness/minima/signals.py +195 -0
- minima_harness/session/__init__.py +14 -0
- minima_harness/session/format.py +35 -0
- minima_harness/session/store.py +236 -0
- minima_harness/tasks/__init__.py +17 -0
- minima_harness/tasks/task_set.py +78 -0
- minima_harness/tools/__init__.py +7 -0
- minima_harness/tools/_io.py +34 -0
- minima_harness/tools/bash.py +70 -0
- minima_harness/tools/builtin.py +23 -0
- minima_harness/tools/edit.py +50 -0
- minima_harness/tools/find.py +38 -0
- minima_harness/tools/grep.py +73 -0
- minima_harness/tools/ls.py +35 -0
- minima_harness/tools/read.py +38 -0
- minima_harness/tools/tasks.py +75 -0
- minima_harness/tools/write.py +36 -0
- minima_harness/tui/__init__.py +3 -0
- minima_harness/tui/analytics.py +111 -0
- minima_harness/tui/app.py +1927 -0
- minima_harness/tui/bridge.py +103 -0
- minima_harness/tui/cli.py +227 -0
- minima_harness/tui/clipboard.py +60 -0
- minima_harness/tui/commands.py +49 -0
- minima_harness/tui/compaction.py +17 -0
- minima_harness/tui/config_cli.py +141 -0
- minima_harness/tui/config_store.py +237 -0
- minima_harness/tui/context.py +93 -0
- minima_harness/tui/customize.py +95 -0
- minima_harness/tui/diff.py +53 -0
- minima_harness/tui/editor.py +43 -0
- minima_harness/tui/extensions.py +84 -0
- minima_harness/tui/extra_models.py +52 -0
- minima_harness/tui/history.py +71 -0
- minima_harness/tui/mubit.py +295 -0
- minima_harness/tui/overlays.py +593 -0
- minima_harness/tui/packages.py +59 -0
- minima_harness/tui/run_modes.py +66 -0
- minima_harness/tui/theme.py +77 -0
- minima_harness/tui/welcome.py +83 -0
- minima_harness/tui/widgets/__init__.py +3 -0
- minima_harness/tui/widgets/banner.py +38 -0
- minima_harness/tui/widgets/editor.py +83 -0
- minima_harness/tui/widgets/footer.py +73 -0
- minima_harness/tui/widgets/messages.py +151 -0
- minima_harness/tui/widgets/status.py +57 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from minima_harness.session import SessionManager
|
|
7
|
+
from minima_harness.tui.customize import GLOBAL_DIR
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class History:
|
|
11
|
+
"""Shell-style prompt history with prev/next cursor (None cursor = the 'new' position)."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, entries: list[str] | None = None) -> None:
|
|
14
|
+
self.entries: list[str] = list(entries or [])
|
|
15
|
+
self._i: int | None = None # None = new (after the last); index = browsing position
|
|
16
|
+
|
|
17
|
+
def add(self, text: str) -> None:
|
|
18
|
+
text = text.strip()
|
|
19
|
+
if text and (not self.entries or self.entries[-1] != text):
|
|
20
|
+
self.entries.append(text)
|
|
21
|
+
self._i = None
|
|
22
|
+
|
|
23
|
+
def prev(self) -> str | None:
|
|
24
|
+
"""Move toward older; returns the entry (or None if there's no history)."""
|
|
25
|
+
if not self.entries:
|
|
26
|
+
return None
|
|
27
|
+
if self._i is None:
|
|
28
|
+
self._i = len(self.entries) - 1
|
|
29
|
+
elif self._i > 0:
|
|
30
|
+
self._i -= 1
|
|
31
|
+
return self.entries[self._i]
|
|
32
|
+
|
|
33
|
+
def next(self) -> str | None:
|
|
34
|
+
"""Move toward newer; returns the entry, '' when back at new, or None if already new."""
|
|
35
|
+
if self._i is None:
|
|
36
|
+
return None
|
|
37
|
+
self._i += 1
|
|
38
|
+
if self._i >= len(self.entries):
|
|
39
|
+
self._i = None
|
|
40
|
+
return ""
|
|
41
|
+
return self.entries[self._i]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _path(cwd: Path) -> Path:
|
|
45
|
+
slug = SessionManager().slug_for(cwd)
|
|
46
|
+
return GLOBAL_DIR / "history" / f"{slug}.jsonl"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_history(cwd: Path) -> list[str]:
|
|
50
|
+
path = _path(cwd)
|
|
51
|
+
if not path.is_file():
|
|
52
|
+
return []
|
|
53
|
+
out: list[str] = []
|
|
54
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
55
|
+
if not line.strip():
|
|
56
|
+
continue
|
|
57
|
+
try:
|
|
58
|
+
out.append(json.loads(line))
|
|
59
|
+
except Exception: # noqa: BLE001
|
|
60
|
+
continue
|
|
61
|
+
return out
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def append_history(cwd: Path, text: str) -> None:
|
|
65
|
+
path = _path(cwd)
|
|
66
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
try:
|
|
68
|
+
with path.open("a", encoding="utf-8") as fh:
|
|
69
|
+
fh.write(json.dumps(text) + "\n")
|
|
70
|
+
except OSError: # noqa: BLE001
|
|
71
|
+
pass
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
_log = logging.getLogger("minima_harness.tui.mubit")
|
|
10
|
+
|
|
11
|
+
_DEFAULT_ENDPOINT = "https://api.mubit.ai"
|
|
12
|
+
_AGENT_ID = "minima-harness"
|
|
13
|
+
_initialized = False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _slug(cwd: Path) -> str:
|
|
17
|
+
from minima_harness.session import SessionManager
|
|
18
|
+
|
|
19
|
+
return SessionManager().slug_for(cwd)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def init_mubit(cwd: Path) -> bool:
|
|
23
|
+
"""Initialize the Mubit SDK for this project (idempotent). Returns True if available."""
|
|
24
|
+
global _initialized
|
|
25
|
+
if _initialized:
|
|
26
|
+
return True
|
|
27
|
+
key = os.environ.get("MUBIT_API_KEY")
|
|
28
|
+
if not key:
|
|
29
|
+
_log.warning("mubit_no_api_key")
|
|
30
|
+
return False
|
|
31
|
+
# `or` (not get-with-default) so a blank MUBIT_ENDPOINT="" — e.g. a stray empty line in a
|
|
32
|
+
# copied .env — falls back to the hosted default instead of passing "" to mubit.init().
|
|
33
|
+
endpoint = os.environ.get("MUBIT_ENDPOINT") or _DEFAULT_ENDPOINT
|
|
34
|
+
try:
|
|
35
|
+
import mubit
|
|
36
|
+
|
|
37
|
+
mubit.init(
|
|
38
|
+
api_key=key,
|
|
39
|
+
endpoint=endpoint,
|
|
40
|
+
agent_id=_AGENT_ID,
|
|
41
|
+
project_id=_slug(cwd),
|
|
42
|
+
auto_instrument=False,
|
|
43
|
+
auto_learn=False,
|
|
44
|
+
inject_lessons=True,
|
|
45
|
+
)
|
|
46
|
+
_initialized = True
|
|
47
|
+
return True
|
|
48
|
+
except Exception: # noqa: BLE001 - Mubit must never block the TUI
|
|
49
|
+
_log.warning("mubit_init_failed", exc_info=True)
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def available() -> bool:
|
|
54
|
+
return _initialized
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_prompt() -> str:
|
|
58
|
+
if not _initialized:
|
|
59
|
+
return ""
|
|
60
|
+
try:
|
|
61
|
+
import mubit
|
|
62
|
+
|
|
63
|
+
return mubit.get_prompt(agent_id=_AGENT_ID) or ""
|
|
64
|
+
except Exception: # noqa: BLE001
|
|
65
|
+
_log.warning("mubit_get_prompt_failed", exc_info=True)
|
|
66
|
+
return ""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def set_prompt(content: str) -> bool:
|
|
70
|
+
try:
|
|
71
|
+
import mubit
|
|
72
|
+
|
|
73
|
+
mubit.set_prompt(content, agent_id=_AGENT_ID, activate=True)
|
|
74
|
+
return True
|
|
75
|
+
except Exception: # noqa: BLE001
|
|
76
|
+
_log.warning("mubit_set_prompt_failed", exc_info=True)
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def optimize_prompt() -> dict[str, Any] | None:
|
|
81
|
+
"""Ask Mubit to optimize this agent's system prompt from accumulated lessons + outcomes.
|
|
82
|
+
|
|
83
|
+
Returns the raw response ``{success, activated, candidate, confidence,
|
|
84
|
+
optimization_summary}`` (the candidate is a non-activated suggestion), or None on any
|
|
85
|
+
failure. Verified live: ``activated`` is False, so this never changes the active prompt.
|
|
86
|
+
"""
|
|
87
|
+
if not _initialized:
|
|
88
|
+
return None
|
|
89
|
+
try:
|
|
90
|
+
from mubit._helpers import require_context
|
|
91
|
+
|
|
92
|
+
return require_context().client.optimize_prompt({"agent_id": _AGENT_ID})
|
|
93
|
+
except Exception: # noqa: BLE001 - Mubit must never block the TUI
|
|
94
|
+
_log.warning("mubit_optimize_prompt_failed", exc_info=True)
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True, slots=True)
|
|
99
|
+
class Optimization:
|
|
100
|
+
"""A proposed system-prompt optimization, for the /optimize preview."""
|
|
101
|
+
|
|
102
|
+
new_prompt: str
|
|
103
|
+
current_tokens: int
|
|
104
|
+
new_tokens: int
|
|
105
|
+
est_savings: int # current - new; negative means the prompt grew (quality over size)
|
|
106
|
+
rationale: str
|
|
107
|
+
source: str # "mubit" | "local"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _local_optimization(cwd: Path) -> Optimization | None:
|
|
111
|
+
"""Fallback when Mubit is unreachable: conservatively drop exact-duplicate lines from the
|
|
112
|
+
current Mubit prompt. Suggestion only; returns None when there's nothing safe to remove."""
|
|
113
|
+
current = get_prompt().strip()
|
|
114
|
+
if not current:
|
|
115
|
+
return None
|
|
116
|
+
lines = current.splitlines()
|
|
117
|
+
seen: set[str] = set()
|
|
118
|
+
deduped: list[str] = []
|
|
119
|
+
for ln in lines:
|
|
120
|
+
key = ln.strip()
|
|
121
|
+
if key and key in seen:
|
|
122
|
+
continue
|
|
123
|
+
if key:
|
|
124
|
+
seen.add(key)
|
|
125
|
+
deduped.append(ln)
|
|
126
|
+
new_prompt = "\n".join(deduped)
|
|
127
|
+
savings = estimate_tokens(current) - estimate_tokens(new_prompt)
|
|
128
|
+
if savings <= 0:
|
|
129
|
+
return None
|
|
130
|
+
removed = len(lines) - len(deduped)
|
|
131
|
+
return Optimization(
|
|
132
|
+
new_prompt=new_prompt,
|
|
133
|
+
current_tokens=estimate_tokens(current),
|
|
134
|
+
new_tokens=estimate_tokens(new_prompt),
|
|
135
|
+
est_savings=savings,
|
|
136
|
+
rationale=f"removed {removed} duplicate line(s)",
|
|
137
|
+
source="local",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def propose_prompt_optimization(cwd: Path, n_sessions: int = 10) -> Optimization | None:
|
|
142
|
+
"""Propose a system-prompt optimization: Mubit's lesson-grounded candidate (Path A) when
|
|
143
|
+
available, else a local dedup (Path B). Never auto-applies — the caller previews + confirms."""
|
|
144
|
+
current = get_prompt()
|
|
145
|
+
resp = optimize_prompt()
|
|
146
|
+
if isinstance(resp, dict) and resp.get("success"):
|
|
147
|
+
cand = resp.get("candidate")
|
|
148
|
+
new_prompt = cand.get("content", "") if isinstance(cand, dict) else ""
|
|
149
|
+
if new_prompt.strip():
|
|
150
|
+
summary = (resp.get("optimization_summary") or "").strip()
|
|
151
|
+
return Optimization(
|
|
152
|
+
new_prompt=new_prompt.strip(),
|
|
153
|
+
current_tokens=estimate_tokens(current),
|
|
154
|
+
new_tokens=estimate_tokens(new_prompt),
|
|
155
|
+
est_savings=estimate_tokens(current) - estimate_tokens(new_prompt),
|
|
156
|
+
rationale=summary or "Mubit consolidated lessons + outcomes into the prompt.",
|
|
157
|
+
source="mubit",
|
|
158
|
+
)
|
|
159
|
+
return _local_optimization(cwd)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_skills(cwd: Path) -> list[dict[str, Any]]:
|
|
163
|
+
if not _initialized:
|
|
164
|
+
return []
|
|
165
|
+
try:
|
|
166
|
+
import mubit
|
|
167
|
+
|
|
168
|
+
return mubit.get_skills(project_id=_slug(cwd)) or []
|
|
169
|
+
except Exception: # noqa: BLE001
|
|
170
|
+
return []
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def set_skill(cwd: Path, name: str, description: str, instructions: str = "") -> bool:
|
|
174
|
+
try:
|
|
175
|
+
import mubit
|
|
176
|
+
|
|
177
|
+
mubit.set_skill(name, description, instructions=instructions, project_id=_slug(cwd))
|
|
178
|
+
return True
|
|
179
|
+
except Exception: # noqa: BLE001
|
|
180
|
+
_log.warning("mubit_set_skill_failed", exc_info=True)
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def recall(query: str, session_id: str | None = None, limit: int = 5) -> list[Any]:
|
|
185
|
+
if not _initialized:
|
|
186
|
+
return []
|
|
187
|
+
try:
|
|
188
|
+
import mubit
|
|
189
|
+
|
|
190
|
+
return mubit.recall(query, session_id=session_id, limit=limit) or []
|
|
191
|
+
except Exception: # noqa: BLE001
|
|
192
|
+
return []
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def learned() -> str:
|
|
196
|
+
if not _initialized:
|
|
197
|
+
return ""
|
|
198
|
+
try:
|
|
199
|
+
import mubit
|
|
200
|
+
|
|
201
|
+
return mubit.learned() or ""
|
|
202
|
+
except Exception: # noqa: BLE001
|
|
203
|
+
return ""
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def estimate_tokens(text: str) -> int:
|
|
207
|
+
return max(1, len(text) // 4)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass(frozen=True, slots=True)
|
|
211
|
+
class PromptLayer:
|
|
212
|
+
"""One layer of the assembled system prompt, for transparent display + control.
|
|
213
|
+
|
|
214
|
+
``header`` is the section prefix used in the joined prompt (empty for the leading
|
|
215
|
+
layer); ``rendered`` reproduces exactly how the layer appears in ``effective_prompt``.
|
|
216
|
+
``editable_target`` is ``"project"`` (→ Mubit), ``"session"`` (→ override), or ``None``.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
name: str
|
|
220
|
+
text: str
|
|
221
|
+
header: str = ""
|
|
222
|
+
source: str = ""
|
|
223
|
+
editable_target: str | None = None
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def rendered(self) -> str:
|
|
227
|
+
return f"{self.header}\n{self.text}" if self.header else self.text
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def tokens(self) -> int:
|
|
231
|
+
return estimate_tokens(self.rendered)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def prompt_layers(cwd: Path, session_override: str = "") -> list[PromptLayer]:
|
|
235
|
+
"""The ordered layers that compose the system prompt. Single source of truth —
|
|
236
|
+
``effective_prompt`` is a thin join over this, so the inspector can never drift."""
|
|
237
|
+
from minima_harness.tui.context import build_system_prompt_parts, load_agents_md
|
|
238
|
+
|
|
239
|
+
layers: list[PromptLayer] = []
|
|
240
|
+
mubit_prompt = get_prompt().strip()
|
|
241
|
+
if mubit_prompt:
|
|
242
|
+
layers.append(
|
|
243
|
+
PromptLayer("system prompt", mubit_prompt, source="mubit", editable_target="project")
|
|
244
|
+
)
|
|
245
|
+
agents = load_agents_md(cwd)
|
|
246
|
+
if agents:
|
|
247
|
+
layers.append(
|
|
248
|
+
PromptLayer("project context", agents, "# Project context", "agents.md")
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
for name, text in build_system_prompt_parts(cwd):
|
|
252
|
+
if name == "base":
|
|
253
|
+
layers.append(PromptLayer("base prompt", text, source="local"))
|
|
254
|
+
else: # agents.md
|
|
255
|
+
layers.append(
|
|
256
|
+
PromptLayer("project context", text, "# Project context", "agents.md")
|
|
257
|
+
)
|
|
258
|
+
override = session_override.strip()
|
|
259
|
+
if override:
|
|
260
|
+
layers.append(
|
|
261
|
+
PromptLayer(
|
|
262
|
+
"session override", override, "# Session override", "session", "session"
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
lessons = learned().strip()
|
|
266
|
+
if lessons:
|
|
267
|
+
layers.append(PromptLayer("lessons (Mubit)", lessons, "# Lessons (Mubit)", "mubit"))
|
|
268
|
+
return layers
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def effective_prompt(cwd: Path, session_override: str = "") -> str:
|
|
272
|
+
"""The system prompt sent to the model: the rendered join of :func:`prompt_layers`."""
|
|
273
|
+
return "\n\n".join(layer.rendered for layer in prompt_layers(cwd, session_override))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def token_breakdown(cwd: Path, messages: list) -> dict[str, int]:
|
|
277
|
+
"""Approximate token counts per section of the context that goes to the model."""
|
|
278
|
+
system = effective_prompt(cwd)
|
|
279
|
+
history = "\n".join(getattr(m, "text", "") for m in messages)
|
|
280
|
+
return {
|
|
281
|
+
"system": estimate_tokens(system),
|
|
282
|
+
"history": estimate_tokens(history),
|
|
283
|
+
"total": estimate_tokens(system) + estimate_tokens(history),
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def layer_token_breakdown(
|
|
288
|
+
cwd: Path, messages: list, session_override: str = ""
|
|
289
|
+
) -> dict[str, Any]:
|
|
290
|
+
"""Per-layer token counts + history + total, for the layered prompt inspector."""
|
|
291
|
+
layers = prompt_layers(cwd, session_override)
|
|
292
|
+
history = estimate_tokens("\n".join(getattr(m, "text", "") for m in messages))
|
|
293
|
+
layer_tokens = [(layer.name, layer.tokens) for layer in layers]
|
|
294
|
+
system = sum(t for _, t in layer_tokens)
|
|
295
|
+
return {"layers": layer_tokens, "system": system, "history": history, "total": system + history}
|