Programming Neam
📖 16 min read

Chapter 27: NeamClaw Traits -- Composable Agent Behaviors #

"The measure of intelligence is the ability to change." -- Albert Einstein

In the preceding chapters, you built claw agents with persistent sessions, forge agents with build-verify loops, and wired them into multi-agent pipelines. Each of those agents had a fixed set of capabilities determined at declaration time. But production agents need more: they need to run periodic health checks, route messages across platforms, sandbox untrusted code, detect behavioral anomalies, hook into spawn and delegate events, and configure their own search strategies. NeamClaw provides six built-in traits that add these behaviors through the same impl Trait for Type syntax you learned in Chapter 9b. This chapter is a deep dive into every one of them.

By the end of this chapter, you will be able to:

💠 Why This Matters

A chat assistant that cannot detect when it is stuck in a loop will burn through your API budget overnight. A coding agent that executes user-submitted code without a sandbox is a security incident waiting to happen. A multi-agent system that spawns child agents with no logging produces invisible failures. NeamClaw traits give you compile-time guarantees that these capabilities are wired correctly. If you try to schedule a stateless agent that has no session infrastructure, the compiler stops you before you deploy. That is the difference between "it works on my laptop" and "it works in production."


27.1 From OOP Traits to Agent Traits #

In Chapter 9b, you learned that a trait defines a set of methods that types can implement. You used impl Describable for Dog to give a Dog struct a .describe() method. The trait system provided polymorphism without inheritance: different types could share the same interface while providing their own implementations.

NeamClaw agent traits work the same way syntactically but serve a different purpose. OOP traits define shared interfaces for data types. Agent traits define composable behaviors for agent types. The syntax is identical:

neam
// OOP trait (Chapter 9b)
trait Describable {
  fn describe(self);
}

impl Describable for Dog {
  fn describe(self) {
    return f"Dog: {self.name}";
  }
}

// Agent trait (this chapter)
impl Schedulable for my_claw_agent {
  fn on_heartbeat(self) {
    return HeartbeatAction.Silent;
  }

  fn heartbeat_interval(self) {
    return 30000;
  }
}

The key differences are:

  1. You do not declare agent traits yourself. The six NeamClaw traits (Schedulable, Channelable, Sandboxable, Monitorable, Orchestrable, Searchable) are built into the language. You only write impl blocks for them.

  2. The compiler enforces compatibility. Not every trait works with every agent type. Implementing Schedulable on a stateless agent produces a compile error because stateless agents have no session infrastructure to support periodic heartbeats. The compatibility matrix (Section 27.2) defines exactly which combinations are valid.

  3. The runtime hooks into your implementations. When you implement Monitorable, the runtime automatically calls your baseline() method to establish expected metrics and invokes your on_anomaly() callback whenever those metrics deviate. You do not need to wire anything manually.

┌─────────────────────────────────────────────────────────────────────┐
│  OOP Traits vs Agent Traits                                         │
│                                                                     │
│  OOP Trait (Chapter 9b)             Agent Trait (Chapter 27)        │
│  ─────────────────────              ────────────────────────        │
│                                                                     │
│  trait Describable {                Schedulable (built-in)           │
│    fn describe(self);               fn on_heartbeat(self);          │
│  }                                  fn heartbeat_interval(self);    │
│                                                                     │
│  impl Describable for Dog {         impl Schedulable for my_bot {   │
│    fn describe(self) {                fn on_heartbeat(self) {       │
│      return "woof";                     return HeartbeatAction      │
│    }                                           .Silent;             │
│  }                                    }                             │
│                                       fn heartbeat_interval(self) { │
│  Purpose:                               return 30000;               │
│    Shared interface for                 }                           │
│    data types                       }                               │
│                                                                     │
│  Enforced by:                       Purpose:                        │
│    Compiler (method exists)           Composable behavior for       │
│                                       agent types                   │
│                                                                     │
│                                     Enforced by:                    │
│                                       Compiler (method exists       │
│                                       AND agent type compatible)    │
└─────────────────────────────────────────────────────────────────────┘
🌎 Real-World Analogy

Think of agent traits as optional certifications for an employee. A "First Aid Certified" badge means the employee can handle medical emergencies. A "Forklift Licensed" badge means they can operate heavy equipment. Not every role qualifies for every certification -- an office receptionist cannot get forklift certification because they do not work in a warehouse. Similarly, a stateless agent cannot implement Schedulable because it has no session infrastructure to schedule against. The compiler is the HR department that checks qualifications before issuing badges.


27.2 Trait Compatibility Matrix #

Not every trait is compatible with every agent type. The following matrix shows which combinations are valid:

Trait agent (stateless) claw agent forge agent
Schedulable Error Yes Error
Channelable Error Yes Error
Sandboxable Warning Yes Yes
Monitorable Warning Yes Yes
Orchestrable Error Yes Yes
Searchable Warning Yes Yes

Legend: - Yes -- Fully supported. The compiler accepts the impl block and the runtime hooks into your methods. - Warning -- The compiler accepts the impl block but emits a warning. The trait may have reduced functionality on this agent type. - Error -- The compiler rejects the impl block with an error. The trait requires capabilities that this agent type does not have.

Why Some Combinations Produce Errors #

Schedulable requires session infrastructure for heartbeat timers and cron scheduling. Stateless agents and forge agents lack this infrastructure. A forge agent runs a finite loop and exits; there is no long-running process to attach a heartbeat to.

Channelable requires multi-channel I/O, which only claw agents provide. Stateless agents have no channel system, and forge agents receive work through their plan file, not through message channels.

Orchestrable requires the spawn and dag_execute runtime to fire callbacks. Stateless agents do not participate in orchestration flows that need lifecycle callbacks.

Compile Error Messages #

When you attempt an invalid combination, the compiler produces a clear error:

bash
# Attempting to implement Schedulable on a stateless agent
neamc my_agent.neam

error[E0271]: trait `Schedulable` is not compatible with `agent`
  --> my_agent.neam:15:1
   |
15 | impl Schedulable for my_agent {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `Schedulable` requires a `claw agent` declaration
   = help: change `agent my_agent` to `claw agent my_agent`
bash
# Attempting to implement Channelable on a forge agent
neamc my_forge.neam

error[E0271]: trait `Channelable` is not compatible with `forge agent`
  --> my_forge.neam:22:1
   |
22 | impl Channelable for code_builder {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `Channelable` requires a `claw agent` declaration
   = help: only `claw agent` supports multi-channel message routing

Compiler Warning Example #

bash
# Implementing Sandboxable on a stateless agent
neamc my_agent.neam

warning[W0150]: trait `Sandboxable` has limited support on `agent`
  --> my_agent.neam:18:1
   |
18 | impl Sandboxable for my_agent {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: sandbox configuration will apply but cannot enforce
     per-session isolation without a `claw agent` or `forge agent`
   = help: consider using `claw agent` or `forge agent` for full
     sandbox enforcement
💡 Tip

When in doubt, use a claw agent. Claw agents support all six traits with full functionality. If your agent does not need persistent sessions, you can set session: { compaction: "disabled" } and treat it as a lightweight session agent that still benefits from the full trait system.


27.3 Schedulable #

Purpose #

The Schedulable trait enables periodic heartbeat and cron-based scheduling for claw agents. A scheduled agent can perform background tasks at regular intervals: checking for pending work, sending daily digests, running periodic cleanup, or proactively notifying users of important events.

Required Methods #

Method Arity Required Returns
on_heartbeat(self) 1 Yes HeartbeatAction
heartbeat_interval(self) 1 Yes Number (milliseconds)
on_cron(self, cron_id) 2 No any (default: nil)

The HeartbeatAction Sealed Type #

The on_heartbeat method returns a HeartbeatAction, a built-in sealed type with two variants:

neam
// Built-in -- you do not declare this yourself
sealed HeartbeatAction {
  Notify(message: string),
  Silent
}

Full Implementation Example #

neam
channel cli_chan {
  type: "cli"
}

claw agent task_monitor {
  provider: "openai"
  model: "gpt-4o"
  channels: [cli_chan]
  system: "You monitor pending tasks and notify users of overdue items."
  workspace: "./task_data"
}

impl Schedulable for task_monitor {
  fn on_heartbeat(self) {
    // Check for overdue tasks in the workspace
    let pending = workspace_read("pending_tasks.json");

    if (pending == nil) {
      return HeartbeatAction.Silent;
    }

    // Parse and check for overdue items
    let tasks = parse_json(pending);
    let overdue_count = 0;

    for (task in tasks) {
      if (task["status"] == "overdue") {
        overdue_count = overdue_count + 1;
      }
    }

    if (overdue_count > 0) {
      return HeartbeatAction.Notify(
        f"You have {overdue_count} overdue task(s). Type 'show overdue' to review."
      );
    }

    return HeartbeatAction.Silent;
  }

  fn heartbeat_interval(self) {
    return 60000;   // Check every 60 seconds
  }

  fn on_cron(self, cron_id) {
    if (cron_id == "daily_digest") {
      let summary = self.ask("Generate a daily task summary from the workspace.");
      return HeartbeatAction.Notify(summary);
    }
    return nil;
  }
}

How the Runtime Uses Schedulable #

┌─────────────────────────────────────────────────────────────────────┐
│  Schedulable Runtime Flow                                           │
│                                                                     │
│  Agent starts                                                       │
│       │                                                             │
│       ▼                                                             │
│  Read heartbeat_interval() ──▶ 60000 ms                            │
│       │                                                             │
│       ▼                                                             │
│  Start timer (every 60s)                                            │
│       │                                                             │
│       ▼                                                             │
│  ┌─────────────────────────────┐                                   │
│  │  Timer fires                │                                   │
│  │       │                     │                                   │
│  │       ▼                     │                                   │
│  │  Call on_heartbeat(self)    │                                   │
│  │       │                     │                                   │
│  │       ├── Silent ──▶ no-op  │                                   │
│  │       │                     │                                   │
│  │       └── Notify(msg) ──▶ deliver to active channel             │
│  │                             │                                   │
│  │  Wait 60s, repeat           │                                   │
│  └─────────────────────────────┘                                   │
│                                                                     │
│  Cron events fire on_cron(self, cron_id) at configured times       │
└─────────────────────────────────────────────────────────────────────┘

Use Cases #


27.4 Channelable #

Purpose #

The Channelable trait enables multi-channel message routing for claw agents. A channelable agent can receive messages from multiple platforms (Slack, Discord, email, HTTP webhooks) and route each message through a unified handler. This is the trait you implement when your agent needs to operate across communication platforms simultaneously.

Required Methods #

Method Arity Required Returns
channels(self) 1 Yes List of strings
on_message(self, msg) 2 Yes any

Full Implementation Example #

neam
channel slack_chan {
  type: "http"
}

channel discord_chan {
  type: "http"
}

channel email_chan {
  type: "http"
}

claw agent omni_bot {
  provider: "openai"
  model: "gpt-4o"
  channels: [slack_chan, discord_chan, email_chan]
  system: "You are a support agent. Adapt your response style to the channel."
  temperature: 0.5
}

impl Channelable for omni_bot {
  fn channels(self) {
    return ["slack", "discord", "email"];
  }

  fn on_message(self, msg) {
    let channel = msg["channel_type"];
    let content = msg["content"];
    let sender = msg["sender"];

    // Log the incoming message
    emit f"[{channel}] {sender}: {content}";

    // Route based on channel type
    if (channel == "email") {
      // Email messages get formal responses
      let response = self.ask(
        f"Respond formally to this email from {sender}: {content}"
      );
      return response;
    }

    if (channel == "slack") {
      // Slack messages get concise responses
      let response = self.ask(
        f"Respond concisely to this Slack message from {sender}: {content}"
      );
      return response;
    }

    // Default: standard response for all other channels
    let response = self.ask(content);
    return response;
  }
}

How the Runtime Uses Channelable #

When a message arrives on any registered channel, the runtime:

  1. Identifies the channel type from the inbound message metadata.
  2. Calls on_message(self, msg) with a map containing channel_type, scope, sender, content, session_key, and metadata_json.
  3. Delivers the return value back through the originating channel.

The channels(self) method is called at startup to register the list of channel names the agent expects to handle. This allows the runtime to validate that all declared channels match the agent's channel configuration.

Use Cases #


27.5 Sandboxable #

Purpose #

The Sandboxable trait configures per-execution Docker sandbox parameters for an agent. When your agent executes untrusted code, calls external tools, or processes user-uploaded content, the sandbox isolates those operations from the host system. Implementing Sandboxable lets you declare exactly what the sandbox allows: network access, filesystem permissions, allowed commands, and memory limits.

Required Methods #

Method Arity Required Returns
sandbox_config(self) 1 Yes Map

Config Map Keys #

The map returned by sandbox_config supports the following keys:

Key Type Default Description
mode string "strict" Sandbox mode: "strict", "permissive", "disabled"
network bool false Allow network access from within the sandbox
filesystem string "readonly" Filesystem access: "readonly", "readwrite", "none"
allowed_commands list [] Shell commands the agent may execute
max_memory_mb number 512 Maximum memory allocation in megabytes

Full Implementation Example #

neam
forge agent code_runner {
  provider: "openai"
  model: "gpt-4o"
  system: "You execute Python code in a sandbox. Write tests, run them, iterate."
  verify: verify_tests
  workspace: "./sandbox_workspace"
  checkpoint: "git"

  loop: {
    max_iterations: 10
    max_cost: 5.0
  }
}

impl Sandboxable for code_runner {
  fn sandbox_config(self) {
    return {
      "mode": "strict",
      "network": false,
      "filesystem": "readwrite",
      "allowed_commands": ["python3", "pip", "pytest"],
      "max_memory_mb": 1024
    };
  }
}

fun verify_tests(ctx) {
  let test_output = workspace_read("test_results.txt");
  if (test_output == nil) {
    return "No test results found. Run pytest and save output to test_results.txt.";
  }
  if (test_output.contains("FAILED")) {
    return f"Tests failed. Fix the failures:\n{test_output}";
  }
  return true;
}

In this example, the forge agent executes Python code inside a strict sandbox that:

Sandbox Modes #

📁┌─────────────────────────────────────────────────────────────────────┐
📁Sandbox Modes │
📁
📁"strict" │
📁All access denied by default │
📁Only allowed_commands can execute │
📁Filesystem access controlled by filesystem key │
📁Network blocked unless explicitly enabled │
📁
📁"permissive" │
📁All access allowed by default │
📁allowed_commands ignored (all commands permitted) │
📁Filesystem read-write by default │
📁Network allowed by default │
📁
📁"disabled" │
📄No sandbox. Agent runs directly on host. │
📄Use only for trusted, internal agents. │
📁
Common Mistake

Do not set mode: "disabled" in production. An agent executing LLM-generated code without a sandbox can delete files, exfiltrate data, or consume unbounded resources. Even for trusted internal agents, mode: "permissive" with a max_memory_mb limit is safer than no sandbox at all.

Use Cases #


27.6 Monitorable #

Purpose #

The Monitorable trait enables behavioral monitoring and anomaly detection for agents. A monitorable agent declares a baseline of expected metrics (average tool calls, average response time, maximum cost per turn). When the runtime detects that actual behavior deviates from the baseline, it invokes your on_anomaly callback, giving you the opportunity to alert, silence, or disable the agent.

Required Methods #

Method Arity Required Returns
baseline(self) 1 Yes Map
on_anomaly(self, event) 2 Yes AnomalyAction

The AnomalyAction Sealed Type #

The on_anomaly method returns an AnomalyAction, a built-in sealed type:

neam
// Built-in -- you do not declare this yourself
sealed AnomalyAction {
  Alert(message: string),
  Silence,
  Disable
}

Baseline Metrics #

The baseline() method returns a map of expected metric values. The runtime compares actual behavior against these values on every turn:

Metric Key Type Description
avg_tool_calls number Expected average number of tool calls per turn
avg_response_time_ms number Expected average response time in milliseconds
max_tool_calls number Maximum tool calls before anomaly fires
max_response_time_ms number Maximum response time before anomaly fires
max_cost_per_turn number Maximum cost (dollars) per single turn
max_consecutive_errors number Maximum sequential errors before anomaly fires

Full Implementation Example #

neam
channel support_chan {
  type: "http"
}

claw agent monitored_support {
  provider: "openai"
  model: "gpt-4o"
  channels: [support_chan]
  system: "You are a customer support agent. Be helpful and concise."
  skills: [lookup_order, process_refund]
}

impl Monitorable for monitored_support {
  fn baseline(self) {
    return {
      "avg_tool_calls": 2,
      "avg_response_time_ms": 800,
      "max_tool_calls": 10,
      "max_response_time_ms": 5000,
      "max_cost_per_turn": 0.05,
      "max_consecutive_errors": 3
    };
  }

  fn on_anomaly(self, event) {
    let metric = event["metric"];
    let actual = event["actual"];
    let threshold = event["threshold"];

    emit f"ANOMALY on {self.name}: {metric} = {actual} (threshold: {threshold})";

    // Critical anomalies: disable the agent
    if (metric == "max_tool_calls" & actual > 20) {
      return AnomalyAction.Disable;
    }

    // Cost anomalies: alert but continue
    if (metric == "max_cost_per_turn") {
      return AnomalyAction.Alert(
        f"Cost anomaly: ${actual} exceeds ${threshold} per turn"
      );
    }

    // Everything else: alert
    return AnomalyAction.Alert(
      f"Behavioral anomaly: {metric} at {actual}"
    );
  }
}

How the Runtime Uses Monitorable #

┌─────────────────────────────────────────────────────────────────────┐
│  Monitorable Runtime Flow                                           │
│                                                                     │
│  Agent registers                                                    │
│       │                                                             │
│       ▼                                                             │
│  Call baseline(self) ──▶ Store expected metrics                     │
│       │                                                             │
│       ▼                                                             │
│  ┌─────────────────────────────┐                                   │
│  │  Agent processes a turn     │                                   │
│  │       │                     │                                   │
│  │       ▼                     │                                   │
│  │  Record actual metrics:     │                                   │
│  │    tool_calls, time_ms,     │                                   │
│  │    cost, errors             │                                   │
│  │       │                     │                                   │
│  │       ▼                     │                                   │
│  │  Compare against baseline   │                                   │
│  │       │                     │                                   │
│  │       ├── Within bounds ──▶ continue                            │
│  │       │                     │                                   │
│  │       └── Exceeded ──▶ call on_anomaly(self, event)             │
│  │                 │           │                                   │
│  │                 ├── Alert(msg) ──▶ log + continue               │
│  │                 ├── Silence    ──▶ suppress + continue          │
│  │                 └── Disable   ──▶ kill switch activated         │
│  │                                 │                               │
│  └─────────────────────────────────┘                               │
└─────────────────────────────────────────────────────────────────────┘

Use Cases #

💡 Tip

Start with generous thresholds and tighten them after observing real traffic patterns. Setting max_tool_calls: 3 on day one will produce constant false alarms. Let the agent run for a week, measure the actual distribution, and set thresholds at the 99th percentile of observed behavior.


27.7 Orchestrable #

Purpose #

The Orchestrable trait provides callbacks for multi-agent spawn and delegate events. When a claw or forge agent spawns a child agent (via spawn) or receives a delegation result, the runtime invokes your on_spawn and on_delegate methods. This gives you hooks for logging, metrics collection, access control, and auditing of sub-agent invocations.

Methods #

Method Arity Required Returns
on_spawn(self, child_name) 2 No (default: nil) any
on_delegate(self, result) 2 No (default: nil) any

Both methods are optional. If you do not implement one, the runtime uses a default implementation that returns nil (no-op). This means you can implement only the callbacks you need.

Full Implementation Example #

neam
channel cli_chan {
  type: "cli"
}

claw agent coordinator {
  provider: "openai"
  model: "gpt-4o"
  channels: [cli_chan]
  system: "You coordinate research tasks by spawning specialist agents."
  skills: [web_search]
}

claw agent researcher {
  provider: "openai"
  model: "gpt-4o"
  channels: [cli_chan]
  system: "You are a research specialist. Provide thorough, cited answers."
}

claw agent summarizer {
  provider: "openai"
  model: "gpt-4o"
  channels: [cli_chan]
  system: "You summarize research findings into concise bullet points."
}

impl Orchestrable for coordinator {
  fn on_spawn(self, child_name) {
    emit f"[orchestration] Spawning child agent: {child_name}";

    // Log the spawn event to workspace
    workspace_append(
      "orchestration.log",
      f"SPAWN: {child_name}\n"
    );

    return nil;
  }

  fn on_delegate(self, result) {
    let preview = result;
    if (len(result) > 100) {
      preview = result[0:100] + "...";
    }

    emit f"[orchestration] Delegate result: {preview}";

    // Log the delegation result
    workspace_append(
      "orchestration.log",
      f"DELEGATE_RESULT: {preview}\n"
    );

    return nil;
  }
}

// Usage: spawn calls trigger the Orchestrable callbacks
{
  // This triggers on_spawn("researcher") before the LLM call,
  // then on_delegate(result) after the result arrives.
  let research = spawn researcher("Summarize recent advances in RAG systems");

  let summary = spawn summarizer(
    f"Summarize the following research:\n{research}"
  );

  emit summary;
}

Spawn-Delegate Lifecycle #

1. on_spawn(self, "researcher")
Log, validate, or deny
2. LLM processes the task
(researcher agent runs)
3. on_delegate(self, result)
Log, transform, or audit

Use Cases #


27.8 Searchable #

Purpose #

The Searchable trait configures RAG (Retrieval-Augmented Generation) and search behavior for an agent. Instead of relying on the default search settings from the agent's declaration, Searchable lets you programmatically control the search strategy, the number of results retrieved, the minimum relevance score, and hook into the indexing pipeline.

Required Methods #

Method Arity Required Returns
search_config(self) 1 Yes Map
on_index(self, doc) 2 No (default: nil) any

Config Map Keys #

The map returned by search_config supports the following keys:

Key Type Default Description
strategy string "hybrid" Search strategy: "hybrid", "vector", "keyword"
top_k number 5 Number of results to retrieve per query
min_score number 0.0 Minimum relevance score (0.0 to 1.0) to include a result

Full Implementation Example #

neam
channel cli_chan {
  type: "cli"
}

claw agent doc_assistant {
  provider: "openai"
  model: "gpt-4o"
  channels: [cli_chan]
  system: "You answer questions based on indexed documentation."
  workspace: "./docs"

  semantic_memory: {
    backend: "local"
    search: "hybrid"
  }
}

impl Searchable for doc_assistant {
  fn search_config(self) {
    return {
      "strategy": "hybrid",
      "top_k": 8,
      "min_score": 0.65
    };
  }

  fn on_index(self, doc) {
    let path = doc["file_path"];
    let size = doc["size_bytes"];

    emit f"[index] Indexed {path} ({size} bytes)";

    // Skip very large files from indexing
    if (size > 1000000) {
      emit f"[index] Warning: {path} exceeds 1MB, chunking may be coarse";
    }

    return nil;
  }
}

Search Strategies #

📁┌─────────────────────────────────────────────────────────────────────┐
📁Search Strategy Comparison │
📁
📁"keyword" (BM25) │
📁Best for: exact term matching, known vocabulary │
📁Fast, no embedding needed │
📁Weak on: paraphrases, synonyms, semantic similarity │
📁
📁"vector" (cosine similarity) │
📁Best for: semantic similarity, paraphrases │
📁Requires embeddings (slower indexing) │
📁Weak on: exact term matching, rare tokens │
📁
📁"hybrid" (70% vector + 30% BM25) │
📁Best for: general-purpose retrieval │
📁Combines strengths of both approaches │
📁Default and recommended for most use cases │
📁

Use Cases #


27.9 Composing Multiple Traits #

A single agent can implement multiple traits. Each trait is implemented in its own impl block, and the order of impl blocks does not matter. The compiler processes all impl blocks for a given agent and registers the corresponding behaviors with the runtime.

Example: Three Traits on One Agent #

neam
channel cli_chan {
  type: "cli"
}

claw agent smart_assistant {
  provider: "openai"
  model: "gpt-4o"
  channels: [cli_chan]
  system: "You are an intelligent assistant with monitoring, scheduling,
           and search capabilities."
  workspace: "./assistant_data"

  semantic_memory: {
    backend: "local"
    search: "hybrid"
  }
}

// Trait 1: Schedulable -- daily health check
impl Schedulable for smart_assistant {
  fn on_heartbeat(self) {
    let status = workspace_read("health_status.txt");
    if (status != nil & status.contains("degraded")) {
      return HeartbeatAction.Notify("System health is degraded. Run diagnostics.");
    }
    return HeartbeatAction.Silent;
  }

  fn heartbeat_interval(self) {
    return 120000;   // Every 2 minutes
  }
}

// Trait 2: Monitorable -- anomaly detection
impl Monitorable for smart_assistant {
  fn baseline(self) {
    return {
      "avg_tool_calls": 3,
      "avg_response_time_ms": 1000,
      "max_tool_calls": 15,
      "max_cost_per_turn": 0.10
    };
  }

  fn on_anomaly(self, event) {
    emit f"[monitor] Anomaly: {event['metric']} = {event['actual']}";
    return AnomalyAction.Alert(
      f"Anomaly detected: {event['metric']}"
    );
  }
}

// Trait 3: Searchable -- custom RAG config
impl Searchable for smart_assistant {
  fn search_config(self) {
    return {
      "strategy": "hybrid",
      "top_k": 10,
      "min_score": 0.6
    };
  }
}

Trait Interaction Considerations #

When composing multiple traits, keep the following in mind:

  1. Each trait operates independently. The Schedulable heartbeat timer does not interact with the Monitorable baseline. Each trait's methods are invoked by different parts of the runtime at different times.

  2. The order of impl blocks does not matter. You can define Monitorable before or after Schedulable. The compiler and runtime process them in the same way regardless of source order.

  3. Traits share the self reference. All trait methods receive the same self parameter, which is the agent instance. This means every trait can call self.ask(), access self.name, or invoke workspace functions in the same way.

  4. A heartbeat can trigger an anomaly. If your on_heartbeat implementation calls self.ask(), that ask operation is subject to monitoring. If the ask exceeds the baseline metrics, on_anomaly will fire. Design your heartbeat implementations to be lightweight to avoid triggering false anomalies.

  5. Searchable affects all queries. Once Searchable is implemented, every self.ask() call uses the search configuration you defined. This includes calls from within other trait methods (such as on_heartbeat or on_anomaly).

📝 Note

There is no limit to the number of traits a single agent can implement, but each trait can only be implemented once per agent. Attempting to write two impl Schedulable for my_agent blocks produces a compile error.


27.10 Real-World Example: Production-Ready Monitored Agent #

This section brings together everything from the chapter into a single, production-ready claw agent that implements four traits. The agent is a customer support bot that:

neam
// ── Skills ──────────────────────────────────────────────────────────

skill lookup_order {
  description: "Look up order status by order ID"
  params: { order_id: string }
  impl(order_id) {
    return "Order " + order_id + ": Shipped, arriving Tuesday";
  }
}

skill process_refund {
  description: "Process a refund for an order"
  params: { order_id: string, reason: string }
  impl(order_id, reason) {
    workspace_append("refunds.log", f"{order_id}: {reason}\n");
    return f"Refund initiated for order {order_id}. Reason: {reason}";
  }
}

skill escalate_to_human {
  description: "Escalate the conversation to a human agent"
  params: { reason: string }
  impl(reason) {
    workspace_append("escalations.log", f"ESCALATE: {reason}\n");
    return "A human agent has been notified and will join shortly.";
  }
}

// ── Guards ──────────────────────────────────────────────────────────

guard input_safety {
  description: "Filters harmful input"
  on_observation(text) { return text; }
}

guardchain safety_chain = [input_safety];

// ── Channel ─────────────────────────────────────────────────────────

channel http_chan {
  type: "http"
}

// ── Agent Declaration ───────────────────────────────────────────────

claw agent support_pro {
  provider: "openai"
  model: "gpt-4o"
  channels: [http_chan]
  system: "You are a professional customer support agent for Acme Corp.
           Be empathetic, concise, and solution-oriented. Use tools to
           look up orders, process refunds, and escalate when needed."
  temperature: 0.3
  skills: [lookup_order, process_refund, escalate_to_human]
  guards: [safety_chain]
  workspace: "./support_pro_data"

  session: {
    idle_reset_minutes: 30
    max_history_turns: 50
    compaction: "auto"
  }

  semantic_memory: {
    backend: "local"
    search: "hybrid"
  }
}

// ── Trait 1: Schedulable ────────────────────────────────────────────

impl Schedulable for support_pro {
  fn on_heartbeat(self) {
    // Check for unresolved escalations
    let escalations = workspace_read("escalations.log");

    if (escalations == nil) {
      return HeartbeatAction.Silent;
    }

    let lines = split(escalations, "\n");
    let unresolved = 0;

    for (line in lines) {
      if (len(line) > 0 & !line.contains("RESOLVED")) {
        unresolved = unresolved + 1;
      }
    }

    if (unresolved > 0) {
      return HeartbeatAction.Notify(
        f"There are {unresolved} unresolved escalation(s) awaiting human review."
      );
    }

    return HeartbeatAction.Silent;
  }

  fn heartbeat_interval(self) {
    return 300000;   // Every 5 minutes
  }

  fn on_cron(self, cron_id) {
    if (cron_id == "daily_summary") {
      let refunds = workspace_read("refunds.log");
      let escalations = workspace_read("escalations.log");

      let summary = f"Daily Support Summary\n";
      summary = summary + f"Refunds: {refunds}\n";
      summary = summary + f"Escalations: {escalations}\n";

      workspace_write("daily_summary.txt", summary);
      return HeartbeatAction.Notify("Daily summary generated. Check daily_summary.txt.");
    }
    return nil;
  }
}

// ── Trait 2: Monitorable ────────────────────────────────────────────

impl Monitorable for support_pro {
  fn baseline(self) {
    return {
      "avg_tool_calls": 2,
      "avg_response_time_ms": 1200,
      "max_tool_calls": 8,
      "max_response_time_ms": 10000,
      "max_cost_per_turn": 0.08,
      "max_consecutive_errors": 5
    };
  }

  fn on_anomaly(self, event) {
    let metric = event["metric"];
    let actual = event["actual"];
    let threshold = event["threshold"];

    // Log all anomalies to workspace
    workspace_append(
      "anomalies.log",
      f"ANOMALY: {metric} = {actual} (threshold: {threshold})\n"
    );

    // Disable on runaway tool usage (possible infinite loop)
    if (metric == "max_tool_calls" & actual > 20) {
      workspace_append("anomalies.log", "ACTION: DISABLED\n");
      return AnomalyAction.Disable;
    }

    // Disable on excessive consecutive errors
    if (metric == "max_consecutive_errors") {
      workspace_append("anomalies.log", "ACTION: DISABLED\n");
      return AnomalyAction.Disable;
    }

    // Alert on cost overruns
    if (metric == "max_cost_per_turn") {
      return AnomalyAction.Alert(
        f"Cost alert: ${actual} per turn exceeds ${threshold} threshold"
      );
    }

    // Default: alert and continue
    return AnomalyAction.Alert(
      f"Behavioral anomaly: {metric} = {actual}"
    );
  }
}

// ── Trait 3: Searchable ─────────────────────────────────────────────

impl Searchable for support_pro {
  fn search_config(self) {
    return {
      "strategy": "hybrid",
      "top_k": 5,
      "min_score": 0.7
    };
  }

  fn on_index(self, doc) {
    emit f"[search] Indexed: {doc['file_path']}";
    return nil;
  }
}

// ── Trait 4: Orchestrable ───────────────────────────────────────────

impl Orchestrable for support_pro {
  fn on_spawn(self, child_name) {
    workspace_append(
      "orchestration.log",
      f"SPAWN: {child_name}\n"
    );
    emit f"[orchestration] Spawned: {child_name}";
    return nil;
  }

  fn on_delegate(self, result) {
    let preview = result;
    if (len(result) > 200) {
      preview = result[0:200] + "...";
    }
    workspace_append(
      "orchestration.log",
      f"RESULT: {preview}\n"
    );
    return nil;
  }
}

// ── Main Execution ──────────────────────────────────────────────────

{
  emit "=== Support Pro Agent ===";
  emit "";

  // Test a support interaction
  let r1 = support_pro.ask("What is the status of order ORD-55921?");
  emit f"Response: {r1}";
  emit "";

  // The agent remembers the previous context
  let r2 = support_pro.ask("I want a refund for that order. The product was damaged.");
  emit f"Response: {r2}";
  emit "";

  // Test escalation
  let r3 = support_pro.ask("This is unacceptable. I want to speak to a manager.");
  emit f"Response: {r3}";
}

Walkthrough #

Let us walk through each trait's contribution to this production agent:

Schedulable provides a heartbeat that runs every 5 minutes. Each heartbeat checks the escalations.log file for unresolved escalations and notifies the active channel if any are found. The on_cron handler generates a daily summary of refunds and escalations, writing it to daily_summary.txt and notifying the operator.

Monitorable establishes baseline metrics for normal operation: an average of 2 tool calls per turn, 1200ms average response time, and a maximum cost of $0.08 per turn. When the runtime detects that actual behavior exceeds these thresholds, on_anomaly fires. The handler logs every anomaly to anomalies.log, disables the agent on runaway tool usage or consecutive errors, and alerts on cost overruns.

Searchable configures hybrid search (70% vector + 30% BM25) with 5 results per query and a minimum relevance score of 0.7. This means the agent only retrieves documentation chunks that are at least 70% relevant to the user's question, reducing noise in the LLM context. The on_index hook logs each file as it enters the search index.

Orchestrable logs every spawn and delegate event to orchestration.log. When support_pro spawns a child agent (for example, to perform deep research on a customer's issue), the spawn event is recorded. When the child agent returns its result, the delegation result is logged with a truncated preview. This creates a complete audit trail of multi-agent interactions.

Together, these four traits transform a basic chat agent into a production-ready system with proactive monitoring, cost controls, searchable documentation, periodic health checks, and full audit logging -- all enforced by the compiler and wired by the runtime.


Summary #


Exercises #

Exercise 27.1: Schedulable Heartbeat Create a claw agent called health_checker that implements Schedulable with a 30-second heartbeat interval. The on_heartbeat method should read a file called system_status.txt from the workspace. If the file contains the word "critical", return HeartbeatAction.Notify with an alert message. Otherwise, return HeartbeatAction.Silent. Test by writing different status values to the file and observing the heartbeat output.

Exercise 27.2: Channelable Message Router Create a claw agent that implements Channelable with three channels: "web", "mobile", and "api". In on_message, route messages differently based on the channel type: web messages get verbose responses, mobile messages get brief responses (under 100 characters), and API messages get JSON-formatted responses. Test each channel path.

Exercise 27.3: Sandboxable Forge Agent Create a forge agent called script_runner that implements Sandboxable. Configure the sandbox to allow only node and npm commands, block network access, allow read-write filesystem access, and limit memory to 256 MB. Write a verify function that checks whether an output.json file was created in the workspace.

Exercise 27.4: Monitorable with AnomalyAction Create a claw agent that implements Monitorable with the following baseline: avg_tool_calls: 2, max_tool_calls: 5, max_cost_per_turn: 0.03. In on_anomaly, return AnomalyAction.Disable if max_tool_calls is exceeded by more than double the threshold, AnomalyAction.Alert for cost anomalies, and AnomalyAction.Silence for response time anomalies during off-peak hours. Write the logic for determining off-peak hours.

Exercise 27.5: Orchestrable Audit Trail Create a claw agent called team_lead that implements Orchestrable. In on_spawn, log the child agent name, the current timestamp, and the spawn count to a workspace file. In on_delegate, log the result length and a 50-character preview. Declare two additional claw agents (worker_a and worker_b) and use spawn to invoke both. Verify that the audit trail in the workspace file contains all expected entries.

Exercise 27.6: Searchable Strategy Comparison Create three claw agents -- keyword_bot, vector_bot, and hybrid_bot -- each implementing Searchable with a different strategy ("keyword", "vector", and "hybrid" respectively). Give all three the same top_k: 5 and min_score: 0.5. Connect all three to the same workspace containing several text files. Ask each agent the same question and compare the quality of their responses. Document your findings as comments in the code.

Exercise 27.7: Multi-Trait Composition Create a single claw agent called super_agent that implements all six NeamClaw traits: Schedulable, Channelable, Sandboxable, Monitorable, Orchestrable, and Searchable. For each trait, provide a minimal but functional implementation. Verify that the program compiles without errors. Then remove the Channelable implementation and confirm that the agent still compiles (since Channelable is not the only trait). Finally, change the agent to a forge agent and observe which impl blocks produce compile errors.

Exercise 27.8: Production Monitoring Dashboard Extend the production-ready example from Section 27.10 by adding a dashboard skill that reads the anomalies.log, escalations.log, and orchestration.log files from the workspace and returns a formatted summary. The summary should include: total anomalies (grouped by metric), total escalations (resolved vs. unresolved), and total spawn/delegate events. Connect the skill to the agent and ask it: "Show me the dashboard." Verify that the agent uses the skill and returns a coherent summary.

Start typing to search...