Skip to content

feat(autonudge): optional per-loop banner for the visible nudge row - #7777

Merged
bolichen97 merged 1 commit into
kirodotdev:mainfrom
rnoack1:feat/autonudge-banner-split-a
Sep 4, 2026
Merged

feat(autonudge): optional per-loop banner for the visible nudge row#7777
bolichen97 merged 1 commit into
kirodotdev:mainfrom
rnoack1:feat/autonudge-banner-split-a

Conversation

@rnoack1

@rnoack1 rnoack1 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

An auto-nudge loop's message serves two consumers with opposite needs. The model needs the
whole instruction re-delivered every cycle — that is the guarantee the nudge exists to provide.
The dashboard already collapses each nudge to a one-line chip (expandable to the full body), so a
reader is not drowned on screen — but the full multi-KB message is still STORED and BROADCAST as
a transcript row every cycle: measured on one long-running loop, 44 nudge rows of ~7.9KB were
51.8% of the entire 671,900-char session file.

Why it matters

The stored/broadcast bloat is per-cycle and unbounded, so a long babysit loop fills its own
session file with repeated copies of the prompt. The chip hides that payload from the reader on
screen, but nothing today keeps the model's full instruction while storing and broadcasting only a
short line. The tradeoff of the fix is explicit: on a bannered row the card's expansion shows the
banner rather than the instruction, and because ordinary loops are remove-on-stop the full
per-cycle instruction leaves the transcript once the loop ends.

What changed

Adds an optional per-loop banner: str = "" — a SHORT stand-in shown in the visible dashboard
transcript row while the prompt still carries the full message.

  • Core: the banner field on NudgeLoop, threaded through add/update and persisted;
    _load repairs a hand-edited store (a non-string or oversized banner is blanked, and a
    persisted credential-shaped banner is scrubbed). _load also credential-scrubs the persisted
    message with the same two passes — a security-positive hardening of the sibling field, since
    the store is writable out-of-band and served raw by GET /api/autonudge; the authorized write
    paths already scrub message, so this closes the load path. Unlike the banner, message is
    redaction-only (never blanked on length): it is the payload the model receives and has no
    fallback row.
  • Fire path: _fire_dashboard_nudge builds the visible row from the banner when set, and the
    prompt handed to _run_chat is never shortened. The row's meta.nudge carries only cycle
    and loop_id and stays byte-identical whether or not a banner shows — the banner distinguishes
    the row through its visible content, and the banner TEXT is never copied into meta. Channel-bound
    loops (slack:/discord:/webex:) have no separate display surface, so a banner there is
    refused with 400.
  • Surfaces: POST/PATCH /api/autonudge and the MCP monitor_start/monitor_update tools
    accept banner (capped at MAX_BANNER_CHARS = 500); PATCH can quiet a running loop without
    resetting its budgets, and an explicit "" clears it. The /goal command sets one from the
    objective.
  • Redaction: a non-blank banner is credential-scrubbed at the write path with the same two
    passes the sibling message field already gets
    in both authorizers (redact_exfiltration_urls
    then redact_credentials), and the cap is re-checked after redaction because masking can grow
    the string. A banner is persisted and served by GET /api/autonudge, so a short field is not a
    safe place to park a credential.
  • Refactor: a shared AutoNudgeService.get_by_id accessor replaces a second inline id-scan; its
    two consumers are autonudge_authz.py (the update-path channel refusal) and
    dashboard/handlers/autonudge.py (the DELETE handler).

Default-off: with no banner, every existing loop's row — content and meta.nudge — is
byte-identical to before, and the store round-trips on a build that predates the field.

Scope: only /goal sets a banner automatically; otherwise a row is shortened only when a
producer (a REST or MCP caller) supplies a banner, so the per-cycle bloat is cured for a loop
exactly when it opts in. A server-side default that banners every loop is deliberately out of
scope for this PR and left to a follow-up.

Tests

test/test_autonudge_banner.py (default byte-identity, row-vs-prompt divergence proven at the
prompt, non-string/oversized/credential load repair for the banner, a persisted-message
credential-scrub test with a clean-message negative control, the REST + MCP cap and pass-through,
channel refusal, persistence round-trip, the meta.nudge contract that a bannered row's meta
stays byte-identical to a plain row with the banner text absent from meta, and the goal-command
producer), plus banner-restore coverage in test/test_slot_close_nudge_race.py and harness
updates in the guardrail/wiring tests. All banner tests pass locally.

@iamwhatever

Copy link
Copy Markdown
Collaborator

👋 Hi! This PR is currently in draft status. Workflow runs won't be auto-approved until it's marked as ready for review.

When you're ready, click "Ready for review" and the workflows will be approved on the next cycle automatically.

1 similar comment
@dwu96

dwu96 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

👋 Hi! This PR is currently in draft status. Workflow runs won't be auto-approved until it's marked as ready for review.

When you're ready, click "Ready for review" and the workflows will be approved on the next cycle automatically.

@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@rnoack1
rnoack1 marked this pull request as ready for review September 2, 2026 01:48
@rnoack1
rnoack1 requested a review from a team as a code owner September 2, 2026 01:48
@rnoack1
rnoack1 requested a review from Zedmor September 2, 2026 01:48
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 2, 2026
@rnoack1
rnoack1 force-pushed the feat/autonudge-banner-split-a branch from 967bf4b to 5bba485 Compare September 2, 2026 02:55
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

Reviewed a93297dae93b65ecfeb67972c7ca363cadb3fcf8 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] a93297d

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — ✅ PASS

Design-level review of a93297dae93b65ecfeb67972c7ca363cadb3fcf8 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

A measured, quantified harm fixed at its root cause — one string, two consumers — via a purely additive, opt-in field with both upgrade and downgrade round-trips proven.

[DESIGN-REVIEWED] a93297d

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5, fork) — ✅ PASS

Premise-level review of a93297dae93b65ecfeb67972c7ca363cadb3fcf8 via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All checks are done. The one candidate finding I chased — that a banner supplied alongside a structured monitor would skip normalization and persist raw — died on verification: in authorize_and_add_nudge the banner is only threaded into the monitor is None arm's svc.add kwargs, and svc.add_monitor takes no banner; no shipped caller reaches the monitor arm with one, and the MCP/directive update path explicitly refuses it. The refactor claim also verified: all 5 inline id-scans in the base dashboard/handlers/autonudge.py (lines 393, 462, 487, 627, 667) are converted to the one accessor, so no unfixed siblings remain. GET /api/autonudge serving raw asdict grounds the load-scrub items, and the base _load runs no redaction today.

First-Principles-Verdict: PASS

Every item traces to one measured cost — 44 nudge rows at ~7.9KB were 51.8% of a 671,900-char session file — or to a named trust boundary, and the riders are declared.

What this change ships

Intent: stop a long-running nudge loop from filling its own session file with per-cycle copies of its multi-KB instruction, without shortening what the model receives — an ADDITION.

  1. A loop can opt into a short visible transcript row while the model still gets the full instruction — justified (measured 51.8% bloat)
  2. POST /api/autonudge accepts banner — justified
  3. PATCH accepts banner, "" clears — justified (quiet a running loop without resetting its budgets)
  4. MCP monitor_start/monitor_update accept banner — justified (the surface that armed the measured offender)
  5. /goal rows now show the objective, not the full instruction — declared default change; reversible via PATCH ""
  6. Banner on a Slack/Discord/Webex loop refused with 400 — justified (no surface ever renders it)
  7. Hand-edited store banners repaired at load (blanked/masked) — justified (agent-writable store, served raw by GET)
  8. Persisted message credential-scrubbed at load — rides along, declared; protects the named external-content boundary
  9. Five inline loop-id scans collapsed into get_by_id — justified deletion (counted 5→0; 6 real consumers)
  10. Slot-close restore replays the banner — justified mechanism of item 1

