Files
timmy-home/uniwizard/job_profiles_design.md

364 lines
9.4 KiB
Markdown
Raw Normal View History

# 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__()`