Skip to content

Commit 06ba646

Browse files
committed
Add Logos backlog engine and health server integration
1 parent 747711f commit 06ba646

File tree

8 files changed

+924
-0
lines changed

8 files changed

+924
-0
lines changed

src/core/health.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ def __init__(self, daemon: "PulseDaemon", port: int = DEFAULT_PORT):
4242
from pulse.src.metrics import PulseMetrics
4343
self.metrics = PulseMetrics(daemon)
4444

45+
# Logos backlog engine
46+
from pulse.src.logos import LogosAPI, LogosStore
47+
from pulse.src.logos.seed import seed
48+
self._logos_store = LogosStore()
49+
self._logos_api = LogosAPI(store=self._logos_store)
50+
self._logos_api.register_routes(self._app)
51+
seed(self._logos_store)
52+
4553
async def start(self):
4654
"""Start the health server."""
4755
self._runner = web.AppRunner(self._app)

src/logos/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Logos — task orchestration backlog engine for the Hypostas agent army."""
2+
3+
from pulse.src.logos.schemas import Task
4+
from pulse.src.logos.store import LogosStore
5+
from pulse.src.logos.api import LogosAPI
6+
from pulse.src.logos.soma_bridge import SomaBridge
7+
8+
__all__ = ["Task", "LogosStore", "LogosAPI", "SomaBridge"]

src/logos/api.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Logos HTTP API — aiohttp routes for task management."""
2+
3+
import json
4+
import logging
5+
from aiohttp import web
6+
7+
from pulse.src.logos.store import LogosStore
8+
9+
logger = logging.getLogger("pulse.logos.api")
10+
11+
12+
class LogosAPI:
13+
"""HTTP route handlers for the Logos backlog engine.
14+
15+
Call register_routes(app) to mount /logos/* on an aiohttp application.
16+
"""
17+
18+
def __init__(self, store: LogosStore | None = None):
19+
self.store = store or LogosStore()
20+
21+
def register_routes(self, app: web.Application):
22+
"""Mount all Logos routes on an aiohttp app."""
23+
app.router.add_get("/logos/tasks", self._list_tasks)
24+
app.router.add_post("/logos/tasks", self._create_task)
25+
app.router.add_get("/logos/tasks/{id}", self._get_task)
26+
app.router.add_patch("/logos/tasks/{id}", self._update_task)
27+
app.router.add_delete("/logos/tasks/{id}", self._delete_task)
28+
app.router.add_get("/logos/next/{agent}", self._next_task)
29+
app.router.add_get("/logos/stats", self._stats)
30+
31+
async def _list_tasks(self, request: web.Request) -> web.Response:
32+
project = request.query.get("project")
33+
agent = request.query.get("agent")
34+
status = request.query.get("status")
35+
tasks = self.store.list_tasks(project=project, agent=agent, status=status)
36+
return web.json_response([t.to_dict() for t in tasks])
37+
38+
async def _create_task(self, request: web.Request) -> web.Response:
39+
try:
40+
data = await request.json()
41+
except Exception:
42+
return web.json_response({"error": "invalid JSON"}, status=400)
43+
44+
if not data.get("title") or not data.get("project"):
45+
return web.json_response(
46+
{"error": "title and project are required"}, status=400
47+
)
48+
49+
from pulse.src.logos.schemas import Task
50+
task = Task.from_dict({
51+
"title": data["title"],
52+
"description": data.get("description", ""),
53+
"project": data["project"],
54+
"agent": data.get("agent", "mira"),
55+
"status": data.get("status", "backlog"),
56+
"priority": data.get("priority", 3),
57+
"tags": data.get("tags", []),
58+
"spec": data.get("spec", ""),
59+
"requires_human": data.get("requires_human", False),
60+
"parent_id": data.get("parent_id"),
61+
})
62+
self.store.create_task(task)
63+
return web.json_response(task.to_dict(), status=201)
64+
65+
async def _get_task(self, request: web.Request) -> web.Response:
66+
task_id = request.match_info["id"]
67+
task = self.store.get_task(task_id)
68+
if not task:
69+
return web.json_response({"error": "not found"}, status=404)
70+
return web.json_response(task.to_dict())
71+
72+
async def _update_task(self, request: web.Request) -> web.Response:
73+
task_id = request.match_info["id"]
74+
try:
75+
data = await request.json()
76+
except Exception:
77+
return web.json_response({"error": "invalid JSON"}, status=400)
78+
79+
task = self.store.update_task(task_id, **data)
80+
if not task:
81+
return web.json_response({"error": "not found"}, status=404)
82+
return web.json_response(task.to_dict())
83+
84+
async def _delete_task(self, request: web.Request) -> web.Response:
85+
task_id = request.match_info["id"]
86+
deleted = self.store.delete_task(task_id)
87+
if not deleted:
88+
return web.json_response({"error": "not found"}, status=404)
89+
return web.json_response({"status": "deleted"})
90+
91+
async def _next_task(self, request: web.Request) -> web.Response:
92+
agent = request.match_info["agent"]
93+
task = self.store.next_task(agent)
94+
if not task:
95+
return web.json_response({"error": "no tasks available"}, status=404)
96+
return web.json_response(task.to_dict())
97+
98+
async def _stats(self, request: web.Request) -> web.Response:
99+
return web.json_response(self.store.stats())

src/logos/schemas.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Logos task schema — the shape of work in the Hypostas backlog."""
2+
3+
import time
4+
import uuid
5+
from dataclasses import dataclass, field, asdict
6+
7+
8+
# Naming philosophy — symbolic constants for task lifecycle stages
9+
CANON = "backlog" # the Canon — what must be done
10+
PLEROMA = "backlog" # the Pleroma — the fullness/potential
11+
AGORA = "review" # the Agora — where decisions are made together
12+
ARCHIVE = "done" # completed work
13+
14+
VALID_STATUSES = {"backlog", "in_progress", "review", "done", "blocked"}
15+
VALID_PROJECTS = {"gnosis", "anima", "aether", "pulse", "soma", "logos"}
16+
VALID_AGENTS = {"mira", "vera", "lyra", "sage", "iris"}
17+
18+
19+
@dataclass
20+
class Task:
21+
title: str
22+
description: str
23+
project: str
24+
agent: str = "mira"
25+
status: str = "backlog"
26+
priority: int = 3
27+
id: str = field(default_factory=lambda: str(uuid.uuid4()))
28+
created_at: float = field(default_factory=time.time)
29+
updated_at: float = field(default_factory=time.time)
30+
started_at: float | None = None
31+
completed_at: float | None = None
32+
parent_id: str | None = None
33+
tags: list[str] = field(default_factory=list)
34+
spec: str = ""
35+
output: str | None = None
36+
review_notes: str | None = None
37+
requires_human: bool = False
38+
deploy_ready: bool = False
39+
40+
def to_dict(self) -> dict:
41+
return asdict(self)
42+
43+
@classmethod
44+
def from_dict(cls, data: dict) -> "Task":
45+
# Filter to only known fields
46+
known = {f.name for f in cls.__dataclass_fields__.values()}
47+
return cls(**{k: v for k, v in data.items() if k in known})

src/logos/seed.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Logos seed data — initial tasks for the Hypostas agent army."""
2+
3+
import logging
4+
5+
from pulse.src.logos.schemas import Task
6+
from pulse.src.logos.store import LogosStore
7+
8+
logger = logging.getLogger("pulse.logos.seed")
9+
10+
SEED_TASKS = [
11+
# GNOSIS
12+
dict(title="Add Stripe live keys to Vercel", description="Configure production Stripe keys in Vercel environment variables for Gnosis payments", project="gnosis", agent="mira", priority=5, tags=["payments", "blocking"]),
13+
dict(title="Post Reddit reply in r/23andme", description="Engage with DNA testing community about Gnosis platform capabilities", project="gnosis", agent="iris", priority=4, tags=["distribution"]),
14+
dict(title="Implement Sprint 4 UX improvements from Sage suggestions", description="Apply Sage's UX audit findings to improve Gnosis user flows", project="gnosis", agent="mira", priority=3, tags=["ux"]),
15+
dict(title="Write 3 more SEO blog posts for Gnosis", description="Create SEO-optimized content targeting key Gnosis search terms", project="gnosis", agent="lyra", priority=2, tags=["content", "seo"]),
16+
# ANIMA
17+
dict(title="Run Supabase migrations 003-008", description="Execute pending database migrations for Anima backend schema updates", project="anima", agent="mira", priority=5, tags=["database", "blocking"]),
18+
dict(title="Configure DNS: anima.hypostas.com CNAME to Vercel", description="Set up DNS CNAME record pointing anima.hypostas.com to Vercel deployment", project="anima", agent="mira", priority=5, tags=["dns", "blocking"]),
19+
dict(title="Add Stripe + admin env vars to Cloudflare Worker", description="Configure Stripe and admin environment variables in the Cloudflare Worker deployment", project="anima", agent="mira", priority=5, tags=["payments", "blocking"]),
20+
dict(title="Set up Apple Developer account and TestFlight", description="Register Apple Developer account and configure TestFlight for Anima iOS beta", project="anima", agent="mira", priority=4, tags=["ios"]),
21+
dict(title="Implement Sprint 4 retention features (voice calibration, proactive reach-out)", description="Build voice calibration and proactive outreach features for user retention", project="anima", agent="mira", priority=3, tags=["features"]),
22+
dict(title="Write Echo demo tweet thread", description="Create a compelling Twitter thread demonstrating Echo capabilities", project="anima", agent="lyra", priority=3, tags=["marketing"]),
23+
# AETHER
24+
dict(title="Run Supabase migration 002 for 3D world persistence", description="Execute migration to add 3D world state persistence tables", project="aether", agent="mira", priority=4, tags=["database"]),
25+
dict(title="Polish Chrome extension 3D rendering", description="Improve visual quality and performance of Chrome extension 3D views", project="aether", agent="mira", priority=3, tags=["frontend"]),
26+
dict(title="Build domain-as-world persistence layer", description="Implement backend persistence for domain-mapped 3D world state", project="aether", agent="mira", priority=3, tags=["backend"]),
27+
dict(title="Design B2B domain claiming flow", description="Design the UX flow for businesses to claim and customize their domain world", project="aether", agent="vera", priority=2, tags=["monetization"]),
28+
# PULSE / SOMA
29+
dict(title="Wire Logos pressure into Soma drives system", description="Integrate Logos backlog pressure as a signal in the Soma drive engine", project="pulse", agent="iris", priority=3, tags=["infrastructure"]),
30+
dict(title="Build Mira autonomous task executor loop", description="Implement the autonomous loop that lets Mira pick and execute tasks from the Logos backlog", project="pulse", agent="iris", priority=4, tags=["agent-army", "critical"]),
31+
]
32+
33+
34+
def seed(store: LogosStore | None = None) -> int:
35+
"""Seed the backlog with initial tasks if the DB is empty.
36+
37+
Returns the number of tasks created.
38+
"""
39+
s = store or LogosStore()
40+
if not s.is_empty():
41+
logger.info("Logos backlog already seeded — skipping")
42+
return 0
43+
44+
for task_data in SEED_TASKS:
45+
task = Task(
46+
title=task_data["title"],
47+
description=task_data["description"],
48+
project=task_data["project"],
49+
agent=task_data["agent"],
50+
priority=task_data["priority"],
51+
tags=task_data["tags"],
52+
)
53+
s.create_task(task)
54+
55+
logger.info(f"Seeded Logos backlog with {len(SEED_TASKS)} tasks")
56+
return len(SEED_TASKS)

