Skip to main content

Gateway Operations

The gateway is the channel runtime. It runs against one selected profile, serves remote adapters, routes approvals, and writes all state to that profile's local directory. Changing the active profile does not mutate a running gateway.

Profile boundary​

Every gateway process is bound to the profile selected at start time. The profile owns:

  • Gateway config (config.json)
  • Gateway state (gateway/)
  • Gateway logs (logs/gateway.log)
  • Channel tokens (.env)
  • Session database (sessions.sqlite, global but profile-scoped by profile_id)
# Run foreground gateway for the current active profile
estacoda gateway run

# Run foreground gateway for a specific profile
estacoda gateway run --profile work

Run and start​

estacoda gateway run # Foreground supervisor. Logs in terminal.
estacoda gateway run --dry-run # Readiness check only. No lock, no PID, no adapters.
estacoda gateway run --once # One supervisor pass, then exit.

estacoda gateway install # Install user-scope service.
estacoda gateway start # Start installed user-scope service.
estacoda gateway start --system # Start installed system-scope service.

gateway run is the foreground/debug path. Use it when you want logs attached to the current terminal and no service manager in the loop.

gateway run --dry-run checks adapter readiness, state directory writability, and gateway lock state without starting adapters or polling remote APIs. Use it to validate configuration before committing to a live process. gateway run --once performs one supervisor pass, including at most one eligible session-finalization job, and exits.

gateway start starts an installed service. It defaults to the selected profile's user-scope service. gateway start --system controls only the system service. If only a system service exists and --system is omitted, the command fails closed and tells the operator to rerun with --system.

gateway install is required before gateway start. gateway start --background is deprecated and no longer creates a detached unmanaged process; use service install/start for persistent operation.

Setup activation prompt​

The setup prompt that offers to install and start the gateway is titled EstaCoda Gateway.

The prompt appears:

  • During first-run onboarding when background memory finalization is enabled, including CLI-only setup with no channel.
  • During the existing-user Setup Editor when the first ready channel is newly configured.

In the existing-user Setup Editor, the prompt does not appear for non-channel changes or channel edits when a ready channel already existed. No setup path offers it when a managed gateway service is already installed or active.

First-run onboarding may still offer a post-apply launch prompt. Existing-user Setup Editor apply reports apply/verify state and exits the setup flow without a launch handoff. Use EstaCoda Doctor in the Setup Editor when you want read-only health checks and required fixes.

Stop​

estacoda gateway stop # Graceful SIGTERM, wait up to 10s
estacoda gateway stop --force # SIGKILL if graceful fails (unmanaged mode)

If a user-scope managed service exists, stop delegates to systemd or launchd. On systemd, --force still uses systemctl stop; it does not send SIGKILL directly. If no managed service exists, stop reads the PID file, sends SIGTERM, waits, then cleans up PID/state/lock files.

Restart​

estacoda gateway restart # Restart installed user-scope service
estacoda gateway restart --graceful # Alias for restart in v0.1.0
estacoda gateway restart --system # Restart system-scope service

restart delegates to the installed service using the same selection rules as start: user service by default, system service only with --system. If no managed service exists, it fails with installation guidance and does not create an unmanaged detached process. restart --graceful remains an alias for restart.

Managed services​

estacoda gateway install # Install user-scope systemd/launchd service
estacoda gateway install --profile work # Install for profile "work"
estacoda gateway install --force # Replace existing service unit
sudo estacoda gateway install --system --run-as-user estacoda # System scope

estacoda gateway uninstall # Remove user-scope service
sudo estacoda gateway uninstall --system # Remove system-scope service

Supported managers: Linux systemd (user and system), macOS launchd (user).

Each profile gets its own hash-suffixed unit or plist. Multiple profiles can have independent managed services.

Operational notes:

  • Services use an explicit HOME but not your interactive shell environment.
  • Put tokens and API keys in the profile .env, not only in shell exports.
  • systemd user services may stop on logout unless linger is enabled: sudo loginctl enable-linger $USER.
  • Service output goes to the journal. Use journalctl --user -u <unit> -f for user services.
  • Source-mode installs hardcode the absolute workspace path. If the repo moves, uninstall and reinstall.
  • Generated services invoke gateway run --profile <id>.
  • gateway start, gateway stop, and gateway restart default to the installed user service. Use --system for an installed system service.
  • The supervisor claims durable memory-finalization work only for its selected profile. One profile lease serializes checkpoints, memory.curate, automatic promotions, and operator memory writes.
  • Graceful shutdown aborts active finalization work; its lease expires and the durable job becomes eligible for bounded retry by the next running gateway.

Diagnostics​

estacoda gateway diagnose # Per-channel readiness check
estacoda gateway status # Full gateway status

gateway diagnose checks:

  • Telegram token presence, allowed users/chats
  • Discord token presence
  • Email IMAP/SMTP hosts, credentials, addresses
  • WhatsApp unofficial-API gate, isolated bridge package/readiness, auth directory writability, device pairing, user authorization, dmPolicy, groupPolicy, mode, and queue pressure
  • Cron directory permissions

Returns exit code 1 if any warnings exist.

gateway status surfaces:

  • Service manager state
  • Process state
  • Durable Task and cron host state
  • All configured channels with ready/configured/disabled state
  • Delivery router platforms
  • Active surface pointers
  • Pending approvals count
  • Cron job summary and recent failures
  • Memory-finalization queue counts: pending, running, retrying, and failed
  • Recent delivery errors
  • Missing config/env warnings
  • Durable Task counts and bounded worker summaries when the active runtime exposes running Attempts

The managed gateway service also owns durable Task wakeups and restart recovery, even when no channel adapter is enabled. A Task bound to another workspace remains waiting_for_host; the service does not rewrite its workspace identity. Graceful shutdown drains active Task work with channel turns. Terminal completion delivery uses a profile-owned, session-authorized outbox. If the process stops while an external send is ambiguous, restart marks that delivery failed instead of sending a possible duplicate.

Session-finalization rows live in global ~/.estacoda/sessions.sqlite with profile_id scope and an immutable message cutoff. They store no transcript copy and use the originating session workspace. If the managed service is stopped, queued work stays durable; it is not tied to the next interactive CLI launch. First-run setup can install the service for CLI-only use. Failed jobs can be inspected and retried with estacoda memory finalization; automatic retention keeps the latest 1,000 terminal rows per profile.

Channel enable and disable​

estacoda channels enable telegram
estacoda channels disable telegram

Valid names: telegram, discord, email, whatsapp (case-insensitive).

enable sets enabled: true in profile config. disable sets enabled: false. Both are idempotent and preserve other fields.

WhatsApp setup​

estacoda whatsapp

WhatsApp setup uses one shared QR flow. It can be launched from first-run onboarding optional capabilities, the existing-user Setup Editor, or the standalone estacoda whatsapp command. Each surface warns about the unofficial Baileys-backed transport, keeps dependencies inside scripts/whatsapp-bridge/, checks bridge package readiness, asks before dependency repair, renders a QR code in the terminal, and writes config/session state only after QR pairing succeeds. Dependency decline/failure and QR timeout/failure leave WhatsApp config unchanged. WhatsApp pairing-code setup is not exposed.

If no allowed senders are entered, the wizard writes dmPolicy: "pairing" so the device is linked but waiting for secure user authorization. Logged-out state requires explicit re-pair/reset of only the selected profile's WhatsApp auth directory.

Channel maturity​

ChannelMaturityNote
TelegramLive-provenFirst-party remote channel for v0.1.0
DiscordPresent, not live-provenAdapter exists; live validation incomplete
EmailPresent, not live-provenAdapter exists; attachments not supported
WhatsAppOperational with external API riskGated behind experimental: true. Uses unofficial Baileys API.

Approval queue​

Gateway approvals use a durable pending_approvals table in the session database. Rows are profile-scoped by profile_id. Pending approvals are ask-only: deterministic deny results and hardline blocks never become approvable queue rows.

Command payloads are transient and redacted after resolution. List and history surfaces use command preview/hash, not raw payload.

estacoda gateway approvals # List pending approvals
estacoda gateway approvals approve <id>
estacoda gateway approvals deny <id>

Busy policy​

When a user sends input while the agent is already processing:

PolicyBehavior
reject (default)Reply immediately with a busy message
queueBuffer and process after the current turn
interruptAbort the current turn and start a new one

Durable delegated Tasks do not hold the creating turn open, so later messages follow the configured busy-session policy. Control commands still bypass it. /stop aborts the foreground turn but does not implicitly cancel a Task whose handle has already been returned. /status can report durable Task counts and bounded worker state for running Attempts.

Configure per-channel in profile config.json:

{
"channels": {
"telegram": {
"busyPolicy": "queue",
"queueDepth": 3
}
}
}

queueDepth is clamped to [1, 10]. Invalid values fall back to reject with a warning.

Voice reply​

Gateway voice reply mode is available when voice state manager and TTS config are present. Auto-TTS is text-first and fail-open. Generated auto-TTS media is ephemeral and profile-temp scoped.

/voice on|all|off|status

/voice on sets the chat to voice_only. /voice all (or /voice tts) enables TTS replies. /voice off disables auto-TTS. /voice status reports the current mode.

Voice state is stored in:

~/.estacoda/profiles/<profile-id>/gateway/voice-mode.json

Telegram pairing​

Telegram uses bot-token-based pairing. The bot token must be present in the profile .env as ESTACODA_TELEGRAM_BOT_TOKEN (or the env var named in botTokenEnv). Allowed users and chats must be configured before the adapter accepts messages.

estacoda channels status telegram

Telegram streaming​

Telegram streaming is an experimental delivery option under channels.telegram.streaming.enabled. It defaults to enabled for configured Telegram channels. Set channels.telegram.streaming.enabled to false to opt out. When enabled, provider tokens edit Telegram messages during a turn, tool boundaries seal the current streamed message, tool progress appears below that sealed message, and later provider tokens start a new streamed message below the progress entry.

The stream is delivery-only. Final response.text remains authoritative, and session state, memory, tool execution, approvals, artifacts, and workflow state are unchanged. Partial edits use lightweight HTML escaping. The final edit or fallback delivery uses the normal Telegram formatter.

Operational constraints:

  • Telegram streaming runs before normal final-text routing. If streaming cannot deliver the completed answer, ChannelGateway falls back to normal DeliveryRouter delivery.
  • A turn abort signal is required.
  • Provider fallback/failure cleanup, Telegram flood-control degradation, oversized partial payloads, approval/artifact ambiguity, cancellation, or final edit failure can force normal final text fallback.
  • Active-turn degradation does not globally disable streaming for future turns.

Rollback is a config change:

{
"channels": {
"telegram": {
"streaming": {
"enabled": false
}
}
}
}

Restart or reload the gateway process bound to that profile after changing the setting.

Logs​

Gateway logs for the active profile:

~/.estacoda/profiles/<profile-id>/logs/gateway.log

In foreground mode (gateway run), logs also appear in the terminal. In managed-service mode, logs go to the profile log file or the system journal.

Failure modes​

SymptomLikely causeRepair
Gateway fails to startMissing token or env varAdd token to profile .env, run gateway diagnose
Channel not readyUnauthorized user or missing allowlistConfigure the channel allowlist (allowedUsers/allowedGroups; WhatsApp setup calls them allowed senders)
Wrong profile behaviorGateway started against a different profileCheck active-profile.json or use --profile
Stale runtime cacheOld session dataRestart gateway or run gateway restart
Approval expiryPending approval timed outRe-issue the command; approvals have a TTL
Hard safety blockCommand matches hardline floorRephrase the command; hard blocks cannot be overridden

Gateway update mode​

estacoda update --gateway is the non-interactive update path for managed gateway deployments.

  • Logs to ~/.estacoda/logs/update.log.
  • After a successful managed-source update, attempts to restart the gateway service through the service-manager abstraction.
  • If no managed service is detected, prints a manual restart instruction: estacoda gateway restart.
  • Never restarts arbitrary user processes.

For full update internals, see Update Operations.

Gateway teardown during uninstall​

estacoda uninstall tears down the gateway before removing install code or user data.

  • Uses the service-manager abstraction (estacoda gateway uninstall-service).
  • No raw pkill, killall, systemctl, or launchctl calls in the uninstall path.
  • On Termux, system service removal is skipped; known wrapper paths are cleaned best-effort.
  • Happens before code removal and before --purge data deletion.

What is not documented here​

  • Full slash-command reference is not included here; see Gateway for user-facing gateway behavior.