Verification notes: the earlier abridged meta flag (zero consumers) was removed before shipping; the opt-in scope and the deferred server-wide default are declared, so restating them is noise. No zero-consumer surface, no duplicate mechanism, no reachable unfixed sibling found.

[FIRST-PRINCIPLES-REVIEWED] a93297d

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed a93297dae93b65ecfeb67972c7ca363cadb3fcf8 via the fork AI-review pipeline; updated in place on each push.

Review details

The candidate hinges on whether a gated monitor_start loop fires through the controller wake path (wake_message set → banner hidden) or the legacy path (wake_message is None → banner shown). I traced it directly.

is_structured_monitor_loop (autonudge.py:518) is monitor is not None and not gate. A gated monitor_start loop has gate=True, so it is not structured. In _timer (autonudge.py:3364) the structured/_on_monitor_tick branch is therefore skipped; the loop falls through the probe gate to _run_fire_cycleself._on_fire(loop) = _fire(loop)_fire_dashboard_nudge(loop) with wake_message=None (gateway.py:6203, 5874). The controller/_fire_monitor path (wake_message = envelope) only ever runs for structured (gate=False) loops from monitor_watch, and those never carry a banner.

So if banner and wake_message is None: is True for a gated monitor_start loop — the banner is rendered, not dead config. The candidate's own low-confidence caveat ("if it always takes the legacy path the banner is honoured and there is no defect") resolves against it. Falsified.

No further grounded defect surfaced in the diff.

No findings.

[OPUS-REVIEWED] a93297d

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 2, 2026
@rnoack1
rnoack1 force-pushed the feat/autonudge-banner-split-a branch from 5bba485 to 230c8d4 Compare September 2, 2026 04:32
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 2, 2026
@rnoack1
rnoack1 force-pushed the feat/autonudge-banner-split-a branch from 230c8d4 to 28a85ac Compare September 2, 2026 05:56
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 2, 2026
@rnoack1
rnoack1 force-pushed the feat/autonudge-banner-split-a branch from 0a019f9 to 7419359 Compare September 2, 2026 13:42
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 2, 2026
@rnoack1
rnoack1 force-pushed the feat/autonudge-banner-split-a branch from 7419359 to 8e65c6b Compare September 2, 2026 14:37
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 2, 2026
@rnoack1
rnoack1 force-pushed the feat/autonudge-banner-split-a branch from 8e65c6b to 1210fbd Compare September 2, 2026 15:59
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision labels Sep 2, 2026
@rnoack1
rnoack1 force-pushed the feat/autonudge-banner-split-a branch from 1210fbd to 9ad38ea Compare September 2, 2026 17:08
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 2, 2026
@rnoack1
rnoack1 force-pushed the feat/autonudge-banner-split-a branch from 9ad38ea to 17daa34 Compare September 2, 2026 18:07
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator

Audit note — #5999 is being closed in favour of this PR

You are the surviving re-cut; #5999 is being closed.

What the two shared

Both are OPEN, both are exactly ONE commit off the SAME merge-base d402acd (no stacking), and origin/main carries no banner at all: no banner field, no MAX_BANNER_CHARS, and _serialize is still plain asdict + monitor redaction. Same author rnoack1, byte-identical title, and 7777's branch is literally feat/autonudge-banner-split-a off 5999's feat/autonudge-banner -- it is the author's own re-cut. On the CURRENT heads, seven files land byte-identical post-change blobs (constants.py dee030df, chat_handlers.py a61dc380, session_directive_apply.py 0be3aae7, validation.py f251413e, test_slot_close_nudge_race.py 08f4f934, test_unattended_slot_guardrails.py 32cb5d3f, test_workflows_nudge_wiring.py d3f9e079); banner: str = "" arrives with a byte-identical 22-line comment block and is threaded through the same six call sites; normalize_banner and banner_unsupported_for are the same two helpers with the same signatures and the same error strings; and both ADD test/test_autonudge_banner.py, where 60 of 7777's 74 top-level names are also in 5999's copy. Only one can land: 5999's TestTheBannerStaysOptIn::test_no_shipped_producer_sets_a_banner asserts "banner=" not in inspect.getsource(chat_runner) while 7777 writes banner=normalize_banner(_objective, absent_ok=True, truncate=True)[0] into chat_runner and asserts that literal is present; both also register autonudge.py in security_posture.py with contradictory rationale text. 7777 survives because it is exactly what maintainer bolichen97 said he would approve on 2026-08-30 ("I would approve a version of this that is the banner alone"), it delivers the /goal producer without which the measured bloat is only conditionally removed, and its own blocking findings are closed on its current head (redaction moved to the write path incl. /goal, plus a _load scrub of banner AND message). 5999 cannot land as written: a live GPT 5.6 BLOCKING data-corruption finding on the quarantine sidecar, the maintainer's blocking ignored_fields correctness bug, a maintainer-identified silent-overwrite data-loss regression, and Design + First Principles both asking for the rider bundle to be split out. Close 5999 only AFTER its ~5,700 lines of non-banner residue are re-cut as the split-b PR its own body promises; no such PR exists among the 280 open ones today.

What #5999 had that this PR does not

Please pick these up (or say they are not wanted) so they do not disappear with that branch:

Re-cut as the promised split-b BEFORE closing 5999; none of it exists anywhere else. (1) src/kiro_crew/dashboard/handlers/autonudge.py::_serialize -- the denylist egress scrub (_UNSCRUBBED_FIELDS = ADDRESSING_FIELDS, per-field scrub_loop_text, monitor routed to _redact_monitor_value) plus the out["message_redacted"] wire flag; main still serves plain asdict. (2) src/kiro_crew/autonudge.py::scrub_loop_text and ADDRESSING_FIELDS -- the shared type-dispatched per-value redactor both the REST and WS surfaces call, so they cannot disagree on what is credential-shaped. (3) src/kiro_crew/slack/gateway.py::GatewayOrchestrator._observer -- the autonudge_state WS broadcast scrub (safe_message = scrub_loop_text(loop.message, field="message") + message_redacted), and the no-banner fire arm visible = redact_via_context(tagged). (4) src/kiro_crew/autonudge_authz.py::message_is_echoed_projection and ::_scrub_policy_unavailable -- the PATCH echo guard and the audited 503 for a host whose credential policy will not compose; plus handlers/autonudge.py's message_ignored: true response key. (5) website/src/components/AutoNudgePopover.tsx -- the editsRedactedGoal overwrite-confirm gate ("Replace goal with masked text") and the two amber notices, their 5 i18n keys across all 13 locales, and website/src/test/AutoNudgeRedactedProjection.test.tsx (409 lines). (6) src/kiro_crew/autonudge.py quarantine sidecar -- _QUARANTINE_FILE / _quarantined / _load_refused / AutoNudgeStoreUnvetted / _read_quarantine_sidecar, holding aside a row whose id/slot_key is credential-shaped, non-printable or non-str while its siblings arm; carries the live GPT 5.6 BLOCKING finding at autonudge.py:795 (an unreadable sidecar still arms loops) which must be fixed in the re-cut, and the maintainer wanted it in the deferred hardening PR regardless. (7) src/kiro_crew/security_posture.py -- the "Auto-nudge loop inventory" egress row and the autonudge_state broadcast sentence; reconcile rather than copy, since 7777 registers autonudge.py with different text. (8) src/kiro_crew/autonudge.py::_load -- the malformed-entry warning fix (scrubbed id + field names in place of %r of the whole row, a pre-existing leak on main) and the non-dict-row tolerance. Nothing needs harvesting from 7777 into 5999.


