Skip to content

Commit ebf629c

Browse files
committed
feat: Implement Airth microservice with health check and mock query handling; add ThoughtMap Phase 2 module with AI expansion functionality
1 parent 703f41f commit ebf629c

File tree

11 files changed

+535
-23
lines changed

11 files changed

+535
-23
lines changed

README_ARCHITECTURE.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# TEC Architecture Blueprint (Operational Extraction)
2+
3+
This document distills the strategic blueprint into actionable implementation layers.
4+
5+
## Stack Selections (Locked-In)
6+
- Core DB: PostgreSQL 15 (Flexible Server) + pgvector extension
7+
- ORM: Prisma (schema-first constitutional data model)
8+
- Vector Retrieval: pgvector (phase 1) – Azure AI Search retained for future hybrid augment
9+
- Agent Framework: LangChain (incremental; mock in Airth for now)
10+
- Microservices: Container Apps per agent (Airth first)
11+
- Realtime: Socket.IO (pending integration after Airth live RAG)
12+
- Frontend: Vite + React + Zustand + Chakra (scaffold next phase)
13+
- Secrets: Key Vault + Managed Identity (migration upcoming)
14+
15+
## Data Model (Initial)
16+
See `prisma/schema.prisma`. Entities: LoreEntry, LoreVersion, Embedding, AgentMemory, Relation, TaskLog.
17+
18+
## Airth Service (Phase 1)
19+
- Location: `services/airth/`
20+
- Endpoints:
21+
- GET `/healthz` – health probe
22+
- POST `/ask` – mock answer until embeddings pipeline connected
23+
- Upcoming: similarity retrieval + answer synthesis.
24+
25+
## pgvector Enablement
26+
Run once against DB:
27+
```
28+
psql "$DATABASE_URL" -f scripts/enable_pgvector.sql
29+
```
30+
31+
## Upcoming Work (Ordered)
32+
1. Embedding pipeline script (generate + store vectors)
33+
2. Replace Airth mock with LangChain RAG chain
34+
3. Frontend scaffold + Ask Airth interface
35+
4. Key Vault secret retrieval refactor
36+
5. ThoughtMap ingestion endpoint -> lore entries + embeddings
37+
6. Observability: correlation IDs, structured logs
38+
39+
## Testing Strategy
40+
- CI: prisma format + migrate diff validation
41+
- Unit: Airth /healthz, expansion stubs
42+
- Integration (later): ephemeral Postgres + vector similarity test
43+
44+
## Principle Mapping
45+
| Principle | Implementation Hook |
46+
|-----------|---------------------|
47+
| Narrative Supremacy | Canonical version pointer in LoreEntry |
48+
| Transparency Mandate | Prisma schema under Git + migrations |
49+
| Generational Responsibility | Append-only LoreVersion + audit |
50+
51+
## Contribution Notes
52+
Schema changes: modify -> `npm run prisma:format` -> `npm run prisma:migrate` -> commit. Avoid manual DB drift.

SESSION_HANDOFF.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ The Guardian’s Burden anchors moral axis; Order of the Fuck-Up Fathers supplie
1515
| Repo Size | ⚠️ ~764MB | History bloat unresolved |
1616
| Sovereign Assets | Partial | Indexed, not fully cross-linked |
1717

18-
## Decision Log (Pending)
19-
1. Nuclear history purge: DEFERRED (needs explicit go/no-go)
20-
2. Phase 2 priority: ThoughtMap AI expansion vs deeper MCP enrichment – NOT CHOSEN
21-
3. Local model (Ollama) vs remote API (Azure OpenAI) – NOT CHOSEN
18+
## Decision Log (Pending / Executed)
19+
1. Nuclear history purge: APPROVED (user confirmed) – NOT YET EXECUTED (awaiting final safety confirmation)
20+
2. Phase 2 priority: Proceed with ThoughtMap AI expansion first, then MCP enrichment
21+
3. Local model vs remote API: DEFER – stub stays deterministic until architecture choice
2222

