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

# Create an Agent

> Subclass Agent, set its instructions, and wire it into a runnable session.

The `Agent` class is the foundation for every voice agent you build. You define a custom agent by subclassing `Agent`, give it instructions, and override its lifecycle hooks to act when it joins or leaves a session.

## Architecture

The `Agent` defines behavior and carries its `Pipeline` (the STT, LLM, and TTS components). You register the agent with `zeroruntime.serve()`, and `zeroruntime.invoke()` starts a session for it; the runtime orchestrates both into a running workflow inside a session.

<img src="https://mintcdn.com/zeroruntime/9aN_6epDgqAoLTd0/images/agent.svg?fit=max&auto=format&n=9aN_6epDgqAoLTd0&q=85&s=9462187f22cf9cd1d12b5a3abe0e4a72" alt="Agent orchestrator wired to instructions, function tools, MCP, and its pipeline" className="block w-full my-4" width="900" height="620" data-path="images/agent.svg" />

## Initialization

Every custom agent calls `super().__init__()` inside its own `__init__`, passing its `instructions`, a unique `agent_id` (required: it's the handle callers reach the agent by), and the `pipeline` it runs on. This runs the base `Agent` so it can register lifecycle hooks and tools, prepare internal state, and store the system prompt.

### Instructions

Provide a clear system prompt via the `instructions` argument. Describe the agent's role, tone, and constraints; it is sent with every conversation turn.

<CodeGroup>
  ```python Python theme={null}
  from zeroruntime import Agent

  class MyAgent(Agent):
      def __init__(self, pipeline):
          super().__init__(
              agent_id="assistant",
              instructions="You are a helpful assistant. Keep replies short and friendly.",
              pipeline=pipeline,
          )
  ```

  ```typescript Node JS theme={null}
  import { Agent } from '@zeroruntime/js-sdk';

  class MyAgent extends Agent {
    constructor(pipeline) {
      super({
        agent_id: 'assistant',
        instructions: 'You are a helpful assistant. Keep replies short and friendly.',
        pipeline,
      });
    }
  }
  ```
</CodeGroup>

## Lifecycle

Agents provide two async lifecycle hooks you override to act on join and leave: `on_enter` (runs after the agent joins) and `on_exit` (runs before it leaves). Together with `instructions`, both hooks are expected on every agent you define.

### on\_enter()

`on_enter` is called once when the agent successfully joins the session. A common use case is greeting participants with `session.say()`.

<Note>
  Limitation: it runs only after the room connection succeeds, so it cannot be used for pre-connection setup.
</Note>

<CodeGroup>
  ```python Python theme={null}
  async def on_enter(self):
      print("Agent has entered the session.")
      await self.session.say("Hello everyone! I'm here to help.")
  ```

  ```typescript Node JS theme={null}
  async on_enter() {
    console.log('Agent has entered the session.');
    await this.session!.say("Hello everyone! I'm here to help.");
  }
  ```
</CodeGroup>

### on\_exit()

`on_exit` is called when the agent is about to leave the session. A common use case is saying goodbye or running cleanup tasks.

<Note>
  Limitation: the session is shutting down, so long-running work may not complete before resources are released.
</Note>

<CodeGroup>
  ```python Python theme={null}
  async def on_exit(self):
      print("Agent is exiting the session.")
      await self.session.say("It was a pleasure assisting you. Goodbye!")
  ```

  ```typescript Node JS theme={null}
  async on_exit() {
    console.log('Agent is exiting the session.');
    await this.session!.say('It was a pleasure assisting you. Goodbye!');
  }
  ```
</CodeGroup>

## Example

A full agent you can run directly. It builds a cascade pipeline, registers the agent with `zeroruntime.serve()`, and starts a playground session with `zeroruntime.invoke()`.

<CodeGroup>
  ```python title="main.py" Python theme={null}
  import zeroruntime
  from zeroruntime import Agent, Pipeline
  from zeroruntime.plugins import DeepgramSTT, GoogleLLM, CartesiaTTS
  from zeroruntime.inference import AICousticsDenoise, TurnDetector

  AGENT_ID = "assistant"

  pipeline = Pipeline(
      stt=DeepgramSTT(),
      llm=GoogleLLM(),
      tts=CartesiaTTS(),
      turn_detector=TurnDetector(model="echo-large"),
      denoise=AICousticsDenoise(model_id="quail-vf-2.2-l-16khz"),
  )

  class MyAgent(Agent):
      def __init__(self):
          super().__init__(
              agent_id=AGENT_ID,
              instructions="You are a helpful voice assistant.",
              pipeline=pipeline,
          )

      async def on_enter(self):
          await self.session.say("Hello! How can I help you today?")

      async def on_exit(self):
          await self.session.say("Goodbye!")

  if __name__ == "__main__":
      # Pass the class itself (not an instance): serve() builds a fresh MyAgent +
      # pipeline per call, which is required for correct per-call state under concurrent calls.
      zeroruntime.serve(MyAgent, on_ready=lambda: zeroruntime.invoke(AGENT_ID, room=zeroruntime.Room(playground=True)))
  ```

  ```typescript title="main.ts" Node JS theme={null}
  import * as zeroruntime from '@zeroruntime/js-sdk';
  import { Agent, Pipeline } from '@zeroruntime/js-sdk';
  import { DeepgramSTT, GoogleLLM, CartesiaTTS } from '@zeroruntime/js-sdk/plugins';
  import { AICousticsDenoise, TurnDetector } from '@zeroruntime/js-sdk/inference';

  const AGENT_ID = 'assistant';

  const pipeline = Pipeline({
    stt: DeepgramSTT(),
    llm: GoogleLLM(),
    tts: CartesiaTTS(),
    turn_detector: TurnDetector({ model: 'echo-large' }),
    denoise: AICousticsDenoise({ model_id: 'quail-vf-2.2-l-16khz' }),
  });

  class MyAgent extends Agent {
    constructor() {
      super({
        agent_id: AGENT_ID,
        instructions: 'You are a helpful voice assistant.',
        pipeline,
      });
    }

    async on_enter() {
      await this.session!.say('Hello! How can I help you today?');
    }

    async on_exit() {
      await this.session!.say('Goodbye!');
    }
  }

  // Pass the class itself (not an instance): serve() builds a fresh MyAgent +
  // pipeline per call, which is required for correct per-call state under concurrent calls.
  zeroruntime.serve(MyAgent, { on_ready: () => zeroruntime.invoke(AGENT_ID, { room: zeroruntime.Room({ playground: true }) }) });
  ```
</CodeGroup>

## What's Next

<CardGroup cols={2}>
  <Card title="Configure a Pipeline" icon="diagram-project" href="/build/configure-a-pipeline">
    Wire up STT, LLM, and TTS.
  </Card>

  <Card title="Run Your Agent" icon="play" href="/build/run-the-runtime">
    Register and serve the agent.
  </Card>
</CardGroup>

## References

<Tabs>
  <Tab title="Python">
    #### Examples

    <CardGroup cols={2}>
      <Card title="Cascade Basic" icon="github" href="https://github.com/ZeroRuntimeAI/zeroruntime-python-examples/blob/main/getting_started/cascade_basic.py">
        Minimal STT-LLM-TTS cascade agent you can run.
      </Card>

      <Card title="Realtime Basic" icon="github" href="https://github.com/ZeroRuntimeAI/zeroruntime-python-examples/blob/main/getting_started/realtime_basic.py">
        Minimal speech-to-speech realtime agent.
      </Card>
    </CardGroup>
  </Tab>

  <Tab title="Node JS">
    #### Examples

    <CardGroup cols={2}>
      <Card title="Cascade Basic" icon="github" href="https://github.com/ZeroRuntimeAI/zeroruntime-js-examples/blob/main/getting_started/cascade_basic.ts">
        Minimal STT-LLM-TTS cascade agent you can run.
      </Card>

      <Card title="Realtime Basic" icon="github" href="https://github.com/ZeroRuntimeAI/zeroruntime-js-examples/blob/main/getting_started/realtime_basic.ts">
        Minimal speech-to-speech realtime agent.
      </Card>
    </CardGroup>
  </Tab>
</Tabs>
