Skip to content

Add Example and functionality to write Rules using AI agents - #927

Open
stumbaumr wants to merge 48 commits into
heishamon:mainfrom
stumbaumr:main
Open

Add Example and functionality to write Rules using AI agents#927
stumbaumr wants to merge 48 commits into
heishamon:mainfrom
stumbaumr:main

Conversation

@stumbaumr

@stumbaumr stumbaumr commented Jul 8, 2026

Copy link
Copy Markdown

Hi,
created an AGENTS.md and directory Examples with sub dirs to create Rules using AI Agents.

This should allow end users to facilitate AI agents to quickly achieve their goals using Rules.

The PR has since grown into a complete rules-authoring toolkit:

What's included

AI agent guidance

  • AGENTS.md plus the heishamon-rules skill (Examples/Rules/SKILL.md): the rules-engine language (sigils, blocks, built-ins) and its sharp edges, verified against the engine source rather than folklore — e.g. setTimer(id, 0) cancels a pending timer (it never fires), isset(x) returns 1 once a value is set, and NULL comparisons inside && chains evaluate truthy even though they are false in a bare if.

Worked examples (Examples/Rules/)

  • Jeisha-DHW-Radiators-Rowbuffer/ — a multi-year-tuned DHW/heating controller, optimized (minute-gated scheduler, min/max clamps, setCurves helper) with a block-by-block README.
  • Compressor-Short-Cycle-Guard/ — a small timer-armed-suppression ruleset, optimized to send commands only when state actually needs changing, with a README explaining each design decision.

Host-side test harness (Examples/Rules/harness/)

  • Compiles the real firmware rules engine (HeishaMon/src/rules/) as a Linux binary — no device needed. Rulesets get the exact same rule_initialize() validation as at upload, including @Name checks against the real topic/command tables from decode.h/commands.h.
  • Drives scripted scenarios against simulated heat-pump state: topic changes, virtual-clock timers (faithful to timerqueue.cpp semantics), a settable wall clock for %hour/%minute schedules, and simulated pump feedback.
  • expectcmd/expectnone assertions make scenarios self-checking regression tests.

Regression tests

  • Compressor-Short-Cycle-Guard/tests/ (8 scenarios) and Jeisha-DHW-Radiators-Rowbuffer/tests/ (13 scenarios) assert the exact @Set… command stream for boot, DHW cycles, defrost interactions, throttling, schedule points and edge cases.
  • Examples/Rules/run_tests.sh builds the harness and runs everything: currently 21 passed, 0 failed. The Jeisha tests pass against both the original and the optimized ruleset, proving the refactor is behavior-preserving.

The harness already paid for itself during development: it caught that a "fire immediately" setTimer(id, 0) idiom would deadlock on real hardware, and that unset (NULL) topics silently flip &&-chained conditions.

Best regards and thanks for your good work so far!
Rainer
Reference: #926

🤖 Generated with Claude Code

stumbaumr and others added 30 commits May 21, 2026 21:36
The vendored CurlyMoo rules engine miscompiles a standalone user-event
call (`name();`) inside an `if … end` body — OP_JMP back-patch gets
stomped during slot assignment, so the body never executes. PR heishamon#899
fixed the simple case in v4.1.5 but not the variant with two adjacent
if-blocks whose bodies are only `dhw_start();`, which is exactly our
shape in `on timer=10`.

Workaround: inline the dhw_start body at both call sites and remove
the `on dhw_start then … end` rule. Built-in calls (`setTimer`,
`@SetForceDefrost`) inside if-bodies remain safe because they emit
OP_CALL with a non-zero result slot, so the bug's trigger condition
(`getval(x->a) == 0`) doesn't fire. `#dhwStarted` double-fire guard is
preserved (set as the first statement of each inlined block).

