Starvation notice promises an automatic ESC/Ctrl+C that cannot fire for a busy agent #190

Description

@pseudoseed

The starvation notice tells the operator that Tower will act automatically. In the case the
notice actually fires in, it will not.

What the notice says

mailbox-wiring.ts:280 and :297:

Tower sends one automatic Ctrl+C after the starvation window. If it remains held, ...
... Tower sends one automatic ESC after the starvation window. ...

Stated unconditionally, in both the Ctrl+C and the ESC variant.

What happened

builder-bugfix-189 (opencode harness), one architect message, Tower log:

16:22:09 Message held (busy): architect → builder-bugfix-189 (mailbox 739ebec1...)
16:23:10 [mailbox] ESCALATED 739ebec1… (held 60s, reason busy) — visibility only, not delivered
16:25:10 [mailbox] STARVATION notice → main about builder-bugfix-189 (1 held ~180s, reason busy)
16:25:35 Interrupt (ESC) sent: architect → builder-bugfix-189 ← me, by hand
16:25:36 [mailbox] delivered 739ebec1-…

No automatic control byte appears anywhere. The message sat 3m26s and moved only when a
human sent the same ESC the notice had promised.

Why it cannot fire here

mailbox-delivery.tsrecordStreak, the auto-recovery arm:

consttoken=`${session.bytesWritten}:${session.info.cols}x${session.info.rows}`;constcurrent=this.recoveryState.get(key);if(!current||current.detail!==outcome.detail||current.session!==session||current.token!==token){this.recoveryState.set(key,{ ...,since: now,attempted: false});// clock RESET}elseif(!current.attempted&&now-current.since>=this.recoveryMs){
... recoverHeld(...)// the promised byte}

Its own comment states the intent plainly:

Input alone does not advance this counter, but ANY app output does. Requiring the same
session + output token for the whole window prevents an unreadable ACTIVE turn from being
interrupted merely because its classifier detail stayed the same.

An agent that is busy is, by definition, producing output. Every pass changes bytesWritten,
which resets since, so now - since >= recoveryMs is never reached. The condition that
raises the notice (reason: busy, an agent working and rendering) is the same condition that
guarantees the recovery will not fire.

The guard itself is right and should not change. Interrupting a live turn because its screen
is unreadable is the wrong trade. The defect is entirely in what the operator is told.

Why this is the house defect class

This program keeps finding one shape: a claim about something, asserted where convenient and
never checked against the thing itself.
Usually it is "I could not tell" spelled the same way
as "no". This is the inverted twin — "I am handling it" printed where the code has decided not
to. An operator who believes the sentence waits for a repair that is structurally unreachable,
and the held message is the architect's correction to a builder that is meanwhile committing to
the wrong fix.

Suggested fix

Derive the sentence from the same predicate the code uses, rather than restating a behaviour
next to it:

  • If the screen has been static for the window, say the automatic byte is coming.
  • If the agent is still producing output, say so, and say the operator's own
    afx interrupt <id> --no-enter is the remedy — because it is the only one.
  • Do not print one unconditional sentence covering both.

The general rule this program has landed on twice already applies exactly: make a guard
report its own reach.
A notice that cannot tell the operator whether its remedy applies
should not claim the remedy.

Related: #179, #180 (same defect class, different subsystem).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/towerTower, afx, terminals, messaging

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Starvation notice promises an automatic ESC/Ctrl+C that cannot fire for a busy agent #190

      Description

      @pseudoseed

      The starvation notice tells the operator that Tower will act automatically. In the case the
      notice actually fires in, it will not.

      What the notice says

      mailbox-wiring.ts:280 and :297:

      Tower sends one automatic Ctrl+C after the starvation window. If it remains held, ...
      ... Tower sends one automatic ESC after the starvation window. ...

      Stated unconditionally, in both the Ctrl+C and the ESC variant.

      What happened

      builder-bugfix-189 (opencode harness), one architect message, Tower log:

      16:22:09 Message held (busy): architect → builder-bugfix-189 (mailbox 739ebec1...)
      16:23:10 [mailbox] ESCALATED 739ebec1… (held 60s, reason busy) — visibility only, not delivered
      16:25:10 [mailbox] STARVATION notice → main about builder-bugfix-189 (1 held ~180s, reason busy)
      16:25:35 Interrupt (ESC) sent: architect → builder-bugfix-189 ← me, by hand
      16:25:36 [mailbox] delivered 739ebec1-…
      

      No automatic control byte appears anywhere. The message sat 3m26s and moved only when a
      human sent the same ESC the notice had promised.

      Why it cannot fire here

      mailbox-delivery.tsrecordStreak, the auto-recovery arm:

      consttoken=`${session.bytesWritten}:${session.info.cols}x${session.info.rows}`;constcurrent=this.recoveryState.get(key);if(!current||current.detail!==outcome.detail||current.session!==session||current.token!==token){this.recoveryState.set(key,{ ...,since: now,attempted: false});// clock RESET}elseif(!current.attempted&&now-current.since>=this.recoveryMs){
      ... recoverHeld(...)// the promised byte}

      Its own comment states the intent plainly:

      Input alone does not advance this counter, but ANY app output does. Requiring the same
      session + output token for the whole window prevents an unreadable ACTIVE turn from being
      interrupted merely because its classifier detail stayed the same.

      An agent that is busy is, by definition, producing output. Every pass changes bytesWritten,
      which resets since, so now - since >= recoveryMs is never reached. The condition that
      raises the notice (reason: busy, an agent working and rendering) is the same condition that
      guarantees the recovery will not fire.

      The guard itself is right and should not change. Interrupting a live turn because its screen
      is unreadable is the wrong trade. The defect is entirely in what the operator is told.

      Why this is the house defect class

      This program keeps finding one shape: a claim about something, asserted where convenient and
      never checked against the thing itself.
      Usually it is "I could not tell" spelled the same way
      as "no". This is the inverted twin — "I am handling it" printed where the code has decided not
      to. An operator who believes the sentence waits for a repair that is structurally unreachable,
      and the held message is the architect's correction to a builder that is meanwhile committing to
      the wrong fix.

      Suggested fix

      Derive the sentence from the same predicate the code uses, rather than restating a behaviour
      next to it:

      • If the screen has been static for the window, say the automatic byte is coming.
      • If the agent is still producing output, say so, and say the operator's own
        afx interrupt <id> --no-enter is the remedy — because it is the only one.
      • Do not print one unconditional sentence covering both.

      The general rule this program has landed on twice already applies exactly: make a guard
      report its own reach.
      A notice that cannot tell the operator whether its remedy applies
      should not claim the remedy.

      Related: #179, #180 (same defect class, different subsystem).

      Activity

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

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        area/towerTower, afx, terminals, messaging

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Starvation notice promises an automatic ESC/Ctrl+C that cannot fire for a busy agent #190

          Description

          @pseudoseed

          The starvation notice tells the operator that Tower will act automatically. In the case the
          notice actually fires in, it will not.

          What the notice says

          mailbox-wiring.ts:280 and :297:

          Tower sends one automatic Ctrl+C after the starvation window. If it remains held, ...
          ... Tower sends one automatic ESC after the starvation window. ...

          Stated unconditionally, in both the Ctrl+C and the ESC variant.

          What happened

          builder-bugfix-189 (opencode harness), one architect message, Tower log:

          16:22:09 Message held (busy): architect → builder-bugfix-189 (mailbox 739ebec1...)
          16:23:10 [mailbox] ESCALATED 739ebec1… (held 60s, reason busy) — visibility only, not delivered
          16:25:10 [mailbox] STARVATION notice → main about builder-bugfix-189 (1 held ~180s, reason busy)
          16:25:35 Interrupt (ESC) sent: architect → builder-bugfix-189 ← me, by hand
          16:25:36 [mailbox] delivered 739ebec1-…
          

          No automatic control byte appears anywhere. The message sat 3m26s and moved only when a
          human sent the same ESC the notice had promised.

          Why it cannot fire here

          mailbox-delivery.tsrecordStreak, the auto-recovery arm:

          consttoken=`${session.bytesWritten}:${session.info.cols}x${session.info.rows}`;constcurrent=this.recoveryState.get(key);if(!current||current.detail!==outcome.detail||current.session!==session||current.token!==token){this.recoveryState.set(key,{ ...,since: now,attempted: false});// clock RESET}elseif(!current.attempted&&now-current.since>=this.recoveryMs){
          ... recoverHeld(...)// the promised byte}

          Its own comment states the intent plainly:

          Input alone does not advance this counter, but ANY app output does. Requiring the same
          session + output token for the whole window prevents an unreadable ACTIVE turn from being
          interrupted merely because its classifier detail stayed the same.

          An agent that is busy is, by definition, producing output. Every pass changes bytesWritten,
          which resets since, so now - since >= recoveryMs is never reached. The condition that
          raises the notice (reason: busy, an agent working and rendering) is the same condition that
          guarantees the recovery will not fire.

          The guard itself is right and should not change. Interrupting a live turn because its screen
          is unreadable is the wrong trade. The defect is entirely in what the operator is told.

          Why this is the house defect class

          This program keeps finding one shape: a claim about something, asserted where convenient and
          never checked against the thing itself.
          Usually it is "I could not tell" spelled the same way
          as "no". This is the inverted twin — "I am handling it" printed where the code has decided not
          to. An operator who believes the sentence waits for a repair that is structurally unreachable,
          and the held message is the architect's correction to a builder that is meanwhile committing to
          the wrong fix.

          Suggested fix

          Derive the sentence from the same predicate the code uses, rather than restating a behaviour
          next to it:

          • If the screen has been static for the window, say the automatic byte is coming.
          • If the agent is still producing output, say so, and say the operator's own
            afx interrupt <id> --no-enter is the remedy — because it is the only one.
          • Do not print one unconditional sentence covering both.

          The general rule this program has landed on twice already applies exactly: make a guard
          report its own reach.
          A notice that cannot tell the operator whether its remedy applies
          should not claim the remedy.

          Related: #179, #180 (same defect class, different subsystem).

          Activity

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

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            area/towerTower, afx, terminals, messaging

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Starvation notice promises an automatic ESC/Ctrl+C that cannot fire for a busy agent #190

              Description

              @pseudoseed

              The starvation notice tells the operator that Tower will act automatically. In the case the
              notice actually fires in, it will not.

              What the notice says

              mailbox-wiring.ts:280 and :297:

              Tower sends one automatic Ctrl+C after the starvation window. If it remains held, ...
              ... Tower sends one automatic ESC after the starvation window. ...

              Stated unconditionally, in both the Ctrl+C and the ESC variant.

              What happened

              builder-bugfix-189 (opencode harness), one architect message, Tower log:

              16:22:09 Message held (busy): architect → builder-bugfix-189 (mailbox 739ebec1...)
              16:23:10 [mailbox] ESCALATED 739ebec1… (held 60s, reason busy) — visibility only, not delivered
              16:25:10 [mailbox] STARVATION notice → main about builder-bugfix-189 (1 held ~180s, reason busy)
              16:25:35 Interrupt (ESC) sent: architect → builder-bugfix-189 ← me, by hand
              16:25:36 [mailbox] delivered 739ebec1-…
              

              No automatic control byte appears anywhere. The message sat 3m26s and moved only when a
              human sent the same ESC the notice had promised.

              Why it cannot fire here

              mailbox-delivery.tsrecordStreak, the auto-recovery arm:

              consttoken=`${session.bytesWritten}:${session.info.cols}x${session.info.rows}`;constcurrent=this.recoveryState.get(key);if(!current||current.detail!==outcome.detail||current.session!==session||current.token!==token){this.recoveryState.set(key,{ ...,since: now,attempted: false});// clock RESET}elseif(!current.attempted&&now-current.since>=this.recoveryMs){
              ... recoverHeld(...)// the promised byte}

              Its own comment states the intent plainly:

              Input alone does not advance this counter, but ANY app output does. Requiring the same
              session + output token for the whole window prevents an unreadable ACTIVE turn from being
              interrupted merely because its classifier detail stayed the same.

              An agent that is busy is, by definition, producing output. Every pass changes bytesWritten,
              which resets since, so now - since >= recoveryMs is never reached. The condition that
              raises the notice (reason: busy, an agent working and rendering) is the same condition that
              guarantees the recovery will not fire.

              The guard itself is right and should not change. Interrupting a live turn because its screen
              is unreadable is the wrong trade. The defect is entirely in what the operator is told.

              Why this is the house defect class

              This program keeps finding one shape: a claim about something, asserted where convenient and
              never checked against the thing itself.
              Usually it is "I could not tell" spelled the same way
              as "no". This is the inverted twin — "I am handling it" printed where the code has decided not
              to. An operator who believes the sentence waits for a repair that is structurally unreachable,
              and the held message is the architect's correction to a builder that is meanwhile committing to
              the wrong fix.

              Suggested fix

              Derive the sentence from the same predicate the code uses, rather than restating a behaviour
              next to it:

              • If the screen has been static for the window, say the automatic byte is coming.
              • If the agent is still producing output, say so, and say the operator's own
                afx interrupt <id> --no-enter is the remedy — because it is the only one.
              • Do not print one unconditional sentence covering both.

              The general rule this program has landed on twice already applies exactly: make a guard
              report its own reach.
              A notice that cannot tell the operator whether its remedy applies
              should not claim the remedy.

              Related: #179, #180 (same defect class, different subsystem).

              Activity

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

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                area/towerTower, afx, terminals, messaging

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Starvation notice promises an automatic ESC/Ctrl+C that cannot fire for a busy agent #190

                  Description

                  @pseudoseed

                  The starvation notice tells the operator that Tower will act automatically. In the case the
                  notice actually fires in, it will not.

                  What the notice says

                  mailbox-wiring.ts:280 and :297:

                  Tower sends one automatic Ctrl+C after the starvation window. If it remains held, ...
                  ... Tower sends one automatic ESC after the starvation window. ...

                  Stated unconditionally, in both the Ctrl+C and the ESC variant.

                  What happened

                  builder-bugfix-189 (opencode harness), one architect message, Tower log:

                  16:22:09 Message held (busy): architect → builder-bugfix-189 (mailbox 739ebec1...)
                  16:23:10 [mailbox] ESCALATED 739ebec1… (held 60s, reason busy) — visibility only, not delivered
                  16:25:10 [mailbox] STARVATION notice → main about builder-bugfix-189 (1 held ~180s, reason busy)
                  16:25:35 Interrupt (ESC) sent: architect → builder-bugfix-189 ← me, by hand
                  16:25:36 [mailbox] delivered 739ebec1-…
                  

                  No automatic control byte appears anywhere. The message sat 3m26s and moved only when a
                  human sent the same ESC the notice had promised.

                  Why it cannot fire here

                  mailbox-delivery.tsrecordStreak, the auto-recovery arm:

                  consttoken=`${session.bytesWritten}:${session.info.cols}x${session.info.rows}`;constcurrent=this.recoveryState.get(key);if(!current||current.detail!==outcome.detail||current.session!==session||current.token!==token){this.recoveryState.set(key,{ ...,since: now,attempted: false});// clock RESET}elseif(!current.attempted&&now-current.since>=this.recoveryMs){
                  ... recoverHeld(...)// the promised byte}

                  Its own comment states the intent plainly:

                  Input alone does not advance this counter, but ANY app output does. Requiring the same
                  session + output token for the whole window prevents an unreadable ACTIVE turn from being
                  interrupted merely because its classifier detail stayed the same.

                  An agent that is busy is, by definition, producing output. Every pass changes bytesWritten,
                  which resets since, so now - since >= recoveryMs is never reached. The condition that
                  raises the notice (reason: busy, an agent working and rendering) is the same condition that
                  guarantees the recovery will not fire.

                  The guard itself is right and should not change. Interrupting a live turn because its screen
                  is unreadable is the wrong trade. The defect is entirely in what the operator is told.

                  Why this is the house defect class

                  This program keeps finding one shape: a claim about something, asserted where convenient and
                  never checked against the thing itself.
                  Usually it is "I could not tell" spelled the same way
                  as "no". This is the inverted twin — "I am handling it" printed where the code has decided not
                  to. An operator who believes the sentence waits for a repair that is structurally unreachable,
                  and the held message is the architect's correction to a builder that is meanwhile committing to
                  the wrong fix.

                  Suggested fix

                  Derive the sentence from the same predicate the code uses, rather than restating a behaviour
                  next to it:

                  • If the screen has been static for the window, say the automatic byte is coming.
                  • If the agent is still producing output, say so, and say the operator's own
                    afx interrupt <id> --no-enter is the remedy — because it is the only one.
                  • Do not print one unconditional sentence covering both.

                  The general rule this program has landed on twice already applies exactly: make a guard
                  report its own reach.
                  A notice that cannot tell the operator whether its remedy applies
                  should not claim the remedy.

                  Related: #179, #180 (same defect class, different subsystem).

                  Activity

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

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    area/towerTower, afx, terminals, messaging

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Starvation notice promises an automatic ESC/Ctrl+C that cannot fire for a busy agent #190

                      Description

                      @pseudoseed

                      The starvation notice tells the operator that Tower will act automatically. In the case the
                      notice actually fires in, it will not.

                      What the notice says

                      mailbox-wiring.ts:280 and :297:

                      Tower sends one automatic Ctrl+C after the starvation window. If it remains held, ...
                      ... Tower sends one automatic ESC after the starvation window. ...

                      Stated unconditionally, in both the Ctrl+C and the ESC variant.

                      What happened

                      builder-bugfix-189 (opencode harness), one architect message, Tower log:

                      16:22:09 Message held (busy): architect → builder-bugfix-189 (mailbox 739ebec1...)
                      16:23:10 [mailbox] ESCALATED 739ebec1… (held 60s, reason busy) — visibility only, not delivered
                      16:25:10 [mailbox] STARVATION notice → main about builder-bugfix-189 (1 held ~180s, reason busy)
                      16:25:35 Interrupt (ESC) sent: architect → builder-bugfix-189 ← me, by hand
                      16:25:36 [mailbox] delivered 739ebec1-…
                      

                      No automatic control byte appears anywhere. The message sat 3m26s and moved only when a
                      human sent the same ESC the notice had promised.

                      Why it cannot fire here

                      mailbox-delivery.tsrecordStreak, the auto-recovery arm:

                      consttoken=`${session.bytesWritten}:${session.info.cols}x${session.info.rows}`;constcurrent=this.recoveryState.get(key);if(!current||current.detail!==outcome.detail||current.session!==session||current.token!==token){this.recoveryState.set(key,{ ...,since: now,attempted: false});// clock RESET}elseif(!current.attempted&&now-current.since>=this.recoveryMs){
                      ... recoverHeld(...)// the promised byte}

                      Its own comment states the intent plainly:

                      Input alone does not advance this counter, but ANY app output does. Requiring the same
                      session + output token for the whole window prevents an unreadable ACTIVE turn from being
                      interrupted merely because its classifier detail stayed the same.

                      An agent that is busy is, by definition, producing output. Every pass changes bytesWritten,
                      which resets since, so now - since >= recoveryMs is never reached. The condition that
                      raises the notice (reason: busy, an agent working and rendering) is the same condition that
                      guarantees the recovery will not fire.

                      The guard itself is right and should not change. Interrupting a live turn because its screen
                      is unreadable is the wrong trade. The defect is entirely in what the operator is told.

                      Why this is the house defect class

                      This program keeps finding one shape: a claim about something, asserted where convenient and
                      never checked against the thing itself.
                      Usually it is "I could not tell" spelled the same way
                      as "no". This is the inverted twin — "I am handling it" printed where the code has decided not
                      to. An operator who believes the sentence waits for a repair that is structurally unreachable,
                      and the held message is the architect's correction to a builder that is meanwhile committing to
                      the wrong fix.

                      Suggested fix

                      Derive the sentence from the same predicate the code uses, rather than restating a behaviour
                      next to it:

                      • If the screen has been static for the window, say the automatic byte is coming.
                      • If the agent is still producing output, say so, and say the operator's own
                        afx interrupt <id> --no-enter is the remedy — because it is the only one.
                      • Do not print one unconditional sentence covering both.

                      The general rule this program has landed on twice already applies exactly: make a guard
                      report its own reach.
                      A notice that cannot tell the operator whether its remedy applies
                      should not claim the remedy.

                      Related: #179, #180 (same defect class, different subsystem).

                      Activity

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

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        area/towerTower, afx, terminals, messaging

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Starvation notice promises an automatic ESC/Ctrl+C that cannot fire for a busy agent #190

                          Description

                          @pseudoseed

                          The starvation notice tells the operator that Tower will act automatically. In the case the
                          notice actually fires in, it will not.

                          What the notice says

                          mailbox-wiring.ts:280 and :297:

                          Tower sends one automatic Ctrl+C after the starvation window. If it remains held, ...
                          ... Tower sends one automatic ESC after the starvation window. ...

                          Stated unconditionally, in both the Ctrl+C and the ESC variant.

                          What happened

                          builder-bugfix-189 (opencode harness), one architect message, Tower log:

                          16:22:09 Message held (busy): architect → builder-bugfix-189 (mailbox 739ebec1...)
                          16:23:10 [mailbox] ESCALATED 739ebec1… (held 60s, reason busy) — visibility only, not delivered
                          16:25:10 [mailbox] STARVATION notice → main about builder-bugfix-189 (1 held ~180s, reason busy)
                          16:25:35 Interrupt (ESC) sent: architect → builder-bugfix-189 ← me, by hand
                          16:25:36 [mailbox] delivered 739ebec1-…
                          

                          No automatic control byte appears anywhere. The message sat 3m26s and moved only when a
                          human sent the same ESC the notice had promised.

                          Why it cannot fire here

                          mailbox-delivery.tsrecordStreak, the auto-recovery arm:

                          consttoken=`${session.bytesWritten}:${session.info.cols}x${session.info.rows}`;constcurrent=this.recoveryState.get(key);if(!current||current.detail!==outcome.detail||current.session!==session||current.token!==token){this.recoveryState.set(key,{ ...,since: now,attempted: false});// clock RESET}elseif(!current.attempted&&now-current.since>=this.recoveryMs){
                          ... recoverHeld(...)// the promised byte}

                          Its own comment states the intent plainly:

                          Input alone does not advance this counter, but ANY app output does. Requiring the same
                          session + output token for the whole window prevents an unreadable ACTIVE turn from being
                          interrupted merely because its classifier detail stayed the same.

                          An agent that is busy is, by definition, producing output. Every pass changes bytesWritten,
                          which resets since, so now - since >= recoveryMs is never reached. The condition that
                          raises the notice (reason: busy, an agent working and rendering) is the same condition that
                          guarantees the recovery will not fire.

                          The guard itself is right and should not change. Interrupting a live turn because its screen
                          is unreadable is the wrong trade. The defect is entirely in what the operator is told.

                          Why this is the house defect class

                          This program keeps finding one shape: a claim about something, asserted where convenient and
                          never checked against the thing itself.
                          Usually it is "I could not tell" spelled the same way
                          as "no". This is the inverted twin — "I am handling it" printed where the code has decided not
                          to. An operator who believes the sentence waits for a repair that is structurally unreachable,
                          and the held message is the architect's correction to a builder that is meanwhile committing to
                          the wrong fix.

                          Suggested fix

                          Derive the sentence from the same predicate the code uses, rather than restating a behaviour
                          next to it:

                          • If the screen has been static for the window, say the automatic byte is coming.
                          • If the agent is still producing output, say so, and say the operator's own
                            afx interrupt <id> --no-enter is the remedy — because it is the only one.
                          • Do not print one unconditional sentence covering both.

                          The general rule this program has landed on twice already applies exactly: make a guard
                          report its own reach.
                          A notice that cannot tell the operator whether its remedy applies
                          should not claim the remedy.

                          Related: #179, #180 (same defect class, different subsystem).

                          Activity

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

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            area/towerTower, afx, terminals, messaging

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Starvation notice promises an automatic ESC/Ctrl+C that cannot fire for a busy agent #190

                              Description

                              @pseudoseed

                              The starvation notice tells the operator that Tower will act automatically. In the case the
                              notice actually fires in, it will not.

                              What the notice says

                              mailbox-wiring.ts:280 and :297:

                              Tower sends one automatic Ctrl+C after the starvation window. If it remains held, ...
                              ... Tower sends one automatic ESC after the starvation window. ...

                              Stated unconditionally, in both the Ctrl+C and the ESC variant.

                              What happened

                              builder-bugfix-189 (opencode harness), one architect message, Tower log:

                              16:22:09 Message held (busy): architect → builder-bugfix-189 (mailbox 739ebec1...)
                              16:23:10 [mailbox] ESCALATED 739ebec1… (held 60s, reason busy) — visibility only, not delivered
                              16:25:10 [mailbox] STARVATION notice → main about builder-bugfix-189 (1 held ~180s, reason busy)
                              16:25:35 Interrupt (ESC) sent: architect → builder-bugfix-189 ← me, by hand
                              16:25:36 [mailbox] delivered 739ebec1-…
                              

                              No automatic control byte appears anywhere. The message sat 3m26s and moved only when a
                              human sent the same ESC the notice had promised.

                              Why it cannot fire here

                              mailbox-delivery.tsrecordStreak, the auto-recovery arm:

                              consttoken=`${session.bytesWritten}:${session.info.cols}x${session.info.rows}`;constcurrent=this.recoveryState.get(key);if(!current||current.detail!==outcome.detail||current.session!==session||current.token!==token){this.recoveryState.set(key,{ ...,since: now,attempted: false});// clock RESET}elseif(!current.attempted&&now-current.since>=this.recoveryMs){
                              ... recoverHeld(...)// the promised byte}

                              Its own comment states the intent plainly:

                              Input alone does not advance this counter, but ANY app output does. Requiring the same
                              session + output token for the whole window prevents an unreadable ACTIVE turn from being
                              interrupted merely because its classifier detail stayed the same.

                              An agent that is busy is, by definition, producing output. Every pass changes bytesWritten,
                              which resets since, so now - since >= recoveryMs is never reached. The condition that
                              raises the notice (reason: busy, an agent working and rendering) is the same condition that
                              guarantees the recovery will not fire.

                              The guard itself is right and should not change. Interrupting a live turn because its screen
                              is unreadable is the wrong trade. The defect is entirely in what the operator is told.

                              Why this is the house defect class

                              This program keeps finding one shape: a claim about something, asserted where convenient and
                              never checked against the thing itself.
                              Usually it is "I could not tell" spelled the same way
                              as "no". This is the inverted twin — "I am handling it" printed where the code has decided not
                              to. An operator who believes the sentence waits for a repair that is structurally unreachable,
                              and the held message is the architect's correction to a builder that is meanwhile committing to
                              the wrong fix.

                              Suggested fix

                              Derive the sentence from the same predicate the code uses, rather than restating a behaviour
                              next to it:

                              • If the screen has been static for the window, say the automatic byte is coming.
                              • If the agent is still producing output, say so, and say the operator's own
                                afx interrupt <id> --no-enter is the remedy — because it is the only one.
                              • Do not print one unconditional sentence covering both.

                              The general rule this program has landed on twice already applies exactly: make a guard
                              report its own reach.
                              A notice that cannot tell the operator whether its remedy applies
                              should not claim the remedy.

                              Related: #179, #180 (same defect class, different subsystem).

                              Activity

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

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                area/towerTower, afx, terminals, messaging

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions