AGENTS.md

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:

  1. Parse arguments (check for interactive mode, agent name)
  2. Load agent definition from functions/_synu_agents/<agent>.fish if it exists
  3. Parse agent-specific flags using argparse with --ignore-unknown for passthrough
  4. Configure agent environment (if agent has _configure function)
  5. Fetch initial quota from Synthetic API
  6. Execute agent with remaining arguments
  7. 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 _configure function
  • Interactive mode → gum + Synthetic API → model IDs → recursive synu call 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_case with descriptive names
    • Private/local vars: set -l var_name
    • Global vars: set -g var_name (agent defaults)
    • Exported vars: set -gx VAR_NAME (environment)
  • Function naming:
    • Public: synu
    • Private/helper: _synu_*
    • Agent-specific: _synu_agent_<name>_<action>
  • Comments: Use # for explanatory comments focusing on why not what
  • Error handling: Check $status after critical operations, return non-zero on errors
  • Error output: Use >&2 for 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:

  1. _synu_agent_<name>_flags - Returns argparse flag specification (one per line)

    function _synu_agent_myagent_flags
        echo "L/large="
        echo "o/option="
    end
    
  2. _synu_agent_<name>_configure - Sets environment variables before execution

    function _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
    
  3. _synu_agent_<name>_env_vars - Returns list of environment variables to clean up after execution

    function _synu_agent_myagent_env_vars
        echo AGENT_MODEL
        echo AGENT_CONFIG
    end
    
  4. _synu_agent_<name>_interactive - Implements interactive model selection

    function _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-unknown in main wrapper so agent-native flags pass through
  • Interactive functions return flags that trigger recursive synu call
  • Configure functions receive flags already parsed from _flag_* variables
  • Environment variables listed in _env_vars are 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

  1. Passthrough requires --ignore-unknown: When parsing agent flags, use argparse --ignore-unknown so agent-native flags aren't consumed by synu's parser.

  2. Flag variables after argparse: After argparse, the $argv variable contains only non-flag arguments. Rebuild flag arguments from _flag_* variables to pass to configure function.

  3. 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

  1. API failures are non-fatal: If quota fetch fails before/after execution, warn but continue. The agent should still run.

  2. Default to zeros on failure: If pre-execution quota fetch fails, use "0 0" to avoid math errors in post-execution display.

  3. Exit status preservation: Always return the agent's exit status ($exit_status), not the quota fetch status.

Environment Variable Scoping

  • Use set -gx for 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:

  1. Calling agent's _interactive function to get flags
  2. Recursively calling synu <agent> <flags> [args...] (non-interactive)
  3. 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 0 immediately 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 string builtin (string split, string match)
  • No local keyword: Use set -l for local scope

Testing Approach

This project has no automated test suite. Testing is manual:

  1. Syntax validation: Run fish -n on all .fish files
  2. Quota tracking: Run synu alone, check quota display with color
  3. Passthrough: Test with unknown agent (synu echo "hello")
  4. Agent config: Test Claude with/without flags
  5. Interactive mode: Test synu i claude with gum installed
  6. 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

  1. Create functions/_synu_agents/<agent>.fish
  2. Add SPDX header
  3. Define _synu_agent_<agent>_flags (return argparse flag specs)
  4. Define _synu_agent_<agent>_configure (set environment variables)
  5. Optionally define _synu_agent_<agent>_interactive (for synu i <agent>)
  6. Update completions/synu.fish to add the new agent to suggestions
  7. 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)

  1. User runs: synu claude --large hf:some/model "prompt"
  2. synu.fish loads functions/_synu_agents/claude.fish
  3. Calls _synu_agent_claude_flags to get flag spec: L/large=, o/opus=, etc.
  4. Parses with argparse --ignore-unknown → sets _flag_large
  5. Rebuilds flags for configure: --large=hf:some/model
  6. Calls _synu_agent_claude_configure --large=hf:some/model
  7. Configure sets ANTHROPIC_DEFAULT_OPUS_MODEL, ANTHROPIC_DEFAULT_SONNET_MODEL, etc.
  8. Executes command claude "prompt" with those environment variables
  9. 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 function
  • quota - quota fetching/display
  • agents/<name> - specific agent configuration (e.g., agents/claude)
  • completions - shell completions
  • docs - 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: HASH for 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