CLAUDE.md updated to reflect the inlining and document the bug so
future edits don't reintroduce the broken pattern. See
heishamon#894 (comment)
for upstream report.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restore the `on dhw_start then … end` user-event and the two
`dhw_start();` call sites in `timer=10`. The codegen bug that forced
the inlining workaround in 7b9ea59 is fixed by IgorYbema/HeishaMon
commit 913e8eb ("reset mathcnt when a call statement closes an
if-block"), confirmed working on the alpha firmware installed on the
device. The fix is not yet in any heishamon/HeishaMon release, so
CLAUDE.md now documents a firmware floor: the `dhw_start` call sites
require a build containing 913e8eb (or the first heishamon release
that picks it up).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update the docs to match the rules: three-timer post-DHW recovery
chain (20/21/22), corrected DHW triggers and time-of-day branches,
the actual valve/defrost/power-consumption flow, and the full set of
MQTT topics the script references.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the DHW/recovery bursts across staggered one-shot timers so no
single firing emits more than two @set… commands. Renumber timers
(drop 20, add 32) and move dhw_start state capture inline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turn the unit on inside dhw_start (where the pre-DHW state is captured)
instead of in timer=22, and read @DHW_Temp/@Operating_Mode_State directly
in the low-tank fallback rather than via snapshot locals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refactor DHW cycle logic for improved performance and clarity, ensuring proper handling of valve states and command emissions.
- Update persistent-global names to lower-camelCase (post-refactor)
- Boot does not disable the heat pump; note SetMaxPumpDuty/SetPump
- Master clock now also snapshots #maxPumpDutyBeforeDHW
- timer=21 no longer snapshots the pump-duty cap

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Added logic to set dhwStarted when defrosting during DWH production.
Remap the DHW/recovery state-machine timer IDs in the docs from the old
{21,22,30,31,32} scheme to the current {20,21,22,23,24}, and note the
@Heatpump_State==1 gating on the valve/defrost handlers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seed $newQuietMode from @Quiet_Mode_Level at the top of the throttle
block so moderate-load firings (heating delta 0.3-1.6, cooling delta
-1..0) hold the current quiet level instead of writing 0 off an unset
local. Sync CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Defrost-end recovery now arms timer=20 (1 s) to re-assert DHW op mode
and clear #dhwStarted, rather than doing it inline; document accordingly.
Also tighten the smart-pause description to match the actual gates
(@Operating_Mode_State == 0 && #offAtNight == 1 && @Z1_Heat_Request_Temp > 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clarify variable persistence and boot logic in HeishaMon script.
Replace the assumed "outlet 2 °C above target stops compressor" note in
the throttle section with the documented behavior: thermo-off at target
+ 1 °C held 3 minutes, thermo-on below inlet-during-off-time − 3 °C.
Includes ManualsLib and Pro Club PDF source links.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
stumbaumr and others added 11 commits June 10, 2026 13:52
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s-Rowbuffer

git-subtree-dir: Examples/Rules/Jeisha-DHW-Radiators-Rowbuffer
git-subtree-mainline: 4f61fd7
git-subtree-split: 54c5b1b
Relocate the skill from .claude/skills/heishamon-rules/SKILL.md to
Examples/Rules/SKILL.md so it lives next to the worked examples it
documents, rename the merged-in CLAUDE.md to RULES.md, drop the
now-redundant standalone LICENSE, and update AGENTS.md/README.md
references to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Added coding standards section for HeishaMon rules including code style guidelines.
A small worked example showing timer-armed heat-request suppression
after the compressor stops, plus a cold-weather override.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…urves helper

Behavior-preserving cleanup of the Jeisha-DHW-Radiators-Rowbuffer example:

- Master clock (timer=10): wrap all time-of-day checks in a single
  '%minute == 0' guard and chain the fixed hours (8/9/18/19) with
  elseif, so the 30-second tick evaluates one comparison instead of
  five multi-term conditions outside minute :00. The configurable
  #dhwStartHour check stays an independent if.
- Replace endpoint-clamping if-blocks with the built-in min()/max()
  functions (DHW target interpolation, heat/cool request caps).
- Centralize every @SetCurves payload in a new setCurves($high, $low)
  helper function.
- Demote throttle's per-firing scratch vars (#roomDelta,
  #outletDeltaHeat, #newHeatRequest) to $ locals; they are recomputed
  each firing and used nowhere else. They stay console-visible via the
  function-block locals fix in the firmware debug dump.
- if/elseif for the mutually exclusive @Defrosting_State branches,
  whitespace cleanup.

Verified behavior-identical to the previous version with the host
rules harness: identical @set command sequences, values, virtual
timings and persistent-global end states across simulated days,
full DHW cycles (incl. defrost-during-DHW and recovery chain),
low-tank fallback, a throttle sweep and the smart-pause path, plus
a dhwStartHour re-tune variant. Bytecode shrinks from 5072 to 5048
bytes; the two-commands-per-firing budget is unchanged.

Also fixes a stale timer=32 reference in the README (it is timer=24).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compiles the unmodified firmware rules engine (HeishaMon/src/rules)
as a Linux binary with a minimal Arduino.h shim and drives it against
simulated heat-pump state: readable @topics come from a scenario-
controlled table, @set... writes are recorded instead of fed back
(matching the device's async command semantics), %hour/%minute are
scenario-controlled and timers elapse on a virtual clock.

'./harness <rules.txt> parse' runs the same parse/validation the
device performs at upload, with @name checking against the real
topic/command tables (build.sh regenerates valid_names.h from
decode.h/commands.h). The scenario mode prints every emitted @set
command, which makes ruleset refactors verifiable: diff the filtered
output of two versions across the built-in regression scenarios for
the Jeisha-DHW-Radiators-Rowbuffer example (simulated days, full DHW
cycles incl. defrost-during-DHW, low-tank fallback, throttle sweep,
smart pause).

Needs only g++ and python3, no Arduino toolchain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stumbaumr and others added 7 commits July 16, 2026 10:07
- Collapse the nested arming conditions into one && chain
- Send the restore command only when the -5 sentinel is actually
  active, removing the unconditional write on every boot/rules-save
- Route every restore (expiry, boot cleanup, cold-weather override)
  through the single timer=1 handler
- Arm the boot cleanup at 60s so all topic values are populated, and
  use setTimer(1, 1) in the override since setTimer(id, 0) cancels a
  pending timer instead of firing it (src/common/timerqueue.cpp)

Verified with the host rules harness; regression scenarios live in
tests/ and run via Examples/Rules/run_tests.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The host harness (Examples/Rules/harness/) now runs scenario files
instead of hardcoded C++ scenarios: topic changes, virtual-clock
timers, a settable wall clock for %hour/%minute schedules, a simulated
pump feedback op, and expectcmd/expectnone assertions so scenarios
double as self-checking regression tests.

- Compressor-Short-Cycle-Guard/tests/: 8 scenarios (boot, normal
  cycle, re-entry guard, defrost/cold stops, cold-snap override,
  stale-suppression reboot cleanup)
- Jeisha-DHW-Radiators-Rowbuffer/tests/: 13 scenarios (boot seeding,
  DHW start/restore/defrost chains, throttle heat+cool, schedule
  points, smart pause)
- run_tests.sh builds the harness and runs every ruleset's tests/
- Timer semantics now mirror the firmware: setTimer(id, 0) cancels a
  pending timer and never fires it
- SKILL.md: document setTimer(id, 0) cancel semantics, correct the
  inverted isset() description, warn about NULL comparisons inside
  && chains (verified against the engine), point to the harness

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings in the optimized Jeisha-DHW-Radiators-Rowbuffer ruleset
(minute-gated scheduler, min/max clamps, setCurves helper) and its
README updates. The harness and SKILL.md conflicts resolve to the
newer scenario-driven versions from main; all 21 tests pass against
the optimized ruleset, confirming the refactor is behavior-preserving.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- README.md: point the rules Examples section at Examples/Rules/,
  the host-side harness, and run_tests.sh
- Jeisha README: replace the outdated "no build, test, or lint
  tooling" intro with the off-device test workflow and add an
  editing-guidance bullet on running/extending the tests
- Compressor README: point at its tests/ and the harness

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Make the command-throughput best practice concrete: no more than two
  @set commands within 30 seconds across the whole ruleset, plus the
  EEPROM write-life caution (both per README.md)
- Add a harness-verified desired-state reconcile pattern (write from a
  parameterless helper, verify the read-back on a 30s timer, re-send
  until it sticks) as the general form of the README retry example
- Document an engine quirk found while validating the pattern: in a
  function block with $ parameters, a top-level @set write directly
  before a built-in call is executed out of order and silently
  dropped; if-wrapped statements and parameterless blocks work

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@stumbaumr stumbaumr changed the title Add Example and functionality to write Rules using KI Add Example and functionality to write Rules using AI agents Jul 27, 2026
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.

1 participant