fix(toolintent): exempt list_dir from reference-exists + rejection-loop breaker
Greenfield discovery/analyst stages thrashed on plane-2 REFERENCE_EXISTS rejections: the model listed a not-yet-created dir (e.g. frontend/), got hard-BLOCKED every round, and burned MAX_TOOL_ROUNDS without progress. - New ToolCapability.DIRECTORY_LIST; ListDirTool declares it alongside FILE_READ. ReferenceExistsRule now exempts directory enumeration (a listing of an absent dir truthfully reports empty; only hallucinated file *reads* still fail loud). Dispatch stays on capability, not name. - Stage-agnostic rejection-loop breaker in the tool loop: after 3 rounds where every tool call is rejected (BLOCKED/ERROR) with no success, force the model to produce its output. Covers JSON-emitting stages the read-loop breaker (file_written-only) never protected.
This commit is contained in:
+6
-1
@@ -17,11 +17,16 @@ import java.nio.file.Path
|
||||
* hallucinated a filename fails loud and self-corrects (list the directory, fix the name) instead of
|
||||
* proceeding on an empty/absent read. Out-of-workspace targets are [PathContainmentRule]'s concern
|
||||
* (it prompts), so this gate stays silent on them to avoid double-handling.
|
||||
*
|
||||
* Directory-enumeration tools ([ToolCapability.DIRECTORY_LIST]) are exempt: listing a not-yet-created
|
||||
* directory is a legitimate survey move (greenfield scaffolding, e.g. "does frontend/ exist yet?")
|
||||
* and the tool itself truthfully reports absent/empty. Hard-blocking it only makes weak models thrash
|
||||
* on a rejection loop, whereas a hallucinated file *read* still fails loud.
|
||||
*/
|
||||
class ReferenceExistsRule : ToolCallRule {
|
||||
|
||||
override fun appliesTo(capabilities: Set<ToolCapability>): Boolean =
|
||||
ToolCapability.FILE_READ in capabilities
|
||||
ToolCapability.FILE_READ in capabilities && ToolCapability.DIRECTORY_LIST !in capabilities
|
||||
|
||||
override fun assess(input: ToolCallAssessmentInput): ToolCallAssessment {
|
||||
val workspaceReal = input.probe.resolveReal(input.workspace.workspaceRoot)
|
||||
|
||||
@@ -38,6 +38,13 @@ class ReferenceExistsRuleTest {
|
||||
assertFalse(rule.appliesTo(setOf(ToolCapability.FILE_WRITE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `directory-enumeration tools are exempt`() {
|
||||
// list_dir carries FILE_READ + DIRECTORY_LIST; listing a not-yet-created dir is a legitimate
|
||||
// survey move, so the anti-hallucination read gate must not block it.
|
||||
assertFalse(rule.appliesTo(setOf(ToolCapability.FILE_READ, ToolCapability.DIRECTORY_LIST)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reading a non-existent in-workspace path is blocked`() {
|
||||
val r = rule.assess(input("/work/project/src/Ghost.kt", FakeProbe(existing = emptySet())))
|
||||
|
||||
Reference in New Issue
Block a user