Spaces:
Runtime error
Runtime error
Cyber Catalyst Team commited on
Commit ·
ed6da33
1
Parent(s): 172babb
feat: async httpx inter-space calls, Karpathy project_state.md, vault push, idle worker protocol
Browse files- __pycache__/backend.cpython-310.pyc +0 -0
- backend.py +234 -65
- requirements.txt +1 -0
__pycache__/backend.cpython-310.pyc
ADDED
|
Binary file (76.6 kB). View file
|
|
|
backend.py
CHANGED
|
@@ -1748,14 +1748,20 @@ class ForgeExecuteRequest(BaseModel):
|
|
| 1748 |
action: str
|
| 1749 |
prompt: str
|
| 1750 |
context_rules: Optional[str] = ""
|
|
|
|
| 1751 |
|
| 1752 |
@app.post("/api/forge/execute")
|
| 1753 |
async def forge_execute(req: ForgeExecuteRequest, authorization: str = Header(None)):
|
| 1754 |
auth(authorization)
|
| 1755 |
-
log_activity(f"[Forge] Received execution request for task {req.task_id}: '{req.prompt}'")
|
| 1756 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1757 |
messages = [
|
| 1758 |
-
{"role": "system", "content":
|
| 1759 |
{"role": "user", "content": req.prompt}
|
| 1760 |
]
|
| 1761 |
|
|
@@ -1861,9 +1867,20 @@ async def db_heartbeat_loop():
|
|
| 1861 |
await asyncio.sleep(240) # Every 4 minutes
|
| 1862 |
|
| 1863 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1864 |
SPACE3_URL = os.environ.get("SPACE3_URL", "https://augment17-claude-code-backend.hf.space")
|
| 1865 |
SPACE4_URL = os.environ.get("SPACE4_URL", "https://shyota-mcp-cloud-host.hf.space")
|
| 1866 |
-
SPACE5_URL = os.environ.get("SPACE5_URL",
|
| 1867 |
SPACE6_URL = os.environ.get("SPACE6_URL", "https://augment17-mcp-cloud-host.hf.space")
|
| 1868 |
|
| 1869 |
async def get_active_projects():
|
|
@@ -1882,117 +1899,232 @@ async def update_db_roadmap(project_name: str, roadmap: list):
|
|
| 1882 |
async with db_pool.acquire() as conn:
|
| 1883 |
await conn.execute("UPDATE eternity_projects SET roadmap = $1 WHERE project_name = $2", json.dumps(roadmap), project_name)
|
| 1884 |
|
| 1885 |
-
|
|
|
|
| 1886 |
headers = {
|
| 1887 |
"Authorization": f"Bearer {BACKEND_API_KEY}",
|
| 1888 |
"Content-Type": "application/json"
|
| 1889 |
}
|
| 1890 |
-
req = urllib.request.Request(
|
| 1891 |
-
url,
|
| 1892 |
-
data=json.dumps(payload).encode('utf-8'),
|
| 1893 |
-
headers=headers,
|
| 1894 |
-
method="POST"
|
| 1895 |
-
)
|
| 1896 |
try:
|
| 1897 |
-
with
|
| 1898 |
-
|
|
|
|
|
|
|
| 1899 |
except Exception as e:
|
| 1900 |
-
|
| 1901 |
return {"status": "error", "error": str(e)}
|
| 1902 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1903 |
async def execute_build_cycle(project_name: str, goal: str):
|
| 1904 |
log_activity(f"[Build Mode] Initiating build cycle for project '{project_name}' (goal: '{goal}')")
|
| 1905 |
await rate_limiter.wait_for_nim()
|
| 1906 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1907 |
try:
|
| 1908 |
res = await nim_client.chat.completions.create(
|
| 1909 |
model="nvidia/llama-3.1-nemotron-70b-instruct",
|
| 1910 |
-
messages=[{"role": "user", "content":
|
| 1911 |
max_tokens=300
|
| 1912 |
)
|
| 1913 |
-
|
| 1914 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1915 |
context_rules = task.get("context_rules", "")
|
| 1916 |
except Exception as e:
|
| 1917 |
log_activity(f"[Build Mode Error] NIM planning failed for project '{project_name}': {e}")
|
| 1918 |
return
|
| 1919 |
|
| 1920 |
-
|
| 1921 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1922 |
"task_id": f"build_{project_name}_{int(time.time())}",
|
| 1923 |
"action": "execute_code",
|
| 1924 |
"prompt": task_prompt,
|
| 1925 |
-
"context_rules": context_rules
|
|
|
|
| 1926 |
})
|
| 1927 |
-
|
| 1928 |
if forge_res.get("status") == "success":
|
| 1929 |
-
|
| 1930 |
-
log_activity(f"[Build Mode]
|
| 1931 |
-
|
|
|
|
|
|
|
| 1932 |
"test_cmd": "verify_ui",
|
| 1933 |
-
"url":
|
| 1934 |
-
|
| 1935 |
-
|
| 1936 |
-
|
| 1937 |
-
|
| 1938 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1939 |
else:
|
| 1940 |
-
log_activity(f"[Build Mode Warning] Space 3
|
| 1941 |
|
| 1942 |
async def execute_eternity_cycle(project_name: str, goal: str):
|
| 1943 |
-
log_activity(f"[Eternity Mode]
|
| 1944 |
-
|
| 1945 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1946 |
brief = research_res.get("brief", "No new features found.")
|
| 1947 |
await update_db_brief(project_name, brief)
|
| 1948 |
-
log_activity(f"[Eternity Mode]
|
| 1949 |
|
|
|
|
| 1950 |
await rate_limiter.wait_for_nim()
|
| 1951 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1952 |
try:
|
| 1953 |
res = await nim_client.chat.completions.create(
|
| 1954 |
model="nvidia/llama-3.1-nemotron-70b-instruct",
|
| 1955 |
-
messages=[{"role": "user", "content":
|
| 1956 |
max_tokens=300
|
| 1957 |
)
|
| 1958 |
-
|
| 1959 |
-
|
|
|
|
|
|
|
|
|
|
| 1960 |
context_rules = task.get("context_rules", "")
|
| 1961 |
except Exception as e:
|
| 1962 |
-
log_activity(f"[Eternity Mode Error] NIM planning failed for
|
| 1963 |
return
|
| 1964 |
|
| 1965 |
-
|
| 1966 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1967 |
"task_id": f"eternity_{project_name}_{int(time.time())}",
|
| 1968 |
"action": "execute_code",
|
| 1969 |
"prompt": task_prompt,
|
| 1970 |
-
"context_rules": context_rules
|
|
|
|
| 1971 |
})
|
| 1972 |
|
| 1973 |
if forge_res.get("status") == "success":
|
| 1974 |
-
|
| 1975 |
-
log_activity("[Eternity Mode]
|
| 1976 |
-
|
| 1977 |
-
|
| 1978 |
-
|
| 1979 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1980 |
|
| 1981 |
async def run_eternity_loop():
|
| 1982 |
-
|
|
|
|
| 1983 |
loop_counter = 0
|
| 1984 |
while True:
|
| 1985 |
try:
|
| 1986 |
if not db_pool:
|
| 1987 |
await asyncio.sleep(10)
|
| 1988 |
continue
|
| 1989 |
-
|
| 1990 |
projects = await get_active_projects()
|
| 1991 |
-
|
| 1992 |
if not projects:
|
| 1993 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1994 |
continue
|
| 1995 |
-
|
| 1996 |
loop_counter += 1
|
| 1997 |
for p in projects:
|
| 1998 |
name = p["project_name"]
|
|
@@ -2000,35 +2132,36 @@ async def run_eternity_loop():
|
|
| 2000 |
deadline = p["deadline"]
|
| 2001 |
current_mode = p["current_mode"]
|
| 2002 |
priority = p["priority"]
|
| 2003 |
-
|
| 2004 |
-
# Priority gating:
|
| 2005 |
if priority == "low" and (loop_counter % 4) != 0:
|
|
|
|
| 2006 |
continue
|
| 2007 |
-
|
| 2008 |
now = datetime.now(timezone.utc)
|
| 2009 |
if current_mode == "build" and now >= deadline:
|
| 2010 |
await update_db_mode(name, "eternity")
|
| 2011 |
current_mode = "eternity"
|
| 2012 |
-
log_activity(f"[Eternity Loop] Project '{name}' deadline
|
| 2013 |
-
|
| 2014 |
if current_mode == "build":
|
| 2015 |
await execute_build_cycle(name, goal)
|
| 2016 |
else:
|
| 2017 |
-
# Enforce the sleep interval for Eternity Mode independently per project
|
| 2018 |
last_run = last_run_times.get(name, 0.0)
|
| 2019 |
now_ts = time.time()
|
| 2020 |
interval = int(os.environ.get("ETERNITY_LOOP_INTERVAL", "3600"))
|
| 2021 |
if now_ts - last_run < interval:
|
|
|
|
|
|
|
| 2022 |
continue
|
| 2023 |
last_run_times[name] = now_ts
|
| 2024 |
-
|
| 2025 |
await execute_eternity_cycle(name, goal)
|
| 2026 |
-
|
| 2027 |
-
# Base check interval
|
| 2028 |
await asyncio.sleep(300)
|
| 2029 |
-
|
| 2030 |
except Exception as e:
|
| 2031 |
-
log_activity(f"[Eternity Loop Error]
|
| 2032 |
await asyncio.sleep(60)
|
| 2033 |
|
| 2034 |
|
|
@@ -2037,6 +2170,42 @@ async def dashboard():
|
|
| 2037 |
return HTMLResponse(content=DASHBOARD_HTML)
|
| 2038 |
|
| 2039 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2040 |
@app.get("/api/logs")
|
| 2041 |
async def get_logs():
|
| 2042 |
return list(activity_logs)
|
|
|
|
| 1748 |
action: str
|
| 1749 |
prompt: str
|
| 1750 |
context_rules: Optional[str] = ""
|
| 1751 |
+
project_state_md: Optional[str] = "" # Karpathy CLAUDE.md protocol — shared project context
|
| 1752 |
|
| 1753 |
@app.post("/api/forge/execute")
|
| 1754 |
async def forge_execute(req: ForgeExecuteRequest, authorization: str = Header(None)):
|
| 1755 |
auth(authorization)
|
| 1756 |
+
log_activity(f"[Forge] Received execution request for task {req.task_id}: '{req.prompt[:80]}'")
|
| 1757 |
try:
|
| 1758 |
+
# Inject project_state.md into system context if provided (Karpathy protocol)
|
| 1759 |
+
system_content = AGENTIC_SYSTEM_PROMPT + f"\nContext rules: {req.context_rules}"
|
| 1760 |
+
if req.project_state_md:
|
| 1761 |
+
system_content += f"\n\n---\n## Project State Document (read this first)\n{req.project_state_md}"
|
| 1762 |
+
|
| 1763 |
messages = [
|
| 1764 |
+
{"role": "system", "content": system_content},
|
| 1765 |
{"role": "user", "content": req.prompt}
|
| 1766 |
]
|
| 1767 |
|
|
|
|
| 1867 |
await asyncio.sleep(240) # Every 4 minutes
|
| 1868 |
|
| 1869 |
|
| 1870 |
+
import httpx
|
| 1871 |
+
|
| 1872 |
+
# ---------------------------------------------------------------------------
|
| 1873 |
+
# Inter-Space URLs — The 6-Space Topology
|
| 1874 |
+
# Space 2 (self) = shyota/claude-code-backend — Cerebrum (Orchestrator)
|
| 1875 |
+
# Space 3 = augment17/claude-code-backend — Forge (Coder)
|
| 1876 |
+
# Space 4 = shyota/mcp-cloud-host — Library (Research + MCP)
|
| 1877 |
+
# Space 5 (Vault) = Space 3 also handles vault push since agent-worker-loop
|
| 1878 |
+
# HF slot is not provisioned. SPACE5_URL = SPACE3_URL.
|
| 1879 |
+
# Space 6 = augment17/mcp-cloud-host — Sandbox (UI Tester)
|
| 1880 |
+
# ---------------------------------------------------------------------------
|
| 1881 |
SPACE3_URL = os.environ.get("SPACE3_URL", "https://augment17-claude-code-backend.hf.space")
|
| 1882 |
SPACE4_URL = os.environ.get("SPACE4_URL", "https://shyota-mcp-cloud-host.hf.space")
|
| 1883 |
+
SPACE5_URL = os.environ.get("SPACE5_URL", SPACE3_URL) # Vault is co-hosted on Space 3
|
| 1884 |
SPACE6_URL = os.environ.get("SPACE6_URL", "https://augment17-mcp-cloud-host.hf.space")
|
| 1885 |
|
| 1886 |
async def get_active_projects():
|
|
|
|
| 1899 |
async with db_pool.acquire() as conn:
|
| 1900 |
await conn.execute("UPDATE eternity_projects SET roadmap = $1 WHERE project_name = $2", json.dumps(roadmap), project_name)
|
| 1901 |
|
| 1902 |
+
# Async HTTP POST — replaces blocking urllib.request so the event loop never freezes
|
| 1903 |
+
async def apost_json(url: str, payload: dict, timeout: float = 120.0) -> dict:
|
| 1904 |
headers = {
|
| 1905 |
"Authorization": f"Bearer {BACKEND_API_KEY}",
|
| 1906 |
"Content-Type": "application/json"
|
| 1907 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1908 |
try:
|
| 1909 |
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
| 1910 |
+
r = await client.post(url, json=payload, headers=headers)
|
| 1911 |
+
r.raise_for_status()
|
| 1912 |
+
return r.json()
|
| 1913 |
except Exception as e:
|
| 1914 |
+
log_activity(f"[HTTP Error] POST to {url} failed: {e}")
|
| 1915 |
return {"status": "error", "error": str(e)}
|
| 1916 |
|
| 1917 |
+
# ---------------------------------------------------------------------------
|
| 1918 |
+
# Karpathy project_state.md — Shared Context Document
|
| 1919 |
+
# Each space receives this instead of bare prompts so agents always know
|
| 1920 |
+
# what phase they are in, what was built before, and what the research says.
|
| 1921 |
+
# ---------------------------------------------------------------------------
|
| 1922 |
+
def build_project_state_md(project_name: str, goal: str, mode: str,
|
| 1923 |
+
task_prompt: str, brief: str = "",
|
| 1924 |
+
prior_summary: str = "") -> str:
|
| 1925 |
+
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%MZ")
|
| 1926 |
+
return f"""# Project State: {project_name}
|
| 1927 |
+
_Generated by Space 2 (Cerebrum) at {ts}_
|
| 1928 |
+
|
| 1929 |
+
## Current Objective
|
| 1930 |
+
**Mode:** {mode.upper()}
|
| 1931 |
+
**Goal:** {goal}
|
| 1932 |
+
|
| 1933 |
+
## Instructions for Space 3 (The Forge)
|
| 1934 |
+
{task_prompt}
|
| 1935 |
+
|
| 1936 |
+
## Research Brief (from Space 4 — The Library)
|
| 1937 |
+
{brief if brief else '_No research brief yet._'}
|
| 1938 |
+
|
| 1939 |
+
## Prior Work Summary
|
| 1940 |
+
{prior_summary if prior_summary else '_First cycle._'}
|
| 1941 |
+
|
| 1942 |
+
## Context Rules
|
| 1943 |
+
- Write deterministic, testable code.
|
| 1944 |
+
- All files go in /tmp/workspace/.
|
| 1945 |
+
- Return a 3-line summary: status, file changed, test result.
|
| 1946 |
+
"""
|
| 1947 |
+
|
| 1948 |
async def execute_build_cycle(project_name: str, goal: str):
|
| 1949 |
log_activity(f"[Build Mode] Initiating build cycle for project '{project_name}' (goal: '{goal}')")
|
| 1950 |
await rate_limiter.wait_for_nim()
|
| 1951 |
+
plan_prompt = (
|
| 1952 |
+
f"We are building: '{goal}'. "
|
| 1953 |
+
"Write ONE JSON instruction for Space 3 (The Forge) to code the next milestone. "
|
| 1954 |
+
"Respond ONLY with JSON: {\"prompt\": \"task description\", \"context_rules\": \"rules\"}"
|
| 1955 |
+
)
|
| 1956 |
try:
|
| 1957 |
res = await nim_client.chat.completions.create(
|
| 1958 |
model="nvidia/llama-3.1-nemotron-70b-instruct",
|
| 1959 |
+
messages=[{"role": "user", "content": plan_prompt}],
|
| 1960 |
max_tokens=300
|
| 1961 |
)
|
| 1962 |
+
raw = res.choices[0].message.content.strip()
|
| 1963 |
+
# Strip markdown code fences if present
|
| 1964 |
+
if raw.startswith("```"):
|
| 1965 |
+
raw = raw.split("```")[1].lstrip("json").strip()
|
| 1966 |
+
task = json.loads(raw)
|
| 1967 |
+
task_prompt = task.get("prompt", "")
|
| 1968 |
context_rules = task.get("context_rules", "")
|
| 1969 |
except Exception as e:
|
| 1970 |
log_activity(f"[Build Mode Error] NIM planning failed for project '{project_name}': {e}")
|
| 1971 |
return
|
| 1972 |
|
| 1973 |
+
# Build the Karpathy project_state.md to send to Space 3
|
| 1974 |
+
state_md = build_project_state_md(
|
| 1975 |
+
project_name=project_name, goal=goal, mode="build",
|
| 1976 |
+
task_prompt=task_prompt
|
| 1977 |
+
)
|
| 1978 |
+
|
| 1979 |
+
log_activity(f"[Build Mode] Dispatching to Space 3 (Forge) — project '{project_name}': '{task_prompt[:80]}'")
|
| 1980 |
+
forge_res = await apost_json(f"{SPACE3_URL}/api/forge/execute", {
|
| 1981 |
"task_id": f"build_{project_name}_{int(time.time())}",
|
| 1982 |
"action": "execute_code",
|
| 1983 |
"prompt": task_prompt,
|
| 1984 |
+
"context_rules": context_rules,
|
| 1985 |
+
"project_state_md": state_md # Karpathy CLAUDE.md protocol
|
| 1986 |
})
|
| 1987 |
+
|
| 1988 |
if forge_res.get("status") == "success":
|
| 1989 |
+
summary = forge_res.get("summary", "")
|
| 1990 |
+
log_activity(f"[Build Mode] Space 3 (Forge) SUCCESS for '{project_name}': {summary}")
|
| 1991 |
+
|
| 1992 |
+
log_activity(f"[Build Mode] Triggering Space 6 (Sandbox) UI verification for '{project_name}'...")
|
| 1993 |
+
test_res = await apost_json(f"{SPACE6_URL}/api/sandbox/test", {
|
| 1994 |
"test_cmd": "verify_ui",
|
| 1995 |
+
"url": SPACE3_URL,
|
| 1996 |
+
"project_name": project_name
|
| 1997 |
+
}, timeout=30.0)
|
| 1998 |
+
verdict = test_res.get("verdict", "UNKNOWN")
|
| 1999 |
+
reason = test_res.get("reason", "")
|
| 2000 |
+
log_activity(f"[Build Mode] Space 6 (Sandbox) Verdict: {verdict} — {reason}")
|
| 2001 |
+
|
| 2002 |
+
log_activity("[Build Mode] Triggering Space 5 (Vault) backup commit...")
|
| 2003 |
+
vault_res = await apost_json(f"{SPACE5_URL}/api/vault/push", {
|
| 2004 |
+
"project_name": project_name,
|
| 2005 |
+
"summary": summary
|
| 2006 |
+
}, timeout=30.0)
|
| 2007 |
+
log_activity(f"[Build Mode] Space 5 (Vault) backup: {vault_res.get('status', 'unknown')}")
|
| 2008 |
else:
|
| 2009 |
+
log_activity(f"[Build Mode Warning] Space 3 (Forge) failure for '{project_name}': {forge_res.get('error')}")
|
| 2010 |
|
| 2011 |
async def execute_eternity_cycle(project_name: str, goal: str):
|
| 2012 |
+
log_activity(f"[Eternity Mode] R&D cycle starting for project '{project_name}' (goal: '{goal}')")
|
| 2013 |
+
|
| 2014 |
+
# Step 1: Ask Space 4 (Library) for research
|
| 2015 |
+
log_activity(f"[Eternity Mode] Querying Space 4 (Library) for '{project_name}'...")
|
| 2016 |
+
research_res = await apost_json(f"{SPACE4_URL}/api/research", {
|
| 2017 |
+
"query": f"novel algorithms, optimizations, and next features for: {goal}"
|
| 2018 |
+
})
|
| 2019 |
brief = research_res.get("brief", "No new features found.")
|
| 2020 |
await update_db_brief(project_name, brief)
|
| 2021 |
+
log_activity(f"[Eternity Mode] Space 4 (Library) brief received ({len(brief)} chars)")
|
| 2022 |
|
| 2023 |
+
# Step 2: Ask NIM to plan next feature based on research
|
| 2024 |
await rate_limiter.wait_for_nim()
|
| 2025 |
+
plan_prompt = (
|
| 2026 |
+
f"Goal: '{goal}'.\nResearch Brief:\n{brief[:2000]}\n\n"
|
| 2027 |
+
"Based on this research, plan the SINGLE most impactful next feature to implement. "
|
| 2028 |
+
"Respond ONLY with JSON: {\"prompt\": \"task\", \"context_rules\": \"rules\"}"
|
| 2029 |
+
)
|
| 2030 |
try:
|
| 2031 |
res = await nim_client.chat.completions.create(
|
| 2032 |
model="nvidia/llama-3.1-nemotron-70b-instruct",
|
| 2033 |
+
messages=[{"role": "user", "content": plan_prompt}],
|
| 2034 |
max_tokens=300
|
| 2035 |
)
|
| 2036 |
+
raw = res.choices[0].message.content.strip()
|
| 2037 |
+
if raw.startswith("```"):
|
| 2038 |
+
raw = raw.split("```")[1].lstrip("json").strip()
|
| 2039 |
+
task = json.loads(raw)
|
| 2040 |
+
task_prompt = task.get("prompt", "")
|
| 2041 |
context_rules = task.get("context_rules", "")
|
| 2042 |
except Exception as e:
|
| 2043 |
+
log_activity(f"[Eternity Mode Error] NIM planning failed for '{project_name}': {e}")
|
| 2044 |
return
|
| 2045 |
|
| 2046 |
+
# Step 3: Build Karpathy project_state.md
|
| 2047 |
+
state_md = build_project_state_md(
|
| 2048 |
+
project_name=project_name, goal=goal, mode="eternity",
|
| 2049 |
+
task_prompt=task_prompt, brief=brief
|
| 2050 |
+
)
|
| 2051 |
+
|
| 2052 |
+
# Step 4: Dispatch to Space 3 (Forge)
|
| 2053 |
+
log_activity(f"[Eternity Mode] Dispatching to Space 3 (Forge): '{task_prompt[:80]}'")
|
| 2054 |
+
forge_res = await apost_json(f"{SPACE3_URL}/api/forge/execute", {
|
| 2055 |
"task_id": f"eternity_{project_name}_{int(time.time())}",
|
| 2056 |
"action": "execute_code",
|
| 2057 |
"prompt": task_prompt,
|
| 2058 |
+
"context_rules": context_rules,
|
| 2059 |
+
"project_state_md": state_md
|
| 2060 |
})
|
| 2061 |
|
| 2062 |
if forge_res.get("status") == "success":
|
| 2063 |
+
summary = forge_res.get("summary", "")
|
| 2064 |
+
log_activity(f"[Eternity Mode] Space 3 (Forge) SUCCESS: {summary}")
|
| 2065 |
+
# Trigger vault backup after each successful eternity cycle
|
| 2066 |
+
vault_res = await apost_json(f"{SPACE5_URL}/api/vault/push", {
|
| 2067 |
+
"project_name": project_name,
|
| 2068 |
+
"summary": summary
|
| 2069 |
+
}, timeout=30.0)
|
| 2070 |
+
log_activity(f"[Eternity Mode] Space 5 (Vault) backup: {vault_res.get('status', 'unknown')}")
|
| 2071 |
+
else:
|
| 2072 |
+
log_activity(f"[Eternity Mode Warning] Space 3 (Forge) failure: {forge_res.get('error')}")
|
| 2073 |
+
|
| 2074 |
+
|
| 2075 |
+
last_run_times = {} # project_name -> last execution timestamp
|
| 2076 |
+
IDLE_TASK_INTERVAL = 1800 # Run idle tasks every 30 min when no active projects
|
| 2077 |
+
_last_idle_run = 0.0
|
| 2078 |
+
|
| 2079 |
+
async def execute_idle_worker_cycle():
|
| 2080 |
+
"""Idle Worker Protocol — runs when no active projects exist.
|
| 2081 |
+
Dispatches self-improvement tasks to Space 3 (Forge) to keep
|
| 2082 |
+
all containers warm and utilise free compute."""
|
| 2083 |
+
log_activity("[Idle Worker] No active projects. Dispatching background optimisation task to Space 3...")
|
| 2084 |
+
idle_tasks = [
|
| 2085 |
+
"Scan /tmp/workspace for any Python or JS files. For each file found, "
|
| 2086 |
+
"check if a corresponding test file exists. If not, write one basic unit test and run it. "
|
| 2087 |
+
"Return a summary of files scanned and tests added.",
|
| 2088 |
+
"Scan /tmp/workspace for duplicate function definitions across files. "
|
| 2089 |
+
"Report any found and refactor the worst one to a shared utility module.",
|
| 2090 |
+
"Run a syntax check on all files in /tmp/workspace and fix any issues found.",
|
| 2091 |
+
]
|
| 2092 |
+
import random
|
| 2093 |
+
chosen = random.choice(idle_tasks)
|
| 2094 |
+
state_md = build_project_state_md(
|
| 2095 |
+
project_name="idle-maintenance", goal="system self-improvement",
|
| 2096 |
+
mode="idle", task_prompt=chosen
|
| 2097 |
+
)
|
| 2098 |
+
res = await apost_json(f"{SPACE3_URL}/api/forge/execute", {
|
| 2099 |
+
"task_id": f"idle_{int(time.time())}",
|
| 2100 |
+
"action": "execute_code",
|
| 2101 |
+
"prompt": chosen,
|
| 2102 |
+
"context_rules": "Be concise. Fix only what is necessary. Return a 3-line summary.",
|
| 2103 |
+
"project_state_md": state_md
|
| 2104 |
+
}, timeout=60.0)
|
| 2105 |
+
log_activity(f"[Idle Worker] Space 3 response: {res.get('status')} — {str(res.get('summary', ''))[:120]}")
|
| 2106 |
|
| 2107 |
async def run_eternity_loop():
|
| 2108 |
+
global _last_idle_run
|
| 2109 |
+
log_activity("[Eternity Loop] Async orchestration task started on FastAPI main loop.")
|
| 2110 |
loop_counter = 0
|
| 2111 |
while True:
|
| 2112 |
try:
|
| 2113 |
if not db_pool:
|
| 2114 |
await asyncio.sleep(10)
|
| 2115 |
continue
|
| 2116 |
+
|
| 2117 |
projects = await get_active_projects()
|
| 2118 |
+
|
| 2119 |
if not projects:
|
| 2120 |
+
# --- Idle Worker Protocol ---
|
| 2121 |
+
now_ts = time.time()
|
| 2122 |
+
if now_ts - _last_idle_run >= IDLE_TASK_INTERVAL:
|
| 2123 |
+
_last_idle_run = now_ts
|
| 2124 |
+
await execute_idle_worker_cycle()
|
| 2125 |
+
await asyncio.sleep(60)
|
| 2126 |
continue
|
| 2127 |
+
|
| 2128 |
loop_counter += 1
|
| 2129 |
for p in projects:
|
| 2130 |
name = p["project_name"]
|
|
|
|
| 2132 |
deadline = p["deadline"]
|
| 2133 |
current_mode = p["current_mode"]
|
| 2134 |
priority = p["priority"]
|
| 2135 |
+
|
| 2136 |
+
# Priority gating: "low" skips 3 of every 4 cycles to conserve RPM
|
| 2137 |
if priority == "low" and (loop_counter % 4) != 0:
|
| 2138 |
+
log_activity(f"[Eternity Loop] Skipping low-priority '{name}' this cycle ({loop_counter})")
|
| 2139 |
continue
|
| 2140 |
+
|
| 2141 |
now = datetime.now(timezone.utc)
|
| 2142 |
if current_mode == "build" and now >= deadline:
|
| 2143 |
await update_db_mode(name, "eternity")
|
| 2144 |
current_mode = "eternity"
|
| 2145 |
+
log_activity(f"[Eternity Loop] Project '{name}' deadline passed — transitioned to Eternity R&D Mode.")
|
| 2146 |
+
|
| 2147 |
if current_mode == "build":
|
| 2148 |
await execute_build_cycle(name, goal)
|
| 2149 |
else:
|
|
|
|
| 2150 |
last_run = last_run_times.get(name, 0.0)
|
| 2151 |
now_ts = time.time()
|
| 2152 |
interval = int(os.environ.get("ETERNITY_LOOP_INTERVAL", "3600"))
|
| 2153 |
if now_ts - last_run < interval:
|
| 2154 |
+
remaining_min = int((interval - (now_ts - last_run)) / 60)
|
| 2155 |
+
log_activity(f"[Eternity Loop] Project '{name}' sleeping — next R&D in {remaining_min}m")
|
| 2156 |
continue
|
| 2157 |
last_run_times[name] = now_ts
|
|
|
|
| 2158 |
await execute_eternity_cycle(name, goal)
|
| 2159 |
+
|
| 2160 |
+
# Base check interval: 5 minutes
|
| 2161 |
await asyncio.sleep(300)
|
| 2162 |
+
|
| 2163 |
except Exception as e:
|
| 2164 |
+
log_activity(f"[Eternity Loop Error] Unhandled exception: {e}")
|
| 2165 |
await asyncio.sleep(60)
|
| 2166 |
|
| 2167 |
|
|
|
|
| 2170 |
return HTMLResponse(content=DASHBOARD_HTML)
|
| 2171 |
|
| 2172 |
|
| 2173 |
+
# ---------------------------------------------------------------------------
|
| 2174 |
+
# Vault Push Endpoint (Space 5 co-hosted here on Space 3)
|
| 2175 |
+
# Space 2 (Cerebrum) calls this after every successful build/eternity cycle.
|
| 2176 |
+
# It zips /tmp/workspace and logs the backup event.
|
| 2177 |
+
# A real git push would require SSH credentials injected via HF secrets.
|
| 2178 |
+
# ---------------------------------------------------------------------------
|
| 2179 |
+
class VaultPushRequest(BaseModel):
|
| 2180 |
+
project_name: Optional[str] = "unknown"
|
| 2181 |
+
summary: Optional[str] = ""
|
| 2182 |
+
|
| 2183 |
+
@app.post("/api/vault/push")
|
| 2184 |
+
async def vault_push(req: VaultPushRequest, authorization: str = Header(None)):
|
| 2185 |
+
auth(authorization)
|
| 2186 |
+
workspace = "/tmp/workspace"
|
| 2187 |
+
archive = "/tmp/vault_snapshot.zip"
|
| 2188 |
+
try:
|
| 2189 |
+
if os.path.exists(workspace) and os.listdir(workspace):
|
| 2190 |
+
proc = await asyncio.create_subprocess_exec(
|
| 2191 |
+
"zip", "-r", archive, workspace,
|
| 2192 |
+
stdout=asyncio.subprocess.DEVNULL,
|
| 2193 |
+
stderr=asyncio.subprocess.DEVNULL
|
| 2194 |
+
)
|
| 2195 |
+
await asyncio.wait_for(proc.communicate(), timeout=30)
|
| 2196 |
+
size_kb = int(os.path.getsize(archive) / 1024) if os.path.exists(archive) else 0
|
| 2197 |
+
log_activity(f"[Vault] Snapshot created for '{req.project_name}': {size_kb}KB | {req.summary[:80]}")
|
| 2198 |
+
return {"status": "success", "archive_kb": size_kb, "project_name": req.project_name}
|
| 2199 |
+
else:
|
| 2200 |
+
log_activity(f"[Vault] Workspace empty — no snapshot for '{req.project_name}'")
|
| 2201 |
+
return {"status": "skipped", "reason": "workspace empty"}
|
| 2202 |
+
except Exception as e:
|
| 2203 |
+
log_activity(f"[Vault Error] Snapshot failed for '{req.project_name}': {e}")
|
| 2204 |
+
return {"status": "error", "error": str(e)}
|
| 2205 |
+
|
| 2206 |
+
|
| 2207 |
+
|
| 2208 |
+
|
| 2209 |
@app.get("/api/logs")
|
| 2210 |
async def get_logs():
|
| 2211 |
return list(activity_logs)
|
requirements.txt
CHANGED
|
@@ -4,4 +4,5 @@ openai==1.86.0
|
|
| 4 |
asyncpg==0.30.0
|
| 5 |
anyio==4.9.0
|
| 6 |
psutil==5.9.8
|
|
|
|
| 7 |
|
|
|
|
| 4 |
asyncpg==0.30.0
|
| 5 |
anyio==4.9.0
|
| 6 |
psutil==5.9.8
|
| 7 |
+
httpx==0.27.2
|
| 8 |
|