2323
## Next Task Checklist
24-
- [ ] Decide purge now vs later (record in this file under Decisions Executed)
25-
- [ ] Implement ThoughtMap Phase 2 AI expansion (expand_node, synthesize_branch)
26-
- [ ] Wire Ctrl+Space hotkey to expansion
27-
- [ ] Persist save format version bump (add `schemaVersion: 2`)
28-
- [ ] MCP adapter: export node graph to Memory Core ingestion stub
29-
- [ ] Pre-commit large file guard (>10MB unless LFS tracked)
30-
- [ ] Lightweight Jest smoke test for TS utils + Python self-test
24+
- [x] Decide purge now vs later (recorded APPROVED, execution pending)
25+
- [x] Implement ThoughtMap Phase 2 AI expansion stubs (Python module extraction pending commit)
26+
- [ ] Wire Ctrl+Space hotkey to expansion (pending module extraction)
27+
- [ ] Persist save format version bump (schemaVersion: 2 in new save routine)
28+
- [ ] MCP adapter: connect to real graph instead of dummy
29+
- [x] Pre-commit large file guard (>10MB unless LFS tracked)
30+
- [ ] Lightweight Jest smoke test + Python self-test script
3131

3232
## Interfaces Planned
3333
### Python Expansion Stub
@@ -48,11 +48,11 @@ Returns list of plausible child node titles (mock until model configured).
4848
| Model hallucination in expansion | Keep stub deterministic until validation layer added |
4949

5050
## Suggested Order of Operations (If Continuing Immediately)
51-
1. Commit this handoff file
52-
2. Implement stubs (done partially in repo once committed)
53-
3. Choose purge strategy; if YES, perform after verifying new stubs committed
54-
4. Integrate expansion UI + persistence version bump
55-
5. Add tests + hook install script
51+
1. Extract ThoughtMap notebook code into module + hotkey
52+
2. Execute nuclear purge (after commit) OR postpone
53+
3. Replace MCP adapter dummy with live exporter
54+
4. Add synthesis UI action (branch summary)
55+
5. Decide model integration path (Azure vs local) and implement
5656

5757
## Handoff Prompt (For New Chat Seed)
5858
```

infra/main.bicep

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var tags = {
4141

4242
var resourceNames = {
4343
containerApp: '${applicationName}-app-${resourceToken}'
44+
airthContainerApp: '${applicationName}-airth-${resourceToken}'
4445
containerAppsEnvironment: '${applicationName}-env-${resourceToken}'
4546
containerRegistry: 'tecregistry${resourceToken}'
4647
keyVault: '${applicationName}-kv-${resourceToken}'
@@ -351,6 +352,91 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
351352
}
352353
}
353354

355+
// Airth Agent Container App
356+
resource airthContainerApp 'Microsoft.App/containerApps@2023-05-01' = {
357+
name: resourceNames.airthContainerApp
358+
location: location
359+
tags: tags
360+
identity: {
361+
type: 'UserAssigned'
362+
userAssignedIdentities: {
363+
'${managedIdentity.id}': {}
364+
}
365+
}
366+
properties: {
367+
managedEnvironmentId: containerAppsEnvironment.id
368+
configuration: {
369+
ingress: {
370+
external: true
371+
targetPort: 8000
372+
allowInsecure: false
373+
traffic: [
374+
{
375+
weight: 100
376+
latestRevision: true
377+
}
378+
]
379+
}
380+
registries: [
381+
{
382+
server: containerRegistry.properties.loginServer
383+
identity: managedIdentity.id
384+
}
385+
]
386+
secrets: [
387+
{
388+
name: 'openai-api-key'
389+
value: openAiApiKey
390+
}
391+
{
392+
name: 'db-connection-string'
393+
value: 'postgresql://${dbAdminUsername}:${dbAdminPassword}@${postgreSQL.properties.fullyQualifiedDomainName}:5432/tecdb'
394+
}
395+
]
396+
}
397+
template: {
398+
containers: [
399+
{
400+
image: '${containerRegistry.properties.loginServer}/elidoras-airth:${containerImageTag}'
401+
name: 'airth'
402+
resources: {
403+
cpu: json(environmentName == 'prod' ? '0.75' : '0.5')
404+
memory: environmentName == 'prod' ? '1.5Gi' : '1Gi'
405+
}
406+
env: [
407+
{
408+
name: 'PORT'
409+
value: '8000'
410+
}
411+
{
412+
name: 'OPENAI_API_KEY'
413+
secretRef: 'openai-api-key'
414+
}
415+
{
416+
name: 'DATABASE_URL'
417+
secretRef: 'db-connection-string'
418+
}
419+
]
420+
}
421+
]
422+
scale: {
423+
minReplicas: environmentName == 'prod' ? 2 : 1
424+
maxReplicas: environmentName == 'prod' ? 5 : 2
425+
rules: [
426+
{
427+
name: 'http-scaler'
428+
http: {
429+
metadata: {
430+
concurrentRequests: '80'
431+
}
432+
}
433+
}
434+
]
435+
}
436+
}
437+
}
438+
}
439+
354440
// Role Assignments
355441
resource acrPullRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
356442
scope: containerRegistry
@@ -385,6 +471,7 @@ resource keyVaultSecretsRole 'Microsoft.Authorization/roleAssignments@2022-04-01
385471
// Outputs
386472
output containerAppFQDN string = containerApp.properties.configuration.ingress.fqdn
387473
output containerAppUrl string = 'https://${containerApp.properties.configuration.ingress.fqdn}'
474+
output airthAppUrl string = 'https://${airthContainerApp.properties.configuration.ingress.fqdn}'
388475
output keyVaultName string = keyVault.name
389476
output storageAccountName string = storageAccount.name
390477
output postgreSQLHostname string = postgreSQL.properties.fullyQualifiedDomainName

package.json

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@
99
"build": "tsc",
1010
"start": "node dist/server.js",
1111
"start:prod": "npm run build && npm start",
12-
"test": "jest",
13-
"test:smoke": "echo 'placeholder smoke test'",
12+
"test": "jest",
13+
"test:smoke": "echo 'placeholder smoke test'",
14+
"thoughtmap:run": "python tec_mcp_server/thoughtmap.py",
15+
"py:selftest": "python tec_mcp_server/self_test.py",
1416
"lint": "eslint src/**/*.ts",
15-
"format": "prettier --write src/**/*.{ts,tsx}",
16-
"hooks:refresh": "chmod +x .git/hooks/pre-commit tools/precommit/large-file-guard.sh",
17+
"format": "prettier --write src/**/*.{ts,tsx}",
18+
"hooks:refresh": "chmod +x .git/hooks/pre-commit tools/precommit/large-file-guard.sh",
1719
"axiom:validate": "ts-node src/scripts/validateAxioms.ts",
1820
"db:migrate": "ts-node src/server/database/migrate.ts",
19-
"db:seed": "ts-node src/server/database/seed.ts"
21+
"db:seed": "ts-node src/server/database/seed.ts",
22+
"prisma:generate": "prisma generate",
23+
"prisma:migrate": "prisma migrate dev --name init",
24+
"prisma:format": "prisma format"
2025
},
2126
"keywords": [
2227
"ai",
@@ -30,8 +35,7 @@
3035
"license": "GPL-3.0",
3136
"dependencies": {
3237
"@azure/openai": "^1.0.0-beta.12",
33-
"@types/bcrypt": "^5.0.2",
34-
"@types/jsonwebtoken": "^9.0.6",
38+
"@prisma/client": "^5.16.1",
3539
"bcrypt": "^5.1.1",
3640
"cors": "^2.8.5",
3741
"dotenv": "^16.4.5",
@@ -60,6 +64,7 @@
6064
"jest": "^29.7.0",
6165
"nodemon": "^3.1.0",
6266
"prettier": "^3.2.5",
67+
"prisma": "^5.16.1",
6368
"ts-jest": "^29.1.2",
6469
"ts-node": "^10.9.2",
6570
"typescript": "^5.4.5"

prisma/schema.prisma

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Prisma schema – Master Lore Database (Phase 1)
2+
// Acts as constitutional data layer. pgvector extension required.
3+
4+
generator client {
5+
provider = "prisma-client-js"
6+
}
7+
8+
datasource db {
9+
provider = "postgresql"
10+
url = env("DATABASE_URL")
11+
}
12+
13+
model LoreEntry {
14+
id String @id @default(cuid())
15+
slug String @unique
16+
title String
17+
type String
18+
canonicalVersionId String? @map("canonical_version_id")
19+
createdAt DateTime @default(now()) @map("created_at")
20+
versions LoreVersion[]
21+
embeddings Embedding[]
22+
memories AgentMemory[] @relation("LoreMemory")
23+
relationsFrom Relation[] @relation("FromRelations")
24+
relationsTo Relation[] @relation("ToRelations")
25+
}
26+
27+
model LoreVersion {
28+
id String @id @default(cuid())
29+
loreEntry LoreEntry @relation(fields: [loreEntryId], references: [id])
30+
loreEntryId String @map("lore_entry_id")
31+
content String
32+
author String?
33+
hash String @unique
34+
createdAt DateTime @default(now()) @map("created_at")
35+
}
36+
37+
model Embedding {
38+
id String @id @default(cuid())
39+
loreEntry LoreEntry @relation(fields: [loreEntryId], references: [id])
40+
loreEntryId String @map("lore_entry_id")
41+
vector Unsupported("vector")
42+
model String
43+
dim Int
44+
createdAt DateTime @default(now()) @map("created_at")
45+
}
46+
47+
model AgentMemory {
48+
id String @id @default(cuid())
49+
agent String
50+
content String
51+
importance Float @default(0)
52+
loreEntry LoreEntry? @relation("LoreMemory", fields: [loreEntryId], references: [id])
53+
loreEntryId String? @map("lore_entry_id")
54+
createdAt DateTime @default(now()) @map("created_at")
55+
}
56+
57+
model Relation {
58+
id String @id @default(cuid())
59+
from LoreEntry @relation("FromRelations", fields: [fromId], references: [id])
60+
fromId String @map("from_id")
61+
to LoreEntry @relation("ToRelations", fields: [toId], references: [id])
62+
toId String @map("to_id")
63+
kind String
64+
createdAt DateTime @default(now()) @map("created_at")
65+
}
66+
67+
model TaskLog {
68+
id String @id @default(cuid())
69+
agent String
70+
input String
71+
outputRef String? @map("output_ref")
72+
status String @default("pending")
73+
createdAt DateTime @default(now()) @map("created_at")
74+
}

scripts/enable_pgvector.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Enable pgvector extension (idempotent)
2+
CREATE EXTENSION IF NOT EXISTS vector;

services/airth/airth_service.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Airth Microservice (Phase 1 Skeleton)
2+
3+
FastAPI service providing /healthz and /ask endpoints.
4+
RAG pipeline placeholder – returns deterministic mock until embeddings pipeline active.
5+
"""
6+
from __future__ import annotations
7+
from fastapi import FastAPI
8+
from pydantic import BaseModel
9+
import hashlib
10+
11+
app = FastAPI(title="Airth Service", version="0.1.0")
12+
13+
class AskRequest(BaseModel):
14+
query: str
15+
max_context: int | None = 5
16+
17+
class AskResponse(BaseModel):
18+
answer: str
19+
sources: list[str]
20+
strategy: str = "mock"
21+
22+
@app.get("/healthz")
23+
def health(): # liveness/readiness simple variant
24+
return {"status": "ok"}
25+
26+
@app.post("/ask", response_model=AskResponse)
27+
def ask(req: AskRequest):
28+
h = hashlib.sha256(req.query.encode()).hexdigest()[:8]
29+
answer = f"[Airth mock] Hash:{h} – query acknowledged. RAG pipeline pending."
30+
return AskResponse(answer=answer, sources=["lore:placeholder"], strategy="mock")
31+
32+
if __name__ == "__main__": # pragma: no cover
33+
import uvicorn
34+
uvicorn.run(app, host="0.0.0.0", port=8000)

services/airth/requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fastapi==0.111.0
2+
uvicorn==0.30.1
3+
langchain==0.2.7
4+
psycopg[binary]==3.1.19
5+
openai==1.35.0
6+
python-dotenv==1.0.1

tec_mcp_server/self_test.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Quick self-test to ensure critical modules import."""
2+
from importlib import import_module
3+
4+
MODULES = [
5+
'tec_mcp_server.ai_expander',
6+
'tec_mcp_server.thoughtmap'
7+
]
8+
9+
def main():
10+
failed = []
11+
for m in MODULES:
12+
try:
13+
import_module(m)
14+
except Exception as e: # pragma: no cover
15+
failed.append((m, repr(e)))
16+
if failed:
17+
print("FAIL", failed)
18+
raise SystemExit(1)
19+
print("OK modules loaded")
20+
21+
if __name__ == '__main__':
22+
main()

0 commit comments

Comments
 (0)