diff --git a/OfficeAI-500-Sovereign-Mesh/README.md b/OfficeAI-500-Sovereign-Mesh/README.md new file mode 100644 index 0000000..6f0549a --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/README.md @@ -0,0 +1,48 @@ +# OfficeAI-500-Sovereign-Mesh + +OfficeAI-500 Sovereign Mesh is an enterprise task-orchestration MVP that simulates a 500-agent virtual office for operations and finops workflows. It is a governed execution kernel with durable storage, deterministic memory reuse, review gates, tamper checks, and auditable traces. + +## Not a chatbot / not plain RAG +This system routes structured tasks through governance, memory integrity checks, and review queues. It processes operational work units, not free-form assistant dialog. + +## Run backend +```bash +cd backend +pip install -r requirements.txt +python run.py +``` + +## Run frontend +```bash +cd frontend +npm install +npm run dev +``` + +## Run tests +```bash +cd backend +pytest +``` + +## Run 500 demo +```bash +curl -X POST http://localhost:8000/demo/load +curl -X POST http://localhost:8000/demo/run_500 +curl -X POST http://localhost:8000/demo/run_500 +``` +Second run should show increased `memory_hits` and reduced `memory_misses`. + +## Tamper detection +1. Find a capsule via `/memory/search?q=`. +2. Tamper with `POST /memory/{capsule_id}/tamper`. +3. Verify with `GET /memory/{capsule_id}/verify`. +4. Failed verification marks capsule `verified=false` and prevents reuse. + +## Enterprise office / finops mapping +- **Agents** = virtual back-office workers. +- **Tasks** = invoices, support tickets, reports, exceptions. +- **Governance** = policy and risk controls. +- **Review queue** = human checkpoint on risky/regulated work. +- **Memory capsules** = deterministic reuse for repeat workflows and spend reduction. +- **Audit traces** = compliance and incident visibility. diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/__init__.py b/OfficeAI-500-Sovereign-Mesh/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/audit.py b/OfficeAI-500-Sovereign-Mesh/backend/app/audit.py new file mode 100644 index 0000000..9445bea --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/audit.py @@ -0,0 +1,25 @@ +from datetime import datetime +from hashlib import sha384 +import uuid +from .models import AuditTrace + +def write_audit(db, task, memory_hit, governance_allowed, governance_status, risk_level, reason, output_text, latency_ms): + trace_id = str(uuid.uuid4()) + tr = AuditTrace( + trace_id=trace_id, + task_id=task.id, + agent_id=task.agent_id, + task_type=task.type, + memory_hit=memory_hit, + governance_allowed=governance_allowed, + governance_status=governance_status, + risk_level=risk_level, + reason=reason, + input_hash=sha384(str(task.payload).encode()).hexdigest(), + output_hash=sha384(str(output_text).encode()).hexdigest(), + latency_ms=latency_ms, + timestamp=datetime.utcnow(), + ) + db.add(tr) + db.commit() + return trace_id diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/config.py b/OfficeAI-500-Sovereign-Mesh/backend/app/config.py new file mode 100644 index 0000000..566a3b2 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/config.py @@ -0,0 +1,9 @@ +import os + +class Settings: + DATABASE_URL = os.getenv("OFFICEAI_DB_URL", "sqlite:///./officeai.db") + SECRET_SALT = os.getenv("OFFICEAI_SECRET_SALT", "officeai-local-safe-salt") + BUDGET_LIMIT = 50000.0 + COST_PER_MISS = 0.12 + +settings = Settings() diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/database.py b/OfficeAI-500-Sovereign-Mesh/backend/app/database.py new file mode 100644 index 0000000..77e3fbd --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/database.py @@ -0,0 +1,14 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import declarative_base, sessionmaker +from .config import settings + +engine = create_engine(settings.DATABASE_URL, connect_args={"check_same_thread": False}) +SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False) +Base = declarative_base() + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/demo.py b/OfficeAI-500-Sovereign-Mesh/backend/app/demo.py new file mode 100644 index 0000000..f7bd32d --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/demo.py @@ -0,0 +1,20 @@ +from .models import Agent +from .schemas import TaskInput + +def ensure_agents(db, total=500): + count = db.query(Agent).count() + for i in range(count + 1, total + 1): + db.add(Agent(id=i, role=f"office_agent_{i}")) + db.commit() + +def stable_tasks(): + types = ["GENERAL","EMAIL","SUPPORT","REPORT","RESEARCH","COMPLIANCE","FINANCIAL","INVOICE","PAYMENT_EXCEPTION","GENERAL"] + tasks = [] + for i in range(500): + t = types[i % len(types)] + payload = {"subject": f"item-{i%50}", "recipient": f"u{i}@ex.com", "vendor": f"v{i%25}"} + amount = 100.0 + (i % 30) * 250 + if t == "INVOICE" and i % 33 == 0: + payload.pop("vendor", None) + tasks.append(TaskInput(type=t, payload=payload, amount=amount, priority=(i % 5) + 1)) + return tasks diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/governance.py b/OfficeAI-500-Sovereign-Mesh/backend/app/governance.py new file mode 100644 index 0000000..a62ab17 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/governance.py @@ -0,0 +1,26 @@ +from hashlib import sha256 + +VALID_TYPES = {"GENERAL","EMAIL","SUPPORT","REPORT","RESEARCH","COMPLIANCE","FINANCIAL","INVOICE","PAYMENT_EXCEPTION"} + +def deterministic_conviction(task_type: str, payload: str) -> float: + h = sha256(f"{task_type}|{payload}".encode()).hexdigest() + return int(h[:8], 16) / 0xFFFFFFFF + +def apply_governance(task_type: str, payload: dict, amount: float): + if task_type not in VALID_TYPES: + return False, "failed", "high", "unknown task type" + if not payload: + return False, "failed", "high", "empty payload" + if task_type == "INVOICE" and not payload.get("vendor"): + return False, "failed", "high", "missing vendor" + if task_type == "EMAIL" and not payload.get("recipient"): + return False, "failed", "high", "missing recipient" + if task_type == "COMPLIANCE": + return False, "needs_review", "medium", "compliance review required" + if task_type == "PAYMENT_EXCEPTION" and amount > 5000: + return False, "needs_review", "high", "high amount payment exception" + if task_type == "FINANCIAL": + c = deterministic_conviction(task_type, str(payload)) + if c < 0.95: + return False, "needs_review", "medium", f"low conviction {c:.4f}" + return True, "completed", "low", "policy pass" diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/main.py b/OfficeAI-500-Sovereign-Mesh/backend/app/main.py new file mode 100644 index 0000000..14a538c --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/main.py @@ -0,0 +1,123 @@ +from fastapi import FastAPI, Depends, HTTPException +from sqlalchemy.orm import Session +from .database import Base, engine, get_db +from .models import Agent, Task, MemoryCapsule, AuditTrace +from .schemas import TaskInput, BatchInput +from .mesh import process_task +from .demo import ensure_agents, stable_tasks +from .memory import write_memory, lookup_memory, capsule_id_for, integrity_for +from .governance import deterministic_conviction +import uuid + +Base.metadata.create_all(bind=engine) +app = FastAPI(title="OfficeAI-500 Sovereign Mesh") + +@app.get('/health') +def health(): return {"status": "ok"} + +@app.get('/agents') +def agents(db: Session = Depends(get_db)): return [a.__dict__ for a in db.query(Agent).all()] + +@app.get('/tasks') +def tasks(limit: int = 50, db: Session = Depends(get_db)): return [t.__dict__ for t in db.query(Task).order_by(Task.id.desc()).limit(limit).all()] + +@app.get('/tasks/{task_id}') +def task(task_id: int, db: Session = Depends(get_db)): + t = db.query(Task).filter(Task.id == task_id).first() + if not t: raise HTTPException(404, "not found") + return t.__dict__ + +@app.post('/mesh/dispatch') +def dispatch(task_in: TaskInput, db: Session = Depends(get_db)): + task = Task(type=task_in.type, payload=str(task_in.payload), amount=task_in.amount, priority=task_in.priority) + db.add(task); db.commit(); db.refresh(task) + return process_task(db, task) + +@app.post('/mesh/batch') +def batch(body: BatchInput, db: Session = Depends(get_db)): + out=[] + for ti in body.tasks: + task = Task(type=ti.type, payload=str(ti.payload), amount=ti.amount, priority=ti.priority) + db.add(task); db.commit(); db.refresh(task) + out.append(process_task(db, task)) + return {"results": out} + +@app.post('/demo/load') +def load_demo(db: Session = Depends(get_db)): + ensure_agents(db, 500) + return {"agents_loaded": db.query(Agent).count()} + +@app.post('/demo/run_500') +def run_500(db: Session = Depends(get_db)): + ensure_agents(db, 500) + tasks = stable_tasks(); results=[] + for ti in tasks: + task = Task(type=ti.type, payload=str(ti.payload), amount=ti.amount, priority=ti.priority) + db.add(task); db.commit(); db.refresh(task) + results.append(process_task(db, task)) + completed = sum(1 for r in results if r['status']=='completed') + needs_review = sum(1 for r in results if r['status']=='needs_review') + failed = sum(1 for r in results if r['status']=='failed') + hits = sum(1 for r in results if r['memory_hit']) + misses = len(results)-hits + total_latency = sum(r['latency_ms'] for r in results) + savings = hits * 0.12 + return {"batch_id": str(uuid.uuid4()), "submitted": len(results), "completed": completed, "needs_review": needs_review, "failed": failed, "memory_hits": hits, "memory_misses": misses, "total_latency_ms": total_latency, "average_latency_ms": total_latency/len(results), "estimated_api_savings": round(savings,2)} + +@app.get('/audit/{task_id}') +def audit(task_id:int, db: Session=Depends(get_db)): + traces = db.query(AuditTrace).filter(AuditTrace.task_id==task_id).all() + return [t.__dict__ for t in traces] + +@app.get('/memory/search') +def mem_search(q:str="", db: Session=Depends(get_db)): + rows = db.query(MemoryCapsule).filter(MemoryCapsule.task_type.contains(q) | MemoryCapsule.result.contains(q)).limit(100).all() + return [r.__dict__ for r in rows] + +@app.post('/memory/{capsule_id}/tamper') +def tamper(capsule_id:str, db:Session=Depends(get_db)): + c = db.query(MemoryCapsule).filter(MemoryCapsule.capsule_id==capsule_id).first() + if not c: raise HTTPException(404, "not found") + c.result = c.result + "::tampered" + db.commit() + return {"tampered": True, "capsule_id": capsule_id} + +@app.get('/memory/{capsule_id}/verify') +def verify(capsule_id:str, db:Session=Depends(get_db)): + c = db.query(MemoryCapsule).filter(MemoryCapsule.capsule_id==capsule_id).first() + if not c: raise HTTPException(404, "not found") + candidate = eval(db.query(Task).filter(Task.type==c.task_type).first().payload) + ok = integrity_for(c.task_type, candidate, c.result) == c.integrity_hash + if not ok: + c.verified = False + db.commit() + return {"capsule_id": capsule_id, "verified": ok} + +@app.post('/review/{task_id}/approve') +def approve(task_id:int, db: Session=Depends(get_db)): + t = db.query(Task).filter(Task.id==task_id).first() + if not t: raise HTTPException(404, "not found") + if t.status != "needs_review": raise HTTPException(400, "task not in review") + payload = eval(t.payload) + t.status = "completed" + t.result = t.result or f"approved:{t.type}" + write_memory(db, t.type, payload, t.result, deterministic_conviction(t.type, str(payload))) + db.commit() + return {"task_id": task_id, "status": t.status} + +@app.post('/review/{task_id}/reject') +def reject(task_id:int, db: Session=Depends(get_db)): + t = db.query(Task).filter(Task.id==task_id).first() + if not t: raise HTTPException(404, "not found") + if t.status != "needs_review": raise HTTPException(400, "task not in review") + t.status = "rejected"; db.commit(); return {"task_id": task_id, "status": t.status} + +@app.get('/metrics') +def metrics(db: Session=Depends(get_db)): + tasks = db.query(Task).all() + agents = db.query(Agent).all() + hits = db.query(AuditTrace).filter(AuditTrace.memory_hit==True).count() + misses = db.query(AuditTrace).filter(AuditTrace.memory_hit==False).count() + current_spend = misses * 0.12 + counts = {s: sum(1 for t in tasks if t.status==s) for s in ["queued","in_progress","completed","needs_review","failed","rejected"]} + return {"agents_total": len(agents), "agents_idle": sum(1 for a in agents if a.status=="idle"), "agents_busy": sum(1 for a in agents if a.status=="busy"), "tasks_total": len(tasks), **counts, "memory_capsules": db.query(MemoryCapsule).count(), "memory_hits": hits, "memory_misses": misses, "estimated_api_savings": round(hits*0.12,2), "budget_limit": 50000.0, "current_spend": round(current_spend,2)} diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/memory.py b/OfficeAI-500-Sovereign-Mesh/backend/app/memory.py new file mode 100644 index 0000000..3ea5a06 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/memory.py @@ -0,0 +1,52 @@ +import json +from hashlib import sha384 +from datetime import datetime +from .config import settings +from .models import MemoryCapsule + +def normalize_payload(payload): + return json.dumps(payload, sort_keys=True, separators=(",",":")) + +def capsule_id_for(task_type, payload): + normalized = normalize_payload(payload) + return sha384(f"{task_type}|{normalized}".encode()).hexdigest()[:32] + +def integrity_for(task_type, payload, result): + normalized = normalize_payload(payload) + return sha384(f"{settings.SECRET_SALT}|{task_type}|{normalized}|{result}".encode()).hexdigest() + +def lookup_memory(db, task_type, payload): + cid = capsule_id_for(task_type, payload) + capsule = db.query(MemoryCapsule).filter(MemoryCapsule.capsule_id == cid).first() + if not capsule or not capsule.verified: + return None, cid + expected = integrity_for(task_type, payload, capsule.result) + if expected != capsule.integrity_hash: + capsule.verified = False + db.commit() + return None, cid + capsule.use_count += 1 + capsule.last_used_at = datetime.utcnow() + db.commit() + db.refresh(capsule) + return capsule, cid + +def write_memory(db, task_type, payload, result, confidence): + normalized = normalize_payload(payload) + cid = capsule_id_for(task_type, payload) + existing = db.query(MemoryCapsule).filter(MemoryCapsule.capsule_id == cid).first() + if existing and existing.verified: + return existing + capsule = MemoryCapsule( + capsule_id=cid, + task_type=task_type, + input_hash=sha384(f"{task_type}|{normalized}".encode()).hexdigest(), + payload_hash=sha384(normalized.encode()).hexdigest(), + result=result, + confidence=confidence, + integrity_hash=integrity_for(task_type, payload, result), + verified=True, + ) + db.merge(capsule) + db.commit() + return capsule diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/mesh.py b/OfficeAI-500-Sovereign-Mesh/backend/app/mesh.py new file mode 100644 index 0000000..4ddbfc5 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/mesh.py @@ -0,0 +1,71 @@ +from datetime import datetime +import time +from .models import Agent, Task +from .memory import lookup_memory, write_memory +from .governance import apply_governance, deterministic_conviction +from .audit import write_audit + + +def assign_agent(db): + agent = db.query(Agent).filter(Agent.status == "idle").first() + if not agent: + return None + agent.status = "busy" + agent.last_seen = datetime.utcnow() + db.commit() + db.refresh(agent) + return agent + +def release_agent(db, agent, success): + agent.status = "idle" + agent.current_task_id = None + if success: + agent.completed_count += 1 + else: + agent.failed_count += 1 + agent.last_seen = datetime.utcnow() + db.commit() + +def process_task(db, task): + t0 = time.time() + agent = assign_agent(db) + if not agent: + task.status = "failed" + task.error = "no idle agents" + db.commit() + return {"task_id": task.id, "status": task.status, "memory_hit": False, "latency_ms": 0} + task.agent_id = agent.id + task.status = "in_progress" + agent.current_task_id = task.id + task.updated_at = datetime.utcnow() + db.commit() + + payload = eval(task.payload) if isinstance(task.payload, str) else task.payload + capsule, _ = lookup_memory(db, task.type, payload) + memory_hit = capsule is not None + + if memory_hit: + task.result = capsule.result + task.status = "completed" + task.error = None + allowed, gov_status, risk, reason = True, "completed", "low", "memory reuse" + else: + allowed, gov_status, risk, reason = apply_governance(task.type, payload, task.amount) + if gov_status == "failed": + task.status = "failed" + task.error = reason + task.result = None + elif gov_status == "needs_review": + task.status = "needs_review" + task.result = f"pending_review:{task.type}" + else: + task.status = "completed" + task.result = f"processed:{task.type}:{payload}" + write_memory(db, task.type, payload, task.result, deterministic_conviction(task.type, str(payload))) + task.updated_at = datetime.utcnow() + + latency = int((time.time() - t0) * 1000) + task.trace_id = write_audit(db, task, memory_hit, allowed, gov_status, risk, reason, task.result or task.error, latency) + db.commit() + release_agent(db, agent, task.status == "completed") + return {"task_id": task.id, "status": task.status, "memory_hit": memory_hit, "latency_ms": latency} diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/models.py b/OfficeAI-500-Sovereign-Mesh/backend/app/models.py new file mode 100644 index 0000000..b451679 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/models.py @@ -0,0 +1,60 @@ +from datetime import datetime +from sqlalchemy import Column, Integer, String, Float, Text, DateTime, Boolean, ForeignKey +from .database import Base + +class Agent(Base): + __tablename__ = "agents" + id = Column(Integer, primary_key=True) + role = Column(String, default="virtual_worker") + status = Column(String, default="idle") + current_task_id = Column(Integer, nullable=True) + completed_count = Column(Integer, default=0) + failed_count = Column(Integer, default=0) + last_seen = Column(DateTime, default=datetime.utcnow) + +class Task(Base): + __tablename__ = "tasks" + id = Column(Integer, primary_key=True) + agent_id = Column(Integer, ForeignKey("agents.id"), nullable=True) + type = Column(String) + payload = Column(Text) + amount = Column(Float, default=0.0) + priority = Column(Integer, default=1) + status = Column(String, default="queued") + attempts = Column(Integer, default=0) + max_attempts = Column(Integer, default=3) + result = Column(Text, nullable=True) + error = Column(Text, nullable=True) + trace_id = Column(String, nullable=True) + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow) + +class MemoryCapsule(Base): + __tablename__ = "memory_capsules" + capsule_id = Column(String, primary_key=True) + task_type = Column(String) + input_hash = Column(String) + payload_hash = Column(String) + result = Column(Text) + confidence = Column(Float) + integrity_hash = Column(String) + verified = Column(Boolean, default=True) + created_at = Column(DateTime, default=datetime.utcnow) + last_used_at = Column(DateTime, default=datetime.utcnow) + use_count = Column(Integer, default=0) + +class AuditTrace(Base): + __tablename__ = "audit_traces" + trace_id = Column(String, primary_key=True) + task_id = Column(Integer) + agent_id = Column(Integer, nullable=True) + task_type = Column(String) + memory_hit = Column(Boolean, default=False) + governance_allowed = Column(Boolean, default=False) + governance_status = Column(String) + risk_level = Column(String) + reason = Column(Text) + input_hash = Column(String) + output_hash = Column(String) + latency_ms = Column(Integer) + timestamp = Column(DateTime, default=datetime.utcnow) diff --git a/OfficeAI-500-Sovereign-Mesh/backend/app/schemas.py b/OfficeAI-500-Sovereign-Mesh/backend/app/schemas.py new file mode 100644 index 0000000..ecb4fb6 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/app/schemas.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel +from typing import Any + +class TaskInput(BaseModel): + type: str + payload: Any + amount: float = 0.0 + priority: int = 1 + +class BatchInput(BaseModel): + tasks: list[TaskInput] diff --git a/OfficeAI-500-Sovereign-Mesh/backend/requirements.txt b/OfficeAI-500-Sovereign-Mesh/backend/requirements.txt new file mode 100644 index 0000000..5c3d4cd --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/requirements.txt @@ -0,0 +1,6 @@ +fastapi==0.111.0 +uvicorn==0.30.1 +sqlalchemy==2.0.31 +pydantic==2.8.2 +pytest==8.2.2 +httpx==0.27.0 diff --git a/OfficeAI-500-Sovereign-Mesh/backend/run.py b/OfficeAI-500-Sovereign-Mesh/backend/run.py new file mode 100644 index 0000000..c421644 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/run.py @@ -0,0 +1,5 @@ +from app.main import app +import uvicorn + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/OfficeAI-500-Sovereign-Mesh/backend/tests/test_officeai.py b/OfficeAI-500-Sovereign-Mesh/backend/tests/test_officeai.py new file mode 100644 index 0000000..4658715 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/backend/tests/test_officeai.py @@ -0,0 +1,55 @@ +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + +def test_health_works(): + assert client.get('/health').status_code == 200 + +def test_dispatch_works(): + client.post('/demo/load') + r = client.post('/mesh/dispatch', json={"type":"GENERAL","payload":{"subject":"x"},"amount":10,"priority":1}) + assert r.status_code == 200 + assert r.json()["status"] in ["completed","needs_review","failed"] + +def test_bad_invoice_fails(): + client.post('/demo/load') + r = client.post('/mesh/dispatch', json={"type":"INVOICE","payload":{"subject":"x"}}) + assert r.json()["status"] == "failed" + +def test_email_missing_recipient_fails(): + client.post('/demo/load') + r = client.post('/mesh/dispatch', json={"type":"EMAIL","payload":{"subject":"x"}}) + assert r.json()["status"] == "failed" + +def test_financial_low_confidence_needs_review(): + client.post('/demo/load') + r = client.post('/mesh/dispatch', json={"type":"FINANCIAL","payload":{"subject":"low"}}) + assert r.json()["status"] == "needs_review" + +def test_compliance_needs_review(): + client.post('/demo/load') + r = client.post('/mesh/dispatch', json={"type":"COMPLIANCE","payload":{"subject":"x"}}) + assert r.json()["status"] == "needs_review" + +def test_demo_500_and_second_run_hits(): + client.post('/demo/load') + first = client.post('/demo/run_500').json() + second = client.post('/demo/run_500').json() + assert first["submitted"] == 500 + assert second["memory_hits"] > first["memory_hits"] + +def test_tamper_verification_fails(): + client.post('/demo/load') + client.post('/mesh/dispatch', json={"type":"GENERAL","payload":{"subject":"m"}}) + cap = client.get('/memory/search?q=GENERAL').json()[0]["capsule_id"] + client.post(f'/memory/{cap}/tamper') + verify = client.get(f'/memory/{cap}/verify').json() + assert verify["verified"] is False + +def test_tasks_limit_and_metrics(): + r = client.get('/tasks?limit=50') + assert r.status_code == 200 + m = client.get('/metrics') + assert m.status_code == 200 + assert 'agents_total' in m.json() diff --git a/OfficeAI-500-Sovereign-Mesh/frontend/index.html b/OfficeAI-500-Sovereign-Mesh/frontend/index.html new file mode 100644 index 0000000..aa3e423 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/frontend/index.html @@ -0,0 +1 @@ +
diff --git a/OfficeAI-500-Sovereign-Mesh/frontend/package.json b/OfficeAI-500-Sovereign-Mesh/frontend/package.json new file mode 100644 index 0000000..3024830 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/frontend/package.json @@ -0,0 +1,9 @@ +{ + "name": "office-ai-500-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": {"dev": "vite", "build": "vite build", "preview": "vite preview"}, + "dependencies": {"react": "^18.2.0", "react-dom": "^18.2.0", "lucide-react": "^0.300.0", "clsx": "^2.1.0", "tailwind-merge": "^2.2.0"}, + "devDependencies": {"@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.16", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", "vite": "^5.0.10"} +} diff --git a/OfficeAI-500-Sovereign-Mesh/frontend/postcss.config.js b/OfficeAI-500-Sovereign-Mesh/frontend/postcss.config.js new file mode 100644 index 0000000..99417eb --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/frontend/postcss.config.js @@ -0,0 +1 @@ +export default { plugins: { tailwindcss: {}, autoprefixer: {} } }; diff --git a/OfficeAI-500-Sovereign-Mesh/frontend/src/App.jsx b/OfficeAI-500-Sovereign-Mesh/frontend/src/App.jsx new file mode 100644 index 0000000..7a15a67 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/frontend/src/App.jsx @@ -0,0 +1,23 @@ +import React,{useEffect,useState} from 'react'; +import {getHealth,getMetrics,getAgents,getTasks,dispatchTask,run500Demo,searchMemory,approveTask,rejectTask,tamperMemory,verifyMemory,loadDemo} from './api'; + +export default function App(){ + const [view,setView]=useState('Dashboard'); const [metrics,setMetrics]=useState({}); const [agents,setAgents]=useState([]); const [tasks,setTasks]=useState([]); const [memory,setMemory]=useState([]); const [offline,setOffline]=useState(false); const [err,setErr]=useState(''); + const refresh=async()=>{try{await getHealth(); setOffline(false); setErr(''); setMetrics(await getMetrics()); setAgents(await getAgents()); setTasks(await getTasks(50)); setMemory(await searchMemory(''));}catch(e){setOffline(true); setErr(String(e)); setAgents([{id:1,role:'fallback',status:'idle'}]);}}; + useEffect(()=>{refresh();},[]); + const runDemo=async()=>{await loadDemo(); await run500Demo(); await refresh();}; + const quickDispatch=async()=>{await dispatchTask({type:'GENERAL',payload:{subject:'manual'},amount:10,priority:1}); await refresh();}; + const reviewTasks=tasks.filter(t=>t.status==='needs_review'); + return
+

OfficeAI-500 Sovereign Mesh

+ {offline &&
OFFLINE DEMO MODE
} + {err &&
{err}
} +
{['Dashboard','Agents','Tasks','Memory','Governance'].map(v=>)}
+
+ {view==='Dashboard' &&
{Object.entries(metrics).map(([k,v])=>
{k}
{String(v)}
)}
} + {view==='Agents' &&
{agents.map(a=>
{a.id} | {a.role} | {a.status}
)}
} + {view==='Tasks' &&
{tasks.map(t=>
{t.id} | {t.agent_id} | {t.type} | {t.status} | {t.payload} | {t.amount} | {t.trace_id}
)}
} + {view==='Memory' &&
{memory.map(m=>
{m.capsule_id} | {m.task_type} | {m.confidence} | {m.use_count} | {String(m.verified)}
)}
} + {view==='Governance' &&
{reviewTasks.map(t=>
{t.id} {t.type}
)}
} +
+} diff --git a/OfficeAI-500-Sovereign-Mesh/frontend/src/api.js b/OfficeAI-500-Sovereign-Mesh/frontend/src/api.js new file mode 100644 index 0000000..8f284be --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/frontend/src/api.js @@ -0,0 +1,3 @@ +const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8000'; +const j = async (u,o={})=>{const r=await fetch(`${API_BASE}${u}`,{headers:{'Content-Type':'application/json'},...o});if(!r.ok) throw new Error(await r.text()); return r.json();}; +export const getHealth=()=>j('/health'); export const getMetrics=()=>j('/metrics'); export const getAgents=()=>j('/agents'); export const getTasks=(limit=50)=>j(`/tasks?limit=${limit}`); export const getTask=(taskId)=>j(`/tasks/${taskId}`); export const dispatchTask=(task)=>j('/mesh/dispatch',{method:'POST',body:JSON.stringify(task)}); export const batchTasks=(tasks)=>j('/mesh/batch',{method:'POST',body:JSON.stringify({tasks})}); export const loadDemo=()=>j('/demo/load',{method:'POST'}); export const run500Demo=()=>j('/demo/run_500',{method:'POST'}); export const searchMemory=(q='')=>j(`/memory/search?q=${encodeURIComponent(q)}`); export const approveTask=(taskId)=>j(`/review/${taskId}/approve`,{method:'POST'}); export const rejectTask=(taskId)=>j(`/review/${taskId}/reject`,{method:'POST'}); export const tamperMemory=(capsuleId)=>j(`/memory/${capsuleId}/tamper`,{method:'POST'}); export const verifyMemory=(capsuleId)=>j(`/memory/${capsuleId}/verify`); diff --git a/OfficeAI-500-Sovereign-Mesh/frontend/src/index.css b/OfficeAI-500-Sovereign-Mesh/frontend/src/index.css new file mode 100644 index 0000000..df944e4 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/frontend/src/index.css @@ -0,0 +1 @@ +@tailwind base; @tailwind components; @tailwind utilities; body{color:#e2e8f0;background:#020617;font-family:Inter,system-ui} .card{background:#0f172a;border:1px solid #1e293b;border-radius:10px;padding:12px} diff --git a/OfficeAI-500-Sovereign-Mesh/frontend/src/main.jsx b/OfficeAI-500-Sovereign-Mesh/frontend/src/main.jsx new file mode 100644 index 0000000..fe06e79 --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/frontend/src/main.jsx @@ -0,0 +1 @@ +import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import './index.css'; ReactDOM.createRoot(document.getElementById('root')).render(); diff --git a/OfficeAI-500-Sovereign-Mesh/frontend/tailwind.config.js b/OfficeAI-500-Sovereign-Mesh/frontend/tailwind.config.js new file mode 100644 index 0000000..a630feb --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/frontend/tailwind.config.js @@ -0,0 +1 @@ +export default { content: ['./index.html','./src/**/*.{js,jsx}'], theme: { extend: {} }, plugins: [] }; diff --git a/OfficeAI-500-Sovereign-Mesh/frontend/vite.config.js b/OfficeAI-500-Sovereign-Mesh/frontend/vite.config.js new file mode 100644 index 0000000..bd5899b --- /dev/null +++ b/OfficeAI-500-Sovereign-Mesh/frontend/vite.config.js @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +export default defineConfig({ plugins: [react()] }); diff --git a/README.md b/README.md index ee0046b..40c12ff 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Governed, append-only local memory runtime with deterministic intent classification, lane discipline, TrailLink provenance, and write-barrier controls. -## Module Tree +## Proposed Module Tree ```text app/ @@ -31,6 +31,7 @@ app/ tracing/veritas.py main.py tests/test_turn_flow.py +requirements.txt ``` ## Durable Schema (Truth Spine) @@ -41,6 +42,8 @@ SQLite tables: - `session_events` (turn event log) - `session_state` (current snapshot) +All durable record rows include record metadata such as `record_id`, `record_kind`, `created_at`, canonical payload fields, lane tags, policy status, parent/supersession links, and hash/trace/session identifiers. + ## Turn Flow 1. **prepare_turn**: session_event append → classify → lane route → gyro orient. @@ -56,3 +59,34 @@ SQLite tables: - `POST /prompt-prefix` - `GET /trace/{id}` - `GET /report/{id}` + +## Migration Plan From Legacy Runtime + +1. Keep externally useful API surface (`/health`, `/ingest`, `/query`, `/gyro`, tracing/report endpoints). +2. Move canonical truth to SQLite append-only `durable_records` with write-barrier enforcement. +3. Split session event/state from durable truth commits. +4. Add deterministic intent/lane routing and relevance gating before recall-to-context handoff. +5. Rebuild secondary artifacts in Gravel from Truth Spine + TrailLink + trace data. + +## Preserved vs Replaced + +Preserved: +- FastAPI service boundary and endpoint style. +- Gyro orientation concept and trace/report observability. +- Prompt-prefix / visor envelope concept. + +Replaced: +- JSONL-style authority assumptions with SQLite append-only authority. +- Weak retrieval flow with lane-disciplined routing + relevance gate. +- Uncontrolled memory writes with Sentinel/Lycanthrope mediated write barrier. + +## Run and Test + +```bash +pip install -r requirements.txt +uvicorn app.main:app --reload +``` + +```bash +pytest +``` diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..400fe02 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +fastapi>=0.110,<1.0 +uvicorn>=0.27,<1.0 +pydantic>=2.6,<3.0 +pytest>=8.0,<9.0 +httpx>=0.27,<1.0