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
2 changes: 0 additions & 2 deletions src/auto_k8s_pilot/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@
version=SETTINGS.APP_VERSION,
)

# Setup OpenTelemetry tracing
setup_tracing(app)

# Initialize
crew_instance = AutoK8sPilot()
flow_runner = FlowRunner(crew_instance, CONFIG_DIR)

Expand Down
18 changes: 9 additions & 9 deletions src/auto_k8s_pilot/config/tasks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ cluster_summary:
status, and explicit unknowns. No fabricated or assumed components.
agent: infra_architect

# --- ArgoCD / GitOps ----------------------------------------------------
# Argo CD and GitOps
argocd_list_apps:
description: >
Use argocd_tool with op="list_apps".
Expand All @@ -64,7 +64,7 @@ argocd_app_status_chat_api:
inputs:
app: chat-api

# ⚠️ Mutating (disabled unless ALLOW_MUTATING=true)
# Mutating; disabled unless ALLOW_MUTATING=true.
argocd_sync_chat_api:
description: >
If and only if ALLOW_MUTATING=true, call argocd_tool with op="app_sync", app="chat-api".
Expand All @@ -75,7 +75,7 @@ argocd_sync_chat_api:
inputs:
app: chat-api

# --- Loki / Observability ------------------------------------------------
# Loki and observability
loki_recent_errors_chat_api:
description: >
Use loki_query to fetch last 30 minutes for {app="chat-api"} with "|= \"ERROR\"" and limit=200.
Expand All @@ -100,7 +100,7 @@ loki_http_activity_chat_api:
minutes: 30
limit: 200

# --- K8s resource pressure ----------------------------------------------
# Kubernetes resource pressure
k8s_top_nodes:
description: >
Call kubectl_tool exactly once with action="top", kind="nodes" to fetch
Expand Down Expand Up @@ -134,7 +134,7 @@ k8s_events_recent:
namespace: all
kind: events

# --- Cloudflare --------------------------------------------------------------
# Cloudflare
dns_check_records:
description: >
Use cloudflare_dns_tool with op="list" to fetch DNS records for the configured zone.
Expand All @@ -153,7 +153,7 @@ dns_get_record_api:
inputs:
name: "api"

# ⚠️ Mutating guarded by ALLOW_MUTATING
# Mutating; guarded by ALLOW_MUTATING.
dns_upsert_record_api:
description: >
If ALLOW_MUTATING=true, use cloudflare_dns_tool with op="upsert" to ensure an A/AAAA/CNAME for API endpoint.
Expand All @@ -163,7 +163,7 @@ dns_upsert_record_api:
"UPSERT OK: <id>" or safety ERROR.
agent: cloudflare_admin

# --- OpenRouter / LLM Gateway ------------------------------------------------
# OpenRouter and LLM gateway
llm_gateway_health:
description: >
Use openrouter_health_tool with op="models" to verify gateway and list a short preview.
Expand All @@ -172,7 +172,7 @@ llm_gateway_health:
"Models:<count>\nPreview:\n<up to 10 lines>" or explicit ERROR.
agent: llm_gateway_observer

# --- MCP-K8s bridge ----------------------------------------------------------
# MCP Kubernetes bridge
mcp_k8s_env_check:
description: >
Validate env for mcp-server-kubernetes connectivity and produce a client config snippet.
Expand All @@ -181,7 +181,7 @@ mcp_k8s_env_check:
"OK: <details>\nSnippet:\n<json/yaml>" or explicit ERROR with missing keys.
agent: mcp_bridge

# --- Incident issue auto-file (GitHub) ----------------------------------
# GitHub incident filing
incident_create_issue_if_needed:
description: >
If ALLOW_MUTATING=true and previous steps (Loki/Argo/K8s) indicate problems
Expand Down
9 changes: 2 additions & 7 deletions src/auto_k8s_pilot/flow_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,18 @@ def run_flow(self, flow_name: str, inputs: dict) -> dict:
flow = self.load_flow(flow_name)
steps_config = flow.get("steps", [])

# Load behavior.yaml for task -> agent mapping
behavior = self._load_behavior()
task_agent_map = {t["id"]: t["agent"] for t in behavior.get("tasks", [])}

results: List[Dict[str, Any]] = []
context: Dict[str, str] = {} # accumulated context from previous steps
context: Dict[str, str] = {}
total_start = time.time()

for step in steps_config:
task_id = step.get("run")
if not task_id:
continue

# Check if task exists
task_method = self.tasks_map.get(task_id)
if not task_method:
enrich_context(
Expand Down Expand Up @@ -155,7 +153,7 @@ def run_flow(self, flow_name: str, inputs: dict) -> dict:
if deterministic is not None:
output, evidence_scope = deterministic
else:
task = task_method() # Get CrewAI Task object
task = task_method()
task_context = yaml.safe_dump(
{**inputs, **context},
allow_unicode=True,
Expand Down Expand Up @@ -183,14 +181,12 @@ def run_flow(self, flow_name: str, inputs: dict) -> dict:
}
)

# Add to context for next steps
context[task_id] = output
if evidence_scope:
context[f"{task_id}_scope"] = evidence_scope

total_duration_ms = int((time.time() - total_start) * 1000)

