Compare commits

...

3 Commits

Author SHA1 Message Date
Hermes
536c44b6e8 gbrain: sync converted org-mode brain files 2026-05-23 07:10:05 +00:00
Hermes
9a95212d1a gbrain: sync converted org-mode brain files 2026-05-23 07:00:09 +00:00
Hermes
cf0c9b1f9d gbrain: sync converted org-mode brain files 2026-05-23 06:47:23 +00:00
3 changed files with 338 additions and 155 deletions

View File

@@ -0,0 +1,29 @@
#+title: Orders of Magnitude — Time
#+filetags: :passepartout:framework:time:scale:hierarchy:
:PROPERTIES:
:ID: orders-of-magnitude-time
:CREATED: [2026-05-23 Sat]
:END:
Time at human scales is best thought of in orders of magnitude, not linear progression. Each jump in scale is qualitatively different — the constraints, the tools, the feedback loops, and the failure modes change entirely.
The hierarchy:
| Scale | What fits | Feedback | Failure mode |
|-------|-----------|----------|--------------|
| Minutes | Firefighting, ops, real-time decisions | Instant | Burnout, whiplash |
| Hours | Work session, meeting, focused task | Same day | Interruption cascade |
| Days | Shippable thing, momentum building | Next day | Drift, distraction |
| Weeks | Sprint, feature, market pulse | One cycle | Wrong direction |
| Months | Product cycle, hiring, traction | One data point | Bleeding out slow |
| Years | Company, moats, technology shifts | Scarce | Irrelevance |
| Generations | Culture, regulation, infrastructure | Post-founding | Irreversibility |
Practical use:
When planning anything, identify which order of magnitude you're operating in — then use the tools and cadence appropriate to that scale, not the one below or above it. A minutes problem solved with a weeks solution is overengineering; a years problem approached with days thinking is naive.
Common mistake: treating a months/years problem as if it can be solved in days/weeks (startup hype, premature optimization) or a minutes problem as if it deserves weeks of deliberation (analysis paralysis, bikeshedding).
See also: [[file:time-estimates.org][Time estimates]] applies this framework to Passepartout's development timeline.

View File

@@ -1,17 +1,68 @@
:PROPERTIES: :PROPERTIES:
:ID: dc2e4f22-1c4c-5d4a-a151-f96e5d3b0d70 :ID: dc2e4f22-1c4c-5d4a-a151-f96e5d3b0d70
:CREATED: [2026-05-23 Sat]
:END: :END:
#+title: Development Velocity and Timeline Estimates #+title: Development Velocity and Timeline Estimates
#+filetags: :passepartout:economics:development:timeline:velocity: #+filetags: :passepartout:economics:development:timeline:velocity:orders-of-magnitude:
At the observed velocity (initial TUI through to production readiness in a single session), the agent writes code and the symbolic engine verifies it at a cycle measured in minutes. The bottleneck is not coding speed — it is LLM API latency, ACL2 verification time, and human review of the 5% of edge cases Screamer flags. The orders-of-magnitude time framework ([[file:orders-of-magnitude-time.org][Orders of Magnitude — Time]]) reveals that the original single-timeline estimate conflated two qualitatively different projects. The line counts are in plausible ranges for Lisp's density (~5-10× fewer lines than C++ for equivalent functionality), but the phases differ in their feedback regimes, constraints, and failure modes. The honest picture splits into two distinct phases.
**To neurosymbolic maturity (~4,500 lines):** ~80 cycles, 3-5 weeks, ~2-3 hours of human review. **Old estimate:** 14,000 lines total, 3-6 months to replace the full computing stack. This was wrong because it treated all phases as linear — microcode has hardware latency (seconds per cycle), GUI has user-testing latency (days per iteration), and the browser alone is a years-scale project if done natively. The numbers don't add linearly across orders of magnitude.
**To [[file:self-driving-lisp-machine.org][self-driving Lisp Machine]] (Logos + Stoa hardware, +~6,000 lines):** ~60 cycles, 2-4 weeks. The microcode must be loaded onto physical hardware and benchmarked, adding seconds per cycle. **Corrected estimate:** Two-phase approach with a clear middleground destination.
**Full Stoa (editor, browser, shell, Qt integration, ~3,500 lines):** ~30 cycles, 2-3 weeks. ---
**Total from today to full Logos + Stoa + Agora triad:** 3-6 months. Most of that time is spent on design decisions and protocol specification, not on code. ## Phase Zero — The MVP (Linux-hosted, ships real product)
The system writes the code. The human makes architectural decisions and reviews the 5% ambiguous rules. This timeline assumes a rapid [[file:sufficiency-flip.org][sufficiency flip]] for each domain. See [[file:investment-thesis.org][Investment thesis]] for the business case that justifies this approach. Run on Linux, use C libraries through CFFI, deliver value without replacing the OS. This is the [[file:sufficiency-flip.org][sufficiency flip]] applied to the verification layer only, not the whole stack.
| Component | Lines | Method | Scale |
|---|---|---|---|
| Neurosymbolic core (ACL2 + Screamer + LLM bridge) | ~4,500 | Agent-generated Lisp, human-validated | Weeks — dense, well-bounded, proven approach |
| Terminal-based Stoa (editor + REPL + shell) | ~2,000 | CL-charms / cl-tty | Weeks — TUI patterns established |
| Gate rule SDK + marketplace | ~1,500 | ACL2 gate packages | Weeks — pure symbolic, well-specified |
| CFFI wrappers (system, GPU, crypto, storage) | ~2,000 | Thin bindings | Days — mechanical translation |
| Verification appliance CLI + Agora namespace | ~1,000 | API surface | Weeks |
**Total MVP: ~11,000 lines. Timeline: 1-3 months. Human review: ~20 hours.**
This ships. Users get verified code execution, gate rule packages, and a verified development environment. No new OS, no new browser, no Qt integration. Value proposition is proven with existing infrastructure.
**Revenue model starts here:** domain gate packages, verification appliance, compute marketplace. The MVP is a product, not a demo.
---
## Phase End State — Full Lisp Machine (cannibalize the stack)
Replace Linux, replace C libraries, own the framebuffer, own the browser. This is the full [[file:self-driving-lisp-machine.org][self-driving Lisp Machine]] vision.
| Phase-out target | Replacement | Lines | Scale | Risk |
|---|---|---|---|---|
| Linux kernel (scheduler, IPC, drivers) | Microcode on Tenstorrent | ~6,000 | Months — hardware-limited cycles | Verification delay; hardware bugs |
| MMU / process isolation | GC + ACL2-verified single address space | ~2,000 | Weeks — architecture already defined | Legacy app compatibility |
| Display server (X11/Wayland) | Lisp framebuffer compositor | ~4,000 | Months — visual debugging is slow | UX gaps at the edges |
| Browser (WebKit embed) | WebKit via Lisp FFI with verified wrappers | ~3,000 | Months — sandboxing the embed | Web platform surface is unbounded |
| Native browser | Full Lisp DOM + render engine | ~30,000+ | Years — browser engines are OS-scale | This is the hardest piece. Consider staying on WebKit |
| Core libraries (libc, SSL, crypto, etc.) | Incremental verified replacements | ~10,000+ | Years — can chip away post-ship | Every replacement must match SOTA perf |
| Qt / terminal / native UI toolkit | Stoa toolkit (verified compositor + widgets) | ~8,000 | Monthsyears | UX parity with modern toolkits is the highest risk |
**Ballpark end state: 25,000-60,000 lines. Timeline: 2-5 years.**
The range reflects uncertainty about the browser. If WebKit embed is sufficient (Phase Zero's terminal UX graduates to a managed WebView), the end state is closer to 25K. If you need a full native browser with verified DOM, ACL2-rendered layout, and a compositor that matches macOS fluidity — that's a years-scale project on its own.
---
## Orders-of-Magnitude Risk Map
| Decision | At stake | True scale | Mistake if treated as |
|---|---|---|---|
| Does the verification marketplace work? | Company thesis | Months (Phase Zero) | Solved in days |
| Can we ship without replacing Linux? | Time-to-market | Weeks to implement | Years of kernel work before product |
| Is WebKit embed enough for Stoa? | 60% of total timeline | Months vs years | Native browser as default path |
| Does the sufficiency flip cover each domain? | Revenue model | Weeks per domain | One-shot, all or nothing |
| Can Lisp match SOTA browser UX? | Full vision | Generations (or never) | Engineering problem, not a research question |
The most dangerous order-of-magnitude error: treating the end state as an engineering sprint. Replacing the browser engine is a years-scale project that has defeated every attempt (Servo, PhantomJS, etc.). If that's the destination, plan accordingly — or accept WebKit embed as the terminal destination and focus verification on the OS/compositor layer where it provides real security value.
See [[file:investment-thesis.org][Investment thesis]] for the business case and [[file:cost-structure.org][Cost structure]] for the economics behind the verification-only-first approach.

View File

@@ -7,130 +7,6 @@ GBRAIN_SRC = "/mnt/hermes/brain"
PANDOC = "/usr/bin/pandoc" PANDOC = "/usr/bin/pandoc"
BUN = os.path.expanduser("~/.bun/bin/gbrain") BUN = os.path.expanduser("~/.bun/bin/gbrain")
def find_org_files():
"""Scan ideas/ recursively for all .org files, return (slug, rel_path, abs_path)."""
files = []
base = f"{BRAIN}/ideas"
for root, dirs, filenames in os.walk(base):
for fn in filenames:
if not fn.endswith('.org'):
continue
abs_path = os.path.join(root, fn)
rel = os.path.relpath(abs_path, base)
# rel is like "compliance/hipaa.org" or "triad-overview.org"
name = fn[:-4] # remove .org
files.append((name, rel, abs_path))
return files
def gbrain_target(rel_path):
"""Derive gbrain target path from org relative path.
ideas/compliance/hipaa.org → concepts/compliance/hipaa.md
ideas/triad-overview.org → concepts/triad-overview.md (via routing dict)
ideas/competitive-analysis...→ ideas/competitive-analysis.md
"""
parts = rel_path.split('/')
if len(parts) == 1:
# Flat file in ideas/ root — use ROUTING dict
slug = parts[0][:-4] if parts[0].endswith('.org') else parts[0][:-4]
category = ROUTING.get(slug, "concepts")
return f"{GBRAIN_SRC}/{category}/{slug}.md"
else:
# In a subdirectory: ideas/compliance/foo.org → concepts/compliance/foo.md
subdir = parts[0]
slug = parts[1][:-4] if parts[1].endswith('.org') else parts[1][:-4]
return f"{GBRAIN_SRC}/concepts/{subdir}/{slug}.md"
def extract_org_properties(src_path):
"""Extract :PROPERTIES: drawer and #+title/#+filetags from an org file."""
props = {}
with open(src_path) as f:
content = f.read()
# Extract title
m = re.search(r'^#\+title:\s+(.+)$', content, re.MULTILINE)
if m:
props['title'] = m.group(1).strip()
# Extract tags
m = re.search(r'^#\+filetags:\s+(.+)$', content, re.MULTILINE)
if m:
tags = [t.strip(':') for t in m.group(1).split()]
props['tags'] = tags
# Extract ID from PROPERTIES drawer
m = re.search(r':ID:\s+([^\s]+)', content)
if m:
props['org_id'] = m.group(1)
# Extract CREATED
m = re.search(r':CREATED:\s+\[([^\]]+)\]', content)
if m:
props['created'] = m.group(1)
return props
def strip_org_header(src_path):
"""Strip the Org-mode header block (PROPERTIES drawer + #+ directives)
before feeding to pandoc, so it doesn't produce raw {=org} blocks."""
with open(src_path) as f:
lines = f.readlines()
# Find first non-header line
in_properties = False
start = 0
for i, line in enumerate(lines):
if line.strip() == ':PROPERTIES:':
in_properties = True
if in_properties and line.strip() == ':END:':
in_properties = False
start = i + 1
continue
if not in_properties:
# Skip #+ lines
if line.startswith('#+'):
start = i + 1
continue
# First real content
if line.strip():
start = i
break
start = i + 1
return ''.join(lines[start:])
def pandoc_convert(clean_body):
"""Convert org body to markdown via pandoc (stdin mode)."""
result = subprocess.run(
[PANDOC, "-f", "org", "-t", "markdown-smart"],
input=clean_body, capture_output=True, text=True
)
if result.returncode != 0:
print(f" ERROR pandoc: {result.stderr[:200]}")
return None
return result.stdout.strip()
def build_frontmatter(props):
"""Build YAML frontmatter string from extracted properties."""
lines = ['---']
if 'title' in props:
lines.append(f'title: "{props["title"]}"')
if 'tags' in props:
tags_str = ', '.join(props['tags'])
lines.append(f'tags: [{tags_str}]')
if 'created' in props:
lines.append(f'created: {props["created"]}')
lines.append('---')
return '\n'.join(lines)
def postprocess_links(md_text):
"""Convert pandoc's markdown links to gbrain-friendly format."""
# Pandoc converts [[file:foo.org][desc]] to [desc](foo.org)
# Strip .org extensions from relative links
md_text = re.sub(r'\(([a-zA-Z0-9_-]+)\.org\)', r'(\1)', md_text)
return md_text
ROUTING = { ROUTING = {
# Concepts — triad architecture, security, economics theory # Concepts — triad architecture, security, economics theory
"triad-overview": "concepts", "triad-overview": "concepts",
@@ -165,44 +41,241 @@ ROUTING = {
"investment-thesis": "concepts", "investment-thesis": "concepts",
"compliance-framework-mapping": "concepts", "compliance-framework-mapping": "concepts",
# Ideas — strategy, competitive analysis # Ideas — strategy, competitive analysis
"orders-of-magnitude-time": "concepts",
"competitive-analysis-2026-05": "ideas", "competitive-analysis-2026-05": "ideas",
"passepartout-economics": "ideas", "passepartout-economics": "ideas",
} }
def find_org_files():
"""Scan ideas/ recursively for all .org files, return (slug, rel_path, abs_path)."""
files = []
base = f"{BRAIN}/ideas"
for root, dirs, filenames in os.walk(base):
for fn in filenames:
if not fn.endswith('.org'):
continue
abs_path = os.path.join(root, fn)
rel = os.path.relpath(abs_path, base)
name = fn[:-4]
files.append((name, rel, abs_path))
return files
def gbrain_target(rel_path):
"""Derive gbrain target path from org relative path."""
parts = rel_path.split('/')
if len(parts) == 1:
slug = parts[0][:-4]
category = ROUTING.get(slug, "concepts")
return f"{GBRAIN_SRC}/{category}/{slug}.md"
else:
subdir = parts[0]
slug = parts[1][:-4]
return f"{GBRAIN_SRC}/concepts/{subdir}/{slug}.md"
def gbrain_slug(rel_path):
"""Return the gbrain slug (e.g. 'concepts/time-estimates') for an org rel_path."""
parts = rel_path.split('/')
if len(parts) == 1:
slug = parts[0][:-4]
category = ROUTING.get(slug, "concepts")
return f"{category}/{slug}"
else:
subdir = parts[0]
slug = parts[1][:-4]
return f"concepts/{subdir}/{slug}"
def build_slug_map():
"""Build mapping: org slug (filename without .org) → gbrain slug."""
mapping = {}
for slug, rel_path, abs_path in find_org_files():
mapping[slug] = gbrain_slug(rel_path)
return mapping
def extract_org_links_and_body(src_path):
"""Read the full org file, extract PROPERTIES + #+ directives, and
return (props, clean_body) where clean_body has header stripped."""
with open(src_path) as f:
content = f.read()
props = {}
# Extract title
m = re.search(r'^#\+title:\s+(.+)$', content, re.MULTILINE)
if m:
props['title'] = m.group(1).strip()
# Extract tags
m = re.search(r'^#\+filetags:\s+(.+)$', content, re.MULTILINE)
if m:
tags = [t.strip(':') for t in m.group(1).split()]
props['tags'] = tags
# Extract ID from PROPERTIES drawer
m = re.search(r':ID:\s+([^\s]+)', content)
if m:
props['org_id'] = m.group(1)
# Extract CREATED
m = re.search(r':CREATED:\s+\[([^\]]+)\]', content)
if m:
created_raw = m.group(1) # e.g. "2026-05-23 Sat"
# Extract just the date portion
date_m = re.match(r'(\d{4}-\d{2}-\d{2})', created_raw)
if date_m:
props['created'] = date_m.group(1)
# Strip header for body
lines = content.split('\n')
in_properties = False
start = 0
for i, line in enumerate(lines):
if line.strip() == ':PROPERTIES:':
in_properties = True
if in_properties and line.strip() == ':END:':
in_properties = False
start = i + 1
continue
if not in_properties:
if line.startswith('#+'):
start = i + 1
continue
if line.strip():
start = i
break
start = i + 1
body = '\n'.join(lines[start:])
return props, body
def resolve_org_link(match, slug_map):
"""Replace [[file:target.org][desc]] with [[file:gbrain_path/target.org][desc]]
when target is a known org slug. Preserves original target if unknown."""
full = match.group(0)
target = match.group(1)
desc = match.group(2) if match.lastindex >= 2 else target
if target in slug_map:
gbrain_path = slug_map[target]
return f"[[file:{gbrain_path}.org][{desc}]]"
return full
def convert_body(body_text, slug_map):
"""Pre-process org body to inject gbrain path prefixes into cross-references,
then convert to markdown via pandoc. Returns (md_body, link_refs) where
link_refs is a list of {slug, type} dicts."""
link_refs = []
# Find all [[file:X.org][desc]] cross-references and collect them
org_link_re = re.compile(r'\[\[file:([^\]]+?)\.org\]\[([^\]]*?)\]\]')
for m in org_link_re.finditer(body_text):
target = m.group(1)
if target in slug_map:
link_refs.append({
"slug": slug_map[target],
"type": "references",
"name": slug_map[target],
})
# Inject directory prefixes into org links so pandoc produces proper paths
def replace_link(m):
target = m.group(1)
desc = m.group(2)
if target in slug_map:
return f"[[file:{slug_map[target]}.org][{desc}]]"
return m.group(0)
processed_body = org_link_re.sub(replace_link, body_text)
# Convert to markdown
result = subprocess.run(
[PANDOC, "-f", "org", "-t", "markdown-smart"],
input=processed_body, capture_output=True, text=True
)
if result.returncode != 0:
print(f" ERROR pandoc: {result.stderr[:200]}")
return None, []
md = result.stdout.strip()
# Pandoc converts [[file:concepts/foo.org][desc]] to [desc](concepts/foo.org)
# Strip .org extensions
md = re.sub(r'\(([a-zA-Z0-9_/-]+)\.org\)', r'(\1)', md)
return md, link_refs
def build_frontmatter(props, link_refs=None):
"""Build YAML frontmatter string from properties and link references."""
lines = ['---']
if 'title' in props:
lines.append(f'title: "{props["title"]}"')
if 'tags' in props:
tags_str = ', '.join(props['tags'])
lines.append(f'tags: [{tags_str}]')
if 'created' in props:
lines.append(f'created: {props["created"]}')
if link_refs:
for lr in link_refs:
# Deduplicate by slug
pass
# Deduplicate
seen = set()
unique_links = []
for lr in link_refs:
k = lr['slug']
if k not in seen:
seen.add(k)
unique_links.append(lr)
if unique_links:
lines.append('links:')
for lr in unique_links:
lines.append(f' - slug: "{lr["slug"]}"')
lines.append(f' type: "{lr["type"]}"')
lines.append('---')
return '\n'.join(lines)
def add_timeline_entry(md_body, props):
"""If the page has a CREATED date, prepend a timeline bullet."""
if 'created' in props and 'title' in props:
date = props['created']
title = props['title']
line = f"- **{date}** | Created — {title}\n\n"
return line + md_body
return md_body
def main(): def main():
# Pre-build slug map for all org files
slug_map = build_slug_map()
imported = [] imported = []
for slug, rel_path, src_path in find_org_files(): for slug, rel_path, src_path in find_org_files():
dst_path = gbrain_target(rel_path) dst_path = gbrain_target(rel_path)
# Create parent directories
os.makedirs(os.path.dirname(dst_path), exist_ok=True) os.makedirs(os.path.dirname(dst_path), exist_ok=True)
# Extract frontmatter from org properties # Extract properties and body from org file
props = extract_org_properties(src_path) props, org_body = extract_org_links_and_body(src_path)
# Strip org header and convert body to markdown # Convert body to markdown, collecting links along the way
clean = strip_org_header(src_path) md, link_refs = convert_body(org_body, slug_map)
md = pandoc_convert(clean)
if md is None: if md is None:
continue continue
md = postprocess_links(md) # Build frontmatter with links
frontmatter = build_frontmatter(props, link_refs)
# Assemble: YAML frontmatter + markdown body
frontmatter = build_frontmatter(props) # Add timeline entry if date exists
md = add_timeline_entry(md, props)
full = frontmatter + '\n\n' + md + '\n' full = frontmatter + '\n\n' + md + '\n'
with open(dst_path, 'w') as f: with open(dst_path, 'w') as f:
f.write(full) f.write(full)
# Show relative path for clarity
rel_dst = os.path.relpath(dst_path, GBRAIN_SRC) rel_dst = os.path.relpath(dst_path, GBRAIN_SRC)
imported.append(rel_dst) imported.append(rel_dst)
print(f" OK {rel_dst}") print(f" OK {rel_dst}")
print(f"\nConverted {len(imported)} files.") print(f"\nConverted {len(imported)} files.")
# Commit to git # Commit to git
subprocess.run(["git", "-C", GBRAIN_SRC, "add", "-A"], capture_output=True) subprocess.run(["git", "-C", GBRAIN_SRC, "add", "-A"], capture_output=True)
subprocess.run( subprocess.run(
@@ -210,7 +283,7 @@ def main():
"-m", "gbrain: sync converted org-mode brain files"], "-m", "gbrain: sync converted org-mode brain files"],
capture_output=True, text=True capture_output=True, text=True
) )
# Import into gbrain # Import into gbrain
print("\nImporting into gbrain...") print("\nImporting into gbrain...")
env = {**os.environ, "PATH": f"{os.path.expanduser('~')}/.bun/bin:{os.environ['PATH']}"} env = {**os.environ, "PATH": f"{os.path.expanduser('~')}/.bun/bin:{os.environ['PATH']}"}
@@ -218,16 +291,15 @@ def main():
[BUN, "import", GBRAIN_SRC], [BUN, "import", GBRAIN_SRC],
capture_output=True, text=True, env=env capture_output=True, text=True, env=env
) )
# Show last 20 lines of stdout (skip noise)
out_lines = result.stdout.strip().split('\n') out_lines = result.stdout.strip().split('\n')
for line in out_lines[-25:]: for line in out_lines[-25:]:
if line.strip() and 'batch caps' not in line and 'max_batch_tokens' not in line: if line.strip() and 'batch caps' not in line and 'max_batch_tokens' not in line:
print(f" {line}") print(f" {line}")
if result.returncode != 0: if result.returncode != 0:
print(f" gbrain import exit code: {result.returncode}") print(f" gbrain import exit code: {result.returncode}")
return return
# Embed # Embed
print("\nGenerating embeddings...") print("\nGenerating embeddings...")
result2 = subprocess.run( result2 = subprocess.run(
@@ -238,5 +310,36 @@ def main():
if line.strip(): if line.strip():
print(f" {line}") print(f" {line}")
# Extract links from frontmatter (now that pages are imported with links:)
print("\nExtracting links from frontmatter...")
result3 = subprocess.run(
[BUN, "extract", "links", "--source", "db", "--include-frontmatter",
"--dir", GBRAIN_SRC],
capture_output=True, text=True, env=env
)
for line in result3.stdout.strip().split('\n')[-10:]:
if line.strip():
print(f" {line}")
# Extract timeline from body
print("\nExtracting timeline...")
result4 = subprocess.run(
[BUN, "extract", "timeline", "--source", "db", "--dir", GBRAIN_SRC],
capture_output=True, text=True, env=env
)
for line in result4.stdout.strip().split('\n')[-10:]:
if line.strip():
print(f" {line}")
# Stats
print("\nBrain stats:")
result5 = subprocess.run(
[BUN, "stats"],
capture_output=True, text=True, env=env
)
for line in result5.stdout.strip().split('\n')[-15:]:
if line.strip():
print(f" {line}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()