agentic-python-coder 3.3.0__tar.gz → 3.4.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 (53) hide show
  1. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/.gitignore +0 -4
  2. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/PKG-INFO +1 -1
  3. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/kernel.py +25 -1
  4. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/mcp_server.py +74 -1
  5. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/pyproject.toml +5 -1
  6. agentic_python_coder-3.3.0/MEM/MEM_000.md +0 -131
  7. agentic_python_coder-3.3.0/MEM/MEM_001_frameworkless_react_agent.md +0 -110
  8. agentic_python_coder-3.3.0/MEM/MEM_002_tool_description_quality.md +0 -107
  9. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/LICENSE +0 -0
  10. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/README.md +0 -0
  11. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/.gitignore +0 -0
  12. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/prompts/system.md +0 -0
  13. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/prompts/system_todo.md +0 -0
  14. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/__init__.py +0 -0
  15. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/agent.py +0 -0
  16. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/cli.py +0 -0
  17. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/__init__.py +0 -0
  18. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/README.md +0 -0
  19. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/clingo.md +0 -0
  20. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/bird_reasoning.md +0 -0
  21. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/diagnosis.md +0 -0
  22. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/simple_coloring.md +0 -0
  23. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/stable_marriage.md +0 -0
  24. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/sudoku_mini.md +0 -0
  25. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/cpmpy/README.md +0 -0
  26. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/cpmpy/cpmpy.md +0 -0
  27. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/cpmpy/sample_tasks/magic_square.md +0 -0
  28. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/cpmpy/sample_tasks/n_queens.md +0 -0
  29. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/README.md +0 -0
  30. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/regex.md +0 -0
  31. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/sample_tasks/email_extraction.md +0 -0
  32. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/sample_tasks/phone_validation.md +0 -0
  33. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/sample_tasks/url_parsing.md +0 -0
  34. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/llm.py +0 -0
  35. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/deepseek31.json +0 -0
  36. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gemini25.json +0 -0
  37. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gemini31.json +0 -0
  38. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gemini3flash.json +0 -0
  39. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gemini3pro.json +0 -0
  40. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gpt52.json +0 -0
  41. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gpt56sol.json +0 -0
  42. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gpt56terra.json +0 -0
  43. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/grok41.json +0 -0
  44. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/opus45.json +0 -0
  45. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/opus48.json +0 -0
  46. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/qwen3.json +0 -0
  47. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/sonnet45.json +0 -0
  48. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/sonnet46.json +0 -0
  49. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/sonnet5.json +0 -0
  50. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/project_md.py +0 -0
  51. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/runner.py +0 -0
  52. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/tools.py +0 -0
  53. {agentic_python_coder-3.3.0 → agentic_python_coder-3.4.0}/examples/clingo/clingo.md +0 -0
@@ -154,7 +154,6 @@ uv.lock
154
154
  # Project specific
155
155
  tests/
156
156
  coder-examples/
157
- CLAUDE-archive.md
158
157
  EXPERTISE/
159
158
  PROCESS_NOTES.md
160
159
  conversation_log.json
@@ -162,8 +161,6 @@ coder_output.log
162
161
  task.md
163
162
  solution.py
164
163
  *.tmp
165
- CLAUDE.md
166
- **/CLAUDE.md
167
164
  TRASH/
168
165
  LOCAL/
169
166
  ASP/
@@ -172,7 +169,6 @@ CPMPY/
172
169
  PAPER/
173
170
  PAPER-ASP/
174
171
  ZEBRA/
175
- .mcp.json
176
172
 
177
173
  # Test files and folders (root level only)
178
174
  /test-*/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-python-coder
3
- Version: 3.3.0
3
+ Version: 3.4.0
4
4
  Summary: A lightweight Python coding agent that writes, executes, and iterates on code through natural language instructions
5
5
  Author: Stefan Szeider
6
6
  License: Apache-2.0
@@ -10,6 +10,7 @@ import logging
10
10
  import os
11
11
  import re
12
12
  import shutil
13
+ import signal
13
14
  import threading
14
15
  import time
15
16
  import uuid