From a repository-wide duplicate/overlap audit of every pull request open against main (2026-09-02, 330 PRs, one reviewer per PR). Each PR was read as its full merge-base diff plus its description and every comment and review, then compared against each candidate PR's own diff and against origin/main at 1a765b88ceb7. This PR is not being closed — the note is informational. If the reading is wrong, please correct the reasoning rather than just the conclusion.

@bolichen97

Copy link
Copy Markdown
Collaborator

Audit note — #7996 rehomes the work #5999 was closed for, and collides with this PR

#7996 (same author) carries the egress-scrub work that #5999 was closed for — so nothing was lost in that closure. It also collides with this PR in seven files, and one collision is semantic rather than textual: slack/gateway.py::_fire_dashboard_nudge. A careless resolution there silently drops either #7996's egress scrub or this PR's banner feature; the merge looks clean and one behaviour is simply gone. Whichever lands second should re-derive that function rather than pick a side.

Verification that the harvest is genuinely covered

Read the merge-base diff of #7996 (5,649 lines) end to end, its body.md, #5999's closing comment, #7777's diff, and checked every item against origin/main = 680baf9 (both PRs' actual base_sha).

ITEM BY ITEM — all eight covered by #7996:

  1. _serialize denylist egress scrub — _UNSCRUBBED_FIELDS = ADDRESSING_FIELDS (1073), out[key] = scrub_loop_text(value, field=key) (1134), monitor routed to _redact_monitor_value (1132), out["message_redacted"] (1139). Main still serves plain asdict + monitor-only redaction.

  2. scrub_loop_text + ADDRESSING_FIELDS (88, 164) — improved: homed in autonudge.py rather than the handler because a third caller (autonudge_authz.message_is_echoed_projection) would create an import cycle; the handler re-exports it so gateway.py keeps importing from there. Still one definition for REST + WS + authz. Main's only ADDRESSING_FIELDS is the unrelated _ADDRESSING_FIELDS in handlers_instances.py:89.

  3. _observer WS scrub — safe_message = scrub_loop_text(loop.message, field="message") (1374) + "message_redacted" (1387), plus the fire arm visible = redact_via_context(tagged) (1319). Main broadcasts "message": loop.message raw at gateway.py:5927.

  4. message_is_echoed_projection (863), _scrub_policy_unavailable (880) wired into both authorizers (on the arm path deliberately before the sentinel unlink), payload["message_ignored"] = True (1177).

  5. Popover: editsRedactedGoal (4766), two text-warn notices, 5 keys × 13 locales, AutoNudgeRedactedProjection.test.tsx at exactly 409 lines. Confirm label reworded ("Overwrite original goal") — same gate.

  6. Quarantine sidecar: _QUARANTINE_FILE (69), AutoNudgeStoreUnvetted (118), _quarantined (302), _load_refused (319), _read_quarantine_sidecar (645). The live GPT 5.6 BLOCKING finding is FIXED, not carried: an unreadable sidecar now arms nothing (338-345), pinned by test_a_non_object_sidecar_refuses_the_store_rather_than_arming, whose docstring names the finding and says the assertion was previously the opposite.

  7. security_posture.py inventory row (1217) + autonudge_state sentence (1240) + "autonudge.py" registration (1264).

  8. _load malformed-entry fix (544) + non-dict-row branch (540). Main still has the %r leak at autonudge.py:640; feat(autonudge): optional per-loop banner for the visible nudge row #7777 leaves that line untouched.

