agentnova 0.3.6__tar.gz → 0.3.8__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 (89) hide show
  1. {agentnova-0.3.6 → agentnova-0.3.8}/PKG-INFO +44 -13
  2. {agentnova-0.3.6 → agentnova-0.3.8}/README.md +43 -12
  3. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/__init__.py +1 -1
  4. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/acp_plugin.py +7 -3
  5. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/agent.py +235 -20
  6. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/backends/__init__.py +5 -4
  7. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/backends/ollama.py +49 -39
  8. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/cli.py +132 -101
  9. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/colors.py +203 -203
  10. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/helpers.py +38 -20
  11. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/tool_cache.py +223 -188
  12. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/types.py +5 -5
  13. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/00_basic_agent.py +29 -5
  14. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/01_quick_diagnostic.py +9 -8
  15. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/02_tool_test.py +221 -52
  16. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/03_reasoning_test.py +39 -3
  17. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/04_gsm8k_benchmark.py +39 -3
  18. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/05_common_sense.py +39 -3
  19. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/06_causal_reasoning.py +39 -3
  20. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/07_logical_deduction.py +39 -3
  21. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/08_reading_comprehension.py +39 -3
  22. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/09_general_knowledge.py +39 -3
  23. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/10_implicit_reasoning.py +39 -3
  24. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/11_analogical_reasoning.py +39 -3
  25. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/loader.py +5 -2
  26. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/SKILL.md +1 -0
  27. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/soul/loader.py +12 -4
  28. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/souls/nova-helper/SOUL.md +29 -1
  29. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/tools/sandboxed_repl.py +4 -5
  30. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/PKG-INFO +44 -13
  31. {agentnova-0.3.6 → agentnova-0.3.8}/pyproject.toml +1 -1
  32. {agentnova-0.3.6 → agentnova-0.3.8}/LICENSE +0 -0
  33. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/__main__.py +0 -0
  34. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/agent_mode.py +0 -0
  35. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/backends/base.py +0 -0
  36. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/backends/bitnet.py +0 -0
  37. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/config.py +0 -0
  38. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/__init__.py +0 -0
  39. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/args_normal.py +0 -0
  40. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/error_recovery.py +0 -0
  41. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/math_prompts.py +0 -0
  42. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/memory.py +0 -0
  43. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/model_config.py +0 -0
  44. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/model_family_config.py +0 -0
  45. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/models.py +0 -0
  46. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/openresponses.py +0 -0
  47. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/prompts.py +0 -0
  48. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/tool_parse.py +0 -0
  49. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/__init__.py +0 -0
  50. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/model_discovery.py +0 -0
  51. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/orchestrator.py +0 -0
  52. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/py.typed +0 -0
  53. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/shared_args.py +0 -0
  54. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/__init__.py +0 -0
  55. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/acp/SKILL.md +0 -0
  56. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/datetime/SKILL.md +0 -0
  57. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/__init__.py +0 -0
  58. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/aggregate_benchmark.py +0 -0
  59. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/generate_report.py +0 -0
  60. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/improve_description.py +0 -0
  61. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/init_skill.py +0 -0
  62. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/package_skill.py +0 -0
  63. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/quick_validate.py +0 -0
  64. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/run_eval.py +0 -0
  65. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/run_loop.py +0 -0
  66. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/security_scan.py +0 -0
  67. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/test_package_skill.py +0 -0
  68. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/test_quick_validate.py +0 -0
  69. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/utils.py +0 -0
  70. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/validate.py +0 -0
  71. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/web-search/SKILL.md +0 -0
  72. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/soul/__init__.py +0 -0
  73. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/soul/types.py +0 -0
  74. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/souls/nova-helper/IDENTITY.md +0 -0
  75. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/souls/nova-helper/STYLE.md +0 -0
  76. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/souls/nova-helper/soul.json +0 -0
  77. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/tools/__init__.py +0 -0
  78. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/tools/builtins.py +0 -0
  79. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/tools/registry.py +0 -0
  80. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/SOURCES.txt +0 -0
  81. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/dependency_links.txt +0 -0
  82. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/entry_points.txt +0 -0
  83. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/requires.txt +0 -0
  84. {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/top_level.txt +0 -0
  85. {agentnova-0.3.6 → agentnova-0.3.8}/localclaw/__init__.py +0 -0
  86. {agentnova-0.3.6 → agentnova-0.3.8}/localclaw-redirect/localclaw/__init__.py +0 -0
  87. {agentnova-0.3.6 → agentnova-0.3.8}/setup.cfg +0 -0
  88. {agentnova-0.3.6 → agentnova-0.3.8}/tests/test_agent.py +0 -0
  89. {agentnova-0.3.6 → agentnova-0.3.8}/tests/test_spec_compliance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentnova
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: ⚛️ AgentNova - A minimal, hackable agentic framework for local LLM inference
5
5
  Author-email: VTSTech <veritas@vts-tech.org>
6
6
  Maintainer-email: VTSTech <veritas@vts-tech.org>
@@ -32,7 +32,7 @@ Requires-Dist: black>=23.0; extra == "dev"
32
32
  Requires-Dist: ruff>=0.1.0; extra == "dev"
33
33
  Dynamic: license-file
34
34
 
35
- # ⚛️ AgentNova R03.6
35
+ # ⚛️ AgentNova R03.8
36
36
 
37
37
  **Status: Alpha**
38
38
 
@@ -49,7 +49,8 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
49
49
 
50
50
  [![License](https://img.shields.io/badge/License-MIT-blue)](#license) [![Go to Python website](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FVTSTech%2FAgentNova%2Frefs%2Fheads%2Fmain%2Fpyproject.toml&query=project.requires-python&label=python&logo=python&logoColor=white)](https://python.org)
51
51
 
52
- <img width="1378" height="996" alt="image" src="https://github.com/user-attachments/assets/0fe69695-73d9-4ce1-999f-08443f879971" />
52
+ <img width="1314" height="993" alt="image" src="https://github.com/user-attachments/assets/586b1a44-441e-41ae-85c2-b65b6a0816b9" />
53
+ <img width="1063" height="574" alt="image" src="https://github.com/user-attachments/assets/eab6f4ad-810c-4741-b637-e120f9ccb974" />
53
54
 
54
55
  ## 📚 Documentation
55
56
 
@@ -63,7 +64,7 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
63
64
 
64
65
  - **Zero dependencies** — Uses Python stdlib only (urllib for HTTP)
65
66
  - **Ollama + BitNet backends** — Switch with `--backend` flag
66
- - **Dual API support** — OpenResponses (`--api resp`) and Chat-Completions (`--api comp`)
67
+ - **Dual API support** — OpenResponses (`--api openre`) and OpenAI Chat-Completions (`--api openai`)
67
68
  - **Three-tier tool support** — Native, ReAct, or none (auto-detected)
68
69
  - **Small model optimized** — Fuzzy matching, argument normalization
69
70
  - **Built-in security** — Path validation, command blocklist, SSRF protection
@@ -99,8 +100,8 @@ agentnova chat -m qwen2.5:0.5b --tools calculator,shell
99
100
  # Autonomous agent mode
100
101
  agentnova agent -m qwen2.5:7b --tools calculator,shell,write_file
101
102
 
102
- # Use Chat-Completions API (OpenAI-compatible)
103
- agentnova chat -m qwen2.5:0.5b --api comp
103
+ # Use OpenAI Chat-Completions API
104
+ agentnova chat -m qwen2.5:0.5b --api openai
104
105
 
105
106
  # List available models
106
107
  agentnova models
@@ -138,7 +139,7 @@ from agentnova.backends import get_backend
138
139
  from agentnova.core.types import ApiMode
139
140
 
140
141
  # Use Chat-Completions mode with streaming
141
- backend = get_backend("ollama", api_mode=ApiMode.COMPLETIONS)
142
+ backend = get_backend("ollama", api_mode=ApiMode.OPENAI)
142
143
 
143
144
  for chunk in backend.generate_completions_stream(
144
145
  model="qwen2.5:0.5b",
@@ -261,7 +262,7 @@ agentnova config --urls # Show only URLs
261
262
 
262
263
  | Option | Description |
263
264
  |--------|-------------|
264
- | `--api resp\|comp` | API mode: OpenResponses (default) or Chat-Completions |
265
+ | `--api openre\|openai` | API mode: OpenResponses (default) or OpenAI Chat-Completions |
265
266
  | `--response-format text\|json` | Response format (Chat-Completions mode) |
266
267
  | `--truncation auto\|disabled` | Truncation behavior for long responses |
267
268
  | `--soul <path>` | Load Soul Spec persona package |
@@ -283,7 +284,7 @@ agentnova run "What is 2+2?"
283
284
 
284
285
  ## Tests & Examples
285
286
 
286
- AgentNova includes a suite of tests for validating agent capabilities:
287
+ AgentNova includes a comprehensive suite of tests for validating agent capabilities across reasoning, knowledge, and tool usage:
287
288
 
288
289
  ```bash
289
290
  # Basic agent test (no tools)
@@ -292,7 +293,7 @@ python -m agentnova.examples.00_basic_agent
292
293
  # Quick 5-question diagnostic
293
294
  python -m agentnova.examples.01_quick_diagnostic
294
295
 
295
- # Tool usage tests (calculator, shell, datetime)
296
+ # Tool usage tests (calculator, shell, datetime, file, python_repl)
296
297
  python -m agentnova.examples.02_tool_test
297
298
 
298
299
  # Logic and reasoning tests (BBH-style)
@@ -300,16 +301,45 @@ python -m agentnova.examples.03_reasoning_test
300
301
 
301
302
  # GSM8K math benchmark (50 questions)
302
303
  python -m agentnova.examples.04_gsm8k_benchmark
304
+
305
+ # Common sense reasoning (BIG-bench)
306
+ python -m agentnova.examples.05_common_sense
307
+
308
+ # Causal reasoning (BIG-bench)
309
+ python -m agentnova.examples.06_causal_reasoning
310
+
311
+ # Logical deduction (BIG-bench)
312
+ python -m agentnova.examples.07_logical_deduction
313
+
314
+ # Reading comprehension
315
+ python -m agentnova.examples.08_reading_comprehension
316
+
317
+ # General knowledge (BIG-bench)
318
+ python -m agentnova.examples.09_general_knowledge
319
+
320
+ # Implicit reasoning
321
+ python -m agentnova.examples.10_implicit_reasoning
322
+
323
+ # Analogical reasoning
324
+ python -m agentnova.examples.11_analogical_reasoning
303
325
  ```
304
326
 
305
327
  ### Test Categories
306
328
 
307
329
  | Test | Questions | Focus |
308
330
  |------|-----------|-------|
331
+ | Basic Agent | 1 | Single prompt, no tools |
309
332
  | Quick Diagnostic | 5 | Calculator tool, multi-step reasoning |
310
- | Tool Test | 10 | Calculator, shell, datetime tools |
311
- | Reasoning Test | 13 | Logic, deduction, patterns, spatial |
333
+ | Tool Test | 10 | Calculator, shell, datetime, file, python_repl tools |
334
+ | Reasoning Test | 14 | Logic, deduction, patterns, spatial |
312
335
  | GSM8K Benchmark | 50 | Math word problems |
336
+ | Common Sense | 25 | Physical properties, everyday reasoning |
337
+ | Causal Reasoning | 25 | Cause and effect relationships |
338
+ | Logical Deduction | 25 | Formal logic puzzles |
339
+ | Reading Comprehension | 25 | Passage-based Q&A |
340
+ | General Knowledge | 25 | Science, history, geography |
341
+ | Implicit Reasoning | 25 | Unstated assumptions and inference |
342
+ | Analogical Reasoning | 25 | Pattern matching and analogies |
313
343
 
314
344
  ### Benchmark Results (Quick Diagnostic)
315
345
 
@@ -318,8 +348,9 @@ python -m agentnova.examples.04_gsm8k_benchmark
318
348
  | functiongemma:270m | 5/5 (100%) | ~20s | native |
319
349
  | granite4:350m | 5/5 (100%) | ~50s | native |
320
350
  | qwen2.5:0.5b | 5/5 (100%) | 38s | native |
321
- | qwen2.5-coder:0.5b | 5/5 (100%) | 93s | react |
351
+ | qwen2.5-coder:0.5b | 5/5 (100%) | 93s | native |
322
352
  | qwen3:0.6b | 5/5 (100%) | 70s | react |
353
+ | deepseek-r1:1.5b | 5/5 (100%) | ~305s | native |
323
354
 
324
355
  All tested models achieve 100% on the Quick Diagnostic. Native models are ~2x faster than ReAct models due to direct API tool calling.
325
356
 
@@ -1,4 +1,4 @@
1
- # ⚛️ AgentNova R03.6
1
+ # ⚛️ AgentNova R03.8
2
2
 
3
3
  **Status: Alpha**
4
4
 
@@ -15,7 +15,8 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
15
15
 
16
16
  [![License](https://img.shields.io/badge/License-MIT-blue)](#license) [![Go to Python website](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FVTSTech%2FAgentNova%2Frefs%2Fheads%2Fmain%2Fpyproject.toml&query=project.requires-python&label=python&logo=python&logoColor=white)](https://python.org)
17
17
 
18
- <img width="1378" height="996" alt="image" src="https://github.com/user-attachments/assets/0fe69695-73d9-4ce1-999f-08443f879971" />
18
+ <img width="1314" height="993" alt="image" src="https://github.com/user-attachments/assets/586b1a44-441e-41ae-85c2-b65b6a0816b9" />
19
+ <img width="1063" height="574" alt="image" src="https://github.com/user-attachments/assets/eab6f4ad-810c-4741-b637-e120f9ccb974" />
19
20
 
20
21
  ## 📚 Documentation
21
22
 
@@ -29,7 +30,7 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
29
30
 
30
31
  - **Zero dependencies** — Uses Python stdlib only (urllib for HTTP)
31
32
  - **Ollama + BitNet backends** — Switch with `--backend` flag
32
- - **Dual API support** — OpenResponses (`--api resp`) and Chat-Completions (`--api comp`)
33
+ - **Dual API support** — OpenResponses (`--api openre`) and OpenAI Chat-Completions (`--api openai`)
33
34
  - **Three-tier tool support** — Native, ReAct, or none (auto-detected)
34
35
  - **Small model optimized** — Fuzzy matching, argument normalization
35
36
  - **Built-in security** — Path validation, command blocklist, SSRF protection
@@ -65,8 +66,8 @@ agentnova chat -m qwen2.5:0.5b --tools calculator,shell
65
66
  # Autonomous agent mode
66
67
  agentnova agent -m qwen2.5:7b --tools calculator,shell,write_file
67
68
 
68
- # Use Chat-Completions API (OpenAI-compatible)
69
- agentnova chat -m qwen2.5:0.5b --api comp
69
+ # Use OpenAI Chat-Completions API
70
+ agentnova chat -m qwen2.5:0.5b --api openai
70
71
 
71
72
  # List available models
72
73
  agentnova models
@@ -104,7 +105,7 @@ from agentnova.backends import get_backend
104
105
  from agentnova.core.types import ApiMode
105
106
 
106
107
  # Use Chat-Completions mode with streaming
107
- backend = get_backend("ollama", api_mode=ApiMode.COMPLETIONS)
108
+ backend = get_backend("ollama", api_mode=ApiMode.OPENAI)
108
109
 
109
110
  for chunk in backend.generate_completions_stream(
110
111
  model="qwen2.5:0.5b",
@@ -227,7 +228,7 @@ agentnova config --urls # Show only URLs
227
228
 
228
229
  | Option | Description |
229
230
  |--------|-------------|
230
- | `--api resp\|comp` | API mode: OpenResponses (default) or Chat-Completions |
231
+ | `--api openre\|openai` | API mode: OpenResponses (default) or OpenAI Chat-Completions |
231
232
  | `--response-format text\|json` | Response format (Chat-Completions mode) |
232
233
  | `--truncation auto\|disabled` | Truncation behavior for long responses |
233
234
  | `--soul <path>` | Load Soul Spec persona package |
@@ -249,7 +250,7 @@ agentnova run "What is 2+2?"
249
250
 
250
251
  ## Tests & Examples
251
252
 
252
- AgentNova includes a suite of tests for validating agent capabilities:
253
+ AgentNova includes a comprehensive suite of tests for validating agent capabilities across reasoning, knowledge, and tool usage:
253
254
 
254
255
  ```bash
255
256
  # Basic agent test (no tools)
@@ -258,7 +259,7 @@ python -m agentnova.examples.00_basic_agent
258
259
  # Quick 5-question diagnostic
259
260
  python -m agentnova.examples.01_quick_diagnostic
260
261
 
261
- # Tool usage tests (calculator, shell, datetime)
262
+ # Tool usage tests (calculator, shell, datetime, file, python_repl)
262
263
  python -m agentnova.examples.02_tool_test
263
264
 
264
265
  # Logic and reasoning tests (BBH-style)
@@ -266,16 +267,45 @@ python -m agentnova.examples.03_reasoning_test
266
267
 
267
268
  # GSM8K math benchmark (50 questions)
268
269
  python -m agentnova.examples.04_gsm8k_benchmark
270
+
271
+ # Common sense reasoning (BIG-bench)
272
+ python -m agentnova.examples.05_common_sense
273
+
274
+ # Causal reasoning (BIG-bench)
275
+ python -m agentnova.examples.06_causal_reasoning
276
+
277
+ # Logical deduction (BIG-bench)
278
+ python -m agentnova.examples.07_logical_deduction
279
+
280
+ # Reading comprehension
281
+ python -m agentnova.examples.08_reading_comprehension
282
+
283
+ # General knowledge (BIG-bench)
284
+ python -m agentnova.examples.09_general_knowledge
285
+
286
+ # Implicit reasoning
287
+ python -m agentnova.examples.10_implicit_reasoning
288
+
289
+ # Analogical reasoning
290
+ python -m agentnova.examples.11_analogical_reasoning
269
291
  ```
270
292
 
271
293
  ### Test Categories
272
294
 
273
295
  | Test | Questions | Focus |
274
296
  |------|-----------|-------|
297
+ | Basic Agent | 1 | Single prompt, no tools |
275
298
  | Quick Diagnostic | 5 | Calculator tool, multi-step reasoning |
276
- | Tool Test | 10 | Calculator, shell, datetime tools |
277
- | Reasoning Test | 13 | Logic, deduction, patterns, spatial |
299
+ | Tool Test | 10 | Calculator, shell, datetime, file, python_repl tools |
300
+ | Reasoning Test | 14 | Logic, deduction, patterns, spatial |
278
301
  | GSM8K Benchmark | 50 | Math word problems |
302
+ | Common Sense | 25 | Physical properties, everyday reasoning |
303
+ | Causal Reasoning | 25 | Cause and effect relationships |
304
+ | Logical Deduction | 25 | Formal logic puzzles |
305
+ | Reading Comprehension | 25 | Passage-based Q&A |
306
+ | General Knowledge | 25 | Science, history, geography |
307
+ | Implicit Reasoning | 25 | Unstated assumptions and inference |
308
+ | Analogical Reasoning | 25 | Pattern matching and analogies |
279
309
 
280
310
  ### Benchmark Results (Quick Diagnostic)
281
311
 
@@ -284,8 +314,9 @@ python -m agentnova.examples.04_gsm8k_benchmark
284
314
  | functiongemma:270m | 5/5 (100%) | ~20s | native |
285
315
  | granite4:350m | 5/5 (100%) | ~50s | native |
286
316
  | qwen2.5:0.5b | 5/5 (100%) | 38s | native |
287
- | qwen2.5-coder:0.5b | 5/5 (100%) | 93s | react |
317
+ | qwen2.5-coder:0.5b | 5/5 (100%) | 93s | native |
288
318
  | qwen3:0.6b | 5/5 (100%) | 70s | react |
319
+ | deepseek-r1:1.5b | 5/5 (100%) | ~305s | native |
289
320
 
290
321
  All tested models achieve 100% on the Quick Diagnostic. Native models are ~2x faster than ReAct models due to direct API tool calling.
291
322
 
@@ -28,7 +28,7 @@ Example Usage:
28
28
  agent = Agent(model="qwen2.5:0.5b", soul="/path/to/soul/package")
29
29
  """
30
30
 
31
- __version__ = "0.3.6"
31
+ __version__ = "0.3.8"
32
32
  __author__ = "VTSTech"
33
33
  __status__ = "Alpha"
34
34
 
@@ -281,6 +281,10 @@ class ACPPlugin:
281
281
  "get_note": "READ",
282
282
  "list_notes": "READ",
283
283
  "list_directory": "READ",
284
+ # ACP v1.0.4: A2A agent-to-agent communication
285
+ "a2a_send": "A2A",
286
+ "a2a_request": "A2A",
287
+ "a2a_response": "A2A",
284
288
  }
285
289
 
286
290
  # v1.0.4: Auto-derive capabilities from tool action map if not explicitly provided
@@ -1089,11 +1093,11 @@ class ACPPlugin:
1089
1093
  """Format tool arguments as a target string for ACP."""
1090
1094
  # Tool-specific formatting
1091
1095
  if tool_name == "read_file":
1092
- return args.get("path", "unknown")
1096
+ return args.get("file_path", args.get("path", "unknown"))
1093
1097
  elif tool_name == "write_file":
1094
- return args.get("path", "unknown")
1098
+ return args.get("file_path", args.get("path", "unknown"))
1095
1099
  elif tool_name == "edit_file":
1096
- return args.get("path", "unknown")
1100
+ return args.get("file_path", args.get("path", "unknown"))
1097
1101
  elif tool_name == "shell":
1098
1102
  return args.get("command", "")[:100]
1099
1103
  elif tool_name == "calculator":
@@ -40,6 +40,7 @@ from .core.openresponses import (
40
40
  MessageItem, FunctionCallItem, FunctionCallOutputItem, ReasoningItem,
41
41
  OutputText, InputText,
42
42
  RequestConfig, Error,
43
+ EventType, ResponseEvent, OutputItemEvent,
43
44
  create_message_item, create_function_call_item, create_function_call_output,
44
45
  create_function_call_output_item,
45
46
  stream_response_events,
@@ -132,7 +133,7 @@ class Agent:
132
133
  system_prompt: Custom system prompt (overrides soul)
133
134
  soul: Path to Soul Spec package (default: "nova-helper")
134
135
  soul_level: Progressive disclosure level for soul (1-3)
135
- num_ctx: Context window size in tokens (default: 4096)
136
+ num_ctx: Context window size in tokens (default: 8192)
136
137
  temperature: Sampling temperature (default: model-specific)
137
138
  top_p: Nucleus sampling probability (default: model-specific)
138
139
  num_predict: Maximum tokens to generate (default: model-specific)
@@ -143,12 +144,12 @@ class Agent:
143
144
  self.model = model
144
145
  self.max_steps = max_steps
145
146
  self.debug = debug
146
- # Get num_ctx from: explicit param > config/env > default 4096
147
+ # Get num_ctx from: explicit param > config/env > default 8192
147
148
  if num_ctx is not None:
148
149
  self.num_ctx = num_ctx
149
150
  else:
150
151
  config = get_config()
151
- self.num_ctx = config.num_ctx if config.num_ctx else 4096
152
+ self.num_ctx = config.num_ctx if config.num_ctx else 8192
152
153
 
153
154
  # Generation parameters (use model defaults if not specified)
154
155
  self._temperature = temperature
@@ -314,7 +315,7 @@ class Agent:
314
315
  @property
315
316
  def _is_comp_mode(self) -> bool:
316
317
  """Check if backend is using Chat-Completions (comp) API mode."""
317
- return hasattr(self.backend, 'api_mode') and self.backend.api_mode == ApiMode.COMPLETIONS
318
+ return hasattr(self.backend, 'api_mode') and self.backend.api_mode == ApiMode.OPENAI
318
319
 
319
320
  def _log_openresponses(self, msg: str) -> None:
320
321
  """Log OpenResponses debug message only when not in comp mode."""
@@ -441,6 +442,31 @@ Final Answer: <the answer>
441
442
  tokens = gen_response.get("usage", {}).get("total_tokens", 0)
442
443
  total_tokens += tokens
443
444
 
445
+ # OpenResponses: Handle finish_reason from backend
446
+ finish_reason = gen_response.get("_finish_reason", "stop")
447
+ if finish_reason == "length":
448
+ # Token budget exhausted — response is incomplete
449
+ if self.debug:
450
+ print(f" [OpenResponses] finish_reason='length' — marking incomplete")
451
+ steps.append(StepResult(
452
+ type=StepResultType.MAX_STEPS,
453
+ content="Response truncated: token limit reached",
454
+ tokens_used=tokens,
455
+ ))
456
+ response.mark_incomplete()
457
+ break
458
+ elif finish_reason == "content_filter":
459
+ # Content was filtered — response failed
460
+ if self.debug:
461
+ print(f" [OpenResponses] finish_reason='content_filter' — marking failed")
462
+ steps.append(StepResult(
463
+ type=StepResultType.ERROR,
464
+ error="Response blocked by content filter",
465
+ tokens_used=tokens,
466
+ ))
467
+ response.mark_failed({"message": "Content filtered by provider", "type": "content_filter"})
468
+ break
469
+
444
470
  if self.debug:
445
471
  print(f" Content: {content[:200] if content else '(empty)'}...")
446
472
  print(f" Native tool calls: {native_tool_calls}")
@@ -873,6 +899,10 @@ Final Answer: <the answer>
873
899
  OpenResponses specification. It yields Server-Sent Events (SSE) that
874
900
  describe the response lifecycle and content deltas.
875
901
 
902
+ IMPORTANT: The agentic loop is fully supported during streaming.
903
+ When the model produces a tool call, it is executed and the loop
904
+ continues, streaming the next model response.
905
+
876
906
  SSE Event Sequence (per OpenResponses spec):
877
907
  1. response.queued - Response is queued
878
908
  2. response.in_progress - Response started
@@ -895,6 +925,8 @@ Final Answer: <the answer>
895
925
  for sse_event in agent.run_stream("Hello!"):
896
926
  print(sse_event) # SSE formatted event
897
927
  """
928
+ start_time = time.time()
929
+
898
930
  # Create OpenResponses Response object
899
931
  response = Response(
900
932
  model=self.model,
@@ -903,9 +935,8 @@ Final Answer: <the answer>
903
935
  allowed_tools=self._allowed_tools or [],
904
936
  )
905
937
 
906
- if self.debug and not self._is_comp_mode:
907
- print(f"\n[OpenResponses] Response created: id={response.id}")
908
- print(f"[OpenResponses] Response status: {response.status.value}")
938
+ if self.debug:
939
+ print(f"\n[OpenResponses stream] Response created: id={response.id}")
909
940
 
910
941
  # Add user prompt to memory
911
942
  self.memory.add("user", prompt)
@@ -914,22 +945,190 @@ Final Answer: <the answer>
914
945
  user_item = create_message_item("user", prompt)
915
946
  response.input.append(user_item)
916
947
 
917
- if self.debug and not self._is_comp_mode:
918
- print(f"[OpenResponses] Input item added: id={user_item.id}, type={user_item.type}, role={user_item.role}")
919
-
920
948
  if self.debug:
921
- print(f"\n[AgentNova] Model: {self.model}")
922
- print(f"[AgentNova] Backend: {self.backend.base_url}")
923
- print(f"[AgentNova] tool_choice: {self.tool_choice.type.value}")
924
- print(f"[AgentNova] Tools: {self.tools.names()}")
925
- print(f"[AgentNova] Prompt (streaming): {prompt}\n")
949
+ print(f"\n[AgentNova stream] Model: {self.model}")
950
+ print(f"[AgentNova stream] Backend: {self.backend.base_url}")
951
+ print(f"[AgentNova stream] tool_choice: {self.tool_choice.type.value}")
952
+ print(f"[AgentNova stream] Tools: {self.tools.names()}")
953
+ print(f"[AgentNova stream] Prompt: {prompt}\n")
954
+
955
+ # OpenResponses: Agentic Loop (streaming variant)
956
+ # Stream model output, check for tool calls, execute them, repeat.
957
+ _expecting_final_answer = False
958
+ _last_successful_result = None
959
+ tool_call_count = 0
960
+
961
+ for step_num in range(self.max_steps):
962
+ if self.debug:
963
+ print(f"[Stream Step {step_num + 1}]")
964
+
965
+ # Collect the full streamed response
966
+ full_content = ""
967
+
968
+ # Stream model response, collecting content for tool-call detection
969
+ try:
970
+ for chunk in self._generate_stream_chunks(prompt):
971
+ full_content += chunk
972
+ except Exception as e:
973
+ if self.debug:
974
+ print(f" [Stream] ERROR: {e}")
975
+ # Emit failure event
976
+ response.mark_failed({"message": str(e), "type": "stream_error"})
977
+ fail_event = ResponseEvent(
978
+ type=EventType.RESPONSE_FAILED,
979
+ response=response,
980
+ )
981
+ yield fail_event.to_sse()
982
+ return
983
+
984
+ # Parse for tool calls (ReAct format)
985
+ tool_calls_found = []
986
+
987
+ if full_content:
988
+ parsed_calls = self._parser.parse(full_content)
989
+ for call in parsed_calls:
990
+ if hasattr(call, 'thought') and call.thought:
991
+ reasoning_item = ReasoningItem(
992
+ content=[OutputText(text=call.thought)]
993
+ )
994
+ reasoning_item.status = ItemStatus.COMPLETED
995
+ response.add_output_item(reasoning_item)
926
996
 
927
- # Get text chunks from backend streaming
928
- text_chunks = self._generate_stream_chunks(prompt)
997
+ tool_calls_found.append({
998
+ "name": call.name,
999
+ "arguments": call.arguments,
1000
+ "id": "",
1001
+ "final_answer": getattr(call, 'final_answer', None),
1002
+ })
929
1003
 
930
- # Wrap with OpenResponses SSE events using stream_response_events()
931
- for sse_event in stream_response_events(response, text_chunks, debug=self.debug):
932
- yield sse_event
1004
+ # ---- Execute tool calls if found ----
1005
+ if tool_calls_found:
1006
+ # Final Answer enforcement (same logic as run())
1007
+ if _expecting_final_answer and _last_successful_result is not None:
1008
+ text_chunks_gen = iter([_last_successful_result])
1009
+ for sse_event in stream_response_events(
1010
+ Response(model=self.model, status=ResponseStatus.IN_PROGRESS,
1011
+ tool_choice=self.tool_choice, allowed_tools=self._allowed_tools or []),
1012
+ text_chunks_gen, debug=self.debug,
1013
+ ):
1014
+ yield sse_event
1015
+ return
1016
+
1017
+ pending_final_answer = None
1018
+ self.memory.add("assistant", full_content)
1019
+
1020
+ for tc in tool_calls_found:
1021
+ tool_name = tc["name"]
1022
+ tool_args = tc["arguments"]
1023
+
1024
+ if tc.get("final_answer"):
1025
+ pending_final_answer = tc["final_answer"]
1026
+
1027
+ # Check allowed_tools
1028
+ if self._allowed_tools and tool_name not in self._allowed_tools:
1029
+ error_msg = f"Tool '{tool_name}' not in allowed_tools: {self._allowed_tools}"
1030
+ self.memory.add("user", f"Observation: Error: {error_msg}")
1031
+ continue
1032
+
1033
+ # Fuzzy match
1034
+ tool_name = self._parser._fuzzy_match_tool(tool_name)
1035
+
1036
+ # Create FunctionCallItem and emit SSE events
1037
+ fc_item = create_function_call_item(tool_name, tool_args)
1038
+ fc_item.status = ItemStatus.IN_PROGRESS
1039
+ response.add_output_item(fc_item)
1040
+ output_index = len(response.output) - 1
1041
+
1042
+ fc_added = OutputItemEvent(
1043
+ type=EventType.OUTPUT_ITEM_ADDED,
1044
+ item=fc_item,
1045
+ output_index=output_index,
1046
+ )
1047
+ yield fc_added.to_sse()
1048
+
1049
+ # Execute the tool
1050
+ result = self._execute_tool(tool_name, tool_args, prompt)
1051
+ tool_call_count += 1
1052
+
1053
+ fc_item.status = ItemStatus.COMPLETED
1054
+
1055
+ fc_done = OutputItemEvent(
1056
+ type=EventType.OUTPUT_ITEM_DONE,
1057
+ item=fc_item,
1058
+ output_index=output_index,
1059
+ )
1060
+ yield fc_done.to_sse()
1061
+
1062
+ # Create function_call_output
1063
+ fco_item = create_function_call_output(fc_item.call_id, str(result))
1064
+ response.add_output_item(fco_item)
1065
+
1066
+ # Build observation and add to memory
1067
+ is_error = is_error_result(str(result))
1068
+ observation_msg = build_enhanced_observation(
1069
+ tool_name=tool_name,
1070
+ result=str(result),
1071
+ tracker=self._error_tracker,
1072
+ available_tools=self.tools.names(),
1073
+ is_error=is_error,
1074
+ )
1075
+
1076
+ if is_error:
1077
+ _expecting_final_answer = False
1078
+ else:
1079
+ _expecting_final_answer = True
1080
+ _last_successful_result = str(result)
1081
+
1082
+ self.memory.add("user", observation_msg)
1083
+
1084
+ # Check for pending final answer
1085
+ if pending_final_answer:
1086
+ text_chunks_gen = iter([pending_final_answer])
1087
+ for sse_event in stream_response_events(
1088
+ Response(model=self.model, status=ResponseStatus.IN_PROGRESS,
1089
+ tool_choice=self.tool_choice, allowed_tools=self._allowed_tools or []),
1090
+ text_chunks_gen, debug=self.debug,
1091
+ ):
1092
+ yield sse_event
1093
+ return
1094
+
1095
+ # Continue the agentic loop (next streaming iteration)
1096
+ continue
1097
+
1098
+ # ---- No tool calls — stream final response ----
1099
+ # Check for Final Answer format
1100
+ if self._parser.is_final_answer(full_content):
1101
+ answer = self._parser.extract_final_answer(full_content)
1102
+ text_chunks_gen = iter([answer])
1103
+ else:
1104
+ text_chunks_gen = iter([full_content])
1105
+
1106
+ # Stream the final response with proper OpenResponses events
1107
+ final_response = Response(
1108
+ model=self.model,
1109
+ status=ResponseStatus.IN_PROGRESS,
1110
+ tool_choice=self.tool_choice,
1111
+ allowed_tools=self._allowed_tools or [],
1112
+ )
1113
+ # Carry over any items from previous loop iterations
1114
+ final_response.output = response.output
1115
+ final_response.input = response.input
1116
+ final_response.usage = response.usage
1117
+
1118
+ for sse_event in stream_response_events(final_response, text_chunks_gen, debug=self.debug):
1119
+ yield sse_event
1120
+
1121
+ # Only one pass needed when there are no tool calls
1122
+ return
1123
+
1124
+ else:
1125
+ # Max steps reached
1126
+ response.mark_incomplete()
1127
+ incomplete_event = ResponseEvent(
1128
+ type=EventType.RESPONSE_INCOMPLETE,
1129
+ response=response,
1130
+ )
1131
+ yield incomplete_event.to_sse()
933
1132
 
934
1133
  def _generate_stream_chunks(self, prompt: str) -> Generator[str, None, None]:
935
1134
  """
@@ -1076,6 +1275,11 @@ Final Answer: <the answer>
1076
1275
  if self._num_predict is not None:
1077
1276
  backend_kwargs["num_predict"] = self._num_predict
1078
1277
 
1278
+ # OpenResponses: Forward tool_choice to backend API
1279
+ # This allows the backend to enforce tool invocation constraints natively
1280
+ if self.tool_choice and self.tool_choice.type != ToolChoiceType.AUTO:
1281
+ backend_kwargs["tool_choice"] = self.tool_choice.to_dict()
1282
+
1079
1283
  # Pass tools for native tool calling (OpenResponses/ChatCompletions compliant)
1080
1284
  # ReAct parsing remains as fallback for models without native support
1081
1285
  tools_for_backend = self.tools.all() if self.tools and len(self.tools) > 0 else None
@@ -1101,6 +1305,17 @@ Final Answer: <the answer>
1101
1305
  **backend_kwargs,
1102
1306
  )
1103
1307
 
1308
+ # OpenResponses / Chat Completions: Handle finish_reason
1309
+ # Per spec, finish_reason affects response status:
1310
+ # "stop" → normal completion (default)
1311
+ # "length" → incomplete — token budget exhausted
1312
+ # "content_filter" → failed — content was filtered
1313
+ finish_reason = response.get("finish_reason", "stop")
1314
+ if self.debug:
1315
+ print(f" [DEBUG] finish_reason: {finish_reason}")
1316
+ # Store for caller to consume
1317
+ response["_finish_reason"] = finish_reason
1318
+
1104
1319
  if self.debug:
1105
1320
  print(f" [DEBUG] Response keys: {list(response.keys())}")
1106
1321
  print(f" [DEBUG] Content: {response.get('content', '')[:100]}...")
@@ -26,7 +26,7 @@ def get_backend(name: str, timeout: int | None = None, api_mode: ApiMode | str |
26
26
  Args:
27
27
  name: Backend name ("ollama", "bitnet")
28
28
  timeout: Request timeout in seconds (default: 120)
29
- api_mode: API mode ("resp" for OpenResponses/native, "comp" for Chat-Completions)
29
+ api_mode: API mode ("openre" for OpenResponses, "openai" for Chat-Completions)
30
30
  **kwargs: Backend-specific configuration
31
31
 
32
32
  Returns:
@@ -59,7 +59,7 @@ def get_backend(name: str, timeout: int | None = None, api_mode: ApiMode | str |
59
59
  return backend_class(**kwargs)
60
60
 
61
61
 
62
- def get_default_backend(name: str | None = None, api_mode: ApiMode | str | None = None) -> BaseBackend:
62
+ def get_default_backend(name: str | None = None, api_mode: ApiMode | str | None = None, timeout: int | None = None) -> BaseBackend:
63
63
  """
64
64
  Get a default backend instance.
65
65
 
@@ -67,13 +67,14 @@ def get_default_backend(name: str | None = None, api_mode: ApiMode | str | None
67
67
 
68
68
  Args:
69
69
  name: Backend name (optional, uses env var if not provided)
70
- api_mode: API mode ("resp" for OpenResponses/native, "comp" for Chat-Completions)
70
+ api_mode: API mode ("openre" for OpenResponses/native, "openai" for Chat-Completions)
71
+ timeout: Request timeout in seconds (default: 120)
71
72
 
72
73
  Returns:
73
74
  Backend instance with default configuration
74
75
  """
75
76
  backend_name = name or AGENTNOVA_BACKEND
76
- return get_backend(backend_name, api_mode=api_mode)
77
+ return get_backend(backend_name, api_mode=api_mode, timeout=timeout)
77
78
 
78
79
 
79
80
  def register_backend(name: str, backend_class: type[BaseBackend]) -> None: