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

# Outbound Scheduling

> Build an outbound agent that schedules appointments

In this example, we'll build an outbound voice agent for a dental office. The agent calls patients to help them schedule appointments.

## Define the Agent

`guava.Agent` is our starting point for building Guava agents. We'll create one for this example.

<CodeGroup>
  ```python Python theme={null}
  import guava

  agent = guava.Agent(
      organization="Bright Smile Dental",
      purpose="Call patients to help them schedule a dental appointment.",
  )
  ```

  ```typescript TypeScript theme={null}
  import * as guava from "@guava-ai/guava-sdk";

  const agent = new guava.Agent({
    organization: "Bright Smile Dental",
    purpose: "You are calling patients to help them schedule a dental appointment",
  });
  ```
</CodeGroup>

## Set up DatetimeFilter

`DatetimeFilter` is a built-in helper that accepts a natural-language availability query (e.g. "Tuesdays work best") and returns a short list of matching slots from your source data. In a production system you would swap this for a call to your own scheduling backend.

<CodeGroup>
  ```python Python theme={null}
  from guava.helpers.openai import DatetimeFilter
  from guava.examples.example_data import MOCK_APPOINTMENTS

  datetime_filter = DatetimeFilter(source_list=MOCK_APPOINTMENTS)
  ```

  ```typescript TypeScript theme={null}
  import { DatetimeFilter } from "@guava-ai/guava-sdk/helpers";
  import { mockAppointmentsForFuture } from "@guava-ai/guava-sdk/example-data";

  const datetimeFilter = new DatetimeFilter({
    sourceList: mockAppointmentsForFuture(),
  });
  ```
</CodeGroup>

## Reach the right person

`on_call_start` fires at the beginning of every outbound call. We read the patient's name from the call variables and invoke `reach_person`, which instructs the agent to confirm it is speaking with the intended recipient before proceeding. Later in this example, we'll see how to set the `patient_name` variable.

<CodeGroup>
  ```python Python theme={null}
  @agent.on_call_start
  def on_call_start(call: guava.Call):
      call.reach_person(
          contact_full_name=call.get_variable("patient_name"),
      )
  ```

  ```typescript TypeScript theme={null}
  agent.onCallStart(async (call: guava.Call) => {
    await call.reachPerson(await call.getVariable("patientName"));
  });
  ```
</CodeGroup>

<Note>
  Under the hood `reach_person()` is just a call to `set_task()`. You can replace `reach_person()` with your own [Task](/sdk-reference/tasks) if you need custom behavior here.
</Note>

## Handle the reach-person outcome

`on_reach_person` fires once the agent has determined whether the intended person is available. If they are, we set a task to collect an appointment time. If not, we hang up gracefully.

<CodeGroup>
  ```python Python theme={null}
  @agent.on_reach_person
  def on_reach_person(call: guava.Call, outcome: str) -> None:
      if outcome == "unavailable":
          call.hangup("Apologize for your mistake and hang up the call.")
      elif outcome == "available":
          call.set_task(
              "schedule_appointment",
              checklist=[
                  "Tell them that it's been a while since their regular cleaning with Dr. Teeth.",
                  guava.Field(
                      key="appointment_time",
                      field_type="calendar_slot",
                      description="Find a time that works for the caller",
                      searchable=True,
                  ),
                  "Tell them their appointment has been confirmed and answer any questions before ending the call.",
              ],
          )
  ```

  ```typescript TypeScript theme={null}
  agent.onReachPerson(async (call: guava.Call, outcome: string) => {
    if (outcome === "available") {
      await call.setTask({
        taskId: "schedule_appointment",
        checklist: [
          "Tell them that it's been a while since their regular cleaning with Dr. Teeth.",
          guava.Field({
            key: "appointment_time",
            fieldType: "calendar_slot",
            description: "Find a time that works for the caller",
            searchable: true,
          }),
          "Tell them their appointment has been confirmed and answer any questions before ending the call.",
        ],
      });
    } else {
      await call.hangup("Apologize for your mistake and hang up the call.");
    }
  });
  ```
</CodeGroup>

## Register an `on_search_query` handler

The `appointment_time` field has a special attribute `searchable=True` set. This turns the field into a "Search Field". Instead of providing a fixed list of choices, we will register an `on_search_query` handler.

The agent will invoke this handler to generate possible candidates for filling the field - at each invocation the agent provides a natural-language search query for us to match against.

In this example, we can simply forward that query to DatetimeFilter and return the result.

<CodeGroup>
  ```python Python theme={null}
  @agent.on_search_query("appointment_time")
  def search_appointments(call: guava.Call, query: str):
      return datetime_filter.filter(query, max_results=3)
  ```

  ```typescript TypeScript theme={null}
  agent.onSearchQuery("appointment_time", async (_call, query) => {
    return datetimeFilter.filter(query, { maxResults: 3 });
  });
  ```
</CodeGroup>

The agent may call this handler multiple times if the patient rejects the initial options or refines their availability.

## Handle task completion

`on_task_complete` fires once every item in the checklist is resolved. This is where you'd write the confirmed slot back to your database, trigger a confirmation SMS, or perform any other post-booking actions.

<CodeGroup>
  ```python Python theme={null}
  @agent.on_task_complete("schedule_appointment")
  def on_appointment_scheduled(call: guava.Call):
      call.hangup("Thank them for their time and hang up the call.")
  ```

  ```typescript TypeScript theme={null}
  agent.onTaskComplete("schedule_appointment", async (call) => {
    await call.hangup("Thank them for their time and hang up the call.");
  });
  ```
</CodeGroup>

## Place the outbound call

Use `call_phone` to initiate the call. Here is where you set initial values for variables - they'll be available inside your handlers via `get_variable()`.

<CodeGroup>
  ```python Python theme={null}
  # Run agent.call_phone to start the outbound call, setting our initial variables.
  agent.call_phone(
      from_number=os.environ["GUAVA_AGENT_NUMBER"],
      to_number=args.phone,
      variables={"patient_name": args.name},
  )

  # Or, test your agent in a text-based chat session in the terminal (no audio required).
  agent.chat(variables={"patient_name": args.name})
  ```

  ```typescript TypeScript theme={null}
  agent.callPhone(process.env.GUAVA_AGENT_NUMBER, toNumber, {
    patientName: patientName,
  });

  // Or, test your agent in a text-based chat session in the terminal (no audio required).
  agent.chat({ patientName: patientName });
  ```
</CodeGroup>

<Note>
  If you intend to dial multiple participants, use [Campaigns](/guides/campaign) instead of individual outbound calls. Campaigns offer settings for automatic retries, call windows, multiple origin phone numbers, and concurrency control.
</Note>

## Complete example

<CodeGroup>
  ```python Python theme={null}
  import logging
  import os
  import argparse
  import guava

  from guava import logging_utils, Agent
  from guava.examples.example_data import MOCK_APPOINTMENTS
  from guava.helpers.openai import DatetimeFilter

  logger = logging.getLogger("guava.examples.scheduling_outbound")

  agent = Agent(
      organization="Bright Smile Dental",
      purpose="Call patients to help them schedule a dental appointment.",
  )
  datetime_filter = DatetimeFilter(source_list=MOCK_APPOINTMENTS)

  @agent.on_call_start
  def on_call_start(call: guava.Call):
      call.reach_person(
          contact_full_name=call.get_variable("patient_name"),
      )

  @agent.on_reach_person
  def on_reach_person(call: guava.Call, outcome: str) -> None:
      if outcome == "unavailable":
          call.hangup("Apologize for your mistake and hang up the call.")
      elif outcome == "available":
          call.set_task(
              "schedule_appointment",
              checklist=[
                  "Tell them that it's been a while since their regular cleaning with Dr. Teeth.",
                  guava.Field(
                      key="appointment_time",
                      field_type="calendar_slot",
                      description="Find a time that works for the caller",
                      searchable=True,
                  ),
                  "Tell them their appointment has been confirmed and answer any questions before ending the call.",
              ],
          )

  @agent.on_search_query("appointment_time")
  def search_appointments(call: guava.Call, query: str):
      return datetime_filter.filter(query, max_results=3)

  @agent.on_task_complete("schedule_appointment")
  def on_appointment_scheduled(call: guava.Call):
      call.hangup("Thank them for their time and hang up the call.")

  if __name__ == "__main__":
      logging_utils.configure_logging()

      parser = argparse.ArgumentParser()
      parser.add_argument("phone", type=str, help="Phone number to call.")
      parser.add_argument("name", nargs="?", help="Name of the patient", default="Benjamin Buttons")
      args = parser.parse_args()

      agent.call_phone(
          from_number=os.environ["GUAVA_AGENT_NUMBER"],
          to_number=args.phone,
          variables={"patient_name": args.name},
      )
  ```

  ```typescript TypeScript theme={null}
  import * as guava from "@guava-ai/guava-sdk";
  import { DatetimeFilter } from "@guava-ai/guava-sdk/helpers";
  import { mockAppointmentsForFuture } from "@guava-ai/guava-sdk/example-data";

  const agent = new guava.Agent({
    organization: "Bright Smile Dental",
    purpose: "You are calling patients to help them schedule a dental appointment",
  });

  const datetimeFilter = new DatetimeFilter({
    sourceList: mockAppointmentsForFuture(),
  });

  agent.onCallStart(async (call: guava.Call) => {
    await call.reachPerson(await call.getVariable("patientName"));
  });

  agent.onSearchQuery("appointment_time", async (_call, query) => {
    return datetimeFilter.filter(query, { maxResults: 3 });
  });

  agent.onReachPerson(async (call: guava.Call, outcome: string) => {
    if (outcome === "available") {
      await call.setTask({
        taskId: "schedule_appointment",
        checklist: [
          "Tell them that it's been a while since their regular cleaning with Dr. Teeth.",
          guava.Field({
            key: "appointment_time",
            fieldType: "calendar_slot",
            description: "Find a time that works for the caller",
            searchable: true,
          }),
          "Tell them their appointment has been confirmed and answer any questions before ending the call.",
        ],
      });
    } else {
      await call.hangup("Apologize for your mistake and hang up the call.");
    }
  });

  agent.onTaskComplete("schedule_appointment", async (call) => {
    await call.hangup("Thank them for their time and hang up the call.");
  });

  export async function run(args: string[]) {
    if (args.includes("--chat")) {
      const patientName = args[args.indexOf("--chat") + 1] ?? "Benjamin Buttons";
      await agent.chat({ patientName });
      return;
    }

    const [toNumber, patientName = "Benjamin Buttons"] = args;

    if (!toNumber) {
      console.error("Usage: guava-example scheduling-outbound <phone> [name]");
      console.error("       guava-example scheduling-outbound --chat [name]");
      process.exit(1);
    }

    agent.callPhone(process.env.GUAVA_AGENT_NUMBER, toNumber, {
      patientName: patientName,
    });
  }

  if (import.meta.main) {
    run(process.argv.slice(2));
  }
  ```
</CodeGroup>


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