diff --git a/agent-node/Dockerfile.binary b/agent-node/Dockerfile.binary new file mode 100644 index 0000000..b10fefa --- /dev/null +++ b/agent-node/Dockerfile.binary @@ -0,0 +1,40 @@ +# Dockerfile.binary +FROM python:3.11-slim + +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# Install building dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + binutils \ + patchelf \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /build + +COPY agent-node/requirements.txt . +RUN pip install --no-cache-dir pyinstaller -r requirements.txt + +# Copy skills manually so we can bundle them dynamically +COPY skills /skills + +COPY agent-node . + +# PyInstaller bundle the application +# --add-data allows dynamic plugins to map correctly +RUN pyinstaller \ + --name cortex-agent \ + --onefile \ + --clean \ + --strip \ + --hidden-import google \ + --hidden-import grpc \ + --hidden-import psutil \ + --hidden-import yaml \ + --add-data "/skills:skills" \ + src/agent_node/main.py + +# Export stage +FROM scratch +COPY --from=0 /build/dist/cortex-agent /cortex-agent diff --git a/agent-node/scripts/build_binaries.sh b/agent-node/scripts/build_binaries.sh new file mode 100755 index 0000000..2754e8b --- /dev/null +++ b/agent-node/scripts/build_binaries.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -e + +# This script builds standalone binaries for the Cortex Agent Node +# using Docker Multi-Arch BuildX and PyInstaller. + +echo "🏗️ Setting up builder..." +cd "$(dirname "$0")/../.." + +# Ensure buildx is available +docker buildx create --use --name cortex-builder || true + +echo "🔨 Building Linux AMD64 Binary..." +docker buildx build \ + --platform linux/amd64 \ + --build-arg ARCH=amd64 \ + -f agent-node/Dockerfile.binary \ + --output type=local,dest=agent-node/dist/linux_amd64 \ + . + +echo "🔨 Building Linux ARM64 Binary..." +docker buildx build \ + --platform linux/arm64 \ + --build-arg ARCH=arm64 \ + -f agent-node/Dockerfile.binary \ + --output type=local,dest=agent-node/dist/linux_arm64 \ + . + +echo "✅ Build complete! Binaries are in agent-node/dist/" +chmod +x agent-node/dist/*/cortex-agent || true diff --git a/ai-hub/app/api/routes/agent_update.py b/ai-hub/app/api/routes/agent_update.py index f63c1db..67648e9 100644 --- a/ai-hub/app/api/routes/agent_update.py +++ b/ai-hub/app/api/routes/agent_update.py @@ -174,4 +174,22 @@ headers={"Content-Disposition": "attachment; filename=bootstrap_installer.py"} ) + @router.get("/binary/{arch}", summary="Download Standalone Binary") + def download_binary(arch: str): + """ + Serves the All-in-One compiled binary for linux_amd64, linux_arm64, or darwin. + """ + binary_path = os.path.join(_AGENT_NODE_DIR, "dist", arch, "cortex-agent") + if not os.path.exists(binary_path): + raise HTTPException(status_code=404, detail=f"Binary for architecture {arch} not found on hub.") + + with open(binary_path, "rb") as f: + content = f.read() + + return Response( + content=content, + media_type="application/octet-stream", + headers={"Content-Disposition": f"attachment; filename=cortex-agent-{arch}"} + ) + return router diff --git a/ai-hub/app/api/routes/nodes.py b/ai-hub/app/api/routes/nodes.py index 9923523..a98b2a9 100644 --- a/ai-hub/app/api/routes/nodes.py +++ b/ai-hub/app/api/routes/nodes.py @@ -423,11 +423,11 @@ node = _get_node_or_404(node_id, db) config_yaml = _generate_node_config_yaml(node) return schemas.NodeConfigYamlResponse(node_id=node_id, config_yaml=config_yaml) - @router.get("/provision/{node_id}", summary="Headless Provisioning Script") + @router.get("/provision/{node_id}", summary="Headless Provisioning Script (Python)") def provision_node(node_id: str, token: str, request: Request, db: Session = Depends(get_db)): """ Returns a Python script that can be piped into python3 to automatically - install and start the agent node. + install and start the python-source agent node. Usage: curl -sSL https://.../provision/{node_id}?token={token} | python3 """ @@ -442,6 +442,24 @@ script = services.mesh_service.generate_provisioning_script(node, config_yaml, base_url) return PlainTextResponse(script) + @router.get("/provision/sh/{node_id}", summary="Headless Provisioning Script (Bash Binary)") + def provision_node_sh(node_id: str, token: str, request: Request, db: Session = Depends(get_db)): + """ + Returns a Bash script that curls and executes the compiled standalone binary. + + Usage: curl -sSL https://.../provision/sh/{node_id}?token={token} | bash + """ + from fastapi.responses import PlainTextResponse + node = db.query(models.AgentNode).filter(models.AgentNode.node_id == node_id).first() + if not node or node.invite_token != token: + raise HTTPException(status_code=403, detail="Invalid node or token.") + + config_yaml = _generate_node_config_yaml(node) + base_url = f"{request.url.scheme}://{request.url.netloc}" + + script = services.mesh_service.generate_provisioning_sh(node, config_yaml, base_url) + return PlainTextResponse(script) + @router.get("/admin/{node_id}/download", summary="[Admin] Download Agent Node Bundle (ZIP)") def admin_download_bundle( node_id: str, diff --git a/ai-hub/app/core/services/mesh.py b/ai-hub/app/core/services/mesh.py index 939f887..0607849 100644 --- a/ai-hub/app/core/services/mesh.py +++ b/ai-hub/app/core/services/mesh.py @@ -80,6 +80,21 @@ logger.error(f"Failed to generate provisioning script: {e}") return f"Error: {e}" + def generate_provisioning_sh(self, node: models.AgentNode, config_yaml: str, base_url: str) -> str: + if not self.jinja_env: + return "Error: Templates directory not found." + try: + template = self.jinja_env.get_template("provision.sh.j2") + return template.render( + node_id=node.node_id, + config_yaml=config_yaml, + base_url=base_url, + invite_token=node.invite_token + ) + except Exception as e: + logger.error(f"Failed to generate provisioning script: {e}") + return f"Error: {e}" + def get_template_content(self, filename: str) -> str: if not self.jinja_env: return "" diff --git a/ai-hub/app/core/templates/provisioning/provision.sh.j2 b/ai-hub/app/core/templates/provisioning/provision.sh.j2 new file mode 100644 index 0000000..600acf3 --- /dev/null +++ b/ai-hub/app/core/templates/provisioning/provision.sh.j2 @@ -0,0 +1,87 @@ +#!/bin/bash +set -e + +echo "🚀 Starting Cortex Agent Provisioning for node: {{ node_id }}" + +# Determine OS and Arch +OS="$(uname -s | tr '[:upper:]' '[:lower:]')" +ARCH="$(uname -m)" + +if [ "$ARCH" = "x86_64" ]; then + ARCH="amd64" +elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then + ARCH="arm64" +fi + +if [ "$OS" = "darwin" ]; then + # Currently fallback to python method or generic binary if available + # For now assume arm64 if darwin and M1 + if [ "$ARCH" = "amd64" ]; then + BIN_ARCH="darwin_amd64" + else + BIN_ARCH="darwin_arm64" + fi +else + BIN_ARCH="${OS}_${ARCH}" +fi + +# 1. Create .cortex/agent-node directory +INSTALL_DIR="$HOME/.cortex/agent-node" +mkdir -p "$INSTALL_DIR" +cd "$INSTALL_DIR" + +# 2. Write agent_config.yaml +echo "[*] Writing configuration..." +cat > agent_config.yaml << 'EOF' +{{ config_yaml }} +EOF + +# 3. Download standalone binary +echo "[*] Downloading unified binary core for $BIN_ARCH..." +INSTALLER_URL="{{ base_url }}/api/v1/agent/binary/$BIN_ARCH" +if ! curl -sSLf "$INSTALLER_URL" -o cortex-agent; then + echo "❌ Failed to download binary for architecture: $BIN_ARCH from $INSTALLER_URL" + echo "Fallback: Try the python headless installer instead: curl -sSL {{ base_url }}/api/v1/nodes/provision/{{ node_id }} | python3" + exit 1 +fi +chmod +x cortex-agent + +# 4. Bootstrap daemon installation +echo "[*] Bootstrapping agent daemon..." +# Run the standalone binary with the install-daemon flags (must implement in main.py) +# Or we can just run it in the background if systemd is unavailable +systemctl --user list-units >/dev/null 2>&1 && HAS_SYSTEMD=true || HAS_SYSTEMD=false + +if [ "$HAS_SYSTEMD" = true ]; then + echo "Installing Linux systemd user service..." + mkdir -p ~/.config/systemd/user + cat > ~/.config/systemd/user/cortex-agent.service << EOF +[Unit] +Description=Cortex Agent Node +After=network.target + +[Service] +Type=simple +ExecStart=$INSTALL_DIR/cortex-agent +WorkingDirectory=$INSTALL_DIR +Restart=always +RestartSec=5 +StandardOutput=append:$HOME/.cortex/agent.out.log +StandardError=append:$HOME/.cortex/agent.err.log + +[Install] +WantedBy=default.target +EOF + systemctl --user daemon-reload + systemctl --user enable cortex-agent + systemctl --user restart cortex-agent + loginctl enable-linger "$USER" 2>/dev/null || true + echo "✅ Systemd Daemon successfully started!" +else + echo "Systemd not available. Falling back to background loop (nohup)..." + nohup ./cortex-agent >> "$HOME/.cortex/agent.out.log" 2>&1 & + echo $! > agent.pid + echo "✅ Background Agent started (PID $!)" +fi + +echo "✅ Provisioning complete! Node should be online in the Mesh Dashboard shortly." diff --git a/ai-hub/native_hub.log b/ai-hub/native_hub.log index 0b59be4..e1c1eb1 100644 --- a/ai-hub/native_hub.log +++ b/ai-hub/native_hub.log @@ -712,3 +712,235 @@ No nodes currently connected. ================================================== + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + +ERROR:app.core.orchestration.scheduler:[Scheduler] Zombie Sweeper iteration failed: (sqlite3.OperationalError) no such column: agent_instances.total_runs +[SQL: SELECT agent_instances.id AS agent_instances_id, agent_instances.template_id AS agent_instances_template_id, agent_instances.session_id AS agent_instances_session_id, agent_instances.mesh_node_id AS agent_instances_mesh_node_id, agent_instances.status AS agent_instances_status, agent_instances.current_workspace_jail AS agent_instances_current_workspace_jail, agent_instances.last_heartbeat AS agent_instances_last_heartbeat, agent_instances.total_runs AS agent_instances_total_runs, agent_instances.successful_runs AS agent_instances_successful_runs, agent_instances.total_tokens_accumulated AS agent_instances_total_tokens_accumulated, agent_instances.total_input_tokens AS agent_instances_total_input_tokens, agent_instances.total_output_tokens AS agent_instances_total_output_tokens, agent_instances.total_running_time_seconds AS agent_instances_total_running_time_seconds, agent_instances.tool_call_counts AS agent_instances_tool_call_counts +FROM agent_instances +WHERE agent_instances.status = ? AND agent_instances.last_heartbeat < ?] +[parameters: ('active', '2026-03-24 05:04:40.229064')] +(Background on this error at: https://sqlalche.me/e/20/e3q8) + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + +ERROR:app.core.orchestration.scheduler:[Scheduler] Zombie Sweeper iteration failed: (sqlite3.OperationalError) no such column: agent_instances.total_runs +[SQL: SELECT agent_instances.id AS agent_instances_id, agent_instances.template_id AS agent_instances_template_id, agent_instances.session_id AS agent_instances_session_id, agent_instances.mesh_node_id AS agent_instances_mesh_node_id, agent_instances.status AS agent_instances_status, agent_instances.current_workspace_jail AS agent_instances_current_workspace_jail, agent_instances.last_heartbeat AS agent_instances_last_heartbeat, agent_instances.total_runs AS agent_instances_total_runs, agent_instances.successful_runs AS agent_instances_successful_runs, agent_instances.total_tokens_accumulated AS agent_instances_total_tokens_accumulated, agent_instances.total_input_tokens AS agent_instances_total_input_tokens, agent_instances.total_output_tokens AS agent_instances_total_output_tokens, agent_instances.total_running_time_seconds AS agent_instances_total_running_time_seconds, agent_instances.tool_call_counts AS agent_instances_tool_call_counts +FROM agent_instances +WHERE agent_instances.status = ? AND agent_instances.last_heartbeat < ?] +[parameters: ('active', '2026-03-24 05:05:40.247844')] +(Background on this error at: https://sqlalche.me/e/20/e3q8) + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + +ERROR:app.core.orchestration.scheduler:[Scheduler] Zombie Sweeper iteration failed: (sqlite3.OperationalError) no such column: agent_instances.total_runs +[SQL: SELECT agent_instances.id AS agent_instances_id, agent_instances.template_id AS agent_instances_template_id, agent_instances.session_id AS agent_instances_session_id, agent_instances.mesh_node_id AS agent_instances_mesh_node_id, agent_instances.status AS agent_instances_status, agent_instances.current_workspace_jail AS agent_instances_current_workspace_jail, agent_instances.last_heartbeat AS agent_instances_last_heartbeat, agent_instances.total_runs AS agent_instances_total_runs, agent_instances.successful_runs AS agent_instances_successful_runs, agent_instances.total_tokens_accumulated AS agent_instances_total_tokens_accumulated, agent_instances.total_input_tokens AS agent_instances_total_input_tokens, agent_instances.total_output_tokens AS agent_instances_total_output_tokens, agent_instances.total_running_time_seconds AS agent_instances_total_running_time_seconds, agent_instances.tool_call_counts AS agent_instances_tool_call_counts +FROM agent_instances +WHERE agent_instances.status = ? AND agent_instances.last_heartbeat < ?] +[parameters: ('active', '2026-03-24 05:06:40.264966')] +(Background on this error at: https://sqlalche.me/e/20/e3q8) + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + +ERROR:app.core.orchestration.scheduler:[Scheduler] Zombie Sweeper iteration failed: (sqlite3.OperationalError) no such column: agent_instances.total_runs +[SQL: SELECT agent_instances.id AS agent_instances_id, agent_instances.template_id AS agent_instances_template_id, agent_instances.session_id AS agent_instances_session_id, agent_instances.mesh_node_id AS agent_instances_mesh_node_id, agent_instances.status AS agent_instances_status, agent_instances.current_workspace_jail AS agent_instances_current_workspace_jail, agent_instances.last_heartbeat AS agent_instances_last_heartbeat, agent_instances.total_runs AS agent_instances_total_runs, agent_instances.successful_runs AS agent_instances_successful_runs, agent_instances.total_tokens_accumulated AS agent_instances_total_tokens_accumulated, agent_instances.total_input_tokens AS agent_instances_total_input_tokens, agent_instances.total_output_tokens AS agent_instances_total_output_tokens, agent_instances.total_running_time_seconds AS agent_instances_total_running_time_seconds, agent_instances.tool_call_counts AS agent_instances_tool_call_counts +FROM agent_instances +WHERE agent_instances.status = ? AND agent_instances.last_heartbeat < ?] +[parameters: ('active', '2026-03-24 05:07:40.273954')] +(Background on this error at: https://sqlalche.me/e/20/e3q8) + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + +[📁⚠️] Mirror Cleanup Thread Error: (sqlite3.OperationalError) no such column: sessions.auto_clear_history +[SQL: SELECT sessions.id AS sessions_id, sessions.user_id AS sessions_user_id, sessions.title AS sessions_title, sessions.provider_name AS sessions_provider_name, sessions.stt_provider_name AS sessions_stt_provider_name, sessions.tts_provider_name AS sessions_tts_provider_name, sessions.feature_name AS sessions_feature_name, sessions.created_at AS sessions_created_at, sessions.is_archived AS sessions_is_archived, sessions.is_cancelled AS sessions_is_cancelled, sessions.sync_workspace_id AS sessions_sync_workspace_id, sessions.attached_node_ids AS sessions_attached_node_ids, sessions.node_sync_status AS sessions_node_sync_status, sessions.sync_config AS sessions_sync_config, sessions.restrict_skills AS sessions_restrict_skills, sessions.allowed_skill_names AS sessions_allowed_skill_names, sessions.system_prompt_override AS sessions_system_prompt_override, sessions.is_locked AS sessions_is_locked, sessions.auto_clear_history AS sessions_auto_clear_history +FROM sessions +WHERE sessions.is_archived = 0 AND sessions.sync_workspace_id IS NOT NULL] +(Background on this error at: https://sqlalche.me/e/20/e3q8) + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + +ERROR:app.core.orchestration.scheduler:[Scheduler] Zombie Sweeper iteration failed: (sqlite3.OperationalError) no such column: agent_instances.total_runs +[SQL: SELECT agent_instances.id AS agent_instances_id, agent_instances.template_id AS agent_instances_template_id, agent_instances.session_id AS agent_instances_session_id, agent_instances.mesh_node_id AS agent_instances_mesh_node_id, agent_instances.status AS agent_instances_status, agent_instances.current_workspace_jail AS agent_instances_current_workspace_jail, agent_instances.last_heartbeat AS agent_instances_last_heartbeat, agent_instances.total_runs AS agent_instances_total_runs, agent_instances.successful_runs AS agent_instances_successful_runs, agent_instances.total_tokens_accumulated AS agent_instances_total_tokens_accumulated, agent_instances.total_input_tokens AS agent_instances_total_input_tokens, agent_instances.total_output_tokens AS agent_instances_total_output_tokens, agent_instances.total_running_time_seconds AS agent_instances_total_running_time_seconds, agent_instances.tool_call_counts AS agent_instances_tool_call_counts +FROM agent_instances +WHERE agent_instances.status = ? AND agent_instances.last_heartbeat < ?] +[parameters: ('active', '2026-03-24 05:08:40.283905')] +(Background on this error at: https://sqlalche.me/e/20/e3q8) + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== + +05:11:46 - LiteLLM:INFO: utils.py:3895 - +LiteLLM completion() model= gemini-3-flash-preview; provider = gemini +INFO:LiteLLM: +LiteLLM completion() model= gemini-3-flash-preview; provider = gemini +05:11:47 - LiteLLM:INFO: utils.py:3895 - +LiteLLM completion() model= deepseek-chat; provider = deepseek +INFO:LiteLLM: +LiteLLM completion() model= deepseek-chat; provider = deepseek + +================================================== +📡 CORTEX MESH DASHBOARD | 0 Nodes Online +-------------------------------------------------- + No nodes currently connected. +================================================== +