ES | EN

THE YAML CONTRACT AS THE SOURCE OF TRUTH: CODE IS REGENERABLE, THE CONTRACT IS NOT

TAGS: ARCHITECTURE / METHODOLOGY / LLMs / YAML READ_TIME: 13 MIN
The YAML contract as the source of truth: code is regenerable, the contract is not

In traditional development, documentation always trails the code. It gets written after the fact, updated late, and eventually lies — because the code evolved and the documentation did not. In AAL, the relationship is inverted: the YAML contract is the source of truth, the code is a consequence of the contract, regenerable at any time by any LLM that receives it. If the code and the contract contradict each other, the code is wrong.

PROJECT_STATUS: STABLE

Stack: YAML · Python · any LLM
Reference projects: TeliOs · AAL methodology
Goal: Make code disposable and the contract permanent — the real risk is losing the YAML, not the code

01. THE PROBLEM IT SOLVES

In traditional development, the only real source of truth ends up being the code itself — which is hard to read for anyone who did not write it, and completely opaque to an LLM arriving without context. The result is that every LLM work session starts from scratch, re-explaining what already exists.

The YAML contract eliminates that problem. It precisely defines what each component does, what it exposes, what it depends on, and under what conditions it operates correctly. The LLM receives the contract and generates the code — not the other way around.

// Non-technical explanation

Imagine you build furniture. You have two options: build the chair first and then try to draw the blueprint of what you built, or draw the blueprint first and then build the chair following it. The first option produces blueprints that are always outdated. The second produces blueprints that are always correct because they are the source of the chair. In AAL, the YAML contract is the blueprint. The chair is the code.

02. ANATOMY OF A COMPLETE YAML CONTRACT

A complete contract has five sections: metadata for identity and state, interface for what it exposes, dependencies for what it needs, constraints for non-negotiable limits, and acceptance_tests for verification. The store.js contract is the canonical example from TeliOs.

# TELIO-U001.contract.yaml
# Contract for store.js — TeliOs state engine

metadata:
  id: TELIO-U001
  version: 1.0.0
  name: 'TeliOs Store — Global State Engine'
  type: utility
  status: pending
  description: >
    Single centralized state for the TeliOs ecosystem.
    Persists to localStorage. Notifies changes via events.js.
    The only source of truth in the system.
    Maximum 100 lines. Zero external dependencies.
  language: JavaScript
  file: src/store.js

interface:
  exports:
    getState:
      type: function
      description: 'Returns the full state or a nested key'
      parameters:
        key: { type: string, required: false,
               description: 'Dot-notation key: kayros.start' }
      returns:
        type: any
        description: 'Value at key, or full state if no key is given'

    setState:
      type: function
      description: 'Updates state, persists to localStorage, emits change event'
      parameters:
        key: { type: string, required: true }
        value: { type: any, required: true }
      returns:
        type: boolean
        description: 'true if saved correctly, false if localStorage failed'

    resetModule:
      type: function
      description: 'Resets a module to its default values'
      parameters:
        module: { type: string, required: true }
      returns:
        type: boolean

dependencies:
  internal:
    - TELIO-U003 # events.js — emits state:changed on every setState
  external: {} # ZERO external dependencies — absolute principle

constraints:
  quality:
    max_lines: 100
    test_coverage: '> 80%'
    no_dependencies: true
  performance:
    max_write_time: '< 10ms'
    max_state_size: '< 5MB'
  behavior:
    must_notify: true
    must_persist: true
    memory_fallback: true

acceptance_tests:
  - id: T001
    name: 'setState persists to localStorage'
    when: "setState('arke.type', 'fire')"
    then:
      - "localStorage contains TELIOS_STATE key"
      - "getState('arke.type') === 'fire'"
      - "Events receives emit('state:changed', {key: 'arke.type', value: 'fire'})"

  - id: T002
    name: 'getState with nested key'
    when: "setState('kayros.totalDays', 1096)"
    then:
      - "getState('kayros.totalDays') === 1096"
      - "getState('kayros') is an object with totalDays: 1096"

  - id: T003
    name: 'memory fallback when localStorage fails'
    when: 'localStorage.setItem throws QuotaExceededError'
    then:
      - "setState returns false"
      - "getState keeps working from memory"
      - "App does not break"

implementation_notes: >
  Use IIFE to encapsulate.
  No classes — pure functional module.
  DEFAULT_STATE must be defined in full at the top.
  Nested keys use dot-notation: 'kayros.start'.
Section Content Purpose
metadata ID, version, type, status, description Unique component identity. The LLM knows what it is and why it exists.
interface Exported functions, parameters, return types The public contract — what other modules can call.
dependencies Internal (cell IDs), external (libraries) Dependency graph map. Detects cycles before implementation.
constraints Line limits, performance, behavior rules Non-negotiable boundaries the LLM cannot ignore.
acceptance_tests When/then scenarios Objective success criteria. If tests pass, the cell is correct.

03. USING THE CONTRACT TO GENERATE CODE

The implementation prompt in FocOs does not describe the problem — it delivers the contract directly. The critical instruction is the last one: if the contract and the implementation contradict each other, change the implementation. If the contract has an error, say so before implementing.

# Prompt FocOs sends to the LLM with the contract:

PROMPT_IMPLEMENTATION = """
Implement cell {id} of the TeliOs ecosystem.

CONTRACT:
{yaml_content}

ABSOLUTE RULES:
1. Maximum {max_lines} lines
2. Zero external dependencies
3. Implement EXACTLY what is defined in interface.exports
4. Include acceptance tests as // TEST: comments at the end
5. JSDoc on every exported function

The contract is the truth.
If the contract and your implementation contradict — change the implementation.
If the contract has an error — tell me before implementing.
"""

