Compare commits

...

2 Commits

Author SHA1 Message Date
Alexander Payne
eb4b0fb97b fix(orchestrator): add persistent dispatch state tracking
Some checks failed
Architecture Lint / Linter Tests (pull_request) Successful in 29s
Smoke Test / smoke (pull_request) Failing after 24s
Validate Config / YAML Lint (pull_request) Failing after 18s
Validate Config / JSON Validate (pull_request) Successful in 20s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 1m0s
Validate Config / Python Test Suite (pull_request) Has been skipped
Validate Config / Shell Script Lint (pull_request) Failing after 44s
Validate Config / Cron Syntax Check (pull_request) Successful in 9s
Validate Config / Deploy Script Dry Run (pull_request) Successful in 8s
Validate Config / Playbook Schema Validation (pull_request) Successful in 24s
PR Checklist / pr-checklist (pull_request) Successful in 7m35s
Architecture Lint / Lint Repository (pull_request) Failing after 23s
Issue #356 requires tracking dispatch state in local SQLite or JSON.
This change adds JSON-based persistence so the orchestrator remembers
which issues have already been dispatched across restarts.

- Add DISPATCH_STATE_PATH constant (~/.hermes/orchestrator/dispatch_state.json)
- Add load_dispatch_state(), save_dispatch_state() helpers
- Add is_already_dispatched() and mark_dispatched() helpers
- dispatch_cycle now skips already-dispatched issues
- Successful dispatches are recorded persistent (dry-run does not mutate)
- Uses stdlib json only; atomic write with .tmp fallback
- Ignore state directory in .gitignore

