Configuration
Environment variables, where secrets live, and the Database, GitHub and Helpdesk packs.
One name per knob
Every variable has one agent-neutral AGENT_* name, the only one the plugin reads. Several homes can run one plugin, so a per-agent spelling could never mean "this agent only". Before 0.8.0 a KEVIN_* spelling overrode its AGENT_* twin; upgrade renames any your home still holds (Upgrades).
Set them per home, in <HOME>/.claude/settings.local.json. The file's location is what scopes the value to that agent, and every host reads it: Claude Code directly, Codex through the shell env policy generated from it. Keep per-home values (the home, code path, repos, databases) out of a user-level settings file: it is machine-wide, so one value reaches every agent home on the machine.
| Variable | Purpose | Default |
|---|---|---|
AGENT_HOME | Path to the home. Normally unset: the home is found by walking up for .state/. Useful as a one-off prefix for the CLI outside a home | resolved from the launch dir |
AGENT_TIMEZONE | IANA timezone for dates | system timezone |
AGENT_HOME_TIMEZONE | Home-base timezone; when it differs from the live one, session context flags you as traveling | unset |
AGENT_KNOWLEDGE · AGENT_PROJECTS · AGENT_REPORTS | Move those directories elsewhere (a synced folder, another repo) | under the home |
AGENT_CODE_PATH | Your primary codebase. Grounds code tasks and surfaces its git activity in session context | none |
AGENT_GIT_REPOS | Comma-separated extra repos shown in the SessionStart git block | derived from AGENT_CODE_PATH |
AGENT_LOG_LEVEL · AGENT_LOG_FILE | debug · info · warn · error; off disables file output | info · .state/logs/app.log |
MARKDOWN_URL | Opener for markdown links on the dashboard | obsidian://open?path={path}&paneType=tab |
If you move knowledge/ or projects/ outside the home after init, add the matching permissions.allow entries to settings.json yourself; the wizard does it only during scaffold.
Where keys live
| Sensitivity | File | Examples |
|---|---|---|
| Secret | <HOME>/.state/secrets/.env (and secrets/google/, secrets/slack/) | SERPAPI_KEY, OPENPAGERANK_API_KEY, PERPLEXITY_API_KEY, GITHUB_TOKEN_<OWNER>, GITHUB_TOKEN, SUPPORTCANDY_APP_PASSWORD[_<NAME>], PYLON_API_TOKEN[_<NAME>], AGENT_DB_*, the Google OAuth client; secrets/.env.claude.json holds an agent's own Claude subscription token, which Claude Code reads when started with agent (Billing per agent) |
| Non-secret | <HOME>/.claude/settings.local.json env block | GSC_SITE_URL, SUPPORTCANDY_URL[_<NAME>], SUPPORTCANDY_USERNAME[_<NAME>], AGENT_SLACK_CLIENT_ID, AGENT_CODE_PATH, AGENT_GIT_REPOS |
Both are gitignored. The secrets store is deny-gated: Kevin's own shell cannot read it, only the MCP server can. Init owns universal-infra keys (only the optional codebase pair); configure-skills plants a pack's keys as empty placeholders when you activate it. You fill values in your editor, never in chat.
Database pack
database_list, database_schema, and database_query run read-only Postgres queries. Connections are discovered by convention: every AGENT_DB_<NAME> line in the secrets store becomes a connection named <name>.
# <HOME>/.state/secrets/.env
AGENT_DB_APP=postgres://user:pass@localhost:5432/app_dev
AGENT_DB_ANALYTICS=postgres://user:pass@host:5432/analyticsEvery query runs inside a BEGIN READ ONLY transaction with a statement timeout and is rolled back, so Postgres rejects any write. For tighter control, point the connection string at a SELECT-only role. database_list reports host, port, and database, never credentials. A column holding keys, tokens or passwords is checked with aggregates only (count(col), a count of its nulls, min(length(col))), never selected, grouped by or cut with substring, so its values stay out of the conversation.
database_fork is the one write tool: it clones a local database via CREATE DATABASE <fork> TEMPLATE <source> (pure SQL, no dump files) so you can run destructive schema changes against a scratch copy. It refuses remote hosts, names the fork after the current branch, can repoint an env file at it, and drop: true tears it down. setup-worktree uses it to give a worktree its own database.
GitHub pack
The engineer skill's pull-request playbooks and adversarial-review run on this pack.
Eighteen tools wrapping the gh CLI (brew install gh). Reads: PR list, view, inline review comments, diff, checks; run list, view, and failed-step logs; issue list and view; and github_sweep, everything waiting on you across your PRs, reviews and issues. github_fast_forward fast-forwards local default branches during sync. Writes: open, edit, comment on and label issues, open a PR from a pushed branch, and replace a PR's title or description (see Writes). Nothing merges, closes or re-runs a workflow.
Auth is a fine-grained, read-only personal access token per repo owner, because a fine-grained token reaches one org's or one user's repos only. Each lives in the secrets store as GITHUB_TOKEN_<OWNER>, the owner's login uppercased with hyphens as underscores (my-org → GITHUB_TOKEN_MY_ORG), and every tool picks the one for the repo it is called on. GITHUB_TOKEN is an optional catch-all for any owner without a key of its own. With neither, the tools use your own gh auth login, and the sync fast-forward skips that repo. configure-skills lists the owners behind your checkouts and walks you through minting each token: resource owner set to that owner, repository permissions Pull requests · Issues · Metadata · Actions · Contents, all read, plus write on whatever you want Kevin to change. The token decides: with read-only permissions the pack stays read-only.
An org can require approval before a fine-grained token reaches its repos. Until it approves, put a classic token under that owner's key instead, with repo, read:org, read:user and user:email and an expiry date. A classic token reaches every repo your account can, not one owner's.
GITHUB_TOKEN_MY_ORG=github_pat_...
GITHUB_TOKEN_MY_LOGIN=github_pat_...
GITHUB_TOKEN=github_pat_... # optional catch-allTurning the pack on also denies gh in Kevin's own shell (Bash(gh) and Bash(gh *) in permissions.deny, and a forbidden rule in Codex's .codex/rules/kevin.rules), so GitHub is reached only through these tools, under the token picked for each repo.
Two permission gotchas
Grant Actions: Read (workflow runs and logs). Do not grant Workflows: despite the name it is write access to the workflow files. Contents: Read is required for git fetch and diffs and grants no push ability. There is no Checks permission for fine-grained tokens, so github_pr_checks returns 403 where CI reports as check runs; read build state from the run tools instead.
When a call omits repo, Kevin resolves it from the origin remote of AGENT_CODE_PATH, then the first AGENT_GIT_REPOS entry. An explicit repo="owner/repo" always wins.
Writes
github_issue_create, github_issue_comment (both can attach screenshots and videos) and github_issue_update (title, description, labels and milestone) let one agent hand work to another: a support agent opens a bug, the owner labels it, and the engineer's agent builds what was accepted, moving it to in progress when work starts and setting its milestone to the release it ships in when the fix is done. github_pr_update replaces a pull request's title or description; the engineer skill offers it on your own PR, after you approve the new text. github_pr_create opens a pull request from a branch you've pushed. No pack grants it: it sits in permissions.ask, so every call waits for you, in auto mode too. Nothing closes an issue or edits someone else's comment.
Writes run on the same per-owner token as the reads, and the token's permissions decide what they may do:
| To | Give the owner's token |
|---|---|
| Open, edit, comment on and label issues | Issues: Read and write |
| Open a PR, or change its title or description | Pull requests: Read and write |
Attaching screenshots or videos needs nothing more on the token, but your role on the repo must be Write. A write the token isn't allowed fails with a message naming the permission to add. An owner with no token runs as your own gh auth login, limited by your role on the repo. Attachments need gh 2.99 or later and take png, jpg, gif, webp, svg, mp4, mov or webm up to 10 MB; a log goes in the issue body as text. Labels and milestones must already exist in the repo.
Helpdesk pack
helpdesk_tickets lists tickets updated in a window, helpdesk_ticket reads one ticket's whole conversation with each message tagged customer, agent, note or event, and helpdesk_attachment downloads a log or screenshot into a temp folder outside the home. helpdesk_accounts and helpdesk_lookups list customer companies and reference data, and helpdesk_stats counts a queue's tickets in a window without reading their text. helpdesk_note and helpdesk_assign are the writes: a private note agents see and the customer never does, so the agent can leave a draft reply for a person to edit and send, and a SupportCandy ticket's assignee, so the ticket reaches that person. helpdesk_assign reads the ticket back and fails when the assignee didn't change. No tool replies, changes a ticket's status or closes it. Tickets carry the people on them (customer, assignee, each message's author), since finding the account and answering by name is the work. The helpdesk skill works the queue on top of these tools; see Helpdesk.
SupportCandy (the WordPress plugin) needs five things on the site, which configure-skills walks you through:
- SupportCandy → Settings → Advanced → turn on REST API. While it is off, every call returns 404.
- An agent role that can view tickets and view and add private notes, and nothing else. SupportCandy checks reply and private-note rights separately, so this role can leave notes but cannot answer a customer. For the agent to assign tickets too, tick Assign Agents for the tickets it should hand off (unassigned, and assigned to it).
- A dedicated WordPress user, added under SupportCandy → Agents with that role. Never a person's own login: every note is attributed to this user.
- The API kept out of the page cache. A caching plugin with REST caching on saves the answers to the agent's reads and serves them to anyone who requests the same address, login or not, because the application password travels in a header rather than a cookie: reads go stale, and the ticket list is readable without a login. In LiteSpeed Cache, turn off Cache → Cache REST API or add
/wp-json/supportcandy/to Excludes → Do Not Cache URIs, then purge. To check, request the ticket list with no login:401is right,200means the cache is serving it. - An application password for it (Users → the user → Application Passwords, HTTPS sites only).
# <HOME>/.claude/settings.local.json env
SUPPORTCANDY_URL=https://help.example.com
SUPPORTCANDY_USERNAME=support-assistant
# <HOME>/.state/secrets/.env
SUPPORTCANDY_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxxPylon needs PYLON_API_TOKEN in the secrets store, minted by a Pylon admin under Settings → API. Notes are posted as the token's user.
Several sites or workspaces
A connection is one SupportCandy site or one Pylon workspace, and it usually serves one project's support. The configure-skills walk asks which project each connection is for: a project's connection is named after its slug, and No project is the default connection on the plain keys above. Every key a named connection uses carries its name as a suffix, uppercased with anything but letters and digits as _:
# the acme-shop project's WordPress helpdesk
SUPPORTCANDY_URL_ACME_SHOP=https://help.acme-shop.test
SUPPORTCANDY_USERNAME_ACME_SHOP=support-assistant
SUPPORTCANDY_APP_PASSWORD_ACME_SHOP=xxxx xxxx xxxx xxxx xxxx xxxx
# the acme project's Pylon workspace
PYLON_API_TOKEN_ACME=pylon_...A call or a queue profile that names no connection uses the default, or the only connection there is. A named connection reads only its own keys and never falls back to the default, since those reach a different site; a missing key stops with an error that names the keys it needs. helpdesk_connections lists what's configured, without credentials, and suffixed secrets are masked in the logs like the plain ones.