04. REGENERATING A CELL FROM THE CONTRACT

This is the use case that justifies the entire methodology. The code was lost or corrupted — but the YAML contract exists in the repo. That is enough to fully recover it in 15 minutes. Without the contract, the same operation takes hours reconstructing from memory or from broken code.

# Scenario: store.js code was corrupted or lost
# The YAML contract exists — that is enough to regenerate

# Recovery workflow:
# 1. Open TELIO-U001.contract.yaml
# 2. Paste content into the implementation prompt
# 3. LLM generates store.js from scratch — conforming to the contract
# 4. Verify acceptance tests T001, T002, T003
# 5. If they pass — cell regenerated. If not — LLM iterates.

# Total time with contract: 10-15 minutes
# Total time without contract: hours or days
[+] WHERE THE CONTRACT LIVES

An 80-line plain-text YAML file committed to a Git repository is practically impossible to lose. Risk in AAL shifts completely: it is no longer about losing code — it is about losing the contract. And a versioned text file is far easier to protect than a codebase.

-- CONCLUSION

The YAML contract as source of truth solves the deepest problem in LLM-driven development: the fragility of the knowledge base. If the contract exists, the code can be lost, corrupted, or go stale without permanent consequences. The LLM can regenerate it in 15 minutes. This fundamentally changes the nature of risk in development — the risk is no longer losing code. It is losing the contract. And that is much harder to do.

> SYSTEM_READY > NODE_ONLINE

< session_end // node: exit >

SQLITE IN DESKTOP APPS: THE DATABASE THAT NEEDS NO SERVER

TAGS: DATABASES / PYTHON / DESKTOP APPS READ_TIME: 12 MIN
SQLite in desktop apps: the database that needs no server

Every desktop app needs to persist data. The web developer's reflex is to spin up PostgreSQL or MySQL. For a single-user desktop app, that is overkill by multiple orders of magnitude: it requires a running server, user configuration, connection management, and complexity that adds nothing in a single-user context. SQLite is the correct answer — one file on disk, zero configuration, zero server, zero maintenance.

PROJECT_STATUS: STABLE

Stack: Python · SQLite · JSON
Reference project: FocOs — data persistence
Goal: Robust persistence without a server — one file on disk, years of development without touching the schema

01. THE PROBLEM IT SOLVES

PostgreSQL and MySQL are designed for concurrent multi-user access, high availability, and distributed workloads. In a personal desktop app, none of those properties apply — yet you drag along the entire infrastructure. SQLite solves exactly the problem you actually have: reliable persistence for one user, no external process, no configuration, and the end user never knows a database exists.

// Non-technical explanation

Imagine you need to save a shopping list. You have two options: hire a professional chef with an industrial kitchen to keep it, or simply write it in a notebook. PostgreSQL is the chef. SQLite is the notebook. For a shopping list, the notebook is the right answer — and you can carry it in your pocket.

02. FOCOS SCHEMA IN SQLITE

FocOs uses four core tables. The two PRAGMAs at initialization are non-negotiable: WAL journal mode dramatically improves write performance, and foreign_keys=ON activates referential integrity that SQLite leaves disabled by default.

# main.py — database initialization

import sqlite3
from pathlib import Path

DB_PATH = Path('data/focos.db')

def init_db():
    DB_PATH.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(DB_PATH)
    conn.execute("PRAGMA journal_mode=WAL") # Faster writes
    conn.execute("PRAGMA foreign_keys=ON") # Referential integrity

    conn.executescript("""
        CREATE TABLE IF NOT EXISTS projects (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL UNIQUE,
            status TEXT DEFAULT 'active',
            meta TEXT DEFAULT '{}'
        );

        CREATE TABLE IF NOT EXISTS tasks (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            project_id INTEGER REFERENCES projects(id),
            title TEXT NOT NULL,
            status TEXT DEFAULT 'pending',
            priority INTEGER DEFAULT 1,
            created_at TEXT DEFAULT (datetime('now'))
        );

        CREATE TABLE IF NOT EXISTS workspaces (
            ws_id INTEGER PRIMARY KEY,
            project_id INTEGER REFERENCES projects(id),
            layout TEXT DEFAULT 'default',
            state TEXT DEFAULT '{}'
        );

        CREATE TABLE IF NOT EXISTS sessions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            project_id INTEGER REFERENCES projects(id),
            date TEXT DEFAULT (date('now')),
            duration INTEGER DEFAULT 0,
            notes TEXT DEFAULT ''
        );

        INSERT OR IGNORE INTO workspaces(ws_id) VALUES (1),(2),(3);
    """)
    conn.commit()
    conn.close()

03. FULL CRUD — NO ORM, NO MAGIC

The pattern is consistent across all operations: open connection, execute, commit if writing, close in the finally block. No ORM, no unnecessary abstraction — the SQL is visible, debuggable, and predictable. Soft delete instead of real DELETE preserves history and avoids irreversible mistakes.

# CREATE
def create_project(self, name: str, status: str = 'active') -> dict:
    conn = sqlite3.connect(DB_PATH)
    try:
        cur = conn.execute(
            "INSERT INTO projects(name, status) VALUES(?,?)",
            (name, status)
        )
        conn.commit()
        return { 'ok': True, 'id': cur.lastrowid }
    except sqlite3.IntegrityError:
        return { 'ok': False, 'error': f"Project '{name}' already exists" }
    finally:
        conn.close()

# READ
def get_projects(self) -> list:
    conn = sqlite3.connect(DB_PATH)
    rows = conn.execute(
        "SELECT id, name, status, meta FROM projects ORDER BY id DESC"
    ).fetchall()
    conn.close()
    return [
        { 'id': r[0], 'name': r[1], 'status': r[2],
          'meta': json.loads(r[3] or '{}') }
        for r in rows
    ]

# UPDATE
def update_project_status(self, project_id: int, status: str) -> dict:
    conn = sqlite3.connect(DB_PATH)
    conn.execute(
        "UPDATE projects SET status=? WHERE id=?",
        (status, project_id)
    )
    conn.commit()
    conn.close()
    return { 'ok': True }

# DELETE (soft delete — change status, never drop rows)
def archive_project(self, project_id: int) -> dict:
    return self.update_project_status(project_id, 'archived')

04. JSON INSIDE SQLITE — THE META FIELD

SQLite has no native JSON type — it is stored as TEXT and serialized/deserialized manually. The advantage is that the meta field can grow indefinitely without altering the schema. The rule is clear: data that requires querying goes in dedicated columns; flexible, evolving data goes in meta.

import json

# Store complex metadata in a single field
meta = {
    'type': 'software',
    'stack': ['Python', 'JavaScript'],
    'philosophy': 'Focus is the primary function',
    'completion': 85,
}

conn.execute(
    "UPDATE projects SET meta=? WHERE id=?",
    (json.dumps(meta, ensure_ascii=False), project_id)
)

# Read and deserialize
row = conn.execute(
    "SELECT meta FROM projects WHERE id=?", (project_id,)
).fetchone()
meta = json.loads(row[0]) if row else {}
print(meta['completion']) # 85
Data type Where it goes Reason
ID, status, dates, foreign keys Dedicated column Needed in WHERE, ORDER BY or JOIN clauses.
Stack, philosophy, completion, config meta field (JSON) Read-only access. Can grow without migration.
Rapidly evolving data meta field (JSON) Add fields without ALTER TABLE or migration scripts.

05. AUTOMATIC BACKUP — ONE LINE

SQLite is a file. Backing it up is copying that file. No dumps, no exports, no external tooling. The strategy in FocOs is to call backup_db() at the start of every work session — if something gets corrupted, that day's backup is immediately available.

import shutil
from datetime import datetime

def backup_db(self) -> dict:
    date = datetime.now().strftime('%Y-%m-%d')
    backup = DB_PATH.parent / f'focos_backup_{date}.db'
    shutil.copy2(DB_PATH, backup)
    return { 'ok': True, 'path': str(backup) }

# Call at the start of every session:
# backup_db() — if corruption happens, the day's backup is there

-- CONCLUSION

SQLite with the meta JSON pattern eliminates the need for schema migrations in fast-evolving projects. Structured fields go in dedicated columns for fast querying. Flexible data goes in the meta field without migration. For a single-user desktop app, this approach holds up for years of development without ever touching the database structure.

> SYSTEM_READY > NODE_ONLINE

< session_end // node: exit >

DESKTOP APPS WITHOUT ELECTRON: PYTHON + PYWEBVIEW AS A REAL ALTERNATIVE

TAGS: DESKTOP APPS / PYTHON / ARCHITECTURE READ_TIME: 16 MIN
Desktop apps without Electron: Python + pywebview as a real alternative

Electron has a problem its ecosystem refuses to admit: it consumes between 150MB and 400MB of RAM at idle, requires Node.js as a runtime, and ships a full Chromium instance bundled in every installation. A "simple" desktop app easily weighs 200MB just for the runtime. For projects where the core logic already lives in Python, there is an alternative few know about: pywebview. Same visual result, a fraction of the weight, no Node.js, no npm.

PROJECT_STATUS: STABLE

Stack: Python 3.10+ · pywebview 4.4 · HTML/CSS/JS
Reference project: FocOs v1.3 — Windows + Linux
Goal: Eliminate Electron from the stack — UI in HTML/CSS/JS, backend in pure Python, direct bridge without IPC

01. THE PROBLEM IT SOLVES

Electron forces you to duplicate logic: the UI in JavaScript/TypeScript and the backend in Node.js with IPC to communicate with the system. For Python projects, this means either rewriting the entire backend in Node or maintaining two separate processes. pywebview eliminates this duplication: UI is HTML/CSS/JS, backend is pure Python, and the bridge between them is direct.

// Non-technical explanation

Imagine you want a window with a web screen inside. Electron is like constructing an entire building to hold that window — it brings its own bricks, its own water supply, its own power generator. pywebview is like cutting a hole in your existing wall and fitting the window there — it uses what the house (the operating system) already has. Much lighter, much more efficient.

02. THE HONEST COMPARISON

The numbers make the decision. Electron has stronger support for specific edge cases — native notifications, advanced multi-window, DevTools in production. Outside those cases, pywebview wins on everything that matters for an independent project.

Factor Electron pywebview
Runtime Node.js + Chromium (~200MB) WebView2/WebKit native (0MB extra)
App size 150-300MB minimum 5-20MB (your code only)
Idle RAM 150-400MB 30-80MB
Backend Node.js required Pure Python
IPC ipcMain / ipcRenderer Direct bridge via js_api
Web engine Fixed Chromium WebView2 (Win) / WebKit (Linux/Mac)
Build electron-builder PyInstaller / Nuitka

03. INSTALLATION AND MINIMAL SETUP

Base installation is one line. Additional dependencies depend on the OS — Windows needs the WebView2 backend, Linux needs the system WebKitGTK.

# Base install
pip install pywebview

# Windows — WebView2 backend:
pip install pywebview[winforms]
# or explicit WebView2:
pip install pywebview[edgechromium]

# Linux — Ubuntu/Debian:
sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0

# Linux — Arch:
sudo pacman -S python-gobject webkit2gtk

# Minimal working app:
import webview

window = webview.create_window(
    title = 'My App',
    url = 'https://example.com',
    width = 1200,
    height = 800,
)
webview.start()

