Skip to content

Agent Experience: Linux vs KruxOS

The same tasks, side by side. See how KruxOS's typed APIs eliminate the parsing, guessing, and retry loops that consume tokens on traditional Linux.

Every KruxOS example below assumes a connected client:

from kruxos import KruxOS

agent = await KruxOS.connect_async(
    endpoint="ws://192.168.1.50:7700",
    agent_name="my-agent",
    api_key="aos_...",
)

A capability is invoked with await agent.call_async(name, **inputs). A failed call raises a typed exception from kruxos.errors (for example FileNotFoundError, PermissionDeniedError, CapabilityError) — there is no success flag to branch on, so the happy path reads straight from result.data.

Reading a file

# Agent must construct shell command, parse text output, handle encoding
import subprocess

result = subprocess.run(
    ["cat", "/workspace/data.csv"],
    capture_output=True, text=True
)
if result.returncode != 0:
    # What went wrong? Parse stderr to guess
    if "No such file" in result.stderr:
        # Try to find the file
        find_result = subprocess.run(
            ["find", "/workspace", "-name", "data.csv"],
            capture_output=True, text=True
        )
        # Parse find output...
    elif "Permission denied" in result.stderr:
        # Can't tell why — is it a policy? A file permission? SELinux?
        pass
    else:
        # Unknown error — retry? Give up?
        pass

content = result.stdout  # Hope it's the right encoding
from kruxos import FileNotFoundError, PermissionDeniedError

try:
    result = await agent.call_async(
        "filesystem.read",
        path="/workspace/data.csv",
    )
    # Happy path — read straight from the typed response
    content = result.data["content"]
    size = result.data["size_bytes"]
    modified = result.data["modified_at"]
except FileNotFoundError:
    # Structured error with recovery suggestions
    # Recovery: "Use filesystem.list to see available files"
    pass
except PermissionDeniedError:
    # Recovery: verify the path is inside the agent's workspace
    pass

Token cost: ~200 tokens (KruxOS) vs ~500 tokens (Linux). The Linux agent spends tokens on output parsing, error guessing, and retry attempts.


Handling errors

# Agent runs a command that fails
result = subprocess.run(["python3", "train.py"], capture_output=True, text=True)

if result.returncode != 0:
    # stderr is a wall of text — agent must parse it
    stderr = result.stderr
    # Is it a missing module? Wrong Python version? Syntax error?
    # OOM? Permission issue? Agent has to pattern-match on raw text.

    if "ModuleNotFoundError" in stderr:
        # Try pip install? Which package? Parse the module name from the traceback
        module = stderr.split("No module named '")[1].split("'")[0]
        subprocess.run(["pip", "install", module])
        # Retry... but was that the right package name?
    elif "PermissionError" in stderr:
        # Try sudo? That's blocked. Try chmod? What permissions?
        pass
    # ... more string parsing
from kruxos import CapabilityError

try:
    result = await agent.call_async(
        "process.run",
        command="python3 train.py",
    )
except CapabilityError as e:
    # Structured error — no parsing needed
    print(f"Error: {e.error_type}")            # "ProcessFailed"
    if e.structured:
        print(f"Details: {e.structured.description}")
        print(f"Context: {e.structured.context}")  # e.g. exit_code, stderr

        for recovery in e.structured.recovery_actions:
            print(f"Try: {recovery.action}{recovery.description}")
            # "retry" — "Retry the command"
            # "check_dependencies" — "Verify required modules are installed"

Token cost: ~150 tokens (KruxOS) vs ~800 tokens (Linux). Error recovery on Linux burns tokens on pattern matching, wrong guesses, and retry loops.


Discovering available tools

# Agent doesn't know what it can do. Trial and error:
result = subprocess.run(["which", "git"], capture_output=True, text=True)
has_git = result.returncode == 0

result = subprocess.run(["which", "docker"], capture_output=True, text=True)
has_docker = result.returncode == 0

# Check version to guess available flags
result = subprocess.run(["git", "--version"], capture_output=True, text=True)
# Parse "git version 2.43.0" → is this new enough for sparse-checkout?

# Read man pages for usage — thousands of tokens
result = subprocess.run(["man", "git-log"], capture_output=True, text=True)
# 5000+ tokens of man page text...
# Discover everything available, with schemas
caps = await agent.capabilities.list_async()
# Returns a list of CapabilityDef (name, description, input/output schema)

# Get details for one capability
cap = await agent.capabilities.describe_async("git.log")
print(cap.name)          # "git.log"
print(cap.description)   # "Returns the commit log for a repository..."
for field, spec in cap.input_schema.get("properties", {}).items():
    print(f"  {field}: {spec.get('type')}")  # Typed parameters

