364 lines
9.4 KiB
Markdown
364 lines
9.4 KiB
Markdown
|
|
# Job Profiles Design Document
|
||
|
|
## [ROUTING] Streamline local Timmy automation context per job
|
||
|
|
|
||
|
|
**Issue:** timmy-config #90
|
||
|
|
**Author:** Timmy (AI Agent)
|
||
|
|
**Date:** 2026-03-30
|
||
|
|
**Status:** Design Complete - Ready for Implementation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Executive Summary
|
||
|
|
|
||
|
|
Local Hermes sessions experience context thrashing when all 40 tools (~9,261 tokens of schema) are loaded for every job. This design introduces **job-specific toolset profiles** that narrow the tool surface based on task type, achieving **73-91% token reduction** and preventing the "loop or thrash" behavior observed in long-running automation.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Problem Statement
|
||
|
|
|
||
|
|
When `toolsets: [all]` is enabled (current default in `~/.hermes/config.yaml`), every AIAgent instantiation loads:
|
||
|
|
|
||
|
|
- **40 tools** across 12+ toolsets
|
||
|
|
- **~9,261 tokens** of JSON schema
|
||
|
|
- Full browser automation (12 tools)
|
||
|
|
- Vision, image generation, TTS, MoA reasoning
|
||
|
|
- All MCP servers (Morrowind, etc.)
|
||
|
|
|
||
|
|
For a simple cron job checking Gitea issues, this is massive overkill. The LLM:
|
||
|
|
1. Sees too many options
|
||
|
|
2. Hallucinates tool calls that aren't needed
|
||
|
|
3. Gets confused about which tool to use
|
||
|
|
4. Loops trying different approaches
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Solution Overview
|
||
|
|
|
||
|
|
Leverage the existing `enabled_toolsets` parameter in `AIAgent.__init__()` to create **job profiles**—pre-defined toolset combinations optimized for specific automation types.
|
||
|
|
|
||
|
|
### Key Design Decisions
|
||
|
|
|
||
|
|
| Decision | Rationale |
|
||
|
|
|----------|-----------|
|
||
|
|
| Use YAML profiles, not code | Easy to extend without deployment |
|
||
|
|
| Map to existing toolsets | No changes needed to Hermes core |
|
||
|
|
| 5 base profiles | Covers 95% of automation needs |
|
||
|
|
| Token estimates in comments | Helps users understand trade-offs |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Profile Specifications
|
||
|
|
|
||
|
|
### 1. CODE-WORK Profile
|
||
|
|
**Purpose:** Software development, git operations, code review
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
toolsets: [terminal, file]
|
||
|
|
tools_enabled: 6
|
||
|
|
token_estimate: "~2,194 tokens"
|
||
|
|
token_savings: "~76%"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Included Tools:**
|
||
|
|
- `terminal`, `process` - git, builds, shell commands
|
||
|
|
- `read_file`, `search_files`, `write_file`, `patch`
|
||
|
|
|
||
|
|
**Use Cases:**
|
||
|
|
- Automated code review
|
||
|
|
- Refactoring tasks
|
||
|
|
- Build and test automation
|
||
|
|
- Git branch management
|
||
|
|
|
||
|
|
**Not Included:**
|
||
|
|
- Web search (assumes local docs/code)
|
||
|
|
- Browser automation
|
||
|
|
- Vision/image generation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. RESEARCH Profile
|
||
|
|
**Purpose:** Information gathering, documentation lookup, analysis
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
toolsets: [web, browser, file]
|
||
|
|
tools_enabled: 15
|
||
|
|
token_estimate: "~2,518 tokens"
|
||
|
|
token_savings: "~73%"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Included Tools:**
|
||
|
|
- `web_search`, `web_extract` - quick lookups
|
||
|
|
- Full browser suite (12 tools) - deep research
|
||
|
|
- File tools - save findings, read local docs
|
||
|
|
|
||
|
|
**Use Cases:**
|
||
|
|
- API documentation research
|
||
|
|
- Competitive analysis
|
||
|
|
- Fact-checking reports
|
||
|
|
- Technical due diligence
|
||
|
|
|
||
|
|
**Not Included:**
|
||
|
|
- Terminal (prevents accidental local changes)
|
||
|
|
- Vision/image generation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. TRIAGE Profile
|
||
|
|
**Purpose:** Read-only status checking, issue monitoring, health checks
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
toolsets: [terminal, file]
|
||
|
|
tools_enabled: 6
|
||
|
|
token_estimate: "~2,194 tokens"
|
||
|
|
token_savings: "~76%"
|
||
|
|
read_only: true # enforced via prompt
|
||
|
|
```
|
||
|
|
|
||
|
|
**Included Tools:**
|
||
|
|
- `terminal` - curl for Gitea API, status commands
|
||
|
|
- `read_file`, `search_files` - log analysis, config inspection
|
||
|
|
|
||
|
|
**Critical Note on Write Safety:**
|
||
|
|
The `file` toolset includes `write_file` and `patch`. For truly read-only triage, the job prompt **MUST** include:
|
||
|
|
|
||
|
|
```
|
||
|
|
[SYSTEM: This is a READ-ONLY triage job. Only use read_file and search_files.
|
||
|
|
Do NOT use write_file, patch, or terminal commands that modify state.]
|
||
|
|
```
|
||
|
|
|
||
|
|
**Future Enhancement:**
|
||
|
|
Consider adding a `disabled_tools` parameter to AIAgent for granular control without creating new toolsets.
|
||
|
|
|
||
|
|
**Use Cases:**
|
||
|
|
- Gitea issue triage
|
||
|
|
- CI/CD status monitoring
|
||
|
|
- Log file analysis
|
||
|
|
- System health checks
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. CREATIVE Profile
|
||
|
|
**Purpose:** Content creation, writing, editing
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
toolsets: [file, web]
|
||
|
|
tools_enabled: 4
|
||
|
|
token_estimate: "~1,185 tokens"
|
||
|
|
token_savings: "~87%"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Included Tools:**
|
||
|
|
- `read_file`, `search_files`, `write_file`, `patch`
|
||
|
|
- `web_search`, `web_extract` - references, fact-checking
|
||
|
|
|
||
|
|
**Use Cases:**
|
||
|
|
- Documentation writing
|
||
|
|
- Content generation
|
||
|
|
- Editing and proofreading
|
||
|
|
- Newsletter/article composition
|
||
|
|
|
||
|
|
**Not Included:**
|
||
|
|
- Terminal (no system access needed)
|
||
|
|
- Browser (web_extract sufficient for text)
|
||
|
|
- Vision/image generation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 5. OPS Profile
|
||
|
|
**Purpose:** System operations, maintenance, deployment
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
toolsets: [terminal, process, file]
|
||
|
|
tools_enabled: 6
|
||
|
|
token_estimate: "~2,194 tokens"
|
||
|
|
token_savings: "~76%"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Included Tools:**
|
||
|
|
- `terminal`, `process` - service management, background tasks
|
||
|
|
- File tools - config editing, log inspection
|
||
|
|
|
||
|
|
**Use Cases:**
|
||
|
|
- Server maintenance
|
||
|
|
- Log rotation
|
||
|
|
- Service restart
|
||
|
|
- Deployment automation
|
||
|
|
- Docker container management
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## How Toolset Filtering Works
|
||
|
|
|
||
|
|
The Hermes harness already supports this via `AIAgent.__init__`:
|
||
|
|
|
||
|
|
```python
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
...
|
||
|
|
enabled_toolsets: List[str] = None, # Only these toolsets
|
||
|
|
disabled_toolsets: List[str] = None, # Exclude these toolsets
|
||
|
|
...
|
||
|
|
):
|
||
|
|
```
|
||
|
|
|
||
|
|
The filtering happens in `model_tools.get_tool_definitions()`:
|
||
|
|
|
||
|
|
```python
|
||
|
|
def get_tool_definitions(
|
||
|
|
enabled_toolsets: List[str] = None,
|
||
|
|
disabled_toolsets: List[str] = None,
|
||
|
|
...
|
||
|
|
) -> List[Dict[str, Any]]:
|
||
|
|
# 1. Resolve toolsets to tool names via toolsets.resolve_toolset()
|
||
|
|
# 2. Filter by availability (check_fn for each tool)
|
||
|
|
# 3. Return OpenAI-format tool definitions
|
||
|
|
```
|
||
|
|
|
||
|
|
### Current Cron Usage (Line 423-443 in `cron/scheduler.py`):
|
||
|
|
|
||
|
|
```python
|
||
|
|
agent = AIAgent(
|
||
|
|
model=turn_route["model"],
|
||
|
|
...
|
||
|
|
disabled_toolsets=["cronjob", "messaging", "clarify"], # Hardcoded
|
||
|
|
quiet_mode=True,
|
||
|
|
platform="cron",
|
||
|
|
...
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Wiring into Cron Dispatch
|
||
|
|
|
||
|
|
### Step 1: Load Profile
|
||
|
|
|
||
|
|
```python
|
||
|
|
import yaml
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
def load_job_profile(profile_name: str) -> dict:
|
||
|
|
"""Load a job profile from ~/.timmy/uniwizard/job_profiles.yaml"""
|
||
|
|
profile_path = Path.home() / ".timmy/uniwizard/job_profiles.yaml"
|
||
|
|
with open(profile_path) as f:
|
||
|
|
config = yaml.safe_load(f)
|
||
|
|
profiles = config.get("profiles", {})
|
||
|
|
return profiles.get(profile_name, profiles.get("minimal", {"toolsets": ["file"]}))
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 2: Modify `run_job()` in `cron/scheduler.py`
|
||
|
|
|
||
|
|
```python
|
||
|
|
def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
||
|
|
...
|
||
|
|
# Load job profile (default to minimal if not specified)
|
||
|
|
profile_name = job.get("tool_profile", "minimal")
|
||
|
|
profile = load_job_profile(profile_name)
|
||
|
|
|
||
|
|
# Build toolset filter
|
||
|
|
enabled_toolsets = profile.get("toolsets", ["file"])
|
||
|
|
disabled_toolsets = profile.get("disabled_toolsets", ["cronjob", "messaging", "clarify"])
|
||
|
|
|
||
|
|
agent = AIAgent(
|
||
|
|
model=turn_route["model"],
|
||
|
|
...
|
||
|
|
enabled_toolsets=enabled_toolsets, # NEW
|
||
|
|
disabled_toolsets=disabled_toolsets, # MODIFIED
|
||
|
|
quiet_mode=True,
|
||
|
|
platform="cron",
|
||
|
|
...
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 3: Update Job Definition Format
|
||
|
|
|
||
|
|
Add `tool_profile` field to `~/.hermes/cron/jobs.yaml`:
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
jobs:
|
||
|
|
- id: daily-issue-triage
|
||
|
|
name: "Triage Gitea Issues"
|
||
|
|
schedule: "0 9 * * *"
|
||
|
|
tool_profile: triage # <-- NEW
|
||
|
|
prompt: "Check timmy-config repo for new issues..."
|
||
|
|
deliver: telegram
|
||
|
|
|
||
|
|
- id: weekly-docs-review
|
||
|
|
name: "Review Documentation"
|
||
|
|
schedule: "0 10 * * 1"
|
||
|
|
tool_profile: creative # <-- NEW
|
||
|
|
prompt: "Review and improve README files..."
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Token Savings Summary
|
||
|
|
|
||
|
|
| Profile | Tools | Tokens | Savings |
|
||
|
|
|---------|-------|--------|---------|
|
||
|
|
| Full (`all`) | 40 | ~9,261 | 0% |
|
||
|
|
| code-work | 6 | ~2,194 | -76% |
|
||
|
|
| research | 15 | ~2,518 | -73% |
|
||
|
|
| triage | 6 | ~2,194 | -76% |
|
||
|
|
| creative | 4 | ~1,185 | -87% |
|
||
|
|
| ops | 6 | ~2,194 | -76% |
|
||
|
|
| minimal | 4 | ~800 | -91% |
|
||
|
|
|
||
|
|
**Benefits:**
|
||
|
|
1. Faster prompt processing (less context to scan)
|
||
|
|
2. Reduced API costs (fewer input tokens)
|
||
|
|
3. More focused tool selection (less confusion)
|
||
|
|
4. Faster tool calls (smaller schema to parse)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Migration Path
|
||
|
|
|
||
|
|
### Phase 1: Deploy Profiles (This PR)
|
||
|
|
- [x] Create `~/.timmy/uniwizard/job_profiles.yaml`
|
||
|
|
- [x] Create design document
|
||
|
|
- [ ] Post Gitea issue comment
|
||
|
|
|
||
|
|
### Phase 2: Cron Integration (Next PR)
|
||
|
|
- [ ] Modify `cron/scheduler.py` to load profiles
|
||
|
|
- [ ] Add `tool_profile` field to job schema
|
||
|
|
- [ ] Update existing jobs to use appropriate profiles
|
||
|
|
|
||
|
|
### Phase 3: CLI Integration (Future)
|
||
|
|
- [ ] Add `/profile` slash command to switch profiles
|
||
|
|
- [ ] Show active profile in CLI banner
|
||
|
|
- [ ] Profile-specific skills loading
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Files Changed
|
||
|
|
|
||
|
|
| File | Purpose |
|
||
|
|
|------|---------|
|
||
|
|
| `~/.timmy/uniwizard/job_profiles.yaml` | Profile definitions |
|
||
|
|
| `~/.timmy/uniwizard/job_profiles_design.md` | This design document |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Open Questions
|
||
|
|
|
||
|
|
1. **Should we add `disabled_tools` parameter to AIAgent?**
|
||
|
|
- Would enable true read-only triage without prompt hacks
|
||
|
|
- Requires changes to `model_tools.py` and `run_agent.py`
|
||
|
|
|
||
|
|
2. **Should profiles include model recommendations?**
|
||
|
|
- e.g., `recommended_model: claude-opus-4` for code-work
|
||
|
|
- Could help route simple jobs to cheaper models
|
||
|
|
|
||
|
|
3. **Should we support profile composition?**
|
||
|
|
- e.g., `profiles: [ops, web]` for ops jobs that need web lookup
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- Hermes toolset system: `~/.hermes/hermes-agent/toolsets.py`
|
||
|
|
- Tool filtering logic: `~/.hermes/hermes-agent/model_tools.py:get_tool_definitions()`
|
||
|
|
- Cron scheduler: `~/.hermes/hermes-agent/cron/scheduler.py:run_job()`
|
||
|
|
- AIAgent initialization: `~/.hermes/hermes-agent/run_agent.py:AIAgent.__init__()`
|