agentnova 0.4.0__tar.gz → 0.4.2__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.
- {agentnova-0.4.0 → agentnova-0.4.2}/PKG-INFO +5 -9
- {agentnova-0.4.0 → agentnova-0.4.2}/README.md +4 -8
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/__init__.py +42 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/acp_plugin.py +99 -2
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/agent.py +51 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/agent_mode.py +23 -7
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/backends/__init__.py +7 -1
- agentnova-0.4.2/agentnova/backends/llama_server.py +540 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/cli.py +150 -198
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/config.py +29 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/error_recovery.py +74 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/prompts.py +39 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/00_basic_agent.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/01_quick_diagnostic.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/02_tool_test.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/03_reasoning_test.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/04_gsm8k_benchmark.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/05_common_sense.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/06_causal_reasoning.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/07_logical_deduction.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/08_reading_comprehension.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/09_general_knowledge.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/10_implicit_reasoning.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/11_analogical_reasoning.py +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/shared_args.py +137 -1
- agentnova-0.4.2/agentnova/skills/codebase-audit/SKILL.md +172 -0
- agentnova-0.4.2/agentnova/souls/nova-helper/AGENTS.md +98 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/souls/nova-helper/soul.json +3 -2
- agentnova-0.4.2/agentnova/souls/nova-skills/AGENTS.md +112 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/souls/nova-skills/soul.json +3 -2
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/tools/builtins.py +500 -2
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova.egg-info/PKG-INFO +5 -9
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova.egg-info/SOURCES.txt +4 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/pyproject.toml +1 -1
- {agentnova-0.4.0 → agentnova-0.4.2}/LICENSE +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/__main__.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/backends/base.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/backends/bitnet.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/backends/ollama.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/colors.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/__init__.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/args_normal.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/helpers.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/math_prompts.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/memory.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/model_config.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/model_family_config.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/models.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/openresponses.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/tool_cache.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/tool_parse.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/core/types.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/examples/__init__.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/model_discovery.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/orchestrator.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/py.typed +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/__init__.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/loader.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/SKILL.md +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/__init__.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/aggregate_benchmark.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/generate_report.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/improve_description.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/init_skill.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/package_skill.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/quick_validate.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/run_eval.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/run_loop.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/security_scan.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/test_package_skill.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/test_quick_validate.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/utils.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/skill-creator/scripts/validate.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/skills/test-harness/SKILL.md +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/soul/__init__.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/soul/loader.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/soul/types.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/souls/nova-helper/IDENTITY.md +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/souls/nova-helper/SOUL.md +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/souls/nova-helper/STYLE.md +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/souls/nova-skills/IDENTITY.md +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/souls/nova-skills/SOUL.md +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/souls/nova-skills/STYLE.md +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/tools/__init__.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/tools/registry.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova/tools/sandboxed_repl.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova.egg-info/dependency_links.txt +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova.egg-info/entry_points.txt +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova.egg-info/requires.txt +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/agentnova.egg-info/top_level.txt +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/localclaw/__init__.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/localclaw-redirect/localclaw/__init__.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/setup.cfg +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/tests/test_agent.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/tests/test_builtins.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/tests/test_security.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/tests/test_skills.py +0 -0
- {agentnova-0.4.0 → agentnova-0.4.2}/tests/test_spec_compliance.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentnova
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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 R04.
|
|
35
|
+
# ⚛️ AgentNova R04.2
|
|
36
36
|
|
|
37
37
|
**Status: Alpha**
|
|
38
38
|
|
|
@@ -56,9 +56,10 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
56
56
|
|
|
57
57
|
| Document | Description |
|
|
58
58
|
|----------|-------------|
|
|
59
|
-
| [
|
|
59
|
+
| [ARCH.md](https://github.com/VTSTech/AgentNova/blob/main/ARCH.md) | Technical documentation for developers (directory structure, core design, orchestrator modes) |
|
|
60
60
|
| [CHANGELOG.md](https://github.com/VTSTech/AgentNova/blob/main/CHANGELOG.md) | Version history and release notes (includes LocalClaw history) |
|
|
61
61
|
| [TESTS.md](https://github.com/VTSTech/AgentNova/blob/main/TESTS.md) | Benchmark results, model recommendations, and testing guide |
|
|
62
|
+
| [CREDITS.md](https://github.com/VTSTech/AgentNova/blob/main/CREDITS.md) | Acknowledges every project, inspiration, API, model creator, and specification that makes AgentNova possible |
|
|
62
63
|
|
|
63
64
|
## Features
|
|
64
65
|
|
|
@@ -376,10 +377,5 @@ MIT License - See LICENSE file for details.
|
|
|
376
377
|
|
|
377
378
|
## Contributing
|
|
378
379
|
|
|
379
|
-
Contributions welcome!
|
|
380
|
+
Contributions welcome!
|
|
380
381
|
|
|
381
|
-
## Acknowledgments
|
|
382
|
-
|
|
383
|
-
- Built for local inference with [Ollama](https://ollama.ai)
|
|
384
|
-
- Optimized for small, efficient models
|
|
385
|
-
- Inspired by ReAct and other agentic frameworks
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ⚛️ AgentNova R04.
|
|
1
|
+
# ⚛️ AgentNova R04.2
|
|
2
2
|
|
|
3
3
|
**Status: Alpha**
|
|
4
4
|
|
|
@@ -22,9 +22,10 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
22
22
|
|
|
23
23
|
| Document | Description |
|
|
24
24
|
|----------|-------------|
|
|
25
|
-
| [
|
|
25
|
+
| [ARCH.md](https://github.com/VTSTech/AgentNova/blob/main/ARCH.md) | Technical documentation for developers (directory structure, core design, orchestrator modes) |
|
|
26
26
|
| [CHANGELOG.md](https://github.com/VTSTech/AgentNova/blob/main/CHANGELOG.md) | Version history and release notes (includes LocalClaw history) |
|
|
27
27
|
| [TESTS.md](https://github.com/VTSTech/AgentNova/blob/main/TESTS.md) | Benchmark results, model recommendations, and testing guide |
|
|
28
|
+
| [CREDITS.md](https://github.com/VTSTech/AgentNova/blob/main/CREDITS.md) | Acknowledges every project, inspiration, API, model creator, and specification that makes AgentNova possible |
|
|
28
29
|
|
|
29
30
|
## Features
|
|
30
31
|
|
|
@@ -342,10 +343,5 @@ MIT License - See LICENSE file for details.
|
|
|
342
343
|
|
|
343
344
|
## Contributing
|
|
344
345
|
|
|
345
|
-
Contributions welcome!
|
|
346
|
+
Contributions welcome!
|
|
346
347
|
|
|
347
|
-
## Acknowledgments
|
|
348
|
-
|
|
349
|
-
- Built for local inference with [Ollama](https://ollama.ai)
|
|
350
|
-
- Optimized for small, efficient models
|
|
351
|
-
- Inspired by ReAct and other agentic frameworks
|
|
@@ -28,10 +28,51 @@ Example Usage:
|
|
|
28
28
|
agent = Agent(model="qwen2.5:0.5b", soul="/path/to/soul/package")
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
__version__ = "0.4.
|
|
31
|
+
__version__ = "0.4.2"
|
|
32
32
|
__author__ = "VTSTech"
|
|
33
33
|
__status__ = "Alpha"
|
|
34
34
|
|
|
35
|
+
|
|
36
|
+
def _get_git_short_hash() -> str:
|
|
37
|
+
"""
|
|
38
|
+
Get the short git commit hash of the repository.
|
|
39
|
+
|
|
40
|
+
Works when running from source (has .git directory).
|
|
41
|
+
Returns empty string when installed via pip or git is unavailable.
|
|
42
|
+
"""
|
|
43
|
+
import subprocess
|
|
44
|
+
try:
|
|
45
|
+
# Try to find the repo root from this file's location
|
|
46
|
+
# Walk up from agentnova/ looking for .git
|
|
47
|
+
import os
|
|
48
|
+
check_dir = os.path.dirname(os.path.abspath(__file__))
|
|
49
|
+
for _ in range(5): # don't walk up more than 5 levels
|
|
50
|
+
if os.path.isdir(os.path.join(check_dir, ".git")):
|
|
51
|
+
# We're inside a git repo — get the short hash
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
["git", "rev-parse", "--short", "HEAD"],
|
|
54
|
+
cwd=check_dir,
|
|
55
|
+
capture_output=True,
|
|
56
|
+
text=True,
|
|
57
|
+
timeout=5,
|
|
58
|
+
)
|
|
59
|
+
if result.returncode == 0:
|
|
60
|
+
return result.stdout.strip()
|
|
61
|
+
break
|
|
62
|
+
parent = os.path.dirname(check_dir)
|
|
63
|
+
if parent == check_dir:
|
|
64
|
+
break
|
|
65
|
+
check_dir = parent
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
return ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Append git commit hash to version if available
|
|
72
|
+
_git_hash = _get_git_short_hash()
|
|
73
|
+
if _git_hash:
|
|
74
|
+
__version__ = f"{__version__}-{_git_hash}"
|
|
75
|
+
|
|
35
76
|
from .agent import Agent
|
|
36
77
|
from .agent_mode import AgentMode, AgentState, TaskPlan
|
|
37
78
|
from .orchestrator import Orchestrator, AgentCard
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
This plugin connects AgentNova agents to an ACP (Agent Control Panel) server,
|
|
5
5
|
enabling real-time monitoring, token tracking, and STOP/Resume control.
|
|
6
6
|
|
|
7
|
-
ACP Specification: v1.0.
|
|
7
|
+
ACP Specification: v1.0.6 (A2A Compliance)
|
|
8
8
|
Compliance: Full (mandatory requirements, hints, orphan handling, nudge support, batch ops, shutdown, A2A, JSON-RPC 2.0, primary agent nudge delivery)
|
|
9
9
|
|
|
10
10
|
Features:
|
|
@@ -25,6 +25,11 @@ Features:
|
|
|
25
25
|
- contextId tracking for session continuity (1.0.4)
|
|
26
26
|
- primary_agent in /api/whoami response (1.0.5)
|
|
27
27
|
- Nudges delivered only to primary agent (1.0.5)
|
|
28
|
+
- CSRF protection optional, disabled by default (1.0.6)
|
|
29
|
+
- CORS preflight support (1.0.6)
|
|
30
|
+
- GET /api/nudge polling support (1.0.6)
|
|
31
|
+
- POST /api/todos/toggle (1.0.6)
|
|
32
|
+
- Bootstrap activity auto-completed (no orphans)
|
|
28
33
|
|
|
29
34
|
Usage:
|
|
30
35
|
from agentnova import Agent
|
|
@@ -246,6 +251,9 @@ class ACPPlugin:
|
|
|
246
251
|
self._capabilities_override = capabilities # Store for later, derive from tools if not provided
|
|
247
252
|
self.endpoint = endpoint # v1.0.4: A2A endpoint
|
|
248
253
|
|
|
254
|
+
# 1.0.6: CSRF disabled by default per spec §4.2
|
|
255
|
+
self._csrf_enabled: bool | None = None # Unknown until first request
|
|
256
|
+
|
|
249
257
|
self._csrf_token: str | None = None
|
|
250
258
|
self._csrf_expiry: float = 0
|
|
251
259
|
self._current_activity_id: str | None = None
|
|
@@ -339,7 +347,8 @@ class ACPPlugin:
|
|
|
339
347
|
"Authorization": f"Basic {self.auth}",
|
|
340
348
|
"Content-Type": "application/json",
|
|
341
349
|
}
|
|
342
|
-
if
|
|
350
|
+
# 1.0.6: Only send CSRF token if enabled (disabled by default per spec §4.2)
|
|
351
|
+
if method == "POST" and self._csrf_token and self._csrf_enabled:
|
|
343
352
|
headers["X-CSRF-Token"] = self._csrf_token
|
|
344
353
|
|
|
345
354
|
url = f"{self.base_url}{endpoint}"
|
|
@@ -377,6 +386,9 @@ class ACPPlugin:
|
|
|
377
386
|
now = time.time()
|
|
378
387
|
if self._csrf_token is None or (now - self._csrf_expiry) > 3000:
|
|
379
388
|
resp = self._request("/api/csrf-token")
|
|
389
|
+
# 1.0.6: CSRF is optional and disabled by default per spec §4.2
|
|
390
|
+
# Track the enabled state so we can skip header when not needed
|
|
391
|
+
self._csrf_enabled = resp.get("csrf_enabled", False)
|
|
380
392
|
token = resp.get("csrf_token")
|
|
381
393
|
# If CSRF is disabled, use empty string sentinel so we don't re-fetch
|
|
382
394
|
self._csrf_token = token if token else ""
|
|
@@ -889,11 +901,35 @@ class ACPPlugin:
|
|
|
889
901
|
"metadata": metadata,
|
|
890
902
|
})
|
|
891
903
|
|
|
904
|
+
def check_nudge(self) -> dict:
|
|
905
|
+
"""
|
|
906
|
+
Check if a nudge is pending without logging a new activity.
|
|
907
|
+
|
|
908
|
+
1.0.6: GET /api/nudge returns {nudge, has_pending} for polling.
|
|
909
|
+
Useful for checking nudge status between steps without
|
|
910
|
+
triggering a full /api/action call.
|
|
911
|
+
|
|
912
|
+
Returns
|
|
913
|
+
-------
|
|
914
|
+
dict
|
|
915
|
+
Response with 'nudge' and 'has_pending' fields
|
|
916
|
+
|
|
917
|
+
Example
|
|
918
|
+
-------
|
|
919
|
+
>>> result = acp.check_nudge()
|
|
920
|
+
>>> if result.get("has_pending"):
|
|
921
|
+
... print(f"Pending nudge: {result['nudge'].get('message')}")
|
|
922
|
+
>>> acp.ack_nudge()
|
|
923
|
+
"""
|
|
924
|
+
return self._request("/api/nudge")
|
|
925
|
+
|
|
892
926
|
def ack_nudge(self) -> dict:
|
|
893
927
|
"""
|
|
894
928
|
Acknowledge a pending nudge.
|
|
895
929
|
|
|
896
930
|
Call this after processing a nudge that had requires_ack=true.
|
|
931
|
+
|
|
932
|
+
1.0.6: Also clears the nudge by setting it to None server-side.
|
|
897
933
|
"""
|
|
898
934
|
resp = self._request("/api/nudge/ack", "POST", {})
|
|
899
935
|
self._pending_nudge = None
|
|
@@ -1636,6 +1672,67 @@ class ACPPlugin:
|
|
|
1636
1672
|
resp = self._request("/api/todos")
|
|
1637
1673
|
return resp.get("todos", [])
|
|
1638
1674
|
|
|
1675
|
+
def add_todo(self, content: str, priority: str = "medium", status: str = "pending") -> dict:
|
|
1676
|
+
"""
|
|
1677
|
+
Add a single TODO item (1.0.6).
|
|
1678
|
+
|
|
1679
|
+
Unlike sync_todos() which replaces the entire list, this adds one item.
|
|
1680
|
+
|
|
1681
|
+
Parameters
|
|
1682
|
+
----------
|
|
1683
|
+
content : str
|
|
1684
|
+
Task description
|
|
1685
|
+
priority : str
|
|
1686
|
+
"high", "medium" (default), or "low"
|
|
1687
|
+
status : str
|
|
1688
|
+
"pending" (default), "in_progress", or "completed"
|
|
1689
|
+
|
|
1690
|
+
Returns
|
|
1691
|
+
-------
|
|
1692
|
+
dict
|
|
1693
|
+
API response
|
|
1694
|
+
|
|
1695
|
+
Example
|
|
1696
|
+
-------
|
|
1697
|
+
>>> acp.add_todo("Fix the login bug", priority="high")
|
|
1698
|
+
>>> acp.add_todo("Run tests", priority="low", status="in_progress")
|
|
1699
|
+
"""
|
|
1700
|
+
metadata = self._build_metadata()
|
|
1701
|
+
return self._request("/api/todos/add", "POST", {
|
|
1702
|
+
"todo": {"content": content, "priority": priority, "status": status},
|
|
1703
|
+
"agent_name": self.agent_name,
|
|
1704
|
+
})
|
|
1705
|
+
|
|
1706
|
+
def toggle_todo(self, todo_id: str) -> dict:
|
|
1707
|
+
"""
|
|
1708
|
+
Toggle a TODO item's status between pending and completed (1.0.6).
|
|
1709
|
+
|
|
1710
|
+
Uses POST /api/todos/toggle which flips a TODO between
|
|
1711
|
+
pending ↔ completed in a single call.
|
|
1712
|
+
|
|
1713
|
+
Parameters
|
|
1714
|
+
----------
|
|
1715
|
+
todo_id : str
|
|
1716
|
+
The TODO item ID to toggle
|
|
1717
|
+
|
|
1718
|
+
Returns
|
|
1719
|
+
-------
|
|
1720
|
+
dict
|
|
1721
|
+
Response with 'todo' and 'toggled' fields
|
|
1722
|
+
"""
|
|
1723
|
+
return self._request("/api/todos/toggle", "POST", {
|
|
1724
|
+
"id": todo_id,
|
|
1725
|
+
})
|
|
1726
|
+
|
|
1727
|
+
def clear_completed_todos(self) -> dict:
|
|
1728
|
+
"""
|
|
1729
|
+
Clear completed TODOs (1.0.6).
|
|
1730
|
+
|
|
1731
|
+
Removes all TODOs with status="completed" from the list.
|
|
1732
|
+
"""
|
|
1733
|
+
return self._request("/api/todos/clear", "POST", {})
|
|
1734
|
+
|
|
1735
|
+
|
|
1639
1736
|
def bootstrap(self, claim_primary: bool = True) -> dict:
|
|
1640
1737
|
"""
|
|
1641
1738
|
Bootstrap ACP session - check status, establish identity, claim primary.
|
|
@@ -30,9 +30,12 @@ from .core.tool_parse import ToolParser
|
|
|
30
30
|
from .core.error_recovery import (
|
|
31
31
|
ErrorRecoveryTracker,
|
|
32
32
|
build_enhanced_observation,
|
|
33
|
+
build_retry_context,
|
|
33
34
|
is_error_result,
|
|
34
35
|
DEFAULT_MAX_CONSECUTIVE_FAILURES,
|
|
35
36
|
DEFAULT_MAX_TOTAL_FAILURES,
|
|
37
|
+
DEFAULT_MAX_TOOL_RETRIES,
|
|
38
|
+
DEFAULT_RETRY_ON_ERROR,
|
|
36
39
|
)
|
|
37
40
|
from .core.openresponses import (
|
|
38
41
|
Response, ResponseStatus, ItemStatus,
|
|
@@ -120,6 +123,9 @@ class Agent:
|
|
|
120
123
|
allowed_tools: list[str] | None = None,
|
|
121
124
|
# Skills injection
|
|
122
125
|
skills_prompt: str | None = None,
|
|
126
|
+
# Retry-with-error-feedback
|
|
127
|
+
retry_on_error: bool = DEFAULT_RETRY_ON_ERROR,
|
|
128
|
+
max_tool_retries: int = DEFAULT_MAX_TOOL_RETRIES,
|
|
123
129
|
**kwargs,
|
|
124
130
|
):
|
|
125
131
|
"""
|
|
@@ -142,6 +148,8 @@ class Agent:
|
|
|
142
148
|
tool_choice: Control tool invocation ("auto", "required", "none", or specific tool name)
|
|
143
149
|
allowed_tools: List of tools the model is allowed to invoke (subset of tools)
|
|
144
150
|
skills_prompt: Optional skill instructions to append to the system prompt
|
|
151
|
+
retry_on_error: Whether to retry failed tool calls with error feedback (default: True)
|
|
152
|
+
max_tool_retries: Maximum retries per tool call failure (default: 2)
|
|
145
153
|
**kwargs: Additional configuration
|
|
146
154
|
"""
|
|
147
155
|
self.model = model
|
|
@@ -159,6 +167,17 @@ class Agent:
|
|
|
159
167
|
self._top_p = top_p
|
|
160
168
|
self._num_predict = num_predict
|
|
161
169
|
|
|
170
|
+
# Retry-with-error-feedback
|
|
171
|
+
self._retry_on_error = retry_on_error
|
|
172
|
+
self._max_tool_retries = max_tool_retries
|
|
173
|
+
|
|
174
|
+
# Dangerous tool confirmation callback.
|
|
175
|
+
# When set, any tool with dangerous=True must be approved by
|
|
176
|
+
# this callback before execution. The callback receives
|
|
177
|
+
# (tool_name, args) and returns True (allow) or False (deny).
|
|
178
|
+
# If not set, dangerous tools execute without confirmation.
|
|
179
|
+
self._confirm_dangerous = kwargs.pop("confirm_dangerous", None)
|
|
180
|
+
|
|
162
181
|
# Initialize backend
|
|
163
182
|
if backend is None:
|
|
164
183
|
self.backend = get_default_backend()
|
|
@@ -320,6 +339,9 @@ class Agent:
|
|
|
320
339
|
max_consecutive_failures=DEFAULT_MAX_CONSECUTIVE_FAILURES,
|
|
321
340
|
max_total_failures=DEFAULT_MAX_TOTAL_FAILURES,
|
|
322
341
|
)
|
|
342
|
+
|
|
343
|
+
if self.debug:
|
|
344
|
+
print(f"[Agent] Retry on error: {self._retry_on_error}, max_tool_retries: {self._max_tool_retries}")
|
|
323
345
|
|
|
324
346
|
@property
|
|
325
347
|
def _is_comp_mode(self) -> bool:
|
|
@@ -649,6 +671,18 @@ Final Answer: <the answer>
|
|
|
649
671
|
name=tool_name,
|
|
650
672
|
content=str(result),
|
|
651
673
|
)
|
|
674
|
+
# Native tool calls also get retry context on error
|
|
675
|
+
if is_error and self._retry_on_error:
|
|
676
|
+
retry_msg = build_retry_context(
|
|
677
|
+
tool_name=tool_name,
|
|
678
|
+
tool_args=tool_args,
|
|
679
|
+
tracker=self._error_tracker,
|
|
680
|
+
max_tool_retries=self._max_tool_retries,
|
|
681
|
+
)
|
|
682
|
+
if retry_msg:
|
|
683
|
+
if self.debug:
|
|
684
|
+
print(f" [Retry Context] Adding retry hint for native tool call: {tool_name}")
|
|
685
|
+
self.memory.add("user", retry_msg)
|
|
652
686
|
else:
|
|
653
687
|
# Use error recovery module for enhanced observation
|
|
654
688
|
observation_msg = build_enhanced_observation(
|
|
@@ -656,7 +690,9 @@ Final Answer: <the answer>
|
|
|
656
690
|
result=str(result),
|
|
657
691
|
tracker=self._error_tracker,
|
|
658
692
|
available_tools=self.tools.names(),
|
|
659
|
-
is_error=is_error
|
|
693
|
+
is_error=is_error,
|
|
694
|
+
retry_on_error=self._retry_on_error,
|
|
695
|
+
tool_args=tool_args,
|
|
660
696
|
)
|
|
661
697
|
|
|
662
698
|
# Update expecting_final_answer flag
|
|
@@ -1091,6 +1127,8 @@ Final Answer: <the answer>
|
|
|
1091
1127
|
tracker=self._error_tracker,
|
|
1092
1128
|
available_tools=self.tools.names(),
|
|
1093
1129
|
is_error=is_error,
|
|
1130
|
+
retry_on_error=self._retry_on_error,
|
|
1131
|
+
tool_args=tool_args,
|
|
1094
1132
|
)
|
|
1095
1133
|
|
|
1096
1134
|
if is_error:
|
|
@@ -1356,12 +1394,24 @@ Final Answer: <the answer>
|
|
|
1356
1394
|
|
|
1357
1395
|
OpenResponses: Tool execution is straightforward - no synthesis.
|
|
1358
1396
|
Arguments must come from the model.
|
|
1397
|
+
|
|
1398
|
+
If the tool is marked dangerous=True and a confirm_dangerous
|
|
1399
|
+
callback is set, the callback is invoked before execution.
|
|
1400
|
+
Returning False from the callback blocks the tool call.
|
|
1359
1401
|
"""
|
|
1360
1402
|
tool = self.tools.get(name)
|
|
1361
1403
|
|
|
1362
1404
|
if tool is None:
|
|
1363
1405
|
return f"Error: Unknown tool '{name}'. Available tools: {self.tools.names()}"
|
|
1364
1406
|
|
|
1407
|
+
# Confirmation gate for dangerous tools
|
|
1408
|
+
if getattr(tool, 'dangerous', False) and self._confirm_dangerous is not None:
|
|
1409
|
+
if not self._confirm_dangerous(name, args):
|
|
1410
|
+
return (
|
|
1411
|
+
f"Tool '{name}' was blocked by user confirmation. "
|
|
1412
|
+
f"The tool is marked as dangerous and was not approved."
|
|
1413
|
+
)
|
|
1414
|
+
|
|
1365
1415
|
# Normalize arguments with tool-specific aliases
|
|
1366
1416
|
expected_params = [p.name for p in tool.params]
|
|
1367
1417
|
|
|
@@ -611,9 +611,20 @@ Example: [{{"description": "Step 1"}}, {{"description": "Step 2"}}]"""
|
|
|
611
611
|
})
|
|
612
612
|
|
|
613
613
|
try:
|
|
614
|
-
#
|
|
615
|
-
#
|
|
616
|
-
|
|
614
|
+
# Build step prompt with plan context for multi-step awareness.
|
|
615
|
+
# The agent reuses the same memory instance across steps, so it
|
|
616
|
+
# already has access to previous step results. We add a brief
|
|
617
|
+
# context header so it knows where it is in the overall plan.
|
|
618
|
+
if self.plan and self.plan.total_steps > 1:
|
|
619
|
+
step_idx = self.plan.current_step_index + 1
|
|
620
|
+
step_count = self.plan.total_steps
|
|
621
|
+
step_prompt = (
|
|
622
|
+
f"[Step {step_idx} of {step_count}] "
|
|
623
|
+
f"Goal: {self.plan.goal}\n\n"
|
|
624
|
+
f"{step.description}"
|
|
625
|
+
)
|
|
626
|
+
else:
|
|
627
|
+
step_prompt = step.description
|
|
617
628
|
|
|
618
629
|
# Run the agent with the step prompt
|
|
619
630
|
if self.verbose:
|
|
@@ -746,11 +757,16 @@ Example: [{{"description": "Step 1"}}, {{"description": "Step 2"}}]"""
|
|
|
746
757
|
def _inject_context(self, message: str):
|
|
747
758
|
"""
|
|
748
759
|
Inject a context message into the agent's memory.
|
|
749
|
-
|
|
760
|
+
|
|
761
|
+
Used to provide awareness prompts during long executions so the
|
|
762
|
+
agent stays focused on the goal and knows where it is in the plan.
|
|
763
|
+
The message is added as a 'user' role message so the model
|
|
764
|
+
processes it on the next step.
|
|
750
765
|
"""
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
766
|
+
try:
|
|
767
|
+
self.agent.memory.add("user", message)
|
|
768
|
+
except Exception:
|
|
769
|
+
pass # Non-fatal — context injection is best-effort
|
|
754
770
|
|
|
755
771
|
|
|
756
772
|
# ------------------------------------------------------------------ #
|
|
@@ -8,7 +8,8 @@ Written by VTSTech — https://www.vts-tech.org
|
|
|
8
8
|
from .base import BaseBackend
|
|
9
9
|
from .ollama import OllamaBackend
|
|
10
10
|
from .bitnet import BitNetBackend
|
|
11
|
-
from
|
|
11
|
+
from .llama_server import LlamaServerBackend
|
|
12
|
+
from ..config import AGENTNOVA_BACKEND, OLLAMA_BASE_URL, BITNET_BASE_URL, LLAMA_SERVER_BASE_URL
|
|
12
13
|
from ..core.types import ApiMode
|
|
13
14
|
|
|
14
15
|
|
|
@@ -16,6 +17,8 @@ from ..core.types import ApiMode
|
|
|
16
17
|
_BACKENDS: dict[str, type[BaseBackend]] = {
|
|
17
18
|
"ollama": OllamaBackend,
|
|
18
19
|
"bitnet": BitNetBackend,
|
|
20
|
+
"llama-server": LlamaServerBackend,
|
|
21
|
+
"llama_server": LlamaServerBackend, # alias
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
|
|
@@ -47,6 +50,8 @@ def get_backend(name: str, timeout: int | None = None, api_mode: ApiMode | str |
|
|
|
47
50
|
kwargs["base_url"] = OLLAMA_BASE_URL
|
|
48
51
|
elif name_lower == "bitnet":
|
|
49
52
|
kwargs["base_url"] = BITNET_BASE_URL
|
|
53
|
+
elif name_lower in ("llama-server", "llama_server"):
|
|
54
|
+
kwargs["base_url"] = LLAMA_SERVER_BASE_URL
|
|
50
55
|
|
|
51
56
|
# Create BackendConfig with timeout if specified
|
|
52
57
|
if "config" not in kwargs and timeout is not None:
|
|
@@ -92,6 +97,7 @@ __all__ = [
|
|
|
92
97
|
"BaseBackend",
|
|
93
98
|
"OllamaBackend",
|
|
94
99
|
"BitNetBackend",
|
|
100
|
+
"LlamaServerBackend",
|
|
95
101
|
"get_backend",
|
|
96
102
|
"get_default_backend",
|
|
97
103
|
"register_backend",
|