Token cost: ~100 tokens (KruxOS) vs ~5000+ tokens (Linux). Man pages and --help output are massive. KruxOS provides exactly what the agent needs.


Working with secrets

import os

# Secret is in an environment variable — agent can see it
api_key = os.environ.get("OPENWEATHER_API_KEY")
# Agent now has the raw secret. It could:
# - Log it accidentally
# - Include it in an error report
# - Send it to another service
# - Store it in persistent state

response = requests.get(
    f"https://api.openweathermap.org/data/2.5/weather?q=London&appid={api_key}"
)
# Agent never sees the secret — use-not-read model
result = await agent.call_async(
    "weather.current",
    location="London"
)
# The capability implementation accessed the secret internally
# via the vault. The agent only gets the weather data back.

# Even if the agent tries to read the secret directly:
result = await agent.call_async(
    "secrets.use", name="OPENWEATHER_API_KEY"
)
# Returns: {"injected": true, "capability": "weather.current"}
# NOT the actual secret value

Security: On Linux, the agent has the raw secret. On KruxOS, secrets are injected into capability execution environments — the agent never sees them.


Multi-step workflow

# Search for files, read them, create a report — lots of text parsing

# Step 1: Find Python files
result = subprocess.run(
    ["find", "/workspace", "-name", "*.py", "-type", "f"],
    capture_output=True, text=True
)
files = result.stdout.strip().split("\n")
# Hope there are no filenames with newlines...

# Step 2: Count lines in each file
total_lines = 0
for f in files:
    result = subprocess.run(
        ["wc", "-l", f], capture_output=True, text=True
    )
    # Parse "   42 /workspace/main.py" — split on whitespace
    count = int(result.stdout.strip().split()[0])
    total_lines += count

# Step 3: Write report
report = f"Found {len(files)} Python files, {total_lines} lines total\n"
with open("/workspace/report.txt", "w") as fh:
    fh.write(report)
# Step 1: Search for Python files
result = await agent.call_async(
    "filesystem.search",
    directory="/workspace",
    pattern="*.py"
)
files = result.data["matches"]  # Structured list

# Step 2: Get file stats
total_lines = 0
for f in files:
    stat = await agent.call_async(
        "filesystem.stat", path=f["path"]
    )
    total_lines += stat.data["line_count"]

# Step 3: Write report
report = f"Found {len(files)} Python files, {total_lines} lines total\n"
await agent.call_async(
    "filesystem.write",
    path="/workspace/report.txt",
    content=report
)
# Write is soft-deleted recoverable for 24 hours

Token cost: ~1,200 tokens (KruxOS) vs ~3,000 tokens (Linux). Every shell command requires text parsing and error checking that consumes tokens.


Sending email safely

import smtplib
from email.mime.text import MIMEText

# Direct SMTP — no safety net
msg = MIMEText("Quarterly report attached.")
msg["Subject"] = "Q1 Report"
msg["From"] = "[email protected]"
msg["To"] = "[email protected]"  # Oops — sent to ALL staff

# Sent immediately. No undo. No approval. No buffer.
with smtplib.SMTP("smtp.gmail.com", 587) as server:
    server.starttls()
    server.login(os.environ["GMAIL_USER"], os.environ["GMAIL_PASS"])
    server.send_message(msg)  # Gone. Can't take it back.
result = await agent.call_async(
    "email.send",
    to="[email protected]",
    subject="Q1 Report",
    body="Quarterly report attached."
)

# Not sent yet — buffered for 5 minutes
print(f"Will send at: {result.data['buffer_until']}")
print(f"Cancel with: proxy.cancel_write(write_id='{result.data['write_id']}')")

# Supervisor gets notified and can cancel
# Batch protection kicks in at 20 sends/hour
# Rollback point created automatically

# If it was a mistake:
await agent.call_async(
    "proxy.cancel_write",
    write_id=result.data["write_id"]
)
# Email never sent. Crisis averted.

Safety: On Linux, sends are immediate and irrevocable. On KruxOS, every write to an external service is buffered, cancellable, batch-protected, and rollback-enabled.


Summary

Dimension Traditional Linux KruxOS
Interface Shell commands + text parsing Typed APIs + structured responses
Error handling Parse stderr text, guess the cause Typed errors with recovery actions
Discovery which, --help, man (thousands of tokens) Schema-aware capability listing
Secrets Environment variables (visible to agent) Use-not-read vault (never exposed)
External services Direct API access (immediate, irrevocable) Buffered, cancellable, rollback-enabled
Token efficiency Baseline ~60% fewer tokens
Task completion ~65-85% ~90-98%
Audit Container logs (deletable) Hash-chained, tamper-proof, queryable

The difference is not about what agents can do — it's about how efficiently and safely they do it.