Preserve delegation work through any upstream outage, not just budget
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 52s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 52s
An exhausted budget was a special case of something more general: the work is not at fault. A network blip, a timeout, a 5xx from the proxy, or a rate limit that did not clear all leave a delegation branch that may be perfectly good, and discarding it would throw away work for a reason that has nothing to do with the work. UpstreamUnavailable now names that family, with BudgetExhausted a member of it — distinguishable where the difference matters, since a budget will not clear within the hour while a network blip will. A delegation stopped by any of them is "interrupted": branch kept, intent left pending, and A3's resume rule picks it up. The benefit of the doubt stops at the server's own door. A 4xx that is not a rate limit means the request itself was wrong, and preserving branches on the strength of one would hide a real bug — so those still fail and clean up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8cfb45aed3
commit
8cfa116aa1
4 files changed
+112
-17
No files matched your search
@@ -21,7 +21,7 @@ from nisse_dev.delegation import (
|
||||
delegation_reconciler,
|
||||
)
|
||||
from nisse_dev.git import GitRepo
|
||||
from nisse_dev.llm import BudgetExhausted, LLMError
|
||||
from nisse_dev.llm import BudgetExhausted, LLMError, UpstreamUnavailable
|
||||
from nisse_dev.oplog import OpLog
|
||||
from nisse_dev.state import StateStore
|
||||
from nisse_dev.tools import Workspace
|
||||
@@ -614,3 +614,36 @@ def test_an_ordinary_failure_still_discards(
|
||||
assert not result.interrupted
|
||||
assert repo.current_branch() == "main"
|
||||
assert store.pending_intents() == []
|
||||
|
||||
|
||||
def test_a_network_outage_also_preserves_the_work(
|
||||
store: StateStore, ws: Workspace, repo: GitRepo
|
||||
) -> None:
|
||||
# Same reasoning as an exhausted budget: the branch may be fine, and the
|
||||
# outage says nothing about the quality of the work on it.
|
||||
class FlakyClient(ScriptedClient):
|
||||
def complete(self, *args: Any, **kwargs: Any):
|
||||
if self.calls:
|
||||
raise UpstreamUnavailable("LLM request failed: connection refused")
|
||||
return super().complete(*args, **kwargs)
|
||||
|
||||
client = FlakyClient([[call("write_file", path="progress.py", content="real")]])
|
||||
result = build(client, store, ws, repo).run(brief="b", branch="d")
|
||||
|
||||
assert result.status == "interrupted"
|
||||
assert (repo.root / "progress.py").exists()
|
||||
assert [i.kind for i in store.pending_intents()] == [DELEGATION_INTENT]
|
||||
|
||||
|
||||
def test_a_bad_request_is_not_given_the_benefit_of_the_doubt(
|
||||
store: StateStore, ws: Workspace, repo: GitRepo
|
||||
) -> None:
|
||||
# A 4xx that is not a rate limit means we sent something wrong; treating
|
||||
# that as an outage would preserve branches and hide a real bug.
|
||||
class BadRequestClient(ScriptedClient):
|
||||
def complete(self, *args: Any, **kwargs: Any):
|
||||
raise LLMError("LLM request returned 400: malformed tool schema")
|
||||
|
||||
result = build(BadRequestClient([]), store, ws, repo).run(brief="b", branch="d")
|
||||
assert result.status == "failed"
|
||||
assert not result.interrupted
|
||||
Reference in new issue
Block a user