Skip to content

feat(hermes): add secure runtime and Tailscale integration - #126

Open
pedrotecinf wants to merge 91 commits into
evolution-foundation:developfrom
pedrotecinf:feat/hermes-tailscale-integration
Open

feat(hermes): add secure runtime and Tailscale integration#126
pedrotecinf wants to merge 91 commits into
evolution-foundation:developfrom
pedrotecinf:feat/hermes-tailscale-integration

Conversation

@pedrotecinf

@pedrotecinf pedrotecinf commented Jul 26, 2026

Copy link
Copy Markdown

Summary

This PR integrates Hermes Agent as a first-class EvoNexus runtime and adds the control-plane, scheduler, dashboard, observability, deployment, and secure remote-access primitives required to operate it.

The integration is additive: existing Claude and OpenClaude providers remain supported, rollout can be scoped per workflow, and HERMES_KILL_SWITCH=1 routes new policy decisions away from Hermes.

Why

EvoNexus currently has provider-specific execution paths but no end-to-end Hermes lifecycle. This change provides a single, observable path from provider/profile selection through scheduled execution, runtime-run state, metrics, Activity, and operator controls.

Architecture

  • ADWs/runner.py remains the compatibility boundary for ADWs and dispatches Hermes through ADWs/hermes_adapter.py.
  • Runtime policy supports off, shadow, canary, and default rollout modes.
  • Scheduler and manual triggers attach trigger provenance and runtime-run IDs.
  • Runtime-run state, approvals, heartbeats, goals, scheduled tasks, Control API audit records, and event outbox records are persisted through Alembic migration 0012.
  • The dashboard exposes provider/profile resolution, runtime state, Control API operations, Hermes UI proxying, and Tailscale controls.
  • The terminal bridge preserves bounded conversation replay and redacts subprocess stderr from client-facing exit messages.

Changes

Hermes runtime and routing

  • Adds Hermes CLI dispatch with validated profile slugs, bounded turns, timeout/cancellation handling, process-group termination, and a JSON compatibility envelope.
  • Adds provider/profile configuration, routing, fallback profile resolution, and masked profile metadata APIs.
  • Adds rollout policy and a kill switch without removing existing providers.
  • Makes runtime metrics concurrency-safe with locking and atomic writes while preserving legacy aggregate fields.

Lifecycle, scheduler, and automation

  • Adds runtime-run state transitions, cancellation, approvals, checkout leases, heartbeats, goals, routines, scheduled tasks, idempotent mutations, and trigger provenance.
  • Acquires ticket leases with a conditional atomic update, persists each heartbeat's configured lock timeout for janitor decisions, validates lock timeouts, and allows only the current owner to release a lease.
  • Periodically and conditionally recovers stale scheduled tasks after a post-timeout grace period, fences superseded attempts before runtime attachment, immediately before external execution, and during finalization, atomically arbitrates terminalization, cancellation, manual requeue, and recovery on SQLite and PostgreSQL, terminalizes orphaned queued or running runtime runs once, preserves fresh active work, and avoids duplicate pollers under Flask's development reloader.
  • Reserves idempotency keys before starting side-effecting routine subprocesses, manual heartbeat dispatches, and scheduled-task mutations; stale transactional reservations are reclaimed after a bounded lease, while indeterminate external effects are never repeated, and final redacted results are stored for replay.
  • Keeps heartbeats alive during long-running jobs and records terminal states, exit codes, duration, and provider metadata.
  • Adds a self-contained, versioned allowlist for built-in ECC workflow identifiers; no host-only .ecc/commands directory is required.
  • Publishes mutation events through a transactional outbox and avoids duplicate Activity entries.

Control API

  • Adds /api/control/v1/* endpoints protected by bearer authentication and exact, deny-by-default scopes.
  • Supports token rotation with a previous-token slot.
  • Requires idempotency keys for mutations and emits correlation IDs and audit records.
  • Never stores or returns the presented bearer token.

Dashboard and UI

  • Adds Hermes provider/profile controls, runtime state, heartbeats, scheduler and routine controls, and a bounded terminal/chat bridge.
  • Adds authenticated agent mention autocomplete with keyboard and pointer interaction, loading/error/empty states, blocked-agent handling, compact one-line summaries, and comment preservation after failed submission.
  • Adds Tailscale status and administrative controls.

Tailscale and dashboard access

  • Tailscale status requires an authenticated dashboard session.
  • Connect and disconnect require administrative configuration permission.
  • Only BackendState == "Running" is treated as connected.
  • Disconnect uses tailscale down.
  • Auth keys are validated, redacted from command errors, and never written to JSON responses or audit details.
  • Userspace deployments publish the loopback-only Hermes UI with tailscale serve only when Hermes Basic Auth is configured; already-connected nodes repair the mapping without re-running tailscale up.
  • Dashboard deployments persist Tailscale node identity in /var/lib/tailscale; auth keys remain ephemeral application inputs.
  • The standalone Hermes dashboard binds to loopback unless direct remote access is explicitly configured.
  • EvoNexus continues to proxy Hermes through its authenticated dashboard route.

Deployment and CI

  • Produces two images from the same immutable revision:
    • evo-nexus-runtime for both runtime and scheduler;
    • evo-nexus-dashboard for the dashboard/control plane.
  • Uses EVONEXUS_IMAGE_TAG for immutable runtime/dashboard parity; scheduler intentionally reuses the runtime image.
  • Keeps image publication separate from infrastructure rollout.
  • Makes the optional native Hermes API server opt-in, disabled by default, and fail-closed when enabled without a key.
  • Makes the local dashboard UI workspace dependency reproducible in CI and image builds.
  • Applies Alembic migrations before dashboard startup, validates model/migration table, column, foreign-key, and ticket-status parity, and restores the pre-0012 ticket constraint safely on downgrade.
  • Removes fork-specific registry, host, branch, and deployment references.

Configuration

Secret examples are intentionally redacted.

Operator-configurable variables

Variable Consumer Required Actual default Purpose / security note
HERMES_MODEL Hermes runtime No Hermes provider default Selects the Hermes model.
HERMES_MAX_ITERATIONS Hermes adapter No ADW max_turns (30 for the compatibility runner) Bounds agent turns; the adapter translates the existing ADW max-turn contract.
OPENROUTER_API_KEY Hermes/OpenRouter Conditional none Secret; required only when the selected provider needs it.
ANTHROPIC_API_KEY Hermes/Anthropic Conditional none Secret; required only when the selected provider needs it.
HERMES_CONTROL_API_TOKEN Control API Conditional none Secret; required to use /api/control/v1/*. No token means authentication always fails.
HERMES_CONTROL_API_TOKEN_PREVIOUS Control API No none Secret; temporary rotation slot.
HERMES_CONTROL_API_SCOPES Control API Conditional empty / deny all Comma-separated exact scopes; required for every permitted operation.
HERMES_KILL_SWITCH Runtime policy No unset / disabled 1, true, yes, or on disables Hermes for new policy decisions.
EVONEXUS_HERMES_USERNAME Dashboard bootstrap/proxy No none Must be paired with EVONEXUS_HERMES_PASSWORD to expose the standalone Hermes UI beyond loopback.
EVONEXUS_HERMES_PASSWORD Dashboard bootstrap/proxy No none Secret; hashed by the Hermes dashboard bootstrap and used server-side by the authenticated proxy.
EVONEXUS_HERMES_API_ENABLED Dashboard bootstrap No false Explicitly enables the optional native Hermes API server.
EVONEXUS_HERMES_API_KEY Native Hermes API Conditional none Secret; mandatory when the native API server is enabled.
HERMES_UI_PORT Dashboard bootstrap/proxy No 9119 Internal Hermes dashboard port.
HERMES_API_PORT Dashboard bootstrap No 8642 Internal optional native API port.
EVONEXUS_TAILSCALE_HOSTNAME Tailscale control route No evonexus-hermes Validated tailnet hostname used by tailscale up.
EVONEXUS_IMAGE_REGISTRY Compose/Swarm deployment No evoapicloud Image namespace/registry override.
EVONEXUS_IMAGE_TAG Compose/Swarm deployment Yes for immutable deployment no floating default in production manifests Shared immutable tag for runtime, scheduler, and dashboard images.

Runtime-generated/internal variables

Variable Producer / consumer Default Purpose
EVONEXUS_TRIGGERED_BY Scheduler/manual trigger → ADW runner schedule when absent Records schedule, manual, or other trigger provenance in metrics and Activity.
EVONEXUS_CONTAINERIZED Container manifest / service controls unset; /.dockerenv is also detected Prevents unsupported host process controls in containers.
EVONEXUS_REVISION Deployment → health endpoint empty Dashboard/runtime revision metadata.
EVONEXUS_SCHEDULER_REVISION Deployment → health endpoint empty Scheduler revision metadata used for parity reporting.
API_SERVER_ENABLED, API_SERVER_HOST, API_SERVER_PORT, API_SERVER_KEY start-dashboard.sh → Hermes Agent generated only when explicitly enabled Internal mapping for the optional native Hermes API server.
HERMES_DASHBOARD_HOST, HERMES_ADMIN_USER, HERMES_ADMIN_PASS start-dashboard.sh → Hermes dashboard generated by bootstrap policy Internal dashboard bind/auth settings; operator-facing inputs use the EVONEXUS_HERMES_* variables above.

Provider-specific keys not selected by a deployment remain optional. No credential value is committed or emitted by the application.

Security considerations

  • Control API: bearer authentication, constant-time token comparison, exact scopes, idempotency, correlation IDs, and audit records.
  • Tailscale: authenticated status, admin-only mutations, strict hostname/auth-key validation, fail-closed state interpretation, and secret redaction.
  • Hermes proxy: EvoNexus session authentication remains the external gate; dashboard Authorization, cookies, CSRF headers, and upstream Set-Cookie never cross the trust boundary.
  • Native Hermes API: disabled by default and refuses startup when enabled without a key.
  • Runtime: process-group cancellation, bounded turns/timeouts, centralized secret redaction before subprocess output is returned or persisted, and no automatic replay of failed side-effecting work on another provider.
  • Profiles API: returns safe scalar metadata only and excludes secret fields.

Compatibility

  • Existing Claude/OpenClaude configurations continue to work.
  • Existing metrics fields are preserved; Hermes/runtime provenance fields are additive.
  • Existing database installations upgrade through Alembic 0012; fresh upgrade, downgrade-to-base, and the 00120011 archived-ticket transition are covered on SQLite.
  • Scheduler does not require a third image.
  • Direct Tailscale access is optional; loopback-only operation remains the default.
  • The optional native Hermes API remains off unless explicitly configured.

Test plan

Executed locally on the final branch:

  • Full Python test suite: 734 passed, 139 skipped.
  • Focused CI-equivalent matrix: 352 passed, 75 skipped.
  • Frontend Vitest: 15 passed.
  • Frontend TypeScript/Vite production build.
  • Locked Linux/ARM64 frontend Docker build stage, including the local UI workspace package.
  • Complete Linux/ARM64 standalone and Swarm dashboard image builds, including in-image frontend, terminal bridge, Tailscale/Hermes CLI, and fresh Alembic upgrade verification.
  • Current Swarm dashboard image migration round-trip: archived accepted at 0012 and mapped to closed when downgrading to 0011.
  • PostgreSQL migration suite, including the 00120011 constraint downgrade, against a temporary PostgreSQL 16 instance: 4 passed.
  • PostgreSQL 16 concurrency/recovery checks: start-versus-recovery produced one winner with a real timeout; orphaned queued runs terminalized; stale idempotency reservations were reclaimed atomically.
  • PostgreSQL 16 unified recovery/fencing check: attached queued runs are terminalized atomically before safe requeue; running/cancel-requested attempts become terminal with an indeterminate external outcome and are not retried automatically; independent runtime recovery preserves runs still attached to active tasks (POSTGRES_UNIFIED_RECOVERY_FENCING_PASS).
  • SQLite and PostgreSQL 16 idempotency ownership fencing: stale reclaim installs a new generation; a superseded holder cannot mutate the task, overwrite the response, or duplicate audit/outbox (POSTGRES_IDEMPOTENCY_OWNER_AND_RECOVERY_FENCE_PASS).
  • SQLite and PostgreSQL 16 pre-attach cancellation fencing: a recovered attempt can be cancelled while runtime_run_id still references a historical attempt; the historical run remains terminal and the paused worker cannot attach/start a new run (POSTGRES_CANCEL_PRE_ATTACH_AND_IDEMPOTENCY_FENCE_PASS).
  • SQLite and PostgreSQL 16 approval lease renewal: resuming a stale awaiting_approval run renews both linked task and run leases atomically, so immediate recovery preserves the approved attempt (POSTGRES_APPROVAL_LEASE_AND_FENCING_PASS).
  • SQLite and PostgreSQL 16 runtime-run cancellation: cancelling a run attached to its owning scheduled task transitions task and run atomically; active execution requests cancellation and signals the linked process after commit (POSTGRES_RUNTIME_CANCEL_AND_APPROVAL_FENCING_PASS).
  • PostgreSQL 16 lock-order and retry fencing: all multi-entity transitions lock scheduled task before runtime run; cancel-versus-finalize and two concurrent retries each produced exactly one winner in 30 repetitions without deadlocks (POSTGRES_LOCK_ORDER_AND_RETRY_CAS_30X_PASS).
  • SQLite and PostgreSQL 16 spawn handoff: cancellation and external spawn arbitrate through the same per-task handoff; runner and script processes are registered before cancellation can commit, and a cancellation that wins first prevents spawn (POSTGRES_SPAWN_HANDOFF_PASS).
  • SQLite and PostgreSQL 16 approval cancellation: cancelling an awaiting_approval run and rejecting an approval both terminate any registered process after the terminal commit; handoff locks are reference-counted and removed after the last active user/waiter (POSTGRES_APPROVAL_CANCEL_PROCESS_AND_LOCK_REGISTRY_PASS).
  • PostgreSQL 16 approval/cancellation checks: cancel_requested won over late success, rejected approval cancelled the linked task, awaiting approval blocked recovery, and concurrent success-versus-approval produced exactly one winner in 20 repetitions (POSTGRES_APPROVAL_CANCEL_CAS_PASS, POSTGRES_APPROVAL_SUCCESS_RACE_20X_PASS).
  • Fresh focused regression script: 11 passed, marker HERMES_VERIFY_APPROVAL_CANCEL_PROCESS_FINAL_PASS; temporary hermes-verify-* script removed.
  • Complete Linux/ARM64 runtime image build with Hermes/Claude CLIs, entrypoint, and Python runtime verification.
  • Production npm dependency audit: 0 vulnerabilities (--omit=dev).
  • Terminal server Node test suite: 5 passed.
  • python -m py_compile for affected runtime/backend modules.
  • bash -n start-dashboard.sh.
  • docker compose config for hub and proxy manifests.
  • docker stack config for evonexus.stack.yml.
  • git diff --check upstream/develop...HEAD.

Rollout

  1. Build and publish evo-nexus-runtime and evo-nexus-dashboard with the same immutable revision tag.
  2. Apply migration 0012 before enabling Hermes mutations.
  3. Configure provider credentials and profiles.
  4. Start with rollout mode off or shadow.
  5. Enable a limited canary and verify runtime-run state, exit code, metrics, heartbeat, Activity, and revision parity.
  6. Promote workflows gradually.
  7. Configure Tailscale only if direct tailnet access is required.

Image publication does not perform infrastructure rollout. Operators must deploy the new immutable tag explicitly.

Rollback

  1. Set HERMES_KILL_SWITCH=1 or switch affected workflows to off.
  2. Restore the previous immutable runtime/dashboard image tag.
  3. Keep the database at migration 0012 during normal application rollback; the schema is additive.
  4. If a full schema rollback is required, stop all new-version services first, back up the database, and then run the Alembic downgrade.
  5. Run tailscale down if tailnet exposure must be removed.

No deployment, service restart, volume deletion, or legacy-stack removal is performed by this PR.

Reviewer guide

Suggested review order:

  1. ADWs/hermes_adapter.py, ADWs/runner.py, and runtime policy.
  2. dashboard/backend/runtime_service.py, scheduler/task lifecycle, and migration 0012.
  3. dashboard/backend/routes/control_api.py and dashboard/backend/event_bus.py.
  4. dashboard/backend/routes/tailscale.py, hermes_proxy.py, and start-dashboard.sh.
  5. Compose/Swarm manifests, Dockerfiles, and publication workflow.
  6. Frontend provider, ticket mention, runtime, and Tailscale surfaces.

UI validation

Automated coverage verifies mention keyboard/click behavior, loading/error/empty states, blocked agents, sanitization/truncation, and comment preservation on failed submission. The production frontend build also completes successfully.

DavidsonGomes and others added 30 commits May 6, 2026 14:01
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
auto_register_if_needed now tries EVOLUTION_OPERATOR_EMAIL first,
calling the licensing server's /v1/register/auto endpoint silently
to activate the instance without the manual setup wizard.

Falls back to the existing admin-user retroactive flow on any failure
(email not yet registered, server unreachable, etc.). Non-fatal.

Requires one prior manual registration so the email is known server-side.
- Add hermes to _ALLOWED_CLI_COMMANDS in ADWs/runner.py
- Add Hermes dispatch branch with hermes_adapter.py and hermes_native.py
- Add hermes_native provider to providers.example.json
- Update heartbeat_runner.py and plugin_scan_runner.py for Hermes CLI
- Update providers.py routes to include hermes_native
- Add HERMES_RUNTIME.md documentation
- Add test_hermes_integration.py
Add full Hermes CLI support across provider-config, claude-bridge,
chat-bridge, and providers.py — matching the OpenCode integration pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Split docker build matrix into per-platform jobs with native runners
(ubuntu-latest for amd64, ubuntu-24.04-arm for arm64) and merge
manifests afterwards. Eliminates 6h+ QEMU-emulated arm64 builds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
OmniRouter now supports both openclaude and hermes as runtime CLIs.
Users pick which CLI to use in the Configure modal — env var fields
swap dynamically based on the selected CLI preset. Status bar shows
hermes installation status alongside claude and openclaude.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add Hermes CLI install (curl installer from nousresearch.com) to both
Dockerfile.swarm and Dockerfile.swarm.dashboard, with ~/.hermes/bin
on PATH. Providers page now shows hermes as installed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Proxy /hermes-ui/* to localhost:9119 via Flask blueprint with
@login_required — port 9119 never exposed externally. Adds sidebar
entry (System > Hermes UI) and iframe page. start-dashboard.sh
launches hermes ui process if hermes is installed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brain Repo backup feature requires git CLI which was missing from the
dashboard container, causing [Errno 2] on /backups page.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The shell installer (hermes-agent.nousresearch.com/install.sh) fails in
Docker builds — it clones the repo, tries to install Python 3.11 via uv,
and runs interactive prompts. Replace with `uv tool install hermes-agent`
which installs from PyPI into ~/.local/bin (already on PATH).

Dashboard image uses [web] extra for `hermes ui` server (fastapi+uvicorn).
Runtime image uses bare install (CLI agent sessions only).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- `hermes ui` doesn't exist — correct command is `hermes dashboard`
- Add --host 127.0.0.1 --no-open flags (no browser in container)
- Wrap in subshell so crash doesn't propagate to wait -n
- wait -n now only watches Flask + terminal-server PIDs
- Hermes crashing no longer kills the entire container

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add missing nav.hermesUI i18n key to en-US, pt-BR, es locales
- OmniRouter provider card now shows both CLI options (openclaude/hermes)
  with active one highlighted in green
- HermesUI page: pre-checks proxy before loading iframe, shows clear
  error state when Hermes dashboard is not running

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hermes --skills flag expects hermes-native skills, not EvoNexus agent
names. Passing --skills oracle causes "Unknown skill(s): oracle" error
and crashes the terminal session.

Fix: read the agent .md file and inject persona via -z (initial prompt)
flag, same approach as openclaude's --system-prompt but using the hermes
equivalent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hermes dashboard sets X-Frame-Options and Content-Security-Policy
headers that block iframe embedding. Strip these in the proxy so the
dashboard renders correctly inside the EvoNexus iframe wrapper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-z is a top-level flag and must come before the chat subcommand.
Also extract only the short description from agent frontmatter
instead of the full .md body (~15KB) which exceeds CLI arg limits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Map EvoNexus agents to Hermes profiles (SOUL.md) on container startup
- Terminal uses `hermes -p <agent> chat` for full agent instructions
- Proxy rewrites HTML asset paths for /hermes-ui/ subpath serving
- Add hermes-data volume to persist ~/.hermes across deploys

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Each Hermes profile is an isolated HERMES_HOME. Without copying
.env and config.yaml from the default profile, agent profiles
have no provider/API key config and prompt for setup again.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…refix

- Proxy now rewrites /api/ and /ws paths in JS responses so Hermes
  dashboard API calls route through /hermes-ui/ prefix correctly
- Use EVONEXUS_HERMES_* env vars in docker-compose to avoid clashing
  with standalone Hermes (mapped to native vars in start-dashboard.sh)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hermes CLI emits OSC 11 (set background color) sequences that
xterm.js cannot interpret, rendering raw text like
^[]11;rgb:0c0c/1111/1d1d in the web terminal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Profiles missing auth.json triggers setup prompt again.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
providers.json created before cli_options existed never gets updated
fields from providers.example.json. Now _read_config() backfills
structural keys (cli_options, cli_env_presets, description, etc.)
without overwriting user values (env_vars, cli_command, API keys).
New providers in example are added entirely.

Fixes OMNIROUTER showing single openclaude badge instead of
openclaude + hermes selector.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Install Composio CLI via official install script and add unzip as
system dependency (required by the installer).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The /hermes tab re-downloaded the SPA bundle on every visit (route
unmounted the iframe) and fetched the root document twice (a 502/503
health-check pre-flight plus the iframe itself).

Replace the route-mounted HermesUI page with HermesFrameHost, mounted
once outside <Routes> in <main> and hidden via display:none when off
/hermes — the iframe stays alive across navigation, so reopening the
tab issues zero new requests. The pre-flight fetch is gone; availability
is detected via the iframe onLoad/onError plus an 8s fallback timeout.
The iframe is created lazily on first open, and stays mounted on error
(overlay) so a slow-but-working Hermes still clears the error on load.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pedrotecinf and others added 27 commits July 25, 2026 17:12
…ts (F9/F10)

Introduces RuntimeService — a provider-aware invocation layer that resolves
the active provider from config/providers.json, fail-closed without fallback
unless explicitly enabled.  Heartbeats now use step7_invoke_runtime instead
of the Claude-first binary-preference chain.

Key changes:
- dashboard/backend/runtime_service.py: new normalized runtime (request/result
  dataclass, provider resolution, profile routing, timeout/kill, fallback)
- heartbeat_runner: step7_invoke_runtime replaces step7_invoke_claude as the
  default path; disabled heartbeats short-circuit before DB/subprocess work
- RuntimeRun model: origin_type, origin_id, agent_slug columns (nullable for
  backward compat); task_id becomes nullable
- runtime_runs.py create_run: accepts origin_type/origin_id for heartbeat and
  routine callers without requiring a ScheduledTask row
- app.py migration: adds origin columns and backfills existing rows
- Tests: 4 contract tests (provider selection, fail-closed, fallback recording,
  timeout kill) + 2 heartbeat-provider tests (runtime delegation, disabled skip)

Co-Authored-By: Claude <noreply@anthropic.com>
…etrics (F9/F10 cont.)

Completes the remaining F9/F10 contract items from the parity plan:

Decision contract:
- parse_decision() extracts {"action":"work"|"skip"} from runtime output
- Skip decision short-circuits execution after step7 (no wasted turns)
- decision_action + decision_json persisted in heartbeat_runs

Atomic ticket checkout/release:
- step5_atomic_checkout uses UPDATE WHERE locked_at IS NULL (row-count=1 wins)
- step9_release_checkout is owner-only (locked_by must match run_id)
- Concurrent checkout test proves exactly 1 of 10 parallel attempts wins

HeartbeatRun schema extended (nullable, backward-compat migration):
- decision_action, decision_json, provider, resolved_profile
- stdout_tail, stderr_tail, runtime_run_id
- ON CONFLICT upsert covers all new + tokens/cost columns

Tests (19 new, 33 total in heartbeat suites):
- TestDecisionParsing: work/skip/empty/invalid/last-line-wins
- TestAtomicCheckout: acquire/conflict/owner-release/concurrent-race
- TestTriggerE2E: manual/interval/mention/goal-context/skip-persists/disabled
- TestTimeoutAndLockCleanup: timeout releases lock, failed is retryable
- TestProviderPersistence: provider+profile+tokens stored

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…duled tasks

Add scoped, idempotent and audited operations:
- Goals cascade: list missions/projects/goals/goal-tasks, update goal and goal-task
- Heartbeats: list/get/enable/disable/manual-run/run-status
- Scheduled tasks: list/get/cancel/run-now with transition guards

All mutations require Idempotency-Key, emit EventBus audit events, and
respect least-privilege scopes. Heartbeat secrets are never exposed.
Scheduled task cancel/retry enforce valid status transitions (409 on invalid).

Co-Authored-By: Claude <noreply@anthropic.com>
Add scoped endpoints for routines:
- GET /api/control/v1/routines — list with metrics summary
- GET /api/control/v1/routines/:id/logs — date-filtered JSONL log
- POST /api/control/v1/routines/:id/run — idempotent manual execution

Execution is sandboxed to ADWs/routines/ scripts only (path traversal
blocked). All mutations require Idempotency-Key and emit audit events.
Arbitrary shell not exposed — only registered routine scripts.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Normalize historical metrics, isolate telemetry failures from completed runs,
and render never-run routines neutrally.

Co-Authored-By: Claude <noreply@anthropic.com>
Prevent duplicate routine log reads and retain manual versus scheduled origins
so the Activity view reports stable, accurate executions.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Publish and deploy dashboard and runtime from the same revision while checking Hermes and Claude capabilities before rollout.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Enable accessible agent suggestions in ticket comments with cursor-safe insertion and test coverage.

Co-Authored-By: Claude <noreply@anthropic.com>
Keep mention suggestions within the composer flow and cover interactive selection behavior.

Co-Authored-By: Claude <noreply@anthropic.com>
Await comment submission before clearing drafts and keep active mention options visible during keyboard navigation.

Co-Authored-By: Claude <noreply@anthropic.com>
Derive safe mention indices and cover Enter selection without effect-driven state resets.

Co-Authored-By: Claude <noreply@anthropic.com>
Move mention layout calculations out of the component to satisfy hot-reload linting.

Co-Authored-By: Claude <noreply@anthropic.com>
…gration

# Conflicts:
#	ADWs/runner.py
#	Dockerfile.swarm.dashboard
#	dashboard/backend/app.py
#	dashboard/backend/heartbeat_dispatcher.py
#	dashboard/backend/heartbeat_runner.py
#	dashboard/backend/heartbeat_schema.py
#	dashboard/backend/routes/providers.py
#	dashboard/frontend/src/App.tsx
#	dashboard/frontend/vite.config.ts
#	scheduler.py
#	uv.lock

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @pedrotecinf, your pull request is larger than the review limit of 150000 diff characters

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants