config

package
v0.16.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package config is the gateway's self-hosted configuration, split the way memcode already splits everything (and the way Hermes does): secrets — the bot tokens — live in the global .env (written by `memcode gateway setup`, never hand-set), and NON-secret settings live here in gateway.yaml. Both sit in the global memcode config dir (per machine, not per project). This file names the secret env keys and models the YAML so one place owns the whole shape.

Index

Constants

View Source
const (
	EnvTelegramToken  = "TELEGRAM_BOT_TOKEN"
	EnvDiscordToken   = "DISCORD_BOT_TOKEN"
	EnvSlackAppToken  = "SLACK_APP_TOKEN"
	EnvSlackBotToken  = "SLACK_BOT_TOKEN"
	EnvGitHubSecret   = "GITHUB_WEBHOOK_SECRET"
	EnvWhatsAppToken  = "WHATSAPP_ACCESS_TOKEN"
	EnvWhatsAppVerify = "WHATSAPP_VERIFY_TOKEN"
	EnvWhatsAppSecret = "WHATSAPP_APP_SECRET" // Meta app secret — signs inbound POSTs
	// Email: a DEDICATED mailbox the agent answers (app password for Gmail/
	// Outlook), never your personal inbox. Hosts may carry :port (defaults
	// 993 IMAP-SSL / 587 SMTP-STARTTLS).
	EnvEmailAddress  = "EMAIL_ADDRESS"
	EnvEmailPassword = "EMAIL_PASSWORD"
	EnvEmailIMAPHost = "EMAIL_IMAP_HOST"
	EnvEmailSMTPHost = "EMAIL_SMTP_HOST"
	// Signal: a signal-cli daemon in native HTTP mode owns the account (linked
	// device via `signal-cli link`; use a dedicated number). The URL points at
	// that daemon; the number is our own account (loop prevention).
	EnvSignalNumber = "SIGNAL_NUMBER"
	EnvSignalCLIURL = "SIGNAL_CLI_URL" // optional; default http://127.0.0.1:8080
	// Matrix: any homeserver, access-token login. Plain rooms only (no E2EE).
	EnvMatrixHomeserver = "MATRIX_HOMESERVER"
	EnvMatrixToken      = "MATRIX_ACCESS_TOKEN"
	// Mattermost: self-hosted server URL + a bot (or personal) access token.
	EnvMattermostURL   = "MATTERMOST_URL"
	EnvMattermostToken = "MATTERMOST_TOKEN"
	// SMS via Twilio. The webhook URL (signature input) is non-secret config:
	// channels.sms.webhook_url in gateway.yaml.
	EnvTwilioAccountSID = "TWILIO_ACCOUNT_SID"
	EnvTwilioAuthToken  = "TWILIO_AUTH_TOKEN"
	EnvTwilioFromNumber = "TWILIO_FROM_NUMBER"
	// Microsoft Teams (Bot Framework / Azure bot registration).
	EnvTeamsAppID       = "TEAMS_APP_ID"
	EnvTeamsAppPassword = "TEAMS_APP_PASSWORD"
	EnvTeamsTenantID    = "TEAMS_TENANT_ID"
	// Google Chat: path to the app's service-account JSON key. The verification
	// audience (project number) is non-secret: channels.googlechat.audience.
	EnvGoogleChatSAKey = "GOOGLE_CHAT_SA_KEY"
)

Secret env keys. These live in the global .env (provider.GlobalEnvPath), NOT in gateway.yaml — a bot token is a secret, and secrets belong in .env. The names are each platform's OWN conventional variable (no memcode prefix), so a user can paste the value straight from the platform's docs and so a config imported from another gateway (Hermes, OpenClaw) drops in unchanged. Only memcode's own infra (MEMCODE_API_TOKEN, …) carries the project prefix.

Variables

This section is empty.

Functions

func CanonicalRoot added in v0.13.0

func CanonicalRoot(path string) (string, error)

CanonicalRoot expands a leading ~ and resolves path to an absolute, symlink-free directory. The resolved directory is the execution authority — a task's root must equal it, so registration alone can't be tricked by a symlink into executing elsewhere.

func ContextPath added in v0.13.0

func ContextPath(session string) (string, error)

ContextPath is where the gateway writes a job's composed supplemental context, keyed by session id. Global and gateway-owned (never under a repo's .memcode); the spawned agent child self-discovers it by session id — so no jobs.Spawn signature change is needed to carry per-task context.

func Dir added in v0.13.0

func Dir() (string, error)

