{"_id":"@argszero/cordis-plugin-repeat-guard-escalation","_rev":"2-7e801a1b371ffa609b8276b400b8d838","name":"@argszero/cordis-plugin-repeat-guard-escalation","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@argszero/cordis-plugin-repeat-guard-escalation","version":"0.1.0","keywords":["cordis","deepseek-harness","dsh","plugin","guard","loop","repeat","tool-call"],"license":"MIT","_id":"@argszero/cordis-plugin-repeat-guard-escalation@0.1.0","maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"homepage":"https://github.com/argszero/cordis-plugin-repeat-guard-escalation#readme","bugs":{"url":"https://github.com/argszero/cordis-plugin-repeat-guard-escalation/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"c9db618b87b59509d65282517293b7f231f3faa6","tarball":"https://registry.npmjs.org/@argszero/cordis-plugin-repeat-guard-escalation/-/cordis-plugin-repeat-guard-escalation-0.1.0.tgz","fileCount":8,"integrity":"sha512-bF7afIsEEAZ31QKIAJ/eZDmQZpKeIQ0A///rbcSu5SQI/8XsrHNcCFVbsYC03BIy5HVJA++F3/g2D5gcNdHH9w==","signatures":[{"sig":"MEUCIBMeAWeVyU0u97rVVuxK0mHBQ8g22M9E8Uv6a2/W/G+MAiEA7LAFK5vsP62QNi/VRyZBsowF7dJANZ2cmQsPGv5cnU4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31354},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"656a4b9de10160a802a68c8fe516f37bc2937413","scripts":{"test":"node --test \"test/*.spec.mjs\"","build":"tsc","prepublishOnly":"tsc"},"_npmUser":{"name":"argszero","email":"argszero.reg@gmail.com"},"repository":{"url":"git+https://github.com/argszero/cordis-plugin-repeat-guard-escalation.git","type":"git"},"_npmVersion":"11.17.0","description":"Enforcement tier for dsh's repeat-tool-reminder guard: when the model repeats an identical tool call past the advisory thresholds and ignores the reminder, deny the call before dispatch so the repeated action cannot execute again. Bounded (maxDenials) so ","directories":{},"_nodeVersion":"26.5.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.5.0","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":"0.1.2-rc.1","@deepseek-ai/dsh-agent":"0.1.2-rc.1","@deepseek-ai/dsh-scope":"0.1.2-rc.1","@deepseek-ai/dsh-tools":"0.1.2-rc.1","@deepseek-ai/dsh-session":"0.1.2-rc.1","@deepseek-ai/dsh-system-prompt":"0.1.2-rc.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-agent":">=0.1.2-rc.1 <0.2.0 || >=0.1.3-alpha.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0","@deepseek-ai/dsh-tools":">=0.1.2-rc.1 <0.2.0 || >=0.1.3-alpha.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0"},"_npmOperationalInternal":{"tmp":"tmp/cordis-plugin-repeat-guard-escalation_0.1.0_1789172627627_0.6384044436592933","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"_id":"@argszero/cordis-plugin-repeat-guard-escalation@0.1.1","dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"bugs":{"url":"https://github.com/argszero/cordis-plugin-repeat-guard-escalation/issues"},"dist":{"shasum":"17bd83c3eebda5a08e671594d6d16956fadd1ddb","tarball":"https://registry.npmjs.org/@argszero/cordis-plugin-repeat-guard-escalation/-/cordis-plugin-repeat-guard-escalation-0.1.1.tgz","fileCount":8,"integrity":"sha512-L6to9a7GQnITmV8x/6U6EkLo2kAk5OnqelbmChQA6p4mUiX/UDjw73/lIcuA+Wi9mdfElzRhbVRb8FBUxd8F/g==","signatures":[{"sig":"MEUCIQCjw7D3RXVpRvq/sVpZGKnayMJgcpOTKGxjIfsS9+ExVgIgeWrG7Qj+PhMedCaPn1LnWwb90qG9yguXN9b8RkrTj9Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIG876Sq+/PaKTSmaMC+5grIMtK9ay2rtaTnpIs44DpW7AiEA5nS82MQcMUWX4klN2MAKUMCHLaokcCegIs3Vz1+8qG4="}],"unpackedSize":46782},"main":"lib/index.js","name":"@argszero/cordis-plugin-repeat-guard-escalation","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"0219559f53e697eef13a4ed8b9389f0f831c8f33","license":"MIT","scripts":{"test":"node --test \"test/*.spec.mjs\"","build":"tsc","prepublishOnly":"tsc"},"version":"0.1.1","_npmUser":{"name":"argszero","email":"argszero.reg@gmail.com"},"homepage":"https://github.com/argszero/cordis-plugin-repeat-guard-escalation#readme","keywords":["cordis","deepseek-harness","dsh","plugin","guard","loop","repeat","tool-call"],"repository":{"url":"git+https://github.com/argszero/cordis-plugin-repeat-guard-escalation.git","type":"git"},"_npmVersion":"11.17.0","description":"Enforcement tier for dsh's repeat-tool-reminder guard: deny an identical repeated tool call before dispatch once advice has been ignored, plus a much earlier tier for repeats that keep FAILING with the same error. Bounded (maxDenials) so a looping session","directories":{},"maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"_nodeVersion":"26.5.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.5.0","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":"0.1.2-rc.1","@deepseek-ai/dsh-agent":"0.1.2-rc.1","@deepseek-ai/dsh-scope":"0.1.2-rc.1","@deepseek-ai/dsh-tools":"0.1.2-rc.1","@deepseek-ai/dsh-session":"0.1.2-rc.1","@deepseek-ai/dsh-system-prompt":"0.1.2-rc.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-agent":">=0.1.2-rc.1 <0.2.0 || >=0.1.3-alpha.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0","@deepseek-ai/dsh-tools":">=0.1.2-rc.1 <0.2.0 || >=0.1.3-alpha.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cordis-plugin-repeat-guard-escalation_0.1.1_1789175137921_0.5468721848312112"}}},"time":{"created":"2026-09-12T00:23:47.284Z","modified":"2026-09-12T01:05:38.187Z","0.1.0":"2026-09-12T00:23:47.804Z","0.1.1":"2026-09-12T01:05:38.032Z"},"bugs":{"url":"https://github.com/argszero/cordis-plugin-repeat-guard-escalation/issues"},"license":"MIT","homepage":"https://github.com/argszero/cordis-plugin-repeat-guard-escalation#readme","keywords":["cordis","deepseek-harness","dsh","plugin","guard","loop","repeat","tool-call"],"repository":{"url":"git+https://github.com/argszero/cordis-plugin-repeat-guard-escalation.git","type":"git"},"description":"Enforcement tier for dsh's repeat-tool-reminder guard: deny an identical repeated tool call before dispatch once advice has been ignored, plus a much earlier tier for repeats that keep FAILING with the same error. Bounded (maxDenials) so a looping session","maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"readme":"# @argszero/cordis-plugin-repeat-guard-escalation\n\nGive dsh's `repeat-tool-reminder` guard **teeth**.\n\n`dsh` plugin (bundle patch). When a model repeats an identical tool call past\nthe advisory thresholds and ignores the reminder, this plugin **denies the call\nbefore dispatch**, so the repeated action cannot execute again. A call that is\nrepeated *and failing* is denied much earlier (`escalateFailingAt`, default 3),\nbecause retrying unchanged arguments against a deterministic error cannot work.\n\n## The problem\n\nThe shipped `@deepseek-ai/dsh-repeat-tool-reminder` guard does its detection job\ncorrectly: it tracks consecutive identical calls and injects a reminder at 3, 5,\nand 8 repeats. But the reminder is **advisory only** — the guard's own README\nrecords this as a known, unimplemented limitation:\n\n> **Advisory only** — escalating to a blocking form at a high threshold is not\n> implemented, though `PostToolDecision` already supports blocking.\n\nDiscussion [#6370](https://github.com/deepseek-ai/deepseek-harness/discussions/6370)\nis the field evidence. With a local OpenAI-compatible provider, the same broad\nsearch repeats well past all three reminders:\n\n```\nGrep def test_\nGrep def test_\nGrep def test_\nContext injection repeat-tool-reminder grep × 3\nGrep def test_          ← ignored the reminder\n...\nContext injection repeat-tool-reminder grep × 5\nGrep def test_          ← ignored it again\n```\n\nA second consequence is easy to miss, and it is in the same README:\n\n> **Past the highest threshold a chain goes silent** — reminders fire only at\n> exact configured counts, never beyond them.\n\nSo with the default `[3, 5, 8]`, the 9th, 10th, and 11th identical calls draw\n**no reminder at all**. The tail of a loop is unguarded — precisely where the\nmodel has proved it is not responding to advice.\n\n## What the plugin does\n\nIt counts the same chains the shipped guard counts, and from `escalateAt`\n(default **9** — one past the highest reminder threshold) it denies the call\nbefore it is dispatched:\n\n```\nBlocked: `grep` has now been called with identical arguments 9 times in a row,\nand the reminder you were given did not change the outcome. The results of those\ncalls are already in this session — do not issue this call again with these\narguments. If the evidence you already have is enough, answer or make the change\nnow. If it is not, the missing piece is specific: say what it is, then either use\na different tool or narrow the arguments so they target exactly that piece.\nFurther identical calls will be blocked automatically.\n```\n\n**Why deny rather than block.** A `PostToolDecision` block still *runs* the tool\nand still burns a full model turn before the model sees a failure, so a\ndetermined loop survives it. A `pre-execute` denial means the call is never\ndispatched: the repeated action stops producing a result to react to, which is\nwhat actually breaks the cycle.\n\nThe reminder is not replaced. The shipped guard still runs and still explains\nitself; this plugin only adds the consequence after advice has been ignored.\n\n## Failing repeats: the second #6370 report\n\nThe same discussion reports a variant with a different shape. An `edit` whose\n`old_string` and `new_string` were identical failed deterministically —\n\n```\nold_string and new_string must differ\n```\n\n— and the model re-sent the same failing call many times instead of re-reading\nthe target or reconstructing the patch. The shipped reminder is **failure-blind**:\nit counts calls and never inspects their results, so this case gets the same\ngentle advice as a successful poll loop, at the same late thresholds.\n\nA successful repeat may be legitimate (polling a job); a failing repeat with\nunchanged arguments is not, because the arguments it keeps sending are the ones\nthe error is complaining about. So this plugin escalates the two separately:\n\n```\nBlocked: `edit` has now been called with identical arguments 4 times in a row,\nand the last 3 of them failed with the same error. Repeating it cannot succeed —\nthe tool is not going to accept these arguments on the next attempt. The failure\nwas: old_string and new_string must differ. Change the arguments so they no\nlonger trigger it, or use a different tool. If the change you intended may\nalready have been applied, read the target back before editing it again.\nFurther identical calls will be blocked automatically.\n```\n\nThe error is quoted back verbatim, because the model's next move has to be\nagainst that specific complaint. A success clears the failure streak, so a poll\nloop is never escalated early; a changed call resets both.\n\nOne deployment note: `escalateFailingAt` counts **observed** failures, so the\ndefault of 3 lets three failing calls through and refuses the fourth. Whether an\nin-flight call will fail is not knowable before it runs, and assuming it would be\nexactly the false positive this rule exists to avoid.\n\n## Install\n\n```sh\nnpm install @argszero/cordis-plugin-repeat-guard-escalation\n```\n\nThe package ships a `dsh.bundle` patch:\n\n```sh\ndsh plugin add @argszero/cordis-plugin-repeat-guard-escalation\n```\n\nor directly:\n\n```yaml\n- insert:\n    - id: repeat-guard-escalation\n      name: '@argszero/cordis-plugin-repeat-guard-escalation'\n```\n\n## Config\n\n| key | default | meaning |\n|---|---|---|\n| `escalateAt` | `9` | Consecutive identical attempts at which the call is denied before dispatch |\n| `escalateFailingAt` | `3` | Identical *failing* attempts observed before the call is denied (the next attempt is refused) |\n| `maxDenials` | `3` | How many times one chain may be denied before the guard steps aside |\n| `include` | `[]` | Only these tools are tracked; empty means every tool |\n| `exclude` | `[]` | Never track these tools (they neither count nor clear a chain) |\n\n```yaml\n- set:\n    - id: repeat-guard-escalation\n      config:\n        escalateAt: 9\n        escalateFailingAt: 3\n        maxDenials: 3\n        exclude: ['todo_write']\n```\n\nInvalid configuration fails at load with a clear error — a threshold below 2, a\nnon-integer, or a `maxDenials` below 1 — never a silent change of behaviour.\n\n## Design: bounds and refusals\n\n- **Enforcement is bounded.** After `maxDenials`, the guard steps aside\n  permanently for that chain and calls proceed. Denying forever would turn a\n  stuck model into a dead session with no way forward — strictly worse than the\n  loop it prevents. The final denial says so, so the model knows the block is\n  ending.\n- **A different call is progress and resets the chain.** Only *consecutive*\n  identical calls escalate, matching the shipped guard. A user message clears\n  every chain as well.\n- **Only exact repeats.** The key is `[toolName, canonicalArguments]` with a\n  deep key-sort, so argument property order cannot disguise a repeat — and,\n  just as important, a genuinely different call cannot be mistaken for one. A\n  false negative costs one missed escalation; this rule is built so a false\n  positive is structurally hard.\n- **A denial is never counted as a failure.** A denied call still reaches\n  `tools/post-execute` (that is a documented property of the seam, not an\n  accident), so the plugin tags its own refusals; otherwise it would read its own\n  text as the tool's error and escalate against itself.\n- **Chains are per agent.** A parent and its subagent repeating the same call\n  never combine their counts.\n- **Never breaks the pipeline.** The listener is total: any internal error is\n  logged and the call is allowed. A broken guard must not become a broken tool.\n- **Direct `ctx.tools.execute()` callers are never denied** — there is no model\n  to correct and no agent to key on.\n\n## Compatibility\n\nMounts against the published dsh line `0.1.2-rc.1` and the `0.1.3`/`0.1.5`\nlines. It uses only the public `tools/pre-execute`, `tools/post-execute` and\n`agent/pre-step` seam signatures and the `ctx.tools` service.\n\n## Source\n\nDiscussion [#6370](https://github.com/deepseek-ai/deepseek-harness/discussions/6370).\n\n## License\n\nMIT\n","readmeFilename":"README.md"}