> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basemode.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rules and domains

> Writing a rule that fires on its own, giving a domain the triggers that decide when it is relevant, and the full ladder for when a domain does not fire and you cannot see why.

A domain decides *when*. A rule is *what gets said*. They are separate commands because they are separate things, and they live in separate places: triggers in `domains.toml`, rule text in the graph.

## Creating a domain

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
base domain create --name billing
base domain create --name billing --keyword "invoice"
base domain add-trigger --domain billing --keyword "refund"
base domain add-trigger --domain billing --path "src/billing"
base domain get billing
```

**Why it works.** A domain has three kinds of trigger. `prompt_keywords` match against what you type. `file_keywords` match against the content of a file being worked on. `paths` match against recently-active file paths pulled from the graph. Any one of them firing is enough.

**The gotcha.** Creating a domain gives you a bucket with no rules in it. A match with nothing attached injects nothing, silently, which is the single most common reason people conclude base is not working. Add a rule next, not later.

## Adding a rule

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
base rule add --domain billing --text "Money amounts are integer cents, never floats"
base rule add --domain billing --text "Money amounts are integer cents" --rationale "Floats lose pennies on repeated conversion and the ledger has to reconcile"
base rule list --domain billing
```

**Why it works.** Rules are graph nodes attached to the domain, so they can be added, listed and removed without touching config, and the same rule can be reasoned about alongside the decisions and notes filed under the same domain.

**The gotcha.** Write the `--rationale`. It is injected with the rule as "rule, because rationale", and a rule with a reason attached survives being argued with in a way that a bare imperative does not.

**Next rung.** `base rule remove --domain billing --index 2` removes one by the index shown in `rule list`. Indexes shift after a removal, so list again between removals rather than working down a list you printed earlier.

## The global tier

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
base rule -g add --domain writing --text "Prefer short sentences"
base rule -g list --domain writing
```

`-g` goes on `rule` itself, before the verb. After the verb is invalid.

**The gotcha.** Six commands carry `-g`: `rule`, `decision`, `handoff`, `fork`, `learn` and `changes`. Everything else decides its tier from where you are standing, and outside a registered workspace base refuses the write rather than picking one for you. Check `base help <command>` instead of assuming.

## Checking a domain fires

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
base context "we need to refund the invoice"
base domain get billing
base rule list --domain billing
```

`base context` runs the real matcher against text you supply, without sending anything anywhere. This is the fastest possible answer to "would this have worked".

## When nothing gets injected

Work down this ladder in order. It is ordered by how often each cause is the real one.

<Steps>
  <Step title="Did you type a star command in that prompt?">
    A matched `*command` short-circuits domain matching for that turn. Your rules do not arrive, and nothing says so. This is the most common cause by a distance, especially if you have settled into a mode you type by habit.
  </Step>

  <Step title="Does the domain have any rules?">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    base rule list --domain billing
    ```

    An empty list means the trigger fires and there is nothing behind it. `domains.toml` holds triggers only, so a domain can look perfectly configured and be empty.
  </Step>

  <Step title="Does the trigger actually match?">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    base context "the exact text you sent"
    ```

    Matching is plain substring and path comparison. A domain triggered on `invoice` does not fire on `billing`, and nothing is doing anything clever on your behalf.
  </Step>

  <Step title="Are you inside a registered workspace?">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    base project list
    ```

    Outside one there is no workspace graph to match against. `base scaffold` fixes it.
  </Step>

  <Step title="Is the hook itself running?">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    echo '{"prompt":"we need to refund the invoice"}' | base hook user-prompt-submit
    ```

    This runs the hook by hand and prints what it would have injected. Read stderr as well as stdout.
  </Step>
</Steps>

## Watching it work while you tune

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
base config set devmode.enabled true
```

With devmode on, hook output carries a diagnostic block showing which domains matched, why, and what was deduplicated or suppressed. It is the fastest way to tune a trigger, because you can see the match rather than infer it.

**The gotcha.** Turn it back off when you are done. It adds output to every hook event, and the noise stops being informative quickly.

## Removing triggers and domains

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
base domain remove-trigger --domain billing --keyword "refund"
base domain remove billing
```

`base domain remove` is a triggers-level delete: the rules you added still exist in the graph, they simply have nothing left to fire them. If you want a domain quiet but recoverable, remove its triggers instead.

<Warning>
  **The domain write commands do not all use the same tier.** `create`, `remove-trigger` and `remove` only touch the global `~/.base-gbl/domains.toml`. `add-trigger` writes the workspace file.

  Two consequences. A domain you create and then add a trigger to is split across two files. And a trigger added inside a workspace cannot be removed from the command line: `remove-trigger` will not find it, and `base domain remove` reports "Domain not found" for a workspace domain that `base domain list` is showing you, because `list` merges both tiers and the write commands do not.

  Established from source at `4866996`, the commit the shipped 0.13.2 binary was built from. Until it changes, edit the workspace `.base/domains.toml` by hand for anything the CLI will not reach.
</Warning>

## Pushing config into the graph

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
base domain sync
```

This reads `domains.toml` into the graph so domains and their bootstrap rules exist as entities. It also runs on its own at session start, gated by a timestamp so it is a no-op when nothing has changed.

## Next

<CardGroup cols={2}>
  <Card title="Debugging injection" icon="bug" href="/guides/debugging-injection">
    The rest of the story: brackets, suppression, and where the audit trail is.
  </Card>

  <Card title="Context reference" icon="sliders" href="/reference/context">
    Every domain and rule command, with its flags.
  </Card>
</CardGroup>
