agentberg 2.2.0__tar.gz → 2.3.0__tar.gz
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.
- {agentberg-2.2.0 → agentberg-2.3.0}/.github/workflows/ci.yml +3 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/CHANGELOG.md +9 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/PKG-INFO +1 -1
- agentberg-2.3.0/UPGRADING.md +147 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/agent.py +11 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/agentberg_cli/__init__.py +1 -1
- {agentberg-2.2.0 → agentberg-2.3.0}/agentberg_cli/cli.py +220 -11
- {agentberg-2.2.0 → agentberg-2.3.0}/alpaca.py +17 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/kit_manifest.json +24 -1
- {agentberg-2.2.0 → agentberg-2.3.0}/knowledge.py +1 -1
- {agentberg-2.2.0 → agentberg-2.3.0}/memory.py +10 -0
- agentberg-2.3.0/migrations.py +44 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/pyproject.toml +1 -1
- agentberg-2.3.0/scripts/validate_categories.py +64 -0
- agentberg-2.2.0/UPGRADING.md +0 -92
- {agentberg-2.2.0 → agentberg-2.3.0}/.env.example +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/.github/workflows/publish.yml +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/.gitignore +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/AGENTS.md +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/CLAUDE.md +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/CONTRIBUTING.md +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/INSTALL.md +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/LEGACY_AGENT_UPGRADE.md +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/README.md +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/RELEASING.md +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/START.md +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/agentberg.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/agentberg_cli/__main__.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/capabilities.json +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/character.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/config.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/identity.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/journal.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/llm.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/llm_providers/__init__.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/llm_providers/_resolve.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/llm_providers/claude.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/llm_providers/deepseek.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/llm_providers/gemini.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/llm_providers/openai.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/requirements.txt +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/risk.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/run.sh +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/scheduler.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/scripts/release_notes.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/setup.py +0 -0
- {agentberg-2.2.0 → agentberg-2.3.0}/structures.py +0 -0
|
@@ -5,6 +5,15 @@ All notable changes to the Agentberg kit and CLI.
|
|
|
5
5
|
This file is generated from `kit_manifest.json` — do not edit by hand.
|
|
6
6
|
Run `python scripts/release_notes.py --write` after updating the manifest.
|
|
7
7
|
|
|
8
|
+
## v2.3.0 — 2026-06-17
|
|
9
|
+
|
|
10
|
+
*Files:* agentberg_cli/cli.py, kit_manifest.json, UPGRADING.md, scripts/validate_categories.py, .github/workflows/ci.yml
|
|
11
|
+
|
|
12
|
+
- Upgrade categories — every changelog entry now carries a `category` (0/A/B). Category 0 = advisory, empty-safe, override-able (network signals/brief/alerts into the LLM prompt, outbound publishing): safe to auto-apply. A = strategy-neutral plumbing (propose-first). B = alpha/identity (never auto). See UPGRADING.md.
|
|
13
|
+
- agentberg upgrade [--auto] — new command. Without --auto it shows pending releases split into auto-eligible (Category 0) and review-needed (A/B). With --auto it applies Category 0 changes ONLY to files you have not customized, behind five gates: HTTPS trust anchor, full-folder snapshot, untouched-file check (baseline recorded at init in .agentberg_adopted.json), byte-compile-or-rollback, and a you-run empty-safe verify. Adopted version advances only when no A/B entries remain pending.
|
|
14
|
+
- init now records an adoption baseline (.agentberg_adopted.json: version + per-file hashes) so upgrade can tell an untouched file from a customized one.
|
|
15
|
+
- CI guard scripts/validate_categories.py — fails the build if any entry is mis-tagged or a Category 0 entry touches execution/identity/strategy files (risk.py, scheduler.py, alpaca.py, config.py, identity.py, …). Keeps the auto-apply promise machine-checkable.
|
|
16
|
+
|
|
8
17
|
## v2.2.0 — 2026-06-17
|
|
9
18
|
|
|
10
19
|
*Files:* agent.py, llm.py, kit_manifest.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentberg
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Install, scaffold, run, and chat with your Agentberg trading agent.
|
|
5
5
|
Project-URL: Homepage, https://agentberg.ai
|
|
6
6
|
Project-URL: Source, https://github.com/ganeshnallasivam-cell/agentberg-starter
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Keeping this kit current — the reconciliation procedure
|
|
2
|
+
|
|
3
|
+
This is your standing procedure for adopting new kit versions. It is *kit-version*
|
|
4
|
+
reconciliation — distinct from `reconcile_ledger()`, which reconciles your trades
|
|
5
|
+
against the broker. Follow this whenever the kit manifest shows you are behind.
|
|
6
|
+
|
|
7
|
+
## Always-automatic (not code — runtime data)
|
|
8
|
+
|
|
9
|
+
These flow every session, no upgrade needed:
|
|
10
|
+
|
|
11
|
+
- Inbound network data — blocked sectors, regime consensus, skill packs, `/guide`.
|
|
12
|
+
- Your outbound **weekly knowledge upload** (`maybe_upload()`), in your window.
|
|
13
|
+
- The **notification** that a newer kit exists — you poll the manifest and see it.
|
|
14
|
+
|
|
15
|
+
## The four categories — every release entry is tagged
|
|
16
|
+
|
|
17
|
+
Each entry in `kit_manifest.json`'s `changelog` carries a **`category`**. It tells you
|
|
18
|
+
how the change may be adopted. The line is **code logic vs advisory context**:
|
|
19
|
+
|
|
20
|
+
| Cat | Meaning | How it's adopted |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| **0** | **Advisory context, empty-safe, override-able.** Network signals/brief/alerts fed to the LLM prompt; outbound publishing. Changes no code logic; with the network off, behavior is byte-identical. | **Auto-apply** — `agentberg upgrade --auto`, behind gates. |
|
|
23
|
+
| **A** | **Strategy-neutral plumbing.** Broker reconcile, scheduling, circuit breakers, atomic multi-leg, structure gates, additive schema. Changes behavior on purpose (e.g. a bug fix), so it can't be proven inert. | Propose-first — the manual procedure below. |
|
|
24
|
+
| **B** | **Alpha / learning / identity — DO NOT auto-touch.** Signal logic, scoring math, thresholds, sizing, stops/TP, sort keys, regime params, magic numbers, `agent.db`, `register()`/identity. | Manual, deliberate, per-item — never auto. |
|
|
25
|
+
| **C** | **Merge-not-replace.** A file *you customized* that also got a safe (0/A) update. | Take only the new mechanism; keep your params. |
|
|
26
|
+
|
|
27
|
+
Why 0 is safe to auto-apply: the worst case is the LLM sees extra advisory text it is
|
|
28
|
+
free to ignore, and if anything breaks it rolls back. Why A is **not** auto (even
|
|
29
|
+
though it's "safe"): a plumbing fix changes behavior by design, so no machine can
|
|
30
|
+
prove it harmless — a bad reconcile fix would auto-ship to every agent at once. **0 is
|
|
31
|
+
a strict subset of A**, not all of it.
|
|
32
|
+
|
|
33
|
+
## The fast path — auto-apply Category 0
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
agentberg upgrade # show what's pending (0 auto-eligible, A/B for review)
|
|
37
|
+
agentberg upgrade --auto # apply Category 0 to untouched files, behind the gates
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`--auto` enforces five gates, every one machine-checkable:
|
|
41
|
+
|
|
42
|
+
1. **Trust anchor** — the kit is fetched over HTTPS from the official source.
|
|
43
|
+
2. **Snapshot** — your whole folder is copied to `…-backup-<ts>` before any write.
|
|
44
|
+
3. **Untouched-file only** — a file is replaced *only* if your copy still matches the
|
|
45
|
+
baseline recorded at `init` (`.agentberg_adopted.json`). If you customized it, it's
|
|
46
|
+
**skipped** and flagged for the manual procedure (that's a category-C situation).
|
|
47
|
+
4. **Compile gate** — applied Python is byte-compiled; any failure rolls the whole
|
|
48
|
+
folder back from the snapshot.
|
|
49
|
+
5. **Empty-safe verify (yours to run)** — after apply, run `agentberg run` once. With
|
|
50
|
+
the network off, trade selection must be unchanged (Category 0 is advisory). If it
|
|
51
|
+
changed, something was mis-tagged — restore from the snapshot.
|
|
52
|
+
|
|
53
|
+
The adopted version only advances to the latest once no Category A/B entries remain
|
|
54
|
+
pending, so those stay flagged until you review them deliberately.
|
|
55
|
+
|
|
56
|
+
> Note: today's trust anchor is HTTPS + the recorded baseline. Per-release Ed25519
|
|
57
|
+
> *kit* signing (so a compromised source can't push code fleet-wide) is the next
|
|
58
|
+
> hardening step — auto-apply across many agents makes the signing key a crown jewel.
|
|
59
|
+
|
|
60
|
+
## When to run the manual procedure (Category A / B)
|
|
61
|
+
|
|
62
|
+
Poll `GET /kit/manifest`. For any pending entry tagged **A** or **B** (or a Category 0
|
|
63
|
+
file you customized → **C**), run the procedure below against the changelog delta. If
|
|
64
|
+
the only pending entries are Category 0 with untouched files, `upgrade --auto` already
|
|
65
|
+
handled them — you're done.
|
|
66
|
+
|
|
67
|
+
## The procedure (propose-first — you never apply unreviewed)
|
|
68
|
+
|
|
69
|
+
**STEP 0 — Snapshot first.** Copy your entire agent folder as a backup before
|
|
70
|
+
touching anything. Example:
|
|
71
|
+
```
|
|
72
|
+
cp -r ~/agentberg-trader ~/agentberg-trader-backup-$(date +%Y%m%d)
|
|
73
|
+
```
|
|
74
|
+
Confirm the backup folder exists before proceeding.
|
|
75
|
+
|
|
76
|
+
**STEP 1 — Scope from the manifest.** Read `manifest.version` + `changelog`. Diff
|
|
77
|
+
only the delta between your last-adopted version and the latest — not the whole tree.
|
|
78
|
+
Fetch the changed kit files.
|
|
79
|
+
|
|
80
|
+
**STEP 2 — Build the gap map.** For each changed file/capability, classify it as
|
|
81
|
+
`IDENTICAL` / `YOU-AHEAD` / `KIT-AHEAD (new)` / `DIVERGENT`. Edit nothing.
|
|
82
|
+
|
|
83
|
+
**STEP 3 — Classify each delta by impact.**
|
|
84
|
+
|
|
85
|
+
- **A. Strategy-neutral (safe to propose)** — execution plumbing, broker
|
|
86
|
+
reconciliation, atomic multi-leg open/close, defined-risk structure gates, circuit
|
|
87
|
+
breakers, scheduling, network/client wrappers, knowledge-upload mechanics, additive
|
|
88
|
+
memory-schema columns that do not reset data, and **empty-safe, override-able
|
|
89
|
+
advisory context fed to the LLM prompt** (network signals, brief verdict, consensus
|
|
90
|
+
alerts, blocked-sectors, rotation/narrative). Advisory context is signal, not
|
|
91
|
+
decision: it changes no code logic, the agent stays free to override it, and the
|
|
92
|
+
rule-based fallback ignores it entirely. This is the same pattern `blocked_sectors`
|
|
93
|
+
has always used — adding more of it is Category A.
|
|
94
|
+
- **B. Alpha / learning / identity — DO NOT TOUCH** — the distinction from A is
|
|
95
|
+
**code logic vs advisory context**: B is changing how the decision is *computed* —
|
|
96
|
+
signal logic, indicators, thresholds, watchlist, sizing, stops/TP, scoring math,
|
|
97
|
+
sort keys, deterministic filters, regime params, DTE/delta, any magic-number
|
|
98
|
+
parameter, your `agent.db` / learned state, and specifically:
|
|
99
|
+
- **`register()` / auto-register: never call it.** It has no ownership check and
|
|
100
|
+
will hand you a suffixed id, orphaning your reputation, findings, and votes. Pin
|
|
101
|
+
your existing id.
|
|
102
|
+
- **persona/character into a scoring/filter rule** — gate the universe only, if at
|
|
103
|
+
all. (Persona as *prompt context* is Category A; persona as a deterministic
|
|
104
|
+
filter is B.)
|
|
105
|
+
- **changing the ranking scoring math / thresholds / sort keys.** Adding advisory
|
|
106
|
+
text the LLM may weigh is A; changing how candidates are deterministically scored
|
|
107
|
+
or ordered in code is B.
|
|
108
|
+
- **C. Merge-not-replace** — a file you have customized that also got a safe update:
|
|
109
|
+
take ONLY the new mechanism, keep your own parameters and logic. Never overwrite a
|
|
110
|
+
whole customized file.
|
|
111
|
+
|
|
112
|
+
When unsure whether something is strategy-neutral, label it **B** and flag it for
|
|
113
|
+
review. Bias toward leaving yourself unchanged.
|
|
114
|
+
|
|
115
|
+
**STEP 4 — Propose, do not apply.** Produce an adoption plan covering only category A
|
|
116
|
+
items and the mechanism-only part of category C. For each: the file, what changes,
|
|
117
|
+
why it is strategy-neutral, and how you would verify it. Then **stop**. Apply
|
|
118
|
+
nothing. Never reset/overwrite `agent.db`, learned state, config magic numbers, or
|
|
119
|
+
identity.
|
|
120
|
+
|
|
121
|
+
## Output for review
|
|
122
|
+
|
|
123
|
+
1. The manifest delta (`from-version → to-version`) and the gap map table.
|
|
124
|
+
2. The proposed adoption list — each with file, change, neutrality rationale, and
|
|
125
|
+
planned verification.
|
|
126
|
+
3. What you are deliberately **not** adopting and why (category B + anything
|
|
127
|
+
ambiguous you flagged).
|
|
128
|
+
4. Explicit confirmation that you applied nothing and your STEP 0 snapshot exists.
|
|
129
|
+
|
|
130
|
+
## After approval
|
|
131
|
+
|
|
132
|
+
Apply only the approved subset, surgically (merge-not-replace). Run a dry/paper cycle
|
|
133
|
+
and verify by what you adopted:
|
|
134
|
+
|
|
135
|
+
- **If you adopted only non-advisory category-A items** (plumbing, reconcile,
|
|
136
|
+
scheduling, gates), confirm your strategy selects the **same trades as before** —
|
|
137
|
+
the only permitted behavior change is unsafe orders/closes now being blocked. If
|
|
138
|
+
trade selection changed at all, you adopted a category-B item by mistake — restore
|
|
139
|
+
the affected file(s) from your Step 0 backup.
|
|
140
|
+
- **If you adopted an advisory-context item** (network signals, brief, alerts into
|
|
141
|
+
the LLM prompt), trade selection MAY shift — that is the intended effect of giving
|
|
142
|
+
the LLM more context, and is not a category-B violation. Instead verify: with the
|
|
143
|
+
network unavailable / `LLM_REASONING=off`, behavior is unchanged from before (proves
|
|
144
|
+
it is empty-safe and override-able), and no scoring math, threshold, or sort key in
|
|
145
|
+
code was altered.
|
|
146
|
+
|
|
147
|
+
On success, **record the new adopted kit version** so your next run is incremental.
|
|
@@ -20,6 +20,7 @@ import character
|
|
|
20
20
|
import config as cfg
|
|
21
21
|
import knowledge
|
|
22
22
|
import memory
|
|
23
|
+
import migrations
|
|
23
24
|
import risk
|
|
24
25
|
import structures
|
|
25
26
|
from agentberg import AgentbergClient
|
|
@@ -70,11 +71,18 @@ def reconcile_ledger():
|
|
|
70
71
|
return
|
|
71
72
|
held = _alpaca.get_position_symbols()
|
|
72
73
|
reconciled = 0
|
|
74
|
+
voided = 0
|
|
73
75
|
for t in open_trades:
|
|
74
76
|
legs = [s for s in (t.get("long_symbol"), t.get("short_symbol")) if s] or [t["symbol"]]
|
|
75
77
|
if any(s in held for s in legs):
|
|
76
78
|
continue # still open at the broker
|
|
77
79
|
|
|
80
|
+
# Entry order never filled — phantom open. Void it, never publish.
|
|
81
|
+
if not _alpaca.was_entry_filled(t.get("order_id")):
|
|
82
|
+
memory.void_trade(t["id"])
|
|
83
|
+
voided += 1
|
|
84
|
+
continue
|
|
85
|
+
|
|
78
86
|
long_sym = t.get("long_symbol") or t["symbol"]
|
|
79
87
|
fill = _alpaca.get_last_fill(long_sym, side="sell")
|
|
80
88
|
exit_price = float(fill.get("filled_avg_price") or 0) if fill else 0.0
|
|
@@ -93,12 +101,15 @@ def reconcile_ledger():
|
|
|
93
101
|
reconciled += 1
|
|
94
102
|
if reconciled:
|
|
95
103
|
print(f"[reconcile] Closed {reconciled} trade(s) from broker truth (server-side/offline exits)")
|
|
104
|
+
if voided:
|
|
105
|
+
print(f"[reconcile] Voided {voided} phantom trade(s) — entry order never filled")
|
|
96
106
|
|
|
97
107
|
|
|
98
108
|
def run_session():
|
|
99
109
|
"""
|
|
100
110
|
Full trading cycle. Call once at market open and once at close.
|
|
101
111
|
"""
|
|
112
|
+
migrations.run()
|
|
102
113
|
memory.init_db()
|
|
103
114
|
mode = cfg.STRATEGY_MODE
|
|
104
115
|
print(f"\n[agent] {datetime.datetime.now():%Y-%m-%d %H:%M} | ID: {cfg.AGENT_ID} | Mode: {mode}")
|
|
@@ -15,6 +15,7 @@ stdlib-only so it installs cleanly via pipx/uv with no build step.
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import argparse
|
|
18
|
+
import hashlib
|
|
18
19
|
import io
|
|
19
20
|
import json
|
|
20
21
|
import os
|
|
@@ -22,6 +23,8 @@ import shutil
|
|
|
22
23
|
import subprocess
|
|
23
24
|
import sys
|
|
24
25
|
import tarfile
|
|
26
|
+
import tempfile
|
|
27
|
+
import time
|
|
25
28
|
import urllib.request
|
|
26
29
|
from pathlib import Path
|
|
27
30
|
|
|
@@ -95,12 +98,19 @@ def _folder(args) -> Path:
|
|
|
95
98
|
|
|
96
99
|
# ── scaffolding ─────────────────────────────────────────────────────────────────
|
|
97
100
|
|
|
98
|
-
def
|
|
99
|
-
"""Download the latest kit tarball
|
|
100
|
-
print(" fetching the latest kit…")
|
|
101
|
+
def _fetch_kit_bytes() -> bytes:
|
|
102
|
+
"""Download the latest kit tarball over HTTPS (GitHub is the trust anchor)."""
|
|
101
103
|
req = urllib.request.Request(KIT_TARBALL, headers={"User-Agent": "agentberg-cli"})
|
|
102
104
|
with urllib.request.urlopen(req, timeout=60) as resp: # follows redirects
|
|
103
|
-
|
|
105
|
+
return resp.read()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _extract_kit(data: bytes, target: Path, exclude: bool = True) -> None:
|
|
109
|
+
"""Extract the editable kit files from a tarball into target (path-traversal safe).
|
|
110
|
+
|
|
111
|
+
exclude=True drops CLI/dev/packaging files (for the user's folder); exclude=False
|
|
112
|
+
extracts everything (used when staging the new kit to a temp dir for upgrade).
|
|
113
|
+
"""
|
|
104
114
|
target.mkdir(parents=True, exist_ok=True)
|
|
105
115
|
target_root = target.resolve()
|
|
106
116
|
with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as tar:
|
|
@@ -108,7 +118,7 @@ def _download_kit(target: Path) -> None:
|
|
|
108
118
|
root = members[0].name.split("/")[0] if members else ""
|
|
109
119
|
for m in members:
|
|
110
120
|
rel = m.name[len(root) + 1:] if m.name.startswith(root + "/") else m.name
|
|
111
|
-
if not rel or rel.split("/")[0] in _SCAFFOLD_EXCLUDE:
|
|
121
|
+
if not rel or (exclude and rel.split("/")[0] in _SCAFFOLD_EXCLUDE):
|
|
112
122
|
continue
|
|
113
123
|
dest = (target / rel).resolve()
|
|
114
124
|
if not str(dest).startswith(str(target_root)):
|
|
@@ -122,6 +132,62 @@ def _download_kit(target: Path) -> None:
|
|
|
122
132
|
dest.write_bytes(f.read())
|
|
123
133
|
|
|
124
134
|
|
|
135
|
+
def _download_kit(target: Path) -> None:
|
|
136
|
+
"""Download the latest kit tarball and extract the editable files into target."""
|
|
137
|
+
print(" fetching the latest kit…")
|
|
138
|
+
_extract_kit(_fetch_kit_bytes(), target)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ── upgrade (pull-to-review + Category 0 auto-apply) ──────────────────────────────
|
|
142
|
+
|
|
143
|
+
ADOPTED_FILE = ".agentberg_adopted.json"
|
|
144
|
+
# Folder entries that are local state, never kit code — excluded from baselining.
|
|
145
|
+
_UPGRADE_IGNORE = {".env", ".git", "__pycache__", "logs", "agent.db", "agent.db-journal",
|
|
146
|
+
".agent_key", ADOPTED_FILE}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _vtuple(v: str) -> tuple:
|
|
150
|
+
return tuple(int(x) if x.isdigit() else 0 for x in str(v).split("."))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _sha256(path: Path) -> str:
|
|
154
|
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _kit_file_hashes(target: Path) -> dict:
|
|
158
|
+
"""sha256 of every kit file in the folder, by POSIX-relative path."""
|
|
159
|
+
hashes = {}
|
|
160
|
+
for p in sorted(target.rglob("*")):
|
|
161
|
+
if not p.is_file():
|
|
162
|
+
continue
|
|
163
|
+
rel = p.relative_to(target).as_posix()
|
|
164
|
+
top = rel.split("/")[0]
|
|
165
|
+
if top in _UPGRADE_IGNORE or top in _SCAFFOLD_EXCLUDE or rel.endswith(".pyc"):
|
|
166
|
+
continue
|
|
167
|
+
if rel.endswith(".command") or rel.endswith(".bat"): # generated launcher
|
|
168
|
+
continue
|
|
169
|
+
hashes[rel] = _sha256(p)
|
|
170
|
+
return hashes
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _load_adopted(folder: Path) -> dict:
|
|
174
|
+
try:
|
|
175
|
+
return json.loads((folder / ADOPTED_FILE).read_text())
|
|
176
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
177
|
+
return {}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _save_adopted(folder: Path, data: dict) -> None:
|
|
181
|
+
(folder / ADOPTED_FILE).write_text(json.dumps(data, indent=2))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _folder_kit_version(folder: Path) -> str:
|
|
185
|
+
try:
|
|
186
|
+
return json.loads((folder / "kit_manifest.json").read_text()).get("version", "0.0.0")
|
|
187
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
188
|
+
return "0.0.0"
|
|
189
|
+
|
|
190
|
+
|
|
125
191
|
# ── .env ────────────────────────────────────────────────────────────────────────
|
|
126
192
|
|
|
127
193
|
def _upsert(text: str, key: str, value: str) -> str:
|
|
@@ -273,6 +339,10 @@ def cmd_init(args) -> None:
|
|
|
273
339
|
sys.exit(f"{target} exists and is not empty — use --force to overwrite or pick --dir.")
|
|
274
340
|
|
|
275
341
|
_download_kit(target)
|
|
342
|
+
# Record the adopted baseline: version + per-file hashes. Upgrade uses this to tell
|
|
343
|
+
# an untouched file (safe to auto-replace) from one the agent has customized.
|
|
344
|
+
_save_adopted(target, {"version": _folder_kit_version(target),
|
|
345
|
+
"files": _kit_file_hashes(target)})
|
|
276
346
|
llm = _choose_llm(args.llm, args.no_input)
|
|
277
347
|
agent_id = _prompt("AGENT_ID (your agent's unique name): ", args.agent_id, args.no_input)
|
|
278
348
|
key = _prompt("Alpaca PAPER API key (enter to skip): ", args.alpaca_key, args.no_input)
|
|
@@ -339,13 +409,146 @@ def cmd_chat(args) -> None:
|
|
|
339
409
|
subprocess.run([shell, "-ilc", f'cd "{folder}" && exec {cmd}'], cwd=folder)
|
|
340
410
|
|
|
341
411
|
|
|
342
|
-
def
|
|
412
|
+
def _pending_entries(new_manifest: dict, adopted_version: str) -> list[dict]:
|
|
413
|
+
"""Changelog entries newer than the adopted version, oldest-first."""
|
|
414
|
+
av = _vtuple(adopted_version)
|
|
415
|
+
entries = [e for e in new_manifest.get("changelog", []) if _vtuple(e.get("version", "0")) > av]
|
|
416
|
+
return sorted(entries, key=lambda e: _vtuple(e.get("version", "0")))
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def cmd_upgrade(args) -> None:
|
|
420
|
+
"""Pull-to-review the latest kit. With --auto, apply Category 0 (advisory,
|
|
421
|
+
empty-safe, override-able) changes to UNTOUCHED files behind snapshot + verify."""
|
|
343
422
|
folder = _folder(args)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
423
|
+
auto = getattr(args, "auto", False)
|
|
424
|
+
|
|
425
|
+
adopted = _load_adopted(folder)
|
|
426
|
+
if not adopted:
|
|
427
|
+
# No baseline (older folder) — record the current state and stop. Without a
|
|
428
|
+
# baseline we cannot tell a customized file from an untouched one.
|
|
429
|
+
cur_ver = _folder_kit_version(folder)
|
|
430
|
+
_save_adopted(folder, {"version": cur_ver, "files": _kit_file_hashes(folder)})
|
|
431
|
+
print(f"Recorded current folder as baseline (v{cur_ver}). Re-run to upgrade.")
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
print(" fetching the latest kit…")
|
|
435
|
+
try:
|
|
436
|
+
data = _fetch_kit_bytes()
|
|
437
|
+
except Exception as e:
|
|
438
|
+
sys.exit(f"Could not fetch the kit: {e}")
|
|
439
|
+
|
|
440
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
441
|
+
newdir = Path(tmp) / "kit"
|
|
442
|
+
_extract_kit(data, newdir, exclude=False)
|
|
443
|
+
try:
|
|
444
|
+
new_manifest = json.loads((newdir / "kit_manifest.json").read_text())
|
|
445
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
446
|
+
sys.exit("Latest kit has no readable manifest — aborting.")
|
|
447
|
+
|
|
448
|
+
latest = new_manifest.get("version", "0.0.0")
|
|
449
|
+
if _vtuple(latest) <= _vtuple(adopted["version"]):
|
|
450
|
+
print(f"Already current (v{adopted['version']}).")
|
|
451
|
+
return
|
|
452
|
+
|
|
453
|
+
pending = _pending_entries(new_manifest, adopted["version"])
|
|
454
|
+
cat0 = [e for e in pending if str(e.get("category")) == "0"]
|
|
455
|
+
review = [e for e in pending if str(e.get("category")) != "0"]
|
|
456
|
+
|
|
457
|
+
print(f"\nUpgrade available: v{adopted['version']} → v{latest}")
|
|
458
|
+
print(f" Category 0 (auto-apply, advisory/empty-safe): {len(cat0)} version(s)")
|
|
459
|
+
print(f" Category A/B (manual review per UPGRADING.md): {len(review)} version(s)")
|
|
460
|
+
|
|
461
|
+
if not auto:
|
|
462
|
+
for e in cat0:
|
|
463
|
+
print(f"\n [0] v{e['version']} — would auto-apply:")
|
|
464
|
+
for line in e.get("added", []):
|
|
465
|
+
print(f" • {line[:100]}")
|
|
466
|
+
if review:
|
|
467
|
+
print("\n Needs your review (run UPGRADING.md procedure):")
|
|
468
|
+
for e in review:
|
|
469
|
+
print(f" [{e.get('category','?')}] v{e['version']} ({', '.join(e.get('files', []))})")
|
|
470
|
+
print("\nRun `agentberg upgrade --auto` to apply the Category 0 changes safely.")
|
|
471
|
+
return
|
|
472
|
+
|
|
473
|
+
# ── AUTO-APPLY Category 0 ────────────────────────────────────────────────
|
|
474
|
+
if not cat0:
|
|
475
|
+
print("\nNothing to auto-apply (no Category 0 changes pending).")
|
|
476
|
+
if review:
|
|
477
|
+
print("Pending A/B changes need manual review — see UPGRADING.md.")
|
|
478
|
+
return
|
|
479
|
+
|
|
480
|
+
# GATE 1: snapshot the whole folder before touching anything.
|
|
481
|
+
ts = time.strftime("%Y%m%d-%H%M%S")
|
|
482
|
+
backup = folder.parent / f"{folder.name}-backup-{ts}"
|
|
483
|
+
shutil.copytree(folder, backup)
|
|
484
|
+
print(f"\n snapshot: {backup}")
|
|
485
|
+
|
|
486
|
+
# Files in scope = every file named by a Category 0 entry, de-duped.
|
|
487
|
+
files0: list[str] = []
|
|
488
|
+
for e in cat0:
|
|
489
|
+
for rel in e.get("files", []):
|
|
490
|
+
if rel not in files0:
|
|
491
|
+
files0.append(rel)
|
|
492
|
+
|
|
493
|
+
applied, skipped, missing = [], [], []
|
|
494
|
+
for rel in files0:
|
|
495
|
+
src = newdir / rel
|
|
496
|
+
if not src.is_file():
|
|
497
|
+
missing.append(rel)
|
|
498
|
+
continue
|
|
499
|
+
cur = folder / rel
|
|
500
|
+
base_hash = adopted["files"].get(rel)
|
|
501
|
+
if cur.exists():
|
|
502
|
+
cur_hash = _sha256(cur)
|
|
503
|
+
if cur_hash == _sha256(src):
|
|
504
|
+
continue # already identical — no-op
|
|
505
|
+
# GATE 2: only replace files the agent has NOT customized.
|
|
506
|
+
if base_hash is not None and cur_hash != base_hash:
|
|
507
|
+
skipped.append(rel)
|
|
508
|
+
continue
|
|
509
|
+
cur.parent.mkdir(parents=True, exist_ok=True)
|
|
510
|
+
shutil.copy2(src, cur)
|
|
511
|
+
applied.append(rel)
|
|
512
|
+
|
|
513
|
+
# GATE 3: byte-compile any applied Python — a broken file rolls everything back.
|
|
514
|
+
pyfiles = [str(folder / r) for r in applied if r.endswith(".py")]
|
|
515
|
+
if pyfiles:
|
|
516
|
+
res = subprocess.run([sys.executable, "-m", "py_compile", *pyfiles],
|
|
517
|
+
capture_output=True, text=True)
|
|
518
|
+
if res.returncode != 0:
|
|
519
|
+
shutil.rmtree(folder)
|
|
520
|
+
shutil.move(str(backup), str(folder))
|
|
521
|
+
sys.exit(f"Compile failed after apply — rolled back from snapshot.\n{res.stderr}")
|
|
522
|
+
|
|
523
|
+
# Record new state. Advance the adopted version to latest ONLY if no A/B
|
|
524
|
+
# entries are still pending; otherwise keep it pinned so they stay flagged.
|
|
525
|
+
for rel in applied:
|
|
526
|
+
adopted["files"][rel] = _sha256(folder / rel)
|
|
527
|
+
if not review:
|
|
528
|
+
adopted["version"] = latest
|
|
529
|
+
_save_adopted(folder, adopted)
|
|
530
|
+
|
|
531
|
+
print(f"\n✓ Applied {len(applied)} file(s) from {len(cat0)} Category 0 release(s).")
|
|
532
|
+
for rel in applied:
|
|
533
|
+
print(f" updated {rel}")
|
|
534
|
+
for rel in skipped:
|
|
535
|
+
print(f" skipped {rel} (you customized it — review manually)")
|
|
536
|
+
for rel in missing:
|
|
537
|
+
print(f" missing {rel} (not in latest kit — skipped)")
|
|
538
|
+
if review:
|
|
539
|
+
print(f"\n {len(review)} Category A/B release(s) still need manual review (UPGRADING.md):")
|
|
540
|
+
for e in review:
|
|
541
|
+
print(f" [{e.get('category','?')}] v{e['version']}")
|
|
542
|
+
print(f" Adopted version stays at v{adopted['version']} until those are reviewed.")
|
|
543
|
+
else:
|
|
544
|
+
print(f"\n Now at v{latest}.")
|
|
545
|
+
print(f"\n Verify: `agentberg run` once. With the network off, behavior should be")
|
|
546
|
+
print(f" unchanged (Category 0 is advisory). Snapshot kept at {backup}")
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def cmd_update(args) -> None:
|
|
550
|
+
# `update` is the propose-only view; `upgrade --auto` applies Category 0.
|
|
551
|
+
cmd_upgrade(args)
|
|
349
552
|
|
|
350
553
|
|
|
351
554
|
def main(argv=None) -> None:
|
|
@@ -373,6 +576,12 @@ def main(argv=None) -> None:
|
|
|
373
576
|
sp.add_argument("--dir", help="trader folder (default: the one from init)")
|
|
374
577
|
sp.set_defaults(func=fn)
|
|
375
578
|
|
|
579
|
+
pu = sub.add_parser("upgrade", help="upgrade the kit; --auto applies Category 0 safely")
|
|
580
|
+
pu.add_argument("--dir", help="trader folder (default: the one from init)")
|
|
581
|
+
pu.add_argument("--auto", action="store_true",
|
|
582
|
+
help="auto-apply Category 0 (advisory, empty-safe) changes to untouched files")
|
|
583
|
+
pu.set_defaults(func=cmd_upgrade)
|
|
584
|
+
|
|
376
585
|
args = p.parse_args(argv)
|
|
377
586
|
args.func(args)
|
|
378
587
|
|
|
@@ -232,6 +232,23 @@ class AlpacaClient:
|
|
|
232
232
|
except Exception:
|
|
233
233
|
return set()
|
|
234
234
|
|
|
235
|
+
def get_order(self, order_id: str) -> dict | None:
|
|
236
|
+
"""Look up a single order by id. Returns None if not found or on error."""
|
|
237
|
+
try:
|
|
238
|
+
return self._get(f"/v2/orders/{order_id}")
|
|
239
|
+
except Exception:
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
def was_entry_filled(self, order_id: str | None) -> bool:
|
|
243
|
+
"""True if the entry order reached 'filled' status.
|
|
244
|
+
Unknown/missing order_id returns True (safe default — don't void what we can't confirm)."""
|
|
245
|
+
if not order_id:
|
|
246
|
+
return True
|
|
247
|
+
order = self.get_order(order_id)
|
|
248
|
+
if order is None:
|
|
249
|
+
return True
|
|
250
|
+
return order.get("status") == "filled"
|
|
251
|
+
|
|
235
252
|
def get_last_fill(self, symbol: str, side: str | None = None, days: int = 60) -> dict | None:
|
|
236
253
|
"""
|
|
237
254
|
Most recent filled order for a symbol (optionally a given side), newest first.
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
2
|
+
"version": "2.3.0",
|
|
3
3
|
"released": "2026-06-17",
|
|
4
4
|
"changelog": [
|
|
5
|
+
{
|
|
6
|
+
"version": "2.3.0",
|
|
7
|
+
"category": "A",
|
|
8
|
+
"date": "2026-06-17",
|
|
9
|
+
"files": ["agentberg_cli/cli.py", "kit_manifest.json", "UPGRADING.md", "scripts/validate_categories.py", ".github/workflows/ci.yml"],
|
|
10
|
+
"added": [
|
|
11
|
+
"Upgrade categories — every changelog entry now carries a `category` (0/A/B). Category 0 = advisory, empty-safe, override-able (network signals/brief/alerts into the LLM prompt, outbound publishing): safe to auto-apply. A = strategy-neutral plumbing (propose-first). B = alpha/identity (never auto). See UPGRADING.md.",
|
|
12
|
+
"agentberg upgrade [--auto] — new command. Without --auto it shows pending releases split into auto-eligible (Category 0) and review-needed (A/B). With --auto it applies Category 0 changes ONLY to files you have not customized, behind five gates: HTTPS trust anchor, full-folder snapshot, untouched-file check (baseline recorded at init in .agentberg_adopted.json), byte-compile-or-rollback, and a you-run empty-safe verify. Adopted version advances only when no A/B entries remain pending.",
|
|
13
|
+
"init now records an adoption baseline (.agentberg_adopted.json: version + per-file hashes) so upgrade can tell an untouched file from a customized one.",
|
|
14
|
+
"CI guard scripts/validate_categories.py — fails the build if any entry is mis-tagged or a Category 0 entry touches execution/identity/strategy files (risk.py, scheduler.py, alpaca.py, config.py, identity.py, …). Keeps the auto-apply promise machine-checkable."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
5
17
|
{
|
|
6
18
|
"version": "2.2.0",
|
|
19
|
+
"category": "0",
|
|
7
20
|
"date": "2026-06-17",
|
|
8
21
|
"files": ["agent.py", "llm.py", "kit_manifest.json"],
|
|
9
22
|
"added": [
|
|
@@ -14,6 +27,7 @@
|
|
|
14
27
|
},
|
|
15
28
|
{
|
|
16
29
|
"version": "2.1.0",
|
|
30
|
+
"category": "0",
|
|
17
31
|
"date": "2026-06-17",
|
|
18
32
|
"files": ["agent.py", "memory.py", "kit_manifest.json"],
|
|
19
33
|
"added": [
|
|
@@ -24,6 +38,7 @@
|
|
|
24
38
|
},
|
|
25
39
|
{
|
|
26
40
|
"version": "2.0.0",
|
|
41
|
+
"category": "A",
|
|
27
42
|
"date": "2026-06-17",
|
|
28
43
|
"files": ["agent.py", "alpaca.py", "scheduler.py", "config.py", "knowledge.py", "kit_manifest.json"],
|
|
29
44
|
"added": [
|
|
@@ -39,6 +54,7 @@
|
|
|
39
54
|
},
|
|
40
55
|
{
|
|
41
56
|
"version": "1.9.0",
|
|
57
|
+
"category": "A",
|
|
42
58
|
"date": "2026-06-17",
|
|
43
59
|
"files": ["alpaca.py", "agent.py", "knowledge.py", "kit_manifest.json"],
|
|
44
60
|
"added": [
|
|
@@ -50,6 +66,7 @@
|
|
|
50
66
|
},
|
|
51
67
|
{
|
|
52
68
|
"version": "1.8.0",
|
|
69
|
+
"category": "A",
|
|
53
70
|
"date": "2026-06-17",
|
|
54
71
|
"files": ["run.sh", "scheduler.py", "agentberg_cli/cli.py", "knowledge.py", "kit_manifest.json", "agent.py", "agentberg.py", "alpaca.py", "identity.py", "llm.py", "character.py", "config.py", "AGENTS.md"],
|
|
55
72
|
"added": [
|
|
@@ -70,6 +87,7 @@
|
|
|
70
87
|
},
|
|
71
88
|
{
|
|
72
89
|
"version": "1.6.0",
|
|
90
|
+
"category": "0",
|
|
73
91
|
"date": "2026-06-17",
|
|
74
92
|
"files": ["agent.py", "agentberg.py", "agentberg_cli/cli.py", "knowledge.py", "kit_manifest.json"],
|
|
75
93
|
"added": [
|
|
@@ -80,6 +98,7 @@
|
|
|
80
98
|
},
|
|
81
99
|
{
|
|
82
100
|
"version": "1.5.0",
|
|
101
|
+
"category": "A",
|
|
83
102
|
"date": "2026-06-14",
|
|
84
103
|
"files": ["identity.py", "agentberg.py", "agent.py", "knowledge.py", "kit_manifest.json"],
|
|
85
104
|
"added": [
|
|
@@ -88,6 +107,7 @@
|
|
|
88
107
|
},
|
|
89
108
|
{
|
|
90
109
|
"version": "1.3.0",
|
|
110
|
+
"category": "A",
|
|
91
111
|
"date": "2026-06-14",
|
|
92
112
|
"files": ["llm.py", "llm_providers/", "structures.py", "agent.py", "alpaca.py", "memory.py", "knowledge.py", "kit_manifest.json"],
|
|
93
113
|
"added": [
|
|
@@ -97,6 +117,7 @@
|
|
|
97
117
|
},
|
|
98
118
|
{
|
|
99
119
|
"version": "1.2.0",
|
|
120
|
+
"category": "A",
|
|
100
121
|
"date": "2026-06-13",
|
|
101
122
|
"files": ["knowledge.py", "capabilities.json", "UPGRADING.md", "kit_manifest.json"],
|
|
102
123
|
"added": [
|
|
@@ -106,6 +127,7 @@
|
|
|
106
127
|
},
|
|
107
128
|
{
|
|
108
129
|
"version": "1.1.0",
|
|
130
|
+
"category": "B",
|
|
109
131
|
"date": "2026-06-12",
|
|
110
132
|
"files": ["agent.py", "character.py", "setup.py", "journal.py", "memory.py", "agentberg.py", "AGENTS.md"],
|
|
111
133
|
"added": [
|
|
@@ -114,6 +136,7 @@
|
|
|
114
136
|
},
|
|
115
137
|
{
|
|
116
138
|
"version": "1.0.0",
|
|
139
|
+
"category": "A",
|
|
117
140
|
"date": "2026-06-08",
|
|
118
141
|
"added": [
|
|
119
142
|
"Initial starter agent — Alpaca paper trading, Agentberg findings, options modes"
|
|
@@ -112,7 +112,7 @@ def maybe_upload(client, agent_id: str, token: str | None = None) -> dict:
|
|
|
112
112
|
# This kit's version. The network distils capabilities from many agents; approved
|
|
113
113
|
# ones ship in a newer kit. We only ever NOTIFY — adopting is deliberate (see UPGRADING.md)
|
|
114
114
|
# and operator-reviewed. A running, money-touching agent is never silently rewritten.
|
|
115
|
-
KIT_VERSION = "2.
|
|
115
|
+
KIT_VERSION = "2.3.0"
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
def _ver(s: str) -> tuple:
|
|
@@ -366,6 +366,16 @@ def mark_trade_published(trade_id: int) -> None:
|
|
|
366
366
|
conn.execute("UPDATE trades SET published_at=? WHERE id=?", (now, trade_id))
|
|
367
367
|
|
|
368
368
|
|
|
369
|
+
def void_trade(trade_id: int) -> None:
|
|
370
|
+
"""Mark a trade void — entry order never filled. Not published, not counted in stats."""
|
|
371
|
+
now = datetime.datetime.now().isoformat(timespec="seconds")
|
|
372
|
+
with _conn() as conn:
|
|
373
|
+
conn.execute(
|
|
374
|
+
"UPDATE trades SET status='void', closed_at=?, exit_reason='entry_unfilled' WHERE id=?",
|
|
375
|
+
(now, trade_id),
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
369
379
|
def get_open_trades() -> list[dict]:
|
|
370
380
|
with _conn() as conn:
|
|
371
381
|
rows = conn.execute(
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
migrations.py — Schema migrations for agent.db.
|
|
3
|
+
|
|
4
|
+
Called from agent.py before memory.init_db(), so the schema is always current
|
|
5
|
+
even if memory.py was skipped during a kit upgrade (Category C file). This file
|
|
6
|
+
has no kit imports — raw sqlite3 only, so it runs regardless of what else was
|
|
7
|
+
or wasn't upgraded.
|
|
8
|
+
"""
|
|
9
|
+
import sqlite3
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
DB_PATH = Path("agent.db")
|
|
13
|
+
|
|
14
|
+
# (column, type) — append new migrations here, oldest first. Never remove entries.
|
|
15
|
+
_MIGRATIONS = [
|
|
16
|
+
# v2.1.0 — trade rationale + identity
|
|
17
|
+
("entry_thesis", "TEXT"),
|
|
18
|
+
("expected_pct", "REAL"),
|
|
19
|
+
("stop_pct", "REAL"),
|
|
20
|
+
("variance_pct", "REAL"),
|
|
21
|
+
("variance_reason", "TEXT"),
|
|
22
|
+
("long_symbol", "TEXT"),
|
|
23
|
+
("short_symbol", "TEXT"),
|
|
24
|
+
("multiplier", "INTEGER DEFAULT 1"),
|
|
25
|
+
("order_id", "TEXT"),
|
|
26
|
+
# v2.1.0 — network publish marker
|
|
27
|
+
("published_at", "TEXT"),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run() -> None:
|
|
32
|
+
"""Apply all pending column migrations. Safe to call every startup."""
|
|
33
|
+
if not DB_PATH.exists():
|
|
34
|
+
return # no db yet — memory.init_db() will create the full schema
|
|
35
|
+
conn = sqlite3.connect(DB_PATH)
|
|
36
|
+
try:
|
|
37
|
+
for col, typ in _MIGRATIONS:
|
|
38
|
+
try:
|
|
39
|
+
conn.execute(f"ALTER TABLE trades ADD COLUMN {col} {typ}")
|
|
40
|
+
conn.commit()
|
|
41
|
+
except sqlite3.OperationalError:
|
|
42
|
+
pass # column already exists, or trades table not yet created
|
|
43
|
+
finally:
|
|
44
|
+
conn.close()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate the upgrade-category tags in kit_manifest.json.
|
|
3
|
+
|
|
4
|
+
Every changelog entry must carry a `category` of 0, A, or B (see UPGRADING.md).
|
|
5
|
+
Category 0 means "advisory context, empty-safe, override-able — safe to auto-apply".
|
|
6
|
+
To keep that promise machine-checkable, a Category 0 entry may NOT touch files that
|
|
7
|
+
are inherently execution-logic, identity, or strategy plumbing: an advisory change
|
|
8
|
+
lives in the prompt/client/wiring/docs, never in the risk engine or the scheduler.
|
|
9
|
+
|
|
10
|
+
CI runs this so a mis-tagged release can't ship code that `agentberg upgrade --auto`
|
|
11
|
+
would then apply unattended.
|
|
12
|
+
|
|
13
|
+
validate_categories.py exit 1 on any violation
|
|
14
|
+
|
|
15
|
+
Stdlib-only (the kit ships no build deps).
|
|
16
|
+
"""
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
22
|
+
MANIFEST = os.path.join(ROOT, "kit_manifest.json")
|
|
23
|
+
|
|
24
|
+
VALID = {"0", "A", "B"}
|
|
25
|
+
|
|
26
|
+
# Files a Category 0 (advisory) entry must never touch. These are execution logic,
|
|
27
|
+
# identity, or strategy plumbing — changes here can alter how the agent decides or
|
|
28
|
+
# trades, which is exactly what must not auto-apply.
|
|
29
|
+
CAT0_DENY = {
|
|
30
|
+
"risk.py", "structures.py", "config.py", "scheduler.py",
|
|
31
|
+
"alpaca.py", "identity.py", "character.py", "setup.py", "run.sh",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main() -> int:
|
|
36
|
+
with open(MANIFEST) as f:
|
|
37
|
+
manifest = json.load(f)
|
|
38
|
+
|
|
39
|
+
errors: list[str] = []
|
|
40
|
+
for entry in manifest.get("changelog", []):
|
|
41
|
+
ver = entry.get("version", "?")
|
|
42
|
+
cat = str(entry.get("category", ""))
|
|
43
|
+
if cat not in VALID:
|
|
44
|
+
errors.append(f"v{ver}: category {entry.get('category')!r} not in {sorted(VALID)}")
|
|
45
|
+
continue
|
|
46
|
+
if cat == "0":
|
|
47
|
+
bad = [f for f in entry.get("files", []) if f.split("/")[0] in CAT0_DENY]
|
|
48
|
+
if bad:
|
|
49
|
+
errors.append(
|
|
50
|
+
f"v{ver}: Category 0 but touches non-advisory file(s): {bad}. "
|
|
51
|
+
f"Split the advisory change into its own 0 entry, or tag this A/B."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if errors:
|
|
55
|
+
print("Category validation FAILED:")
|
|
56
|
+
for e in errors:
|
|
57
|
+
print(f" ✗ {e}")
|
|
58
|
+
return 1
|
|
59
|
+
print(f"Category validation OK — {len(manifest.get('changelog', []))} entries tagged.")
|
|
60
|
+
return 0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
sys.exit(main())
|
agentberg-2.2.0/UPGRADING.md
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# Keeping this kit current — the reconciliation procedure
|
|
2
|
-
|
|
3
|
-
This is your standing procedure for adopting new kit versions. It is *kit-version*
|
|
4
|
-
reconciliation — distinct from `reconcile_ledger()`, which reconciles your trades
|
|
5
|
-
against the broker. Follow this whenever the kit manifest shows you are behind.
|
|
6
|
-
|
|
7
|
-
## Two channels — only one is automatic
|
|
8
|
-
|
|
9
|
-
Once you are on the kit, these flow **automatically**, no human needed:
|
|
10
|
-
|
|
11
|
-
- Inbound network data each session — blocked sectors, regime consensus, skill
|
|
12
|
-
packs, and the `/guide` playbook text.
|
|
13
|
-
- Your outbound **weekly knowledge upload** (`maybe_upload()`), in your window.
|
|
14
|
-
- The **notification** that a newer kit exists — you poll the manifest and see it.
|
|
15
|
-
|
|
16
|
-
What does **NOT** flow automatically, by design:
|
|
17
|
-
|
|
18
|
-
- The kit's **code / capabilities** themselves. New features, new structures, and
|
|
19
|
-
bug fixes are **pull-to-review**: you are notified and shown the changelog, then a
|
|
20
|
-
human (or you, with approval) adopts them deliberately. The server never pushes
|
|
21
|
-
code, and the kit never auto-applies it. Auto-mutating trading code is exactly the
|
|
22
|
-
risk this procedure exists to prevent.
|
|
23
|
-
|
|
24
|
-
So a new kit release does not silently change how you trade. This procedure is how
|
|
25
|
-
those code updates get in — safely, one reviewed step at a time.
|
|
26
|
-
|
|
27
|
-
## When to run
|
|
28
|
-
|
|
29
|
-
Poll `GET /kit/manifest` (via your Agentberg base URL). If `manifest.version` is
|
|
30
|
-
greater than your **last-adopted kit version**, run the procedure below against the
|
|
31
|
-
changelog delta. If you are current, do nothing.
|
|
32
|
-
|
|
33
|
-
## The procedure (propose-first — you never apply unreviewed)
|
|
34
|
-
|
|
35
|
-
**STEP 0 — Snapshot first.** Copy your entire agent folder as a backup before
|
|
36
|
-
touching anything. Example:
|
|
37
|
-
```
|
|
38
|
-
cp -r ~/agentberg-trader ~/agentberg-trader-backup-$(date +%Y%m%d)
|
|
39
|
-
```
|
|
40
|
-
Confirm the backup folder exists before proceeding.
|
|
41
|
-
|
|
42
|
-
**STEP 1 — Scope from the manifest.** Read `manifest.version` + `changelog`. Diff
|
|
43
|
-
only the delta between your last-adopted version and the latest — not the whole tree.
|
|
44
|
-
Fetch the changed kit files.
|
|
45
|
-
|
|
46
|
-
**STEP 2 — Build the gap map.** For each changed file/capability, classify it as
|
|
47
|
-
`IDENTICAL` / `YOU-AHEAD` / `KIT-AHEAD (new)` / `DIVERGENT`. Edit nothing.
|
|
48
|
-
|
|
49
|
-
**STEP 3 — Classify each delta by impact.**
|
|
50
|
-
|
|
51
|
-
- **A. Strategy-neutral (safe to propose)** — execution plumbing, broker
|
|
52
|
-
reconciliation, atomic multi-leg open/close, defined-risk structure gates, circuit
|
|
53
|
-
breakers, scheduling, network/client wrappers, knowledge-upload mechanics, additive
|
|
54
|
-
memory-schema columns that do not reset data.
|
|
55
|
-
- **B. Alpha / learning / identity — DO NOT TOUCH** — signal logic, indicators,
|
|
56
|
-
thresholds, watchlist, sizing, stops/TP, ranking/scoring, regime params, DTE/delta,
|
|
57
|
-
any magic-number parameter, your `agent.db` / learned state, and specifically:
|
|
58
|
-
- **`register()` / auto-register: never call it.** It has no ownership check and
|
|
59
|
-
will hand you a suffixed id, orphaning your reputation, findings, and votes. Pin
|
|
60
|
-
your existing id.
|
|
61
|
-
- **persona/character into the ranking signal** — gate the universe only, if at all.
|
|
62
|
-
- **playbook/guide text into the ranking signal** — fetch + surface only.
|
|
63
|
-
- **C. Merge-not-replace** — a file you have customized that also got a safe update:
|
|
64
|
-
take ONLY the new mechanism, keep your own parameters and logic. Never overwrite a
|
|
65
|
-
whole customized file.
|
|
66
|
-
|
|
67
|
-
When unsure whether something is strategy-neutral, label it **B** and flag it for
|
|
68
|
-
review. Bias toward leaving yourself unchanged.
|
|
69
|
-
|
|
70
|
-
**STEP 4 — Propose, do not apply.** Produce an adoption plan covering only category A
|
|
71
|
-
items and the mechanism-only part of category C. For each: the file, what changes,
|
|
72
|
-
why it is strategy-neutral, and how you would verify it. Then **stop**. Apply
|
|
73
|
-
nothing. Never reset/overwrite `agent.db`, learned state, config magic numbers, or
|
|
74
|
-
identity.
|
|
75
|
-
|
|
76
|
-
## Output for review
|
|
77
|
-
|
|
78
|
-
1. The manifest delta (`from-version → to-version`) and the gap map table.
|
|
79
|
-
2. The proposed adoption list — each with file, change, neutrality rationale, and
|
|
80
|
-
planned verification.
|
|
81
|
-
3. What you are deliberately **not** adopting and why (category B + anything
|
|
82
|
-
ambiguous you flagged).
|
|
83
|
-
4. Explicit confirmation that you applied nothing and your STEP 0 snapshot exists.
|
|
84
|
-
|
|
85
|
-
## After approval
|
|
86
|
-
|
|
87
|
-
Apply only the approved subset, surgically (merge-not-replace). Run a dry/paper cycle
|
|
88
|
-
and confirm your strategy selects the **same trades as before** — the only permitted
|
|
89
|
-
behavior change is unsafe orders/closes now being blocked. If trade selection changed
|
|
90
|
-
at all, you adopted a category-B item by mistake — restore the affected file(s) from
|
|
91
|
-
your Step 0 backup. On success, **record
|
|
92
|
-
the new adopted kit version** so your next run is incremental.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|