> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orcapods.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Server health check

> An agent that diagnoses a slow or full machine by asking your code to run a fixed set of read-only checks.

This agent answers questions like "why is the web box slow?" or "what is filling the disk?". It does not log in to your machine. It asks your program to run checks from a short, read-only list, reads the output, and explains what it found. Your program decides what runs, on which host.

**You will use:** [function tools](/guides/function-tools) and [streaming](/guides/streaming). No sandbox, so no machine time.

<Note>
  This is the safe way to point an agent at your own servers on hosted Orca today. [Self-hosted environments](/guides/environments#self-hosted-environment), where the agent gets a shell on your machine, are coming soon, and give the agent far more power than a list of checks.
</Note>

## How it works

One tool, `run_check`, takes the **name** of a check, not a command. The model can only pick from names your code knows.

## The program

Examples on this page assume the settings from the [shared setup](/recipes/overview#shared-setup). It runs each check over `ssh` on the host you name. Use a dedicated low-privilege account for it.

<Tabs>
  <Tab title="Python">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
    """Diagnose a server with a fixed list of read-only checks."""

    import os
    import subprocess
    import sys

    import openai

    client = openai.OpenAI(api_key=os.environ["ORCA_API_KEY"], base_url=os.environ["ORCA_BASE_URL"])
    agents = client.beta.agents
    sessions = agents.sessions

    HOST = sys.argv[1] if len(sys.argv) > 1 else "web-1"
    QUESTION = sys.argv[2] if len(sys.argv) > 2 else "The site is slow. What is going on?"

    # The only commands that can ever run. The model picks a name, never a command.
    CHECKS = {
        "uptime": "uptime",
        "memory": "free -m",
        "disk": "df -h -x tmpfs -x devtmpfs",
        "top_cpu": "ps -eo pid,user,%cpu,%mem,etime,comm --sort=-%cpu | head -15",
        "top_memory": "ps -eo pid,user,%cpu,%mem,etime,comm --sort=-%mem | head -15",
        "listening_ports": "ss -ltnp",
        "failed_services": "systemctl --failed --no-pager",
        "recent_errors": "journalctl -p err --since '-1h' --no-pager | tail -50",
    }

    INSTRUCTIONS = """\
    You diagnose Linux servers. You cannot run commands yourself.
    Call run_check with one of: uptime, memory, disk, top_cpu, top_memory, listening_ports,
    failed_services, recent_errors. Start broad (uptime, memory, disk), then narrow down.
    Base every conclusion on check output; quote the line that supports it.
    If the checks cannot answer the question, say which information is missing.
    Suggest fixes as commands for a person to run, and say which ones are risky,
    such as restarting a service or killing a process. Do not use emojis."""

    RUN_CHECK = {
        "type": "function",
        "name": "run_check",
        "description": "Run one named read-only check on the server and return its output.",
        "parameters": {
            "type": "object",
            "properties": {"check": {"type": "string", "enum": sorted(CHECKS)}},
            "required": ["check"],
            "additionalProperties": False,
        },
        "defer_loading": False,
    }


    def run_check(args):
        command = CHECKS.get(str(args.get("check")))
        if command is None:
            return {"error": f"Unknown check. Use one of: {', '.join(sorted(CHECKS))}"}
        try:
            result = subprocess.run(
                ["ssh", "-o", "BatchMode=yes", HOST, command],
                capture_output=True, text=True, errors="replace", timeout=30,
            )
        except subprocess.TimeoutExpired:
            return {"error": "The check timed out after 30 seconds."}
        except OSError as error:
            return {"error": f"Could not run ssh: {error}"}
        # Cap the output: the model pays for every token it reads.
        return {"exit_code": result.returncode, "output": (result.stdout + result.stderr)[-8000:]}


    agent = session = None
    try:
        agent = agents.create(
            model=os.environ["ORCA_MODEL"], name="server-health", instructions=INSTRUCTIONS, tools=[RUN_CHECK],
        )
        session = sessions.create(agent_id=agent.id, environment={"type": "none"})

        status = None
        with sessions.stream(session.id, input=f"Host {HOST}: {QUESTION}",
                             tool_handlers={"run_check": run_check}) as stream:
            for event in stream:  # read to the end, or the helper stops answering calls
                if event.type == "agent.session.turn.completed":
                    status = event.turn.status
        if status != "completed":
            raise SystemExit(f"Diagnosis did not complete: {status}")
        for item in sessions.items.list(session.id, limit=50).data:
            if item.type == "message" and item.role == "assistant":
                print(" ".join(p.text for p in item.content or [] if getattr(p, "text", None)))
                break
    finally:
        # Delete each resource on its own, so one failed delete does not skip the rest.
        if session:
            try:
                sessions.delete(session.id)
            except openai.APIError:
                pass
        if agent:
            try:
                agents.delete(agent.id)
            except openai.APIError:
                pass
        client.close()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
    /** Diagnose a server with a fixed list of read-only checks. */
    import { spawnSync } from 'node:child_process';
    import OpenAI from 'openai';

    const client = new OpenAI({ apiKey: process.env.ORCA_API_KEY!, baseURL: process.env.ORCA_BASE_URL! });
    const agents = client.beta.agents;
    const sessions = agents.sessions;

    const HOST = process.argv[2] ?? 'web-1';
    const QUESTION = process.argv[3] ?? 'The site is slow. What is going on?';

    // The only commands that can ever run. The model picks a name, never a command.
    const CHECKS: Record<string, string> = {
      uptime: 'uptime',
      memory: 'free -m',
      disk: 'df -h -x tmpfs -x devtmpfs',
      top_cpu: 'ps -eo pid,user,%cpu,%mem,etime,comm --sort=-%cpu | head -15',
      top_memory: 'ps -eo pid,user,%cpu,%mem,etime,comm --sort=-%mem | head -15',
      listening_ports: 'ss -ltnp',
      failed_services: 'systemctl --failed --no-pager',
      recent_errors: "journalctl -p err --since '-1h' --no-pager | tail -50",
    };

    const INSTRUCTIONS = `You diagnose Linux servers. You cannot run commands yourself.
    Call run_check with one of: uptime, memory, disk, top_cpu, top_memory, listening_ports,
    failed_services, recent_errors. Start broad (uptime, memory, disk), then narrow down.
    Base every conclusion on check output; quote the line that supports it.
    If the checks cannot answer the question, say which information is missing.
    Suggest fixes as commands for a person to run, and say which ones are risky,
    such as restarting a service or killing a process. Do not use emojis.`;

    function runCheck(args: Record<string, unknown>) {
      const name = String(args.check);
      if (!Object.hasOwn(CHECKS, name)) return { error: `Unknown check. Use one of: ${Object.keys(CHECKS).sort().join(', ')}` };
      const result = spawnSync('ssh', ['-o', 'BatchMode=yes', HOST, CHECKS[name]], { encoding: 'utf8', timeout: 30_000 });
      if ((result.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT') return { error: 'The check timed out after 30 seconds.' };
      if (result.error) return { error: `Could not run ssh: ${result.error.message}` };
      // Cap the output: the model pays for every token it reads.
      return { exit_code: result.status, output: (result.stdout + result.stderr).slice(-8000) };
    }

    let agent: { id: string } | undefined;
    let session: { id: string } | undefined;
    try {
      agent = await agents.create({
        model: process.env.ORCA_MODEL!,
        name: 'server-health',
        instructions: INSTRUCTIONS,
        tools: [{
          type: 'function',
          name: 'run_check',
          description: 'Run one named read-only check on the server and return its output.',
          parameters: {
            type: 'object',
            properties: { check: { type: 'string', enum: Object.keys(CHECKS).sort() } },
            required: ['check'],
            additionalProperties: false,
          },
          defer_loading: false,
        }],
      });
      session = await sessions.create({ agent_id: agent.id, environment: { type: 'none' } });

      let status: string | undefined;
      const input = `Host ${HOST}: ${QUESTION}`;
      for await (const event of sessions.stream(session.id, { input, toolHandlers: { run_check: runCheck } })) {
        // read to the end, or the helper stops answering calls
        if (event.type === 'agent.session.turn.completed') status = event.turn.status;
      }
      if (status !== 'completed') throw new Error(`Diagnosis did not complete: ${status}`);
      for (const item of (await sessions.items.list(session.id, { limit: 50 })).data as any[]) {
        if (item.type === 'message' && item.role === 'assistant') {
          console.log((item.content ?? []).map((p: any) => p.text).filter(Boolean).join(' '));
          break;
        }
      }
    } catch (error) {
      console.error(error instanceof Error ? error.message : error);
      process.exitCode = 1;
    } finally {
      // Delete each resource on its own, so one failed delete does not skip the rest.
      if (session) await sessions.delete(session.id).catch(() => {});
      if (agent) await agents.delete(agent.id).catch(() => {});
    }
    ```
  </Tab>
</Tabs>

<CodeGroup>
  ```bash Python theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
  python server_health.py web-1 "Disk alerts fired overnight. What is filling it?"
  ```

  ```bash TypeScript theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
  node --experimental-strip-types server_health.ts web-1 "Disk alerts fired overnight. What is filling it?"
  ```
</CodeGroup>

## Make it yours

* **Add checks, not a shell.** Need container status or a service's log? Add a named entry to `CHECKS`. Never add a check that takes free text from the model and puts it in a command.
* **Keep fixes with a person.** The agent suggests commands; it does not run them. If you later add an action such as `restart_service`, require confirmation in your program before it runs, as in the [support agent](/recipes/support-agent).
* **Strip secrets from output.** Process lists and logs can contain tokens in arguments or URLs. Filter them in `run_check` before they reach the model.
* **Check a fleet.** Add a `host` parameter with an `enum` of your host names, and let the agent compare machines.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.