Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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,6 +10,12 @@ 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,
) {
companion object {
fun success(message: String, data: String? = null): ToolResult {
Expand All @@ -23,6 +29,11 @@ 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)
}
}

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