# Rootnode Mode Router

> Selects the active profile from triggering context. Evaluates a rule set against inputs (current time, calendar state, geofence, manual command, custom signals) and returns the profile name that consuming Skills (handoff-trigger-check, critic-gate, future Skills) should apply. Configuration-driven; rule set lives in a router config file conforming to the included schema. Deterministic precedence — manual override beats calendar beats time-window beats default. Required default profile guarantees a routing decision always exists. Use when an orchestrator needs to pick a profile automatically rather than having the caller specify one, when wiring calendar/time/Telegram triggers to gate behavior, or when a Skill's behavior should change with availability mode. Trigger on: "what profile should I use," "route to a profile," "which mode am I in," "auto-select profile," "active mode," "current profile." Do NOT use to evaluate work readiness or review proposed changes. Always returns a profile name.

- Skill: `majiayu000/rootnode-mode-router` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/rootnode-mode-router`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/rootnode-mode-router/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- License: Apache-2.0
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/rootnode-mode-router

---


# Mode Router

Selects the active profile from triggering context. The piece that ties everything together: gates apply profiles, gates need to know which profile is active, the router decides.

The premise: **profile selection is a routing problem with deterministic precedence, not a reasoning problem.** Operational modes (e.g., active-supervision, mobile-only HITL, unattended) have well-defined boundaries — calendar entries, time windows, geofences, manual override commands — that a rule engine can evaluate without an LLM. The router applies those rules and returns a profile name. No interpretation, no judgment calls.

This Skill is the rootnode runtime layer's analog to a "context dispatcher" — the orchestrator's first call when work arrives, before the gates fire. Without it, every gate invocation requires the caller to specify which profile applies. With it, the orchestrator picks a profile based on what the world looks like at that moment.

## Important

**Configuration-driven, not hardcoded.** The Skill itself contains no user-specific rules. The rule set lives in a router config file (validated against the included schema). An example router config (`configs/example-router.json`) ships as a working reference; users author their own.

**Deterministic precedence.** Manual override always wins over time-based rules. Time-based rules apply in a documented priority order. Default profile is the fallback when no rule matches. The Skill never picks "the most plausible profile" — it follows the rules in order.

**Required default profile.** Every router config must specify a default. This guarantees the router always returns a valid profile name. Halt-with-error on no-match is forbidden — autonomous orchestrators cannot recover from a routing failure.

**The router doesn't validate that the returned profile exists.** Profile existence is the consuming Skill's concern (the gate checks its own profile directory). The router returns a name; the consumer either has it or surfaces "profile not found." Single Responsibility Principle.

**Time-zone awareness is the caller's responsibility.** The router accepts a current-time input as ISO-8601 with timezone offset. The caller (UCIS, OpenClaw, etc.) is responsible for passing the right timezone. The router doesn't infer location.

---

## Trigger Sources — Overview

The router supports five trigger source types. A router config can use any combination. Per-type configuration semantics, supported predicates, evaluation behavior, edge cases, and worked examples are in `references/trigger-types-detailed.md`.

### 1. Manual Override

A user-provided profile name that wins over all other rules. Source: Telegram command, Apple Shortcut, CLI flag. Has a TTL; after expiry, rules-based routing resumes. Always evaluated first.

### 2. Calendar State

The router evaluates the user's calendar at decision time. Supports three predicates: `event_in_progress(category)`, `event_starts_within(category, minutes)`, `no_events_for(minutes)`. The router doesn't fetch calendar data — the caller provides `active_calendar_events` in context.

### 3. Time Window

Day-of-week + time-of-day windows. Configurable time zone per rule. Supports wrapping windows (start_time > end_time, e.g., 22:00-06:00). Inclusive of start, exclusive of end.

### 4. Geofence

Caller-supplied geofence state as a label (e.g., `at_office`, `at_home`, `in_transit`). The router doesn't compute geofences — it accepts a label. Match is exact string equality.

### 5. Custom Signal

User-defined named signals passed by the caller (battery level, network state, "do not disturb" toggle, etc.). Match types include `equals`, `not_equals`, `truthy`, `falsy`. The escape hatch for triggers the schema doesn't anticipate.

### Compound Triggers

Multiple trigger conditions combined with AND, OR, or NOT logic. The most common trigger structure in well-designed router configs — express real intent ("weekdays during work hours AND at the office") rather than single-axis conditions. Full semantics including evaluation order, short-circuiting behavior, nesting, common patterns, and anti-patterns are in `references/compound-trigger-semantics.md`.

---

## Workflow

When invoked:

**Step 1 — Receive inputs.** The Skill expects:
- `router_config`: A router config object conforming to the schema. Specifies rules, default profile, manual override state.
- `context`: Current state — current time, calendar state, geofence label, custom signals. Caller is responsible for providing accurate context.

Optional:
- `verbose`: When true, return the full rule evaluation trace in the output. Default false (just the verdict).

**Step 2 — Check manual override.** If `router_config.manual_override` is set AND the override's TTL has not expired, return the override's profile name immediately. Skip rule evaluation.

**Step 3 — Evaluate rules in order.** Walk the rule list in declared priority order (lowest priority number first). For each rule:
- Evaluate the trigger condition against `context`
- If the condition matches, return the rule's `profile` name
- If not, continue to next rule

**Step 4 — Fall back to default.** If no rule matched, return `router_config.default_profile`.

**Step 5 — Generate verdict.** Produce structured JSON output (see Output Format below). Include the selected profile, the trigger that matched (or `default_fallback`), and optionally the full evaluation trace.

**Step 6 — Return.** Output the JSON. The consuming Skill (handoff-gate, critic-gate, etc.) reads `selected_profile` and proceeds.

---

## Output Format

```json
{
  "selected_profile": "balanced",
  "matched_trigger": {
    "type": "time_window",
    "rule_id": "weekday-work-hours",
    "description": "Weekdays 9-5 → day-job profile"
  },
  "context_snapshot": {
    "current_time": "2026-05-01T14:30:00-07:00",
    "geofence": "at_office",
    "active_calendar_events": [],
    "manual_override": null
  },
  "router_config_version": "1.0.0",
  "evaluated_at": "2026-05-01T14:30:00-07:00",
  "evaluation_trace": []
}
```

When manual override is active:

```json
{
  "selected_profile": "lenient",
  "matched_trigger": {
    "type": "manual_override",
    "set_at": "2026-05-01T14:00:00-07:00",
    "expires_at": "2026-05-01T15:00:00-07:00",
    "set_by": "telegram_command"
  },
  "context_snapshot": { "...": "..." }
}
```

When fallback to default:

```json
{
  "selected_profile": "lenient",
  "matched_trigger": {
    "type": "default_fallback",
    "reason": "no rules matched"
  },
  "context_snapshot": { "...": "..." }
}
```

When `verbose: true`, `evaluation_trace` contains an entry for each rule evaluated:

```json
"evaluation_trace": [
  {
    "rule_id": "sleeping-window",
    "evaluated": true,
    "matched": false,
    "reason": "current_time 14:30 is outside sleeping window (22:00-06:00)"
  },
  {
    "rule_id": "weekday-work-hours",
    "evaluated": true,
    "matched": true,
    "reason": "current_time 14:30 falls in weekday window (Mon-Fri 09:00-17:00) AND geofence is at_office"
  }
]
```

---

## Examples

Three worked examples — standard weekday afternoon at office (compound trigger match), Saturday morning with manual override (override pre-empts rules), and late evening with no rule match (default fallback) — are in `references/trigger-types-detailed.md` (in the "Worked Examples" section). A pre-existing routing walkthrough is also in `examples/routing-walkthrough.md`.

---

## Router Config Structure

The router config conforms to `schema/router-config.schema.json`. Minimum structure:

```json
{
  "schema_version": "1.0.0",
  "name": "example-router",
  "description": "Example routing rules for three operational modes (lenient/balanced/strict)",
  "default_profile": "lenient",
  "rules": [
    {
      "rule_id": "sleeping-window",
      "priority": 1,
      "trigger": {
        "type": "time_window",
        "time_zone": "America/Phoenix",
        "days_of_week": ["all"],
        "start_time": "22:00",
        "end_time": "06:00"
      },
      "profile": "strict"
    },
    {
      "rule_id": "weekday-work-hours",
      "priority": 2,
      "trigger": {
        "type": "compound",
        "logic": "AND",
        "conditions": [
          { "type": "time_window", "days_of_week": ["mon","tue","wed","thu","fri"], "start_time": "09:00", "end_time": "17:00" },
          { "type": "geofence", "location": "at_office" }
        ]
      },
      "profile": "balanced"
    }
  ],
  "manual_override": null
}
```

A starter router config (`configs/example-router.json`) ships as a working example. Authoring custom router configs uses `rootnode-profile-builder` (if available), which supports the router-config schema in addition to gate-profile schemas — or hand-edit JSON against `schema/router-config.schema.json`.

---

## When to Use This Skill

Use this Skill when:
- An orchestrator needs to select a profile automatically without caller-specified parameter
- Wiring calendar/time-of-day/Telegram/geofence triggers to gate behavior
- A Skill's behavior should change with availability mode and the change should happen without manual intervention
- Setting up the "active profile" lookup that gates and other context-aware Skills consume

Do NOT use this Skill when:
- Evaluating work readiness for autonomous handoff (use rootnode-handoff-trigger-check)
- Reviewing a proposed change against authority (use rootnode-critic-gate)
- Building a profile config (use rootnode-profile-builder)
- The caller has already determined the profile and just wants to invoke a gate

---

## Troubleshooting

Common failure modes and resolution guidance — covering match failure issues (default returned when rule should match, multiple-rule-match priority, profile-doesn't-exist), manual override issues (doesn't expire, doesn't take effect, stacking), time zone and calendar issues (timezone offset, DST transitions, calendar predicate evaluation), schema validation issues (config validation failures, custom signal naming), and rule-set design issues (decision feels wrong, rule set growing unmanageable) — are in `references/troubleshooting.md`. Consult that file when routing decisions don't match expectations.

