Skip to content

fix: Operator drives the rk cron clock explicitly — mute/unmute on tracked-set transitions, lease as bounded snooze - #660

Merged
sahil-noon merged 4 commits into
mainfrom
260909-qvek-operator-cron-mute-lease
Sep 9, 2026
Merged

fix: Operator drives the rk cron clock explicitly — mute/unmute on tracked-set transitions, lease as bounded snooze#660
sahil-noon merged 4 commits into
mainfrom
260909-qvek-operator-cron-mute-lease

Conversation

@sahil-noon

Copy link
Copy Markdown
Collaborator

Meta

Change ID Type Confidence Plan Review
qvek fix 4.6/5.0 13/13 tasks, 34/34 acceptance ✓ ✓ 1 cycle
Impact +/− Net
raw +1064 / −78 +986
true +657 / −22 +635
└ impl +262 / −21 +241
└ tests +395 / −1 +394

excludes fab/, docs/ · generated by fab-kit v2.24.5

Pipeline: intake ✓ → apply ✓ → review ✓ → hydrate ✓ → ship → review-pr

Summary

run-kit's operator-tick cron entry lost both suppress_while guards (nothing-tracked, operator-loop-fresh) in sahil87/run-kit#889 after rk's state-file slug mismatch silently suppressed every operator tick for days. fab-kit's documented clock was then wrong in three places, and nothing on the fab side stopped ticks on an empty operator. This change moves quiescence from rk-side inference to explicit, deterministic mute/unmute issued by fab's tracked-set verbs, realigning skill text, memory, and specs to the guard-free entry.

Changes

  • Go: quiescence is an explicit mute issued by the tracked-set verbs — edge-triggered flip inside mutateOperatorState issues rk cron mute <id> when the tracked set (monitored / watches / active autopilot / open coordination note) empties and rk cron mute <id> --off when it first fills; every call is exec.LookPath-gated, argv exec, 5s-bounded via a new context-bound pane.RunCmd variant, and fail-silent
  • Lightweight reconciletick-start --diff mutes on a post-diff untracked state; skill §2 Init step 4 unmutes when the entry is muted while work is tracked
  • Lease is a bounded snooze, not a heartbeat — new §4 "Mute and Lease" prose: rk cron mute <id> --for <dur> for user-requested bounded quiet; never leave an indefinite mute behind while work is tracked
  • Skill text (fab-operator.md) — §2 ready line gains · muted / · muted until <t>; §4 quotes the guard-free, anchor-less entry with respawn: ["rk","operator","-L","{server}"]; ownership reworded to tracked-set-verb mute/unmute; Tick Behavior step 7, §6 known-gap paragraph, and §9 Cadence row updated
  • Memory and spec realignmentdocs/memory/runtime/operator.md clock block + Design Decisions (add "Explicit Mute Over Inferred Quiescence", "Lease Is a Bounded Snooze, Not a Heartbeat"; supersede the nothing-tracked gap entry), docs/specs/skills.md two-line update, _cli-fab.md shared clock-side-effect paragraph, _cli-external.md § rk pointer
  • Cross-repo contract noteslugify/serverSlug/StatePath in operator.go documented as owned by fab-kit and mirrored by run-kit for display; renaming either requires a coordinated run-kit change

🤖 Generated with Claude Code

sahil87 and others added 2 commits September 9, 2026 18:47
…, lease as bounded snooze

run-kit removed both suppress_while guards (nothing-tracked,
operator-loop-fresh) from the operator-tick cron entry; quiescence is
now an explicit mute issued edge-triggered from mutateOperatorState via
rk cron mute <id> / --off (fail-silent, 5s-bounded, LookPath-gated),
with lightweight reconcile in tick-start --diff and the skill's Init
step 4. Skill, memory, spec, and _cli-fab/_cli-external realigned to
the guard-free entry (no anchor, respawn argv); slugify/StatePath
documented as a cross-repo contract with run-kit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

rk cron list --json parsing can silently fail when muted_until is null, which would disable clock resolution/mute logic in the common “muted” state.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR realigns fab-kit’s operator cadence with run-kit’s guard-free cron entry by moving “quiescence” from rk-side inference to explicit, deterministic rk cron mute/--off calls driven by fab’s tracked-set state mutations, and updates the skills/docs to match that contract.

Changes:

  • Add Go-side tracked predicate + edge-triggered clock sync (mute/unmute on tracked↔untracked transitions) with a lightweight tick-start reconcile for drift.
  • Introduce a context-bounded subprocess helper (pane.RunCmdContext) and wire clock calls through it with timeout + fail-silent behavior.
  • Update operator skill text and docs/specs/memory to reflect mute/lease semantics and the new ownership model.
File summaries
File Description
src/kit/skills/fab-operator.md Updates operator skill to describe mute/unmute ownership, lease semantics, and ready-line clock status.
src/kit/skills/_cli-fab.md Documents the shared “clock side effect” across operator state verbs and tick-start diff reconcile.
src/kit/skills/_cli-external.md Adds a pointer to the fab-owned operator clock mute/lease rules.
src/go/fab/internal/pane/pane.go Adds RunCmdContext and routes RunCmd through it to preserve behavior while enabling timeouts.
src/go/fab/internal/pane/pane_test.go Adds tests covering deadline cancellation and stdout/stderr capture for RunCmdContext.
src/go/fab/cmd/fab/operator.go Adds cross-repo contract comments for state-file slug derivation and ownership.
src/go/fab/cmd/fab/operator_tick_start.go Disables edge-triggered sync in tick-start diff path and adds reconcile mute-if-untracked after mutation.
src/go/fab/cmd/fab/operator_test.go Updates slugify test comments to note the cross-repo consumer contract.
src/go/fab/cmd/fab/operator_state.go Introduces mutateOperatorStateClock and wires edge-triggered sync post-save.
src/go/fab/cmd/fab/operator_clock.go Implements tracked predicate, rk cron runner seam, entry resolution, and mute/unmute behavior.
src/go/fab/cmd/fab/operator_clock_test.go Adds comprehensive tests for tracked predicate, flips, reconcile, and fail-silent degradation.
fab/changes/260909-qvek-operator-cron-mute-lease/plan.md Captures the implementation plan, requirements, and acceptance checklist for this change.
fab/changes/260909-qvek-operator-cron-mute-lease/intake.md Records clarified decisions and updated requirements (placement + reconcile strategy).
fab/changes/260909-qvek-operator-cron-mute-lease/.status.yaml Updates fab change status/progress metadata and computed impact summary.
fab/changes/260909-qvek-operator-cron-mute-lease/.history.jsonl Logs stage transitions and confidence scoring history for the change.
docs/specs/skills.md Updates /fab-operator spec lines to reflect mute/lease posture and tools usage.
docs/memory/runtime/operator.md Realigns runtime/operator memory to the new explicit mute/lease clock contract and design decisions.
docs/memory/runtime/index.md Updates runtime index description for operator memory to include tracked-set-verb mute/lease quiescence.
Review details
  • Files reviewed: 18/18 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +88 to +92
Name string `json:"name"`
Target string `json:"target"`
Muted bool `json:"muted"`
MutedUntil int64 `json:"muted_until"`
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped — Go's encoding/json unmarshals a JSON null into a non-pointer numeric type as a no-op, never an error; the existing test fixture (cronListJSONMuted) already exercises "muted_until":null through resolveOperatorCronRow and passes, so clock resolution does not no-op here.

Comment on lines +821 to +830
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
start := time.Now()
_, _, err := RunCmdContext(ctx, "sleep", "30")
if err == nil {
t.Fatal("RunCmdContext past-deadline sleep: err = nil, want non-nil")
}
if elapsed := time.Since(start); elapsed > 5*time.Second {
t.Fatalf("RunCmdContext returned after %v, want within the deadline", elapsed)
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — tightened the elapsed bound in TestRunCmdContext_DeadlineKillsChild from 5s to 2s so the test actually validates the 100ms deadline kills the child. (68adb1e)

sahil87 and others added 2 commits September 9, 2026 18:55
Tighten TestRunCmdContext_DeadlineKillsChild elapsed bound 5s -> 2s so the
test actually validates that the 100ms context deadline kills the child.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@sahil-noon
sahil-noon marked this pull request as ready for review September 9, 2026 14:28
@sahil-noon
sahil-noon merged commit 3def486 into main Sep 9, 2026
3 checks passed
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.

3 participants