# Determine final answer
final_answer = self._extract_final_answer(results)
metadata = self._extract_metadata(results)
metadata["total_duration_ms"] = total_duration_ms
Expand Down Expand Up @@ -373,7 +369,6 @@ def _extract_metadata(self, results: List[Dict[str, Any]]) -> dict:
metadata: Dict[str, Any] = {}
for r in results:
if r["task"] == "cluster_summary":
# Extract first 500 chars as summary
metadata["cluster_summary"] = r["output"][:500] if r["output"] else ""
if r["task"] == "incident_create_issue_if_needed":
if "Created issue" in r.get("output", ""):
Expand Down
9 changes: 1 addition & 8 deletions src/auto_k8s_pilot/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,12 @@

warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")

# --- Local runner for the Crew ------------------------------------------------
# This file is just an entrypoint to run your crew locally.
# Keep it simple: only pass runtime inputs if you really need to.
# Otherwise, tasks.yaml already describes what to run.


def run():
"""
Run the crew (sequential process by default).
"""
# Example: you can override inputs here if needed
inputs = {
"namespace": "all", # or "default"
"namespace": "all",
"current_year": str(datetime.now().year),
}

Expand Down
4 changes: 0 additions & 4 deletions src/auto_k8s_pilot/observability/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import structlog
from opentelemetry import trace

# Context variables for additional attributes
log_context: ContextVar[Dict[str, Any]] = ContextVar("log_context", default={})


Expand All @@ -24,7 +23,6 @@ def _add_trace_context(self, event_dict: Dict[str, Any]) -> Dict[str, Any]:
event_dict["trace_id"] = format(span_context.trace_id, "032x")
event_dict["span_id"] = format(span_context.span_id, "016x")

# Add context variables
event_dict.update(log_context.get())

return event_dict
Expand All @@ -51,7 +49,6 @@ def debug(self, msg: str, **kwargs: Any) -> None:
self.logger.debug(msg, **kwargs)


# Initialize structlog
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
Expand All @@ -69,7 +66,6 @@ def debug(self, msg: str, **kwargs: Any) -> None:
cache_logger_on_first_use=True,
)

# Base logger
base_logger = structlog.get_logger()


Expand Down
9 changes: 1 addition & 8 deletions src/auto_k8s_pilot/observability/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,11 @@ def setup_tracing(app: Any) -> None:
logging.getLogger(__name__).info("OpenTelemetry export disabled")
return

# Resource with service and Kubernetes metadata
resource = Resource(
attributes={
"service.name": SETTINGS.OTEL_SERVICE_NAME,
"service.version": SETTINGS.APP_VERSION,
"deployment.environment": SETTINGS.ENVIRONMENT,
# Kubernetes metadata
"k8s.pod.name": SETTINGS.K8S_POD_NAME or "unknown",
"k8s.namespace": SETTINGS.K8S_NAMESPACE or "default",
"k8s.node.name": SETTINGS.K8S_NODE_NAME or "unknown",
Expand All @@ -55,14 +53,13 @@ def setup_tracing(app: Any) -> None:
}
)

# For HTTP protocol add correct paths
# OTLP/HTTP exporters require a signal-specific path.
traces_endpoint = (
f"{otlp_endpoint}/v1/traces"
if not otlp_endpoint.endswith("/v1/traces")
else otlp_endpoint
)

# === TRACES ===
provider = TracerProvider(resource=resource)

headers = None
Expand All @@ -83,11 +80,9 @@ def setup_tracing(app: Any) -> None:
provider.add_span_processor(trace_processor)
trace.set_tracer_provider(provider)

# Configure root logger
root_logger = logging.getLogger()
root_logger.setLevel(getattr(logging, SETTINGS.LOG_LEVEL.upper()))

# Console handler
if SETTINGS.ENVIRONMENT == "production":
import json

Expand Down Expand Up @@ -118,14 +113,12 @@ def format(self, record: logging.LogRecord) -> str:
)
root_logger.addHandler(console_handler)

# FastAPI instrumentation
FastAPIInstrumentor().instrument_app(
app,
tracer_provider=provider,
server_request_hook=server_request_hook,
)

# Log successful initialization
logger = logging.getLogger(__name__)
logger.info(
"OpenTelemetry initialized",
Expand Down
8 changes: 3 additions & 5 deletions src/auto_k8s_pilot/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ class Settings(BaseSettings):
# OpenRouter (LLM Gateway)
OPENROUTER_BASE_URL: str = "https://openrouter.ai/api/v1"
OPENROUTER_API_KEY: Optional[str] = None
OPENROUTER_SITE_URL: Optional[str] = None # optional Referer
OPENROUTER_APP_NAME: Optional[str] = None # optional X-Title
OPENROUTER_SITE_URL: Optional[str] = None
OPENROUTER_APP_NAME: Optional[str] = None

# MCP Kubernetes server
MCP_K8S_SERVER_URL: Optional[str] = None # ws:// or wss:// if applicable
MCP_K8S_SERVER_URL: Optional[str] = None
MCP_K8S_API_KEY: Optional[str] = None
MCP_K8S_INSECURE: bool = False
# Optional stdio command, for example "mcp-server-kubernetes".
Expand Down Expand Up @@ -71,10 +71,8 @@ class Settings(BaseSettings):
# Flow
DEFAULT_FLOW: str = "k8s-healthcheck"

# Singleton
SETTINGS = Settings()

# Paths
BASE_DIR = Path(__file__).parent
CONFIG_DIR = BASE_DIR / "config"
LAYERS_DIR = CONFIG_DIR / "layers"
Expand Down
Loading