handcode 0.3.0rc1__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.
- agentctl/__init__.py +0 -0
- agentctl/adapters/__init__.py +0 -0
- agentctl/adapters/litellm/__init__.py +9 -0
- agentctl/adapters/litellm/hook.py +49 -0
- agentctl/adapters/litellm/recorder.py +187 -0
- agentctl/adapters/openhands/__init__.py +169 -0
- agentctl/adapters/openhands/handoff.py +155 -0
- agentctl/adapters/openhands/seam_b.py +259 -0
- agentctl/adapters/openhands/seam_c.py +209 -0
- agentctl/cli.py +1450 -0
- agentctl/control/__init__.py +0 -0
- agentctl/control/cost/__init__.py +4 -0
- agentctl/control/cost/ledger.py +210 -0
- agentctl/control/dash.py +697 -0
- agentctl/control/keys.py +440 -0
- agentctl/control/matrix/__init__.py +0 -0
- agentctl/control/matrix/data/tools.yaml +149 -0
- agentctl/control/policy/__init__.py +10 -0
- agentctl/control/policy/compile.py +258 -0
- agentctl/control/policy/data/policy.compiled.json +38 -0
- agentctl/control/policy/data/policy.yaml +46 -0
- agentctl/control/probe.py +399 -0
- agentctl/control/providers.py +293 -0
- agentctl/control/proxy.py +536 -0
- agentctl/control/proxyenv.py +309 -0
- agentctl/control/replay/__init__.py +14 -0
- agentctl/control/replay/cassette.py +281 -0
- agentctl/control/replay/server.py +109 -0
- agentctl/demo/__init__.py +214 -0
- agentctl/demo/child.py +84 -0
- agentctl/demo/mock.py +79 -0
- agentctl/demo/tool.py +62 -0
- agentctl/gha.py +488 -0
- agentctl/kernel/__init__.py +0 -0
- agentctl/kernel/classify.py +170 -0
- agentctl/kernel/gate.py +391 -0
- agentctl/kernel/hook.py +229 -0
- agentctl/kernel/ledger/__init__.py +0 -0
- agentctl/kernel/ledger/models.py +160 -0
- agentctl/kernel/ledger/schema.sql +62 -0
- agentctl/kernel/ledger/store.py +596 -0
- agentctl/kernel/paths.py +203 -0
- agentctl/kernel/policy.py +160 -0
- agentctl/kernel/reconcile/__init__.py +31 -0
- agentctl/kernel/reconcile/base.py +106 -0
- agentctl/kernel/reconcile/external.py +137 -0
- agentctl/kernel/reconcile/filesystem.py +162 -0
- agentctl/kernel/reconcile/git.py +162 -0
- agentctl/runtime/__init__.py +20 -0
- agentctl/runtime/citations.py +179 -0
- agentctl/runtime/config.py +97 -0
- agentctl/runtime/doctor.py +335 -0
- agentctl/runtime/init.py +148 -0
- agentctl/runtime/lease.py +143 -0
- agentctl/runtime/orchestrate.py +187 -0
- agentctl/runtime/plugins.py +130 -0
- agentctl/runtime/report.py +361 -0
- agentctl/runtime/runner.py +787 -0
- agentctl/runtime/runs.py +191 -0
- agentctl/runtime/subagent.py +274 -0
- agentctl/runtime/tools.py +350 -0
- handcode-0.3.0rc1.dist-info/METADATA +659 -0
- handcode-0.3.0rc1.dist-info/RECORD +67 -0
- handcode-0.3.0rc1.dist-info/WHEEL +5 -0
- handcode-0.3.0rc1.dist-info/entry_points.txt +3 -0
- handcode-0.3.0rc1.dist-info/licenses/LICENSE +21 -0
- handcode-0.3.0rc1.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Cost ledger — spend attributed to logical work. `docs/0008` §8, M5.
|
|
2
|
+
|
|
3
|
+
Control plane, so it may fail: nothing here is on the request path. It consumes
|
|
4
|
+
Seam A telemetry after the fact.
|
|
5
|
+
|
|
6
|
+
**Pricing coverage is a first-class column, not a nicety.** `docs/0021` §5 found
|
|
7
|
+
that LiteLLM reports `response_cost: 0.0` for an endpoint it cannot price —
|
|
8
|
+
not `None`, *zero*. So an unpriced call and a genuinely free one are
|
|
9
|
+
indistinguishable in the raw data, and any budget built on it silently
|
|
10
|
+
under-counts exactly where a free-tier pool lives.
|
|
11
|
+
|
|
12
|
+
The ledger therefore records `priced` separately from `cost_usd`, and every
|
|
13
|
+
total is reported alongside the share of calls it actually covers. "I spent
|
|
14
|
+
$0.00" and "I do not know what I spent" must never render the same.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import sqlite3
|
|
20
|
+
import time
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
SCHEMA = """
|
|
25
|
+
PRAGMA journal_mode = WAL;
|
|
26
|
+
|
|
27
|
+
CREATE TABLE IF NOT EXISTS usage_record (
|
|
28
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
29
|
+
trace_id TEXT,
|
|
30
|
+
conversation_id TEXT,
|
|
31
|
+
turn_id TEXT,
|
|
32
|
+
model TEXT,
|
|
33
|
+
deployment TEXT,
|
|
34
|
+
prompt_tokens INTEGER,
|
|
35
|
+
completion_tokens INTEGER,
|
|
36
|
+
cached_tokens INTEGER,
|
|
37
|
+
cost_usd REAL,
|
|
38
|
+
-- The Q18 column. 0 means litellm could not price this call, so cost_usd
|
|
39
|
+
-- is meaningless rather than zero. docs/0021 §5.
|
|
40
|
+
priced INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
-- The third state (docs/0042 I-10): the proxy config says this
|
|
42
|
+
-- deployment is a free tier, so its cost is a known zero -- whatever list
|
|
43
|
+
-- price litellm attached, and even when litellm attached none.
|
|
44
|
+
free INTEGER NOT NULL DEFAULT 0,
|
|
45
|
+
latency_s REAL,
|
|
46
|
+
ts REAL NOT NULL,
|
|
47
|
+
UNIQUE(trace_id, ts)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX IF NOT EXISTS ix_usage_conv ON usage_record(conversation_id);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS ix_usage_ts ON usage_record(ts);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS ix_usage_dep ON usage_record(deployment);
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class Totals:
|
|
58
|
+
calls: int = 0
|
|
59
|
+
priced_calls: int = 0
|
|
60
|
+
cost_usd: float = 0.0
|
|
61
|
+
prompt_tokens: int = 0
|
|
62
|
+
completion_tokens: int = 0
|
|
63
|
+
cached_tokens: int = 0
|
|
64
|
+
free_calls: int = 0
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def known_calls(self) -> int:
|
|
68
|
+
"""Calls whose cost is known: priced, or free per the proxy config."""
|
|
69
|
+
return self.priced_calls + self.free_calls
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def coverage(self) -> float:
|
|
73
|
+
"""Share of calls whose cost is known. 1.0 means trustworthy."""
|
|
74
|
+
return (self.known_calls / self.calls) if self.calls else 0.0
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def trustworthy(self) -> bool:
|
|
78
|
+
return self.calls > 0 and self.known_calls == self.calls
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def cache_hit_ratio(self) -> float:
|
|
82
|
+
return (self.cached_tokens / self.prompt_tokens) if self.prompt_tokens else 0.0
|
|
83
|
+
|
|
84
|
+
def describe_cost(self) -> str:
|
|
85
|
+
"""Never render an unpriced total as though it were a real zero."""
|
|
86
|
+
if self.calls == 0:
|
|
87
|
+
return "no calls"
|
|
88
|
+
free = (f" ({self.free_calls} call(s) free tier, per proxy config)"
|
|
89
|
+
if self.free_calls else "")
|
|
90
|
+
if self.known_calls == 0:
|
|
91
|
+
return f"unknown (0/{self.calls} calls priced)"
|
|
92
|
+
if not self.trustworthy:
|
|
93
|
+
return (f"${self.cost_usd:.4f} + unknown "
|
|
94
|
+
f"({self.known_calls}/{self.calls} known){free}")
|
|
95
|
+
return f"${self.cost_usd:.4f}{free}"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class CostLedger:
|
|
99
|
+
"""Ingests Seam A telemetry and answers what work cost."""
|
|
100
|
+
|
|
101
|
+
def __init__(self, path: str | Path):
|
|
102
|
+
self.path = Path(path)
|
|
103
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
self._db = sqlite3.connect(str(self.path), isolation_level=None)
|
|
105
|
+
self._db.row_factory = sqlite3.Row
|
|
106
|
+
self._db.executescript(SCHEMA)
|
|
107
|
+
# A ledger written before the `free` column existed. CREATE TABLE IF
|
|
108
|
+
# NOT EXISTS does not add columns, so add it, once.
|
|
109
|
+
cols = {r["name"] for r in self._db.execute("PRAGMA table_info(usage_record)")}
|
|
110
|
+
if "free" not in cols:
|
|
111
|
+
self._db.execute("ALTER TABLE usage_record ADD COLUMN "
|
|
112
|
+
"free INTEGER NOT NULL DEFAULT 0")
|
|
113
|
+
|
|
114
|
+
def close(self) -> None:
|
|
115
|
+
self._db.close()
|
|
116
|
+
|
|
117
|
+
def __enter__(self) -> "CostLedger":
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def __exit__(self, *_) -> None:
|
|
121
|
+
self.close()
|
|
122
|
+
|
|
123
|
+
# ── ingest ─────────────────────────────────────────────────────────
|
|
124
|
+
def ingest_record(self, rec: dict) -> bool:
|
|
125
|
+
"""Store one Seam A telemetry record. Idempotent on (trace_id, ts)."""
|
|
126
|
+
trace = rec.get("trace_id") or ""
|
|
127
|
+
conv, _, turn = trace.partition(":")
|
|
128
|
+
cost = rec.get("cost")
|
|
129
|
+
# Free per the proxy config: a known zero, whatever litellm said. Its
|
|
130
|
+
# list price is NOT spend -- counting it put phantom dollars against
|
|
131
|
+
# `daily_usd` on a pool that bills nothing (docs/0042 I-10).
|
|
132
|
+
free = 1 if rec.get("free") is True else 0
|
|
133
|
+
# THE Q18 DISTINCTION: a falsy cost means litellm could not price it.
|
|
134
|
+
# Storing 0.0 as though it were a measured zero is the whole hazard.
|
|
135
|
+
priced = 1 if (cost and not free) else 0
|
|
136
|
+
try:
|
|
137
|
+
self._db.execute(
|
|
138
|
+
"INSERT OR IGNORE INTO usage_record(trace_id, conversation_id, "
|
|
139
|
+
"turn_id, model, deployment, prompt_tokens, completion_tokens, "
|
|
140
|
+
"cached_tokens, cost_usd, priced, free, latency_s, ts) "
|
|
141
|
+
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
142
|
+
(trace or None, conv or None, turn or None,
|
|
143
|
+
rec.get("model"), rec.get("deployment"),
|
|
144
|
+
rec.get("prompt_tokens") or 0, rec.get("completion_tokens") or 0,
|
|
145
|
+
rec.get("cached_tokens") or 0,
|
|
146
|
+
float(cost) if priced else 0.0, priced, free,
|
|
147
|
+
rec.get("latency_s"), rec.get("ts") or time.time()))
|
|
148
|
+
return True
|
|
149
|
+
except sqlite3.Error:
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
def ingest_telemetry(self, path: str | Path) -> int:
|
|
153
|
+
"""Load a Seam A telemetry file. Returns the number of records stored."""
|
|
154
|
+
try:
|
|
155
|
+
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
156
|
+
except Exception: # noqa: BLE001
|
|
157
|
+
return 0
|
|
158
|
+
return sum(1 for r in data.get("records", []) if self.ingest_record(r))
|
|
159
|
+
|
|
160
|
+
# ── queries ────────────────────────────────────────────────────────
|
|
161
|
+
def totals(self, conversation_id: str | None = None,
|
|
162
|
+
since: float | None = None) -> Totals:
|
|
163
|
+
where, args = [], []
|
|
164
|
+
if conversation_id:
|
|
165
|
+
where.append("conversation_id=?")
|
|
166
|
+
args.append(conversation_id)
|
|
167
|
+
if since is not None:
|
|
168
|
+
where.append("ts>=?")
|
|
169
|
+
args.append(since)
|
|
170
|
+
clause = f"WHERE {' AND '.join(where)}" if where else ""
|
|
171
|
+
row = self._db.execute(
|
|
172
|
+
"SELECT COUNT(*) calls, COALESCE(SUM(priced),0) priced_calls, "
|
|
173
|
+
"COALESCE(SUM(free),0) free_calls, "
|
|
174
|
+
"COALESCE(SUM(cost_usd),0) cost, "
|
|
175
|
+
"COALESCE(SUM(prompt_tokens),0) pt, "
|
|
176
|
+
"COALESCE(SUM(completion_tokens),0) ct, "
|
|
177
|
+
"COALESCE(SUM(cached_tokens),0) cached "
|
|
178
|
+
f"FROM usage_record {clause}", args).fetchone()
|
|
179
|
+
return Totals(row["calls"], row["priced_calls"], row["cost"],
|
|
180
|
+
row["pt"], row["ct"], row["cached"], row["free_calls"])
|
|
181
|
+
|
|
182
|
+
def by_conversation(self, limit: int = 20) -> list[dict]:
|
|
183
|
+
rows = self._db.execute(
|
|
184
|
+
"SELECT conversation_id, COUNT(*) calls, SUM(priced) priced_calls, "
|
|
185
|
+
"SUM(free) free_calls, "
|
|
186
|
+
"SUM(cost_usd) cost, SUM(prompt_tokens) pt, MAX(ts) last_ts "
|
|
187
|
+
"FROM usage_record WHERE conversation_id IS NOT NULL "
|
|
188
|
+
"GROUP BY conversation_id ORDER BY last_ts DESC LIMIT ?",
|
|
189
|
+
(limit,)).fetchall()
|
|
190
|
+
return [dict(r) for r in rows]
|
|
191
|
+
|
|
192
|
+
def by_deployment(self) -> list[dict]:
|
|
193
|
+
rows = self._db.execute(
|
|
194
|
+
"SELECT deployment, COUNT(*) calls, SUM(priced) priced_calls, "
|
|
195
|
+
"SUM(free) free_calls, "
|
|
196
|
+
"SUM(cost_usd) cost, SUM(cached_tokens) cached, SUM(prompt_tokens) pt "
|
|
197
|
+
"FROM usage_record WHERE deployment IS NOT NULL "
|
|
198
|
+
"GROUP BY deployment ORDER BY calls DESC").fetchall()
|
|
199
|
+
return [dict(r) for r in rows]
|
|
200
|
+
|
|
201
|
+
def unpriced_deployments(self) -> list[str]:
|
|
202
|
+
"""Endpoints whose spend is invisible. The budget guard's blind spot."""
|
|
203
|
+
rows = self._db.execute(
|
|
204
|
+
"SELECT deployment FROM usage_record WHERE deployment IS NOT NULL "
|
|
205
|
+
"GROUP BY deployment HAVING SUM(priced)=0 AND SUM(free)=0").fetchall()
|
|
206
|
+
return [r["deployment"] for r in rows]
|
|
207
|
+
|
|
208
|
+
def cost_per_task(self, conversation_id: str) -> Totals:
|
|
209
|
+
"""The metric that matters: what one completed task cost."""
|
|
210
|
+
return self.totals(conversation_id=conversation_id)
|