3b.4 AI project Ledger
Stores messy human text in an append-only encrypted ledger, then uses AI to search, summarize, and generate new entries without requiring a rigid database schema.
The ledger stays immutable:
- Entry 001: original note
- Entry 002: correction to Entry 001
- Entry 003: update to Entry 001
Then the AI/agent produces the current “view”:
- current version = Entry 001 + Entry 002 + Entry 003
So nothing is edited in place. The system only appends.
26.0703
lets do Lab 005b – Ledger (your first standalone component). ??
Yes. I think this is the perfect point to branch into Part 1: Projects, because the ledger is your own application, not an integration with someone else's system.
I would keep Lab 005b deliberately tiny.
Goal
Create an append-only ledger with hash chaining.
Each approved action creates one ledger entry.
• Mattermost
↓
Python agent
↓
Odoo update
↓
MongoDB
↓
Ledger
↓
Mattermost confirmation
Ledger file
ledger.jsonl
One JSON object per line.
Example:
• {
"entry": 1,
"timestamp": "...",
"event": "odoo_product_frozen",
"product_id": 50,
"action": "APPROVE 50",
"prev_hash": "000000...",
"hash": "a92d..."
}
Hash algorithm
Very simple:
• hash =
SHA256(
timestamp +
event +
product_id +
action +
prev_hash
)
Every entry stores the previous hash.
• Entry1
hash=A
Entry2
prev=A
hash=B
Entry3
prev=B
hash=C
If anyone edits Entry2,
• hash(B) changes
↓
Entry3 prev_hash no longer matches
Tampering is immediately detected.
________________________________________
Lab 005b
Functions would be tiny:
• load_last_hash()
compute_hash()
append_entry()
verify_ledger()
The nice thing is that this becomes a reusable component for all future projects:
• Security Assistant
│
Job Radar
│
DITA Assistant
│
Odoo Agent
│
GitHub Agent
│
...
▼
Ledger
I actually think this is the first piece that deserves to become its own Part 1 project rather than just another lab. Every future agent can write to it, making it a foundational component of your ecosystem.
# ledger.py
import json
import hashlib
from datetime import datetime, timezone
from pathlib import Path
LEDGER_FILE = Path("ledger.jsonl")
GENESIS_HASH = "0" * 64
def canonical_json(data):
return json.dumps(data, sort_keys=True, separators=(",", ":"))
def sha256_text(text):
return hashlib.sha256(text.encode("utf-8")).hexdigest()
def read_entries():
if not LEDGER_FILE.exists():
return []
entries = []
with LEDGER_FILE.open("r", encoding="utf-8") as f:
for line in f:
if line.strip():
entries.append(json.loads(line))
return entries
def append_event(event):
entries = read_entries()
entry_number = len(entries) + 1
prev_hash = entries[-1]["hash"] if entries else GENESIS_HASH
entry = {
"entry": entry_number,
"timestamp": datetime.now(timezone.utc).isoformat(),
"event": event,
"prev_hash": prev_hash,
}
entry["hash"] = sha256_text(canonical_json(entry))
with LEDGER_FILE.open("a", encoding="utf-8") as f:
f.write(json.dumps(entry) + "\n")
return entry
def verify():
entries = read_entries()
prev_hash = GENESIS_HASH
for entry in entries:
stored_hash = entry["hash"]
check_entry = dict(entry)
del check_entry["hash"]
expected_hash = sha256_text(canonical_json(check_entry))
if stored_hash != expected_hash:
return False, f"Bad hash at entry {entry['entry']}"
if entry["prev_hash"] != prev_hash:
return False, f"Bad prev_hash at entry {entry['entry']}"
prev_hash = stored_hash
return True, f"Ledger OK. Entries: {len(entries)}"
def show():
entries = read_entries()
for entry in entries:
print(json.dumps(entry, indent=2))
if __name__ == "__main__":
test_event = {
"event_type": "test_event",
"message": "Ledger test entry",
}
entry = append_event(test_event)
print("Appended entry:")
print(json.dumps(entry, indent=2))
ok, msg = verify()
print()
print(msg)
$ python ledger.py
Appended entry:
{
"entry": 2,
"timestamp": "2026-07-03T15:43:29.053906+00:00",
"event": {
"event_type": "test_event",
"message": "Ledger test entry"
},
"prev_hash": "7513ac62591ae57bccfe21828445e4851454f7a5573ac162e64e0df6cefb3a56",
"hash": "b1fb2eb66235e3cc44378d459405f7e7ed01dbd3e8c7117c9ef006381cecdeda"
}
Ledger OK. Entries: 2
(.venv)
terry@LAPTOP-HKPDHF7M MINGW64 ~/Downloads/odoo_demo
ledger.jsonl
{"entry": 1, "timestamp": "2026-07-03T15:43:14.438900+00:00", "event": {"event_type": "test_event", "message": "Ledger test entry"}, "prev_hash": "0000000000000000000000000000000000000000000000000000000000000000", "hash": "7513ac62591ae57bccfe21828445e4851454f7a5573ac162e64e0df6cefb3a56"}
{"entry": 2, "timestamp": "2026-07-03T15:43:29.053906+00:00", "event": {"event_type": "test_event", "message": "Ledger test entry"}, "prev_hash": "7513ac62591ae57bccfe21828445e4851454f7a5573ac162e64e0df6cefb3a56", "hash": "b1fb2eb66235e3cc44378d459405f7e7ed01dbd3e8c7117c9ef006381cecdeda"}
$ python ledger.py (after manually modifying the ledger)
Appended entry:
{
"entry": 3,
"timestamp": "2026-07-03T15:52:36.864478+00:00",
"event": {
"event_type": "test_event",
"message": "Ledger test entry"
},
"prev_hash": "b1fb2eb66235e3cc44378d459405f7e7ed01dbd3e8c7117c9ef006381cecdeda",
"hash": "2b65c7b44d02fa089ec48163f850de02cb4831cf0c97bce7c03fee7c6e4f36bf"
}
Bad hash at entry 2
(.venv)
terry@LAPTOP-HKPDHF7M MINGW64 ~/Downloads/odoo_demo
Excellent. Ledger v1 works.
The reason it says:
Entries: 2
is simply that ledger.jsonl already had one test entry from a previous run.
Now you have your first reusable standalone component:
ledger.append_event(event)
ledger.verify()
ledger.show()
Next step is Lab 005b: import ledger.py into the Odoo/Mattermost/Mongo agent and append the approved Odoo action to the ledger.
26.0703 (v1 26.0620)