Apus is a self-hosted gateway that connects Discord, Google Chat, iMessage, Matrix, Microsoft Teams, Signal, Slack, Telegram, WhatsApp, Zalo, and more to AI agents. This guide covers the "personal assistant" setup: a dedicated WhatsApp number that behaves like your always-on AI assistant. Setting up a shared gateway for several people instead? See Team setup.

Good defaults first

A connected agent is a capable one: depending on your tool policy it can run commands, work with files in its workspace, and message people on your behalf. The defaults keep that power scoped to you; a few settings are worth confirming up front:

Prerequisites

The two-phone setup (recommended)

You want this:

flowchart TB
    A["<b>Your Phone (personal)<br></b><br>Your WhatsApp<br>+1-555-YOU"] -- message --> B["<b>Second Phone (assistant)<br></b><br>Assistant WA<br>+1-555-ASSIST"]
    B -- linked via QR --> C["<b>Your Mac (apus)<br></b><br>AI agent"]

If you link your personal WhatsApp to Apus, every message to you becomes "agent input". That's rarely what you want.

5-minute quick start

  1. Pair WhatsApp Web (shows QR; scan with the assistant phone):
apus channels login
  1. Start the Gateway (leave it running):
apus gateway --port 18789
  1. Put a minimal config in ~/.apus/apus.json:
{
  gateway: { mode: "local" },
  channels: { whatsapp: { allowFrom: ["+15555550123"] } },
}

Now message the assistant number from your allowlisted phone.

When onboarding finishes, Apus auto-opens the dashboard and prints a clean (non-tokenized) link. If the dashboard prompts for auth, paste the configured shared secret into Control UI settings. Onboarding uses a token by default (gateway.auth.token), but password auth works too if you switched gateway.auth.mode to password. To reopen later: apus dashboard.

Give the agent a workspace (AGENTS)

Apus reads operating instructions and "memory" from its workspace directory.

By default, Apus uses ~/.apus/workspace as the agent workspace, and creates it (plus starter AGENTS.md, SOUL.md, IDENTITY.md, USER.md) automatically on onboarding or first agent run. Put environment-specific tool notes in the ## Tools section of AGENTS.md. BOOTSTRAP.md is only created for a brand-new workspace and should not come back after you delete it. MEMORY.md is optional and never auto-created; when present, it loads for normal sessions. Subagent sessions only inject AGENTS.md.

Treat this folder like Apus's memory and make it a git repo (ideally private) so your AGENTS.md and memory files are backed up. If git is installed, brand-new workspaces are auto-initialized with git init.

To create the workspace and config folders without running the full onboarding wizard:

apus setup --baseline

(Bare apus setup is an alias for apus onboard and runs the full interactive wizard.)

Full workspace layout + backup guide: Agent workspace Memory workflow: Memory

Optional: choose a different workspace with agents.defaults.workspace (supports ~).

{
  agents: {
    defaults: {
      workspace: "~/.apus/workspace",
    },
  },
}

If you already ship your own workspace files from a repo, you can disable bootstrap file creation entirely:

{
  agents: {
    defaults: {
      skipBootstrap: true,
    },
  },
}

The config that turns it into "an assistant"

Apus defaults to a good assistant setup, but you'll usually want to tune:

Example:

{
  logging: { level: "info" },
  agents: {
    defaults: {
      workspace: "~/.apus/workspace",
      thinkingDefault: "high",
      timeoutSeconds: 1800,
      // Start with 0; enable later.
      heartbeat: { every: "0m" },
    },
    entries: {
      main: {
        groupChat: {
          mentionPatterns: ["@apus", "apus"],
        },
      },
    },
  },
  channels: {
    whatsapp: {
      allowFrom: ["+15555550123"],
      groups: {
        "*": { requireMention: true },
      },
    },
  },
  session: {
    scope: "per-sender",
    resetTriggers: ["/new", "/reset"],
    reset: {
      mode: "daily",
      atHour: 4,
      idleMinutes: 10080,
    },
  },
}

Sessions and memory

Heartbeats (proactive mode)

By default, Apus runs a heartbeat every 30 minutes — or every hour when Anthropic OAuth/token auth is configured (including Claude CLI reuse). See Heartbeat for the full defaults. The prompt is: Follow the heartbeat monitor scratch context when provided. Recurring tasks are automations; create or change their schedules with the automations tool, not heartbeat scratch. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply NO_REPLY. Set agents.defaults.heartbeat.every: "0m" to disable recurring cadence. Targeted event-driven wakes, such as background exec completion follow-ups, remain available and do not create a recurring schedule. Heartbeat checklists live in the monitor's cron scratch (see Heartbeat); apus doctor --fix migrates a legacy workspace HEARTBEAT.md into it.

{
  agents: {
    defaults: {
      heartbeat: { every: "30m" },
    },
  },
}

Media in and out

Inbound attachments (images/audio/docs) can be surfaced to your command via templates:

The older {{MediaPath}}, {{MediaUrl}}, {{MediaType}}, and {{MediaDir}} names remain available as deprecated compatibility aliases.

Outbound attachments from the agent use structured media fields on the message tool or reply payload, such as media, mediaUrl, mediaUrls, path, or filePath. Example message-tool arguments:

{
  "message": "Here's the screenshot.",
  "mediaUrl": "https://example.com/screenshot.png"
}

Apus sends structured media alongside the text. Legacy final assistant replies may still be normalized for compatibility, but tool output, browser output, streaming blocks, and message actions do not parse text as attachment commands.

If you must use a legacy final-reply MEDIA: line, keep it as standalone plain text. Markdown wrappers, code fences, and inline prose such as **MEDIA:/path.png**, `MEDIA:/path.png`, or Here is the image: MEDIA:/path.png stay text and do not attach media. See Rich output protocol.

Local-path behavior follows the same file-read trust model as the agent:

Keep sensitive files outside the agent-readable filesystem, or keep tools.fs.workspaceOnly: true for stricter local-path sends.

When the message tool cannot stage an attachment for the current conversation, its error identifies the file and the reported reason, such as a missing file or an unsupported local format. Being inside the workspace does not make every file type eligible for host-local attachment reads.

Operations checklist

apus status          # local status (creds, sessions, queued events)
apus status --all    # full diagnosis (read-only, pasteable)
apus status --deep   # check channels (WhatsApp Web + Telegram + Discord + Slack + Signal)
apus health --json   # gateway health snapshot over the WS connection

Logs live under /tmp/apus/: apus-YYYY-MM-DD.log for the default profile and apus-<profile>-YYYY-MM-DD.log for named profiles.

Next steps

Related