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
159 changes: 71 additions & 88 deletions mini_coding_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import shutil
import subprocess
import sys
import textwrap
import urllib.error
import urllib.request
import uuid
Expand All @@ -22,16 +21,16 @@
"\\\\ \\|/ /",
"`-----'__",
)
HELP_DETAILS = textwrap.dedent(
"""\
Commands:
/help Show this help message.
/memory Show the agent's distilled working memory.
/session Show the path to the saved session file.
/reset Clear the current session history and memory.
/exit Exit the agent.
"""
).strip()
HELP_DETAILS = "\n".join(
[
"Commands:",
"/help Show this help message.",
"/memory Show the agent's distilled working memory.",
"/session Show the path to the saved session file.",
"/reset Clear the current session history and memory.",
"/exit Exit the agent.",
]
)
MAX_TOOL_OUTPUT = 4000
MAX_HISTORY = 12000
IGNORED_PATH_NAMES = {".git", ".mini-coding-agent", "__pycache__", ".pytest_cache", ".ruff_cache", ".venv", "venv"}
Expand Down Expand Up @@ -126,21 +125,19 @@ def git(args, fallback=""):
def text(self):
commits = "\n".join(f"- {line}" for line in self.recent_commits) or "- none"
docs = "\n".join(f"- {path}\n{snippet}" for path, snippet in self.project_docs.items()) or "- none"
return textwrap.dedent(
f"""\
Workspace:
- cwd: {self.cwd}
- repo_root: {self.repo_root}
- branch: {self.branch}
- default_branch: {self.default_branch}
- status:
{self.status}
- recent_commits:
{commits}
- project_docs:
{docs}
"""
).strip()
return "\n".join([
"Workspace:",
f"- cwd: {self.cwd}",
f"- repo_root: {self.repo_root}",
f"- branch: {self.branch}",
f"- default_branch: {self.default_branch}",
"- status:",
self.status,
"- recent_commits:",
commits,
"- project_docs:",
docs,
])


##############################
Expand Down Expand Up @@ -350,49 +347,42 @@ def build_prefix(self):
"<final>Done.</final>",
]
)
return textwrap.dedent(
f"""\
You are Mini-Coding-Agent, a small local coding agent running through Ollama.

Rules:
- Use tools instead of guessing about the workspace.
- Return exactly one <tool>...</tool> or one <final>...</final>.
- Tool calls must look like:
<tool>{{"name":"tool_name","args":{{...}}}}</tool>
- For write_file and patch_file with multi-line text, prefer XML style:
<tool name="write_file" path="file.py"><content>...</content></tool>
- Final answers must look like:
<final>your answer</final>
- Never invent tool results.
- Keep answers concise and concrete.
- If the user asks you to create or update a specific file and the path is clear, use write_file or patch_file instead of repeatedly listing files.
- Before writing tests for existing code, read the implementation first.
- When writing tests, match the current implementation unless the user explicitly asked you to change the code.
- New files should be complete and runnable, including obvious imports.
- Do not repeat the same tool call with the same arguments if it did not help. Choose a different tool or return a final answer.
- Required tool arguments must not be empty. Do not call read_file, write_file, patch_file, run_shell, or delegate with args={{}}.

Tools:
{tool_text}

Valid response examples:
{examples}

{self.workspace.text()}
"""
).strip()
rules = "\n".join([
"- Use tools instead of guessing about the workspace.",
"- Return exactly one <tool>...</tool> or one <final>...</final>.",
"- Tool calls must look like:",
' <tool>{"name":"tool_name","args":{...}}</tool>',
"- For write_file and patch_file with multi-line text, prefer XML style:",
' <tool name="write_file" path="file.py"><content>...</content></tool>',
"- Final answers must look like:",
" <final>your answer</final>",
"- Never invent tool results.",
"- Keep answers concise and concrete.",
"- If the user asks you to create or update a specific file and the path is clear, use write_file or patch_file instead of repeatedly listing files.",
"- Before writing tests for existing code, read the implementation first.",
"- When writing tests, match the current implementation unless the user explicitly asked you to change the code.",
"- New files should be complete and runnable, including obvious imports.",
"- Do not repeat the same tool call with the same arguments if it did not help. Choose a different tool or return a final answer.",
"- Required tool arguments must not be empty. Do not call read_file, write_file, patch_file, run_shell, or delegate with args={}.",
])
return "\n\n".join([
"You are Mini-Coding-Agent, a small local coding agent running through Ollama.",
"Rules:\n" + rules,
"Tools:\n" + tool_text,
"Valid response examples:\n" + examples,
self.workspace.text(),
])