Dir returns the global memcode config directory: $XDG_CONFIG_HOME/memcode or ~/.config/memcode. Per machine, not per project — the home for gateway.yaml, the global .env, and the gateway's OWN operational state (durable inbox, singleton lock, event log). A gateway therefore never writes its operational state into a repo's .memcode.

func EnabledChannels

func EnabledChannels() []string

EnabledChannels lists channels whose required secret(s) are present in the environment. The global .env must be loaded first (provider.LoadDotEnv).

func MediaDir added in v0.15.0

func MediaDir() (string, error)

MediaDir is the gateway's media spool: downloaded inbound attachments and synthesized voice replies, content-addressed (<sha256>.<ext>). Gateway-owned and global like the rest of the operational state; pruned with the inbox. The spool is the TRUST BOUNDARY for job media: jobs receive spool IDs, never paths, and resolve them only inside this directory.

func Path

func Path() (string, error)

Path returns the gateway settings file inside Dir(): $XDG_CONFIG_HOME/memcode/gateway.yaml or ~/.config/memcode/gateway.yaml.

func PersonaHome added in v0.13.0

func PersonaHome(id string) (string, error)

PersonaHome is a persona's state directory: ~/.memcode/agents/<id>, holding its own memory.md, MEMCODE.md, and skills. Distinct from the project (the cwd) and from user-global ~/.memcode (shared by all personas).

func Save

func Save(s Settings) error

Save writes gateway.yaml atomically. 0600 — it holds no secrets, but the allow-list of user ids is sensitive on a shared host, so keep it owner-only.

Types

type Channel

type Channel struct {
	// AllowFrom is the set of stable user ids permitted to drive the agent through
	// this channel; "*" allows anyone on the channel. Empty means no one is
	// allowed (unless the global AllowAll is set). Use stable ids, not @handles —
	// authorization is on ids. Secrets never live here; bot tokens are in the .env.
	AllowFrom []string `yaml:"allow_from,omitempty"`
	// RespondToAll makes the bot act on every message in a group/channel it can
	// see. Default false: in a group the bot only acts when it is mentioned, so it
	// doesn't spawn a paid agent job for ordinary chatter. Direct messages always
	// trigger regardless of this setting.
	RespondToAll bool `yaml:"respond_to_all,omitempty"`
	// Tier routes this channel's agent runs to a stronger model tier: "strong"
	// (the strong vendor's balanced tier) or "frontier" (top). Empty is automatic
	// routing (cheap for routine work). Lets a code-review channel run strong while
	// a status channel stays cheap.
	Tier string `yaml:"tier,omitempty"`
	// Agent binds this channel to a persona by id (see Settings.Agents). Empty
	// means the gateway's plain default (no persona context layered on).
	Agent string `yaml:"agent,omitempty"`
	// Projects narrows which registered projects this channel may execute
	// against (/project and the default). Empty means every enabled project —
	// fine for a solo operator, too coarse for a shared group channel: list the
	// project ids a channel is for, and /project can't point it anywhere else.
	Projects []string `yaml:"projects,omitempty"`
	// ReplyTo (GitHub) routes an autonomous result to a chat conversation, e.g.
	// "telegram:123456".
	ReplyTo string `yaml:"reply_to,omitempty"`
	// PhoneNumberID (WhatsApp) is the non-secret Cloud API sender id.
	PhoneNumberID string `yaml:"phone_number_id,omitempty"`
	// Active (WhatsApp) gates the adapter: it stays inert (built but not mounted)
	// until the Meta business is verified and the operator flips this to true —
	// verification is an external account state the gateway can't detect.
	Active bool `yaml:"active,omitempty"`
	// Poll (email) is the mailbox poll cadence as a Go duration ("15s", "1m").
	// Empty uses the adapter default.
	Poll string `yaml:"poll,omitempty"`
	// WebhookURL (sms) is the EXACT public URL Twilio posts to — the signature
	// input, which a proxied server can't reliably reconstruct. Without it the
	// SMS webhook rejects everything (fail closed).
	WebhookURL string `yaml:"webhook_url,omitempty"`
	// Audience (googlechat) is the app's project number — the JWT audience
	// inbound Chat events are verified against.
	Audience string `yaml:"audience,omitempty"`
	// VoiceReplies controls synthesized speech replies on channels that can
	// carry voice notes (Telegram, WhatsApp, Signal, Discord, Matrix):
	// "off" (default — voice output costs money and speaks replies aloud, so
	// it is a deliberate opt-in), "in_kind" (a voice note in gets a voice
	// reply out), or "always". The full text reply is always sent too.
	VoiceReplies string `yaml:"voice_replies,omitempty"`
}

Channel is a channel's non-secret configuration.

type Persona added in v0.13.0

type Persona struct {
	Type string `yaml:"type,omitempty"` // assistant | coding | research (coarse behavior hint)
}

Persona is a durable agent identity: a home directory (~/.memcode/agents/<id>) holding its own memory.md, MEMCODE.md, and skills, plus a coarse type. It is NOT a project and NOT the `memcode run` CLI command — the persona's context is composed and handed to the coding engine as generic supplemental context.

type Project added in v0.13.0

type Project struct {
	Path    string `yaml:"path"`
	Enabled bool   `yaml:"enabled"`
}

Project is a registered working directory. Path is the configured location; the AUTHORITY is its canonicalized form (see ResolveProject), resolved at use time so a symlink swap can't redirect execution. Registration (is this path runnable at all?) is deliberately distinct from authorization (may THIS surface run against it?): by default every allow-listed gateway principal may execute against every enabled project, and channels.<name>.projects narrows that to a per-channel set (see ProjectAllowed) — the right grain for a shared group channel. A finer per-principal policy is a later primitive.

type Schedule

type Schedule struct {
	Name      string `yaml:"name"`
	Every     string `yaml:"every,omitempty"`
	Cron      string `yaml:"cron,omitempty"`
	Task      string `yaml:"task"`
	DeliverTo string `yaml:"deliver_to"`
}

Schedule is a time-triggered task: the gateway runs Task on the given cadence and posts the result to DeliverTo ("<channel>:<conversation>", e.g. "telegram:123456"). Set exactly one of Every (a Go duration like "24h" or "30m") or Cron (a 5-field cron expression like "0 9 * * 1-5"). This is what turns the gateway from purely reactive into autonomous.

type Settings

type Settings struct {
	// AllowAll disables the per-channel allow-list entirely — anyone who can reach
	// a channel may drive the agent. Defaults false: the gateway is default-deny,
	// so an unconfigured channel answers no one until you add yourself.
	AllowAll  bool               `yaml:"allow_all,omitempty"`
	Webhook   Webhook            `yaml:"webhook,omitempty"`
	Channels  map[string]Channel `yaml:"channels,omitempty"`
	Schedules []Schedule         `yaml:"schedules,omitempty"`
	// Projects is the registry of working directories the gateway may execute
	// against (added with `memcode project add`). A remote message may select
	// among these; it can never manufacture an arbitrary filesystem root.
	Projects map[string]Project `yaml:"projects,omitempty"`
	// DefaultProject is the project id the gateway executes against when a task
	// carries no explicit project (all of them, until conversations land).
	DefaultProject string `yaml:"default_project,omitempty"`
	// Agents is the registry of durable personas (internally Persona) — an
	// assistant identity with its own home (memory/skills/instructions), distinct
	// from any project. A channel binds to one by name (Channel.Agent).
	Agents map[string]Persona `yaml:"agents,omitempty"`
}

Settings is the NON-secret gateway configuration (gateway.yaml). A channel's presence is decided by its secret in .env (see EnabledChannels); the per-channel blocks under Channels carry the non-secret knobs and the access list. The shape mirrors what Hermes and OpenClaw use (a channels.<name> object), so a config can be imported from either with a direct field mapping.

func Load

func Load() (Settings, error)

Load reads gateway.yaml, returning zero Settings if the file does not exist.

func (Settings) Allowed

func (s Settings) Allowed(channel, principal string) bool

Allowed reports whether principal may drive the agent through channel. It is default-deny: only the global AllowAll, an explicit "*", or an exact principal match grants access.

func (Settings) Get

func (s Settings) Get(name string) Channel

Get returns the settings for a channel (a zero Channel if unset), so callers don't repeat nil-map/missing-key handling.

func (Settings) ProjectAllowed added in v0.13.0

func (s Settings) ProjectAllowed(channel, project string) bool

ProjectAllowed reports whether channel may execute against project id. An empty channels.<name>.projects list allows every registered project (the registry itself is still the boundary — see ResolveProject); a non-empty list is exhaustive for that channel.

func (Settings) ResolveProject added in v0.13.0

func (s Settings) ResolveProject(id string) (string, error)

ResolveProject resolves a registered project id to its canonical filesystem root, enforcing the registration boundary: only a registered + enabled project resolves, and the returned root — not the raw config string — is the authority a task executes against.

type Webhook

type Webhook struct {
	Addr string `yaml:"addr,omitempty"`
}

Webhook is the inbound HTTP listener shared by GitHub/WhatsApp. Defaults to ":8787" when a webhook-using channel is enabled but no address is set.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL