This commit was merged in pull request #766.
This commit is contained in:
281
timmy_automations/README.md
Normal file
281
timmy_automations/README.md
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
# Timmy Automations
|
||||||
|
|
||||||
|
Central home for all automated processes that keep the Timmy development loop running smoothly.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This directory consolidates scripts, configurations, and manifests for automations that operate on behalf of Timmy — the autonomous development agent. These automations handle everything from daily issue triage to cycle retrospectives, workspace management, and metrics collection.
|
||||||
|
|
||||||
|
**Design principle:** Automations should be discoverable, configurable, and observable. Every automation in this folder can be found by Timmy, enabled or disabled via configuration, and reports its status for dashboard integration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
| Directory | Purpose |
|
||||||
|
|-----------|---------|
|
||||||
|
| `daily_run/` | Scripts that run periodically (cycle retros, idle detection, daily triage) |
|
||||||
|
| `triage/` | Deep triage helpers — intelligent issue refinement and prioritization |
|
||||||
|
| `metrics/` | Dashboard and metrics integration — data collection for loop health |
|
||||||
|
| `workspace/` | Agent workspace management — isolated environments per agent |
|
||||||
|
| `config/` | Automation manifests and configuration files |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Types of Automations
|
||||||
|
|
||||||
|
### 1. Daily Run Automations
|
||||||
|
|
||||||
|
These run continuously or on a schedule to keep the dev loop operational:
|
||||||
|
|
||||||
|
- **Cycle Retrospective** (`cycle_retro.py`) — Logs structured data after each development cycle
|
||||||
|
- **Loop Guard** (`loop_guard.py`) — Idle detection with exponential backoff (prevents burning cycles on empty queues)
|
||||||
|
- **Triage Scoring** (`triage_score.py`) — Mechanical issue scoring based on scope/acceptance/alignment
|
||||||
|
|
||||||
|
### 2. Deep Triage Automations
|
||||||
|
|
||||||
|
Intelligent, LLM-assisted workflows that run less frequently (~every 20 cycles):
|
||||||
|
|
||||||
|
- **Deep Triage** (`deep_triage.sh` + `deep_triage_prompt.md`) — Hermes-driven issue refinement, breaking down large issues, adding acceptance criteria
|
||||||
|
- **Loop Introspection** (`loop_introspect.py`) — Self-improvement engine that analyzes retro data and produces recommendations
|
||||||
|
|
||||||
|
### 3. Workspace Automations
|
||||||
|
|
||||||
|
Environment management for multi-agent operation:
|
||||||
|
|
||||||
|
- **Agent Workspace** (`agent_workspace.sh`) — Creates isolated git clones, port ranges, and data directories per agent
|
||||||
|
- **Bootstrap** (`bootstrap.sh`) — One-time setup for new Kimi workspaces
|
||||||
|
- **Resume** (`resume.sh`) — Quick status check and resume prompt
|
||||||
|
|
||||||
|
### 4. Metrics & Integration
|
||||||
|
|
||||||
|
Data collection for dashboard visibility:
|
||||||
|
|
||||||
|
- **Backfill Retro** (`backfill_retro.py`) — Seeds retrospective data from Gitea PR history
|
||||||
|
- **Pre-commit Checks** (`pre_commit_checks.py`) — CI hygiene validation before commits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How Timmy Discovers Automations
|
||||||
|
|
||||||
|
Automations are discovered via manifest files in `config/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
config/
|
||||||
|
├── automations.json # Master manifest of all automations
|
||||||
|
├── daily_run.json # Daily run schedule configuration
|
||||||
|
└── triage_rules.yaml # Triage scoring weights and thresholds
|
||||||
|
```
|
||||||
|
|
||||||
|
### Discovery Protocol
|
||||||
|
|
||||||
|
1. **Scan** — Timmy scans `config/automations.json` on startup
|
||||||
|
2. **Validate** — Each automation entry is validated (script exists, is executable)
|
||||||
|
3. **Enable** — Automations marked `enabled: true` are registered
|
||||||
|
4. **Schedule** — Daily runs are scheduled via the loop's internal scheduler
|
||||||
|
5. **Report** — Status is written to `.loop/automation_state.json`
|
||||||
|
|
||||||
|
### Automation Manifest Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"automations": [
|
||||||
|
{
|
||||||
|
"id": "cycle_retro",
|
||||||
|
"name": "Cycle Retrospective",
|
||||||
|
"description": "Logs structured data after each dev cycle",
|
||||||
|
"script": "daily_run/cycle_retro.py",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "post_cycle",
|
||||||
|
"config": {
|
||||||
|
"retro_file": ".loop/retro/cycles.jsonl",
|
||||||
|
"summary_window": 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "loop_guard",
|
||||||
|
"name": "Loop Guard",
|
||||||
|
"description": "Idle detection with exponential backoff",
|
||||||
|
"script": "daily_run/loop_guard.py",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "pre_cycle",
|
||||||
|
"config": {
|
||||||
|
"backoff_base": 60,
|
||||||
|
"backoff_max": 600
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How Timmy Enables/Disables Automations
|
||||||
|
|
||||||
|
### Method 1: Edit Manifest
|
||||||
|
|
||||||
|
Modify `config/automations.json` and set `enabled: true/false`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Disable the loop guard
|
||||||
|
jq '.automations[] | select(.id == "loop_guard").enabled = false' \
|
||||||
|
config/automations.json > tmp.json && mv tmp.json config/automations.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: CLI (Future)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
timmy automation enable loop_guard
|
||||||
|
timmy automation disable cycle_retro
|
||||||
|
timmy automation list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 3: Dashboard (Future)
|
||||||
|
|
||||||
|
Mission Control panel will have toggles for each automation with real-time status.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How Timmy Configures Automations
|
||||||
|
|
||||||
|
Each automation reads configuration from the manifest + environment variables:
|
||||||
|
|
||||||
|
| Priority | Source | Override |
|
||||||
|
|----------|--------|----------|
|
||||||
|
| 1 | Environment variables | `TIMMY_AUTOMATION_*` prefix |
|
||||||
|
| 2 | Manifest `config` object | Per-automation settings |
|
||||||
|
| 3 | Code defaults | Fallback values |
|
||||||
|
|
||||||
|
Example environment overrides:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export TIMMY_CYCLE_RETRO_WINDOW=100 # Override summary_window
|
||||||
|
export TIMMY_LOOP_GUARD_MAX_BACKOFF=300 # Override backoff_max
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Script References
|
||||||
|
|
||||||
|
The following scripts live in their original locations but are conceptually part of Timmy Automations:
|
||||||
|
|
||||||
|
| Script | Location | Category | Purpose |
|
||||||
|
|--------|----------|----------|---------|
|
||||||
|
| `cycle_retro.py` | `../scripts/` | Daily Run | Log cycle retrospective data |
|
||||||
|
| `loop_guard.py` | `../scripts/` | Daily Run | Idle detection & backoff |
|
||||||
|
| `triage_score.py` | `../scripts/` | Daily Run | Mechanical issue scoring |
|
||||||
|
| `deep_triage.sh` | `../scripts/` | Triage | LLM-driven issue refinement |
|
||||||
|
| `deep_triage_prompt.md` | `../scripts/` | Triage | Prompt template for deep triage |
|
||||||
|
| `loop_introspect.py` | `../scripts/` | Triage | Self-improvement analysis |
|
||||||
|
| `agent_workspace.sh` | `../scripts/` | Workspace | Agent environment management |
|
||||||
|
| `backfill_retro.py` | `../scripts/` | Metrics | Seed retro data from history |
|
||||||
|
| `pre_commit_checks.py` | `../scripts/` | Metrics | CI hygiene validation |
|
||||||
|
|
||||||
|
### Why scripts aren't moved here (yet)
|
||||||
|
|
||||||
|
These scripts are referenced rather than moved to maintain backward compatibility with:
|
||||||
|
- Existing CI/CD pipelines
|
||||||
|
- Agent workspace setups
|
||||||
|
- Shell aliases and documentation
|
||||||
|
|
||||||
|
Future work may migrate scripts here with symlink redirects from original locations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration with Dashboard
|
||||||
|
|
||||||
|
Automations report status for dashboard visibility:
|
||||||
|
|
||||||
|
```
|
||||||
|
.loop/
|
||||||
|
├── automation_state.json # Current state of all automations
|
||||||
|
├── queue.json # Current work queue (produced by triage)
|
||||||
|
├── retro/
|
||||||
|
│ ├── cycles.jsonl # Cycle retrospective log
|
||||||
|
│ ├── deep-triage.jsonl # Deep triage history
|
||||||
|
│ ├── triage.jsonl # Mechanical triage log
|
||||||
|
│ └── insights.json # Loop introspection output
|
||||||
|
└── quarantine.json # Quarantined issues (repeat failures)
|
||||||
|
```
|
||||||
|
|
||||||
|
The Mission Control dashboard (`/mission-control`) displays:
|
||||||
|
- Last run time for each automation
|
||||||
|
- Success/failure counts
|
||||||
|
- Queue depth and triage statistics
|
||||||
|
- Repeat failure alerts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adding New Automations
|
||||||
|
|
||||||
|
1. **Create the script** in the appropriate subdirectory
|
||||||
|
2. **Add manifest entry** to `config/automations.json`
|
||||||
|
3. **Document in this README** — add to the relevant table
|
||||||
|
4. **Add tests** in `tests/timmy_automations/`
|
||||||
|
5. **Update dashboard** if the automation produces visible output
|
||||||
|
|
||||||
|
### Automation Script Template
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Brief description of what this automation does.
|
||||||
|
|
||||||
|
Run: python3 timmy_automations/daily_run/my_automation.py
|
||||||
|
Env: See config/automations.json for configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Load automation config from manifest
|
||||||
|
CONFIG_PATH = Path(__file__).parent.parent / "config" / "automations.json"
|
||||||
|
|
||||||
|
def load_config() -> dict:
|
||||||
|
"""Load configuration for this automation."""
|
||||||
|
manifest = json.loads(CONFIG_PATH.read_text())
|
||||||
|
for auto in manifest["automations"]:
|
||||||
|
if auto["id"] == "my_automation_id":
|
||||||
|
return auto.get("config", {})
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
config = load_config()
|
||||||
|
# Your automation logic here
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Token Economy Integration
|
||||||
|
|
||||||
|
Automations participate in the token economy:
|
||||||
|
|
||||||
|
| Action | Token Cost/Reward | Reason |
|
||||||
|
|--------|-------------------|--------|
|
||||||
|
| Run daily automation | 1 token | Resource usage |
|
||||||
|
| Successful cycle retro | +5 tokens | Data contribution |
|
||||||
|
| Find quarantine candidate | +10 tokens | Quality improvement |
|
||||||
|
| Deep triage refinement | +20 tokens | High-value work |
|
||||||
|
| Automation failure | -2 tokens | Penalty |
|
||||||
|
|
||||||
|
See `src/lightning/` for token economy implementation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- `CLAUDE.md` — Architecture patterns and conventions
|
||||||
|
- `AGENTS.md` — Agent roster and development standards
|
||||||
|
- `.kimi/README.md` — Kimi agent workspace guide
|
||||||
|
- `.loop/` — Runtime data directory (created on first run)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Maintained by: Timmy Automations Subsystem_
|
||||||
|
_Updated: 2026-03-21_
|
||||||
271
timmy_automations/__init__.py
Normal file
271
timmy_automations/__init__.py
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
"""Timmy Automations — Central automation discovery and control module.
|
||||||
|
|
||||||
|
This module provides:
|
||||||
|
- Discovery of all configured automations
|
||||||
|
- Enable/disable control
|
||||||
|
- Status reporting
|
||||||
|
- Configuration management
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
from timmy_automations import AutomationRegistry
|
||||||
|
|
||||||
|
registry = AutomationRegistry()
|
||||||
|
for auto in registry.list_automations():
|
||||||
|
print(f"{auto.id}: {auto.name} ({'enabled' if auto.enabled else 'disabled'})")
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Automation:
|
||||||
|
"""Represents a single automation configuration."""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
script: str
|
||||||
|
category: str
|
||||||
|
enabled: bool
|
||||||
|
trigger: str
|
||||||
|
executable: str
|
||||||
|
config: dict[str, Any]
|
||||||
|
outputs: list[str]
|
||||||
|
depends_on: list[str]
|
||||||
|
schedule: str | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_script_path(self) -> Path:
|
||||||
|
"""Resolve the script path relative to repo root."""
|
||||||
|
repo_root = Path(__file__).parent.parent
|
||||||
|
return repo_root / self.script
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_executable(self) -> bool:
|
||||||
|
"""Check if the script file exists and is executable."""
|
||||||
|
path = self.full_script_path
|
||||||
|
return path.exists() and os.access(path, os.X_OK)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_runnable(self) -> bool:
|
||||||
|
"""Check if automation can be run (enabled + executable)."""
|
||||||
|
return self.enabled and self.is_executable
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationRegistry:
|
||||||
|
"""Registry for discovering and managing Timmy automations."""
|
||||||
|
|
||||||
|
MANIFEST_PATH = Path(__file__).parent / "config" / "automations.json"
|
||||||
|
STATE_PATH = Path(__file__).parent.parent / ".loop" / "automation_state.json"
|
||||||
|
|
||||||
|
def __init__(self, manifest_path: Path | None = None) -> None:
|
||||||
|
"""Initialize the registry, loading the manifest.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
manifest_path: Optional override for manifest file location.
|
||||||
|
"""
|
||||||
|
self._manifest_path = manifest_path or self.MANIFEST_PATH
|
||||||
|
self._automations: dict[str, Automation] = {}
|
||||||
|
self._load_manifest()
|
||||||
|
|
||||||
|
def _load_manifest(self) -> None:
|
||||||
|
"""Load automations from the manifest file."""
|
||||||
|
if not self._manifest_path.exists():
|
||||||
|
self._automations = {}
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(self._manifest_path.read_text())
|
||||||
|
for auto_data in data.get("automations", []):
|
||||||
|
auto = Automation(
|
||||||
|
id=auto_data["id"],
|
||||||
|
name=auto_data["name"],
|
||||||
|
description=auto_data["description"],
|
||||||
|
script=auto_data["script"],
|
||||||
|
category=auto_data["category"],
|
||||||
|
enabled=auto_data.get("enabled", True),
|
||||||
|
trigger=auto_data["trigger"],
|
||||||
|
executable=auto_data.get("executable", "python3"),
|
||||||
|
config=auto_data.get("config", {}),
|
||||||
|
outputs=auto_data.get("outputs", []),
|
||||||
|
depends_on=auto_data.get("depends_on", []),
|
||||||
|
schedule=auto_data.get("schedule"),
|
||||||
|
)
|
||||||
|
self._automations[auto.id] = auto
|
||||||
|
except (json.JSONDecodeError, KeyError) as e:
|
||||||
|
raise AutomationError(f"Failed to load manifest: {e}")
|
||||||
|
|
||||||
|
def _save_manifest(self) -> None:
|
||||||
|
"""Save current automation states back to manifest."""
|
||||||
|
data = {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Master manifest of all Timmy automations",
|
||||||
|
"last_updated": "2026-03-21",
|
||||||
|
"automations": []
|
||||||
|
}
|
||||||
|
|
||||||
|
for auto in self._automations.values():
|
||||||
|
auto_dict = {
|
||||||
|
"id": auto.id,
|
||||||
|
"name": auto.name,
|
||||||
|
"description": auto.description,
|
||||||
|
"script": auto.script,
|
||||||
|
"category": auto.category,
|
||||||
|
"enabled": auto.enabled,
|
||||||
|
"trigger": auto.trigger,
|
||||||
|
"executable": auto.executable,
|
||||||
|
"config": auto.config,
|
||||||
|
"outputs": auto.outputs,
|
||||||
|
"depends_on": auto.depends_on,
|
||||||
|
}
|
||||||
|
if auto.schedule:
|
||||||
|
auto_dict["schedule"] = auto.schedule
|
||||||
|
data["automations"].append(auto_dict)
|
||||||
|
|
||||||
|
self._manifest_path.write_text(json.dumps(data, indent=2) + "\n")
|
||||||
|
|
||||||
|
def list_automations(
|
||||||
|
self,
|
||||||
|
category: str | None = None,
|
||||||
|
enabled_only: bool = False,
|
||||||
|
trigger: str | None = None,
|
||||||
|
) -> list[Automation]:
|
||||||
|
"""List automations with optional filtering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
category: Filter by category (daily_run, triage, metrics, workspace)
|
||||||
|
enabled_only: Only return enabled automations
|
||||||
|
trigger: Filter by trigger type (pre_cycle, post_cycle, scheduled, manual)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of matching Automation objects.
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
for auto in self._automations.values():
|
||||||
|
if category and auto.category != category:
|
||||||
|
continue
|
||||||
|
if enabled_only and not auto.enabled:
|
||||||
|
continue
|
||||||
|
if trigger and auto.trigger != trigger:
|
||||||
|
continue
|
||||||
|
results.append(auto)
|
||||||
|
return sorted(results, key=lambda a: (a.category, a.name))
|
||||||
|
|
||||||
|
def get_automation(self, automation_id: str) -> Automation | None:
|
||||||
|
"""Get a specific automation by ID."""
|
||||||
|
return self._automations.get(automation_id)
|
||||||
|
|
||||||
|
def enable(self, automation_id: str) -> bool:
|
||||||
|
"""Enable an automation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if automation was found and enabled, False otherwise.
|
||||||
|
"""
|
||||||
|
if automation_id not in self._automations:
|
||||||
|
return False
|
||||||
|
self._automations[automation_id].enabled = True
|
||||||
|
self._save_manifest()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def disable(self, automation_id: str) -> bool:
|
||||||
|
"""Disable an automation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if automation was found and disabled, False otherwise.
|
||||||
|
"""
|
||||||
|
if automation_id not in self._automations:
|
||||||
|
return False
|
||||||
|
self._automations[automation_id].enabled = False
|
||||||
|
self._save_manifest()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_by_trigger(self, trigger: str) -> list[Automation]:
|
||||||
|
"""Get all automations for a specific trigger."""
|
||||||
|
return [a for a in self._automations.values() if a.trigger == trigger]
|
||||||
|
|
||||||
|
def get_by_schedule(self, schedule: str) -> list[Automation]:
|
||||||
|
"""Get all automations for a specific schedule."""
|
||||||
|
return [
|
||||||
|
a for a in self._automations.values()
|
||||||
|
if a.schedule == schedule
|
||||||
|
]
|
||||||
|
|
||||||
|
def validate_all(self) -> list[tuple[str, str]]:
|
||||||
|
"""Validate all automations and return any issues.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of (automation_id, error_message) tuples.
|
||||||
|
"""
|
||||||
|
issues = []
|
||||||
|
for auto in self._automations.values():
|
||||||
|
if not auto.full_script_path.exists():
|
||||||
|
issues.append((auto.id, f"Script not found: {auto.script}"))
|
||||||
|
elif auto.enabled and not auto.is_executable:
|
||||||
|
# Check if file is readable even if not executable
|
||||||
|
if not os.access(auto.full_script_path, os.R_OK):
|
||||||
|
issues.append((auto.id, f"Script not readable: {auto.script}"))
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def get_status(self) -> dict[str, Any]:
|
||||||
|
"""Get overall registry status."""
|
||||||
|
total = len(self._automations)
|
||||||
|
enabled = sum(1 for a in self._automations.values() if a.enabled)
|
||||||
|
runnable = sum(1 for a in self._automations.values() if a.is_runnable)
|
||||||
|
issues = self.validate_all()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_automations": total,
|
||||||
|
"enabled": enabled,
|
||||||
|
"disabled": total - enabled,
|
||||||
|
"runnable": runnable,
|
||||||
|
"validation_issues": len(issues),
|
||||||
|
"issues": [{"id": i[0], "error": i[1]} for i in issues],
|
||||||
|
"categories": sorted(set(a.category for a in self._automations.values())),
|
||||||
|
}
|
||||||
|
|
||||||
|
def save_state(self) -> None:
|
||||||
|
"""Save current automation state to .loop directory."""
|
||||||
|
state = {
|
||||||
|
"automations": {
|
||||||
|
id: {
|
||||||
|
"enabled": auto.enabled,
|
||||||
|
"runnable": auto.is_runnable,
|
||||||
|
"script_exists": auto.full_script_path.exists(),
|
||||||
|
}
|
||||||
|
for id, auto in self._automations.items()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.STATE_PATH.write_text(json.dumps(state, indent=2) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationError(Exception):
|
||||||
|
"""Raised when automation operations fail."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Convenience functions for CLI usage
|
||||||
|
def list_automations(category: str | None = None, enabled_only: bool = False) -> list[Automation]:
|
||||||
|
"""List automations (convenience function)."""
|
||||||
|
return AutomationRegistry().list_automations(category, enabled_only)
|
||||||
|
|
||||||
|
|
||||||
|
def enable_automation(automation_id: str) -> bool:
|
||||||
|
"""Enable an automation (convenience function)."""
|
||||||
|
return AutomationRegistry().enable(automation_id)
|
||||||
|
|
||||||
|
|
||||||
|
def disable_automation(automation_id: str) -> bool:
|
||||||
|
"""Disable an automation (convenience function)."""
|
||||||
|
return AutomationRegistry().disable(automation_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_status() -> dict[str, Any]:
|
||||||
|
"""Get registry status (convenience function)."""
|
||||||
|
return AutomationRegistry().get_status()
|
||||||
197
timmy_automations/config/automations.json
Normal file
197
timmy_automations/config/automations.json
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Master manifest of all Timmy automations",
|
||||||
|
"last_updated": "2026-03-21",
|
||||||
|
"automations": [
|
||||||
|
{
|
||||||
|
"id": "cycle_retro",
|
||||||
|
"name": "Cycle Retrospective",
|
||||||
|
"description": "Logs structured retrospective data after each development cycle",
|
||||||
|
"script": "scripts/cycle_retro.py",
|
||||||
|
"category": "daily_run",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "post_cycle",
|
||||||
|
"executable": "python3",
|
||||||
|
"config": {
|
||||||
|
"retro_file": ".loop/retro/cycles.jsonl",
|
||||||
|
"summary_file": ".loop/retro/summary.json",
|
||||||
|
"summary_window": 50,
|
||||||
|
"epoch_enabled": true
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
".loop/retro/cycles.jsonl",
|
||||||
|
".loop/retro/summary.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "loop_guard",
|
||||||
|
"name": "Loop Guard",
|
||||||
|
"description": "Idle detection with exponential backoff to prevent burning cycles on empty queues",
|
||||||
|
"script": "scripts/loop_guard.py",
|
||||||
|
"category": "daily_run",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "pre_cycle",
|
||||||
|
"executable": "python3",
|
||||||
|
"config": {
|
||||||
|
"queue_file": ".loop/queue.json",
|
||||||
|
"idle_state_file": ".loop/idle_state.json",
|
||||||
|
"backoff_base_seconds": 60,
|
||||||
|
"backoff_max_seconds": 600,
|
||||||
|
"backoff_multiplier": 2,
|
||||||
|
"cycle_duration_seconds": 300
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
".loop/idle_state.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "triage_score",
|
||||||
|
"name": "Mechanical Triage Scoring",
|
||||||
|
"description": "Pure heuristic scoring of open issues based on scope, acceptance criteria, and alignment",
|
||||||
|
"script": "scripts/triage_score.py",
|
||||||
|
"category": "daily_run",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "scheduled",
|
||||||
|
"schedule": "every_10_cycles",
|
||||||
|
"executable": "python3",
|
||||||
|
"config": {
|
||||||
|
"ready_threshold": 5,
|
||||||
|
"quarantine_lookback": 20,
|
||||||
|
"queue_file": ".loop/queue.json",
|
||||||
|
"retro_file": ".loop/retro/triage.jsonl",
|
||||||
|
"quarantine_file": ".loop/quarantine.json"
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
".loop/queue.json",
|
||||||
|
".loop/retro/triage.jsonl",
|
||||||
|
".loop/quarantine.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "deep_triage",
|
||||||
|
"name": "Deep Triage",
|
||||||
|
"description": "LLM-driven intelligent issue refinement, breaking down large issues, adding acceptance criteria",
|
||||||
|
"script": "scripts/deep_triage.sh",
|
||||||
|
"category": "triage",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "scheduled",
|
||||||
|
"schedule": "every_20_cycles",
|
||||||
|
"executable": "bash",
|
||||||
|
"depends_on": ["loop_introspect"],
|
||||||
|
"config": {
|
||||||
|
"queue_file": ".loop/queue.json",
|
||||||
|
"retro_file": ".loop/retro/deep-triage.jsonl",
|
||||||
|
"prompt_file": "scripts/deep_triage_prompt.md",
|
||||||
|
"timmy_consultation": true,
|
||||||
|
"timmy_timeout_seconds": 60
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
".loop/queue.json",
|
||||||
|
".loop/retro/deep-triage.jsonl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "loop_introspect",
|
||||||
|
"name": "Loop Introspection",
|
||||||
|
"description": "Self-improvement engine that analyzes retro data and produces structured recommendations",
|
||||||
|
"script": "scripts/loop_introspect.py",
|
||||||
|
"category": "triage",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "scheduled",
|
||||||
|
"schedule": "every_20_cycles",
|
||||||
|
"executable": "python3",
|
||||||
|
"config": {
|
||||||
|
"cycles_file": ".loop/retro/cycles.jsonl",
|
||||||
|
"deep_triage_file": ".loop/retro/deep-triage.jsonl",
|
||||||
|
"triage_file": ".loop/retro/triage.jsonl",
|
||||||
|
"quarantine_file": ".loop/quarantine.json",
|
||||||
|
"insights_file": ".loop/retro/insights.json",
|
||||||
|
"trend_window_days": 7
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
".loop/retro/insights.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "agent_workspace",
|
||||||
|
"name": "Agent Workspace Manager",
|
||||||
|
"description": "Creates and maintains isolated git clones, port ranges, and data directories per agent",
|
||||||
|
"script": "scripts/agent_workspace.sh",
|
||||||
|
"category": "workspace",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "manual",
|
||||||
|
"executable": "bash",
|
||||||
|
"config": {
|
||||||
|
"agents_dir": "/tmp/timmy-agents",
|
||||||
|
"canonical_repo": "~/Timmy-Time-dashboard",
|
||||||
|
"gitea_remote": "http://localhost:3000/rockachopa/Timmy-time-dashboard.git",
|
||||||
|
"agents": ["hermes", "kimi-0", "kimi-1", "kimi-2", "kimi-3", "smoke"],
|
||||||
|
"port_base_dashboard": 8100,
|
||||||
|
"port_base_serve": 8200
|
||||||
|
},
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kimi_bootstrap",
|
||||||
|
"name": "Kimi Workspace Bootstrap",
|
||||||
|
"description": "One-time setup script for new Kimi agent workspaces",
|
||||||
|
"script": ".kimi/scripts/bootstrap.sh",
|
||||||
|
"category": "workspace",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "manual",
|
||||||
|
"executable": "bash",
|
||||||
|
"config": {},
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kimi_resume",
|
||||||
|
"name": "Kimi Resume",
|
||||||
|
"description": "Quick status check and resume prompt for Kimi workspaces",
|
||||||
|
"script": ".kimi/scripts/resume.sh",
|
||||||
|
"category": "workspace",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "manual",
|
||||||
|
"executable": "bash",
|
||||||
|
"config": {},
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "backfill_retro",
|
||||||
|
"name": "Backfill Retrospective",
|
||||||
|
"description": "One-time script to seed retrospective data from Gitea PR history",
|
||||||
|
"script": "scripts/backfill_retro.py",
|
||||||
|
"category": "metrics",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "manual",
|
||||||
|
"executable": "python3",
|
||||||
|
"config": {
|
||||||
|
"retro_file": ".loop/retro/cycles.jsonl",
|
||||||
|
"summary_file": ".loop/retro/summary.json",
|
||||||
|
"gitea_api": "http://localhost:3000/api/v1"
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
".loop/retro/cycles.jsonl",
|
||||||
|
".loop/retro/summary.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pre_commit_checks",
|
||||||
|
"name": "Pre-commit Checks",
|
||||||
|
"description": "CI hygiene validation before commits — import checks, model config, syntax, formatting",
|
||||||
|
"script": "scripts/pre_commit_checks.py",
|
||||||
|
"category": "metrics",
|
||||||
|
"enabled": true,
|
||||||
|
"trigger": "pre_commit",
|
||||||
|
"executable": "python3",
|
||||||
|
"config": {
|
||||||
|
"check_imports": true,
|
||||||
|
"check_model_config": true,
|
||||||
|
"check_test_syntax": true,
|
||||||
|
"check_platform_paths": true,
|
||||||
|
"check_docker_tests": true,
|
||||||
|
"check_black_formatting": true
|
||||||
|
},
|
||||||
|
"outputs": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
36
timmy_automations/config/daily_run.json
Normal file
36
timmy_automations/config/daily_run.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Daily run schedule configuration",
|
||||||
|
"schedules": {
|
||||||
|
"every_cycle": {
|
||||||
|
"description": "Run before/after every dev cycle",
|
||||||
|
"automations": ["loop_guard", "cycle_retro"]
|
||||||
|
},
|
||||||
|
"every_10_cycles": {
|
||||||
|
"description": "Run approximately every 10 cycles",
|
||||||
|
"automations": ["triage_score"]
|
||||||
|
},
|
||||||
|
"every_20_cycles": {
|
||||||
|
"description": "Run approximately every 20 cycles",
|
||||||
|
"automations": ["loop_introspect", "deep_triage"]
|
||||||
|
},
|
||||||
|
"manual": {
|
||||||
|
"description": "Run on-demand only",
|
||||||
|
"automations": ["agent_workspace", "kimi_bootstrap", "kimi_resume", "backfill_retro"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"triggers": {
|
||||||
|
"pre_cycle": {
|
||||||
|
"description": "Run before each dev cycle begins",
|
||||||
|
"automations": ["loop_guard"]
|
||||||
|
},
|
||||||
|
"post_cycle": {
|
||||||
|
"description": "Run after each dev cycle completes",
|
||||||
|
"automations": ["cycle_retro"]
|
||||||
|
},
|
||||||
|
"pre_commit": {
|
||||||
|
"description": "Run before git commit",
|
||||||
|
"automations": ["pre_commit_checks"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
timmy_automations/config/triage_rules.yaml
Normal file
99
timmy_automations/config/triage_rules.yaml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Triage scoring weights and thresholds
|
||||||
|
# Used by: triage_score.py, deep_triage.sh
|
||||||
|
|
||||||
|
version: "1.0.0"
|
||||||
|
|
||||||
|
# Scoring thresholds
|
||||||
|
thresholds:
|
||||||
|
ready: 5 # Minimum score to be considered "ready" for work
|
||||||
|
excellent: 8 # Score indicating well-scoped, actionable issue
|
||||||
|
|
||||||
|
# Scope scoring (0-3)
|
||||||
|
scope:
|
||||||
|
file_patterns:
|
||||||
|
- pattern: '(?:src/|tests/|scripts/|\.py|\.html|\.js|\.yaml|\.toml|\.sh)'
|
||||||
|
weight: 1
|
||||||
|
description: "Mentions specific files"
|
||||||
|
function_patterns:
|
||||||
|
- pattern: '(?:def |class |function |method |`\w+\(\)`)'
|
||||||
|
weight: 1
|
||||||
|
description: "Mentions specific functions/classes"
|
||||||
|
title_length:
|
||||||
|
max_chars: 80
|
||||||
|
weight: 1
|
||||||
|
description: "Short, focused title"
|
||||||
|
meta_penalty: -2 # Penalty for philosophy/meta issues
|
||||||
|
|
||||||
|
# Acceptance criteria scoring (0-3)
|
||||||
|
acceptance:
|
||||||
|
language_patterns:
|
||||||
|
- pattern: '(?:should|must|expect|verify|assert|test.?case|acceptance|criteria|pass(?:es|ing)|fail(?:s|ing)|return(?:s)?|raise(?:s)?)'
|
||||||
|
weight: 2
|
||||||
|
min_matches: 3
|
||||||
|
description: "Has acceptance-related language"
|
||||||
|
test_patterns:
|
||||||
|
- pattern: '(?:tox|pytest|test_\w+|\.test\.|assert\s)'
|
||||||
|
weight: 1
|
||||||
|
description: "Mentions specific tests"
|
||||||
|
structure_patterns:
|
||||||
|
- pattern: '##\s*(problem|solution|expected|actual|steps)'
|
||||||
|
weight: 1
|
||||||
|
description: "Has structured sections"
|
||||||
|
|
||||||
|
# Alignment scoring (0-3)
|
||||||
|
alignment:
|
||||||
|
bug_tags:
|
||||||
|
- bug
|
||||||
|
- broken
|
||||||
|
- crash
|
||||||
|
- error
|
||||||
|
- fix
|
||||||
|
- regression
|
||||||
|
- hotfix
|
||||||
|
bug_score: 3 # Bugs on main = highest priority
|
||||||
|
|
||||||
|
refactor_tags:
|
||||||
|
- refactor
|
||||||
|
- cleanup
|
||||||
|
- tech-debt
|
||||||
|
- optimization
|
||||||
|
- perf
|
||||||
|
refactor_score: 2
|
||||||
|
|
||||||
|
feature_tags:
|
||||||
|
- feature
|
||||||
|
- feat
|
||||||
|
- enhancement
|
||||||
|
- capability
|
||||||
|
- timmy-capability
|
||||||
|
feature_score: 2
|
||||||
|
|
||||||
|
loop_generated_bonus: 1 # Boost for loop-generated issues
|
||||||
|
|
||||||
|
meta_tags:
|
||||||
|
- philosophy
|
||||||
|
- soul-gap
|
||||||
|
- discussion
|
||||||
|
- question
|
||||||
|
- rfc
|
||||||
|
meta_score: 0 # Philosophy issues are valid but lowest priority
|
||||||
|
|
||||||
|
# Quarantine rules
|
||||||
|
quarantine:
|
||||||
|
failure_threshold: 2 # Failures before quarantine
|
||||||
|
lookback_cycles: 20 # How many cycles to look back
|
||||||
|
|
||||||
|
# Issue type classification
|
||||||
|
types:
|
||||||
|
bug:
|
||||||
|
tags: [bug, broken, crash, error, fix, regression, hotfix]
|
||||||
|
priority_bonus: 0 # Handled by alignment scoring
|
||||||
|
feature:
|
||||||
|
tags: [feature, feat, enhancement, capability, timmy-capability]
|
||||||
|
refactor:
|
||||||
|
tags: [refactor, cleanup, tech-debt, optimization, perf]
|
||||||
|
philosophy:
|
||||||
|
tags: [philosophy, soul-gap, discussion, question, rfc]
|
||||||
|
dev_actionable: false
|
||||||
|
unknown:
|
||||||
|
default: true
|
||||||
30
timmy_automations/daily_run/README.md
Normal file
30
timmy_automations/daily_run/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Daily Run Automations
|
||||||
|
|
||||||
|
Scripts that run periodically to keep the development loop operational.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
| Script | Source | Purpose | Trigger |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| `cycle_retro.py` | `../../scripts/cycle_retro.py` | Log structured retrospective data | Post-cycle |
|
||||||
|
| `loop_guard.py` | `../../scripts/loop_guard.py` | Idle detection with exponential backoff | Pre-cycle |
|
||||||
|
| `triage_score.py` | `../../scripts/triage_score.py` | Mechanical issue scoring | Every 10 cycles |
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
These scripts are invoked by the dev loop orchestrator (Hermes). Manual execution:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# After a successful cycle
|
||||||
|
python3 scripts/cycle_retro.py --cycle 42 --success --issue 123 --type bug
|
||||||
|
|
||||||
|
# Check if queue has work (exits 0 if ready, 1 if idle)
|
||||||
|
python3 scripts/loop_guard.py
|
||||||
|
|
||||||
|
# Score open issues
|
||||||
|
python3 scripts/triage_score.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
See `../config/automations.json` for automation manifests and `../config/daily_run.json` for scheduling.
|
||||||
51
timmy_automations/metrics/README.md
Normal file
51
timmy_automations/metrics/README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Metrics & Integration Automations
|
||||||
|
|
||||||
|
Data collection, validation, and dashboard integration.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
| Script | Source | Purpose |
|
||||||
|
|--------|--------|---------|
|
||||||
|
| `backfill_retro.py` | `../../scripts/backfill_retro.py` | Seed retrospective data from Gitea history |
|
||||||
|
| `pre_commit_checks.py` | `../../scripts/pre_commit_checks.py` | CI hygiene validation |
|
||||||
|
|
||||||
|
## Backfill Retrospective
|
||||||
|
|
||||||
|
One-time script to populate `.loop/retro/` from Gitea merged PRs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/backfill_retro.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This seeds the cycle retrospective log so the LOOPSTAT panel isn't empty on new setups.
|
||||||
|
|
||||||
|
## Pre-commit Checks
|
||||||
|
|
||||||
|
Runs automatically before commits to catch common issues:
|
||||||
|
|
||||||
|
- ImportError regressions
|
||||||
|
- Model name assertions
|
||||||
|
- Platform-specific path issues
|
||||||
|
- Syntax errors in test files
|
||||||
|
- Black formatting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run manually
|
||||||
|
python3 scripts/pre_commit_checks.py
|
||||||
|
|
||||||
|
# Or via pre-commit hook
|
||||||
|
bash scripts/pre-commit-hook.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dashboard Integration
|
||||||
|
|
||||||
|
Metrics automations write to:
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `.loop/retro/cycles.jsonl` | Cycle retrospective log |
|
||||||
|
| `.loop/retro/summary.json` | Rolling statistics |
|
||||||
|
| `.loop/retro/insights.json` | Introspection recommendations |
|
||||||
|
| `.loop/automation_state.json` | Current automation states |
|
||||||
|
|
||||||
|
These feed the Mission Control dashboard at `/mission-control`.
|
||||||
34
timmy_automations/triage/README.md
Normal file
34
timmy_automations/triage/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Deep Triage Automations
|
||||||
|
|
||||||
|
Intelligent, LLM-assisted workflows for issue refinement and prioritization.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
| Script | Source | Purpose | Frequency |
|
||||||
|
|--------|--------|---------|-----------|
|
||||||
|
| `deep_triage.sh` | `../../scripts/deep_triage.sh` | LLM-driven issue refinement | Every 20 cycles |
|
||||||
|
| `deep_triage_prompt.md` | `../../scripts/deep_triage_prompt.md` | Prompt template for deep triage | — |
|
||||||
|
| `loop_introspect.py` | `../../scripts/loop_introspect.py` | Self-improvement analysis | Every 20 cycles |
|
||||||
|
|
||||||
|
## Deep Triage Protocol
|
||||||
|
|
||||||
|
1. **Mechanical scoring** runs first (`triage_score.py`)
|
||||||
|
2. **Introspection** analyzes trends (`loop_introspect.py`)
|
||||||
|
3. **Deep triage** consults Hermes + Timmy for refinement
|
||||||
|
4. **Queue updated** with refined, prioritized issues
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full deep triage (includes introspection)
|
||||||
|
bash scripts/deep_triage.sh
|
||||||
|
|
||||||
|
# Introspection only
|
||||||
|
python3 scripts/loop_introspect.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
- `.loop/queue.json` — Updated work queue
|
||||||
|
- `.loop/retro/deep-triage.jsonl` — Deep triage history
|
||||||
|
- `.loop/retro/insights.json` — Introspection recommendations
|
||||||
60
timmy_automations/workspace/README.md
Normal file
60
timmy_automations/workspace/README.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Workspace Automations
|
||||||
|
|
||||||
|
Agent workspace management for multi-agent operation.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
| Script | Source | Purpose |
|
||||||
|
|--------|--------|---------|
|
||||||
|
| `agent_workspace.sh` | `../../scripts/agent_workspace.sh` | Manage isolated agent environments |
|
||||||
|
| `bootstrap.sh` | `../../.kimi/scripts/bootstrap.sh` | One-time Kimi workspace setup |
|
||||||
|
| `dev.sh` | `../../.kimi/scripts/dev.sh` | Development helper commands |
|
||||||
|
| `resume.sh` | `../../.kimi/scripts/resume.sh` | Quick status check |
|
||||||
|
|
||||||
|
## Agent Workspace Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
/tmp/timmy-agents/
|
||||||
|
├── hermes/ # Loop orchestrator
|
||||||
|
├── kimi-0/ # Kimi pane 0
|
||||||
|
├── kimi-1/ # Kimi pane 1
|
||||||
|
├── kimi-2/ # Kimi pane 2
|
||||||
|
├── kimi-3/ # Kimi pane 3
|
||||||
|
└── smoke/ # Smoke testing environment
|
||||||
|
```
|
||||||
|
|
||||||
|
Each workspace gets:
|
||||||
|
- Isolated git clone (from Gitea, not local repo)
|
||||||
|
- Unique port range (8100+, 8200+)
|
||||||
|
- Separate data directory
|
||||||
|
- Own TIMMY_HOME
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Initialize all workspaces
|
||||||
|
bash scripts/agent_workspace.sh init-all
|
||||||
|
|
||||||
|
# Reset a specific workspace
|
||||||
|
bash scripts/agent_workspace.sh reset kimi-0
|
||||||
|
|
||||||
|
# Create branch in workspace
|
||||||
|
bash scripts/agent_workspace.sh branch kimi-0 feature/my-branch
|
||||||
|
|
||||||
|
# Bootstrap Kimi workspace
|
||||||
|
bash .kimi/scripts/bootstrap.sh
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
bash .kimi/scripts/resume.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Port Allocation
|
||||||
|
|
||||||
|
| Agent | Dashboard | Serve |
|
||||||
|
|-------|-----------|-------|
|
||||||
|
| hermes | 8100 | 8200 |
|
||||||
|
| kimi-0 | 8101 | 8201 |
|
||||||
|
| kimi-1 | 8102 | 8202 |
|
||||||
|
| kimi-2 | 8103 | 8203 |
|
||||||
|
| kimi-3 | 8104 | 8204 |
|
||||||
|
| smoke | 8109 | 8209 |
|
||||||
Reference in New Issue
Block a user