OmniNode-ai

decision-store skill

Record, query, and conflict-check architectural and design decisions across the OmniNode platform

OmniNode-ai 4 3 Actualitzat fa 4 mesos

Recursos

3
GitHub

Instal·la

npx skillscat add omninode-ai/omniclaude/plugins-onex-skills-decision-store

Instal·la des del registre de SkillsCat.

SKILL.md

decision-store skill

Skill ID: onex:decision_store
Version: 1.1.0
Owner: omniclaude

Purpose

Record, query, and conflict-check architectural and design decisions across the OmniNode
platform. Decisions are persisted by NodeDecisionStoreEffect and queried
via NodeDecisionStoreQueryCompute.

Conflicts are detected in two stages:

  1. Structural check (sync, pure function) — always runs
  2. Semantic check (async, LLM via DeepSeek-R1) — only if structural confidence >= 0.6

HIGH-severity conflicts trigger the Slack conflict resolution gate, which blocks
the pipeline until an operator resolves each conflict via Slack reply.


Sub-operations

record

Write a new decision entry, run structural conflict check against all existing entries
in the same domain, and route to Slack if any conflict is HIGH severity.

/decision-store record
  --type TECH_STACK_CHOICE|DESIGN_PATTERN|API_CONTRACT|SCOPE_BOUNDARY|REQUIREMENT_CHOICE
  --domain <domain>
  --layer architecture|design|planning
  --services <svc1,svc2,...>   # omit = platform-wide
  --summary "<one-line summary>"
  --rationale "<full rationale>"
  [--dry-run]

Behavior:

  1. Validate required fields.
  2. Call NodeDecisionStoreEffect to persist the entry.
  3. Run structural_confidence() against all existing entries in the same domain.
  4. For each conflict with confidence > 0.0, call compute_severity().
  5. If any severity is HIGH: post Slack conflict notification (via _lib/slack-gate helpers)
    and wait for operator resolution via Slack command grammar.
  6. If structural confidence >= 0.6: fire async semantic_check_async() — non-blocking.
  7. Emit decision-conflict-status-changed.v1 event for each new conflict (status = OPEN).
  8. Emit decision-conflict-status-changed.v1 on each subsequent status transition
    (OPEN -> RESOLVED or OPEN -> DISMISSED) after Slack resolution.

query

Retrieve existing decisions with optional filters and cursor-based pagination.

/decision-store query
  [--domain <domain>]
  [--layer architecture|design|planning]
  [--type <decision_type>]
  [--service <service_name>]
  [--status open|resolved|dismissed]
  [--cursor <cursor_token>]
  [--limit 20]

Behavior:

  1. Call NodeDecisionStoreQueryCompute with filter params.
  2. Display paginated results with conflict indicators.
  3. If --cursor provided, fetch next page.

check-conflicts

Dry-run structural conflict check for a hypothetical new decision, without writing anything.

/decision-store check-conflicts
  --type <decision_type>
  --domain <domain>
  --layer architecture|design|planning
  --services <svc1,svc2,...>
  --summary "<one-line summary>"

Behavior:

  1. Run structural_confidence() and compute_severity() against all existing entries.
  2. Display conflict report: confidence scores, severities, affected entries.
  3. Does NOT write to the store. Does NOT trigger semantic check. Does NOT emit events.

Severity Matrix

Decision Type Layer Base Severity
TECH_STACK_CHOICE architecture HIGH
TECH_STACK_CHOICE design HIGH
TECH_STACK_CHOICE planning MEDIUM
DESIGN_PATTERN architecture HIGH
DESIGN_PATTERN design MEDIUM
DESIGN_PATTERN planning LOW
API_CONTRACT architecture HIGH
API_CONTRACT design MEDIUM
API_CONTRACT planning LOW
SCOPE_BOUNDARY architecture MEDIUM
SCOPE_BOUNDARY design MEDIUM
SCOPE_BOUNDARY planning LOW
REQUIREMENT_CHOICE architecture MEDIUM
REQUIREMENT_CHOICE design LOW
REQUIREMENT_CHOICE planning LOW

Modifiers (applied in order after base):

  1. Platform-wide scope (either entry has scope_services = []): floor at MEDIUM
  2. Architecture layer (either entry has scope_layer = "architecture"): floor at HIGH
  3. Semantic shift (from DeepSeek-R1): +/-1 step, clamped to [LOW, HIGH]

Hard rules:

  • structural_confidence == 0.0 (cross-domain): no conflict, semantic check MUST NOT run
  • structural_confidence == 0.3 (disjoint services): semantic check CANNOT escalate to HIGH
  • Semantic check only runs if structural_confidence >= 0.6

Structural Confidence Values

Value Meaning
0.0 Cross-domain — no possible conflict (semantic blocked)
0.3 Disjoint service sets — low probability (semantic capped at MEDIUM)
0.4 Same domain, different layer
0.7 Overlapping service sets
0.8 One platform-wide vs specific
0.9 Both platform-wide
1.0 Identical service scope

Conflict Resolution (Slack Notification)

When a HIGH severity conflict is detected after record, the pipeline posts a conflict
notification to Slack (via _lib/slack-gate credential helpers) and blocks until an
operator resolves the conflict via the command grammar below.

Slack Command Grammar

Operators resolve conflicts by replying in the Slack gate thread:

Format:  <action> <conflict_id> [note]

Actions:
  proceed <id> [note]  — mark RESOLVED, record approver + note, continue pipeline
  hold    <id>         — pipeline stays paused (gate remains open)
  dismiss <id>         — mark DISMISSED permanently (no further checks for this pair)

proceed rules:

  • proceed <id> with no note -> resolution_note = "proceed (no note provided)"
  • proceed <id> <note> -> resolution_note = <note text>

hold rules:

  • Pipeline stays paused; no state change; no event emitted

dismiss rules:

  • Conflict marked DISMISSED permanently
  • This decision pair will NEVER trigger a Slack gate again (even if re-recorded)
  • resolution_note is None for dismissals

Multiple conflicts:

  • Each conflict must be resolved separately in its own reply
  • All OPEN conflicts must be resolved before the pipeline resumes
  • RESOLVED and DISMISSED conflicts do not block the pipeline

Unrecognized commands:

  • Re-prompt operator with usage text; no state changes

Conflict Workflow (Full Sequence)

1. record sub-operation detects HIGH conflict
2. Post conflict notification to Slack (via _lib/slack-gate helpers)
3. Pipeline blocks (polling for Slack replies)
4. Operator replies with: proceed | hold | dismiss
5. For proceed:
   - Update conflict: status=RESOLVED, resolved_by, resolved_at, resolution_note
   - Emit decision-conflict-status-changed.v1 (OPEN -> RESOLVED)
   - If all conflicts resolved: resume pipeline
6. For hold:
   - No state change, no event
   - Continue polling
7. For dismiss:
   - Update conflict: status=DISMISSED
   - Emit decision-conflict-status-changed.v1 (OPEN -> DISMISSED)
   - Mark pair as permanently suppressed
   - If all conflicts resolved/dismissed: resume pipeline
8. Pipeline resumes after all blocking conflicts are RESOLVED or DISMISSED

Status Change Events

decision-conflict-status-changed.v1 is emitted on EVERY conflict status transition.

Topic constant: TopicBase.DECISION_CONFLICT_STATUS_CHANGED
Wire topic: onex.evt.omniclaude.decision-conflict-status-changed.v1
Source: omniclaude/src/omniclaude/hooks/topics.py

Transitions that emit:

  • None -> OPEN (conflict first detected during record)
  • OPEN -> RESOLVED (via proceed command)
  • OPEN -> DISMISSED (via dismiss command)

Payload:

{
  "conflict_id": "uuid",
  "old_status": "OPEN | null",
  "new_status": "OPEN | RESOLVED | DISMISSED",
  "resolved_by": "slack_user_id | null",
  "resolved_at": "ISO8601 | null",
  "resolution_note": "string | null"
}

Note: old_status is null for the initial OPEN event (no previous status).
resolution_note is null for dismiss actions.


Files

File Purpose
SKILL.md This document — usage, severity matrix, conflict resolution workflow
prompt.md Operational prompt followed by the agent
detect_conflicts.py structural_confidence() and compute_severity() pure functions
semantic_check.py Async LLM check via DeepSeek-R1 (only if structural >= 0.6)
examples/record_decision.md Example: recording a tech stack choice
examples/query_decisions.md Example: querying decisions with filters
examples/conflict_resolution.md Example: Slack conflict resolution walkthrough

Dependencies

Component Role
ModelDecisionStoreEntry, ModelDecisionConflict Data models
NodeDecisionStoreEffect Write node
NodeDecisionStoreQueryCompute Query node
TopicBase.DECISION_CONFLICT_STATUS_CHANGED in topics.py Event emission
Slack command grammar + status change events Conflict gate

Event Schema

decision-conflict-status-changed.v1 (emitted on every status transition):

{
  "conflict_id": "uuid",
  "old_status": "OPEN | RESOLVED | DISMISSED | null",
  "new_status": "OPEN | RESOLVED | DISMISSED",
  "resolved_by": "string | null",
  "resolved_at": "ISO8601 | null",
  "resolution_note": "string | null"
}

Note: The event schema was simplified in an earlier pass. Fields decision_a_id,
decision_b_id, and approver were consolidated: approver is now resolved_by,
and decision pair IDs are carried by the conflict record (not the status event).
The status event is intentionally minimal — consumers query the conflict record for
full decision pair context.