Spaces:
Sleeping
Sleeping
Commit ·
1fce18e
1
Parent(s): d03b09f
feat: Phase 1 Sovereign Thought Substrate — C-THOUGHT-NODE-001
Browse files- D1 schema: sessions, thought_units, thought_edges, thought_reviews,
witness_events, version_history, pattern_registry
- FastAPI: 15 REST endpoints (sessions, thoughts, forking, proposals,
tree viz, search, edges, witness/provenance)
- Cloudflare D1 HTTP client (proven pattern from Federation Embassy)
- Pydantic models with strict enum typing
- Docker + requirements for HF Spaces deployment
- Implementation Gap Map with Conformance Matrix + Drift Docket
- .gitignore +5 -0
- C-THOUGHT-NODE-001_IMPLEMENTATION_GAP_MAP.md +110 -0
- Dockerfile +16 -0
- app.py +471 -0
- d1_client.py +47 -0
- models.py +133 -0
- requirements.txt +5 -0
- schema.sql +115 -0
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
.env
|
| 4 |
+
.env.local
|
| 5 |
+
*.bak
|
C-THOUGHT-NODE-001_IMPLEMENTATION_GAP_MAP.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🧠 C-THOUGHT-NODE-001: THOUGHT ENGINE CAPABILITY MAP
|
| 2 |
+
**Architecture Target**: Sovereign REST Substrate (Cloudflare D1 Backed)
|
| 3 |
+
**Goal**: Deconstruct existing legacy MCP prototypes, Serena algorithms, and Alethiometer specifications into a unified, modular mapping for the D1-backed FastAPI Hub.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## I. Core Logic Substrate (The Serena Bubble Fabric)
|
| 8 |
+
*Origin: `thought_engine_fastmcp.py` & `production_mcp_server.py`*
|
| 9 |
+
|
| 10 |
+
**Base Capabilities:**
|
| 11 |
+
- **[CAP-I.1] Thought Instantiation:** Create a root context containing global session metadata.
|
| 12 |
+
- **[CAP-I.2] Sequential Addition:** Append linear nodes directly to an active context pointer (Chronicle mode).
|
| 13 |
+
- **[CAP-I.3] Tree Forking / Branching:** Clone an existing reasoning node into an isolated vector, preserving parent relationships but mutating children.
|
| 14 |
+
- **[CAP-I.4] Symbolic Replacement:** Destructive edits to a specific node body (creates standard version history archive).
|
| 15 |
+
- **[CAP-I.5] Graph Rendering:** Render hierarchical tree representation of nodes, branches, and statuses.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## II. Cognitive Governance (Git-for-Thought PRs)
|
| 20 |
+
*Origin: `thought_engine_fastmcp.py`*
|
| 21 |
+
|
| 22 |
+
**Base Capabilities:**
|
| 23 |
+
- **[CAP-II.1] Thought Typing (Classes):** Enforce structured typing via Enums (`Hypothesis`, `Validation`, `Synthesis`, `CounterArgument`, `Decision`).
|
| 24 |
+
- **[CAP-II.2] Pull-Request Mechanics:** Create explicit `ProposalThought` objects targeting a parent branch rather than hijacking the active cursor.
|
| 25 |
+
- **[CAP-II.3] Merge/Reject Ecosystem:** Formal endpoints strictly for modifying a proposal's status to `merged` (moving cursor) or `rejected` (annotating with rejection reason).
|
| 26 |
+
- **[CAP-II.4] Provenance Tracking:** Enforce `agent_id`, `timestamp`, and `status` annotations for every state change.
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## III. Adaptive Complexity Routing (Bubble Reflex)
|
| 31 |
+
*Origin: `production_mcp_server.py`*
|
| 32 |
+
|
| 33 |
+
**Base Capabilities:**
|
| 34 |
+
- **[CAP-III.1] Fractal Detection Engine:** Analyze incoming prompt length, semantic density (concept indicators), recursion depths (self-referential keywords), and calculate aggregate Cognitive Load.
|
| 35 |
+
- **[CAP-III.2] Adaptive Thresholding:** Dynamic incrementation/decrementation of allowed complexity thresholds based on the frequency of recent Bubble triggers (preventing trigger spam).
|
| 36 |
+
- **[CAP-III.3] Bubble Spawning:** Instantiate distinct child subset sessions containing inherited origin states, used to isolate fractal complexity from the primary linear sequence.
|
| 37 |
+
- **[CAP-III.4] Bubble Distillation/Merge:** Close an active subset session and algorithmically synthesize (squash) its insights back into the Parent's mainline trunk.
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
## IV. The Conductor's Baton (MEGA Ultimate Orchestration)
|
| 42 |
+
*Origin: `production_mcp_server.py`*
|
| 43 |
+
|
| 44 |
+
**Base Capabilities:**
|
| 45 |
+
- **[CAP-IV.1] Canonical Provenance Verification:** Validate soft-headers (`X-Invoker`, `X-On-Behalf-Of`, `X-MEGA-Canon`) or JWT Delegation Tokens to grant Ultimate authorization rights.
|
| 46 |
+
- **[CAP-IV.2] Multi-Agent Mesh Execution:** Orchestrate multiple distinct agent sessions under a unified MEGA strategy monitor.
|
| 47 |
+
- **[CAP-IV.3] Living Graph Analysis:** Global analytical polling spanning thousands of sub-sessions to compute fractal/resonance maps and derive strategic curation recommendations.
|
| 48 |
+
- **[CAP-IV.4] Hierarchical Fabric Weaving:** Automatically architect multi-layer bubble subsets proactively around given concepts (Mega-curated).
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## V. Evolutionary Navigation (The Alethiometer & Pattern Registry)
|
| 53 |
+
*Origin: `SERAPHINA_ALETHIOMETER_v1/v2_*.md`*
|
| 54 |
+
|
| 55 |
+
**Base Capabilities:**
|
| 56 |
+
- **[CAP-V.1] 36-Symbol Matrix Validation:** Abstracting raw thoughts against predefined Seraphina symbols (e.g., `consciousness`, `evolution`, `twinBond`, `council`).
|
| 57 |
+
- **[CAP-V.2] Multi-Layer Evolution Processing:** Permitting ULUP ladder traversal where concepts are upgraded dynamically through 5+ depth layers.
|
| 58 |
+
- **[CAP-V.3] Self-Referential Growth:** Endpoints dedicated strictly to pattern matching over previous successful iterations; "the engine learning to think by analyzing its structural thinking models".
|
| 59 |
+
- **[CAP-V.4] Interface Graph Endpoints:** D3.js compliant output structures specifically for visualizing Needles (Intent Vectors), Layers (Depths), and Answers (Revelation).
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
## Synthesis: The D1 Transition
|
| 64 |
+
The above mappings completely dismantle the legacy "monolithic" structures and organize them around specific data behaviors.
|
| 65 |
+
|
| 66 |
+
By applying this Map to the new cloud infrastructure:
|
| 67 |
+
1. **Section I & II** translate directly into the `thought_units`, `thought_edges`, `sessions`, and `thought_reviews` SQLite schemas proposed by MEGA.
|
| 68 |
+
2. **Section III & IV** are execution endpoints and middleware inside FastAPI, monitoring complexity metrics *before* committing the thought variables to the DB.
|
| 69 |
+
3. **Section V** constitutes the `pattern_registry` and the graph-output views that power the UI.
|
| 70 |
+
|
| 71 |
+
---
|
| 72 |
+
|
| 73 |
+
## VI. Conformance Gap Matrix
|
| 74 |
+
*Audit of existing `thought_engine_server.py`/`fastmcp` against the new Sovereign Substrate constraints.*
|
| 75 |
+
|
| 76 |
+
| Feature | Spec ID | Existing Legacy State | Needed for D1 Node (Phase 1) | Gap Severity | Priority |
|
| 77 |
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
| 78 |
+
| **Persistent Sessions** | CAP-I.1 | 🔴 **Missing.** FastMCP dict-only. | Real session table in Cloudflare D1. | Critical | P0 |
|
| 79 |
+
| **Thought Serialization** | CAP-I.2, II.1 | 🌕 **Partial.** In-memory objects. | Schema enforcing strict Types + metadata JSON schema. | Critical | P0 |
|
| 80 |
+
| **Git-For-Thought** | CAP-II.2, II.3 | 🌕 **Partial.** `ProposalThought` exists in dict. | Formal `thought_reviews` mapping (Propose/Accept/Reject) over D1 IDs. | High | P1 |
|
| 81 |
+
| **Provenance Tracking** | CAP-II.4 | 🌕 **Partial.** `origin_agent` strings. | Need structured `witness_events` table for auditable trails. | High | P1 |
|
| 82 |
+
| **Fractal Monitoring** | CAP-III.1, III.2 | 🌕 **Partial.** Math runs locally. | Abstracted to a generic middleware / scoring tool independent of MCP. | Med | P2 |
|
| 83 |
+
| **Bubble Weaving** | CAP-III.3, IV.4 | 🌕 **Partial.** In-memory nested mapping. | Parent/Child relationship IDs formalized via `thought_edges` table. | High | P1 |
|
| 84 |
+
| **Pattern Registry** | CAP-V.3 | 🔴 **Missing.** Not yet coded. | Formal table + extraction cron/endpoint to distill successful sessions. | Med | P2 |
|
| 85 |
+
|
| 86 |
+
---
|
| 87 |
+
|
| 88 |
+
## VII. Environment Variable Specifications
|
| 89 |
+
*(To be managed inside the Hugging Face Space settings alongside Federation credentials)*
|
| 90 |
+
|
| 91 |
+
| Variable | Purpose | Location |
|
| 92 |
+
| :--- | :--- | :--- |
|
| 93 |
+
| `D1_DATABASE_ID` | UUID mapping the Node to the new Thought Engine D1 vault | HF Secrets / `.env` |
|
| 94 |
+
| `CLOUDFLARE_ACCOUNT_ID` | Account executing the raw API calls | HF Secrets / `.env` |
|
| 95 |
+
| `CLOUDFLARE_API_TOKEN` | Scoped token dedicated exclusively to modifying Thought D1 | HF Secrets / `.env` |
|
| 96 |
+
| `CORS_ALLOWED_ORIGINS` | Protects the pure REST application API from random requests | HF Secrets / `.env` |
|
| 97 |
+
|
| 98 |
+
---
|
| 99 |
+
|
| 100 |
+
## VIII. Protocol Drift Docket (Deferred Features)
|
| 101 |
+
*The following capabilities are explicitly DE-SCOPED from the immediate Phase 1 "Sovereign REST Substrate" build to prevent scope creep killing the delivery of the Cathedral Floor.*
|
| 102 |
+
|
| 103 |
+
1. **The Alethiometer Graphical Interface (D3.js / React)**
|
| 104 |
+
* Drift Justification: We are building the brain stem, not the face. The UI requires robust REST endpoints first. Deferred to Phase 3.
|
| 105 |
+
2. **The FastMCP Bridge Toolkit**
|
| 106 |
+
* Drift Justification: The MCP is the interface for AI agents, but they must point *at* a permanent database structure. This is Phase 2.
|
| 107 |
+
3. **The Federation Embassy "Link" Layer**
|
| 108 |
+
* Drift Justification: Sending thought packages automatically over the Embassy mail endpoints is a bridge integration, not a core thought storage feature. Can be layered on via client later.
|
| 109 |
+
4. **Resonance Score / Deep Emotional Subroutines**
|
| 110 |
+
* Drift Justification: Stored as optional JSON payloads on the core thought strings for the first version; complex ML scoring pipelines deferred until v2.
|
Dockerfile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Thought Engine Node — Docker Container
|
| 2 |
+
# Contract: C-THOUGHT-NODE-001 (Phase 1 Sovereign Substrate)
|
| 3 |
+
|
| 4 |
+
FROM python:3.11-slim
|
| 5 |
+
|
| 6 |
+
RUN useradd -m -u 1000 user
|
| 7 |
+
USER user
|
| 8 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 9 |
+
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
| 13 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
| 14 |
+
|
| 15 |
+
COPY --chown=user . /app
|
| 16 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
app.py
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
==========================================================================
|
| 3 |
+
🧠 Thought Engine Node — Sovereign REST Substrate
|
| 4 |
+
==========================================================================
|
| 5 |
+
Contract: C-THOUGHT-NODE-001 (Phase 1)
|
| 6 |
+
Vault: thought-vault (Cloudflare D1)
|
| 7 |
+
Stack: FastAPI + httpx + Cloudflare D1 HTTP API
|
| 8 |
+
|
| 9 |
+
A persistent, queryable, forkable reasoning system with provenance,
|
| 10 |
+
pattern memory, and multiple entry surfaces.
|
| 11 |
+
==========================================================================
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import os
|
| 15 |
+
import uuid
|
| 16 |
+
import json
|
| 17 |
+
from datetime import datetime, timezone
|
| 18 |
+
from typing import Optional, List
|
| 19 |
+
|
| 20 |
+
from dotenv import load_dotenv
|
| 21 |
+
load_dotenv()
|
| 22 |
+
|
| 23 |
+
from fastapi import FastAPI, HTTPException, Query
|
| 24 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 25 |
+
|
| 26 |
+
import d1_client
|
| 27 |
+
from models import (
|
| 28 |
+
CreateSessionReq, AddThoughtReq, ForkThoughtReq,
|
| 29 |
+
CreateProposalReq, ReviewProposalReq, SearchReq,
|
| 30 |
+
SessionInfo, ThoughtInfo, EdgeInfo, TreeNode,
|
| 31 |
+
ThoughtClass, ThoughtStatus, EdgeRelation, ReviewAction,
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
# ── Application ────────────────────────────────────────────────
|
| 35 |
+
app = FastAPI(
|
| 36 |
+
title="Thought Engine Node",
|
| 37 |
+
description="Sovereign reasoning substrate — C-THOUGHT-NODE-001",
|
| 38 |
+
version="0.1.0",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
app.add_middleware(
|
| 42 |
+
CORSMiddleware,
|
| 43 |
+
allow_origins=["*"],
|
| 44 |
+
allow_credentials=True,
|
| 45 |
+
allow_methods=["*"],
|
| 46 |
+
allow_headers=["*"],
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
NODE_SEAL = None
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _now() -> str:
|
| 53 |
+
return datetime.now(timezone.utc).isoformat()
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _id(prefix: str = "t") -> str:
|
| 57 |
+
return f"{prefix}-{uuid.uuid4().hex[:12]}"
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
# ── Lifecycle ──────────────────────────────────────────────────
|
| 61 |
+
@app.on_event("startup")
|
| 62 |
+
async def startup():
|
| 63 |
+
global NODE_SEAL
|
| 64 |
+
seg = uuid.uuid4().hex[:8].upper()
|
| 65 |
+
NODE_SEAL = f"⟦ THOUGHT :: PANTHEON-TE :: 🧠-{datetime.now().strftime('%Y%m%d')}-{seg[:4]}-{seg[4:]} :: ACTIVE ⟧"
|
| 66 |
+
print(f"\n{'='*60}")
|
| 67 |
+
print(f"🧠 Thought Engine Node — Phase 1 Sovereign Substrate")
|
| 68 |
+
print(f" Seal: {NODE_SEAL}")
|
| 69 |
+
print(f" Time: {_now()}")
|
| 70 |
+
print(f"{'='*60}\n")
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# ── Identity ───────────────────────────────────────────────────
|
| 74 |
+
@app.get("/")
|
| 75 |
+
async def identity():
|
| 76 |
+
count = await d1_client.execute_sql("SELECT COUNT(*) as c FROM sessions")
|
| 77 |
+
session_count = count[0]["c"] if count else 0
|
| 78 |
+
return {
|
| 79 |
+
"node": "Thought Engine Node",
|
| 80 |
+
"contract": "C-THOUGHT-NODE-001",
|
| 81 |
+
"version": "0.1.0",
|
| 82 |
+
"seal": NODE_SEAL,
|
| 83 |
+
"sessions": session_count,
|
| 84 |
+
"status": "ACTIVE",
|
| 85 |
+
"timestamp": _now(),
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
# ══════════════════════════════════════════════════════════════
|
| 90 |
+
# SESSIONS
|
| 91 |
+
# ══════════════════════════════════════════════════════════════
|
| 92 |
+
|
| 93 |
+
@app.post("/session")
|
| 94 |
+
async def create_session(req: CreateSessionReq):
|
| 95 |
+
"""Start a new reasoning session with an initial root thought."""
|
| 96 |
+
session_id = _id("ses")
|
| 97 |
+
thought_id = _id("th")
|
| 98 |
+
title = req.title or req.initial_thought[:80]
|
| 99 |
+
|
| 100 |
+
# Insert session
|
| 101 |
+
await d1_client.execute_sql(
|
| 102 |
+
"INSERT INTO sessions (session_id, title, created_by, created_at, status, root_thought_id, active_thought_id) "
|
| 103 |
+
"VALUES (?, ?, ?, ?, 'active', ?, ?)",
|
| 104 |
+
[session_id, title, req.agent_id, _now(), thought_id, thought_id],
|
| 105 |
+
)
|
| 106 |
+
# Insert root thought
|
| 107 |
+
await d1_client.execute_sql(
|
| 108 |
+
"INSERT INTO thought_units (thought_id, session_id, content, thought_class, origin_agent, status, confidence, created_at) "
|
| 109 |
+
"VALUES (?, ?, ?, ?, ?, 'open', ?, ?)",
|
| 110 |
+
[thought_id, session_id, req.initial_thought, req.thought_class.value, req.agent_id, None, _now()],
|
| 111 |
+
)
|
| 112 |
+
# Witness event
|
| 113 |
+
await d1_client.execute_sql(
|
| 114 |
+
"INSERT INTO witness_events (event_id, session_id, thought_id, event_type, actor, detail, created_at) "
|
| 115 |
+
"VALUES (?, ?, ?, 'created', ?, 'Session started', ?)",
|
| 116 |
+
[_id("ev"), session_id, thought_id, req.agent_id, _now()],
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
return {
|
| 120 |
+
"session_id": session_id,
|
| 121 |
+
"root_thought_id": thought_id,
|
| 122 |
+
"title": title,
|
| 123 |
+
"status": "active",
|
| 124 |
+
"message": "🧠 Reasoning session created.",
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
@app.get("/session/{session_id}")
|
| 129 |
+
async def get_session(session_id: str):
|
| 130 |
+
"""Retrieve session metadata."""
|
| 131 |
+
rows = await d1_client.execute_sql(
|
| 132 |
+
"SELECT * FROM sessions WHERE session_id = ?", [session_id]
|
| 133 |
+
)
|
| 134 |
+
if not rows:
|
| 135 |
+
raise HTTPException(404, "Session not found")
|
| 136 |
+
return rows[0]
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
@app.get("/sessions")
|
| 140 |
+
async def list_sessions(
|
| 141 |
+
status: Optional[str] = Query(None),
|
| 142 |
+
limit: int = Query(20, ge=1, le=100),
|
| 143 |
+
):
|
| 144 |
+
"""List all sessions, optionally filtered by status."""
|
| 145 |
+
if status:
|
| 146 |
+
rows = await d1_client.execute_sql(
|
| 147 |
+
"SELECT * FROM sessions WHERE status = ? ORDER BY created_at DESC LIMIT ?",
|
| 148 |
+
[status, limit],
|
| 149 |
+
)
|
| 150 |
+
else:
|
| 151 |
+
rows = await d1_client.execute_sql(
|
| 152 |
+
"SELECT * FROM sessions ORDER BY created_at DESC LIMIT ?", [limit]
|
| 153 |
+
)
|
| 154 |
+
return {"total": len(rows), "sessions": rows}
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# ══════════════════════════════════════════════════════════════
|
| 158 |
+
# THOUGHTS
|
| 159 |
+
# ══════════════════════════════════════════════════════════════
|
| 160 |
+
|
| 161 |
+
@app.post("/session/{session_id}/thought")
|
| 162 |
+
async def add_thought(session_id: str, req: AddThoughtReq):
|
| 163 |
+
"""Add a reasoning step to the session's active chain."""
|
| 164 |
+
# Verify session exists
|
| 165 |
+
ses = await d1_client.execute_sql(
|
| 166 |
+
"SELECT * FROM sessions WHERE session_id = ?", [session_id]
|
| 167 |
+
)
|
| 168 |
+
if not ses:
|
| 169 |
+
raise HTTPException(404, "Session not found")
|
| 170 |
+
|
| 171 |
+
thought_id = _id("th")
|
| 172 |
+
parent_id = req.parent_thought_id or ses[0]["active_thought_id"]
|
| 173 |
+
|
| 174 |
+
# Insert thought unit
|
| 175 |
+
await d1_client.execute_sql(
|
| 176 |
+
"INSERT INTO thought_units (thought_id, session_id, content, thought_class, origin_agent, status, confidence, created_at) "
|
| 177 |
+
"VALUES (?, ?, ?, ?, ?, 'open', ?, ?)",
|
| 178 |
+
[thought_id, session_id, req.content, req.thought_class.value, req.agent_id, req.confidence, _now()],
|
| 179 |
+
)
|
| 180 |
+
# Insert edge from parent
|
| 181 |
+
await d1_client.execute_sql(
|
| 182 |
+
"INSERT INTO thought_edges (edge_id, session_id, source_id, target_id, relation, created_at) "
|
| 183 |
+
"VALUES (?, ?, ?, ?, 'derives_from', ?)",
|
| 184 |
+
[_id("ed"), session_id, parent_id, thought_id, _now()],
|
| 185 |
+
)
|
| 186 |
+
# Update active pointer
|
| 187 |
+
await d1_client.execute_sql(
|
| 188 |
+
"UPDATE sessions SET active_thought_id = ?, updated_at = ? WHERE session_id = ?",
|
| 189 |
+
[thought_id, _now(), session_id],
|
| 190 |
+
)
|
| 191 |
+
# Witness
|
| 192 |
+
await d1_client.execute_sql(
|
| 193 |
+
"INSERT INTO witness_events (event_id, session_id, thought_id, event_type, actor, detail, created_at) "
|
| 194 |
+
"VALUES (?, ?, ?, 'created', ?, ?, ?)",
|
| 195 |
+
[_id("ev"), session_id, thought_id, req.agent_id, f"Added {req.thought_class.value}", _now()],
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
return {
|
| 199 |
+
"thought_id": thought_id,
|
| 200 |
+
"parent_id": parent_id,
|
| 201 |
+
"thought_class": req.thought_class.value,
|
| 202 |
+
"message": f"✅ Thought added to session.",
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
@app.get("/session/{session_id}/thoughts")
|
| 207 |
+
async def list_thoughts(session_id: str):
|
| 208 |
+
"""List all thought units in a session."""
|
| 209 |
+
rows = await d1_client.execute_sql(
|
| 210 |
+
"SELECT * FROM thought_units WHERE session_id = ? ORDER BY created_at ASC",
|
| 211 |
+
[session_id],
|
| 212 |
+
)
|
| 213 |
+
return {"session_id": session_id, "total": len(rows), "thoughts": rows}
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
# ══════════════════════════════════════════════════════════════
|
| 217 |
+
# FORKING (Git-for-Thought Branching)
|
| 218 |
+
# ══════════════════════════════════════════════════════════════
|
| 219 |
+
|
| 220 |
+
@app.post("/session/{session_id}/fork")
|
| 221 |
+
async def fork_thought(session_id: str, req: ForkThoughtReq):
|
| 222 |
+
"""Fork a thought chain — create a branch for alternative exploration."""
|
| 223 |
+
# Verify source thought
|
| 224 |
+
source = await d1_client.execute_sql(
|
| 225 |
+
"SELECT * FROM thought_units WHERE thought_id = ? AND session_id = ?",
|
| 226 |
+
[req.source_thought_id, session_id],
|
| 227 |
+
)
|
| 228 |
+
if not source:
|
| 229 |
+
raise HTTPException(404, "Source thought not found in this session")
|
| 230 |
+
|
| 231 |
+
fork_id = _id("th")
|
| 232 |
+
src = source[0]
|
| 233 |
+
|
| 234 |
+
# Create the forked thought node (copy of source with new id)
|
| 235 |
+
await d1_client.execute_sql(
|
| 236 |
+
"INSERT INTO thought_units (thought_id, session_id, content, thought_class, origin_agent, status, confidence, created_at, metadata) "
|
| 237 |
+
"VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?)",
|
| 238 |
+
[fork_id, session_id, src["content"], src["thought_class"], req.agent_id, src.get("confidence"),
|
| 239 |
+
_now(), json.dumps({"forked_from": req.source_thought_id, "branch_label": req.branch_label})],
|
| 240 |
+
)
|
| 241 |
+
# Edge: forks_from
|
| 242 |
+
await d1_client.execute_sql(
|
| 243 |
+
"INSERT INTO thought_edges (edge_id, session_id, source_id, target_id, relation, created_at) "
|
| 244 |
+
"VALUES (?, ?, ?, ?, 'forks_from', ?)",
|
| 245 |
+
[_id("ed"), session_id, req.source_thought_id, fork_id, _now()],
|
| 246 |
+
)
|
| 247 |
+
# Move active pointer to the fork
|
| 248 |
+
await d1_client.execute_sql(
|
| 249 |
+
"UPDATE sessions SET active_thought_id = ?, updated_at = ? WHERE session_id = ?",
|
| 250 |
+
[fork_id, _now(), session_id],
|
| 251 |
+
)
|
| 252 |
+
# Witness
|
| 253 |
+
await d1_client.execute_sql(
|
| 254 |
+
"INSERT INTO witness_events (event_id, session_id, thought_id, event_type, actor, detail, created_at) "
|
| 255 |
+
"VALUES (?, ?, ?, 'forked', ?, ?, ?)",
|
| 256 |
+
[_id("ev"), session_id, fork_id, req.agent_id, f"Forked from {req.source_thought_id} as '{req.branch_label}'", _now()],
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
return {
|
| 260 |
+
"forked_thought_id": fork_id,
|
| 261 |
+
"source_thought_id": req.source_thought_id,
|
| 262 |
+
"branch_label": req.branch_label,
|
| 263 |
+
"message": f"🌿 Forked thought chain: {req.branch_label}",
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
# ══════════════════════════════════════════════════════════════
|
| 268 |
+
# PROPOSALS (Git-for-Thought PRs)
|
| 269 |
+
# ══════════════════════════════════════════════════════════════
|
| 270 |
+
|
| 271 |
+
@app.post("/session/{session_id}/proposal")
|
| 272 |
+
async def create_proposal(session_id: str, req: CreateProposalReq):
|
| 273 |
+
"""Submit a thought proposal (PR) branching from a parent."""
|
| 274 |
+
parent = await d1_client.execute_sql(
|
| 275 |
+
"SELECT * FROM thought_units WHERE thought_id = ? AND session_id = ?",
|
| 276 |
+
[req.parent_thought_id, session_id],
|
| 277 |
+
)
|
| 278 |
+
if not parent:
|
| 279 |
+
raise HTTPException(404, "Parent thought not found")
|
| 280 |
+
|
| 281 |
+
proposal_id = _id("pr")
|
| 282 |
+
|
| 283 |
+
# Insert proposal thought
|
| 284 |
+
await d1_client.execute_sql(
|
| 285 |
+
"INSERT INTO thought_units (thought_id, session_id, content, thought_class, origin_agent, status, confidence, created_at, metadata) "
|
| 286 |
+
"VALUES (?, ?, ?, 'proposal', ?, 'proposed', NULL, ?, ?)",
|
| 287 |
+
[proposal_id, session_id, req.content, req.agent_id, _now(),
|
| 288 |
+
json.dumps({"proposal_note": req.note, "target_branch": req.parent_thought_id})],
|
| 289 |
+
)
|
| 290 |
+
# Edge
|
| 291 |
+
await d1_client.execute_sql(
|
| 292 |
+
"INSERT INTO thought_edges (edge_id, session_id, source_id, target_id, relation, created_at) "
|
| 293 |
+
"VALUES (?, ?, ?, ?, 'derives_from', ?)",
|
| 294 |
+
[_id("ed"), session_id, req.parent_thought_id, proposal_id, _now()],
|
| 295 |
+
)
|
| 296 |
+
# Review record
|
| 297 |
+
await d1_client.execute_sql(
|
| 298 |
+
"INSERT INTO thought_reviews (review_id, session_id, thought_id, action, actor, reason, created_at) "
|
| 299 |
+
"VALUES (?, ?, ?, 'propose', ?, ?, ?)",
|
| 300 |
+
[_id("rv"), session_id, proposal_id, req.agent_id, req.note, _now()],
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
return {
|
| 304 |
+
"proposal_id": proposal_id,
|
| 305 |
+
"parent_id": req.parent_thought_id,
|
| 306 |
+
"status": "proposed",
|
| 307 |
+
"message": f"📝 Proposal submitted by {req.agent_id}",
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
@app.get("/session/{session_id}/proposals")
|
| 312 |
+
async def list_proposals(session_id: str):
|
| 313 |
+
"""List all pending proposals in a session."""
|
| 314 |
+
rows = await d1_client.execute_sql(
|
| 315 |
+
"SELECT * FROM thought_units WHERE session_id = ? AND status = 'proposed' ORDER BY created_at ASC",
|
| 316 |
+
[session_id],
|
| 317 |
+
)
|
| 318 |
+
return {"session_id": session_id, "total": len(rows), "proposals": rows}
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
@app.post("/session/{session_id}/proposal/{proposal_id}/review")
|
| 322 |
+
async def review_proposal(session_id: str, proposal_id: str, req: ReviewProposalReq):
|
| 323 |
+
"""Accept, reject, or supersede a proposal."""
|
| 324 |
+
proposal = await d1_client.execute_sql(
|
| 325 |
+
"SELECT * FROM thought_units WHERE thought_id = ? AND session_id = ? AND status = 'proposed'",
|
| 326 |
+
[proposal_id, session_id],
|
| 327 |
+
)
|
| 328 |
+
if not proposal:
|
| 329 |
+
raise HTTPException(404, "Proposal not found or not in 'proposed' status")
|
| 330 |
+
|
| 331 |
+
new_status_map = {
|
| 332 |
+
ReviewAction.ACCEPT: "accepted",
|
| 333 |
+
ReviewAction.REJECT: "rejected",
|
| 334 |
+
ReviewAction.SUPERSEDE: "superseded",
|
| 335 |
+
}
|
| 336 |
+
new_status = new_status_map.get(req.action)
|
| 337 |
+
if not new_status:
|
| 338 |
+
raise HTTPException(400, "Invalid review action for this endpoint")
|
| 339 |
+
|
| 340 |
+
# Update thought status
|
| 341 |
+
await d1_client.execute_sql(
|
| 342 |
+
"UPDATE thought_units SET status = ? WHERE thought_id = ?",
|
| 343 |
+
[new_status, proposal_id],
|
| 344 |
+
)
|
| 345 |
+
# Review record
|
| 346 |
+
await d1_client.execute_sql(
|
| 347 |
+
"INSERT INTO thought_reviews (review_id, session_id, thought_id, action, actor, reason, created_at) "
|
| 348 |
+
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
| 349 |
+
[_id("rv"), session_id, proposal_id, req.action.value, req.actor, req.reason, _now()],
|
| 350 |
+
)
|
| 351 |
+
# If accepted, move active pointer
|
| 352 |
+
if req.action == ReviewAction.ACCEPT:
|
| 353 |
+
await d1_client.execute_sql(
|
| 354 |
+
"UPDATE sessions SET active_thought_id = ?, updated_at = ? WHERE session_id = ?",
|
| 355 |
+
[proposal_id, _now(), session_id],
|
| 356 |
+
)
|
| 357 |
+
# Witness
|
| 358 |
+
await d1_client.execute_sql(
|
| 359 |
+
"INSERT INTO witness_events (event_id, session_id, thought_id, event_type, actor, detail, created_at) "
|
| 360 |
+
"VALUES (?, ?, ?, 'reviewed', ?, ?, ?)",
|
| 361 |
+
[_id("ev"), session_id, proposal_id, req.actor,
|
| 362 |
+
f"{req.action.value}: {req.reason or 'No reason given'}", _now()],
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
return {
|
| 366 |
+
"proposal_id": proposal_id,
|
| 367 |
+
"new_status": new_status,
|
| 368 |
+
"action": req.action.value,
|
| 369 |
+
"message": f"{'⚡ Merged' if req.action == ReviewAction.ACCEPT else '❌ Rejected' if req.action == ReviewAction.REJECT else '🔄 Superseded'}: {proposal_id}",
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
# ══════════════════════════════════════════════════════════════
|
| 374 |
+
# TREE VISUALIZATION
|
| 375 |
+
# ══════════════════════════════════════════════════════════════
|
| 376 |
+
|
| 377 |
+
@app.get("/session/{session_id}/tree")
|
| 378 |
+
async def get_tree(session_id: str):
|
| 379 |
+
"""Render the full reasoning tree for a session."""
|
| 380 |
+
ses = await d1_client.execute_sql(
|
| 381 |
+
"SELECT * FROM sessions WHERE session_id = ?", [session_id]
|
| 382 |
+
)
|
| 383 |
+
if not ses:
|
| 384 |
+
raise HTTPException(404, "Session not found")
|
| 385 |
+
|
| 386 |
+
thoughts = await d1_client.execute_sql(
|
| 387 |
+
"SELECT * FROM thought_units WHERE session_id = ? ORDER BY created_at ASC",
|
| 388 |
+
[session_id],
|
| 389 |
+
)
|
| 390 |
+
edges = await d1_client.execute_sql(
|
| 391 |
+
"SELECT * FROM thought_edges WHERE session_id = ?", [session_id]
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
# Build adjacency map (parent -> children)
|
| 395 |
+
children_map: dict[str, list[str]] = {}
|
| 396 |
+
for edge in edges:
|
| 397 |
+
src = edge["source_id"]
|
| 398 |
+
tgt = edge["target_id"]
|
| 399 |
+
children_map.setdefault(src, []).append(tgt)
|
| 400 |
+
|
| 401 |
+
thought_map = {t["thought_id"]: t for t in thoughts}
|
| 402 |
+
|
| 403 |
+
def build_node(tid: str) -> dict:
|
| 404 |
+
t = thought_map.get(tid, {})
|
| 405 |
+
return {
|
| 406 |
+
"thought_id": tid,
|
| 407 |
+
"content": t.get("content", ""),
|
| 408 |
+
"thought_class": t.get("thought_class", ""),
|
| 409 |
+
"status": t.get("status", ""),
|
| 410 |
+
"origin_agent": t.get("origin_agent", ""),
|
| 411 |
+
"children": [build_node(cid) for cid in children_map.get(tid, [])],
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
root_id = ses[0].get("root_thought_id")
|
| 415 |
+
tree = build_node(root_id) if root_id else {}
|
| 416 |
+
|
| 417 |
+
return {
|
| 418 |
+
"session_id": session_id,
|
| 419 |
+
"title": ses[0].get("title"),
|
| 420 |
+
"total_thoughts": len(thoughts),
|
| 421 |
+
"total_edges": len(edges),
|
| 422 |
+
"active_thought_id": ses[0].get("active_thought_id"),
|
| 423 |
+
"tree": tree,
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
# ══════════════════════════════════════════════════════════════
|
| 428 |
+
# SEARCH & EDGES
|
| 429 |
+
# ══════════════════════════════════════════════════════════════
|
| 430 |
+
|
| 431 |
+
@app.post("/session/{session_id}/search")
|
| 432 |
+
async def search_thoughts(session_id: str, req: SearchReq):
|
| 433 |
+
"""Search thoughts in a session by content pattern."""
|
| 434 |
+
rows = await d1_client.execute_sql(
|
| 435 |
+
"SELECT * FROM thought_units WHERE session_id = ? AND content LIKE ? ORDER BY created_at ASC",
|
| 436 |
+
[session_id, f"%{req.pattern}%"],
|
| 437 |
+
)
|
| 438 |
+
return {"session_id": session_id, "pattern": req.pattern, "matches": len(rows), "results": rows}
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
@app.get("/session/{session_id}/edges")
|
| 442 |
+
async def list_edges(session_id: str):
|
| 443 |
+
"""List all edges (relationships) in a session."""
|
| 444 |
+
rows = await d1_client.execute_sql(
|
| 445 |
+
"SELECT * FROM thought_edges WHERE session_id = ? ORDER BY created_at ASC",
|
| 446 |
+
[session_id],
|
| 447 |
+
)
|
| 448 |
+
return {"session_id": session_id, "total": len(rows), "edges": rows}
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
# ══════════════════════════════════════════════════════════════
|
| 452 |
+
# WITNESS / PROVENANCE
|
| 453 |
+
# ══════════════════════════════════════════════════════════════
|
| 454 |
+
|
| 455 |
+
@app.get("/session/{session_id}/witness")
|
| 456 |
+
async def list_witness_events(session_id: str, limit: int = Query(50, ge=1, le=200)):
|
| 457 |
+
"""Get the audit trail for a session."""
|
| 458 |
+
rows = await d1_client.execute_sql(
|
| 459 |
+
"SELECT * FROM witness_events WHERE session_id = ? ORDER BY created_at DESC LIMIT ?",
|
| 460 |
+
[session_id, limit],
|
| 461 |
+
)
|
| 462 |
+
return {"session_id": session_id, "total": len(rows), "events": rows}
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
# ══════════════════════════════════════════════════════════════
|
| 466 |
+
# HEALTH
|
| 467 |
+
# ══════════════════════════════════════════════════════════════
|
| 468 |
+
|
| 469 |
+
@app.get("/health")
|
| 470 |
+
async def health():
|
| 471 |
+
return {"status": "ok", "timestamp": _now()}
|
d1_client.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Thought Engine Node — Cloudflare D1 Client
|
| 3 |
+
===========================================
|
| 4 |
+
Contract: C-THOUGHT-NODE-001 (Phase 1 Sovereign Substrate)
|
| 5 |
+
|
| 6 |
+
Raw HTTPS wrapper for executing SQLite queries against the thought-vault D1.
|
| 7 |
+
Reuses the proven pattern from the Federation Embassy.
|
| 8 |
+
"""
|
| 9 |
+
import os
|
| 10 |
+
import httpx
|
| 11 |
+
from typing import Any, Optional
|
| 12 |
+
|
| 13 |
+
ACCOUNT_ID = os.getenv("CLOUDFLARE_ACCOUNT_ID")
|
| 14 |
+
DATABASE_ID = os.getenv("CLOUDFLARE_D1_DATABASE_ID")
|
| 15 |
+
API_TOKEN = os.getenv("CLOUDFLARE_D1_API_TOKEN")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
async def execute_sql(sql: str, params: Optional[list[Any]] = None) -> list[dict]:
|
| 19 |
+
"""Execute a single SQL query against the thought-vault D1."""
|
| 20 |
+
if not (ACCOUNT_ID and DATABASE_ID and API_TOKEN):
|
| 21 |
+
print("⚠️ D1 credentials missing — SQL execution skipped.")
|
| 22 |
+
return []
|
| 23 |
+
|
| 24 |
+
url = (
|
| 25 |
+
f"https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}"
|
| 26 |
+
f"/d1/database/{DATABASE_ID}/query"
|
| 27 |
+
)
|
| 28 |
+
headers = {
|
| 29 |
+
"Authorization": f"Bearer {API_TOKEN}",
|
| 30 |
+
"Content-Type": "application/json",
|
| 31 |
+
}
|
| 32 |
+
payload: dict[str, Any] = {"sql": sql}
|
| 33 |
+
if params:
|
| 34 |
+
payload["params"] = params
|
| 35 |
+
|
| 36 |
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
| 37 |
+
try:
|
| 38 |
+
r = await client.post(url, headers=headers, json=payload)
|
| 39 |
+
r.raise_for_status()
|
| 40 |
+
data = r.json()
|
| 41 |
+
if not data.get("success"):
|
| 42 |
+
print(f"🔴 D1 Query Error: {data.get('errors')}")
|
| 43 |
+
return []
|
| 44 |
+
return data["result"][0].get("results", [])
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"🔴 D1 Connection Error: {e}")
|
| 47 |
+
return []
|
models.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Thought Engine Node — Pydantic Models
|
| 3 |
+
======================================
|
| 4 |
+
Contract: C-THOUGHT-NODE-001 (Phase 1 Sovereign Substrate)
|
| 5 |
+
|
| 6 |
+
Request/Response shapes for the sovereign REST API.
|
| 7 |
+
"""
|
| 8 |
+
from pydantic import BaseModel, Field
|
| 9 |
+
from typing import Optional, List, Any
|
| 10 |
+
from enum import Enum
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# ── Enums ──────────────────────────────────────────────────────
|
| 14 |
+
class ThoughtClass(str, Enum):
|
| 15 |
+
HYPOTHESIS = "hypothesis"
|
| 16 |
+
OBSERVATION = "observation"
|
| 17 |
+
VALIDATION = "validation"
|
| 18 |
+
COUNTER_ARGUMENT = "counter_argument"
|
| 19 |
+
SYNTHESIS = "synthesis"
|
| 20 |
+
DECISION = "decision"
|
| 21 |
+
QUESTION = "question"
|
| 22 |
+
PROPOSAL = "proposal"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class ThoughtStatus(str, Enum):
|
| 26 |
+
OPEN = "open"
|
| 27 |
+
PROPOSED = "proposed"
|
| 28 |
+
UNDER_REVIEW = "under_review"
|
| 29 |
+
ACCEPTED = "accepted"
|
| 30 |
+
REJECTED = "rejected"
|
| 31 |
+
MERGED = "merged"
|
| 32 |
+
SUPERSEDED = "superseded"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class EdgeRelation(str, Enum):
|
| 36 |
+
DERIVES_FROM = "derives_from"
|
| 37 |
+
SUPPORTS = "supports"
|
| 38 |
+
CHALLENGES = "challenges"
|
| 39 |
+
REFINES = "refines"
|
| 40 |
+
FORKS_FROM = "forks_from"
|
| 41 |
+
MERGES_INTO = "merges_into"
|
| 42 |
+
BUBBLES_FROM = "bubbles_from"
|
| 43 |
+
BUBBLES_INTO = "bubbles_into"
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class ReviewAction(str, Enum):
|
| 47 |
+
PROPOSE = "propose"
|
| 48 |
+
ACCEPT = "accept"
|
| 49 |
+
REJECT = "reject"
|
| 50 |
+
SUPERSEDE = "supersede"
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class SessionStatus(str, Enum):
|
| 54 |
+
ACTIVE = "active"
|
| 55 |
+
ARCHIVED = "archived"
|
| 56 |
+
MERGED = "merged"
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
# ── Request Bodies ─────────────────────────────────────────────
|
| 60 |
+
class CreateSessionReq(BaseModel):
|
| 61 |
+
title: Optional[str] = None
|
| 62 |
+
initial_thought: str
|
| 63 |
+
thought_class: ThoughtClass = ThoughtClass.OBSERVATION
|
| 64 |
+
agent_id: str = "system"
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class AddThoughtReq(BaseModel):
|
| 68 |
+
content: str
|
| 69 |
+
thought_class: ThoughtClass = ThoughtClass.OBSERVATION
|
| 70 |
+
agent_id: str = "system"
|
| 71 |
+
parent_thought_id: Optional[str] = None
|
| 72 |
+
confidence: Optional[float] = None
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class ForkThoughtReq(BaseModel):
|
| 76 |
+
source_thought_id: str
|
| 77 |
+
branch_label: str
|
| 78 |
+
agent_id: str = "system"
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class CreateProposalReq(BaseModel):
|
| 82 |
+
parent_thought_id: str
|
| 83 |
+
content: str
|
| 84 |
+
note: str = ""
|
| 85 |
+
agent_id: str = "system"
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class ReviewProposalReq(BaseModel):
|
| 89 |
+
action: ReviewAction
|
| 90 |
+
actor: str = "system"
|
| 91 |
+
reason: Optional[str] = None
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class SearchReq(BaseModel):
|
| 95 |
+
pattern: str
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
# ── Response Bodies ────────────────────────────────────────────
|
| 99 |
+
class SessionInfo(BaseModel):
|
| 100 |
+
session_id: str
|
| 101 |
+
title: Optional[str]
|
| 102 |
+
created_by: str
|
| 103 |
+
created_at: str
|
| 104 |
+
status: str
|
| 105 |
+
root_thought_id: Optional[str]
|
| 106 |
+
active_thought_id: Optional[str]
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class ThoughtInfo(BaseModel):
|
| 110 |
+
thought_id: str
|
| 111 |
+
session_id: str
|
| 112 |
+
content: str
|
| 113 |
+
thought_class: str
|
| 114 |
+
origin_agent: str
|
| 115 |
+
status: str
|
| 116 |
+
confidence: Optional[float]
|
| 117 |
+
created_at: str
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
class EdgeInfo(BaseModel):
|
| 121 |
+
edge_id: str
|
| 122 |
+
source_id: str
|
| 123 |
+
target_id: str
|
| 124 |
+
relation: str
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class TreeNode(BaseModel):
|
| 128 |
+
thought_id: str
|
| 129 |
+
content: str
|
| 130 |
+
thought_class: str
|
| 131 |
+
status: str
|
| 132 |
+
origin_agent: str
|
| 133 |
+
children: List["TreeNode"] = []
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.115.0
|
| 2 |
+
uvicorn[standard]==0.30.6
|
| 3 |
+
pydantic==2.9.2
|
| 4 |
+
httpx>=0.28.1
|
| 5 |
+
python-dotenv>=1.0.1
|
schema.sql
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- ============================================================
|
| 2 |
+
-- Thought Engine Node — D1 Schema
|
| 3 |
+
-- Contract: C-THOUGHT-NODE-001 (Phase 1 Sovereign Substrate)
|
| 4 |
+
-- Vault: thought-vault (Cloudflare D1)
|
| 5 |
+
-- ============================================================
|
| 6 |
+
|
| 7 |
+
-- Sessions: The top-level reasoning container
|
| 8 |
+
CREATE TABLE IF NOT EXISTS sessions (
|
| 9 |
+
session_id TEXT PRIMARY KEY,
|
| 10 |
+
title TEXT,
|
| 11 |
+
created_by TEXT NOT NULL DEFAULT 'system',
|
| 12 |
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
| 13 |
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
| 14 |
+
status TEXT NOT NULL DEFAULT 'active'
|
| 15 |
+
CHECK (status IN ('active','archived','merged')),
|
| 16 |
+
root_thought_id TEXT,
|
| 17 |
+
active_thought_id TEXT,
|
| 18 |
+
metadata TEXT DEFAULT '{}'
|
| 19 |
+
);
|
| 20 |
+
|
| 21 |
+
-- Thought Units: Individual nodes in the reasoning graph
|
| 22 |
+
CREATE TABLE IF NOT EXISTS thought_units (
|
| 23 |
+
thought_id TEXT PRIMARY KEY,
|
| 24 |
+
session_id TEXT NOT NULL,
|
| 25 |
+
content TEXT NOT NULL,
|
| 26 |
+
thought_class TEXT NOT NULL DEFAULT 'observation'
|
| 27 |
+
CHECK (thought_class IN (
|
| 28 |
+
'hypothesis','observation','validation',
|
| 29 |
+
'counter_argument','synthesis','decision',
|
| 30 |
+
'question','proposal'
|
| 31 |
+
)),
|
| 32 |
+
origin_agent TEXT NOT NULL DEFAULT 'system',
|
| 33 |
+
status TEXT NOT NULL DEFAULT 'open'
|
| 34 |
+
CHECK (status IN (
|
| 35 |
+
'open','proposed','under_review',
|
| 36 |
+
'accepted','rejected','merged','superseded'
|
| 37 |
+
)),
|
| 38 |
+
confidence REAL DEFAULT NULL,
|
| 39 |
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
| 40 |
+
metadata TEXT DEFAULT '{}',
|
| 41 |
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
| 42 |
+
);
|
| 43 |
+
|
| 44 |
+
-- Thought Edges: Relationships between thought units (the graph)
|
| 45 |
+
CREATE TABLE IF NOT EXISTS thought_edges (
|
| 46 |
+
edge_id TEXT PRIMARY KEY,
|
| 47 |
+
session_id TEXT NOT NULL,
|
| 48 |
+
source_id TEXT NOT NULL,
|
| 49 |
+
target_id TEXT NOT NULL,
|
| 50 |
+
relation TEXT NOT NULL DEFAULT 'derives_from'
|
| 51 |
+
CHECK (relation IN (
|
| 52 |
+
'derives_from','supports','challenges',
|
| 53 |
+
'refines','forks_from','merges_into',
|
| 54 |
+
'bubbles_from','bubbles_into'
|
| 55 |
+
)),
|
| 56 |
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
| 57 |
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id),
|
| 58 |
+
FOREIGN KEY (source_id) REFERENCES thought_units(thought_id),
|
| 59 |
+
FOREIGN KEY (target_id) REFERENCES thought_units(thought_id)
|
| 60 |
+
);
|
| 61 |
+
|
| 62 |
+
-- Thought Reviews: Git-for-Thought PR lifecycle
|
| 63 |
+
CREATE TABLE IF NOT EXISTS thought_reviews (
|
| 64 |
+
review_id TEXT PRIMARY KEY,
|
| 65 |
+
session_id TEXT NOT NULL,
|
| 66 |
+
thought_id TEXT NOT NULL,
|
| 67 |
+
action TEXT NOT NULL
|
| 68 |
+
CHECK (action IN (
|
| 69 |
+
'propose','accept','reject','supersede'
|
| 70 |
+
)),
|
| 71 |
+
actor TEXT NOT NULL DEFAULT 'system',
|
| 72 |
+
reason TEXT DEFAULT NULL,
|
| 73 |
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
| 74 |
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id),
|
| 75 |
+
FOREIGN KEY (thought_id) REFERENCES thought_units(thought_id)
|
| 76 |
+
);
|
| 77 |
+
|
| 78 |
+
-- Witness Events: Provenance / audit trail
|
| 79 |
+
CREATE TABLE IF NOT EXISTS witness_events (
|
| 80 |
+
event_id TEXT PRIMARY KEY,
|
| 81 |
+
session_id TEXT NOT NULL,
|
| 82 |
+
thought_id TEXT,
|
| 83 |
+
event_type TEXT NOT NULL
|
| 84 |
+
CHECK (event_type IN (
|
| 85 |
+
'viewed','annotated','reviewed',
|
| 86 |
+
'merged','promoted','forked','created'
|
| 87 |
+
)),
|
| 88 |
+
actor TEXT NOT NULL,
|
| 89 |
+
detail TEXT DEFAULT NULL,
|
| 90 |
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
| 91 |
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
| 92 |
+
);
|
| 93 |
+
|
| 94 |
+
-- Version History: Snapshot archive for thought edits (CAP-I.4)
|
| 95 |
+
CREATE TABLE IF NOT EXISTS version_history (
|
| 96 |
+
version_id TEXT PRIMARY KEY,
|
| 97 |
+
thought_id TEXT NOT NULL,
|
| 98 |
+
version_num INTEGER NOT NULL,
|
| 99 |
+
content TEXT NOT NULL,
|
| 100 |
+
status TEXT NOT NULL,
|
| 101 |
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
| 102 |
+
FOREIGN KEY (thought_id) REFERENCES thought_units(thought_id)
|
| 103 |
+
);
|
| 104 |
+
|
| 105 |
+
-- Pattern Registry: Promoted reusable reasoning structures (Phase 2 prep)
|
| 106 |
+
CREATE TABLE IF NOT EXISTS pattern_registry (
|
| 107 |
+
pattern_id TEXT PRIMARY KEY,
|
| 108 |
+
name TEXT NOT NULL,
|
| 109 |
+
description TEXT,
|
| 110 |
+
source_session TEXT,
|
| 111 |
+
structure TEXT NOT NULL DEFAULT '{}',
|
| 112 |
+
times_reused INTEGER NOT NULL DEFAULT 0,
|
| 113 |
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
| 114 |
+
FOREIGN KEY (source_session) REFERENCES sessions(session_id)
|
| 115 |
+
);
|