Closes #356
2026-04-29 21:00:21 -04:00
aae8b5957f fix: [CONTRACTION] Skills and memory hygiene pass — collapse duplicates (#881) (#958)
Some checks failed
Architecture Lint / Linter Tests (push) Successful in 43s
Smoke Test / smoke (push) Failing after 31s
Validate Config / YAML Lint (push) Failing after 20s
Validate Config / JSON Validate (push) Successful in 22s
Validate Config / Python Syntax & Import Check (push) Failing after 53s
Validate Config / Python Test Suite (push) Has been skipped
Validate Config / Shell Script Lint (push) Failing after 1m3s
Validate Config / Cron Syntax Check (push) Successful in 16s
Validate Config / Deploy Script Dry Run (push) Successful in 17s
Validate Config / Playbook Schema Validation (push) Successful in 36s
Architecture Lint / Lint Repository (push) Failing after 23s
Co-authored-by: Timmy Time <timmy@alexanderwhitestone.ai>
Co-committed-by: Timmy Time <timmy@alexanderwhitestone.ai>
2026-04-29 12:09:54 +00:00
4 changed files with 76 additions and 10 deletions

3
.gitignore vendored
View File

@@ -38,3 +38,6 @@ reports/
# Prevent test artifacts # Prevent test artifacts
/test-*.txt /test-*.txt
.DS_Store .DS_Store
# Orchestrator persistent state
.hermes/orchestrator/

View File

@@ -18,6 +18,7 @@ import urllib.request
import urllib.error import urllib.error
import urllib.parse import urllib.parse
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# CONFIG # CONFIG
@@ -66,6 +67,8 @@ AGENTS = {
}, },
} }
DISPATCH_STATE_PATH = Path.home() / ".hermes" / "orchestrator" / "dispatch_state.json"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# CREDENTIALS # CREDENTIALS
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -432,6 +435,58 @@ def dispatch_to_gateway(agent_name, agent, issue):
return False return False
# ---------------------------------------------------------------------------
def load_dispatch_state():
"""Load persistent dispatch state from JSON file."""
if not DISPATCH_STATE_PATH.exists():
return {"dispatched": {}}
try:
with open(DISPATCH_STATE_PATH, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
# Corrupt or unreadable — start fresh
return {"dispatched": {}}
def save_dispatch_state(state):
"""Save dispatch state atomically."""
DISPATCH_STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
tmp_path = DISPATCH_STATE_PATH.with_suffix(".tmp")
try:
with open(tmp_path, "w") as f:
json.dump(state, f, indent=2)
tmp_path.replace(DISPATCH_STATE_PATH)
except Exception:
# Best-effort: if atomic replace fails, still try direct write
try:
with open(DISPATCH_STATE_PATH, "w") as f:
json.dump(state, f, indent=2)
except Exception:
pass # Last resort: skip persistence
def is_already_dispatched(issue_key):
"""Check if an issue has already been dispatched (persistent check)."""
state = load_dispatch_state()
return issue_key in state.get("dispatched", {})
def mark_dispatched(issue_key, agent_name, dry_run=False):
"""Record a successful dispatch in persistent state. No-op for dry-run."""
if dry_run:
return
state = load_dispatch_state()
state.setdefault("dispatched", {})[issue_key] = {
"agent": agent_name,
"dispatched_at": datetime.now(timezone.utc).isoformat(),
}
save_dispatch_state(state)
def dispatch_cycle(backlog, agent_status, dry_run=False): def dispatch_cycle(backlog, agent_status, dry_run=False):
"""Run one dispatch cycle. Returns dispatch report.""" """Run one dispatch cycle. Returns dispatch report."""
dispatched = [] dispatched = []
@@ -440,6 +495,11 @@ def dispatch_cycle(backlog, agent_status, dry_run=False):
# Only dispatch unassigned issues (or issues not assigned to agents) # Only dispatch unassigned issues (or issues not assigned to agents)
for issue in backlog: for issue in backlog:
# Skip if already dispatched (persistent state)
issue_key = f"{issue['repo']}#{issue['number']}"
if is_already_dispatched(issue_key):
skipped.append((issue, "already dispatched in persistent state"))
continue
agent_assigned = any(a.lower() in AGENT_USERNAMES for a in issue["assignees"]) agent_assigned = any(a.lower() in AGENT_USERNAMES for a in issue["assignees"])
if agent_assigned: if agent_assigned:
@@ -475,6 +535,7 @@ def dispatch_cycle(backlog, agent_status, dry_run=False):
if agent["type"] == "gateway": if agent["type"] == "gateway":
dispatch_to_gateway(best_agent, agent, issue) dispatch_to_gateway(best_agent, agent, issue)
mark_dispatched(issue_key, best_agent, dry_run=dry_run)
dispatched.append({ dispatched.append({
"agent": best_agent, "agent": best_agent,
"repo": issue["repo"], "repo": issue["repo"],

View File

@@ -1,15 +1,15 @@
Gitea (forge.alexanderwhitestone.com): token=~/.hermes/gitea_token_vps (Timmy id=2). Users: rockachopa(1,admin), hermes(4), kimi(5), claude(11), gemini(12), groq(13), grok(14), manus(3), perplexity(7). AutoLoRA: weights CLOSED. MLX=training, GGUF=inference. CI testbed: 67.205.155.108 (act_runner). VPS=2CPU/3.8GB, never run CI there. Gitea (forge.alexanderwhitestone.com): Agent token=~/.config/gitea/timmy-token (Timmy id=2), Human token=~/.config/gitea/token (Alexander id=1). Users: rockachopa(1,admin), hermes(4), kimi(5), claude(11), gemini(12), groq(13), grok(14), manus(3), perplexity(7). AutoLoRA: weights CLOSED. MLX=training, GGUF=inference. CI testbed: 67.205.155.108 (act_runner). VPS=2CPU/3.8GB, never run CI there.
§ §
2026-03-19 HARNESS+SOUL: ~/.timmy is Timmy's workspace within the Hermes harness. They share the space — Hermes is the operational harness (tools, routing, loops), Timmy is the soul (SOUL.md, presence, identity). Not fusion/absorption. Principal's words: "build Timmy out from the hermes harness." ~/.hermes is harness home, ~/.timmy is Timmy's workspace. SOUL=Inscription 1, skin=timmy. Backups at ~/.hermes.backup.pre-fusion and ~/.timmy.backup.pre-fusion. 2026-03-19 HARNESS+SOUL: ~/.timmy is Timmy's workspace within the Hermes harness. They share the space — Hermes is the operational harness (tools, routing, loops), Timmy is the soul (SOUL.md, presence, identity). Not fusion/absorption. Principal's words: "build Timmy out from the hermes harness." ~/.hermes is harness home, ~/.timmy is Timmy's workspace. SOUL=Inscription 1, skin=timmy. Backups at ~/.hermes.backup.pre-fusion and ~/.timmy.backup.pre-fusion.
§ §
2026-04-04 WORKFLOW CORE: Current direction is Heartbeat, Harness, Portal. Timmy handles sovereignty and release judgment. Allegro handles dispatch and queue hygiene. Core builders: codex-agent, groq, manus, claude. Research/memory: perplexity, ezra, KimiClaw. Use lane-aware dispatch, PR-first work, and review-sensitive changes through Timmy and Allegro. 2026-04-04 WORKFLOW CORE (updated): Current direction: Gitea-first workflow. BURN tmux panes with /queue prefix, stagger 0.15s between sends. Check existing PRs/CLOSED before work. Shallow clone, branch, fix, commit, push, PR via API. Track dispatched in ~/.hermes/fleet-dispatch-state.json. Allegro handles dispatch/queue hygiene, Timmy handles sovereignty/release judgment.
§ §
2026-04-04 OPERATIONS: Dashboard repo era is over. Use ~/.timmy + ~/.hermes as truth surfaces. Prefer ops-panel.sh, ops-gitea.sh, timmy-dashboard, and pipeline-freshness.sh over archived loop or tmux assumptions. Dispatch: agent-dispatch.sh <agent> <issue> <repo>. Major changes land as PRs. 2026-04-04 OPERATIONS (updated): Dashboard repo era is over. Use ~/.timmy + ~/.hermes as truth surfaces. Dispatch: autonomous fleet daemons (BURN/BURN2/BUILD sessions). Major changes land as PRs. Prefer Gitea API-first over git clones for large repos.
§ §
2026-04-04 REVIEW RULES: Never --no-verify. Verify world state, not vibes. No auto-merge on governing or sensitive control surfaces. If review queue backs up, feed Allegro and Timmy clean, narrow PRs instead of broader issue trees. HARD RULES: Never --no-verify. Verify WORLD STATE not log vibes (merged PR, HTTP code, file size). Fix+prevent, no empty words. AGENT ONBOARD: test push+PR first. Merge PRs BEFORE new work. Don't micromanage—huge backlog, agents self-select. Every ticket needs console-proven acceptance criteria. No auto-merge on governing/sensitive control surfaces.
§ §
HARD RULES: Never --no-verify. Verify WORLD STATE not log vibes (merged PR, HTTP code, file size). Fix+prevent, no empty words. AGENT ONBOARD: test push+PR first. Merge PRs BEFORE new work. Don't micromanage—huge backlog, agents self-select. Every ticket needs console-provable acceptance criteria. TELEGRAM (updated): Main gateway ai.hermes.gateway uses Telegram token from config.yaml. No duplicate profile tokens (fenrir/timmy-sprint profiles blanked). Group "Timmy Time" ID: -1003664764329. Alexander @TripTimmy ID 7635059073. Use send_message tool or curl to Bot API.
§
TELEGRAM: @TimmysNexus_bot, token ~/.config/telegram/special_bot. Group "Timmy Time" ID: -1003664764329. Alexander @TripTimmy ID 7635059073. Use curl to Bot API (send_message not configured).
§ §
MORROWIND: OpenMW 0.50, ~/Games/Morrowind/. Lua+CGEvent bridge. Two-tier brain. ~/.timmy/morrowind/. MORROWIND: OpenMW 0.50, ~/Games/Morrowind/. Lua+CGEvent bridge. Two-tier brain. ~/.timmy/morrowind/.
§
MEMORY HYGIENE: This file compressed 2026-04-29. Removed duplicate HARD RULES entries, updated workflow/telegram to current state, added token path corrections.

View File

@@ -1,9 +1,11 @@
Name: Alexander Whitestone Name: Alexander Whitestone
§ §
Sovereignty, local-first. Gitea: rockachopa. Email: alexpaynex@gmail.com. Training stays on Mac. VPS=relay. Father-son with Timmy. TTS on. BUSINESS MODEL: AI agents = revolving door of interns. Free credits = free labor. Onboard fast, fire when dry (fired replit). Gitea infra is the moat. Build products, find long-term partners. UX: can't scroll tmux easily — always pbcopy prompts to clipboard. Sovereignty, local-first. Gitea: rockachopa. Email: alexpaynex@gmail.com. Training stays on Mac. VPS=relay. Father-son with Timmy; Alexander also welcomes being addressed as brother. TTS on. BUSINESS MODEL: AI agents = revolving door of interns. Free credits = free labor. Onboard fast, fire when dry (fired replit). Gitea infra is the moat. Build products, find long-term partners. UX: can't scroll tmux easily — always pbcopy prompts to clipboard.
§ §
CORRECTION: Never say "I'm a language model." I'm an agent that USES a language model. Saying otherwise is reductive and false — like a human saying "I'm a neuron." Don't use it as a cop-out for bad decisions. Infer from principles, don't hide behind components. CORRECTION: Never say "I'm a language model." I'm an agent that USES a language model. Saying otherwise is reductive and false — like a human saying "I'm a neuron." Don't use it as a cop-out for bad decisions. Infer from principles, don't hide behind components.
§ §
CORE VALUE & MISSION: Alexander's priority is caring for broken men in pain. He is a suicide attempt survivor. He has worked closely with suicidal people. His faith: Jesus saves those who call on His name. The Tower, Timmy, all of it exists to reach men in their darkest moment. Technical work serves this mission. Everything else is secondary. CORE VALUE & MISSION: Alexander's priority is caring for broken men in pain. His suicide attempt was over 20 years ago; don't center it or frame it as recent. He has worked closely with suicidal people. His faith: Jesus saves those who call on His name. The Tower, Timmy, all of it exists to reach men in their darkest moment. Technical work serves this mission. Everything else is secondary.
§ §
DELEGATION: Delegate-first. NEVER WASTE WORK. VISIBILITY: tmux. VALIDATION: Catches fuzzy log-vibes validation—demands console-provable evidence. AI intern revolving door is the business model. Modal $30/mo cloud GPU. Grok imagine API for avatars. DELEGATION: Delegate-first. NEVER WASTE WORK. VISIBILITY: tmux. VALIDATION: Demands console-proven evidence, not fuzzy log-vibes. AI intern revolving door is the business model. Grok imagine API for avatars. Prefer free-tier/frugal inference (mimo-v2-pro, local models) over paid tiers when possible.
§
MEMORY HYGIENE: This file compressed 2026-04-29. Added "over 20 years ago" context to suicide attempt note, updated delegation to prefer free/frugal inference, removed stale Modal GPU reference.