ness-agent 0.1.0__py3-none-any.whl
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.
- ness_agent/__init__.py +49 -0
- ness_agent/agent.py +473 -0
- ness_agent/compaction.py +562 -0
- ness_agent/context/__init__.py +0 -0
- ness_agent/context/coding_overlay.py +153 -0
- ness_agent/context/layers.py +400 -0
- ness_agent/context/overlay.py +160 -0
- ness_agent/defaults/__init__.py +21 -0
- ness_agent/defaults/agents/explore.md +10 -0
- ness_agent/graph/__init__.py +0 -0
- ness_agent/graph/builder.py +47 -0
- ness_agent/graph/helpers.py +114 -0
- ness_agent/graph/nodes.py +640 -0
- ness_agent/graph/state.py +21 -0
- ness_agent/hooks.py +232 -0
- ness_agent/instructions/__init__.py +42 -0
- ness_agent/instructions/act_mode.py +13 -0
- ness_agent/instructions/compaction.py +14 -0
- ness_agent/instructions/init_memory.py +9 -0
- ness_agent/instructions/l0_harness.py +94 -0
- ness_agent/instructions/l1_profile.py +11 -0
- ness_agent/instructions/plan_mode.py +30 -0
- ness_agent/instructions/reflection.py +32 -0
- ness_agent/instructions/subagent.py +22 -0
- ness_agent/instructions/thread_summary.py +5 -0
- ness_agent/mcp.py +385 -0
- ness_agent/memory.py +305 -0
- ness_agent/options.py +63 -0
- ness_agent/permissions.py +340 -0
- ness_agent/persistence.py +767 -0
- ness_agent/reflection.py +281 -0
- ness_agent/session.py +1163 -0
- ness_agent/session_context.py +50 -0
- ness_agent/skills.py +81 -0
- ness_agent/tools/__init__.py +324 -0
- ness_agent/tools/ask.py +116 -0
- ness_agent/tools/discover.py +185 -0
- ness_agent/tools/fs.py +353 -0
- ness_agent/tools/search.py +102 -0
- ness_agent/tools/shell.py +663 -0
- ness_agent/tools/skill.py +43 -0
- ness_agent/tools/subagents.py +574 -0
- ness_agent/tools/todo.py +89 -0
- ness_agent/tools/web.py +750 -0
- ness_agent/tracing/__init__.py +27 -0
- ness_agent/tracing/config.py +43 -0
- ness_agent/tracing/cost.py +275 -0
- ness_agent/tracing/exporters/__init__.py +6 -0
- ness_agent/tracing/exporters/console.py +126 -0
- ness_agent/tracing/exporters/otlp.py +146 -0
- ness_agent/tracing/messages.py +152 -0
- ness_agent/tracing/semconv.py +64 -0
- ness_agent/tracing/tracer.py +245 -0
- ness_agent/types.py +123 -0
- ness_agent/utils.py +88 -0
- ness_agent/workspace/__init__.py +12 -0
- ness_agent/workspace/bootstrap.py +65 -0
- ness_agent/workspace/git_context.py +59 -0
- ness_agent/workspace/project_context.py +152 -0
- ness_agent-0.1.0.dist-info/METADATA +181 -0
- ness_agent-0.1.0.dist-info/RECORD +119 -0
- ness_agent-0.1.0.dist-info/WHEEL +4 -0
- ness_agent-0.1.0.dist-info/entry_points.txt +2 -0
- ness_agent-0.1.0.dist-info/licenses/LICENSE +202 -0
- ness_cli/__init__.py +4 -0
- ness_cli/anthropic_messages.py +491 -0
- ness_cli/chat_model.py +244 -0
- ness_cli/coding_session.py +703 -0
- ness_cli/config.py +370 -0
- ness_cli/config_store.py +168 -0
- ness_cli/events.py +198 -0
- ness_cli/factory.py +173 -0
- ness_cli/goal.py +341 -0
- ness_cli/headless.py +181 -0
- ness_cli/instructions/__init__.py +91 -0
- ness_cli/instructions/act_mode.md +13 -0
- ness_cli/instructions/compaction.md +14 -0
- ness_cli/instructions/goal_generic_repair.md +1 -0
- ness_cli/instructions/goal_judge.md +15 -0
- ness_cli/instructions/goal_repair.md +10 -0
- ness_cli/instructions/init_memory.md +9 -0
- ness_cli/instructions/l0_harness.md +94 -0
- ness_cli/instructions/persona.md +1 -0
- ness_cli/instructions/plan_mode.md +30 -0
- ness_cli/instructions/reflection.md +32 -0
- ness_cli/instructions/subagent.md +22 -0
- ness_cli/instructions/thread_summary.md +5 -0
- ness_cli/mentions.py +126 -0
- ness_cli/model_catalog.py +257 -0
- ness_cli/paths.py +206 -0
- ness_cli/prompts.py +61 -0
- ness_cli/rollback.py +187 -0
- ness_cli/tui/__init__.py +1 -0
- ness_cli/tui/app.py +776 -0
- ness_cli/tui/chrome.py +336 -0
- ness_cli/tui/command_catalog.py +41 -0
- ness_cli/tui/commands.py +484 -0
- ness_cli/tui/config_flow.py +358 -0
- ness_cli/tui/config_registry.py +209 -0
- ness_cli/tui/constants.py +15 -0
- ness_cli/tui/formatting.py +92 -0
- ness_cli/tui/header.py +544 -0
- ness_cli/tui/images.py +216 -0
- ness_cli/tui/keys.py +302 -0
- ness_cli/tui/main.py +334 -0
- ness_cli/tui/markdown.py +286 -0
- ness_cli/tui/mentions.py +203 -0
- ness_cli/tui/models.py +20 -0
- ness_cli/tui/pickers.py +564 -0
- ness_cli/tui/prompts.py +235 -0
- ness_cli/tui/render.py +311 -0
- ness_cli/tui/stream.py +177 -0
- ness_cli/tui/theme.py +162 -0
- ness_cli/tui/tool_display.py +530 -0
- ness_cli/tui/transcript.py +802 -0
- ness_cli/tui/turn_renderer.py +227 -0
- ness_cli/tui/utils.py +71 -0
- ness_cli/tui/widgets.py +344 -0
- ness_cli/worktree.py +120 -0
ness_agent/__init__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from ness_agent.agent import NessAgent, NessAgentConfig, AgentSpec
|
|
2
|
+
from ness_agent.session import Session
|
|
3
|
+
from ness_agent.options import (
|
|
4
|
+
NessAgentOptions, MemoryConfig, ModeConfig, SubagentConfig, PermissionRules,
|
|
5
|
+
)
|
|
6
|
+
from ness_agent.types import (
|
|
7
|
+
RunResult, SessionEvent, UsageEvent, ApprovalHandler,
|
|
8
|
+
QuestionHandler,
|
|
9
|
+
PlanTurnHandler, InterruptHandler, ContextPreview,
|
|
10
|
+
aggregate_usage,
|
|
11
|
+
)
|
|
12
|
+
from ness_agent.context.layers import PromptLayers, PromptLayersConfig, AuxPrompts
|
|
13
|
+
from ness_agent.context.overlay import OverlayContext, OverlayProvider, render_overlay_delta, wrap_system_reminder
|
|
14
|
+
from ness_agent.context.coding_overlay import CodingOverlay, NoOverlay
|
|
15
|
+
from ness_agent.graph.state import AgentState
|
|
16
|
+
from ness_agent.utils import message_to_text
|
|
17
|
+
from ness_agent.memory import MemoryBackend, MemoryStore
|
|
18
|
+
from ness_agent.persistence import ThreadStore
|
|
19
|
+
from ness_agent.permissions import PermissionStore
|
|
20
|
+
from ness_agent.hooks import Hook, HookRunner
|
|
21
|
+
from ness_agent.skills import SkillLoader
|
|
22
|
+
from ness_agent.tools import ToolRegistry, coding_tools
|
|
23
|
+
from ness_agent.tracing.cost import CostTracker
|
|
24
|
+
from ness_agent.tracing.config import PricingDict, TracingConfig
|
|
25
|
+
from ness_agent.tracing.tracer import (
|
|
26
|
+
Tracer, NoopTracer, NoopSpan, InMemorySpan, MultiTracer, MultiSpan,
|
|
27
|
+
build_tracer, Span,
|
|
28
|
+
)
|
|
29
|
+
from ness_agent.tracing.cost import TokenUsage
|
|
30
|
+
from ness_agent.workspace import (
|
|
31
|
+
git_worktree_summary, get_project_context, setup_ness_structure,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"NessAgent", "NessAgentConfig", "AgentSpec", "Session",
|
|
36
|
+
"NessAgentOptions", "MemoryConfig", "ModeConfig", "SubagentConfig", "PermissionRules",
|
|
37
|
+
"RunResult", "SessionEvent", "UsageEvent", "ApprovalHandler", "QuestionHandler",
|
|
38
|
+
"PlanTurnHandler", "InterruptHandler", "ContextPreview", "aggregate_usage",
|
|
39
|
+
"PromptLayers", "PromptLayersConfig", "AuxPrompts",
|
|
40
|
+
"OverlayContext", "OverlayProvider", "render_overlay_delta", "wrap_system_reminder",
|
|
41
|
+
"CodingOverlay", "NoOverlay", "AgentState",
|
|
42
|
+
"message_to_text",
|
|
43
|
+
"MemoryBackend", "MemoryStore", "ThreadStore", "PermissionStore",
|
|
44
|
+
"Hook", "HookRunner", "SkillLoader", "ToolRegistry", "coding_tools",
|
|
45
|
+
"CostTracker", "TokenUsage", "PricingDict",
|
|
46
|
+
"TracingConfig", "Tracer", "NoopTracer", "NoopSpan", "InMemorySpan",
|
|
47
|
+
"MultiTracer", "MultiSpan", "build_tracer", "Span",
|
|
48
|
+
"git_worktree_summary", "get_project_context", "setup_ness_structure",
|
|
49
|
+
]
|
ness_agent/agent.py
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, TYPE_CHECKING
|
|
7
|
+
from langchain_core.language_models import BaseChatModel
|
|
8
|
+
from langchain_core.tools import BaseTool
|
|
9
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
10
|
+
|
|
11
|
+
from ness_agent.options import (
|
|
12
|
+
NessAgentOptions, MemoryConfig, ModeConfig, SubagentConfig,
|
|
13
|
+
)
|
|
14
|
+
from ness_agent.context.layers import PromptLayers, PromptLayersConfig, AuxPrompts
|
|
15
|
+
from ness_agent.context.overlay import OverlayProvider
|
|
16
|
+
from ness_agent.types import ApprovalHandler, QuestionHandler
|
|
17
|
+
from ness_agent.memory import MemoryBackend, MemoryStore
|
|
18
|
+
from ness_agent.persistence import ThreadStore
|
|
19
|
+
from ness_agent.permissions import PermissionStore
|
|
20
|
+
from ness_agent.hooks import Hook, HookRunner
|
|
21
|
+
from ness_agent.skills import SkillLoader
|
|
22
|
+
from ness_agent.tools import BUILTIN_TOOLS, ToolRegistry
|
|
23
|
+
from ness_agent.utils import normalize_tool
|
|
24
|
+
from ness_agent.tracing.cost import CostTracker
|
|
25
|
+
from ness_agent.tracing.config import TracingConfig
|
|
26
|
+
from ness_agent.tracing.tracer import NoopTracer, Tracer, build_tracer
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from ness_agent.session import Session
|
|
29
|
+
from ness_agent.types import (
|
|
30
|
+
InterruptHandler,
|
|
31
|
+
PlanTurnHandler,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _require_instance(name: str, value: object, expected: type) -> None:
|
|
36
|
+
if value is not None and not isinstance(value, expected):
|
|
37
|
+
raise TypeError(
|
|
38
|
+
f"{name} must be an instance of {expected.__name__}, "
|
|
39
|
+
f"got {type(value).__qualname__}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@dataclass(kw_only=True)
|
|
43
|
+
class AgentSpec:
|
|
44
|
+
"""User-facing agent configuration.
|
|
45
|
+
|
|
46
|
+
.. highlight:: python
|
|
47
|
+
|
|
48
|
+
Pass directly to :meth:`NessAgent.from_spec` or let
|
|
49
|
+
``NessAgent(model=..., prompt=..., **kwargs)`` build one internally.
|
|
50
|
+
Every field may be overridden individually; backends are lazily
|
|
51
|
+
resolved into a :class:`NessAgentConfig` by ``NessAgentConfig.resolve``.
|
|
52
|
+
|
|
53
|
+
**Required fields**
|
|
54
|
+
|
|
55
|
+
``model``
|
|
56
|
+
The primary chat model that drives the agent loop.
|
|
57
|
+
``prompt``
|
|
58
|
+
L0–L2 prompt: a :class:`~ness_agent.context.layers.PromptLayers`,
|
|
59
|
+
a :class:`~ness_agent.context.layers.PromptLayersConfig`, or a
|
|
60
|
+
plain dict (matching keys extracted, others ignored).
|
|
61
|
+
|
|
62
|
+
**Tools** (optional — defaults to all SDK built-in tools)
|
|
63
|
+
|
|
64
|
+
``tools``
|
|
65
|
+
``None`` or a sequence of ``BaseTool``, plain callables
|
|
66
|
+
(auto-wrapped), or strings naming built-in tools (e.g.
|
|
67
|
+
``"read"``, ``"grep"``, ``"shell"``).
|
|
68
|
+
|
|
69
|
+
**Optional auxiliary models**
|
|
70
|
+
|
|
71
|
+
``compaction_model``, ``reflection_model``
|
|
72
|
+
Models for compaction summaries and background reflection.
|
|
73
|
+
Falls back to ``model`` when ``None``.
|
|
74
|
+
|
|
75
|
+
**Behaviours**
|
|
76
|
+
|
|
77
|
+
``options``
|
|
78
|
+
:class:`NessAgentOptions` — compaction budget, context window,
|
|
79
|
+
approval flag, etc.
|
|
80
|
+
``overlay``
|
|
81
|
+
``None`` → default :class:`~ness_agent.context.coding_overlay.CodingOverlay`
|
|
82
|
+
(plan/act, git, todos, compaction, session memory).
|
|
83
|
+
Pass :class:`~ness_agent.context.coding_overlay.NoOverlay` for no L3.
|
|
84
|
+
Custom overlays must subclass
|
|
85
|
+
:class:`~ness_agent.context.overlay.OverlayProvider`.
|
|
86
|
+
``memory``
|
|
87
|
+
:class:`MemoryConfig` — project / user / session memory paths
|
|
88
|
+
(used when ``memory_store`` is not injected).
|
|
89
|
+
``memory_store``
|
|
90
|
+
Optional :class:`~ness_agent.memory.MemoryBackend` subclass
|
|
91
|
+
instance. When set, skips constructing the default
|
|
92
|
+
:class:`MemoryStore`.
|
|
93
|
+
``modes``
|
|
94
|
+
:class:`ModeConfig` for plan/act mode (optional; toggle still works
|
|
95
|
+
with default instruction texts when ``None``).
|
|
96
|
+
``subagents``
|
|
97
|
+
:class:`SubagentConfig` for the ``spawn_subagent`` tool.
|
|
98
|
+
``aux_prompts``
|
|
99
|
+
:class:`AuxPrompts` — templates for auxiliary LLM calls.
|
|
100
|
+
|
|
101
|
+
**Filesystem paths**
|
|
102
|
+
|
|
103
|
+
``skills_dir``
|
|
104
|
+
``.ness/skills/`` directory (or ``None`` to disable skills).
|
|
105
|
+
``hooks_config``
|
|
106
|
+
Path to ``hooks.json`` for pre/post tool-use hooks. When ``None``,
|
|
107
|
+
defaults to ``{ness_dir}/hooks.json``.
|
|
108
|
+
``hooks``
|
|
109
|
+
Optional in-memory :class:`~ness_agent.hooks.Hook` list seeded into
|
|
110
|
+
the runner at resolve (combined with the JSON file).
|
|
111
|
+
|
|
112
|
+
**Runtime hooks**
|
|
113
|
+
|
|
114
|
+
``approval_handler``, ``question_handler``
|
|
115
|
+
Callbacks for destructive-tool approval and user questions.
|
|
116
|
+
|
|
117
|
+
**Integrations**
|
|
118
|
+
|
|
119
|
+
``checkpoint_factory``
|
|
120
|
+
Callable returning a langgraph ``BaseCheckpointSaver``
|
|
121
|
+
(``None`` → in-memory ``MemorySaver``).
|
|
122
|
+
``cost_tracker``
|
|
123
|
+
Pricing-aware :class:`CostTracker` (optional). When ``None``,
|
|
124
|
+
resolved from ``tracing.pricing``.
|
|
125
|
+
``tracer``
|
|
126
|
+
OpenTelemetry-compatible :class:`Tracer` (optional). When
|
|
127
|
+
``None``, resolved via :func:`build_tracer` from ``tracing``.
|
|
128
|
+
``tracing``
|
|
129
|
+
:class:`TracingConfig` — toggles tracing, exporter selection,
|
|
130
|
+
capture options, and per-model pricing for cost estimation.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
# required
|
|
134
|
+
model: BaseChatModel
|
|
135
|
+
prompt: PromptLayers | PromptLayersConfig | Mapping[str, Any]
|
|
136
|
+
|
|
137
|
+
# tools — defaults to all SDK tools (BUILTIN_TOOLS) when None
|
|
138
|
+
tools: Sequence[BaseTool] | None = None
|
|
139
|
+
|
|
140
|
+
# optional auxiliary models
|
|
141
|
+
compaction_model: BaseChatModel | None = None
|
|
142
|
+
reflection_model: BaseChatModel | None = None
|
|
143
|
+
|
|
144
|
+
# behaviours
|
|
145
|
+
options: NessAgentOptions = field(default_factory=NessAgentOptions)
|
|
146
|
+
overlay: OverlayProvider | None = None
|
|
147
|
+
memory: MemoryConfig = field(default_factory=MemoryConfig)
|
|
148
|
+
memory_store: MemoryBackend | None = None
|
|
149
|
+
modes: ModeConfig | None = None
|
|
150
|
+
subagents: SubagentConfig | None = None
|
|
151
|
+
# Prompt templates for auxiliary model calls
|
|
152
|
+
aux_prompts: AuxPrompts = field(default_factory=AuxPrompts)
|
|
153
|
+
|
|
154
|
+
# specs
|
|
155
|
+
skills_dir: Path | None = None
|
|
156
|
+
hooks_config: Path | None = None
|
|
157
|
+
hooks: Sequence[Hook] | None = None
|
|
158
|
+
|
|
159
|
+
# runtime hooks
|
|
160
|
+
approval_handler: ApprovalHandler | None = None
|
|
161
|
+
question_handler: QuestionHandler | None = None
|
|
162
|
+
|
|
163
|
+
# integrations
|
|
164
|
+
checkpoint_factory: Callable[[], BaseCheckpointSaver] | None = None
|
|
165
|
+
tracing: TracingConfig = field(default_factory=TracingConfig)
|
|
166
|
+
cost_tracker: CostTracker | None = None
|
|
167
|
+
tracer: Tracer | None = None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass(kw_only=True, eq=False)
|
|
171
|
+
class NessAgentConfig:
|
|
172
|
+
"""Fully resolved agent config including backends. Prefer AgentSpec / NessAgent(...)."""
|
|
173
|
+
|
|
174
|
+
# required
|
|
175
|
+
model: BaseChatModel
|
|
176
|
+
tools: Sequence[BaseTool]
|
|
177
|
+
prompts: PromptLayers
|
|
178
|
+
|
|
179
|
+
# optional auxiliary models
|
|
180
|
+
compaction_model: BaseChatModel | None = None
|
|
181
|
+
reflection_model: BaseChatModel | None = None
|
|
182
|
+
|
|
183
|
+
# behaviors
|
|
184
|
+
options: NessAgentOptions = field(default_factory=NessAgentOptions)
|
|
185
|
+
overlay: OverlayProvider | None = None
|
|
186
|
+
memory: MemoryConfig = field(default_factory=MemoryConfig)
|
|
187
|
+
modes: ModeConfig | None = None
|
|
188
|
+
subagents: SubagentConfig | None = None
|
|
189
|
+
aux_prompts: AuxPrompts = field(default_factory=AuxPrompts)
|
|
190
|
+
|
|
191
|
+
# specs
|
|
192
|
+
skills_dir: Path | None = None
|
|
193
|
+
hooks_config: Path | None = None
|
|
194
|
+
|
|
195
|
+
# runtime hooks
|
|
196
|
+
approval_handler: ApprovalHandler | None = None
|
|
197
|
+
question_handler: QuestionHandler | None = None
|
|
198
|
+
|
|
199
|
+
# integrations
|
|
200
|
+
checkpoint_factory: Callable[[], BaseCheckpointSaver] | None = None
|
|
201
|
+
tracing: TracingConfig = field(default_factory=TracingConfig)
|
|
202
|
+
|
|
203
|
+
# agent backends (resolved)
|
|
204
|
+
memory_store: MemoryBackend | None = None
|
|
205
|
+
thread_store: ThreadStore | None = None
|
|
206
|
+
permission_store: PermissionStore | None = None
|
|
207
|
+
hook_runner: HookRunner | None = None
|
|
208
|
+
skill_loader: SkillLoader | None = None
|
|
209
|
+
tool_registry: ToolRegistry | None = None
|
|
210
|
+
cost_tracker: CostTracker
|
|
211
|
+
tracer: Tracer
|
|
212
|
+
|
|
213
|
+
@classmethod
|
|
214
|
+
def resolve(cls, spec: AgentSpec) -> "NessAgentConfig":
|
|
215
|
+
"""Resolve a user-facing :class:`AgentSpec` into a ready-to-run config.
|
|
216
|
+
|
|
217
|
+
This is called internally by ``NessAgent.__init__`` and
|
|
218
|
+
``NessAgent.from_spec``. It:
|
|
219
|
+
|
|
220
|
+
1. Normalises the ``prompt`` field (dict → ``PromptLayersConfig``).
|
|
221
|
+
2. Resolves ``project_root`` and ``ness_dir`` paths without
|
|
222
|
+
mutating the caller's ``NessAgentOptions`` instance.
|
|
223
|
+
3. Normalises every tool via :func:`normalize_tool` (BaseTool,
|
|
224
|
+
callable, or name string).
|
|
225
|
+
4. Instantiates backend stores (``MemoryStore``, ``ThreadStore``,
|
|
226
|
+
``PermissionStore``, etc.) so the config is fully wired.
|
|
227
|
+
5. Installs a default :class:`CodingOverlay` when ``overlay`` is
|
|
228
|
+
``None``, picking up plan/act templates from ``spec.modes`` if
|
|
229
|
+
provided.
|
|
230
|
+
"""
|
|
231
|
+
prompt = spec.prompt
|
|
232
|
+
if isinstance(prompt, Mapping):
|
|
233
|
+
prompts = PromptLayers.from_dict(prompt)
|
|
234
|
+
elif isinstance(prompt, PromptLayersConfig):
|
|
235
|
+
prompts = PromptLayers(prompt)
|
|
236
|
+
else:
|
|
237
|
+
prompts = prompt
|
|
238
|
+
|
|
239
|
+
# Resolve paths without mutating the caller's options instance —
|
|
240
|
+
# reusing one NessAgentOptions across two NessAgent builds in
|
|
241
|
+
# different roots otherwise "sticks" the first agent's paths.
|
|
242
|
+
raw_options = spec.options
|
|
243
|
+
project_root = (raw_options.project_root or Path.cwd()).resolve()
|
|
244
|
+
ness_dir = (raw_options.ness_dir or (project_root / ".ness")).resolve()
|
|
245
|
+
if raw_options.project_root is None or raw_options.ness_dir is None:
|
|
246
|
+
import dataclasses as _dc
|
|
247
|
+
overrides = {}
|
|
248
|
+
if raw_options.project_root is None:
|
|
249
|
+
overrides["project_root"] = project_root
|
|
250
|
+
if raw_options.ness_dir is None:
|
|
251
|
+
overrides["ness_dir"] = ness_dir
|
|
252
|
+
options = _dc.replace(raw_options, **overrides)
|
|
253
|
+
else:
|
|
254
|
+
options = raw_options
|
|
255
|
+
|
|
256
|
+
model_name = (
|
|
257
|
+
getattr(spec.model, "model", None)
|
|
258
|
+
or getattr(spec.model, "model_name", None)
|
|
259
|
+
or ""
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
resolved_tools = [
|
|
263
|
+
normalize_tool(t)
|
|
264
|
+
for t in (spec.tools if spec.tools is not None else BUILTIN_TOOLS)
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
# Default overlay: CodingOverlay ships with the SDK
|
|
268
|
+
# Pass overlay=NoOverlay() to opt out of L3 entirely.
|
|
269
|
+
overlay = spec.overlay
|
|
270
|
+
if overlay is None:
|
|
271
|
+
from ness_agent.context.coding_overlay import CodingOverlay
|
|
272
|
+
modes_cfg = spec.modes
|
|
273
|
+
overlay = CodingOverlay(
|
|
274
|
+
plans_dir=(
|
|
275
|
+
str(modes_cfg.plans_dir) if modes_cfg and modes_cfg.plans_dir
|
|
276
|
+
else ".ness/plans/"
|
|
277
|
+
),
|
|
278
|
+
plan_mode_template=modes_cfg.plan_mode_template if modes_cfg else None,
|
|
279
|
+
act_mode_template=modes_cfg.act_mode_template if modes_cfg else None,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
_require_instance("overlay", overlay, OverlayProvider)
|
|
283
|
+
_require_instance("memory_store", spec.memory_store, MemoryBackend)
|
|
284
|
+
_require_instance("approval_handler", spec.approval_handler, ApprovalHandler)
|
|
285
|
+
|
|
286
|
+
return cls(
|
|
287
|
+
model=spec.model,
|
|
288
|
+
tools=resolved_tools,
|
|
289
|
+
prompts=prompts,
|
|
290
|
+
|
|
291
|
+
compaction_model=spec.compaction_model,
|
|
292
|
+
reflection_model=spec.reflection_model,
|
|
293
|
+
|
|
294
|
+
options=options,
|
|
295
|
+
overlay=overlay,
|
|
296
|
+
|
|
297
|
+
memory=spec.memory,
|
|
298
|
+
modes=spec.modes,
|
|
299
|
+
subagents=spec.subagents,
|
|
300
|
+
aux_prompts=spec.aux_prompts,
|
|
301
|
+
skills_dir=spec.skills_dir,
|
|
302
|
+
hooks_config=spec.hooks_config if spec.hooks_config is not None else ness_dir / "hooks.json",
|
|
303
|
+
|
|
304
|
+
approval_handler=spec.approval_handler,
|
|
305
|
+
question_handler=spec.question_handler,
|
|
306
|
+
checkpoint_factory=spec.checkpoint_factory,
|
|
307
|
+
tracing=spec.tracing,
|
|
308
|
+
|
|
309
|
+
memory_store=(
|
|
310
|
+
spec.memory_store
|
|
311
|
+
if spec.memory_store is not None
|
|
312
|
+
else MemoryStore(
|
|
313
|
+
spec.memory, ness_dir=ness_dir, project_root=project_root
|
|
314
|
+
)
|
|
315
|
+
),
|
|
316
|
+
thread_store=ThreadStore(
|
|
317
|
+
threads_dir=ness_dir / "threads",
|
|
318
|
+
auto_save=options.auto_save_threads,
|
|
319
|
+
default_model=str(model_name or ""),
|
|
320
|
+
),
|
|
321
|
+
permission_store=PermissionStore(ness_dir=ness_dir, project_root=project_root),
|
|
322
|
+
hook_runner=HookRunner(
|
|
323
|
+
spec.hooks_config if spec.hooks_config is not None else ness_dir / "hooks.json",
|
|
324
|
+
project_root=project_root,
|
|
325
|
+
hooks=spec.hooks,
|
|
326
|
+
),
|
|
327
|
+
skill_loader=SkillLoader(spec.skills_dir),
|
|
328
|
+
tool_registry=ToolRegistry(resolved_tools),
|
|
329
|
+
cost_tracker=spec.cost_tracker or CostTracker(pricing=spec.tracing.pricing),
|
|
330
|
+
tracer=spec.tracer or build_tracer(spec.tracing),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class NessAgent:
|
|
335
|
+
"""Top-level agent — owns a shared config and creates per-thread sessions.
|
|
336
|
+
|
|
337
|
+
This is the primary entry point for the Ness Agent SDK. Construct
|
|
338
|
+
one with a model and prompt, then call ``.session(thread_id=...)``
|
|
339
|
+
for each conversation thread.
|
|
340
|
+
|
|
341
|
+
**Minimal usage** (everything defaults to a working coding agent)::
|
|
342
|
+
|
|
343
|
+
from ness_agent import NessAgent, PromptLayersConfig
|
|
344
|
+
from langchain_openai import ChatOpenAI
|
|
345
|
+
|
|
346
|
+
agent = NessAgent(model=ChatOpenAI(model="gpt-4o"),
|
|
347
|
+
prompt=PromptLayersConfig())
|
|
348
|
+
session = agent.session(thread_id="proj-1")
|
|
349
|
+
result = await session.run("add rate limiter on /api/login")
|
|
350
|
+
|
|
351
|
+
Tools are optional (all SDK built-ins are loaded when omitted).
|
|
352
|
+
The default :class:`~ness_agent.context.coding_overlay.CodingOverlay`
|
|
353
|
+
provides plan/act mode blocks, git snapshot, compaction notes, todos,
|
|
354
|
+
session memory, and loaded skills out of the box.
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
def __init__(self, *, model, prompt, tools: Sequence[BaseTool] | None = None, **kwargs) -> None:
|
|
358
|
+
"""Create an agent.
|
|
359
|
+
|
|
360
|
+
Parameters
|
|
361
|
+
----------
|
|
362
|
+
model : BaseChatModel
|
|
363
|
+
The primary chat model that drives the agent loop.
|
|
364
|
+
prompt : PromptLayers | PromptLayersConfig | dict
|
|
365
|
+
L0–L2 prompt configuration. Accepts a ``PromptLayers`` instance,
|
|
366
|
+
a ``PromptLayersConfig`` dataclass, or a plain dict of field->value
|
|
367
|
+
(unknown keys ignored).
|
|
368
|
+
tools : iterable of BaseTool / callable / str, optional
|
|
369
|
+
Tools available to the agent. ``None`` (the default) loads all
|
|
370
|
+
SDK built-in tools (:data:`~ness_agent.tools.BUILTIN_TOOLS`).
|
|
371
|
+
Items may be ``BaseTool`` instances, plain callables
|
|
372
|
+
(auto-wrapped), or strings resolved from the built-in tool map.
|
|
373
|
+
**kwargs
|
|
374
|
+
Any remaining :class:`AgentSpec` field such as ``options``,
|
|
375
|
+
``overlay``, ``modes``, ``aux_prompts``, ``memory``, etc.
|
|
376
|
+
"""
|
|
377
|
+
spec = AgentSpec(model=model, tools=tools, prompt=prompt, **kwargs)
|
|
378
|
+
self._config = NessAgentConfig.resolve(spec)
|
|
379
|
+
|
|
380
|
+
@classmethod
|
|
381
|
+
def from_spec(cls, spec: AgentSpec) -> "NessAgent":
|
|
382
|
+
"""Build an agent from a pre-built :class:`AgentSpec`.
|
|
383
|
+
|
|
384
|
+
Useful when you want to assemble the spec in one place and
|
|
385
|
+
resolve backends later, or when you need to create multiple
|
|
386
|
+
agents from the same spec::
|
|
387
|
+
|
|
388
|
+
agent = NessAgent.from_spec(AgentSpec(
|
|
389
|
+
model=ChatOpenAI(model="gpt-4o"),
|
|
390
|
+
prompt=PromptLayersConfig(),
|
|
391
|
+
options=NessAgentOptions(context_window=128_000),
|
|
392
|
+
))
|
|
393
|
+
"""
|
|
394
|
+
agent = object.__new__(cls)
|
|
395
|
+
agent._config = NessAgentConfig.resolve(spec)
|
|
396
|
+
return agent
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def config(self) -> NessAgentConfig:
|
|
400
|
+
"""The fully-resolved :class:`NessAgentConfig` for this agent.
|
|
401
|
+
|
|
402
|
+
Contains the resolved backends (``memory_store``, ``thread_store``,
|
|
403
|
+
``tool_registry``, etc.) plus the user-supplied fields.
|
|
404
|
+
"""
|
|
405
|
+
return self._config
|
|
406
|
+
|
|
407
|
+
def session(
|
|
408
|
+
self,
|
|
409
|
+
*,
|
|
410
|
+
thread_id: str,
|
|
411
|
+
mode: str | None = None,
|
|
412
|
+
metadata: Mapping[str, Any] | None = None,
|
|
413
|
+
git_available: bool | None = None,
|
|
414
|
+
vision: bool | None = None,
|
|
415
|
+
on_plan_turn: "PlanTurnHandler | None" = None,
|
|
416
|
+
on_interrupt: "InterruptHandler | None" = None,
|
|
417
|
+
) -> Session:
|
|
418
|
+
"""Create a runnable :class:`~ness_agent.session.Session` for one thread.
|
|
419
|
+
|
|
420
|
+
Parameters
|
|
421
|
+
----------
|
|
422
|
+
thread_id : str
|
|
423
|
+
Unique identifier for this conversation thread.
|
|
424
|
+
mode : str, optional
|
|
425
|
+
Initial mode — ``"act"`` or ``"plan"``. Falls back to
|
|
426
|
+
``config.modes.default`` (or ``"act"``) when ``None``.
|
|
427
|
+
metadata : dict, optional
|
|
428
|
+
Arbitrary key-value pairs surfaced to the L3 overlay provider
|
|
429
|
+
via ``ctx.metadata``. Set ``session.metadata[k] = v`` before
|
|
430
|
+
each ``run()`` call if you want to mutate live.
|
|
431
|
+
git_available : bool, optional
|
|
432
|
+
Whether the project has a git repo. Auto-detected when
|
|
433
|
+
``None``.
|
|
434
|
+
vision : bool, optional
|
|
435
|
+
Forwards image attachments to the model when ``True``; drops
|
|
436
|
+
to text-only and emits a ``warning`` SessionEvent when ``False``;
|
|
437
|
+
shape-blind (forwards verbatim) when ``None``. See
|
|
438
|
+
:class:`~ness_agent.session.Session`.
|
|
439
|
+
on_plan_turn, on_interrupt
|
|
440
|
+
Per-Session runtime hooks. Stored on the :class:`Session` instance
|
|
441
|
+
(not the shared :class:`NessAgentConfig`) so concurrent threads on
|
|
442
|
+
one agent never clobber each other. See :mod:`ness_agent.types`
|
|
443
|
+
for the handler signatures.
|
|
444
|
+
"""
|
|
445
|
+
from ness_agent.session import Session
|
|
446
|
+
|
|
447
|
+
cfg = self._config
|
|
448
|
+
mode = mode or (cfg.modes.default if cfg.modes else "act")
|
|
449
|
+
return Session(
|
|
450
|
+
self,
|
|
451
|
+
thread_id=thread_id,
|
|
452
|
+
mode=mode,
|
|
453
|
+
metadata=dict(metadata or {}),
|
|
454
|
+
git_available=git_available,
|
|
455
|
+
vision=vision,
|
|
456
|
+
on_plan_turn=on_plan_turn,
|
|
457
|
+
on_interrupt=on_interrupt,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
def new_thread_id(self, prefix: str = "session") -> str:
|
|
461
|
+
"""Generate a fresh thread ID string.
|
|
462
|
+
|
|
463
|
+
Parameters
|
|
464
|
+
----------
|
|
465
|
+
prefix : str
|
|
466
|
+
Prefix for the thread ID (default ``"session"``).
|
|
467
|
+
|
|
468
|
+
Returns
|
|
469
|
+
-------
|
|
470
|
+
str
|
|
471
|
+
A short hex string like ``"session-a1b2c3d4"``.
|
|
472
|
+
"""
|
|
473
|
+
return f"{prefix}-{uuid.uuid4().hex[:8]}"
|