Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/AI-Core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ into one document:
| `rules.yml` | `rules` | Priority groups, highest first; each has a `heading` (`CRITICAL`, `IMPORTANT`, `MANDATORY`, `OPTIONAL`) and its `items`. **Adding a rule is adding an item.** |
| `tools.yml` | `tools`, `tool_call_format` | What introduces the tool list, and how to write a call as text (sent only under the text protocol). The list itself is the tools the run offers (`PromptToolCatalog`). |
| `ide_context.yml` | `ide_context`, `session` | One line per fact the IDE can state: open files, module paths. `session` states the device's date and time on every prompt, and that the web tools are there. |
| `agent_loop.yml` | `agent_loop`, `approval` | What the agent is told after each tool batch: the `FAILED:` marker, the truncation notice, what to do next after a success or a failure, the `unfinished` turn sent once when a run that has used tools replies without `respond`, and the `required_tool` turn sent once when a run that had to search first answers without searching. `approval` is what it is told when the user denies a call, asks for a revision, or leaves the dialog unanswered. |
| `agent_loop.yml` | `agent_loop`, `approval` | What the agent is told after each tool batch: the `FAILED:` marker, the truncation notice, what to do next after a success or a failure, the `unfinished` turn sent once when a run that has used tools replies without `respond`, the `unapplied_write` turn sent once when a run tries to finish while a file change it tried failed and was not redone, and the `required_tool` turn sent once when a run that had to search first answers without searching. `approval` is what it is told when the user denies a call, asks for a revision, or leaves the dialog unanswered. |
| `context_files.yml` | `context_files` | The heading over the files the user attached to a message, and the notice inside one cut to the size limit. |
| `chat_title.yml` | `chat_title` | The system prompt of the one-off request that names a chat from its first message, before the agent replies. |
| `web_search.yml` | `web_search` | The system prompt of the one-off request the `web_search` tool makes through the active backend. It may use `CURRENT_TIME`, so "latest" is read as of the device's date. |
Expand Down
9 changes: 9 additions & 0 deletions plugins/AI-Core/src/main/assets/prompts/agent_loop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ agent_loop:
Your reply called no tool, so the run is still open. If every part of the request is now
answered, call the "{{TERMINAL_TOOL}}" tool with a one-line summary — do not repeat what you
already wrote. Otherwise do the next part now.
# Sent once when a run tries to finish while a file change it attempted failed and has not been
# redone: a small model tends to answer as if the change had been made.
unapplied_write: >-
A file change you tried failed and was not made. Other changes to that file may have been
made; do not redo those. Do not tell the user it was done. To redo the failed change: for
edit_file, read the file with read_file and copy the text exactly from it; if create_file said
the file already exists, use update_file; if update_file said the file does not exist, use
create_file; otherwise fix what the error said. If you cannot, call the "{{TERMINAL_TOOL}}"
tool and say plainly that the change was not made.
# Sent once when a run that had to check the web first answered without searching; TOOL is the
# search tool. A backend that forces the call never sends it (see VerificationPolicy).
required_tool: >-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,34 @@ data class ToolResult(
val error_details: String? = null,
/** The user refused the call at the approval dialog, so the tool never ran. */
val denied: Boolean = false,
/**
* The user refused or corrected the call at the approval dialog, so the tool never ran. A call
* left unanswered is a plain failure, since nobody decided against it. True whenever [denied]
* is; only [denied] offers the user a Retry.
*/
val notApproved: Boolean = denied,
/**
* A write that failed because its change is in the file whatever was written to it since: a
* create whose file exists, as no write tool deletes one.
*/
val alreadyMade: Boolean = false,
/**
* A write that failed the way its own repeat fails once its change is made: an edit whose
* old_string is not found. That shows the change made only if nothing has written the file
* since the change worked, which only the agent loop can tell; a first attempt at text that was
* never there fails the same way.
*/
val madeIfUnchanged: Boolean = false,
/**
* The canonical path of the file a write resolved to, however the call spelled it: set by the
* file-writing tools on success and on an [alreadyMade] or [madeIfUnchanged] failure, so a
* repeat is matched to the change it repeats. Null for every other result.
*/
val file: String? = null,
) {
companion object {
fun success(message: String, data: String? = null): ToolResult {
return ToolResult(true, message, data)
fun success(message: String, data: String? = null, file: String? = null): ToolResult {
return ToolResult(true, message, data, file = file)
}

fun failure(message: String, error_details: String? = null): ToolResult {
Expand All @@ -23,6 +47,21 @@ data class ToolResult(
fun denied(message: String): ToolResult {
return ToolResult(false, message, denied = true)
}

/** A call the user corrected at the approval dialog. */
fun notApproved(message: String): ToolResult {
return ToolResult(false, message, notApproved = true)
}

/** A write to [file] that failed because its change is there; see [alreadyMade]. */
fun alreadyMade(message: String, file: String): ToolResult {
return ToolResult(false, message, alreadyMade = true, file = file)
}

/** A write to [file] that failed as its repeat would once made; see [madeIfUnchanged]. */
fun madeIfUnchanged(message: String, file: String): ToolResult {
return ToolResult(false, message, madeIfUnchanged = true, file = file)
}
}

fun toResultMap(): Map<String, Any> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class ToolResultsPrompt(
/** The turn sent after a reply that ran tools and ended without the terminal tool. */
suspend fun unfinished(): String = renderUnfinished(config.config(), terminalTool)

/** The turn sent after a reply that tried to finish while a failed file change was not redone. */
suspend fun unappliedWrite(): String = renderUnappliedWrite(config.config(), terminalTool)

/** The turn sent after a reply that answered before calling [tool], which the run had to call first. */
suspend fun requiredTool(tool: String): String = renderRequiredTool(config.config(), terminalTool, tool)

Expand Down Expand Up @@ -93,6 +96,19 @@ class ToolResultsPrompt(
mapOf(PromptVariables.TERMINAL_TOOL to terminalTool),
)

/**
* Renders the turn that tells a run its last attempt to change a file failed.
*
* @param config the loaded prompt config.
* @param terminalTool the name of the tool that ends a run by answering the user.
* @return the turn to add to the transcript.
*/
fun renderUnappliedWrite(config: AgentPromptConfig, terminalTool: String): String =
PromptTemplateEngine.render(
config.agentLoop.unappliedWrite,
mapOf(PromptVariables.TERMINAL_TOOL to terminalTool),
)

/**
* Renders the turn that asks a run which answered too early to call [tool] first.
*
Expand All @@ -117,6 +133,7 @@ class ToolResultsPrompt(
val renders: List<() -> String> = CHECK_BATCHES.map { (calls, results) ->
{ render(config, CHECK_TERMINAL_TOOL, CHECK_CHAR_LIMIT, calls, results) }
} + { renderUnfinished(config, CHECK_TERMINAL_TOOL) } +
{ renderUnappliedWrite(config, CHECK_TERMINAL_TOOL) } +
{ renderRequiredTool(config, CHECK_TERMINAL_TOOL, CHECK_REQUIRED_TOOL) }
return renders.mapNotNull { check ->
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ data class AgentPromptConfig(
* @property afterFailure what to do next otherwise.
* @property unfinished the turn after a reply that ran tools and then ended without the
* terminal tool.
* @property unappliedWrite the turn after a reply that tried to finish while a file change had
* failed and no later write to that file had succeeded.
* @property requiredTool the turn after a reply that answered before calling the tool the run
* had to call first, such as a web search before a code review.
*/
Expand All @@ -121,6 +123,7 @@ data class AgentPromptConfig(
val afterSuccess: PromptText,
val afterFailure: PromptText,
val unfinished: PromptText,
val unappliedWrite: PromptText,
val requiredTool: PromptText,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ object AgentPromptConfigParser : PromptConfigParser<AgentPromptConfig> {
afterSuccess = text("after_success"),
afterFailure = text("after_failure"),
unfinished = text("unfinished"),
unappliedWrite = text("unapplied_write"),
requiredTool = text("required_tool"),
)
},
Expand Down
Loading