heya

Configuration reference

Heya reads ~/.config/heya/config.toml. Every block is optional; Heya runs with sensible defaults if the file is missing. An example config lives in the repo (config.example.toml) with a copy-paste template of all of this.

Models: [profiles.<name>]

A profile fully describes how to reach one model.

[profiles.local]
base_url = "http://localhost:11434/v1"
model = "qwen2.5-coder:14b"
provider_type = "local"        # local | api_key | oauth
context_window = 32768         # used for context compaction
# api_key_env = "SOME_KEY"     # the NAME of the env var holding the key
# timeout = 600                # request timeout in seconds
# vision = false               # set true to enable image attachments for this profile
# input_price = 0.40           # USD per 1M input tokens
# output_price = 1.20          # USD per 1M output tokens

Pick the active profile with heya --profile <name> or the HEYA_PROFILE env var. The default profile is local. Set vision = true for any profile whose model accepts image inputs (GPT-4o, Claude 3+, Gemini 1.5+, and similar). Heya also auto-detects vision from common model name patterns.

input_price and output_price are USD per 1,000,000 tokens. Heya has a built-in price table for common cloud models (Claude, GPT-4o, Gemini, and others) which /cost uses when no profile price is set. Local models are free, so /cost shows $0.0000 for any profile with provider_type = "local" or a localhost base URL. For models not in the built-in table, set both values explicitly; verify the figures against your provider's current pricing page before relying on them.

Cheaper secondary model: [routing]

Route compaction summaries and explicitly-marked sub-agent tasks to a cheaper profile. It names another profile. Unset means off.

[routing]
weak_profile = "local-small"

fallback names a chain of profiles to try, in order, when the primary model errors after retries (a rate limit, a provider outage). Heya fails over to the next model and keeps going instead of ending the session. Unset means off.

[routing]
fallback = ["cloud", "local-small"]

Context management: [context]

[context]
threshold = 0.85            # compact at this fraction of the window
reserve_tokens = 2048       # headroom for the reply
keep_recent_tokens = 4096   # verbatim recent-tail budget
task_token_budget = 200000  # per-task ceiling; 0 = unlimited

Working folders: [workspace]

The file and command tools are confined to these folders. The current directory is always allowed.

[workspace]
allowed_roots = ["~/projects", "/abs/path/site"]

Identity: [identity]

The persona Heya writes as. Leave it unset for the generic default voice. The writing style is bundled either way.

[identity]
name = "Your Name"
role = "WooCommerce Happiness Engineer"
[search]
provider = "duckduckgo"     # duckduckgo (keyless) | brave | tavily
# api_key_env = "BRAVE_API_KEY"

Memory: [memory]

Heya's long-term memory has two homes. User-level facts (preferences, voice, feedback) live in the global store, ~/.config/heya/memory (or path). Facts about a project land in that project's own store, <project>/.heya/memory, and load only when you work there. The model can also target a store explicitly when saving (scope on remember).

[memory]
project = true                       # false keeps everything global
# path = "~/somewhere/heya-memory"   # global store location

Sub-agents can read memory (the index plus read_memory) but can never save, update, or forget; writes stay with the root agent.

One privacy note: .heya/memory lives in the repo tree, so it can be committed and shared. Keep personal data in global memory, and add .heya/memory/ to .gitignore when the project is public.

Knowledge base: [knowledge]

Folders of your own markdown notes: support macros, runbooks, past resolutions, personal reference. Heya lists what it finds to the model, reads a note on demand (read_knowledge), searches across them (search_knowledge), and cites the notes it used. The folders are read-only to Heya: nothing is ever written there.

[knowledge]
enabled = true
# paths = ["~/dotbrain", "~/notes/support"]   # replaces the defaults when given

Defaults when paths is not set: ~/.config/heya/knowledge and the project's .heya/knowledge. Set enabled = false to turn discovery off.

Hosting your Claude ecosystem

Heya reads your existing Claude directories by default. Turn any of them off or point them elsewhere. See hosting-claude-ecosystem.md.

[skills]
enabled = true
# paths = ["~/.claude/skills"]

[plugins]
enabled = true
# paths = ["~/.claude/plugins/cache"]
# disabled = ["plugin-name"]

[commands]
enabled = true

[agents]
enabled = true

[hooks]
enabled = false             # hooks run shell, so they are off by default
# sources = ["~/.claude/settings.json"]

Browser: [browser]

[browser]
headless = true             # set false to watch a reproduction run

WordPress: [wordpress]

A default local WordPress path for the wp-cli and log tools.

[wordpress]
path = "~/sites/my-store"

Memory: [memory]

Where Heya keeps its remembered facts.

[memory]
path = "~/.config/heya/memory"

Project instructions

When you run Heya inside a project, it reads the nearest AGENTS.md and CLAUDE.md (walking up to the git root) and adds them to its context, so it follows that repo's conventions. They are text only, never run, and they do not override Heya's safety rules. Turn it off if you prefer:

[project]
read_instructions = false

Command auto-approve: [approval]

A list of command prefixes that bypass the per-command prompt. A command auto-approves only when its argv starts with one of these prefixes and contains no shell metacharacters (;, &&, |, >, and similar). Anything not in the list still prompts. The default is none.

[approval]
allow = ["wp plugin list", "wp option get", "git status"]

Security note: shell metacharacters are never auto-approved, even for a listed prefix. A command like wp plugin list && rm -rf / still hits the gate.

Web safety: [web]

[web]
block_metadata = true   # block cloud-metadata / link-local addresses (default true)

web_fetch and browser_navigate reach any real site by default. With block_metadata = true (the default), they refuse to connect to link-local addresses such as 169.254.169.254 (cloud instance-metadata). Set to false only if your network genuinely needs link-local access.

Background agents

[agents]
max_background = 4

max_background caps how many background agents run at once. The default is 4.

Updates: [update]

[update]
check = false

By default Heya checks PyPI about once a day for a newer version and shows a one-line notice at startup. The check runs in the background, never delays startup, and is silent if you are offline. Set check = false to turn it off. It only contacts the public PyPI endpoint and sends no data about you.