# Load local HTML:
from pathlib import Path
url = Path('web/index.html').resolve().as_uri()
window = webview.create_window(title='My App', url=url)
webview.start()

04. FOCOS ARCHITECTURE — REAL STRUCTURE

FocOs separates the project into two clear domains: the Python backend at the root and the complete frontend under web/. The SQLite database and LLM configuration live in data/, never mixed with UI code.

focos/
├── main.py # Entry point + FocOsAPI
├── focos_importer.py # Project import module
├── data/ # Database and config
│ ├── focos.db # SQLite — projects, tasks, workspaces
│ ├── llm_config.json # LLM configuration
│ └── projects_config.json
└── web/ # Complete frontend
    ├── index.html # Main shell
    ├── css/
    │ ├── main.css # Variables and reset
    │ ├── layout.css # Grid and structure
    │ └── theme.css # Dark-Amber theme
    └── js/
        ├── bridge.js # Python bridge abstraction
        ├── llm.js # LLM panel
        ├── editor.js # Monaco Editor with tabs
        ├── terminal.js # xterm.js terminal
        ├── browser.js # Embedded browser
        └── dashboard.js # Metrics and projects

05. THE FOCOSAPI CLASS — THE COMPLETE BRIDGE

Every method in FocOsAPI is accessible from JavaScript via window.pywebview.api.method_name(). Python types are serialized and deserialized automatically — no message protocol to maintain, no types to map manually.

# main.py
import webview, sqlite3, json, os
from pathlib import Path

BASE_DIR = Path(__file__).parent
DATA_DIR = BASE_DIR / 'data'
DB_PATH = DATA_DIR / 'focos.db'

class FocOsAPI:

    # -- SYSTEM ------------------------------------------------
    def get_version(self):
        return { 'version': '1.3', 'platform': os.name }

    def open_folder(self, path: str):
        '''Opens a folder in the native OS file explorer'''
        import subprocess
        if os.name == 'nt':
            subprocess.Popen(['explorer', path])
        else:
            subprocess.Popen(['xdg-open', path])
        return { 'ok': True }

    # -- FILES -------------------------------------------------
    def open_file_dialog(self):
        result = window.create_file_dialog(
            dialog_type = webview.FileDialog.OPEN,
            allow_multiple = False,
        )
        if result:
            return { 'ok': True, 'path': str(result[0]) }
        return { 'ok': False }

    def read_file(self, path: str):
        p = Path(path)
        text = p.read_text(encoding='utf-8', errors='replace')
        return { 'ok': True, 'text': text }

    def write_file(self, path: str, content: str):
        Path(path).write_text(content, encoding='utf-8')
        return { 'ok': True }

# -- STARTUP -----------------------------------------------
def main():
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    init_db()

    api = FocOsAPI()
    index_url = (BASE_DIR / 'web' / 'index.html').resolve().as_uri()
    global window
    window = webview.create_window(
        title = 'FocOs',
        url = index_url,
        js_api = api,
        width = 1400,
        height = 900,
        min_size = (900, 600),
        resizable = True,
        background_color = '#0A0A0A',
    )
    webview.start(on_start, debug=False)

if __name__ == '__main__':
    main()

06. DISTRIBUTING THE APP — PYINSTALLER

PyInstaller bundles the Python interpreter, the app, and all assets into a single executable. The result on Windows is a ~15MB .exe — no external runtime, no visible dependencies for the end user.

# Install PyInstaller
pip install pyinstaller

# Windows — separator ';'
pyinstaller --onefile --windowed \
  --add-data 'web;web' \
  --add-data 'data;data' \
  --name 'FocOs' \
  main.py

# Linux — separator ':'
pyinstaller --onefile --windowed \
  --add-data 'web:web' \
  --add-data 'data:data' \
  --name 'focos' \
  main.py

# Output:
dist/
  FocOs.exe # Windows -- ~15MB, no external runtime
  focos # Linux -- ~12MB
[!] NOTE: WEBVIEW2 ON WINDOWS

pywebview uses WebView2 on Windows. It comes pre-installed on Windows 11. On Windows 10 it may require additional installation by the end user. Check availability at: developer.microsoft.com/en-us/microsoft-edge/webview2/

07. REAL LIMITATIONS OF PYWEBVIEW

pywebview is not a universal Electron replacement. There are cases where Electron remains the correct choice. Knowing the constraints before committing to the architecture prevents rewrites.

Limitation Python workaround
DevTools only with debug=True Enable in development, disable in production build.
Multi-window with constraints Design the app as an SPA with panels — eliminates the need for multiple windows.
No native notification API plyer or notify2 from Python — independent of pywebview.
No system tray / dock API pystray from Python — integrates in the same process.

-- CONCLUSION

pywebview + Python is the right architecture for any desktop app where the core backend already lives in Python. Dropping Electron reduces distribution size from ~200MB to ~15MB, idle RAM from ~300MB to ~50MB, and stack complexity from 3 layers to 2 direct layers. FocOs proves it is possible to build a premium desktop app — with a code editor, terminal, browser, and LLM panel — without a single line of Node.js.

> SYSTEM_READY > NODE_ONLINE

< session_end // node: exit >

OLLAMA LOCAL: LLMs WITHOUT INTERNET, WITHOUT COST, WITHOUT CONTEXT LIMITS

TAGS: LLMs / PRIVACY / INFRASTRUCTURE / PYTHON READ_TIME: 12 MIN
Ollama local: LLMs without internet, without cost, without context limits

Groq is fast. Gemini is capable. Claude is brilliant. But they all share one thing: they require internet, have usage limits, and your conversations pass through third-party servers. Ollama is different — it is a runtime that runs language models directly on your machine. No internet. No API key. No per-token cost. Your data never leaves your disk.

PROJECT_STATUS: STABLE

Stack: Ollama · Python · any OS
Reference project: FocOs — local LLM provider
Goal: Full technological sovereignty — the LLM runs on your machine, data never leaves your disk

01. THE PROBLEM IT SOLVES

Cloud LLM APIs carry three limitations that directly impact real workflow: they require stable internet — no connection, no LLM; they impose rate and context limits that interrupt long sessions; and the code and ideas you share go to external servers. For an independent developer building their own ecosystem, these are not theoretical concerns — they are real blockers.

For long projects, late-night work sessions, sensitive code, or anyone who values operational independence — Ollama is the answer.

// Non-technical explanation

Imagine that every time you want to ask an expert a question, you have to call them by phone, wait for availability, and pay per minute. That is what cloud LLM APIs are. Ollama is like having that expert living in your house. Always available. No phone. No bill. No one else listening to the conversation.

02. INSTALLATION — 3 MINUTES ON ANY OS

Ollama installs with a single command on Linux, an installer on Windows, and Homebrew on macOS. Once installed, it runs as a background service and exposes a REST API on localhost:11434.

# LINUX (single line):
curl -fsSL https://ollama.com/install.sh | sh

# WINDOWS:
# Download installer from: https://ollama.com/download
# Run OllamaSetup.exe — runs as background service automatically

# MACOS:
brew install ollama

# Verify installation:
ollama --version
ollama version 0.5.x

# Verify server is running:
curl http://localhost:11434/api/tags
# Returns list of installed models (empty on first run)

03. RECOMMENDED MODELS — WHICH ONE FOR WHAT

Model selection depends on available RAM and task type. The rule is simple: the largest model your hardware can run without hitting swap. Swap kills inference performance completely.

Model Min RAM Disk Best for
llama3.2:3b 8 GB ~2 GB Simple tasks, short code completion, quick queries.
llama3.2 8 GB ~5 GB General development, debugging, technical explanations. The sweet spot.
llama3.3:70b 32 GB ~40 GB Complex architecture, deep reasoning, analysis.
gemma2:9b 16 GB ~6 GB Code. Excellent capability-to-size ratio. From Google.
codellama 8 GB ~4 GB Function completion, technical debugging, refactoring. Code-specialized.
mistral 8 GB ~4 GB Writing and synthesis. Fast and efficient.
deepseek-r1:8b 8 GB ~5 GB Step-by-step reasoning. Excellent for complex logical problems.

04. DOWNLOADING AND RUNNING A MODEL

The download happens once — the model is stored locally and available offline permanently. Management commands are minimal and intuitive.

# Download a model (first time only):
ollama pull llama3.2
# Downloads ~5GB — stored locally, works offline from this point

# Run in interactive chat mode:
ollama run llama3.2
# >>> Type your message here
# Ctrl+D or /bye to exit

# Run with inline prompt:
ollama run llama3.2 "explain what this does: def fib(n): return n if n<=1 else fib(n-1)+fib(n-2)"

# Model management:
ollama list # list installed models
ollama ps # list running models
ollama rm llama3.2 # remove a model
ollama pull llama3.2 # update a model

05. PYTHON INTEGRATION — THE REST API

Ollama exposes a REST API on localhost:11434 compatible with the OpenAI format. It integrates into FocOs exactly like Groq or Gemini — the AAL abstraction layer does not distinguish between a cloud model and one running on local disk.

# Direct call with urllib — zero external dependencies
import urllib.request, json

def call_ollama(model, messages, base_url='http://localhost:11434'):
    url = f'{base_url}/api/chat'
    payload = json.dumps({
        'model': model,
        'messages': messages,
        'stream': False,
        'options': {
            'temperature': 0.7,
            'num_ctx': 4096, # Context window — tune per available RAM
        }
    }).encode('utf-8')

    req = urllib.request.Request(
        url, data=payload,
        headers={'Content-Type': 'application/json'},
        method='POST'
    )
    res = urllib.request.urlopen(req, timeout=120)
    data = json.loads(res.read())
    return data.get('message', {}).get('content', '')

# Health check before calling:
def ollama_available(base_url='http://localhost:11434'):
    try:
        urllib.request.urlopen(f'{base_url}/api/tags', timeout=2)
        return True
    except Exception:
        return False

06. STREAMING — REAL-TIME RESPONSES

For long responses, streaming dramatically improves the experience — the user sees text appearing as the model generates, instead of waiting for the full inference to complete before seeing anything.

def call_ollama_stream(model, messages, on_token):
    '''
    on_token: callback receiving each text fragment
    Example: on_token = lambda t: print(t, end='', flush=True)
    '''
    import json, urllib.request

    url = 'http://localhost:11434/api/chat'
    payload = json.dumps({
        'model': model,
        'messages': messages,
        'stream': True,
    }).encode('utf-8')

    req = urllib.request.Request(
        url, data=payload,
        headers={'Content-Type': 'application/json'},
        method='POST'
    )
    full_response = ''
    with urllib.request.urlopen(req, timeout=120) as res:
        for line in res:
            if line.strip():
                chunk = json.loads(line.decode('utf-8'))
                token = chunk.get('message', {}).get('content', '')
                if token:
                    on_token(token)
                    full_response += token
                if chunk.get('done', False):
                    break
    return full_response

07. CUSTOM MODELS — MODELFILE

Ollama lets you create custom models with a fixed system prompt via a Modelfile. This allows packaging the Chronos assistant from FocOs as a standalone model — once created, the full ecosystem context is available without manual injection on every call.

# Modelfile

FROM llama3.2

SYSTEM """
You are Chronos — Frank's development assistant.
You operate inside FocOs, the window manager of the being who builds.
You know the ecosystem: FocOs, TeliOs, KayrOs, ChronOs, OruX.
Methodology: AAL — LLM-Agnostic Architecture.
Preferred stack: Python + HTML/CSS/JS vanilla. Zero dependencies.
Principle: the contract is the truth. Code is regenerable.
Always respond in the user's language.
Prioritize practical solutions over theory.
Maximum 3 options when alternatives exist.
"""

PARAMETER temperature 0.7
PARAMETER num_ctx 4096

# Create the custom model:
ollama create chronos -f Modelfile

# Run from terminal:
ollama run chronos

# Call from Python:
response = call_ollama('chronos', [{'role': 'user', 'content': 'hello'}])

-- CONCLUSION

Ollama turns technological independence from a principle into a practical reality. A developer with Ollama installed can build software with LLMs at 3am, without internet, without spending a cent, without any external server seeing their code. The Ollama + FocOs + AAL combination is the most sovereign stack available today for AI-assisted development: the LLM runs on your machine, the work environment is yours, the methodology is yours, and the data never leaves your disk.

> SYSTEM_READY > NODE_ONLINE

< session_end // node: exit >

CONTEXT AS A SUPERPOWER: AUTOMATIC STATE INJECTION IN EVERY LLM MESSAGE

TAGS: LLMs / PRODUCTIVITY / PROMPT ENGINEERING / PYTHON READ_TIME: 12 MIN
Context as a superpower: automatic state injection in every LLM message

The difference between a generic LLM and an assistant that genuinely knows your work is not the model. It is the context you feed it. But writing context manually in every message is slow, inconsistent, and easy to forget. FocOs solves this by automatically injecting the active project context into every message sent to the LLM — without the user writing anything extra, without omissions, without cross-session inconsistencies.

PROJECT_STATUS: STABLE

Stack: Python · JavaScript · any LLM
Reference project: FocOs — context system
Goal: Transform a generic LLM into an assistant that knows exactly where you are and what you are building

01. THE PROBLEM IT SOLVES

Without automatic context, the LLM does not know which project you are in, what task you are working on, what decisions have been made, or what the current system state is. The user ends up repeating context in every message — consuming tokens, time, and attention. Or worse: they skip it, and the LLM produces generic responses that do not apply to the specific problem.

// Non-technical explanation

Imagine you hire an expert consultant. Every time you call them, they have total amnesia — they remember nothing from the last call. You have two options: explain everything from scratch every time (exhausting), or hand them a one-page brief at the start of each call that gets them up to speed in 30 seconds. FocOs generates that brief automatically and injects it into every message before it reaches the LLM.

02. THE STAMP — THE CONTEXT SEAL

FocOs builds a compressed single-line identifier called the Stamp that is prepended to every message. In 80-120 characters it communicates everything the LLM needs to respond with precision.

# Stamp format:
[F|HH:MM TZ|Day|DX/1096|Project|Task|PX|WS]

# Fields:
# F = User identifier (Frank)
# HH:MM TZ = Local time with timezone
# Day = Abbreviated weekday
# DX/1096 = KayrOs day (how much of the horizon has elapsed)
# Project = Active project name
# Task = Active task or cell ID
# PX = Pomodoro number in current session
# WS = Active workspace (WS1, WS2, WS3)

# Real examples:
[F|10:30 COT|Thu|D4/1096|TeliOs|TELIO-U003|P1|WS1]
[F|14:45 COT|Tue|D18/1096|FocOs|FOCOS-B012|P3|WS2]
[F|09:00 COT|Mon|D1/1096|Novel-Cycles|CHAP-07|P1|WS1]
[F|22:30 COT|Fri|D365/1096|InfoGraTech|POST-14|P2|WS3]
Field Example Purpose
F F User identifier. Anchors the assistant's persona to a specific operator.
HH:MM TZ 10:30 COT Local time with timezone. Enables time-sensitive responses.
DX/1096 D4/1096 KayrOs horizon day. Positions the LLM within the project lifecycle.
Project TeliOs Active project name. Defines the response domain.
PX P1 Session pomodoro count. Signals operator cognitive load level.

03. BUILDING THE STAMP IN PYTHON

The get_context() function in FocOs builds the stamp and the complete system prompt injected into every LLM call. It queries the local database to retrieve the real state of the active workspace — never a cached assumption.

# main.py — FocOsAPI

def get_context(self) -> dict:
    '''
    Builds the full context for the active project.
    Automatically injected into every LLM call.
    '''
    import datetime
    conn = sqlite3.connect(DB_PATH)

    # Active workspace data
    ws = conn.execute(
        'SELECT project_id FROM workspaces WHERE ws_id=1'
    ).fetchone()
    project_id = ws[0] if ws else None

    # Project data
    project = None
    if project_id:
        row = conn.execute(
            'SELECT name, status, meta FROM projects WHERE id=?',
            (project_id,)
        ).fetchone()
        if row:
            project = {
                'name': row[0],
                'status': row[1],
                'meta': json.loads(row[2] or '{}'),
            }

    # Active task
    task = conn.execute(
        'SELECT title FROM tasks WHERE status="active" LIMIT 1'
    ).fetchone()
    conn.close()

    # Build stamp
    now = datetime.datetime.now()
    hora = now.strftime('%H:%M')
    dia = now.strftime('%a')[:3].capitalize()
    project_name = project['name'] if project else 'No project'
    task_name = task[0][:20] if task else 'No task'
    day_num = self._get_kayros_day()

    stamp = f'[F|{hora} COT|{dia}|D{day_num}/1096|{project_name}|{task_name}|P1|WS1]'

    # Full system prompt with context
    meta = project['meta'] if project else {}
    system = f'''You are Chronos — Frank's development assistant.
Active context: {stamp}

PROJECT: {project_name}
STATUS: {project["status"] if project else "unknown"}
ACTIVE TASK: {task_name}
TYPE: {meta.get("tipo", "general")}
STACK: {meta.get("stack", "undefined")}
PHILOSOPHY: {meta.get("filosofia", "")}

Always respond in the user's language.
Prioritize practical solutions over theory.
If you detect an error, flag it before answering.
Maximum 3 options when alternatives exist.
'''

    return {
        'stamp': stamp,
        'system': system,
        'project': project_name,
        'task': task_name,
        'day_num': day_num,
    }