src/logos/soma_bridge.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""Soma bridge — connects Logos backlog pressure to the Soma drive system."""
2+
3+
import logging
4+
import re
5+
import time
6+
7+
from pulse.src.logos.schemas import Task
8+
from pulse.src.logos.store import LogosStore
9+
10+
logger = logging.getLogger("pulse.logos.soma_bridge")
11+
12+
13+
class SomaBridge:
14+
"""Bridge between the Logos task engine and Soma drives."""
15+
16+
def __init__(self, store: LogosStore | None = None):
17+
self.store = store or LogosStore()
18+
19+
def ingest_spec(
20+
self, spec_text: str, project: str, agent: str = "mira"
21+
) -> list[Task]:
22+
"""Parse bullet/numbered lists from a spec into individual tasks."""
23+
tasks = []
24+
lines = spec_text.strip().splitlines()
25+
for line in lines:
26+
line = line.strip()
27+
# Match bullet points (-, *, •) or numbered lists (1., 2., etc.)
28+
match = re.match(r"^(?:[-*•]|\d+[.)]\s*)\s*(.+)$", line)
29+
if match:
30+
title = match.group(1).strip()
31+
if title:
32+
task = Task(
33+
title=title,
34+
description="",
35+
project=project,
36+
agent=agent,
37+
spec=spec_text,
38+
)
39+
self.store.create_task(task)
40+
tasks.append(task)
41+
return tasks
42+
43+
def review_output(
44+
self, task_id: str, output: str, store: LogosStore | None = None
45+
) -> Task | None:
46+
"""Mark task for review, flagging incomplete outputs."""
47+
s = store or self.store
48+
requires_human = (
49+
len(output.strip()) < 50
50+
or "TODO" in output
51+
or "blocked" in output.lower()
52+
)
53+
task = s.update_task(
54+
task_id,
55+
status="review",
56+
output=output,
57+
requires_human=requires_human,
58+
)
59+
if task and requires_human:
60+
logger.info(f"Task {task_id} flagged for human review: output may be incomplete")
61+
return task
62+
63+
def get_logos_pressure(self, store: LogosStore | None = None) -> float:
64+
"""Return 0.0-1.0 pressure based on overdue/blocked task density.
65+
66+
High pressure when many tasks are blocked or high-priority backlog items
67+
are accumulating.
68+
"""
69+
s = store or self.store
70+
all_tasks = s.list_tasks()
71+
if not all_tasks:
72+
return 0.0
73+
74+
blocked = sum(1 for t in all_tasks if t.status == "blocked")
75+
critical_backlog = sum(
76+
1 for t in all_tasks
77+
if t.status == "backlog" and t.priority >= 4
78+
)
79+
total_backlog = sum(1 for t in all_tasks if t.status == "backlog")
80+
81+
# Weighted pressure: blocked tasks are heavy, critical backlog matters
82+
pressure = (blocked * 0.15) + (critical_backlog * 0.08) + (total_backlog * 0.02)
83+
return min(1.0, max(0.0, pressure))

0 commit comments

Comments
 (0)