mini-code-cli 0.0.8.4__tar.gz → 0.1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/PKG-INFO +5 -2
  2. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/README.md +3 -0
  3. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/agent_loop.py +68 -11
  4. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/plan.py +55 -17
  5. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/prompts.py +36 -2
  6. mini_code_cli-0.1.0.0/mini_code_cli/skills.py +365 -0
  7. mini_code_cli-0.1.0.0/mini_code_cli/subagents/builtin.py +171 -0
  8. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/tools.py +115 -44
  9. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli.egg-info/PKG-INFO +5 -2
  10. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli.egg-info/SOURCES.txt +2 -0
  11. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/pyproject.toml +2 -2
  12. mini_code_cli-0.1.0.0/tests/test_skills.py +245 -0
  13. mini_code_cli-0.0.8.4/mini_code_cli/subagents/builtin.py +0 -79
  14. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/LICENSE +0 -0
  15. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/__init__.py +0 -0
  16. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/config.py +0 -0
  17. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/permissions.py +0 -0
  18. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/persistence.py +0 -0
  19. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/runner.py +0 -0
  20. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/sandbox.py +0 -0
  21. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/shell_safety_utils.py +0 -0
  22. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/subagents/__init__.py +0 -0
  23. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/subagents/base.py +0 -0
  24. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/ui.py +0 -0
  25. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli/utils.py +0 -0
  26. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli.egg-info/dependency_links.txt +0 -0
  27. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli.egg-info/entry_points.txt +0 -0
  28. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli.egg-info/requires.txt +0 -0
  29. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/mini_code_cli.egg-info/top_level.txt +0 -0
  30. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/setup.cfg +0 -0
  31. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/tests/test_agent_loop.py +0 -0
  32. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/tests/test_integration.py +0 -0
  33. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/tests/test_plan.py +0 -0
  34. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/tests/test_sandboxing.py +0 -0
  35. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/tests/test_shell_safety.py +0 -0
  36. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/tests/test_subagents.py +0 -0
  37. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/tests/test_tools.py +0 -0
  38. {mini_code_cli-0.0.8.4 → mini_code_cli-0.1.0.0}/tests/test_ui.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-code-cli
3
- Version: 0.0.8.4
4
- Summary: Mini Coding CLI.
3
+ Version: 0.1.0.0
4
+ Summary: Mini Coding Agent. Allows you to easily experiment with a small coding agent.
5
5
  Author-email: Nikolai Rozanov <nikolai.rozanov@gmail.com>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
7
7
  Version 3, 19 November 2007
@@ -700,6 +700,9 @@ mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash"
700
700
  ```bash
701
701
  mini-code --enable-shell --auto-mode
702
702
  mini-code -s -a --sudo
703
+ mini-code -s -a -p -g -l --sudo #plan, previous session, subagents (#or short-hand the next commands)
704
+ mini-code --all
705
+ mini-code --all-last
703
706
  ```
704
707
 
705
708
  4. Advanced usage:
@@ -26,6 +26,9 @@ mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash"
26
26
  ```bash
27
27
  mini-code --enable-shell --auto-mode
28
28
  mini-code -s -a --sudo
29
+ mini-code -s -a -p -g -l --sudo #plan, previous session, subagents (#or short-hand the next commands)
30
+ mini-code --all
31
+ mini-code --all-last
29
32
  ```
30
33
 
31
34
  4. Advanced usage:
@@ -16,6 +16,7 @@ smaller, focused modules so each concern is easy to read and test:
16
16
  * :mod:`mini_code_cli.runner` -- running a tool with a wall-clock timeout.
17
17
  * :mod:`mini_code_cli.config` -- shared default settings.
18
18
  * :mod:`mini_code_cli.subagents` -- delegating a task to a sub-agent.
19
+ * :mod:`mini_code_cli.skills` -- the ``.mini`` skills feature.
19
20
 
20
21
  For backward compatibility (and the test-suite) the most commonly imported
21
22
  helpers are re-exported from here, so ``from mini_code_cli.agent_loop import ...``
@@ -32,6 +33,7 @@ from . import tools
32
33
  from . import utils
33
34
  from . import ui
34
35
  from . import subagents
36
+ from . import skills
35
37
  from . import plan as plan_utils
36
38
  from .config import (
37
39
  DEFAULT_CACHE_LOCATION,
@@ -116,14 +118,29 @@ def parse_args():
116
118
  parser.add_argument("--allowed-dir", type=str, default=".", help="Allowed directory for file operations, default='./'")
117
119
  parser.add_argument("-s", "--enable-shell", action="store_true", help="Allow shell execution. default= False")
118
120
  parser.add_argument("-g","--enable-subagents", action="store_true", help="Allow the agent to delegate work to sub-agents (exposed as extra tools). default= False")
121
+ parser.add_argument("--skills", action="store_true", help="Enable the skills feature: a local .mini directory (inside --allowed-dir) of skill.md files, skill sub-dirs, a high-level SKILLS.md summary, and executable Python scripts, exposed to the agent as list_skills/read_skill/create_skill/run_skill tools. default= False")
119
122
  parser.add_argument("--llm-timeout", type=int, default=DEFAULT_HTTP_TIMEOUT, help=f"HTTP timeout in seconds for LLM requests (reasoning models can be slow). default={DEFAULT_HTTP_TIMEOUT}")
120
123
  parser.add_argument("--tool-timeout", type=int, default=DEFAULT_TOOL_TIMEOUT, help=f"Timeout in seconds for a single tool call. default={DEFAULT_TOOL_TIMEOUT}")
121
124
  parser.add_argument("-p","--plan", action="store_true", help="Enable plan mode: the agent keeps a CURRENT_PLAN.md (its step-by-step plan, marked finished when the goal is done) and CURRENT_GOAL.md (the user's latest instruction) in the session directory. Also changes how history compaction works.")
122
125
 
126
+ parser.add_argument("--all", action="store_true", help="Enable: --plan, --subagents, --shell, --automode, --sudo")
127
+ parser.add_argument("--all-last", action="store_true", help="Enable: --plan, --subagents, --shell, --automode, --sudo, --load-last-session")
128
+
123
129
  # Output verbosity
124
130
  parser.add_argument("--verbose", action="store_true", help="Print the full, detailed output. Without it (default), only user/assistant turns, token usage and tool calls are shown.")
125
131
 
126
132
  args = parser.parse_args()
133
+
134
+ if args.all or args.all_last:
135
+ args.plan = True
136
+ args.enable_subagents = True
137
+ args.enable_shell = True
138
+ args.sudo = True
139
+ args.auto_mode = True
140
+
141
+ if args.all_last:
142
+ args.load_last_session = True
143
+
127
144
  return args
128
145
 
129
146
 
@@ -154,6 +171,10 @@ def print_help(allowed_dir, server_url, args, unique_id, cache_dir, model_name):
154
171
  ui.console.emit(ui.console.style("Sub-agents:", bold=True, fg="cyan"))
155
172
  for sa in subagents.get_registry().values():
156
173
  ui.console.emit(f" {sa.name:16} {sa.description}")
174
+ if args.enable_skills or args.verbose:
175
+ ui.console.emit(ui.console.style("Skills:", bold=True, fg="cyan"))
176
+ for name in skills.SKILL_TOOL_NAMES:
177
+ ui.console.emit(f" {name:16} (from the local .mini directory)")
157
178
  ui.console.rule(char="═", fg="cyan")
158
179
 
159
180
 
@@ -285,6 +306,19 @@ class AgentSession:
285
306
  # real function.
286
307
  self.tools_dict[sa.name] = {"function": None, "description": sa.description, "subagent": True}
287
308
 
309
+ # Skills (opt-in via --skills). Each registered skill tool is exposed to
310
+ # the model as a normal tool, but they are all sandbox-safe: the .mini
311
+ # directory lives *inside* --allowed-dir and the tools never accept a
312
+ # path from the model -- skill / script names are validated as single
313
+ # path segments and resolved under the .mini root. So skills are exposed
314
+ # even in sessions without --enable-shell (run_skill only executes a
315
+ # named Python file, never a free-form shell command).
316
+ self.enable_skills = bool(getattr(args, "enable_skills", False))
317
+ if self.enable_skills:
318
+ self.skills_tools_dict = skills.get_skills_tools_dict(self.allowed_dir)
319
+ self.llm_tools.extend(skills.get_skills_tools(self.allowed_dir))
320
+ self.tools_dict.update(self.skills_tools_dict)
321
+
288
322
  # Token accounting for the whole session.
289
323
  self.token_stats = {
290
324
  "prompt_tokens": 0,
@@ -363,7 +397,11 @@ class AgentSession:
363
397
  if self.args.system_prompt:
364
398
  system_prompt = self.args.system_prompt
365
399
  else:
366
- system_prompt = prompts.system_prompt(tools_dict=self.tools_dict, allowed_dir=self.allowed_dir, plan_mode=self.plan_mode)
400
+ system_prompt = prompts.system_prompt(
401
+ tools_dict=self.tools_dict, allowed_dir=self.allowed_dir,
402
+ plan_mode=self.plan_mode,
403
+ skills_dir=self.allowed_dir if self.enable_skills else None,
404
+ )
367
405
 
368
406
  messages = [{"role": "system", "content": system_prompt}]
369
407
 
@@ -619,20 +657,39 @@ class AgentSession:
619
657
  Appends ``initial_task`` as a user turn, then keeps stepping until the
620
658
  model makes no further tool calls (auto-mode). ``max_steps`` caps the
621
659
  number of model calls so a misbehaving sub-agent cannot loop forever.
660
+
661
+ The sub-agent records its work in its own cache dir: every step persists
662
+ history / recorder / usage into the child's own session folder, and it
663
+ seeds CURRENT_GOAL.md / CURRENT_PLAN.md (in plan mode) so each sub-session
664
+ is self-contained and easy to inspect or resume. A final record is left
665
+ even on a mid-run failure (the seeded goal is marked finished).
622
666
  """
623
667
  self.messages.append({"role": "user", "content": initial_task})
668
+ # Seed the goal up front so the child keeps a self-contained record in
669
+ # its own cache dir (a sub-agent always records a goal, independent of
670
+ # the parent's --plan flag). We use the child's own plan_manager here so
671
+ # nothing is written to the parent's session folder.
672
+ from .plan import PlanManager
673
+ child_plan = PlanManager(os.path.join(self.cache_dir, self.unique_id), enabled=True)
674
+ child_plan.write_goal(initial_task)
624
675
  steps = 0
625
- while True:
626
- should_continue = self._step()
676
+ try:
677
+ while True:
678
+ should_continue = self._step()
679
+ self._persist()
680
+ steps += 1
681
+ if max_steps and steps >= max_steps:
682
+ self.console.note(
683
+ f"Sub-agent reached max_steps ({max_steps}); stopping.", "warn")
684
+ break
685
+ if not should_continue:
686
+ break
687
+ return self._last_assistant_text()
688
+ finally:
689
+ # Leave a clean, finished record in the child's cache dir even on an
690
+ # error / early stop, so the sub-session is never left open.
691
+ child_plan.mark_finished()
627
692
  self._persist()
628
- steps += 1
629
- if max_steps and steps >= max_steps:
630
- self.console.note(
631
- f"Sub-agent reached max_steps ({max_steps}); stopping.", "warn")
632
- break
633
- if not should_continue:
634
- break
635
- return self._last_assistant_text()
636
693
 
637
694
  def _last_assistant_text(self):
638
695
  """Return the content of the most recent assistant (non tool-call) turn."""
@@ -6,14 +6,16 @@ active goal and the plan for reaching it:
6
6
 
7
7
  * ``CURRENT_GOAL.md`` -- the user's latest instruction / goal (verbatim).
8
8
  * ``CURRENT_PLAN.md`` -- the agent's current plan for that goal, followed by
9
- a short running log (e.g. an entry recording when a goal was finished).
9
+ a short running log (e.g. an entry recording when a goal was set/changed,
10
+ and when it was finished).
10
11
 
11
12
  The plan document itself is authored by the agent (the system prompt tells it
12
13
  to, and it uses its ``write_file`` tool). This manager owns the parts the loop
13
14
  is responsible for -- seeding/reading the goal, reading the plan back so history
14
- compaction can re-inject it, and appending the "goal finished" log entry. It is
15
- deliberately free of any network or terminal dependency, and every method is a
16
- no-op when disabled, so it is trivial to unit-test in isolation.
15
+ compaction can re-inject it, and appending the "goal finished" / goal-change log
16
+ entries. It is deliberately free of any network or terminal dependency, and
17
+ every method is a no-op when disabled, so it is trivial to unit-test in
18
+ isolation.
17
19
  """
18
20
 
19
21
  import os
@@ -31,6 +33,11 @@ class PlanManager:
31
33
  ``history_{n}.json``). When ``enabled`` is False every method is a no-op, so
32
34
  callers can use the manager unconditionally and let the ``--plan`` flag
33
35
  decide whether anything actually happens.
36
+
37
+ ``session_dir`` is the session directory (``cache_dir/<unique_id>``) -- i.e.
38
+ the files always live in the *cache* dir, independent of ``--allowed-dir``.
39
+ The manager writes with plain ``open`` (not through the file tools), so the
40
+ records are created even when the allowed dir is a different location.
34
41
  """
35
42
 
36
43
  def __init__(self, session_dir, enabled=True, goal=None):
@@ -39,6 +46,10 @@ class PlanManager:
39
46
  # The last goal this manager was asked to record, so callers can tell a
40
47
  # new goal apart from the previous one. Kept in sync by write_goal().
41
48
  self.goal = goal
49
+ # The last goal (stripped) that was logged into CURRENT_PLAN.md, so
50
+ # write_goal can tell a genuine change apart from a re-stated goal and
51
+ # avoid spamming the plan with duplicate "goal updated" entries.
52
+ self._last_logged_goal = None
42
53
 
43
54
  # -- paths -------------------------------------------------------------
44
55
  @property
@@ -50,20 +61,55 @@ class PlanManager:
50
61
  return os.path.join(self.session_dir, GOAL_FILENAME)
51
62
 
52
63
  # -- writing -----------------------------------------------------------
64
+ def _append_plan_note(self, text):
65
+ """Append one dated line to CURRENT_PLAN.md (creating it if needed).
66
+
67
+ Shared by the goal-change log and ``mark_finished``. Best-effort and
68
+ never raises: a plan-tracking hiccup must never break the agent loop.
69
+ """
70
+ if not self.enabled:
71
+ return
72
+ try:
73
+ os.makedirs(self.session_dir, exist_ok=True)
74
+ if not os.path.exists(self.plan_path):
75
+ with open(self.plan_path, "w", encoding="utf-8") as f:
76
+ f.write("# Current Plan\n\n")
77
+ with open(self.plan_path, "a", encoding="utf-8") as f:
78
+ f.write(text + "\n")
79
+ except OSError:
80
+ pass
81
+
53
82
  def write_goal(self, goal):
54
83
  """Persist the user's current goal / last instruction to CURRENT_GOAL.md.
55
84
 
56
85
  Overwrites any previous goal -- the file always reflects the *latest*
57
- instruction. Best-effort and never raises: a plan-tracking hiccup must
58
- never break the agent loop.
86
+ instruction. Also makes sure ``CURRENT_PLAN.md`` exists in the cache dir
87
+ (so both tracking files are present from the very first goal), and -- in
88
+ plan mode -- logs the goal into CURRENT_PLAN.md the *first* time it is
89
+ set and again whenever it changes, so the plan records goal pivots.
90
+ Best-effort and never raises.
59
91
  """
60
92
  if not self.enabled:
61
93
  return
94
+ stripped = (goal or "").strip()
62
95
  try:
63
96
  os.makedirs(self.session_dir, exist_ok=True)
97
+ # Ensure CURRENT_PLAN.md exists in the cache dir too, so the goal
98
+ # always has a companion plan file sitting next to it.
99
+ if not os.path.exists(self.plan_path):
100
+ with open(self.plan_path, "w", encoding="utf-8") as f:
101
+ f.write("# Current Plan\n\n")
64
102
  with open(self.goal_path, "w", encoding="utf-8") as f:
65
103
  f.write("# Current Goal\n\n")
66
- f.write((goal or "").strip() + "\n")
104
+ f.write(stripped + "\n")
105
+ # Log the goal change into the plan -- but only on a genuine change
106
+ # (compared against the last goal actually logged), so re-stating the
107
+ # same goal on later turns does not spam the plan log.
108
+ if stripped and stripped != self._last_logged_goal:
109
+ verb = "Goal set to" if self._last_logged_goal is None else "Goal updated to"
110
+ stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
111
+ self._append_plan_note(f"- [{stamp}] {verb}: {stripped}")
112
+ self._last_logged_goal = stripped
67
113
  self.goal = goal
68
114
  except OSError:
69
115
  pass
@@ -82,16 +128,8 @@ class PlanManager:
82
128
  # never captured one itself, e.g. a one-shot --prompt preset), and only
83
129
  # then fall back to a generic description.
84
130
  goal = goal or self.goal or self._read(self.goal_path) or "current goal"
85
- try:
86
- os.makedirs(self.session_dir, exist_ok=True)
87
- if not os.path.exists(self.plan_path):
88
- with open(self.plan_path, "w", encoding="utf-8") as f:
89
- f.write("# Current Plan\n\n")
90
- stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
91
- with open(self.plan_path, "a", encoding="utf-8") as f:
92
- f.write(f"\n- [{stamp}] Task finished: {goal}\n")
93
- except OSError:
94
- pass
131
+ stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
132
+ self._append_plan_note(f"- [{stamp}] Task finished: {goal}")
95
133
 
96
134
  # -- reading -----------------------------------------------------------
97
135
  def read_plan(self):
@@ -1,5 +1,5 @@
1
1
  # System prompt defining available tools and compaction behavior
2
- def system_prompt(tools_dict=None, allowed_dir=None, plan_mode=False):
2
+ def system_prompt(tools_dict=None, allowed_dir=None, plan_mode=False, skills_dir=None):
3
3
  """
4
4
  Takes a list of tool names and returns a system prompt string.
5
5
 
@@ -26,6 +26,10 @@ def system_prompt(tools_dict=None, allowed_dir=None, plan_mode=False):
26
26
  if allowed_dir:
27
27
  allowed_dir_prompt = f"\nThe path you are allowed to operate in is called: {allowed_dir}"
28
28
 
29
+ skills_prompt = ""
30
+ if skills_dir is not None:
31
+ skills_prompt = "\n" + skills_instructions(skills_dir)
32
+
29
33
  plan_mode_prompt = ""
30
34
  if plan_mode:
31
35
  plan_mode_prompt = "\n" + plan_instructions()
@@ -33,7 +37,7 @@ def system_prompt(tools_dict=None, allowed_dir=None, plan_mode=False):
33
37
 
34
38
  prompt = f"""
35
39
  {base_prompt}
36
- {tool_descriptions}{end_prompt}{allowed_dir_prompt}{plan_mode_prompt}
40
+ {tool_descriptions}{end_prompt}{allowed_dir_prompt}{plan_mode_prompt}{skills_prompt}
37
41
  Important: Follow the user instruction. Do not create additional .md files (unless asked to do so). Do not create unit tests (unless asked to do so), etc.
38
42
  """
39
43
  return prompt.strip()
@@ -62,6 +66,36 @@ def plan_instructions():
62
66
  "easy to track and resume."
63
67
  )
64
68
 
69
+ def skills_instructions(skills_dir):
70
+ """The extra system-prompt block when the skills feature is enabled.
71
+
72
+ Tells the agent it has a local ``.mini`` directory of reusable skills and
73
+ explains the four skill tools (see :mod:`mini_code_cli.skills`). ``skills_dir``
74
+ is the actual ``.mini`` path so the prompt can point at it concretely.
75
+ """
76
+ from . import skills as skills_mod
77
+ return (
78
+ "SKILLS MODE: You can work with reusable skills kept in the local "
79
+ f"`.mini` directory at: {skills_mod.skills_dir(skills_dir)}.\n"
80
+ "Each skill is a sub-directory containing a `skill.md` (what the skill is "
81
+ "/ how to use it) and optional executable Python helper scripts. There is "
82
+ "also a high-level `SKILLS.md` summary of all skills.\n"
83
+ "Use these tools to work with skills:\n"
84
+ " * `list_skills` - see which skills exist (name + one-line summary).\n"
85
+ " * `read_skill` - read a skill's `skill.md` by name, to learn how to "
86
+ "use it or which scripts it provides.\n"
87
+ " * `create_skill` - create a new skill (its sub-directory and `skill.md`) "
88
+ "when you build a reusable capability. Give a clear, single-segment name "
89
+ "and a short summary. It is normal to then add Python helper scripts into "
90
+ "the skill's directory.\n"
91
+ " * `run_skill` - run one of a skill's Python scripts, passing any "
92
+ "command-line arguments via its `args`. Read the skill first to know "
93
+ "which script to run and what arguments it expects.\n"
94
+ "Prefer turning reusable helpers into skills (with a `skill.md` plus "
95
+ "scripts) so they can be reused later, rather than repeating one-off work.\n"
96
+ )
97
+
98
+
65
99
  def compaction_prompt(messages):
66
100
  """
67
101
  Takes a messages list and returns a compact prompt string summarizing the conversation.