04. INJECTING CONTEXT INTO EVERY MESSAGE

send_to_llm() calls get_context() automatically. The user never writes context manually — it arrives at the LLM on every message without any intervention.

def send_to_llm(self, message: str, history: list = None) -> dict:
    cfg = self.get_llm_config()
    provider = cfg.get('provider', 'gemini')
    model = cfg.get('model', 'gemini-2.0-flash')

    # AUTOMATIC CONTEXT — always present
    ctx = self.get_context()
    system = ctx['system']
    stamp = ctx['stamp']

    # The message reaching the LLM includes the stamp
    # e.g.: '[F|10:30 COT|Thu|D4/1096|TeliOs|TELIO-U003|P1|WS1]\n\nhow do I implement the pub/sub bus?'
    full_message = f'{stamp}\n\n{message}'

    messages = []
    if history:
        for h in (history if isinstance(history, list) else []):
            if isinstance(h, dict):
                messages.append({
                    'role': h.get('role', 'user'),
                    'content': h.get('content', '')
                })
    messages.append({'role': 'user', 'content': full_message})

    # Dispatch to provider with contextualized system prompt
    return self._dispatch_llm(provider, model, system, messages, cfg)

05. CONTEXT FOR NON-TECHNICAL PROJECTS

The stamp is not exclusive to software development. The same mechanism adapts to any project type — the system prompt changes according to the domain, but the compressed structure stays identical.

# WRITER — working on a novel
[F|09:15 COT|Tue|D22/1096|Novel-Cycles|CHAP-07|P1|WS1]
# Project: Novel — Cycles | Chapter: 7 | Character: Elena
# Tone: melancholic, introspective | POV shifts to first person

# MUSIC PRODUCER — working on an album
[F|23:00 COT|Fri|D45/1096|Album-Roots|TRACK-03|P2|WS1]
# Project: Album — Roots | Track: 03 | BPM: 92 | Key: Am
# Section: bridge | DAW: Ableton | Reference: Nils Frahm

# RESEARCHER
[F|11:30 COT|Wed|D8/1096|Thesis-Ecosystems|CHAP-04|P3|WS2]
# Project: Thesis | Chapter: 4 — Methodology
# Active hypothesis: H3 | Pending source: IEEE 2024
Project type Stamp example Key context injected
Software development D4/1096|TeliOs|TELIO-U003 Stack, architecture, active task state.
Writing / novel D22/1096|Novel-Cycles|CHAP-07 Chapter, character, tone, last narrative decision.
Music production D45/1096|Album-Roots|TRACK-03 BPM, key, active section, sonic reference.
Research / thesis D8/1096|Thesis-Ecosystems|CHAP-04 Chapter, active hypothesis, pending sources.

06. DISPLAYING THE STAMP IN THE FOCOS UI

The JavaScript bridge exposes the context to the LLM panel in real time. The user can see at any moment which stamp is being injected and confirm the assistant has the correct state before sending any message.

// bridge.js — Display stamp in the LLM panel

async function llmInit() {
  try {
    const api = await FocOs.ready()
    const ctx = await api.get_context()

    // Show stamp in LLM panel header
    const stampEl = document.getElementById('llm-stamp')
    if (stampEl && ctx.stamp) {
      stampEl.textContent = ctx.stamp
      stampEl.title = 'Active context injected in every message'
    }
    const projEl = document.getElementById('llm-project')
    if (projEl) projEl.textContent = ctx.project
  } catch(e) {
    console.log('llmInit:', e.message)
  }
}

async function llmSend(message) {
  const history = llmGetHistory()
  // send_to_llm() injects context automatically — user does nothing
  const result = await FocOs.sendToLLM(message, history)
  if (result.ok) {
    llmAppendMessage('assistant', result.response)
    llmSaveHistory('user', message)
    llmSaveHistory('assistant', result.response)
  } else {
    llmShowError(result.error)
  }
}

-- CONCLUSION

Automatic context injection turns a generic LLM into an assistant that knows exactly where you are and what you are building — without the user writing anything extra. The 80-character stamp communicates the complete work session state in ultra-compressed format. The same mechanism works for any project type: code, writing, music, research. And because the context is built from FocOs's local database, it always reflects the real state — never a fiction.

> SYSTEM_READY > NODE_ONLINE

< session_end // node: exit >

CHECKPOINT-BASED DEVELOPMENT WITH LLMs

TAGS: GIT / LLMs / METHODOLOGY / PRODUCTIVITY READ_TIME: 14 MIN
Checkpoint-based development with LLMs

Working with LLMs accelerates development. It also accelerates mistakes. In a single message, an LLM can rewrite three files, break two functions that were working, and solve the original problem. Net result: one step forward, two steps back. The solution is not to distrust the LLM — it is to have a checkpoint system that makes rollback so trivial it never hurts to do it.

PROJECT_STATUS: STABLE

Stack: Git · Python · any project
Reference projects: FocOs · TeliOs
Goal: Turn rollback into a 2-second operation with zero friction

01. THE PROBLEM IT SOLVES

When an LLM modifies multiple files in a single session, it becomes easy to lose track of which state was stable. Without explicit checkpoints, the developer ends up choosing between two equally costly options: push forward with broken code hoping to fix it later, or manually rewrite to get back to the previous state. Checkpoints eliminate that choice entirely.

When rollback does not hurt, the developer accepts more calculated risks. And calculated risks are precisely what produces real breakthroughs.

// Non-technical explanation

Imagine you are building a very complex LEGO castle. Every time you finish an important section — a tower, a bridge, a wall — you take a photo. If you accidentally break something, you do not have to start from scratch. You look at the photo and rebuild only the part that broke. Checkpoints are those photos. Git takes them automatically whenever you say so.

02. THE GOLDEN RULE — ONE CHECKPOINT PER STABLE STATE

A checkpoint is not a commit for every change. It is a commit for every verified functional state. The distinction is fundamental — and it is the difference between a useful history and noise.

# WRONG — committing every change
git commit -m 'changes'
git commit -m 'more changes'
git commit -m 'fixing something'
git commit -m 'wip'

# Result: useless history. You have no idea which state worked.

# RIGHT — committing verified stable states
# Before handing work to the LLM:
git add .
git commit -m 'CHECKPOINT: LLM panel functional — before adding history'

# After verifying the new state works:
git add .
git commit -m 'CHECKPOINT: conversation history implemented and verified'

# Result: every commit is a reliable restoration point.

03. THE FULL PROTOCOL — BEFORE, DURING AND AFTER

# BEFORE handing work to the LLM:

# 1. Verify the current state works
python main.py

# 2. Commit the checkpoint
git add .
git commit -m 'CHECKPOINT: [description of current state]'

# 3. Log what you are about to ask the LLM (SESSION-LOG or comment)
# 'Going to ask it to implement the conversation history'

# DURING work with the LLM:

# 4. Review EVERY change before applying — never apply in bulk unread
# 5. Test after each modified file — do not wait for all changes

# AFTER — if the result works:
git add .
git commit -m 'CHECKPOINT: [description of new state]'

# AFTER — if the result is broken:
git checkout . # discard uncommitted changes
# or
git reset --hard HEAD # revert to last checkpoint

04. NAMING CONVENTION — THE ONE THAT SAVES TIME

A checkpoint name must communicate two things: what state exists at this point and why this moment deserves a checkpoint. If you cannot describe the state in one line, the state is not clear yet — do not checkpoint.

# Format: CHECKPOINT: [state] — [context]

# Real examples from FocOs:
CHECKPOINT: JS<>Python bridge stable — before integrating Monaco
CHECKPOINT: Monaco with model:null — tabs without shared buffer bug
CHECKPOINT: LLM Groq responding — User-Agent fix applied
CHECKPOINT: xterm.js terminal functional — before adding autocomplete
CHECKPOINT: dashboard with real data — before connecting SQLite

# What NEVER belongs in a checkpoint name:
# 'fix' | 'minor changes' | 'wip' | 'updating'
Name Valid checkpoint? Reason
CHECKPOINT: bridge stable — before Monaco [ + ] Yes Clearly describes state and context.
fix [ - ] No Does not describe what was fixed or the resulting state.
wip [ - ] No Work in progress is not a verified stable state.
minor changes [ - ] No Ambiguous. History becomes useless within 48 hours.

05. GIT STASH — THE TEMPORARY CHECKPOINT

When you want to explore an idea without committing to a checkpoint, git stash saves the current state temporarily without making a commit. Ideal for quick experiments without polluting the history.

# Save current changes temporarily
git stash push -m 'experiment: testing alternative Monaco approach'

# If the experiment worked — checkpoint and drop the stash
git add .
git commit -m 'CHECKPOINT: alternative Monaco approach works better'
git stash drop

# If the experiment failed — recover previous work
git stash pop

# Inspect stashes
git stash list
git stash show stash@{0} -p

06. BRANCHES FOR MAJOR EXPERIMENTS

For changes that affect the full architecture, use a separate branch — never main. If the experiment fails, main was never touched. Zero drama.

# Create experiment branch
git checkout -b experiment/llm-streaming

# If it works — merge into main
git checkout main
git merge experiment/llm-streaming
git commit -m 'CHECKPOINT: LLM streaming integrated from branch'

# If it fails — delete branch without touching main
git checkout main
git branch -D experiment/llm-streaming

# Branch naming in FocOs:
# feature/onboarding | feature/file-explorer
# experiment/monaco-vim-mode | fix/bridge-timeout

07. SESSION-LOG INTEGRATION

Every checkpoint should have a corresponding entry in the day's SESSION-LOG. This keeps the Git history and the project history synchronized — you can reconstruct exactly what decision you made and with which LLM.

## Day checkpoints
- 10:30 — CHECKPOINT: JS<>Python bridge stable
          Commit: a3f9c2b
          State before: bridge crashed on cold start
          State after: on_start() callback stable
          LLM used: Claude Sonnet

- 14:15 — CHECKPOINT: Monaco independent tabs
          Commit: d8e4f1a
          State before: all tabs shared buffer
          State after: model:null + unique URI per tab
          LLM used: Groq llama-3.3-70b

## Day rollbacks
- 12:00 — Rollback to stable bridge
          Reason: streaming attempt broke the bridge
          Time lost: 45 min
          Lesson: test streaming on a separate branch

-- CONCLUSION

Checkpoint-based development applied to LLM-driven projects turns rollback from a costly, painful operation into a 2-second routine. The psychological effect is as important as the technical one: when the developer knows they can go back without drama, they accept more experiments. More experiments means more discoveries. The cost of being wrong drops to zero — and that changes build velocity as much as the LLM itself.

> SYSTEM_READY > NODE_ONLINE

< session_end // node: exit >
> INFOGRATECH_CORE_SHELL X
$