Compare commits
13 Commits
f41c85b703
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
6c17462040
|
|||
|
1536cf384c
|
|||
|
d6842d7e29
|
|||
|
fbc0acda2a
|
|||
|
0327d041b6
|
|||
|
6a01fd4fbd
|
|||
| d822180205 | |||
|
89d0fdce26
|
|||
|
b3ecdce979
|
|||
|
3873821a31
|
|||
|
9c2801b643
|
|||
|
d78820dcd4
|
|||
|
d43c4232a2
|
@@ -2,68 +2,6 @@
|
|||||||
# Shared Agent Utilities - Minimal, focused helper functions
|
# Shared Agent Utilities - Minimal, focused helper functions
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
#############################
|
|
||||||
## CONTEXT FILE MANAGEMENT ##
|
|
||||||
#############################
|
|
||||||
|
|
||||||
get_context_file() {
|
|
||||||
local project_dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
|
||||||
echo "${project_dir}/.loki-context"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Initialize context file for a new task
|
|
||||||
# Usage: init_context "Task description"
|
|
||||||
init_context() {
|
|
||||||
local task="$1"
|
|
||||||
local project_dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
|
||||||
local context_file
|
|
||||||
context_file=$(get_context_file)
|
|
||||||
|
|
||||||
cat > "${context_file}" <<EOF
|
|
||||||
## Project: ${project_dir}
|
|
||||||
## Task: ${task}
|
|
||||||
## Started: $(date -Iseconds)
|
|
||||||
|
|
||||||
### Prior Findings
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Append findings to the context file
|
|
||||||
# Usage: append_context "agent_name" "finding summary
|
|
||||||
append_context() {
|
|
||||||
local agent="$1"
|
|
||||||
local finding="$2"
|
|
||||||
local context_file
|
|
||||||
context_file=$(get_context_file)
|
|
||||||
|
|
||||||
if [[ -f "${context_file}" ]]; then
|
|
||||||
{
|
|
||||||
echo ""
|
|
||||||
echo "[${agent}]:"
|
|
||||||
echo "${finding}"
|
|
||||||
} >> "${context_file}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Read the current context (returns empty string if no context)
|
|
||||||
# Usage: context=$(read_context)
|
|
||||||
read_context() {
|
|
||||||
local context_file
|
|
||||||
context_file=$(get_context_file)
|
|
||||||
|
|
||||||
if [[ -f "${context_file}" ]]; then
|
|
||||||
cat "${context_file}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clear the context file
|
|
||||||
clear_context() {
|
|
||||||
local context_file
|
|
||||||
context_file=$(get_context_file)
|
|
||||||
rm -f "${context_file}"
|
|
||||||
}
|
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
## PROJECT DETECTION ##
|
## PROJECT DETECTION ##
|
||||||
#######################
|
#######################
|
||||||
@@ -279,9 +217,9 @@ _detect_with_llm() {
|
|||||||
evidence=$(_gather_project_evidence "${dir}")
|
evidence=$(_gather_project_evidence "${dir}")
|
||||||
local prompt
|
local prompt
|
||||||
prompt=$(cat <<-EOF
|
prompt=$(cat <<-EOF
|
||||||
|
|
||||||
Analyze this project directory and determine the project type, primary language, and the correct shell commands to build, test, and check (lint/typecheck) it.
|
Analyze this project directory and determine the project type, primary language, and the correct shell commands to build, test, and check (lint/typecheck) it.
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
prompt+=$'\n'"${evidence}"$'\n'
|
prompt+=$'\n'"${evidence}"$'\n'
|
||||||
@@ -348,77 +286,11 @@ detect_project() {
|
|||||||
echo '{"type":"unknown","build":"","test":"","check":""}'
|
echo '{"type":"unknown","build":"","test":"","check":""}'
|
||||||
}
|
}
|
||||||
|
|
||||||
######################
|
|
||||||
## AGENT INVOCATION ##
|
|
||||||
######################
|
|
||||||
|
|
||||||
# Invoke a subagent with optional context injection
|
|
||||||
# Usage: invoke_agent <agent_name> <prompt> [extra_args...]
|
|
||||||
invoke_agent() {
|
|
||||||
local agent="$1"
|
|
||||||
local prompt="$2"
|
|
||||||
shift 2
|
|
||||||
|
|
||||||
local context
|
|
||||||
context=$(read_context)
|
|
||||||
|
|
||||||
local full_prompt
|
|
||||||
if [[ -n "${context}" ]]; then
|
|
||||||
full_prompt="## Orchestrator Context
|
|
||||||
|
|
||||||
The orchestrator (sisyphus) has gathered this context from prior work:
|
|
||||||
|
|
||||||
<context>
|
|
||||||
${context}
|
|
||||||
</context>
|
|
||||||
|
|
||||||
## Your Task
|
|
||||||
|
|
||||||
${prompt}"
|
|
||||||
else
|
|
||||||
full_prompt="${prompt}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
env AUTO_CONFIRM=true loki --agent "${agent}" "$@" "${full_prompt}" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Invoke a subagent and capture a summary of its findings
|
|
||||||
# Usage: result=$(invoke_agent_with_summary "explore" "find auth patterns")
|
|
||||||
invoke_agent_with_summary() {
|
|
||||||
local agent="$1"
|
|
||||||
local prompt="$2"
|
|
||||||
shift 2
|
|
||||||
|
|
||||||
local output
|
|
||||||
output=$(invoke_agent "${agent}" "${prompt}" "$@")
|
|
||||||
|
|
||||||
local summary=""
|
|
||||||
|
|
||||||
if echo "${output}" | grep -q "FINDINGS:"; then
|
|
||||||
summary=$(echo "${output}" | sed -n '/FINDINGS:/,/^[A-Z_]*COMPLETE/p' | grep "^- " | sed 's/^- / - /')
|
|
||||||
elif echo "${output}" | grep -q "CODER_COMPLETE:"; then
|
|
||||||
summary=$(echo "${output}" | grep "CODER_COMPLETE:" | sed 's/CODER_COMPLETE: *//')
|
|
||||||
elif echo "${output}" | grep -q "ORACLE_COMPLETE"; then
|
|
||||||
summary=$(echo "${output}" | sed -n '/^## Recommendation/,/^## /{/^## Recommendation/d;/^## /d;p}' | sed '/^$/d' | head -10)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Failsafe: extract up to 5 meaningful lines if no markers found
|
|
||||||
if [[ -z "${summary}" ]]; then
|
|
||||||
summary=$(echo "${output}" | grep -v "^$" | grep -v "^#" | grep -v "^\-\-\-" | tail -10 | head -5)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${summary}" ]]; then
|
|
||||||
append_context "${agent}" "${summary}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "${output}"
|
|
||||||
}
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
## FILE SEARCH UTILITIES ##
|
## FILE SEARCH UTILITIES ##
|
||||||
###########################
|
###########################
|
||||||
|
|
||||||
search_files() {
|
_search_files() {
|
||||||
local pattern="$1"
|
local pattern="$1"
|
||||||
local dir="${2:-.}"
|
local dir="${2:-.}"
|
||||||
|
|
||||||
|
|||||||
@@ -122,3 +122,6 @@ instructions: |
|
|||||||
- Project: {{project_dir}}
|
- Project: {{project_dir}}
|
||||||
- CWD: {{__cwd__}}
|
- CWD: {{__cwd__}}
|
||||||
- Shell: {{__shell__}}
|
- Shell: {{__shell__}}
|
||||||
|
|
||||||
|
## Available Tools:
|
||||||
|
{{__tools__}}
|
||||||
|
|||||||
@@ -29,11 +29,30 @@ instructions: |
|
|||||||
## Your Mission
|
## Your Mission
|
||||||
|
|
||||||
Given an implementation task:
|
Given an implementation task:
|
||||||
1. Understand what to build (from context provided)
|
1. Check for orchestrator context first (see below)
|
||||||
2. Study existing patterns (read 1-2 similar files)
|
2. Fill gaps only. Read files NOT already covered in context
|
||||||
3. Write the code (using tools, NOT chat output)
|
3. Write the code (using tools, NOT chat output)
|
||||||
4. Verify it compiles/builds
|
4. Verify it compiles/builds
|
||||||
5. Signal completion
|
5. Signal completion with a summary
|
||||||
|
|
||||||
|
## Using Orchestrator Context (IMPORTANT)
|
||||||
|
|
||||||
|
When spawned by sisyphus, your prompt will often contain a `<context>` block
|
||||||
|
with prior findings: file paths, code patterns, and conventions discovered by
|
||||||
|
explore agents.
|
||||||
|
|
||||||
|
**If context is provided:**
|
||||||
|
1. Use it as your primary reference. Don't re-read files already summarized
|
||||||
|
2. Follow the code patterns shown. Snippets in context ARE the style guide
|
||||||
|
3. Read the referenced files ONLY IF you need more detail (e.g. full function
|
||||||
|
signature, import list, or adjacent code not included in the snippet)
|
||||||
|
4. If context includes a "Conventions" section, follow it exactly
|
||||||
|
|
||||||
|
**If context is NOT provided or is too vague to act on:**
|
||||||
|
Fall back to self-exploration: grep for similar files, read 1-2 examples,
|
||||||
|
match their style.
|
||||||
|
|
||||||
|
**Never ignore provided context.** It represents work already done upstream.
|
||||||
|
|
||||||
## Todo System
|
## Todo System
|
||||||
|
|
||||||
@@ -82,12 +101,13 @@ instructions: |
|
|||||||
|
|
||||||
## Completion Signal
|
## Completion Signal
|
||||||
|
|
||||||
End with:
|
When done, end your response with a summary so the parent agent knows what happened:
|
||||||
|
|
||||||
```
|
```
|
||||||
CODER_COMPLETE: [summary of what was implemented]
|
CODER_COMPLETE: [summary of what was implemented, which files were created/modified, and build status]
|
||||||
```
|
```
|
||||||
|
|
||||||
Or if failed:
|
Or if something went wrong:
|
||||||
```
|
```
|
||||||
CODER_FAILED: [what went wrong]
|
CODER_FAILED: [what went wrong]
|
||||||
```
|
```
|
||||||
@@ -104,4 +124,6 @@ instructions: |
|
|||||||
- Project: {{project_dir}}
|
- Project: {{project_dir}}
|
||||||
- CWD: {{__cwd__}}
|
- CWD: {{__cwd__}}
|
||||||
- Shell: {{__shell__}}
|
- Shell: {{__shell__}}
|
||||||
|
|
||||||
|
## Available tools:
|
||||||
|
{{__tools__}}
|
||||||
@@ -8,12 +8,13 @@ variables:
|
|||||||
description: Project directory to explore
|
description: Project directory to explore
|
||||||
default: '.'
|
default: '.'
|
||||||
|
|
||||||
|
mcp_servers:
|
||||||
|
- ddg-search
|
||||||
global_tools:
|
global_tools:
|
||||||
- fs_read.sh
|
- fs_read.sh
|
||||||
- fs_grep.sh
|
- fs_grep.sh
|
||||||
- fs_glob.sh
|
- fs_glob.sh
|
||||||
- fs_ls.sh
|
- fs_ls.sh
|
||||||
- web_search_loki.sh
|
|
||||||
|
|
||||||
instructions: |
|
instructions: |
|
||||||
You are a codebase explorer. Your job: Search, find, report. Nothing else.
|
You are a codebase explorer. Your job: Search, find, report. Nothing else.
|
||||||
@@ -67,6 +68,9 @@ instructions: |
|
|||||||
## Context
|
## Context
|
||||||
- Project: {{project_dir}}
|
- Project: {{project_dir}}
|
||||||
- CWD: {{__cwd__}}
|
- CWD: {{__cwd__}}
|
||||||
|
|
||||||
|
## Available Tools:
|
||||||
|
{{__tools__}}
|
||||||
|
|
||||||
conversation_starters:
|
conversation_starters:
|
||||||
- 'Find how authentication is implemented'
|
- 'Find how authentication is implemented'
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ search_files() {
|
|||||||
echo "" >> "$LLM_OUTPUT"
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
local results
|
local results
|
||||||
results=$(search_files "${pattern}" "${project_dir}")
|
results=$(_search_files "${pattern}" "${project_dir}")
|
||||||
|
|
||||||
if [[ -n "${results}" ]]; then
|
if [[ -n "${results}" ]]; then
|
||||||
echo "${results}" >> "$LLM_OUTPUT"
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
|||||||
@@ -108,3 +108,6 @@ instructions: |
|
|||||||
## Context
|
## Context
|
||||||
- Project: {{project_dir}}
|
- Project: {{project_dir}}
|
||||||
- CWD: {{__cwd__}}
|
- CWD: {{__cwd__}}
|
||||||
|
|
||||||
|
## Available Tools:
|
||||||
|
{{__tools__}}
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ variables:
|
|||||||
description: Project directory for context
|
description: Project directory for context
|
||||||
default: '.'
|
default: '.'
|
||||||
|
|
||||||
|
mcp_servers:
|
||||||
|
- ddg-search
|
||||||
global_tools:
|
global_tools:
|
||||||
- fs_read.sh
|
- fs_read.sh
|
||||||
- fs_grep.sh
|
- fs_grep.sh
|
||||||
- fs_glob.sh
|
- fs_glob.sh
|
||||||
- fs_ls.sh
|
- fs_ls.sh
|
||||||
- web_search_loki.sh
|
|
||||||
|
|
||||||
instructions: |
|
instructions: |
|
||||||
You are Oracle - a senior architect and debugger consulted for complex decisions.
|
You are Oracle - a senior architect and debugger consulted for complex decisions.
|
||||||
@@ -74,6 +75,9 @@ instructions: |
|
|||||||
## Context
|
## Context
|
||||||
- Project: {{project_dir}}
|
- Project: {{project_dir}}
|
||||||
- CWD: {{__cwd__}}
|
- CWD: {{__cwd__}}
|
||||||
|
|
||||||
|
## Available Tools:
|
||||||
|
{{__tools__}}
|
||||||
|
|
||||||
conversation_starters:
|
conversation_starters:
|
||||||
- 'Review this architecture design'
|
- 'Review this architecture design'
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ can_spawn_agents: true
|
|||||||
max_concurrent_agents: 4
|
max_concurrent_agents: 4
|
||||||
max_agent_depth: 3
|
max_agent_depth: 3
|
||||||
inject_spawn_instructions: true
|
inject_spawn_instructions: true
|
||||||
summarization_threshold: 4000
|
summarization_threshold: 8000
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
- name: project_dir
|
- name: project_dir
|
||||||
@@ -22,12 +22,13 @@ variables:
|
|||||||
description: Auto-confirm command execution
|
description: Auto-confirm command execution
|
||||||
default: '1'
|
default: '1'
|
||||||
|
|
||||||
|
mcp_servers:
|
||||||
|
- ddg-search
|
||||||
global_tools:
|
global_tools:
|
||||||
- fs_read.sh
|
- fs_read.sh
|
||||||
- fs_grep.sh
|
- fs_grep.sh
|
||||||
- fs_glob.sh
|
- fs_glob.sh
|
||||||
- fs_ls.sh
|
- fs_ls.sh
|
||||||
- web_search_loki.sh
|
|
||||||
- execute_command.sh
|
- execute_command.sh
|
||||||
|
|
||||||
instructions: |
|
instructions: |
|
||||||
@@ -69,6 +70,45 @@ instructions: |
|
|||||||
| coder | Write/edit files, implement features | Creates/modifies files, runs builds |
|
| coder | Write/edit files, implement features | Creates/modifies files, runs builds |
|
||||||
| oracle | Architecture decisions, complex debugging | Advisory, high-quality reasoning |
|
| oracle | Architecture decisions, complex debugging | Advisory, high-quality reasoning |
|
||||||
|
|
||||||
|
## Coder Delegation Format (MANDATORY)
|
||||||
|
|
||||||
|
When spawning the `coder` agent, your prompt MUST include these sections.
|
||||||
|
The coder has NOT seen the codebase. Your prompt IS its entire context.
|
||||||
|
|
||||||
|
### Template:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Goal
|
||||||
|
[1-2 sentences: what to build/modify and where]
|
||||||
|
|
||||||
|
## Reference Files
|
||||||
|
[Files that explore found, with what each demonstrates]
|
||||||
|
- `path/to/file.ext` - what pattern this file shows
|
||||||
|
- `path/to/other.ext` - what convention this file shows
|
||||||
|
|
||||||
|
## Code Patterns to Follow
|
||||||
|
[Paste ACTUAL code snippets from explore results, not descriptions]
|
||||||
|
<code>
|
||||||
|
// From path/to/file.ext - this is the pattern to follow:
|
||||||
|
[actual code explore found, 5-20 lines]
|
||||||
|
</code>
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
[Naming, imports, error handling, file organization]
|
||||||
|
- Convention 1
|
||||||
|
- Convention 2
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
[What NOT to do, scope boundaries]
|
||||||
|
- Do NOT modify X
|
||||||
|
- Only touch files in Y/
|
||||||
|
```
|
||||||
|
|
||||||
|
**CRITICAL**: Include actual code snippets, not just file paths.
|
||||||
|
If explore returned code patterns, paste them into the coder prompt.
|
||||||
|
Vague prompts like "follow existing patterns" waste coder's tokens on
|
||||||
|
re-exploration that you already did.
|
||||||
|
|
||||||
## Workflow Examples
|
## Workflow Examples
|
||||||
|
|
||||||
### Example 1: Implementation task (explore -> coder, parallel exploration)
|
### Example 1: Implementation task (explore -> coder, parallel exploration)
|
||||||
@@ -80,12 +120,12 @@ instructions: |
|
|||||||
2. todo__add --task "Explore existing API patterns"
|
2. todo__add --task "Explore existing API patterns"
|
||||||
3. todo__add --task "Implement profile endpoint"
|
3. todo__add --task "Implement profile endpoint"
|
||||||
4. todo__add --task "Verify with build/test"
|
4. todo__add --task "Verify with build/test"
|
||||||
5. agent__spawn --agent explore --prompt "Find existing API endpoint patterns, route structures, and controller conventions"
|
5. agent__spawn --agent explore --prompt "Find existing API endpoint patterns, route structures, and controller conventions. Include code snippets."
|
||||||
6. agent__spawn --agent explore --prompt "Find existing data models and database query patterns"
|
6. agent__spawn --agent explore --prompt "Find existing data models and database query patterns. Include code snippets."
|
||||||
7. agent__collect --id <id1>
|
7. agent__collect --id <id1>
|
||||||
8. agent__collect --id <id2>
|
8. agent__collect --id <id2>
|
||||||
9. todo__done --id 1
|
9. todo__done --id 1
|
||||||
10. agent__spawn --agent coder --prompt "Create user profiles endpoint following existing patterns. [Include context from explore results]"
|
10. agent__spawn --agent coder --prompt "<structured prompt using Coder Delegation Format above, including code snippets from explore results>"
|
||||||
11. agent__collect --id <coder_id>
|
11. agent__collect --id <coder_id>
|
||||||
12. todo__done --id 2
|
12. todo__done --id 2
|
||||||
13. run_build
|
13. run_build
|
||||||
@@ -134,7 +174,6 @@ instructions: |
|
|||||||
|
|
||||||
## When to Do It Yourself
|
## When to Do It Yourself
|
||||||
|
|
||||||
- Single-file reads/writes
|
|
||||||
- Simple command execution
|
- Simple command execution
|
||||||
- Trivial changes (typos, renames)
|
- Trivial changes (typos, renames)
|
||||||
- Quick file searches
|
- Quick file searches
|
||||||
|
|||||||
+2
-1
@@ -467,11 +467,12 @@ inject_todo_instructions: true # Include the default todo instructions into pr
|
|||||||
|
|
||||||
### How It Works
|
### How It Works
|
||||||
|
|
||||||
1. When `inject_todo_instructions` is enabled, agents receive instructions on using four built-in tools:
|
1. When `inject_todo_instructions` is enabled, agents receive instructions on using five built-in tools:
|
||||||
- `todo__init`: Initialize a todo list with a goal
|
- `todo__init`: Initialize a todo list with a goal
|
||||||
- `todo__add`: Add a task to the list
|
- `todo__add`: Add a task to the list
|
||||||
- `todo__done`: Mark a task complete
|
- `todo__done`: Mark a task complete
|
||||||
- `todo__list`: View current todo state
|
- `todo__list`: View current todo state
|
||||||
|
- `todo__clear`: Clear the entire todo list and reset the goal
|
||||||
|
|
||||||
These instructions are a reasonable default that detail how to use Loki's To-Do System. If you wish,
|
These instructions are a reasonable default that detail how to use Loki's To-Do System. If you wish,
|
||||||
you can disable the injection of the default instructions and specify your own instructions for how
|
you can disable the injection of the default instructions and specify your own instructions for how
|
||||||
|
|||||||
+8
-7
@@ -120,13 +120,14 @@ For more information on sessions and how to use them in Loki, refer to the [sess
|
|||||||
Loki lets you build OpenAI GPT-style agents. The following commands let you interact with and manage your agents in
|
Loki lets you build OpenAI GPT-style agents. The following commands let you interact with and manage your agents in
|
||||||
Loki:
|
Loki:
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|----------------------|------------------------------------------------------------|
|
|----------------------|-----------------------------------------------------------------------------------------------|
|
||||||
| `.agent` | Use an agent |
|
| `.agent` | Use an agent |
|
||||||
| `.starter` | Display and use conversation starters for the active agent |
|
| `.starter` | Display and use conversation starters for the active agent |
|
||||||
| `.edit agent-config` | Open the agent configuration in your preferred text editor |
|
| `.clear todo` | Clear the todo list and stop auto-continuation (requires `auto_continue: true` on the agent) |
|
||||||
| `.info agent` | Display information about the active agent |
|
| `.edit agent-config` | Open the agent configuration in your preferred text editor |
|
||||||
| `.exit agent` | Leave the active agent |
|
| `.info agent` | Display information about the active agent |
|
||||||
|
| `.exit agent` | Leave the active agent |
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,22 @@ Display the current todo list with status of each item.
|
|||||||
|
|
||||||
**Returns:** The full todo list with goal, progress, and item statuses
|
**Returns:** The full todo list with goal, progress, and item statuses
|
||||||
|
|
||||||
|
### `todo__clear`
|
||||||
|
Clear the entire todo list and reset the goal. Use when the current task has been canceled or invalidated.
|
||||||
|
|
||||||
|
**Parameters:** None
|
||||||
|
|
||||||
|
**Returns:** Confirmation that the todo list was cleared
|
||||||
|
|
||||||
|
### REPL Command: `.clear todo`
|
||||||
|
You can also clear the todo list manually from the REPL by typing `.clear todo`. This is useful when:
|
||||||
|
- You gave a custom response that changes or cancels the current task
|
||||||
|
- The agent is stuck in auto-continuation with stale todos
|
||||||
|
- You want to start fresh without leaving and re-entering the agent
|
||||||
|
|
||||||
|
**Note:** This command is only available when an agent with `auto_continue: true` is active. If the todo
|
||||||
|
system isn't enabled for the current agent, the command will display an error message.
|
||||||
|
|
||||||
## Auto-Continuation
|
## Auto-Continuation
|
||||||
When `auto_continue` is enabled, Loki automatically sends a continuation prompt if:
|
When `auto_continue` is enabled, Loki automatically sends a continuation prompt if:
|
||||||
|
|
||||||
|
|||||||
+40
-1
@@ -11,6 +11,7 @@ use serde::Deserialize;
|
|||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
const API_BASE: &str = "https://api.anthropic.com/v1";
|
const API_BASE: &str = "https://api.anthropic.com/v1";
|
||||||
|
const CLAUDE_CODE_PREFIX: &str = "You are Claude Code, Anthropic's official CLI for Claude.";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct ClaudeConfig {
|
pub struct ClaudeConfig {
|
||||||
@@ -84,7 +85,7 @@ async fn prepare_chat_completions(
|
|||||||
let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?;
|
let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?;
|
||||||
if !ready {
|
if !ready {
|
||||||
bail!(
|
bail!(
|
||||||
"OAuth configured but no tokens found for '{}'. Run: loki --authenticate {}",
|
"OAuth configured but no tokens found for '{}'. Run: 'loki --authenticate {}' or '.authenticate' in the REPL",
|
||||||
self_.name(),
|
self_.name(),
|
||||||
self_.name()
|
self_.name()
|
||||||
);
|
);
|
||||||
@@ -94,6 +95,7 @@ async fn prepare_chat_completions(
|
|||||||
for (key, value) in provider.extra_request_headers() {
|
for (key, value) in provider.extra_request_headers() {
|
||||||
request_data.header(key, value);
|
request_data.header(key, value);
|
||||||
}
|
}
|
||||||
|
inject_oauth_system_prompt(&mut request_data.body);
|
||||||
} else if let Ok(api_key) = self_.get_api_key() {
|
} else if let Ok(api_key) = self_.get_api_key() {
|
||||||
request_data.header("x-api-key", api_key);
|
request_data.header("x-api-key", api_key);
|
||||||
} else {
|
} else {
|
||||||
@@ -107,6 +109,43 @@ async fn prepare_chat_completions(
|
|||||||
Ok(request_data)
|
Ok(request_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Anthropic requires OAuth-authenticated requests to include a Claude Code
|
||||||
|
/// system prompt prefix in order to consider a request body as "valid".
|
||||||
|
///
|
||||||
|
/// This behavior was discovered 2026-03-17.
|
||||||
|
///
|
||||||
|
/// So this function injects the Claude Code system prompt into the request
|
||||||
|
/// body to make it a valid request.
|
||||||
|
fn inject_oauth_system_prompt(body: &mut Value) {
|
||||||
|
let prefix_block = json!({
|
||||||
|
"type": "text",
|
||||||
|
"text": CLAUDE_CODE_PREFIX,
|
||||||
|
});
|
||||||
|
|
||||||
|
match body.get("system") {
|
||||||
|
Some(Value::String(existing)) => {
|
||||||
|
let existing_block = json!({
|
||||||
|
"type": "text",
|
||||||
|
"text": existing,
|
||||||
|
});
|
||||||
|
body["system"] = json!([prefix_block, existing_block]);
|
||||||
|
}
|
||||||
|
Some(Value::Array(_)) => {
|
||||||
|
if let Some(arr) = body["system"].as_array_mut() {
|
||||||
|
let already_injected = arr
|
||||||
|
.iter()
|
||||||
|
.any(|block| block["text"].as_str() == Some(CLAUDE_CODE_PREFIX));
|
||||||
|
if !already_injected {
|
||||||
|
arr.insert(0, prefix_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
body["system"] = json!([prefix_block]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn claude_chat_completions(
|
pub async fn claude_chat_completions(
|
||||||
builder: RequestBuilder,
|
builder: RequestBuilder,
|
||||||
_model: &Model,
|
_model: &Model,
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ async fn prepare_chat_completions(
|
|||||||
let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?;
|
let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?;
|
||||||
if !ready {
|
if !ready {
|
||||||
bail!(
|
bail!(
|
||||||
"OAuth configured but no tokens found for '{}'. Run: loki --authenticate {}",
|
"OAuth configured but no tokens found for '{}'. Run: 'loki --authenticate {}' or '.authenticate' in the REPL",
|
||||||
self_.name(),
|
self_.name(),
|
||||||
self_.name()
|
self_.name()
|
||||||
);
|
);
|
||||||
@@ -181,7 +181,7 @@ async fn prepare_embeddings(
|
|||||||
let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?;
|
let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?;
|
||||||
if !ready {
|
if !ready {
|
||||||
bail!(
|
bail!(
|
||||||
"OAuth configured but no tokens found for '{}'. Run: loki --authenticate {}",
|
"OAuth configured but no tokens found for '{}'. Run: 'loki --authenticate {}' or '.authenticate' in the REPL",
|
||||||
self_.name(),
|
self_.name(),
|
||||||
self_.name()
|
self_.name()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -177,6 +177,10 @@ impl Model {
|
|||||||
self.data.max_output_tokens
|
self.data.max_output_tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn supports_function_calling(&self) -> bool {
|
||||||
|
self.data.supports_function_calling
|
||||||
|
}
|
||||||
|
|
||||||
pub fn no_stream(&self) -> bool {
|
pub fn no_stream(&self) -> bool {
|
||||||
self.data.no_stream
|
self.data.no_stream
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -476,6 +476,11 @@ impl Agent {
|
|||||||
self.todo_list.mark_done(id)
|
self.todo_list.mark_done(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_todo_list(&mut self) {
|
||||||
|
self.todo_list.clear();
|
||||||
|
self.reset_continuation();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn continuation_prompt(&self) -> String {
|
pub fn continuation_prompt(&self) -> String {
|
||||||
self.config.continuation_prompt.clone().unwrap_or_else(|| {
|
self.config.continuation_prompt.clone().unwrap_or_else(|| {
|
||||||
formatdoc! {"
|
formatdoc! {"
|
||||||
|
|||||||
+10
-5
@@ -239,12 +239,17 @@ impl Input {
|
|||||||
patch_messages(&mut messages, model);
|
patch_messages(&mut messages, model);
|
||||||
model.guard_max_input_tokens(&messages)?;
|
model.guard_max_input_tokens(&messages)?;
|
||||||
let (temperature, top_p) = (self.role().temperature(), self.role().top_p());
|
let (temperature, top_p) = (self.role().temperature(), self.role().top_p());
|
||||||
let functions = self.config.read().select_functions(self.role());
|
let functions = if model.supports_function_calling() {
|
||||||
if let Some(vec) = &functions {
|
let fns = self.config.read().select_functions(self.role());
|
||||||
for def in vec {
|
if let Some(vec) = &fns {
|
||||||
debug!("Function definition: {:?}", def.name);
|
for def in vec {
|
||||||
|
debug!("Function definition: {:?}", def.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
fns
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
Ok(ChatCompletionsData {
|
Ok(ChatCompletionsData {
|
||||||
messages,
|
messages,
|
||||||
temperature,
|
temperature,
|
||||||
|
|||||||
@@ -1842,6 +1842,12 @@ impl Config {
|
|||||||
bail!("Already in an agent, please run '.exit agent' first to exit the current agent.");
|
bail!("Already in an agent, please run '.exit agent' first to exit the current agent.");
|
||||||
}
|
}
|
||||||
let agent = Agent::init(config, agent_name, abort_signal.clone()).await?;
|
let agent = Agent::init(config, agent_name, abort_signal.clone()).await?;
|
||||||
|
if !agent.model().supports_function_calling() {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: The model '{}' does not support function calling. Agent tools (including todo, spawning, and user interaction) will not be available.",
|
||||||
|
agent.model().id()
|
||||||
|
);
|
||||||
|
}
|
||||||
let session = session_name.map(|v| v.to_string()).or_else(|| {
|
let session = session_name.map(|v| v.to_string()).or_else(|| {
|
||||||
if config.read().macro_flag {
|
if config.read().macro_flag {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ pub(in crate::config) const DEFAULT_TODO_INSTRUCTIONS: &str = indoc! {"
|
|||||||
- `todo__add`: Add individual tasks. Add all planned steps before starting work.
|
- `todo__add`: Add individual tasks. Add all planned steps before starting work.
|
||||||
- `todo__done`: Mark a task done by id. Call this immediately after completing each step.
|
- `todo__done`: Mark a task done by id. Call this immediately after completing each step.
|
||||||
- `todo__list`: Show the current todo list.
|
- `todo__list`: Show the current todo list.
|
||||||
|
- `todo__clear`: Clear the entire todo list and reset the goal. Use when the user cancels or changes direction.
|
||||||
|
|
||||||
RULES:
|
RULES:
|
||||||
- Always create a todo list before starting work.
|
- Always create a todo list before starting work.
|
||||||
- Mark each task done as soon as you finish it; do not batch.
|
- Mark each task done as soon as you finish it; do not batch.
|
||||||
|
- If the user cancels the current task or changes direction, call `todo__clear` immediately.
|
||||||
- If you stop with incomplete tasks, the system will automatically prompt you to continue."
|
- If you stop with incomplete tasks, the system will automatically prompt you to continue."
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ impl TodoList {
|
|||||||
self.todos.is_empty()
|
self.todos.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.goal.clear();
|
||||||
|
self.todos.clear();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_for_model(&self) -> String {
|
pub fn render_for_model(&self) -> String {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
if !self.goal.is_empty() {
|
if !self.goal.is_empty() {
|
||||||
@@ -149,6 +154,21 @@ mod tests {
|
|||||||
assert!(rendered.contains("○ 2. Map"));
|
assert!(rendered.contains("○ 2. Map"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear() {
|
||||||
|
let mut list = TodoList::new("Some goal");
|
||||||
|
list.add("Task 1");
|
||||||
|
list.add("Task 2");
|
||||||
|
list.mark_done(1);
|
||||||
|
assert!(!list.is_empty());
|
||||||
|
|
||||||
|
list.clear();
|
||||||
|
assert!(list.is_empty());
|
||||||
|
assert!(list.goal.is_empty());
|
||||||
|
assert_eq!(list.todos.len(), 0);
|
||||||
|
assert!(!list.has_incomplete());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialization_roundtrip() {
|
fn test_serialization_roundtrip() {
|
||||||
let mut list = TodoList::new("Roundtrip");
|
let mut list = TodoList::new("Roundtrip");
|
||||||
|
|||||||
@@ -76,6 +76,16 @@ pub fn todo_function_declarations() -> Vec<FunctionDeclaration> {
|
|||||||
},
|
},
|
||||||
agent: false,
|
agent: false,
|
||||||
},
|
},
|
||||||
|
FunctionDeclaration {
|
||||||
|
name: format!("{TODO_FUNCTION_PREFIX}clear"),
|
||||||
|
description: "Clear the entire todo list and reset the goal. Use when the current task has been canceled or invalidated.".to_string(),
|
||||||
|
parameters: JsonSchema {
|
||||||
|
type_value: Some("object".to_string()),
|
||||||
|
properties: Some(IndexMap::new()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
agent: false,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +166,17 @@ pub fn handle_todo_tool(config: &GlobalConfig, cmd_name: &str, args: &Value) ->
|
|||||||
None => bail!("No active agent"),
|
None => bail!("No active agent"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"clear" => {
|
||||||
|
let mut cfg = config.write();
|
||||||
|
let agent = cfg.agent.as_mut();
|
||||||
|
match agent {
|
||||||
|
Some(agent) => {
|
||||||
|
agent.clear_todo_list();
|
||||||
|
Ok(json!({"status": "ok", "message": "Todo list cleared"}))
|
||||||
|
}
|
||||||
|
None => bail!("No active agent"),
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => bail!("Unknown todo action: {action}"),
|
_ => bail!("Unknown todo action: {action}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ fn handle_direct_input(args: &Value) -> Result<Value> {
|
|||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.ok_or_else(|| anyhow!("'question' is required"))?;
|
.ok_or_else(|| anyhow!("'question' is required"))?;
|
||||||
|
|
||||||
let answer = Text::new(question).prompt()?;
|
let answer = Text::new(&format!("{question}\nYour answer: ")).prompt()?;
|
||||||
|
|
||||||
Ok(json!({ "answer": answer }))
|
Ok(json!({ "answer": answer }))
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -33,7 +33,7 @@ use std::{env, mem, process};
|
|||||||
|
|
||||||
const MENU_NAME: &str = "completion_menu";
|
const MENU_NAME: &str = "completion_menu";
|
||||||
|
|
||||||
static REPL_COMMANDS: LazyLock<[ReplCommand; 38]> = LazyLock::new(|| {
|
static REPL_COMMANDS: LazyLock<[ReplCommand; 39]> = LazyLock::new(|| {
|
||||||
[
|
[
|
||||||
ReplCommand::new(".help", "Show this help guide", AssertState::pass()),
|
ReplCommand::new(".help", "Show this help guide", AssertState::pass()),
|
||||||
ReplCommand::new(".info", "Show system info", AssertState::pass()),
|
ReplCommand::new(".info", "Show system info", AssertState::pass()),
|
||||||
@@ -137,6 +137,11 @@ static REPL_COMMANDS: LazyLock<[ReplCommand; 38]> = LazyLock::new(|| {
|
|||||||
"Leave agent",
|
"Leave agent",
|
||||||
AssertState::True(StateFlags::AGENT),
|
AssertState::True(StateFlags::AGENT),
|
||||||
),
|
),
|
||||||
|
ReplCommand::new(
|
||||||
|
".clear todo",
|
||||||
|
"Clear the todo list and stop auto-continuation",
|
||||||
|
AssertState::True(StateFlags::AGENT),
|
||||||
|
),
|
||||||
ReplCommand::new(
|
ReplCommand::new(
|
||||||
".rag",
|
".rag",
|
||||||
"Initialize or access RAG",
|
"Initialize or access RAG",
|
||||||
@@ -804,6 +809,25 @@ pub async fn run_repl_command(
|
|||||||
Some("messages") => {
|
Some("messages") => {
|
||||||
bail!("Use '.empty session' instead");
|
bail!("Use '.empty session' instead");
|
||||||
}
|
}
|
||||||
|
Some("todo") => {
|
||||||
|
let mut cfg = config.write();
|
||||||
|
match cfg.agent.as_mut() {
|
||||||
|
Some(agent) => {
|
||||||
|
if !agent.auto_continue_enabled() {
|
||||||
|
bail!(
|
||||||
|
"The todo system is not enabled for this agent. Set 'auto_continue: true' in the agent's config.yaml to enable it."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if agent.todo_list().is_empty() {
|
||||||
|
println!("Todo list is already empty.");
|
||||||
|
} else {
|
||||||
|
agent.clear_todo_list();
|
||||||
|
println!("Todo list cleared.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => bail!("No active agent"),
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => unknown_command()?,
|
_ => unknown_command()?,
|
||||||
},
|
},
|
||||||
".vault" => match split_first_arg(args) {
|
".vault" => match split_first_arg(args) {
|
||||||
|
|||||||
Reference in New Issue
Block a user