AGENTS.md - Developer Guide for synu
This document helps AI agents work effectively with the synu codebase.
Project Overview
synu is a universal wrapper for AI agents that tracks quota usage for Synthetic API calls. It's written in Fish shell and provides:
- Transparent quota tracking before/after agent execution
- Agent-specific configuration system (e.g., model routing for Claude Code)
- Interactive model selection with
gum - Passthrough mode for agents without special configuration
Architecture
synu (main wrapper)
├─ _synu_get_quota (API quota fetching)
└─ _synu_agents/ (agent-specific configuration)
└─ claude.fish (Claude Code model configuration)
Control Flow:
- Parse arguments (check for interactive mode, agent name)
- Load agent definition from
functions/_synu_agents/<agent>.fishif it exists - Parse agent-specific flags using
argparsewith--ignore-unknownfor passthrough - Configure agent environment (if agent has
_configurefunction) - Fetch initial quota from Synthetic API
- Execute agent with remaining arguments
- Fetch final quota and calculate/display session usage
Data Flow:
- Quota API →
_synu_get_quota→ space-separated "requests limit" string - Agent flags →
argparse→_flag_*variables → agent_configurefunction - Interactive mode →
gum+ Synthetic API → model IDs → recursivesynucall with flags
File Structure
├── functions/
│ ├── synu.fish # Main wrapper function
│ ├── _synu_get_quota.fish # Private: Fetch quota from API
│ └── _synu_agents/
│ └── claude.fish # Claude Code agent configuration
├── completions/
│ └── synu.fish # Shell completions
├── crush.json # LSP configuration (fish-lsp)
├── README.md # User documentation
└── .gitignore # Ignore agent working directories
Essential Commands
This is a Fish shell library with no build system. Key commands:
Testing Manually
# Source the function (from repo root)
source functions/synu.fish
source functions/_synu_get_quota.fish
# Check quota only
synu
# Test with an agent
synu claude "What does this function do?"
# Test interactive mode (requires gum)
synu i claude "prompt"
# Test with model override flags
synu claude --opus hf:some/model "prompt"
synu claude --large hf:some/model "prompt"
Installation Testing
# Install via fundle (in a clean fish instance)
fundle plugin 'synu' --url 'https://git.secluded.site/amolith/llm-projects'
fundle install
fundle init
Checking for Syntax Errors
# Validate Fish syntax
fish -n functions/synu.fish
fish -n functions/_synu_get_quota.fish
fish -n functions/_synu_agents/claude.fish
fish -n completions/synu.fish
LSP Integration
The project includes crush.json configuring fish-lsp for Fish language support. The LSP should automatically provide diagnostics when editing .fish files.
Code Conventions
Fish Shell Style
- Indentation: 4 spaces (no tabs)
- Variable naming:
snake_casewith descriptive names- Private/local vars:
set -l var_name - Global vars:
set -g var_name(agent defaults) - Exported vars:
set -gx VAR_NAME(environment)
- Private/local vars:
- Function naming:
- Public:
synu - Private/helper:
_synu_* - Agent-specific:
_synu_agent_<name>_<action>
- Public:
- Comments: Use
#for explanatory comments focusing on why not what - Error handling: Check
$statusafter critical operations, return non-zero on errors - Error output: Use
>&2for all error messages - SPDX headers: All files must include SPDX copyright and license headers
Agent Configuration Pattern
Each agent definition in functions/_synu_agents/<agent>.fish can provide four functions:
-
_synu_agent_<name>_flags- Returns argparse flag specification (one per line)function _synu_agent_myagent_flags echo "L/large=" echo "o/option=" end -
_synu_agent_<name>_configure- Sets environment variables before executionfunction _synu_agent_myagent_configure argparse 'L/large=' 'o/option=' -- $argv or return 1 # Configure based on flags if set -q _flag_large set -gx AGENT_MODEL $_flag_large end end -
_synu_agent_<name>_env_vars- Returns list of environment variables to clean up after executionfunction _synu_agent_myagent_env_vars echo AGENT_MODEL echo AGENT_CONFIG end -
_synu_agent_<name>_interactive- Implements interactive model selectionfunction _synu_agent_myagent_interactive # Use gum to get user input set -l selection (gum choose "option1" "option2") or return 1 # Return flags to pass to main synu call echo --option=$selection end
Important patterns:
- Agents without definitions work as passthrough (no special config)
- Use
argparse --ignore-unknownin main wrapper so agent-native flags pass through - Interactive functions return flags that trigger recursive
synucall - Configure functions receive flags already parsed from
_flag_*variables - Environment variables listed in
_env_varsare automatically unset after agent exits
API Integration
Synthetic API endpoints:
- Quota:
GET https://api.synthetic.new/v2/quotas - Models:
GET https://api.synthetic.new/openai/v1/models - Claude routing:
https://api.synthetic.new/anthropic
Authentication: Bearer token via Authorization: Bearer $SYNTHETIC_API_KEY
Response parsing: Use jq with fallbacks (// 0) for missing fields
Error handling: Check curl exit status and response emptiness before parsing
Important Gotchas
Argument Passing and argparse
-
Passthrough requires
--ignore-unknown: When parsing agent flags, useargparse --ignore-unknownso agent-native flags aren't consumed by synu's parser. -
Flag variables after argparse: After
argparse, the$argvvariable contains only non-flag arguments. Rebuild flag arguments from_flag_*variables to pass to configure function. -
Preserve argument order: When executing the agent with
command $agent $agent_args, all original arguments (except synu-specific flags) must pass through unchanged.
Quota Tracking Edge Cases
-
API failures are non-fatal: If quota fetch fails before/after execution, warn but continue. The agent should still run.
-
Default to zeros on failure: If pre-execution quota fetch fails, use
"0 0"to avoid math errors in post-execution display. -
Exit status preservation: Always return the agent's exit status (
$exit_status), not the quota fetch status.
Environment Variable Scoping
- Use
set -gxfor environment variables that need to be visible to child processes (the actual agent binary) - Agent defaults use
set -g(global but not exported) - Local calculations use
set -l
Interactive Mode Flow
Interactive mode (synu i <agent> [args...]) works by:
- Calling agent's
_interactivefunction to get flags - Recursively calling
synu <agent> <flags> [args...](non-interactive) - The recursive call then follows normal path with pre-parsed flags
This means the _configure function must handle both direct flag input and flags from interactive mode identically.
Fish-specific Considerations
- Status checks: Always use
test $status -ne 0immediately after command, as any subsequent command overwrites$status - Array slicing: Use
$argv[2..-1]syntax (1-indexed, inclusive) - Command substitution: Use
(command)not$(command) - String operations: Use
stringbuiltin (string split,string match) - No
localkeyword: Useset -lfor local scope
Testing Approach
This project has no automated test suite. Testing is manual:
- Syntax validation: Run
fish -non all.fishfiles - Quota tracking: Run
synualone, check quota display with color - Passthrough: Test with unknown agent (
synu echo "hello") - Agent config: Test Claude with/without flags
- Interactive mode: Test
synu i claudewith gum installed - Error cases: Test without
SYNTHETIC_API_KEY, with invalid API key
When making changes:
- Validate syntax with
fish -n - Source the function and test manually with different argument combinations
- Check that exit status is preserved
- Verify quota display shows correct before/after values
Common Tasks
Adding a New Agent Configuration
- Create
functions/_synu_agents/<agent>.fish - Add SPDX header
- Define
_synu_agent_<agent>_flags(return argparse flag specs) - Define
_synu_agent_<agent>_configure(set environment variables) - Optionally define
_synu_agent_<agent>_interactive(forsynu i <agent>) - Update
completions/synu.fishto add the new agent to suggestions - Test manually
Modifying Quota Display
Quota display logic is in two places:
- No args case: lines 6-28 of
synu.fish - Post-execution: lines 128-166 of
synu.fish
Both use the same color thresholds:
- Green: < 33% used
- Yellow: 33-66% used
- Red: > 66% used
Debugging API Issues
# Test quota fetch directly
source functions/_synu_get_quota.fish
set -gx SYNTHETIC_API_KEY your_key_here
_synu_get_quota
# Should output: "requests_used limit"
# Test with curl directly
curl -s -f -H "Authorization: Bearer $SYNTHETIC_API_KEY" \
"https://api.synthetic.new/v2/quotas" | jq .
Understanding Model Configuration Flow (Claude)
- User runs:
synu claude --large hf:some/model "prompt" synu.fishloadsfunctions/_synu_agents/claude.fish- Calls
_synu_agent_claude_flagsto get flag spec:L/large=,o/opus=, etc. - Parses with
argparse --ignore-unknown→ sets_flag_large - Rebuilds flags for configure:
--large=hf:some/model - Calls
_synu_agent_claude_configure --large=hf:some/model - Configure sets
ANTHROPIC_DEFAULT_OPUS_MODEL,ANTHROPIC_DEFAULT_SONNET_MODEL, etc. - Executes
command claude "prompt"with those environment variables - Claude Code sees Synthetic models via env vars and uses them
Commit Conventions
Use Conventional Commits with type and scope:
Types: feat, fix, refactor, docs, chore, test
Scopes:
synu- main wrapper functionquota- quota fetching/displayagents/<name>- specific agent configuration (e.g.,agents/claude)completions- shell completionsdocs- documentation
Examples:
feat(synu): add support for model override flags
fix(quota): handle API errors gracefully
feat(agents/claude): add interactive model selection
docs(readme): document interactive mode
Trailers:
- Use
References: HASHfor related commits/bugs - Always include
Assisted-by: [Model Name] via [Tool Name]when AI-assisted
Project-Specific Context
Why Fish Shell?
This is Amolith's personal project and Fish is their shell of choice. Fish provides:
- Clean syntax without bashisms
- Better error handling than POSIX shells
- Native arrays and proper scoping
- Built-in string manipulation
Why Synthetic API?
Synthetic provides a unified API for multiple LLM providers with quota tracking. This wrapper makes quota visibility automatic and consistent across different agent tools.
Design Philosophy
- Minimal interference: Pass through as much as possible to the underlying agent
- Graceful degradation: If quota tracking fails, still run the agent
- Extensible: Easy to add new agent configurations without modifying core wrapper
- Interactive when helpful: Support both CLI flags and TUI selection
Known Agents
Suggested in completions (may or may not have specific configurations):
claude- Claude Code (has configuration)crush- Crush agent (passthrough)amp- Amp agent (passthrough)octo- Octo agent (passthrough)codex- Codex agent (passthrough)
Any command can be wrapped, these are just suggestions for completion.
Future Considerations
Potential enhancements (not currently implemented):
- Cache quota between rapid calls to reduce API hits
- Support for other quota tracking APIs besides Synthetic
- Agent-specific usage tracking/history
- Budget warnings/limits
- More agent configurations (crush, amp, etc.)
When implementing features, maintain the core principles:
- Don't break passthrough mode
- Keep it simple and transparent
- Graceful degradation
- Follow Fish idioms