@@ -311,7 +312,21 @@ except ImportError:
311
312
  return self._execute_raw(code, deadline_timeout)
312
313
 
313
314
  def shutdown(self):
314
- """Shutdown the kernel and clean up."""
315
+ """Shutdown the kernel and clean up.
316
+
317
+ Kernels run in their own process group (jupyter_client launches with
318
+ start_new_session=True). After the regular shutdown, defensively kill
319
+ the whole group: uv-wrapped kernels are a tree (uv parent + python
320
+ child) and a surviving member would be orphaned.
321
+ """
322
+ # Capture the kernel's process group before shutdown clears it
323
+ pgid = None
324
+ try:
325
+ if hasattr(self, "km") and self.km:
326
+ pgid = getattr(getattr(self.km, "provisioner", None), "pgid", None)
327
+ except Exception:
328
+ pass
329
+
315
330
  try:
316
331
  if hasattr(self, "kc") and self.kc:
317
332
  try:
@@ -330,6 +345,15 @@ except ImportError:
330
345
  # Ignore errors during shutdown
331
346
  pass
332
347
 
348
+ # Defensive: ensure no process-tree member survived. Never signal our
349
+ # own group (pgid could be bogus if start_new_session did not apply).
350
+ if pgid and pgid > 0:
351
+ try:
352
+ if pgid != os.getpgid(0):
353
+ os.killpg(pgid, signal.SIGKILL)
354
+ except (ProcessLookupError, PermissionError, OSError):
355
+ pass
356
+
333
357
 
334
358
  # =============================================================================
335
359
  # Core Registry Functions
@@ -7,6 +7,9 @@ import ast
7
7
  import asyncio
8
8
  import json
9
9
  import logging
10
+ import os
11
+ import signal
12
+ import threading
10
13
 
11
14
  from mcp.server import Server
12
15
  from mcp.server.stdio import stdio_server
@@ -20,6 +23,7 @@ from agentic_python_coder.kernel import (
20
23
  kernel_exists,
21
24
  list_kernels,
22
25
  restart_kernel,
26
+ shutdown_all_kernels,
23
27
  )
24
28
 
25
29
  # Configure logging
@@ -240,6 +244,30 @@ No side effects - safe to call anytime.""",
240
244
  },
241
245
  },
242
246
  ),
247
+ Tool(
248
+ name="submit_code",
249
+ description="""Submit your final, verified, self-contained Python program.
250
+
251
+ Call this ONCE at the end of a solve, only after you have executed the program
252
+ via python_exec and your verification passed. The submission must be a
253
+ standalone program: all imports included, no reliance on session state.
254
+
255
+ The code is syntax-checked with compile():
256
+ - {"ok": true} if it parses
257
+ - {"ok": false, "error": "<detail>"} on a syntax error — fix and resubmit
258
+
259
+ The code is NOT executed and NOT written to disk; the client persists it.""",
260
+ inputSchema={
261
+ "type": "object",
262
+ "properties": {
263
+ "code": {
264
+ "type": "string",
265
+ "description": "Complete standalone Python program (final answer).",
266
+ },
267
+ },
268
+ "required": ["code"],
269
+ },
270
+ ),
243
271
  Tool(
244
272
  name="python_interrupt",
245
273
  description="""Interrupt running code in the session.
@@ -391,6 +419,21 @@ versions
391
419
  result = await execute_with_timeout(code, timeout, kernel_id)
392
420
  return [TextContent(type="text", text=json.dumps(result))]
393
421
 
422
+ elif name == "submit_code":
423
+ code = arguments.get("code", "")
424
+ if not code.strip():
425
+ payload = {"ok": False, "error": "Empty submission"}
426
+ else:
427
+ try:
428
+ compile(code, "<submission>", "exec")
429
+ payload = {"ok": True}
430
+ except SyntaxError as e:
431
+ payload = {
432
+ "ok": False,
433
+ "error": f"Syntax error at line {e.lineno}: {e.msg}",
434
+ }
435
+ return [TextContent(type="text", text=json.dumps(payload))]
436
+
394
437
  elif name == "python_status":
395
438
  kernel_id = get_kernel_id(arguments)
396
439
  is_active = kernel_exists(kernel_id)