def memory_text(self):
memory = self.session["memory"]
return textwrap.dedent(
f"""\
Memory:
- task: {memory['task'] or "-"}
- files: {", ".join(memory["files"]) or "-"}
- notes:
{chr(10).join(f"- {note}" for note in memory["notes"]) or "- none"}
"""
).strip()
notes = "\n".join(f"- {note}" for note in memory["notes"]) or "- none"
return "\n".join([
"Memory:",
f"- task: {memory['task'] or '-'}",
f"- files: {', '.join(memory['files']) or '-'}",
"- notes:",
notes,
])

#####################################################
#### 4) Context Reduction And Output Management #####
Expand Down Expand Up @@ -430,19 +420,12 @@ def history_text(self):
#### 2) Prompt Shape And Cache Reuse (Continued) #######
########################################################
def prompt(self, user_message):
return textwrap.dedent(
f"""\
{self.prefix}

{self.memory_text()}

Transcript:
{self.history_text()}

Current user request:
{user_message}
"""
).strip()
return "\n\n".join([
self.prefix,
self.memory_text(),
"Transcript:\n" + self.history_text(),
"Current user request:\n" + user_message,
])

###############################################
#### 5) Session Memory (Continued) ###########
Expand Down Expand Up @@ -825,15 +808,15 @@ def tool_run_shell(self, args):
text=True,
timeout=timeout,
)
return textwrap.dedent(
f"""\
exit_code: {result.returncode}
stdout:
{result.stdout.strip() or "(empty)"}
stderr:
{result.stderr.strip() or "(empty)"}
"""
).strip()
return "\n".join(
[
f"exit_code: {result.returncode}",
"stdout:",
result.stdout.strip() or "(empty)",
"stderr:",
result.stderr.strip() or "(empty)",
]
)

def tool_write_file(self, args):
path = self.path(args["path"])
Expand Down
41 changes: 41 additions & 0 deletions tests/test_mini_coding_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,47 @@ def test_welcome_screen_keeps_box_shape_for_long_paths(tmp_path):
assert "commands: Commands:" not in welcome


def test_prompt_top_level_sections_stay_flush_left_with_multiline_content(tmp_path):
workspace = WorkspaceContext(
cwd=str(tmp_path),
repo_root=str(tmp_path),
branch="fix/prompt-indentation",
default_branch="main",
status=" M mini_coding_agent.py\n?? tests/test_prompt.py",
recent_commits=["abc123 first commit", "def456 second commit"],
project_docs={"README.md": "line1\nline2"},
)
store = SessionStore(tmp_path / ".mini-coding-agent" / "sessions")
agent = MiniAgent(
model_client=FakeModelClient([]),
workspace=workspace,
session_store=store,
approval_policy="auto",
)
agent.session["memory"] = {
"task": "verify prompt formatting",
"files": ["mini_coding_agent.py"],
"notes": ["saw inconsistent indentation", "need regression coverage"],
}
agent.record({"role": "user", "content": "inspect prompt()", "created_at": "1"})
agent.record(
{
"role": "tool",
"name": "read_file",
"args": {"path": "mini_coding_agent.py"},
"content": " def prompt(self, user_message):\n ...",
"created_at": "2",
}
)

prompt = agent.prompt("is this issue legit?")
lines = prompt.splitlines()

for label in ["Rules:", "Tools:", "Valid response examples:", "Workspace:", "Memory:", "Transcript:", "Current user request:"]:
assert label in lines
assert f" {label}" not in prompt


def _make_filler(i):
return {"role": "tool", "name": "list_files", "args": {}, "content": "", "created_at": str(i)}

Expand Down
Loading