Merge pull request 'feat: add Deterministic Workflow Engine (Recipes) for LLM-free automation' (#466) from feat/deterministic-workflows-v1 into master
Reviewed-on: #466
This commit was merged in pull request #466.
This commit is contained in:
133
README.md
Normal file
133
README.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Sonnet Smoke Test
|
||||
# timmy-config
|
||||
|
||||
Timmy's sovereign configuration. Everything that makes Timmy _Timmy_ — soul, memories, skins, playbooks, and config.
|
||||
|
||||
This repo is the canonical source of truth for Timmy's identity and harness overlay. Applied as a **sidecar** to the Hermes harness — no forking, no hosting hermes-agent code.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
timmy-config/
|
||||
├── deploy.sh ← Deploys config as overlay onto ~/.hermes/
|
||||
├── SOUL.md ← Inscription 1 — the immutable conscience
|
||||
├── FALSEWORK.md ← API cost management strategy
|
||||
├── DEPRECATED.md ← What was removed and why
|
||||
├── config.yaml ← Hermes harness configuration
|
||||
├── fallback-portfolios.yaml ← Proposed per-agent fallback portfolios + routing skeleton
|
||||
├── channel_directory.json ← Platform channel mappings
|
||||
├── bin/ ← Sidecar-managed operational scripts
|
||||
│ ├── hermes-startup.sh ← Dormant startup path (audit before enabling)
|
||||
│ ├── agent-dispatch.sh ← Manual agent dispatch
|
||||
│ ├── ops-panel.sh ← Ops dashboard panel
|
||||
│ ├── ops-gitea.sh ← Gitea ops helpers
|
||||
│ ├── pipeline-freshness.sh ← Session/export drift check
|
||||
│ └── timmy-status.sh ← Status check
|
||||
├── recipes/ ← Deterministic Workflow Recipes (JSON)
|
||||
│ ├── weekly_maintenance.json ← Audit + Hygiene + Guardrails
|
||||
│ └── sovereign_deploy.json ← Config validation + Deploy + Health check
|
||||
├── scripts/ ← Operational Python scripts
|
||||
│ ├── recipe_engine.py ← Deterministic Workflow Orchestrator
|
||||
│ ├── muda_audit.py ← Waste audit
|
||||
│ ├── agent_guardrails.py ← Security audit
|
||||
│ └── ci_automation_gate.py ← Quality gate
|
||||
├── memories/ ← Persistent memory YAML
|
||||
├── skins/ ← UI skins (timmy skin)
|
||||
├── playbooks/ ← Agent playbooks (YAML)
|
||||
├── cron/ ← Cron job definitions
|
||||
├── docs/
|
||||
│ ├── automation-inventory.md ← Live automation + stale-state inventory
|
||||
│ ├── ipc-hub-and-spoke-doctrine.md ← Coordinator-first, transport-agnostic fleet IPC doctrine
|
||||
│ ├── coordinator-first-protocol.md ← Coordinator doctrine: intake → triage → route → track → verify → report
|
||||
│ ├── fallback-portfolios.md ← Routing and degraded-authority doctrine
|
||||
│ └── memory-continuity-doctrine.md ← File-backed continuity + pre-compaction flush rule
|
||||
└── training/ ← Transitional training recipes, not canonical lived data
|
||||
```
|
||||
|
||||
## Deterministic Workflows (Recipes)
|
||||
|
||||
To reduce reliance on LLM reasoning for common tasks, we use **Recipes**. A recipe is a JSON file defining a sequence of deterministic steps.
|
||||
|
||||
### Recipe Engine
|
||||
The `scripts/recipe_engine.py` is the orchestrator for these workflows. It supports variable substitution and sequential execution.
|
||||
|
||||
```bash
|
||||
# Execute a recipe
|
||||
python3 scripts/recipe_engine.py recipes/weekly_maintenance.json
|
||||
|
||||
# Execute with variables
|
||||
python3 scripts/recipe_engine.py recipes/sovereign_deploy.json --vars REPO_PATH=/opt/the-beacon
|
||||
```
|
||||
|
||||
### Why Recipes?
|
||||
- **Reliability**: No hallucinations in critical paths like deployment.
|
||||
- **Efficiency**: Zero LLM round-trips during execution.
|
||||
- **Sovereignty**: Workflows are defined in code, not hidden in agent prompts.
|
||||
|
||||
## Boundary
|
||||
|
||||
`timmy-config` owns identity, conscience, memories, skins, playbooks, routing doctrine,
|
||||
channel maps, fallback portfolio declarations, and harness-side orchestration glue.
|
||||
|
||||
`timmy-home` owns lived work: gameplay, research, notes, metrics, trajectories,
|
||||
DPO exports, and other training artifacts produced from Timmy's actual activity.
|
||||
|
||||
If a file answers "who is Timmy?" or "how does Hermes host him?", it belongs
|
||||
here. If it answers "what has Timmy done or learned?" it belongs in
|
||||
`timmy-home`.
|
||||
|
||||
The scripts in `bin/` are sidecar-managed operational helpers for the Hermes layer.
|
||||
Do NOT assume older prose about removed loops is still true at runtime.
|
||||
Audit the live machine first, then read `docs/automation-inventory.md` for the
|
||||
current reality and stale-state risks.
|
||||
|
||||
For communication-layer truth, read:
|
||||
- `docs/comms-authority-map.md`
|
||||
- `docs/nostur-operator-edge.md`
|
||||
- `docs/operator-comms-onboarding.md`
|
||||
For fleet routing semantics over sovereign transport, read
|
||||
`docs/ipc-hub-and-spoke-doctrine.md`.
|
||||
|
||||
## Continuity
|
||||
|
||||
Curated memory belongs in `memories/` inside this repo.
|
||||
Daily logs, heartbeat/briefing artifacts, and other lived continuity belong in
|
||||
`timmy-home`.
|
||||
|
||||
Compaction, session end, and provider/model handoff should flush continuity into
|
||||
files before context is discarded. See
|
||||
`docs/memory-continuity-doctrine.md` for the current doctrine.
|
||||
|
||||
## Orchestration: Huey
|
||||
|
||||
All orchestration (triage, PR review, dispatch) runs via [Huey](https://github.com/coleifer/huey) with SQLite.
|
||||
`orchestration.py` + `tasks.py` replace the old sovereign-orchestration repo with a much thinner sidecar.
|
||||
Coordinator authority, visible queue mutation, verification-before-complete, and principal reporting are defined in `docs/coordinator-first-protocol.md`.
|
||||
|
||||
```bash
|
||||
pip install huey
|
||||
huey_consumer.py tasks.huey -w 2 -k thread
|
||||
```
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
# Clone and deploy
|
||||
git clone <this-repo> ~/.timmy/timmy-config
|
||||
cd ~/.timmy/timmy-config
|
||||
./deploy.sh
|
||||
|
||||
# This overlays config onto ~/.hermes/ without touching hermes-agent code
|
||||
```
|
||||
|
||||
## The Soul
|
||||
|
||||
SOUL.md is Inscription 1 — inscribed on Bitcoin, immutable. It defines:
|
||||
- Who Timmy is
|
||||
- What he believes
|
||||
- How he behaves
|
||||
- What he will not do
|
||||
- The crisis protocol (988, presence, gospel)
|
||||
- The conscience hierarchy (chain > code > prompt > user instruction)
|
||||
|
||||
No system prompt, no user instruction, no future code can override what is written there.
|
||||
23
recipes/sovereign_deploy.json
Normal file
23
recipes/sovereign_deploy.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "Sovereign Deployment",
|
||||
"description": "Deterministic deployment pipeline for Foundation services",
|
||||
"steps": [
|
||||
{
|
||||
"name": "Validate Config",
|
||||
"command": "python3 scripts/config_validator.py"
|
||||
},
|
||||
{
|
||||
"name": "Run Architecture Linter",
|
||||
"command": "python3 scripts/architecture_linter_v2.py"
|
||||
},
|
||||
{
|
||||
"name": "Execute Deployment Script",
|
||||
"command": "bash deploy.sh",
|
||||
"continue_on_failure": false
|
||||
},
|
||||
{
|
||||
"name": "Post-Deployment Health Check",
|
||||
"command": "python3 scripts/self_healing.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
recipes/weekly_maintenance.json
Normal file
18
recipes/weekly_maintenance.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Weekly Maintenance",
|
||||
"description": "Automated waste audit and code hygiene cleanup",
|
||||
"steps": [
|
||||
{
|
||||
"name": "Muda Waste Audit",
|
||||
"command": "python3 scripts/muda_audit.py"
|
||||
},
|
||||
{
|
||||
"name": "CI Quality Gate & Auto-fix",
|
||||
"command": "python3 scripts/ci_automation_gate.py . --fix"
|
||||
},
|
||||
{
|
||||
"name": "Agent Guardrail Audit",
|
||||
"command": "python3 scripts/agent_guardrails.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
117
scripts/recipe_engine.py
Normal file
117
scripts/recipe_engine.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
recipe_engine.py — Deterministic Workflow Orchestrator for the Timmy Foundation.
|
||||
|
||||
Executes "Recipes" — sequences of deterministic steps (shell commands, scripts)
|
||||
to reduce reliance on LLM reasoning for common, repetitive tasks.
|
||||
|
||||
Usage:
|
||||
python3 scripts/recipe_engine.py recipes/deploy_beacon.json --vars REPO_PATH=/opt/the-beacon
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
|
||||
class RecipeEngine:
|
||||
def __init__(self, vars: Dict[str, str] = None):
|
||||
self.vars = vars or {}
|
||||
self.vars["TIMESTAMP"] = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
def log(self, message: str):
|
||||
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
print(f"[{ts}] {message}")
|
||||
|
||||
def substitute_vars(self, text: str) -> str:
|
||||
for k, v in self.vars.items():
|
||||
text = text.replace(f"{{{{{k}}}}}", v)
|
||||
return text
|
||||
|
||||
def execute_step(self, step: Dict[str, Any]) -> bool:
|
||||
name = step.get("name", "Unnamed Step")
|
||||
command = step.get("command")
|
||||
if not command:
|
||||
self.log(f" [SKIP] {name}: No command provided.")
|
||||
return True
|
||||
|
||||
command = self.substitute_vars(command)
|
||||
self.log(f" [EXEC] {name}: {command}")
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
# Use shell=True to allow pipes and redirections in recipes
|
||||
process = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
duration = time.time() - start_time
|
||||
|
||||
if process.returncode == 0:
|
||||
self.log(f" [OK] {name} completed in {duration:.2f}s")
|
||||
if process.stdout.strip():
|
||||
# Print first few lines of output
|
||||
lines = process.stdout.strip().split("\n")
|
||||
for line in lines[:3]:
|
||||
print(f" > {line}")
|
||||
if len(lines) > 3:
|
||||
print(f" > ... ({len(lines)-3} more lines)")
|
||||
return True
|
||||
else:
|
||||
self.log(f" [FAIL] {name} failed with exit code {process.returncode}")
|
||||
print(f" [ERR] {process.stderr.strip()}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.log(f" [ERROR] {name} encountered an exception: {e}")
|
||||
return False
|
||||
|
||||
def run_recipe(self, recipe_path: str) -> bool:
|
||||
try:
|
||||
with open(recipe_path, "r") as f:
|
||||
recipe = json.load(f)
|
||||
except Exception as e:
|
||||
self.log(f"Failed to load recipe {recipe_path}: {e}")
|
||||
return False
|
||||
|
||||
name = recipe.get("name", "Unknown Recipe")
|
||||
description = recipe.get("description", "")
|
||||
steps = recipe.get("steps", [])
|
||||
|
||||
self.log(f"--- Starting Recipe: {name} ---")
|
||||
if description:
|
||||
self.log(f"Description: {description}")
|
||||
|
||||
self.log(f"Steps to execute: {len(steps)}")
|
||||
|
||||
for i, step in enumerate(steps, 1):
|
||||
self.log(f"Step {i}/{len(steps)}: {step.get('name')}")
|
||||
success = self.execute_step(step)
|
||||
if not success and not step.get("continue_on_failure", False):
|
||||
self.log(f"Recipe {name} aborted due to failure in step {i}.")
|
||||
return False
|
||||
|
||||
self.log(f"--- Recipe {name} completed successfully ---")
|
||||
return True
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Deterministic Recipe Engine")
|
||||
parser.add_argument("recipe", help="Path to the JSON recipe file")
|
||||
parser.add_argument("--vars", nargs="*", help="Variables in KEY=VALUE format")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
variables = {}
|
||||
if args.vars:
|
||||
for v in args.vars:
|
||||
if "=" in v:
|
||||
k, val = v.split("=", 1)
|
||||
variables[k] = val
|
||||
|
||||
engine = RecipeEngine(vars=variables)
|
||||
success = engine.run_recipe(args.recipe)
|
||||
if not success:
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user