@@ -521,9 +564,39 @@ async def run_server():
521
564
  await server.run(read, write, server.create_initialization_options())
522
565
 
523
566
 
567
+ # Hard-exit fallback if kernel cleanup hangs after a termination signal
568
+ _TERMINATION_CLEANUP_TIMEOUT = 10.0
569
+
570
+
571
+ def _handle_termination(signum, frame):
572
+ """Shut down all kernels on SIGTERM/SIGINT, then exit.
573
+
574
+ The cleanup runs in a thread (not in signal-handler context) so it can
575
+ safely take the kernel-registry locks; a timer hard-exits if it hangs.
576
+ """
577
+
578
+ def _cleanup_and_exit():
579
+ try:
580
+ shutdown_all_kernels()
581
+ finally:
582
+ os._exit(128 + signum)
583
+
584
+ threading.Timer(
585
+ _TERMINATION_CLEANUP_TIMEOUT, lambda: os._exit(128 + signum)
586
+ ).start()
587
+ threading.Thread(target=_cleanup_and_exit, daemon=True).start()
588
+
589
+
524
590
  def main():
525
591
  """Entry point for coder-mcp command."""
526
- asyncio.run(run_server())
592
+ signal.signal(signal.SIGTERM, _handle_termination)
593
+ signal.signal(signal.SIGINT, _handle_termination)
594
+ try:
595
+ asyncio.run(run_server())
596
+ finally:
597
+ # stdin EOF / transport close / normal exit: deterministic cleanup
598
+ # (atexit also runs this, but only on clean interpreter shutdown)
599
+ shutdown_all_kernels()
527
600
 
528
601
 
529
602
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentic-python-coder"
3
- version = "3.3.0"
3
+ version = "3.4.0"
4
4
  description = "A lightweight Python coding agent that writes, executes, and iterates on code through natural language instructions"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13,<3.14"
@@ -60,6 +60,10 @@ exclude = [
60
60
  "*.jsonl",
61
61
  "*_code.py",
62
62
  "RELEASE_NOTES_*.md",
63
+ "CLAUDE.md",
64
+ "CLAUDE-archive.md",
65
+ "MEM/",
66
+ ".claude/",
63
67
  ]
64
68
 
65
69
  [tool.hatch.build.targets.wheel]
@@ -1,131 +0,0 @@
1
- ---
2
- keywords: [session log, process notes, history]
3
- context: Searchable session log — grep when needed, not loaded routinely
4
- added: 2026-01-31
5
- ---
6
- # Process Notes
7
-
8
- Searchable session log. Not loaded routinely—search with grep when needed.
9
-
10
- ---
11
- ## 2026-02-23
12
-
13
- **Goal**: Add new model configs (sonnet46, gemini31) and publish v3.2.0
14
-
15
- **Done**:
16
- - Created sonnet46.json (anthropic/claude-sonnet-4.6, temp 0.0)
17
- - Created gemini31.json (google/gemini-3.1-pro-preview, temp 0.0, timeout 120)
18
- - Added reasoning/thinking token support in llm.py
19
- - Bumped version to 3.2.0, all 70 unit tests pass
20
- - Published to PyPI
21
-
22
- **Tried**:
23
- - Initially copied gemini3pro params (temp 0.3, top_p 0.9) for gemini31 — Stefan corrected to temp 0.0, no top_p
24
-
25
- **Notes**: Quick session. Stefan provided the gemini model path directly (google/gemini-3.1-pro-preview).
26
-
27
- ---
28
- ## 2026-02-15
29
-
30
- **Goal**: Routine maintenance — dependency updates, new model config, code review fixes
31
-
32
- **Done**:
33
- - Bumped deps: openai 2.16→2.21, rich 14.3.1→14.3.2, ipykernel 7.1→7.2
34
- - Added gemini3flash model config (Gemini 3 Flash Preview)
35
- - Code review (ULTRA) found 6 issues in llm.py — all fixed: provider routing bug, OSError handling, model_path→model_id rename, empty "Available:" message, Warning→Error label, unnecessary ./ prefix
36
- - Published v3.1.0 to PyPI
37
- - Migrated EXPERTISE/ → MEM/ (2 entries + PROCESS_NOTES.md)
38
-
39
- **Tried**:
40
- - Initially copied gemini3pro.json params blindly for gemini3flash — Stefan caught this, verified actual supported params via WS7
41
-
42
- **Notes**: PyPI token is at ~/git/.pipy.txt (updated CLAUDE.md to record this)
43
-
44
- ---
45
-
46
-
47
- ## 2026-01-31 (session 3)
48
-
49
- **Goal**: Run e2e benchmark tests across models, fix failures, improve prompts, merge v3.0.0 to main, publish to PyPI
50
-
51
- **Done**:
52
- - Ran hard problem benchmarks (5 CPMpy + 5 Clingo) across opus45, gpt52, gemini3pro
53
- - Created gemini3pro.json model config (google/gemini-3-pro-preview)
54
- - Fixed gpt52 max_tokens: 3000→16384 (was truncating tool calls, caused 0/5 Clingo)
55
- - Rewrote Clingo validate.py (old one was wrong ASP-Bench full-suite validator)
56
- - Added mandatory verification prompts to system.md, system_todo.md, and save_code tool description
57
- - Analyzed GPT-5.2 CPMpy failures with GPPT consult (circular verification, wrong problem interpretation)
58
- - Fixed flaky test_todo_flag_integration (catch TimeoutExpired for partial output)
59
- - Squash-merged dev→main, published v3.0.0 to PyPI
60
- - Updated README: examples link, gemini3pro in model list
61
-
62
- **Tried**:
63
- - Verification prompts in system prompt + tool description — improved opus45 CPMpy 4/5→5/5 and gpt52 Clingo 0/5→5/5
64
- - GPT-5.2 on CPMpy still only 2/5 despite verification prompts — model sometimes ignores them (2 tool calls before save) or writes circular verify()
65
- - Gemini 3 Pro initially 0/10 with max_tokens=2048; bumped to 16384 → 5/10
66
-
67
- **Notes**: Key insight — verification gates work but models can write verify() functions that re-implement solver bugs instead of checking problem text independently. Future prompt work should address this circular verification pitfall.
68
-
69
- ---
70
-
71
- ## 2026-01-31 (session 2)
72
-
73
- **Goal**: Clean up dev branch for v3.0.0 release — model configs, tool descriptions, README, examples consolidation
74
-
75
- **Done**:
76
- - Replaced gpt5 model config with gpt52 (openai/gpt-5.2)
77
- - Restored verbose LangGraph-era tool descriptions in tools.py (fixes performance regression on hard tasks)
78
- - Restored parameter schemas to match tested reference (title fields, additionalProperties style)
79
- - Created EXPERTISE/E002_tool_description_quality.md
80
- - Removed LangGraph badge and references from README
81
- - Bumped all dependency minimum versions to current
82
- - Consolidated examples: deleted legacy examples/ dir, kept only bundled coder/src/.../examples/
83
- - Confirmed _todo.md project prompts are unused (--todo only switches system prompt)
84
- - Improved CPMpy project prompt with InDomain constraint and pitfalls section
85
- - Copied e2e challenge tests (hard_cpmpy, hard_clingo) to tests/ (gitignored)
86
- - All 70 unit tests pass
87
-
88
- **Tried**:
89
- - Initially kept minimal tool descriptions after LangGraph removal — caused performance regression on challenging tasks, discovered in separate test session
90
- - Tried using "description" field in JSON schemas instead of "title" — reverted to match LangGraph-generated style that was proven to work
91
-
92
- **Notes**: Tool descriptions are behavioral instructions for the model, not documentation. Verbose descriptions with examples, Args/Returns sections are essential for tool-calling quality. This is documented in E002.
93
-
94
- ---
95
-
96
- ## 2026-01-31
97
-
98
- **Goal**: Update model configs and fix tool description regression
99
-
100
- **Done**:
101
- - Replaced gpt5 model config with gpt52 (openai/gpt-5.2) — JSON, README, tests
102
- - Restored tool descriptions in tools.py to verbose LangGraph-era format (critical for performance)
103
- - Restored parameter schemas to match tested reference (title fields, additionalProperties style)
104
- - Created EXPERTISE/E002_tool_description_quality.md
105
- - Removed unused _task_basename import in test_library_api.py
106
-
107
- **Tried**:
108
- - Initially kept minimal tool descriptions after LangGraph removal — caused performance regression on challenging tasks
109
- - Discovered that verbose descriptions with examples, Args/Returns sections are essential for model tool-calling quality
110
-
111
- **Notes**: Tool descriptions are behavioral instructions for the model, not documentation for humans. This is a key insight for any framework migration.
112
-
113
- ---
114
-
115
- ## 2026-01-30
116
-
117
- **Goal**: Verify test suite, create expertise entry for frameworkless agent pattern, housekeeping
118
-
119
- **Done**:
120
- - Ran all 70 pytest tests — all pass after v3.0.0 migration
121
- - Confirmed examples/ has sample tasks but no automated e2e tests
122
- - Created EXPERTISE/E001_frameworkless_react_agent.md documenting the frameworkless ReAct pattern
123
- - Added EXPERTISE/ to .gitignore, committed and pushed to dev
124
- - Removed dead links from Key Documents (LOCAL/ directory no longer exists)
125
-
126
- **Tried**:
127
- - Looked for e2e tests in examples/ — only sample task files and one manual test run (regex/sample_tasks/test_email/)
128
-
129
- **Notes**: E2E tests remain the top priority open goal. They need mocked OpenAI API responses (mock client.chat.completions.create()).
130
-
131
- ---
@@ -1,110 +0,0 @@
1
- ---
2
- keywords: [react, agent, openai, frameworkless, langgraph, langchain, migration, tool-calling]
3
- context: Building LLM-powered agents without heavy frameworks (LangGraph, LangChain, CrewAI, etc.)
4
- added: 2026-01-30
5
- ---
6
- # Frameworkless ReAct Agent Pattern
7
-
8
- ## Insight
9
-
10
- A fully functional ReAct agent requires only ~4 components using the `openai` Python client directly, with no framework overhead:
11
-
12
- 1. **LLM config** — A dataclass wrapping `openai.OpenAI` client, model path, and API params (temperature, max_tokens). Route through OpenRouter for multi-provider access.
13
-
14
- 2. **Tool registry** — A `Tool` dataclass (name, description, JSON Schema parameters, callable function) with a `ToolRegistry` class that converts tools to OpenAI format via `to_openai_tool()`. Each tool's `execute()` returns a JSON string.
15
-
16
- 3. **ReAct loop** — A simple `for step in range(limit)` loop that:
17
- - Calls `client.chat.completions.create()` with messages + tools
18
- - If response has `tool_calls`: execute each, append tool results to messages
19
- - If no `tool_calls`: it's the final answer — break
20
- - Messages list grows naturally (system → user → assistant+tool_calls → tool → assistant+tool_calls → ... → assistant)
21
-
22
- 4. **Agent dataclass** — Holds config, tools, system prompt, working directory, and persistent `messages` list (enables multi-turn/interactive mode).
23
-
24
- ### Key advantages over LangGraph/LangChain
25
-
26
- - **Dependency reduction**: 112 → 69 installed packages (removed all LangChain/LangGraph deps)
27
- - **Debuggability**: The entire agent loop is ~60 lines of straightforward Python. No graph compilation, no state machines, no hidden middleware.
28
- - **Message format control**: Full control over the `messages` list. No framework-imposed message wrappers or serialization formats.
29
- - **Token tracking**: Direct access to `response.usage.prompt_tokens` / `completion_tokens`.
30
- - **Error handling**: Tool errors are just JSON strings fed back to the model — the model self-corrects. No need for framework error recovery machinery.
31
-
32
- ### Pitfalls learned during migration
33
-
34
- - **`model or "default"` bug**: If `model=""` (empty string), `or` evaluates to `"default"` — use explicit `None` checks instead.
35
- - **Environment variable leaks**: Global env vars (e.g., `CODER_WITH_PACKAGES`) persist across runs — always clear them with `os.environ.pop()` when not set.
36
- - **Global state accumulation**: Module-level globals (todo lists, file basenames) accumulate across runs. Add a `reset_global_state()` function called at agent creation time.
37
- - **Tool call message format**: OpenAI API requires tool_calls in assistant messages to be dicts with `id`, `type`, `function.name`, `function.arguments`. Missing any field causes cryptic errors.
38
- - **UTF-8 encoding**: Always use `encoding="utf-8"` when opening files — platform default encoding can vary.
39
-
40
- ### When to use a framework instead
41
-
42
- - If you need **graph-based workflows** with conditional branching between multiple agents
43
- - If you need **built-in memory/persistence** across sessions (vector stores, checkpoints)
44
- - If you need **streaming with backpressure** and complex output routing
45
- - For simple single-agent ReAct loops, the framework adds complexity without benefit
46
-
47
- ## Example
48
-
49
- ```python
50
- from dataclasses import dataclass, field
51
- from openai import OpenAI
52
- import json
53
-
54
- @dataclass
55
- class Tool:
56
- name: str
57
- description: str
58
- parameters: dict
59
- function: callable
60
-
61
- def to_openai_tool(self):
62
- return {
63
- "type": "function",
64
- "function": {
65
- "name": self.name,
66
- "description": self.description,
67
- "parameters": self.parameters,
68
- },
69
- }
70
-
71
- def execute(self, **kwargs):
72
- return self.function(**kwargs)
73
-
74
- # ReAct loop core (~30 lines)
75
- def run(client, model, messages, tools, limit=200):
76
- openai_tools = [t.to_openai_tool() for t in tools]
77
- tool_map = {t.name: t for t in tools}
78
-
79
- for _ in range(limit):
80
- resp = client.chat.completions.create(
81
- model=model, messages=messages, tools=openai_tools, tool_choice="auto"
82
- )
83
- msg = resp.choices[0].message
84
-
85
- if msg.tool_calls:
86
- messages.append({
87
- "role": "assistant",
88
- "content": msg.content or "",
89
- "tool_calls": [
90
- {"id": tc.id, "type": "function",
91
- "function": {"name": tc.function.name,
92
- "arguments": tc.function.arguments}}
93
- for tc in msg.tool_calls
94
- ],
95
- })
96
- for tc in msg.tool_calls:
97
- args = json.loads(tc.function.arguments)
98
- result = tool_map[tc.function.name].execute(**args)
99
- messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})
100
- else:
101
- messages.append({"role": "assistant", "content": msg.content or ""})
102
- break
103
- return messages
104
- ```
105
-
106
- ## Reference
107
-
108
- - Blueprint implementation: `/Users/szeider/work/myreact` (the original pattern)
109
- - Production implementation: `coder/src/agentic_python_coder/agent.py` (v3.0.0)
110
- - Migration context: LangGraph v2.x → frameworkless in one session, all 70 tests passing
@@ -1,107 +0,0 @@
1
- ---
2
- keywords: [tool-descriptions, openai, tool-calling, performance, langgraph, schema, regression]
3
- context: When writing or modifying OpenAI tool definitions for agents that use tool calling
4
- added: 2026-01-31
5
- ---
6
- # Tool Description Quality Impacts Agent Performance
7
-
8
- ## Insight
9
-
10
- Verbose, LangGraph-style tool descriptions significantly outperform minimal descriptions on challenging tasks. When migrating from LangGraph to frameworkless tool definitions, preserving the full docstring content is critical.
11
-
12
- ### What works (LangGraph-style verbose descriptions)
13
-
14
- 1. **Multiple examples** showing multi-step usage patterns — not just what the tool does, but how to chain calls (e.g., define a function in one call, use it in the next)
15
- 2. **Explicit Args section** describing each parameter in natural language
16
- 3. **Explicit Returns section** listing every possible field in the response JSON (`success`, `stdout`, `result`, `stderr`, `error`)
17
- 4. **Behavioral hints** embedded in the description (e.g., "The kernel maintains state between executions!", "use print() to see output")
18
-
19
- ### What fails (minimal descriptions)
20
-
21
- Stripping descriptions to just the core purpose (e.g., "Execute Python code in a persistent IPython kernel.") removes behavioral guidance the model relies on for:
22
- - Understanding that state persists across calls
23
- - Knowing what output format to expect
24
- - Planning multi-step tool use sequences
25
-
26
- ### Parameter schema style also matters
27
-
28
- The LangGraph auto-generated schemas use `"title"` fields on properties and top-level schema, plus `"additionalProperties": True` for flexible object items. Switching to `"description"` fields and strict schemas (with explicit properties and enums) may change how models interpret and use the tools. When in doubt, match the schema style that was tested and proven to work.
29
-
30
- ### Verification gates in tool descriptions
31
-
32
- Adding verification requirements to `save_code` tool description ("Only call this AFTER you have executed the code and confirmed it produces correct output") measurably improved benchmark scores. Opus 4.5 went from 4/5→5/5 on hard CPMpy and GPT-5.2 from 0/5→5/5 on hard Clingo after adding these gates. However, GPT-5.2 still sometimes ignores verification (only 2 tool calls before saving on 016_sports). The verification prompt works better on some models than others.
33
-
34
- ### Circular verification pitfall
35
-
36
- Models that do write verify() functions sometimes verify against their own wrong model rather than the problem statement. The verify() re-implements the same bugs as the solver constraints. Prompt language should explicitly require deriving verification checks from the problem text, not from the model code.
37
-
38
- ### Key takeaway
39
-
40
- Tool descriptions are not documentation for humans — they are **behavioral instructions for the model**. Treat them as part of the prompt engineering, not as code comments to be minimized.
41
-
42
- **Updated**: 2026-01-31
43
-
44
- ## Example
45
-
46
- ```python
47
- # BAD: Minimal description (causes performance regression)
48
- PYTHON_EXEC_TOOL = Tool(
49
- name="python_exec",
50
- description="Execute Python code in a persistent IPython kernel.",
51
- parameters={
52
- "type": "object",
53
- "properties": {
54
- "code": {"type": "string", "description": "Python code to execute."},
55
- },
56
- "required": ["code"],
57
- },
58
- function=python_exec,
59
- )
60
-
61
- # GOOD: Verbose LangGraph-style description (proven performance)
62
- PYTHON_EXEC_TOOL = Tool(
63
- name="python_exec",
64
- description=(
65
- "Execute Python code in a persistent IPython kernel.\n\n"
66
- "IMPORTANT: The kernel maintains state between executions!\n"
67
- "- Variables, functions, and imports persist across calls\n"
68
- "- Use print() to see output, or the last expression will be returned\n"
69
- "- The kernel runs in the working directory context\n\n"
70
- "Example:\n"
71
- " First call: x = 5\n"
72
- ' Second call: print(x) # Returns: {"success": true, "stdout": "5"}\n\n'
73
- " First call: def add(a, b): return a + b\n"
74
- ' Second call: add(3, 4) # Returns: {"success": true, "result": "7"}\n\n'
75
- "The code executes in the working directory context, so you can read/write files\n"
76
- "using relative paths.\n\n"
77
- "Args:\n"
78
- " code: Python code to execute. Multi-line code is supported.\n\n"
79
- "Returns:\n"
80
- " JSON string with execution results:\n"
81
- " - success: boolean indicating if execution succeeded\n"
82
- " - stdout: captured print output (if any)\n"
83
- " - result: the last expression's value (if any)\n"
84
- " - stderr: warnings (if any)\n"
85
- " - error: error message (if execution failed)"
86
- ),
87
- parameters={
88
- "type": "object",
89
- "properties": {
90
- "code": {
91
- "type": "string",
92
- "title": "Code",
93
- },
94
- },
95
- "required": ["code"],
96
- "title": "python_exec",
97
- },
98
- function=python_exec,
99
- )
100
- ```
101
-
102
- ## Reference
103
-
104
- - Regression discovered: 2026-01-31 during benchmark testing of dev branch
105
- - Fix verified in: `/Users/szeider/temp/codertest/agentic-python-coder-dev/`
106
- - Production file: `coder/src/agentic_python_coder/tools.py`
107
- - Related: E001_frameworkless_react_agent.md (migration context)