Governance Stack Reference
Part of the Hyraxknot Division architecture review, Card 1.
Documents the full governance runtime: lease guard, blocked-card alerting, profile contracts, operation gates, tool registry, handoff guard, evidence ingestion, and scheduled advisory jobs.Last updated: 2026-07-09
Component of: Governance Layer (Hyrax OS)
1. Execution Lease Guard
Source: /root/.hermes/governance/execution_lease_guard.py
Store: /root/.hermes/governance/execution_leases.jsonl
A pure read-only lease detection system that prevents duplicate agent lanes from working the same Linear/Kanban task simultaneously (HYX-222). All detector functions are pure (no I/O) except the store loaders. Mutation operations are not exposed.
Lease States
| State | Description |
|---|---|
proposed |
Lease has been proposed but not yet activated |
active |
Lease is currently held and within its TTL |
heartbeat_stale |
Lease is active but last heartbeat exceeds the stale threshold |
duplicate_detected |
Another active lease was found for the same task |
released |
Lease was explicitly released |
expired |
Lease TTL elapsed without renewal |
revoked |
Lease was revoked by governance |
archived |
Historical record, no longer actionable |
Valid Claim Sources
bridge— claimed via the Linear↔Kanban bridgekanban_dispatch— claimed via kanban dispatcherreader_proposed— proposed by a reader/sistermanual— manually claimeddry_run— report-only proposal (never written to store)
Valid Lease Types
| Type | Description |
|---|---|
normal_dispatch |
Standard task dispatch |
active_session_takeover |
Takeover of a running session by another sister |
tracking_only |
Informational lease for tracking without exclusive execution |
review_only |
Lease for review purposes only, not execution |
Constants
- Heartbeat stale threshold: 20 minutes
- Default lease duration: 120 minutes (TTL)
Detection Functions
| Function | Purpose |
|---|---|
summarize_leases() |
Produce a summary dict of lease state by status, profile, and source |
detect_duplicate_leases() |
Find active leases sharing the same linear_issue_id or kanban_task_id |
detect_stale_heartbeats() |
Find active leases without a heartbeat within the stale threshold |
detect_expired_leases() |
Find active/proposed leases past their expiration |
detect_missing_leases() |
Return kanban task IDs that are running but have no active lease |
detect_kanban_without_linear() |
Find leases with kanban_task_id but no linear_issue_id |
detect_stale_active_leases() |
Find active leases whose Linear issue or Kanban task is already Done |
propose_lease_record_dry_run() |
Create a dry-run lease proposal (does NOT write to store) |
filter_leases_by_type() |
Return only leases matching a specific lease_type |
count_takeover_leases() |
Count leases by takeover-related types vs normal dispatch |
detect_normal_dispatch_takeover_conflicts() |
Detect normal_dispatch leases colliding with an active takeover |
Lease Record Schema (Dry-Run Proposal)
{
"lease_id": str, # UUID v4
"linear_issue_id": str,
"linear_issue_identifier": str, # e.g. "HYX-123"
"kanban_task_id": str | None,
"claimed_by_profile": str, # "tai", "rei", "nei", "mai"
"claimed_by_gateway": str,
"claimed_by_session": str,
"claimed_by_app_actor": str, # default: "Hyraxknot Agent"
"claim_source": str,
"lease_status": str, # one of VALID_LEASE_STATUSES
"lease_started_at": str, # ISO 8601
"lease_expires_at": str, # ISO 8601
"last_heartbeat_at": str, # ISO 8601
"heartbeat_source": str,
"policy_mode": str, # "report_only" for dry-run
"duplicate_claim_detected": bool,
"duplicate_of_lease_id": str | None,
"evidence_links": list[str],
"notes": str,
}
JSONL Event Store
The execution_leases.jsonl store records lease lifecycle events, not the lease records themselves. Each line is a JSON event:
| Event | Description |
|---|---|
lease_issued |
Lease granted with lease_id, profile, proposal_type, allowed_action, expires_at |
lease_validated |
Lease validated (can fire multiple times per lease) |
lease_used |
Lease consumed — execution_id recorded, status set to “consumed” |
lease_denied |
Lease denied — reason populated (e.g. “proposal type ‘dream’ is blocked”) |
Denied lease events have an empty lease_id field.
2. Pre-exec Blocked-Card Alerting
Source: /root/.hermes/governance/preexec_blocked_alerts.py
Store: /root/.hermes/governance/preexec_blocked_alerts.jsonl
Addresses the pre-exec blocked-card no-alert bug: when the Kanban pre-exec gate blocks a ready → running transition because the required ## Plan or ## Pre-Execution Plan comment is missing. All detector functions are pure (no I/O) except the store loaders.
Alert States
| State | Description |
|---|---|
detected |
Alert has been detected and not yet acted upon |
acknowledged |
Alert is known to an operator |
resolved |
The blocking condition has been resolved |
stale |
Alert has been open beyond the stale threshold |
archived |
Historical record |
Required Plan Markers
The pre-exec gate checks for these Markdown headings in a Linear issue’s comments:
## Plan## Pre-Execution Plan
Thresholds
| Threshold | Value | Purpose |
|---|---|---|
STALE_THRESHOLD_MINUTES |
15 min | Alert becomes “stale” after this long unresolved |
REPEAT_SUPPRESSION_MINUTES |
30 min | Suppress duplicate alert creation within this window |
ESCALATION_THRESHOLD_MINUTES |
60 min | Alert qualifies for escalation after this long unresolved |
Detection Functions
| Function | Purpose |
|---|---|
summarize_alerts() |
Produce a summary dict of alert state by state and profile |
detect_blocked_from_kanban() |
Detect blocked Kanban cards that should generate alerts, with repeat suppression |
detect_missing_plan_comments() |
Detect cards whose linked Linear issue lacks required plan markers |
detect_stale_blocked_cards() |
Detect open alerts beyond the stale threshold |
detect_escalation_needed() |
Detect alerts beyond the escalation threshold |
summarize_by_profile() |
Group open alerts by assigned profile |
propose_alert_record_dry_run() |
Create a dry-run alert proposal (does NOT write to store) |
render_alert_message_dry_run() |
Render a dry-run alert as a human-readable message |
Alert Record Schema
{
"alert_id": str, # UUID v4
"timestamp_detected": str, # ISO 8601
"timestamp_updated": str, # ISO 8601
"linear_issue_id": str,
"linear_issue_identifier": str, # e.g. "HYX-123"
"kanban_task_id": str | None,
"blocked_reason": str,
"required_marker_missing": str, # "## Plan" or "## Pre-Execution Plan"
"card_status": str,
"linear_status": str | None,
"assigned_profile": str | None,
"routing_label": str | None,
"lease_id": str | None,
"alert_state": str, # one of VALID_ALERT_STATES
"notification_status": str, # "report_only" for dry-run
"evidence_links": list[str],
"recommended_action": str,
"notes": str,
}
3. Profile Contracts
Source: /root/.hermes/governance/profile-contracts.yaml
Status: Draft, not applied to live profiles.
Defines the intended contractual boundaries for each profile. These serve as review targets for future validation scripts.
hyrax-os (Control Plane)
| Property | Value |
|---|---|
| Role | control-plane |
| Default posture | read-only |
| Allowed platforms | cli, discord |
| Prohibited platforms | (none) |
Allowed CLI toolsets: fleet_audit
Allowed work categories:
- Audit profile configs, tool visibility, governance contracts
- Detect drift, check gateway health, check memory context budgets
- Draft patch proposals, coordinate Codex manual changes
- Prepare script-only cron watchdogs
- Governance audit (fleet, profile, status, gateway classes, operation gates)
- Governance notices (read-only), proactive warnings
Discord notice scope: governance notices only, no chat response, read-only command responder, high-risk proactive notices
Discord runtime status: disabled — Discord gateway for hyrax-os remains disabled until a notice-only wrapper exists
Prohibited work:
- Chat persona routing, affinity state mutation, media/selfie generation, Spotify control
- Governance mutation without contract, fleet ops without Josh approval, fleet ops self-escalation
- Mnemosyne DB mutation, skill tree mutation, sister tool access
- Execution action, lease issuance, handoff note write, proposal approval
Resolver exception (Discord): The kanban toolset may appear on Discord even when platform_toolsets.discord is explicitly empty, due to Hermes recovering non-configurable platform toolsets from the Discord composite. This is accepted only when the Discord gateway is truly disabled and no other Discord toolsets are visible.
Other Profiles (default, rei, nei, tai, mai)
| Property | Value |
|---|---|
| Status | observed-only |
| Apply changes | false |
All sister profiles are observed-only. No contracts are enforced on them yet. The contracts document serves as the design target.
Identity Source-of-Truth Rule
Per-profile SOUL.md = identity source of truth
config.yaml system_prompt = must NOT contain sister identity/personality content
Shared skills = must NOT contain personality-defining content
Shared project context = must be operational only
Identity content = must be profile-scoped
4. Operation Gates
Source: /root/.hermes/governance/operation-gates.yaml
Status: Draft design, not enforced. Mode: read_only_design.
Operation-level gate design for mixed-risk sister-being and work toolsets. Defines how each tool/operation should be gated across profiles.
Scope
- Profiles considered: rei, nei
- Entrypoints considered: cli, discord
- Protected from change: live profile configs, gateway services, Hermes core, plugins, skills, SOUL.md, USER.md, MEMORY.md, Mnemosyne, Discord tokens, tai, mai, default, hyrax-os
Operation Classes (13 classes)
| Class | Description |
|---|---|
baseline_allowed |
Allowed as part of Base Sister Runtime when visible on an approved entrypoint |
gated_by_mode |
Allowed only in a contracted mode or posture; mode changes cannot expand permissions |
gated_by_user_request |
Allowed only after an explicit user request |
gated_by_role_extension |
Allowed only for a named profile extension and work context |
gated_by_session_protocol |
Allowed only under session memory/work protocol |
approval_required |
Requires explicit approval plus audit/logging, and rollback where practical |
hyrax_os_only |
Reserved to the Hyrax OS control-plane profile by default |
forbidden_until_plugin_refactor |
Do not expose until toolsets or handlers can enforce the operation split |
forbidden_until_path_cleanup |
Blocked until hardcoded or cross-profile paths are fixed or documented |
needs_human_decision |
Policy choice is not technical; Josh or governance owner must decide |
optional_gated_embodiment_extension |
Optional photo/embodiment capability; not required for Base Sister Runtime |
Global Gate Requirements
- Discord: high-risk default for all operations
- No permission expansion by mode changes
- Explicit target required for cross-sister operations
- Audit log required for mutation, generation, admin operations
- Approval required for force override
- Unknown plugins: drift status unless recorded in
known_plugin_toolsetsandplatform_toolsets - Warning: Hermes currently exposes whole toolsets; several gates require tool-level enforcement
Per-Toolset Gate Analysis
Each toolset is analyzed operation-by-operation with:
primary_class— the main gate classadditional_classes— secondary gate classesmutability— read_only, bounded_mutating, durable_write, etc.state_scope— profile_private, shared_sister, shared_surface, etc.required_gates— list of gate conditionscurrent_visibilityfor rei/nei profilescurrent_gate_status— acceptable, violation, insufficient enforcement
Toolset analysis summary:
| Toolset | Operations Analyzed | Gate Status |
|---|---|---|
| sister_thought | 5 (microthought, decision, distill, dream, gacha_pull) | 2 violations (dream, gacha_pull) |
| affinity | 8 (show, react, group_react, photo_status, milestones, adjust, mode_set, photo_toggle + admin_write) | Re-add unsafe as whole toolset |
| sister_photo | 6 (list_poses, list_outfits, list_lighting, photo_status, photo_history, generate_selfie + force_override) | 2 violations; not baseline-safe |
| kanban | 8 (show, list, comment, create, link, block, unblock, complete, heartbeat) | Read ops baseline; mutation ops gated |
| memory | 6 (read_private, read_shared, add, replace, remove, shared_promotion, bulk_edit) | Write ops gated by session protocol |
| fleet_ops | 3 (check_updates, health_check, apply_updates) | hyrax_os_only; disabled by default |
5. Tool Registry
Source: /root/.hermes/governance/tool-registry.yaml
Status: Draft, governance-planning-only.
Defines the effective toolset composition for the hyrax-os control-plane profile.
hyrax-os CLI Toolset
| Category | Setting |
|---|---|
| Default posture | read-only |
| Allowed initial toolsets | clarify, code_execution, cronjob, file, fleet_audit, kanban, session_search, terminal, web |
| Disabled/prohibited | discord, discord_admin, fleet_ops, governance, moa, persona, sister_photo, sister_thought, spotify |
Fleet Control
| Property | Value |
|---|---|
| Audit | Enabled via fleet_audit toolset (legacy alias: governance_audit) |
| Admin | Disabled by default. Toolsets: fleet_ops, governance. Approval authority: Josh. |
| Enablement scope | Explicit Josh approval per named task/session/change window; hyrax-os cannot self-approve escalation |
Policy Enforcement
- Currently disabled (
enabled: false) - Contracts and approval boundaries are not implemented yet
Media & Memory Tools
| Category | Status | Rationale |
|---|---|---|
| Media tools | Disabled | Control-plane profile has no media role |
| Memory tools | Disabled | Audit memory budgets from config/state only; do not mutate memory stores |
Validation Targets
The registry identifies these aspects for future validation:
- Effective CLI toolsets
- Effective Discord toolsets (empty or exception-only)
- Disabled plugin toolsets
- Gateway platforms disabled
- Memory context limits
- Profile contract drift
6. Sister Label Handoff Guard
Source: /root/.hermes/governance/sister_label_handoff_guard.py
Expected store: /root/.hermes/governance/sister_label_handoff_handoffs.jsonl
Closes the loop between Linear sister routing labels, execution leases, and handoff markers. All detector functions are pure; no mutation or notification.
Sister Labels
sister:rei, sister:nei, sister:tai, sister:mari
Handoff States
detected, acknowledged, resolved, stale, archived
Valid Detector Rules (10 rules)
| Rule ID | Description |
|---|---|
multiple_sister_labels |
Task has more than one sister routing label |
missing_sister_label |
Active lease exists but no sister label on the task |
lease_label_mismatch |
Lease actor does not match the task’s sister label |
handoff_without_label_update |
Handoff occurred but label wasn’t updated |
label_update_without_handoff |
Label changed without a handoff comment |
blocked_wrong_sister_lane |
Blocked task is in the wrong sister’s lane |
kanban_linear_owner_mismatch |
Kanban routing field doesn’t match Linear label |
stale_handoff |
Open handoff record beyond the stale threshold (60 min) |
orphaned_handoff |
Open handoff record for a closed/resolved task |
hyrax_os_mislabel |
Task mislabeled as hyrax-os instead of a sister |
Detection Functions
| Function | Rule | Purpose |
|---|---|---|
detect_multiple_sister_labels() |
1 | Tasks with more than one sister label |
detect_missing_sister_label() |
2 | Active lease but no sister label |
detect_lease_label_mismatch() |
3 | Lease actor vs label mismatch |
detect_stale_handoffs() |
8 | Open handoffs beyond stale threshold |
detect_orphaned_handoffs() |
9 | Open handoffs for closed tasks |
Handoff Record Schema (Dry-Run Proposal)
{
"record_id": str,
"timestamp_detected": str,
"timestamp_updated": str,
"linear_issue_id": str,
"linear_issue_identifier": str,
"kanban_task_id": str | None,
"detector_rule": str,
"sister_labels_found": list[str],
"expected_sister": str | None,
"actual_sister": str | None,
"lease_id": str | None,
"handoff_state": str, # "detected"
"notification_status": str, # "report_only"
"evidence_links": list[str],
"recommended_action": str,
"notes": str,
}
Thresholds
| Threshold | Value |
|---|---|
STALE_THRESHOLD_MINUTES |
60 |
REPEAT_SUPPRESSION_MINUTES |
30 |
7. Evidence Ingestion Pipeline
Source: /root/.hermes/governance/evidence_ingest.py
Read-only evidence ingestion from Kanban and Linear. All operations use read-only SQLite (mode=ro) and read-only GraphQL queries. No writes to kanban.db, Linear, or any production store.
Data Sources
Kanban Database (/root/.hermes/kanban.db)
| Table | Ingestion |
|---|---|
tasks |
All tasks, ordered by created_at DESC. Skills and comments JSON fields are parsed. Body is truncated to 200 chars for snapshots. |
task_comments |
All comments, linked to tasks via task_id |
task_runs |
Running/blocked runs enriched into tasks as _run metadata (profile, status, claim_lock, heartbeat) |
Linear API (https://api.linear.app/graphql)
Fetches the HYX team (team ID: 7bb182c4-1328-42ce-ab7d-c29021adf1a5) issues with:
- Title, state, labels, assignee, creator
- Timestamps (created, updated)
- Up to 10 comments per issue
- Token loaded from
/root/.hermes/secrets/linear-app.env
Pipeline Steps (run_ingestion())
- Kanban schema — introspect table definitions
- Kanban tasks — read all tasks (redacted)
- Kanban running tasks — filter running/blocked tasks with run info
- Linear issues — fetch recent HYX issues via GraphQL
- Build detector inputs — construct
detector_tasks,detector_leases, andlinear_comment_mapfor downstream detectors - Snapshots — write redacted JSON snapshots to disk
Detector Inputs
The pipeline produces three data structures for downstream detectors:
detector_tasks— kanban tasks with extracted Linear references (HYX-### patterns from title, body, comments)detector_leases— synthetic lease records from running kanban taskslinear_comment_map—{issue_id: [comment_body, ...]}for plan marker detection
Snapshot System
| Detail | Value |
|---|---|
| Snapshot directory | /root/.hermes/governance/evidence_snapshots/ |
| Naming | kanban_tasks_{timestamp}.json and linear_issues_{timestamp}.json |
| Retention | Not auto-pruned (handled separately) |
| Redaction | Strings truncated to 80 chars (general) or 200 chars (body fields). Comments limited to 300 chars. |
| Connect mode | mode=ro (read-only URI flag) for SQLite |
8. Scheduled Advisory Jobs
Source: /root/.hermes/governance/scheduled_advisory_jobs.py
Read-only cron runners for governance. No mutations to kanban.db, Linear, or production stores. No notifications. No comments. No label changes.
Job Types
| Command | Schedule Intent | What It Does |
|---|---|---|
ingest |
Hourly | Runs evidence_ingest.run_ingestion(), writes redacted snapshots. Output: scheduled_reports/evidence_snapshot/ |
guardrails |
Hourly | Runs G3D-4A/4B/4C detectors against latest evidence. Output: scheduled_reports/guardrails/ |
audit |
Daily | Full advisory work-state audit in markdown. Output: scheduled_reports/audits/ |
status |
On-demand | Show lockfile status and latest report timestamps |
cleanup |
On-demand | Force-clear stale lockfiles |
Lock System
Each job uses a PID-based lockfile to prevent concurrent execution:
| Job | Lock Path | Stale Threshold | Max Timeout |
|---|---|---|---|
ingest |
scheduled_reports/.locks/ingest.lock |
65 min | 5 min |
guardrails |
scheduled_reports/.locks/guardrails.lock |
65 min | 5 min |
audit |
scheduled_reports/.locks/audit.lock |
25 hr | 10 min |
Locks contain {pid, started_at}. Stale locks are auto-cleared. Locks are released in finally blocks.
Guardrail Summary Output
The guardrails command runs all three G3D-4 detector families:
G3D-4A (Lease Guard):
- Active lease count, duplicates, stale heartbeats, expired, kanban-without-linear
G3D-4B (Pre-exec Alerting):
- Blocked kanban tasks, open Linear issues, issues without plan, ready-state issues without plan
G3D-4C (Handoff Detection):
- Multiple sister labels, missing sister labels, lease-label mismatches
All output is report_only. No enforcement.
Output Directory Structure
scheduled_reports/
├── .locks/
│ ├── ingest.lock
│ ├── guardrails.lock
│ └── audit.lock
├── evidence_snapshot/
│ ├── latest_evidence.json
│ ├── evidence_20260709-120000.json
│ └── ...
├── guardrails/
│ ├── latest_guardrails.json
│ ├── guardrails_20260709-120000.json
│ └── ...
├── audits/
│ ├── latest_audit.md
│ ├── audit_20260709-120000.md
│ └── ...
└── job_log.txt
Architecture Diagram: Data Flow
Kanban DB ──┐
├──> evidence_ingest.py ──> evidence_snapshots/
Linear API ─┘ │
├──> detector_tasks ──> sister_label_handoff_guard.py
├──> detector_leases ──> execution_lease_guard.py
└──> linear_comment_map ──> preexec_blocked_alerts.py
│
scheduled_advisory_jobs.py
├── ingest (scheduled reports)
├── guardrails (G3D-4A + 4B + 4C summary)
└── audit (work-state audit markdown)
Enforcement Status (as of 2026-07-09)
| Component | Design | Enforcement | Store Populated |
|---|---|---|---|
| Execution Lease Guard | Complete | None (report-only) | execution_leases.jsonl active |
| Pre-exec Blocked Alerts | Complete | None (report-only) | preexec_blocked_alerts.jsonl exists, empty |
| Profile Contracts | Draft | None | Not enforced |
| Operation Gates | Draft | None | Not enforced |
| Tool Registry | Draft | Partial (via profile config) | N/A |
| Handoff Guard | Complete | None (report-only) | Expected store empty |
| Evidence Ingestion | Complete | Hourly cron | Snapshots active |
| Scheduled Advisory Jobs | Complete | Cron ready | Reports directory active |
| Plane Completion Forwarder | Complete | Staged (path unit, manifest=gated) | plane_completion_forwarder.db ready |
| Plane Kanban Enrollment | Complete | Per-binding mode, manifest kill switch | HYX-4 (staged_disabled), HYX-5 (active, ready) |
All detectors run in report_only / dry_run mode. No mutations, no notifications, no enforcement are active.