DISJOINTNESS PROVEN BOTH WAYS: zero harvest tokens in #7777's current head (all 12 probes = 0); zero banner tokens in #7996 (MAX_BANNER_CHARS/normalize_banner/banner_unsupported_for = 0), matching its body claim.

  1. The cached the merge-base diff of feat(autonudge): optional per-loop banner for the visible nudge row #7777 is STALE — 17 files vs the current head's 20, missing src/kiro_crew/security_posture.py, test/test_autonudge_authz_cov80.py and test/test_slack_gateway_coverage.py. I re-fetched feat(autonudge): optional per-loop banner for the visible nudge row #7777's current patch over REST (GraphQL was rate-limited) to catch the security_posture.py collision; anyone re-running this from the cached file will miss it.

  2. feat(autonudge): scrub loop text at every egress sink #7996's body is stale: it says "I could not capture screenshots in this environment", but the head carries 6 PNGs under temp-screenshots/autonudge-redaction/ plus a capture harness (website/capture/autonudge-redaction.{html,tsx}, website/scripts/capture-autonudge-redaction.mjs) — the 9 files my local 24-section patch lacked. temp-screenshots/ is established convention (temp-screenshots/README.md and .github/workflows/cleanup-temp-screenshots.yml are both on main), so this is a body that no longer matches its diff, not a defect.

  3. Not verified: feat(autonudge): scrub loop text at every egress sink #7996's claim that test_autonudge.py::TestSentinelPathRepair::test_unnormalized_path_escaping_legacy_is_preserved is a pre-existing base-state failure. I did not run the suite.

  4. The 5 new i18n keys are inserted out of alphabetical order (confirm_overwrite_question after message_redacted_notice). No gate under website/scripts/ appears to enforce ordering, so cosmetic.

  5. I did not survey all ~280 other open PRs for overlap with feat(autonudge): scrub loop text at every egress sink #7996 — the duplication check was scoped to feat(autonudge): optional per-loop banner for the visible nudge row #7777 and origin/main as the task framed it. The harvest text itself asserts no split-b PR existed among the 280 open ones as of 2026-09-02.


From a repository-wide duplicate/overlap audit of every pull request open against main, re-run against the current tree (origin/main 680baf9448dc). This PR is not being closed — the note is informational. If the reading is wrong, please correct the reasoning rather than just the conclusion.

A loop may opt into a short `banner` shown in the dashboard transcript row while the model still receives the full `message` every cycle.

Default-off: an empty banner leaves the row byte-identical, and a persisted banner is repaired (non-string/oversized blanked) and capped at 500 chars.
@bolichen97

Copy link
Copy Markdown
Collaborator

Open PR relationship audit

This is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion.

Relationship findings

  • PR #7163 is OVERLAPPING relative to this PR. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7163: REBASE. Same file and same design policy for a capped display string, different features; both should land. Files: src/kiro_crew/dashboard/session_directive_apply.py, src/kiro_crew/validation.py. The two independent directions used different labels; the matrix conservatively retains OVERLAPPING for coordination.
  • This PR is OVERLAPPING with PR #7996. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7777: MERGE_DISCUSSION. Same author, same merge base (31b737e), both OPEN, 7 shared files, and hard textual conflicts in 4 of them (injected-messages.md, autonudge.py, autonudge_authz.py, slack/gateway.py) per git merge-tree. Goals differ, so neither should be closed — but they must be sequenced, and whichever lands second has to re-derive _fire_dashboard_nudge and drop its duplicate get_by_id and NON_EGRESS_REDACTION_MODULES entry rather than resolve the conflict by picking a side. Files: src/kiro_crew/slack/gateway.py, src/kiro_crew/autonudge.py, src/kiro_crew/security_posture.py.

No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit.

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The split between the visible transcript row and the model prompt is done in the one place that matters -- _fire_dashboard_nudge appends visible while _run_chat still receives the full tagged string -- so the per-cycle store/broadcast bloat is cured without ever shortening the instruction the nudge exists to re-deliver. Default-off is real (blank banner leaves content and meta.nudge byte-identical), the cap+redaction sequence is centralized in normalize_banner and re-checked after masking because redaction can grow the string, _load repairs a non-string/oversized/credential-bearing persisted banner so a hand-edited store cannot AttributeError the fire path, and the channel-bound refusal prevents storing config the Slack/Discord/Webex fire paths could never honour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants