Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,7 @@ checkpoints/
*.swp
*.swo
.DS_Store
Thumbs.db
Thumbs.db
# Logs
logs/
*.log
82 changes: 82 additions & 0 deletions scripts/safe_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
AetherForge Safe Agent Client
=============================
Demonstrates an external AI agent interacting with the AetherForge API.
The agent discovers the VRAM optimization tool, attempts a strategy swap
based on workload, monitors hardware health, and handles 503 thermal locks.
"""

import time
import requests
import json
import os

API_BASE = os.getenv("AETHER_API_BASE", "http://127.0.0.1:8000")

def print_header(title: str):
print(f"\n{'='*50}\n[AGENT] {title}\n{'='*50}")

def agent_loop():
print_header("BOOT SEQUENCE & TOOL DISCOVERY")

try:
print("-> Querying /system/tools for hypervisor capabilities...")
tool_response = requests.get(f"{API_BASE}/system/tools")
tool_schema = tool_response.json()
print(f"-> Discovered capability: {tool_schema['function']['name']}")
except requests.exceptions.ConnectionError:
print("FATAL: AetherForge Control Plane is offline. Exiting.")
return

print_header("PRE-FLIGHT HARDWARE CHECK")
metrics = requests.get(f"{API_BASE}/system/metrics").json()
print(f"Engine State: {metrics['engine_state'].upper()} | Thermal Lock: {'ACTIVE' if metrics['thermal_lock_active'] else 'CLEAR'}")

# 503 Resilience Loop
while metrics["thermal_lock_active"]:
print("-> WATCHDOG ACTIVE: GPU is cooling. Agent enters 5s standby mode...")
time.sleep(5)
metrics = requests.get(f"{API_BASE}/system/metrics").json()
print("-> Hardware cleared for execution.")

print_header("STRATEGY NEGOTIATION (TOOL USAGE)")
print("-> Agent Task: Generate a massive 2000-token structural report.")
print("-> Agent Action: Requesting 'high_fidelity' VRAM allocation via tool schema.")

strategy_payload = {
"mode": "high_fidelity",
"estimated_context_tokens": 150,
"expected_output_tokens": 2000
}

strat_response = requests.post(f"{API_BASE}/system/strategy", json=strategy_payload)
if strat_response.status_code == 200:
strat_data = strat_response.json()
if strat_data["status"] == "strategy_applied":
print(f"-> Gatekeeper ACCEPTED swap. Active mode is now: {strat_data['active_mode']}")
else:
print(f"-> Gatekeeper REJECTED swap: {strat_data.get('reason')}. Proceeding with: {strat_data['active_mode']}")
elif strat_response.status_code == 503:
print("-> 503 REJECTED: Thermal limit breached during negotiation. Aborting.")
return

print_header("EXECUTING WORKLOAD")
gen_payload = {
"prompt": "Analyze the structural integrity of the bridge.",
"max_tokens": 2000
}

print(f"-> Submitting generation payload...")
gen_response = requests.post(f"{API_BASE}/generate", json=gen_payload)

if gen_response.status_code == 200:
data = gen_response.json()
print(f"-> SUCCESS: Generated {data['metrics']['tokens_generated']} tokens at {data['metrics']['tokens_per_second']:.2f} TPS.")
elif gen_response.status_code == 413:
print(f"-> 413 REJECTED: Context violates hardware safety limits.")
print("-> Agent Recovery: Would summarize context and retry.")
else:
print(f"-> ERROR {gen_response.status_code}: {gen_response.text}")

if __name__ == "__main__":
agent_loop()
7 changes: 6 additions & 1 deletion src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import yaml
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field
from typing import Dict, Any, Optional
from typing import Dict, Any, Optional, Literal

class AetherSettings(BaseSettings):
# Hardware & Model Configuration
Expand Down Expand Up @@ -49,6 +49,10 @@ class AetherSettings(BaseSettings):
# Server Configuration
api_host: str = Field(default="127.0.0.1")
api_port: int = Field(default=8000, gt=0, le=65535)
aether_engine: Literal["auto", "mock", "llama", "ktransformers"] = Field(
default="auto",
description="Force 'mock', 'llama', or 'ktransformers'. 'auto' detects based on model presence."
)

model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")

Expand Down Expand Up @@ -84,6 +88,7 @@ def load_from_yaml(cls, yaml_path: str = "config.yaml") -> "AetherSettings":
if "server" in raw_yaml:
flat_data["api_host"] = raw_yaml["server"].get("host")
flat_data["api_port"] = raw_yaml["server"].get("port")
flat_data["aether_engine"] = raw_yaml["server"].get("engine")

# Strip out None values to allow fields to fall back to Pydantic defaults
cleaned_data = {k: v for k, v in flat_data.items() if v is not None}
Expand Down
2 changes: 1 addition & 1 deletion src/engines/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def apply_strategy(self, mode: str) -> Dict[str, Any]:
pass

@abstractmethod
def generate(self, prompt: str, max_tokens: int = 100) -> Dict[str, Any]:
def generate(self, prompt: str, max_tokens: int = 100, temperature: float = 0.7) -> Dict[str, Any]:
"""
Must yield text and execution telemetry.
Expected shape: {"text": str, "metrics": {"tokens_generated": int, "time_seconds": float, "tokens_per_second": float}}
Expand Down
30 changes: 17 additions & 13 deletions src/engines/kt_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
Supports heterogeneous expert scheduling for RTX 4060.
"""

from src.engines.base import BaseAetherEngine
import os
from typing import Dict, Any
from src.engines.base import BaseAetherEngine

class KTransformersEngine(BaseAetherEngine):
"""Engine adapter for ktransformers backend."""
Expand All @@ -20,23 +21,26 @@ def __init__(self, model_path: str, vram_budget_mb: int, n_ctx: int):
if os.getenv("ENABLE_KTRANSFORMERS") == "true":
try:
# Lazy import to protect main path
# TODO: Replace with actual kt-kernel import after verification
pass
except ImportError as e:
raise RuntimeError(f"ktransformers not available: {e}") from e

def apply_strategy(self, mode: str):
def count_tokens(self, text: str) -> int:
"""Token count for Gatekeeper calculations."""
return max(1, len(text) // 4)

def apply_strategy(self, mode: str) -> Dict[str, Any]:
"""Map strategy to KT expert placement."""
self.current_strategy = mode
# TODO: Implement hot/cold logic for 4060
return {"success": True, "metrics": {"reload_seconds": 4.5}}
return {"success": True, "metrics": {"extract_seconds": 0.0, "reload_seconds": 4.5, "inject_seconds": 0.0}}

def generate(self, prompt: str, max_tokens: int):
def generate(self, prompt: str, max_tokens: int = 100, temperature: float = 0.7) -> Dict[str, Any]:
"""Proxy generation to KT backend."""
# TODO: Full implementation
return {"text": "[KT placeholder output]", "metrics": {"tokens_per_second": 12.0}}

def count_tokens(self, text: str) -> int:
"""Token count for Gatekeeper calculations."""
# Simple approximation until KT tokenizer hook
return max(1, len(text) // 4)
return {
"text": "[KT placeholder output]",
"metrics": {
"tokens_generated": max_tokens,
"time_seconds": max_tokens / 12.0,
"tokens_per_second": 12.0
}
}
Loading
Loading