Skip to content

Dev - #9

Merged
a9lim merged 31 commits into
mainfrom
dev
Apr 30, 2026
Merged

Dev#9
a9lim merged 31 commits into
mainfrom
dev

Conversation

@a9lim

@a9lim a9lim commented Apr 30, 2026

Copy link
Copy Markdown
Owner

2.0: add positive feedback

a9lim and others added 30 commits April 29, 2026 15:39
Reorganizes the config into symmetric [negative] and [positive]
sections, parameterizes the safety layer by ChannelSpec, and adds an
IntifaceProvider with a three-layer disconnect watchdog (explicit stop,
atexit, signal handler) so the device cannot run past max_duration_s
when the controller process dies cleanly. The 1.x [auth]/[provider]/
[device]/[safety]/[rate_limit]/[tool] schemas are rejected at load with
a migration message; rlaif init writes the 2.0 shape. 297 tests
passing, dry-run green on both channels including the watchdog
no-refund invariant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revised project description and tool functionalities for clarity.
Replace the single-client snippet prompt with a multi-pick that accepts
comma- or space-separated indices/names plus 'a'/'all' and 's'/'skip'.
For each pick, auto-install via installer.install() when the client is
in installer.SUPPORTED; fall back to snippet_run() for vscode/zed (the
two clients we still won't auto-mutate). Failures on one client don't
take out the rest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- dry_run: extract typed MockProvider/MockRewardProvider locals so
  `.calls` accesses don't go through the Provider/RewardProvider ABCs
  (which legitimately don't expose it). Knocks out 16 pyright errors
  and the downstream "Scenario.ok partially unknown" cascade.
- rewards/intiface: drop unused `import buttplug` (the `from buttplug
  import (...)` form is what's actually used). Bind the freshly
  connected client to a typed local in get_client() before stashing
  on self, so the return is `ButtplugClient` not `... | None`.
- server: delete the dead `_build_default_safety` helper (no callers
  in src/ or tests/). Cleans up the now-orphan NEGATIVE_CHANNEL,
  POSITIVE_CHANNEL, SafetyConfig imports.
- test_intiface: extend the file-level `# pyright:` disables with
  `reportArgumentType=false` so the deliberately-structural _FakeCore
  injection passes. Matches the four disables already on that line.
- test_rewards, test_safety, test_intiface: ruff --fix import-order
  (I001).

uv run pyright: 0 errors. uv run ruff check: clean. uv run pytest:
297 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README and CLAUDE.md still described `rlaif init` as offering a single
MCP snippet at the end. Update them to reflect the new multi-select
auto-install flow (with vscode/zed falling back to snippet).

Also fix some pre-existing doc drift in cli.py: the install/uninstall
help text and module docstring still said "7 clients" / "seven
supported configs" and the docstring's enumerated auto-install list
was missing opencode.

No code changes; pyright + ruff + pytest all still clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`argparse.ArgumentParser.error` is typed `NoReturn` (it calls
sys.exit), so the trailing `return 2` was unreachable — pyright was
flagging it as dead code. The defensive parser.error() catch-all
stays as a guard against someone adding a sub.add_parser() without
a dispatch arm.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- inline compose_negative_description / compose_positive_description into
  _compose_description at the two call sites; drop one-line wrappers.
- promote server.{_build_negative_runtime,_build_positive_runtime} to
  build_negative_runtime / build_positive_runtime; doctor and live_smoke
  reuse them instead of reimplementing.
- add rlaif._util.pretty_json; replace identical _pretty copies in dry_run,
  live_smoke, log.
- live_smoke: load() once in run(), pass Config into _run_negative /
  _run_positive.
- config: factor missing-field reporting into _require_fields.
- installer: merge _print_unsupported_install / _print_unsupported_uninstall
  into _print_unsupported(action=...).
- doctor: collapse _negative_issues / _positive_issues into _channel_issues
  with an _offline_hint(channel, kind) helper.
- rewards/__init__: drop "(planned)" from IntifaceProvider line; it shipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the "shock" default — it was a footgun left over from before the
positive channel landed. Every real construction site (safety.py x2)
already passes channel=spec.name; the only other caller was a test stub.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TokenBucket.snapshot(now) returns (tokens, next_refill_at) in one
  _refill; info_snapshot uses it instead of two separate calls.
- OpsLog.recent: list(islice(reversed(deque), limit)) — O(limit) instead
  of materializing the entire ring just to slice the head off.
- log._run_stats: single-pass split of fired vs refused with inline
  intensity/duration/refusal-reason accumulation.
- intiface._select_device: collect visible names while scanning for the
  match instead of iterating devices.values() twice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Default rlaif log --tail 10 was slurping the entire ops.jsonl into
memory. Replace with a backward seek that reads 8K chunks from EOF and
stops once it has the requested number of complete non-empty lines.
Output is byte-for-byte identical to the previous implementation; the
multi-MB synthetic test in test_cli.py guards the equivalence.

tail <= 0 still reads the whole file (it must — it shows everything).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build_file_sink already paid the cost of re-opening the file per write;
without flush+fsync, a crash mid-line could lose entries the safety
story claims are persisted. SSD latency is invisible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls the duplicated authorize -> fire -> commit|rollback shape out of
handle_rlaif_negative and handle_rlaif_positive into a private
_fire_channel helper. Each handler still owns its own typed-error
dispatch table, so Provider and RewardProvider namespaces stay disjoint
at the call site (CLAUDE.md hard rule #8). The watchdog no-refund
contract (hard rule #6) is encoded as the single refund=False entry in
the positive-channel table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three parallel dicts (BUILDERS in snippet, _PATHS + _ADAPTERS in
installer) all keyed on the same client names had no structural
guarantee against drift. Consolidate into a frozen ClientRecord
dataclass + CLIENTS_REGISTRY in a new leaf module rlaif._clients;
snippet.py and installer.py defer their registry imports to break
the cycle, leaving tomlkit / ruamel.yaml confined to installer.py
as before. Adding a client now means one record entry plus the
matching adapter / path-fn / builder, not three coordinated edits.

CLI behavior is unchanged: same client list, same argparse choices,
same snippet output, same install/uninstall paths and exit codes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md architecture block now lists `_clients.py` (the single MCP-client
registry) and `_util.py`, describes `_fire_channel`'s dispatch-table shape,
documents the canonical `build_*_runtime` constructors, and replaces the
dead `compose_*_description` references with `_compose_description`.

The "things that look like bugs but are not" section gains entries for the
fsync-on-every-append durability guarantee and the backward-scan `--tail`
implementation, and the pyright-cache note is generalized past `rlaif.rewards`
to cover any newly-added internal module.

README architecture tree picks up `_clients.py` and `_util.py`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updated README to clarify feedback channels and configuration instructions.
Removed unnecessary repetition about safety checks and MCP tools.
Updated comments for clarity and simplified instructions for the configuration file.
Updated the description in pyproject.toml to remove specific references to feedback channels.
Clarified the description of the MCP server and its reinforcement channels.
Apply ruff format to files that had drifted from the project formatter
(multi-line string-arg splits collapsed to one line where they fit).
Pure formatting; no behavior change. Gets the file format-clean before
the next round of edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…defaults

PiShock and OpenShock now share field names — PiShock's api_key →
api_token, sharecode → shocker_id. The pishock SDK still sees its
legacy parameter names internally; only the rlaif config/CLI surface
changes. Env vars renamed to match: RLAIF_PISHOCK_API_TOKEN,
RLAIF_PISHOCK_SHOCKER_ID, RLAIF_OPENSHOCK_API_TOKEN.

[positive.intiface] simplified: ws_url → base_url for parity with
OpenShock; client_name dropped (always "rlaif"); device_index removed
in favor of device_name (exact name or display name) since intiface's
index ordering was never stable across reconnects. Env var renamed to
RLAIF_INTIFACE_BASE_URL.

ChannelSpec gained five default_* fields and SafetyConfig.for_spec
fills missing kwargs from the spec, so a partial [positive.safety]
block now picks up the README's positive defaults (75/5/5/30) instead
of inheriting the negative dataclass-level ones (25/2/3/600). The
positive-channel description frame advertises the actual positive
defaults.

README example reconciled with its own defaults table on positive
max_intensity (70 → 75). Pre-fuse keys (api_key, sharecode, ws_url,
client_name, device_index) are rejected at load time with messages
pointing at their replacements rather than silently dropped.

PiShock's shocker_id is treated as capability-bearing in the redacted
config dump (prefix-only); OpenShock's UUID-shaped shocker_id stays
plain — it shows up in the dashboard and isn't a secret on its own.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@a9lim
a9lim merged commit b2838e6 into main Apr 30, 2026
11 checks passed
a9lim added a commit that referenced this pull request May 2, 2026
2.0: add positive feedback
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