Skip to content

Add token usage logging to gateway #86

@drpedapati

Description

@drpedapati

Summary

Add per-turn token usage logging so we can track API consumption over time, especially to measure the impact of the memory/summarization system.

Current State

  • Provider responses include Usage info (input/output tokens)
  • This data is returned but not persisted anywhere
  • No way to track token consumption trends

Proposed Implementation

1. Add usage to existing LLM complete log (quick win)

In pkg/agent/loop.go around line 868, extend the "LLM call complete" log:

logger.InfoCF("agent", "LLM call complete",
    map[string]interface{}{
        "turn_id":          opts.TurnID,
        "iteration":        iteration,
        "duration":         time.Since(llmCallStartedAt).String(),
        "response_chars":   len(response.Content),
        "tool_calls_count": len(response.ToolCalls),
        // ADD:
        "input_tokens":     response.Usage.PromptTokens,
        "output_tokens":    response.Usage.CompletionTokens,
        "total_tokens":     response.Usage.TotalTokens,
    })

2. Add dedicated usage tracker (new file)

Create pkg/agent/usage.go:

package agent

import (
    "encoding/json"
    "os"
    "path/filepath"
    "sync"
    "time"
)

type UsageEntry struct {
    Timestamp    time.Time `json:"ts"`
    SessionKey   string    `json:"session"`
    Model        string    `json:"model"`
    TurnID       string    `json:"turn_id"`
    InputTokens  int       `json:"input"`
    OutputTokens int       `json:"output"`
    TotalTokens  int       `json:"total"`
}

var usageMu sync.Mutex

func LogUsage(configDir string, entry UsageEntry) error {
    usageMu.Lock()
    defer usageMu.Unlock()
    
    f, err := os.OpenFile(
        filepath.Join(configDir, "usage.jsonl"),
        os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return err
    }
    defer f.Close()
    
    return json.NewEncoder(f).Encode(entry)
}

3. Call after successful LLM response

After line 875 in loop.go:

if response.Usage != nil {
    LogUsage(al.configDir, UsageEntry{
        Timestamp:    time.Now(),
        SessionKey:   opts.SessionKey,
        Model:        al.model,
        TurnID:       opts.TurnID,
        InputTokens:  response.Usage.PromptTokens,
        OutputTokens: response.Usage.CompletionTokens,
        TotalTokens:  response.Usage.TotalTokens,
    })
}

Output

Creates ~/.picoclaw/usage.jsonl with JSONL entries:

{"ts":"2026-02-27T14:30:00Z","session":"discord:123","model":"claude-opus-4-6","turn_id":"turn-1","input":1500,"output":200,"total":1700}

Analysis examples

# Total tokens by session
cat ~/.picoclaw/usage.jsonl | jq -s 'group_by(.session) | map({session: .[0].session, total: (map(.total) | add)})'

# Daily totals
cat ~/.picoclaw/usage.jsonl | jq -s 'group_by(.ts[:10]) | map({date: .[0].ts[:10], total: (map(.total) | add)})'

# Average input tokens per turn (measure memory system effectiveness)
cat ~/.picoclaw/usage.jsonl | jq -s '{avg_input: (map(.input) | add / length), avg_output: (map(.output) | add / length)}'

Future enhancements

  • Add cost calculation based on model pricing
  • CLI command: picoclaw usage --last 7d
  • Config option to disable logging
  • Log rotation / max file size

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions