name: triggerfish version: 1.0.0 description: > How to operate and configure Triggerfish itself. Covers all CLI commands and flags, triggerfish.yaml parameters, secrets management, daemon management, log analysis, health diagnostics, filesystem layout, and core architecture concepts. Use when the user asks about Triggerfish commands, configuration, channels, logs, diagnostics, or how the platform works. classification_ceiling: INTERNAL
Triggerfish — Platform Reference
Triggerfish is a secure, multi-channel AI agent platform with deterministic policy enforcement below the LLM layer. This skill covers how to operate and configure it.
CLI Reference
Top-Level Commands
| Command | Description |
|---|---|
triggerfish |
If no config exists, runs dive; otherwise shows help |
triggerfish chat |
Start an interactive CLI chat session |
triggerfish run |
Run the gateway in the foreground (not as daemon) |
triggerfish start |
Install and start the OS daemon |
triggerfish stop |
Stop the OS daemon |
triggerfish status |
Show daemon running state, PID, uptime, manager type |
triggerfish logs |
Show last 50 log lines |
triggerfish patrol |
Run health diagnostics |
triggerfish dive |
First-run setup wizard (creates triggerfish.yaml) |
triggerfish changelog |
Show release notes between versions |
triggerfish update |
Download latest release, verify SHA256, replace binary, restart daemon |
triggerfish version / --version |
Show version string |
triggerfish help |
Show help |
triggerfish tidepool url |
Print the Tidepool A2UI URL |
logs Flags
| Flag | Description |
|---|---|
--tail |
Follow the log live (like tail -f) |
--level <LEVEL> |
Filter by level: ERROR, WARN, INFO, DEBUG, TRACE (shows that level and above) |
dive Flags
| Flag | Description |
|---|---|
--force |
Re-run wizard even if config already exists (lets you selectively update sections) |
--install-daemon |
Auto-start daemon after wizard completes (used by the install script) |
config Subcommands
| Subcommand | Description |
|---|---|
config set <dotted.key> <value> |
Set any YAML key by dotted path |
config get <dotted.key> |
Read any YAML key (masks keys containing key/secret/token) |
config validate |
Parse YAML and check structure |
config add-channel [type] |
Interactive channel setup (telegram, slack, discord, whatsapp, webchat, email, signal) |
config remove-channel [type] |
Remove a channel and optionally restart daemon |
config add-plugin [name] |
Interactive plugin setup (obsidian) |
config set-secret <key> <value> |
Store a value in the OS keychain |
config get-secret <key> |
Retrieve a value from the OS keychain |
config migrate-secrets |
Find plaintext secrets in YAML, move to keychain, rewrite with secret: refs |
config add-mcp |
Interactive MCP server setup (prompts for ID, transport, classification) |
config remove-mcp [server_id] |
Remove an MCP server from config |
config list-mcp |
List all configured MCP servers with transport and classification |
cron Subcommands
| Subcommand | Description |
|---|---|
cron add "<cron-expression>" <task> |
Create a scheduled job |
cron list |
Show all jobs with last-run time and run count |
cron delete <job_id> |
Remove a job by UUID |
cron history <job_id> |
Show last 20 executions with status/duration/error |
cron add flag:
--classification <level>— classification ceiling for this job's session (default:INTERNAL). Valid:PUBLIC,INTERNAL,CONFIDENTIAL,RESTRICTED.
connect / disconnect Subcommands
| Subcommand | Description |
|---|---|
connect google |
OAuth2 flow for Gmail, Calendar, Tasks, Drive, Sheets |
connect github |
Store GitHub PAT in keychain |
disconnect google |
Remove Google tokens |
disconnect github |
Remove GitHub token |
Config File — triggerfish.yaml
NEVER use read_file, write_file, edit_file, or run_command on
triggerfish.yaml. It is a RESTRICTED file. Use config_manage to read/write
config values and mcp_manage to manage MCP servers. These tools handle backup,
validation, and runtime updates automatically. Direct file access is forbidden.
Location: ~/.triggerfish/triggerfish.yaml (overridden by
$TRIGGERFISH_DATA_DIR env var; Docker uses /data/triggerfish.yaml)
A backup is automatically created in ~/.triggerfish/backups/ before every
config write (10 backups retained).
Models
models:
primary:
provider: anthropic
model: claude-sonnet-4-5
providers:
anthropic:
model: claude-sonnet-4-5
apiKey: "secret:provider:anthropic:apiKey"
openai:
model: gpt-4o
apiKey: "secret:provider:openai:apiKey"
google:
model: gemini-2.0-flash
apiKey: "secret:provider:google:apiKey"
openrouter:
model: anthropic/claude-sonnet-4-5
apiKey: "secret:provider:openrouter:apiKey"
ollama:
model: llama3
endpoint: http://localhost:11434
lmstudio:
model: lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF
endpoint: http://localhost:1234
Common config set paths for models:
models.primary.provider— which provider to usemodels.primary.model— model IDmodels.providers.anthropic.apiKey— API key (usesecret:reference)
Channels
Each channel has a classification field controlling what data can flow through
it.
Telegram
channels:
telegram:
botToken: "secret:telegram:botToken"
ownerId: 123456789 # numeric Telegram user ID
classification: INTERNAL
Slack
channels:
slack:
botToken: "secret:slack:botToken" # xoxb-...
appToken: "secret:slack:appToken" # xapp-... for Socket Mode
signingSecret: "secret:slack:signingSecret"
ownerId: "U012ABC3DEF" # optional
classification: PUBLIC
Discord
channels:
discord:
botToken: "secret:discord:botToken"
ownerId: "123456789012345678" # optional snowflake
classification: PUBLIC
channels:
whatsapp:
accessToken: "secret:whatsapp:accessToken"
phoneNumberId: "123456789"
verifyToken: "secret:whatsapp:webhookVerifyToken"
webhookPort: 8443
ownerPhone: "+15551234567" # optional
classification: PUBLIC
WebChat
channels:
webchat:
port: 8765
classification: PUBLIC
channels:
email:
smtpApiUrl: "https://api.sendgrid.com/v3/mail/send"
smtpApiKey: "secret:email:smtpApiKey"
imapHost: "imap.gmail.com"
imapPort: 993
imapUser: "you@example.com"
imapPassword: "secret:email:imapPassword"
fromAddress: "you@example.com"
pollInterval: 30000 # milliseconds
ownerEmail: "you@example.com" # optional
classification: CONFIDENTIAL
Signal
channels:
signal:
account: "+15551234567" # E.164 format
endpoint: "tcp://localhost:7583" # signal-cli daemon
pairing: false # require pairing code for new contacts
defaultGroupMode: "always" # always | mentioned-only | owner-only
classification: PUBLIC
Signal requires a separate signal-cli binary and daemon. Run
triggerfish config add-channel signal for guided setup.
Web
web:
search:
provider: brave
api_key: "secret:web:search:apiKey"
fetch:
enabled: true
Scheduler
scheduler:
trigger:
enabled: true
interval_minutes: 30
classification_ceiling: INTERNAL
quiet_hours:
start: 23 # 11pm local time
end: 7 # 7am local time
webhooks:
sources:
my-webhook:
secret: "secret:webhook:my-webhook:secret"
Plugins
plugins:
obsidian:
enabled: true
vault_path: ~/Documents/Obsidian
classification: INTERNAL
daily_notes:
folder: daily
date_format: YYYY-MM-DD
Classification & Logging
classification:
mode: personal
levels: standard
logging:
level: normal # quiet | normal | verbose | debug
Policy
policy:
default_action: ALLOW
Secrets Management
Never store plaintext secrets in triggerfish.yaml. Use secret:
references resolved from the OS keychain at runtime.
Store a secret:
triggerfish config set-secret provider:anthropic:apiKey sk-ant-...
Reference it in config:
models:
providers:
anthropic:
apiKey: "secret:provider:anthropic:apiKey"
Migrate existing plaintext secrets:
triggerfish config migrate-secrets
This scans known secret fields, stores values in keychain, rewrites config with
secret: refs, and creates a backup first.
Known secret field → keychain key mappings:
| Config path | Keychain key |
|---|---|
models.providers.<name>.apiKey |
provider:<name>:apiKey |
web.search.api_key |
web:search:apiKey |
channels.telegram.botToken |
telegram:botToken |
channels.discord.botToken |
discord:botToken |
channels.slack.botToken |
slack:botToken |
channels.slack.appToken |
slack:appToken |
channels.slack.signingSecret |
slack:signingSecret |
channels.whatsapp.accessToken |
whatsapp:accessToken |
channels.email.imapPassword |
email:imapPassword |
Filesystem Layout
~/.triggerfish/ # base dir
│ # ($TRIGGERFISH_DATA_DIR or /data in Docker)
├── triggerfish.yaml # config
├── SPINE.md # agent identity & mission (optional)
├── TRIGGER.md # proactive trigger prompt (optional)
├── logs/
│ ├── triggerfish.log # current log
│ └── triggerfish.1.log ... .10.log # rotated (1 = most recent, 10 max)
├── data/
│ └── triggerfish.db # SQLite: cron, sessions, memory
├── skills/ # managed (Reef-installed) skills
├── workspace/
│ └── <agent-id>/
│ └── skills/ # workspace (agent-authored) skills
└── backups/ # timestamped config backups (10 max)
# Binary locations:
# macOS/Linux: /usr/local/bin/triggerfish or ~/.local/bin/triggerfish
# Windows: %LOCALAPPDATA%\Triggerfish\triggerfish.exe
# Daemon service files:
# macOS: ~/Library/LaunchAgents/dev.triggerfish.agent.plist
# Linux: ~/.config/systemd/user/triggerfish.service
# Windows: Windows Service "Triggerfish"
Ports:
- Gateway WebSocket:
18789 - Tidepool A2UI:
18790
Daemon Management
| Platform | Manager | Service name | Notes |
|---|---|---|---|
| macOS | launchd | dev.triggerfish.agent |
plist in ~/Library/LaunchAgents/ |
| Linux | systemd (user) | triggerfish.service |
unit in ~/.config/systemd/user/ |
| Windows | Windows Service | Triggerfish |
installer registers; start triggers elevation |
triggerfish start— installs service definition and starts ittriggerfish stop— stops the servicetriggerfish status— shows PID, uptime, manager typetriggerfish run— runs gateway in foreground (no service install)triggerfish update— fetches latest GitHub release → verifies SHA256 → stops daemon → replaces binary → restarts daemon
Linux linger note: systemd user services stop on logout by default. The
installer enables linger (loginctl enable-linger $USER). If not set, run:
sudo loginctl enable-linger $USER
Log System
Log path: ~/.triggerfish/logs/triggerfish.log Rotation: .1.log
through .10.log (.1 = most recent), 10 files max
Log line format:
[2026-02-17T14:30:45.123Z] [LEVEL] [component] message
Levels (most severe → least): ERROR > WARN > INFO > DEBUG > TRACE
CLI access:
triggerfish logs # last 50 lines
triggerfish logs --tail # follow live
triggerfish logs --level ERROR # only ERROR and WARN
triggerfish logs --level WARN # WARN and ERROR
Classification: Log output is INTERNAL — never share raw logs on PUBLIC channels.
Bug report prep: Collect last 50 ERROR/WARN lines, redact tokens and keys, exclude TRACE content.
Diagnostics — triggerfish patrol
Checks runtime health and prints a report:
| Check | What it verifies |
|---|---|
| Gateway | WebSocket on port 18789 is reachable |
| LLM | Provider connected (inferred from gateway health) |
| Channels | Number of channels configured in YAML |
| Policy | Policy rules loaded (fixed rules when running) |
| Skills | Installed skill count in ~/.triggerfish/skills/ |
Status values: HEALTHY / WARNING / CRITICAL
Exit code 1 if any check is CRITICAL.
Architecture Concepts
Classification levels (ascending sensitivity): PUBLIC < INTERNAL <
CONFIDENTIAL < RESTRICTED
Taint — Session-level classification that only escalates, never decreases. The session taint is determined by the most sensitive data it has accessed.
No write-down — Data cannot flow to a lower classification channel. CONFIDENTIAL data is blocked from reaching a PUBLIC channel at the policy layer.
Deterministic hooks — Pure functions running below the LLM. Same input always produces the same decision. The LLM cannot bypass or influence them.
Hook types: PRE_CONTEXT_INJECTION, PRE_TOOL_CALL, POST_TOOL_RESPONSE,
PRE_OUTPUT
Session isolation — Each session tracks taint independently. Background sessions (triggers, cron jobs) start with fresh PUBLIC taint.
Default deny — Tools, integrations, and channels with no configured classification are rejected. Unclassified operations are a security violation.
SSRF prevention — All outbound HTTP (web_fetch, browser navigate) resolves DNS first and checks against a hardcoded IP denylist. Private/reserved IP ranges are always blocked. This is not user-configurable.
Audit trail — Every policy decision is logged with timestamp, hook type, session ID, input, and result.
SPINE.md — Agent Identity
~/.triggerfish/SPINE.md is the agent's identity and mission file. Its contents
form the foundation of the system prompt. Use it to define the agent's name,
personality, capabilities, and standing instructions.
If the file does not exist, a generic identity is used.
TRIGGER.md — Proactive Monitoring
~/.triggerfish/TRIGGER.md defines what the agent monitors and acts on during
periodic trigger wakeups. The scheduler reads this file at each trigger interval
and sends it as the prompt to a fresh session.
If TRIGGER.md does not exist, the scheduler uses a vague fallback prompt. Create it when setting up proactive monitoring.
# Morning Briefing
Check and summarize:
- Weather forecast for today
- New GitHub notifications
- Upcoming calendar events
## Alerts
- If any email marked urgent, notify me immediately
Trigger config in triggerfish.yaml:
scheduler:
trigger:
enabled: true
interval_minutes: 30
classification_ceiling: INTERNAL # session cannot exceed this
quiet_hours:
start: 23
end: 7
Skills System
Skill structure: a folder containing SKILL.md with YAML frontmatter and a
markdown body.
Frontmatter fields:
name— unique skill identifierdescription— when to use this skill (used for skill selection)classification_ceiling— max classification the skill can accessrequires_tools— list of tool names the skill needsnetwork_domains— domains this skill is permitted to fetch from
Three-tier priority (higher wins):
- Workspace — agent-authored, at
~/.triggerfish/workspace/<agent-id>/skills/Require owner approval before activation. - Managed — installed from The Reef, at
~/.triggerfish/skills/ - Bundled — ship with Triggerfish binary, read-only
Approval states for workspace skills: PENDING_APPROVAL → APPROVED /
REJECTED
Security scan checks for prompt injection, identity hijacking, and privilege escalation patterns before approval.
MCP Server Management (mcp_manage tool)
Use the mcp_manage tool to manage MCP (Model Context Protocol) servers at
runtime. Changes take effect immediately without restart.
| Action | Parameters | Description |
|---|---|---|
list |
(none) | List all servers with connection state |
add |
server_id, command or url, args?, classification?, env? |
Add a new server |
remove |
server_id |
Remove and disconnect a server |
enable |
server_id |
Enable a disabled server |
disable |
server_id |
Disable without removing |
status |
server_id |
Detailed status for one server |
reconnect |
server_id |
Force reconnect |
Examples:
- Add context7:
mcp_manage(action: "add", server_id: "context7", command: "npx", args: "-y @upstash/context7-mcp") - Add SSE server:
mcp_manage(action: "add", server_id: "my-server", url: "http://localhost:3000/sse", classification: "CONFIDENTIAL") - List all:
mcp_manage(action: "list") - Reconnect:
mcp_manage(action: "reconnect", server_id: "context7")
Classification defaults to INTERNAL. Environment variables support secret:<name> references.
Configuration Management (config_manage tool)
Use the config_manage tool to read or modify triggerfish.yaml settings.
| Action | Parameters | Description |
|---|---|---|
get |
key (dotted path) |
Read a config value |
set |
key, value |
Set a config value |
show |
(none) | Full config (secrets redacted) |
add_channel |
channel_type, channel_config (JSON) |
Add a channel |
remove_channel |
channel_type |
Remove a channel |
list_channels |
(none) | List configured channels |
set_search |
provider, api_key_secret? |
Configure search provider |
set_models |
provider, model |
Configure LLM models |
set_integration |
integration, enabled, classification? |
Enable/disable an integration |
set_domain_classification |
domain_pattern, classification |
Classify a domain pattern |
set_tool_floor |
tool_prefix, classification |
Set minimum tool classification |
set_filesystem_path |
path, classification |
Classify a filesystem path |
set_logging |
level (quiet/normal/verbose/debug) |
Set log level |
Most config writes require a daemon restart. Secret values must use secret:<name> references.
Common dotted key paths:
| Key | Example value |
|---|---|
models.primary.provider |
anthropic |
models.primary.model |
claude-sonnet-4-5-20250514 |
web.search.provider |
brave |
scheduler.trigger.enabled |
true |
scheduler.trigger.interval_minutes |
30 |
Common Tasks
Start fresh / initial setup:
triggerfish dive
triggerfish start
Add a channel:
triggerfish config add-channel telegram
Check if everything works:
triggerfish patrol
triggerfish status
triggerfish logs --level WARN
Change LLM model:
triggerfish config set models.primary.model claude-opus-4-5
triggerfish config set models.primary.provider anthropic
Add a daily 9am scheduled task:
triggerfish cron add "0 9 * * *" morning briefing — check weather and email
triggerfish cron list
Connect Google Workspace:
triggerfish connect google
Update to latest version:
triggerfish update
View release notes between versions:
triggerfish changelog # Latest releases
triggerfish changelog v0.2.16 v0.3.3 # Between two versions
triggerfish changelog v0.2.16 # From version to current
triggerfish changelog --latest 10 # Last 10 releases
In chat, use the release_notes tool to fetch and summarize changes between
versions. Example: ask "What changed between v0.2.16 and v0.3.3?" and the agent
will fetch and summarize the release notes.
Migrate plaintext secrets to keychain:
triggerfish config migrate-secrets
Re-run setup wizard to update a section:
triggerfish dive --force