noeta-runtime 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.
- noeta/agent/registry.py +71 -0
- noeta/agent/spec.py +139 -0
- noeta/context/__init__.py +5 -0
- noeta/context/composer.py +922 -0
- noeta/context/content_channel.py +142 -0
- noeta/context/environment.py +199 -0
- noeta/context/instructions.py +156 -0
- noeta/context/memory.py +196 -0
- noeta/context/skills/__init__.py +20 -0
- noeta/context/skills/_frontmatter.py +225 -0
- noeta/context/skills/indexer.py +385 -0
- noeta/core/__init__.py +5 -0
- noeta/core/_decision_handlers.py +2035 -0
- noeta/core/composer.py +38 -0
- noeta/core/engine.py +1392 -0
- noeta/core/fold.py +896 -0
- noeta/core/hooks.py +87 -0
- noeta/core/observers.py +221 -0
- noeta/core/snapshot.py +86 -0
- noeta/core/wiring.py +41 -0
- noeta/execution/__init__.py +134 -0
- noeta/execution/background_subagent.py +294 -0
- noeta/execution/builder.py +1101 -0
- noeta/execution/commands.py +156 -0
- noeta/execution/driver.py +1887 -0
- noeta/execution/environment.py +179 -0
- noeta/execution/host.py +170 -0
- noeta/execution/instructions.py +134 -0
- noeta/execution/memory.py +218 -0
- noeta/execution/multi_turn.py +102 -0
- noeta/execution/resolver.py +772 -0
- noeta/execution/runner.py +523 -0
- noeta/execution/skills.py +482 -0
- noeta/execution/subtask_drain.py +806 -0
- noeta/execution/title.py +171 -0
- noeta/guards/__init__.py +31 -0
- noeta/guards/budget.py +139 -0
- noeta/guards/hook.py +126 -0
- noeta/guards/permission.py +287 -0
- noeta/guards/repetition.py +122 -0
- noeta/observers/__init__.py +43 -0
- noeta/observers/audit.py +362 -0
- noeta/observers/fanout.py +234 -0
- noeta/observers/hook.py +299 -0
- noeta/observers/metrics.py +79 -0
- noeta/observers/trace_export.py +208 -0
- noeta/policies/__init__.py +5 -0
- noeta/policies/_control_translate.py +35 -0
- noeta/policies/_workflow_sandbox.py +188 -0
- noeta/policies/control_semantics.py +1346 -0
- noeta/policies/control_tools.py +86 -0
- noeta/policies/descriptions/__init__.py +49 -0
- noeta/policies/descriptions/ask_user_question.md +31 -0
- noeta/policies/descriptions/run_workflow.md +71 -0
- noeta/policies/descriptions/skill.md +27 -0
- noeta/policies/descriptions/spawn_subagent.md +40 -0
- noeta/policies/descriptions/todo_write.md +28 -0
- noeta/policies/orchestration.py +509 -0
- noeta/policies/react.py +873 -0
- noeta/policies/skill_tools.py +127 -0
- noeta/policies/stub.py +41 -0
- noeta/presets/__init__.py +195 -0
- noeta/presets/prompts/__init__.py +15 -0
- noeta/presets/prompts/explore.md +9 -0
- noeta/presets/prompts/general-purpose.md +13 -0
- noeta/presets/prompts/main.md +18 -0
- noeta/presets/prompts/plan.md +13 -0
- noeta/protocols/__init__.py +55 -0
- noeta/protocols/canonical.py +136 -0
- noeta/protocols/composer.py +20 -0
- noeta/protocols/content_store.py +73 -0
- noeta/protocols/context_plan.py +67 -0
- noeta/protocols/decisions.py +425 -0
- noeta/protocols/dispatcher.py +221 -0
- noeta/protocols/engine.py +184 -0
- noeta/protocols/errors.py +235 -0
- noeta/protocols/event_log.py +326 -0
- noeta/protocols/events.py +1242 -0
- noeta/protocols/hooks.py +173 -0
- noeta/protocols/messages.py +321 -0
- noeta/protocols/policy.py +29 -0
- noeta/protocols/resources.py +48 -0
- noeta/protocols/step_context.py +40 -0
- noeta/protocols/step_transition.py +67 -0
- noeta/protocols/task.py +373 -0
- noeta/protocols/token_estimate.py +109 -0
- noeta/protocols/tool.py +162 -0
- noeta/protocols/tool_args.py +114 -0
- noeta/protocols/values.py +114 -0
- noeta/protocols/view.py +119 -0
- noeta/protocols/wake.py +231 -0
- noeta/providers/__init__.py +25 -0
- noeta/providers/anthropic.py +795 -0
- noeta/providers/catalog.py +276 -0
- noeta/providers/codecs.py +132 -0
- noeta/providers/openai_compat.py +491 -0
- noeta/providers/openai_responses.py +1006 -0
- noeta/read_models/__init__.py +18 -0
- noeta/read_models/sessions.py +96 -0
- noeta/runtime/__init__.py +10 -0
- noeta/runtime/background_shell.py +894 -0
- noeta/runtime/cancellation.py +46 -0
- noeta/runtime/compaction.py +226 -0
- noeta/runtime/file_checkpoint.py +64 -0
- noeta/runtime/llm.py +379 -0
- noeta/runtime/tool.py +253 -0
- noeta/runtime/worker.py +1097 -0
- noeta/storage/__init__.py +7 -0
- noeta/storage/_wake_match.py +36 -0
- noeta/storage/memory.py +859 -0
- noeta/storage/sqlite/__init__.py +35 -0
- noeta/storage/sqlite/_connection.py +133 -0
- noeta/storage/sqlite/_transaction.py +50 -0
- noeta/storage/sqlite/contentstore.py +101 -0
- noeta/storage/sqlite/dispatcher.py +823 -0
- noeta/storage/sqlite/eventlog.py +700 -0
- noeta/storage/sqlite/migrations.py +312 -0
- noeta/storage/sqlite/readonly.py +147 -0
- noeta/storage/stacks.py +73 -0
- noeta/testing/__init__.py +13 -0
- noeta/testing/composer.py +89 -0
- noeta/testing/fake_llm.py +71 -0
- noeta/testing/profile.py +321 -0
- noeta/testing/stub_provider.py +96 -0
- noeta/tools/__init__.py +12 -0
- noeta/tools/_env.py +38 -0
- noeta/tools/_invocation.py +152 -0
- noeta/tools/_limits.py +85 -0
- noeta/tools/_refs.py +26 -0
- noeta/tools/app/__init__.py +14 -0
- noeta/tools/app/_gateway.py +54 -0
- noeta/tools/app/open_app.py +105 -0
- noeta/tools/decorator.py +146 -0
- noeta/tools/descriptions/__init__.py +48 -0
- noeta/tools/descriptions/apply_patch.md +8 -0
- noeta/tools/descriptions/edit.md +7 -0
- noeta/tools/descriptions/glob.md +5 -0
- noeta/tools/descriptions/grep.md +7 -0
- noeta/tools/descriptions/open_app.md +21 -0
- noeta/tools/descriptions/read.md +8 -0
- noeta/tools/descriptions/shell_kill.md +6 -0
- noeta/tools/descriptions/shell_poll.md +5 -0
- noeta/tools/descriptions/shell_run.md +8 -0
- noeta/tools/descriptions/web_search.md +6 -0
- noeta/tools/descriptions/webfetch.md +6 -0
- noeta/tools/descriptions/write.md +6 -0
- noeta/tools/fake.py +96 -0
- noeta/tools/fs/__init__.py +142 -0
- noeta/tools/fs/_diff.py +61 -0
- noeta/tools/fs/_subprocess.py +111 -0
- noeta/tools/fs/_workspace.py +165 -0
- noeta/tools/fs/edit.py +428 -0
- noeta/tools/fs/patch.py +579 -0
- noeta/tools/fs/read.py +611 -0
- noeta/tools/fs/shell.py +730 -0
- noeta/tools/fs/skill_script.py +249 -0
- noeta/tools/mcp/__init__.py +81 -0
- noeta/tools/mcp/_client.py +312 -0
- noeta/tools/mcp/_http_client.py +254 -0
- noeta/tools/mcp/prompts.py +204 -0
- noeta/tools/mcp/resources.py +158 -0
- noeta/tools/mcp/tool.py +484 -0
- noeta/tools/memory.py +239 -0
- noeta/tools/web/__init__.py +46 -0
- noeta/tools/web/fetch.py +255 -0
- noeta/tools/web/search.py +237 -0
- noeta_runtime-0.1.0.dist-info/METADATA +36 -0
- noeta_runtime-0.1.0.dist-info/RECORD +169 -0
- noeta_runtime-0.1.0.dist-info/WHEEL +4 -0
noeta/agent/registry.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""``AgentRegistry`` — name → :class:`AgentSpec` resolve target.
|
|
2
|
+
|
|
3
|
+
The single entry point the server and worker use to resolve an Agent by name.
|
|
4
|
+
Resolution of an unknown name is a **hard error** (``UnknownAgentError``), never
|
|
5
|
+
a silent no-op now that ``agent_name`` is load-bearing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from noeta.agent.spec import AgentSpec
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AgentRegistry",
|
|
15
|
+
"UnknownAgentError",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UnknownAgentError(Exception):
|
|
20
|
+
"""A name was resolved that no registered Agent answers to.
|
|
21
|
+
|
|
22
|
+
Generic over the resolution context: ``task_id`` is supplied when the
|
|
23
|
+
lookup is driven by a leased Task (``noeta.agent.resolver``), and omitted for
|
|
24
|
+
a bare registry lookup. This is the agent-layer home for the error;
|
|
25
|
+
``noeta.agent.resolver`` re-exports it (task #5).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
agent_name: str,
|
|
32
|
+
available: list[str],
|
|
33
|
+
task_id: str | None = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
self.agent_name = agent_name
|
|
36
|
+
self.available = available
|
|
37
|
+
self.task_id = task_id
|
|
38
|
+
where = f"task {task_id!r} names" if task_id is not None else "no agent named"
|
|
39
|
+
super().__init__(
|
|
40
|
+
f"{where} unknown agent {agent_name!r}; available: {available}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AgentRegistry:
|
|
45
|
+
"""In-process ``name → AgentSpec`` map. Duplicate names are rejected so a
|
|
46
|
+
package cannot shadow another's Agent by accident."""
|
|
47
|
+
|
|
48
|
+
def __init__(self) -> None:
|
|
49
|
+
self._specs: dict[str, AgentSpec] = {}
|
|
50
|
+
|
|
51
|
+
def add(self, spec: AgentSpec) -> None:
|
|
52
|
+
"""Register ``spec``. A name already present is an error."""
|
|
53
|
+
if spec.name in self._specs:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"agent {spec.name!r} already registered; names must be unique"
|
|
56
|
+
)
|
|
57
|
+
self._specs[spec.name] = spec
|
|
58
|
+
|
|
59
|
+
def resolve(self, name: str) -> AgentSpec:
|
|
60
|
+
"""Return the ``AgentSpec`` named ``name``; unknown ⇒ ``UnknownAgentError``."""
|
|
61
|
+
spec = self._specs.get(name)
|
|
62
|
+
if spec is None:
|
|
63
|
+
raise UnknownAgentError(agent_name=name, available=self.names())
|
|
64
|
+
return spec
|
|
65
|
+
|
|
66
|
+
def __contains__(self, name: object) -> bool:
|
|
67
|
+
return name in self._specs
|
|
68
|
+
|
|
69
|
+
def names(self) -> list[str]:
|
|
70
|
+
"""Registered Agent names, sorted."""
|
|
71
|
+
return sorted(self._specs)
|
noeta/agent/spec.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""``AgentSpec`` — the serializable Agent identity object.
|
|
2
|
+
|
|
3
|
+
An ``AgentSpec`` is *only identity*: declared, canonical-serializable fields,
|
|
4
|
+
no ``Callable``\\s. Runtime wiring (how a ``policy`` ref becomes a live
|
|
5
|
+
``Policy``, a ``ToolRef`` a live ``Tool``) is kept in a separate builder keyed
|
|
6
|
+
by the same ``(name, version)`` refs — ``noeta.agent`` for coding agents, the
|
|
7
|
+
future agent-sdk for official batteries. Keeping closures off the spec is what
|
|
8
|
+
keeps identity declarative: component lists are normalised to sorted tuples at
|
|
9
|
+
construction, so two specs that differ only in author ordering are ``==``.
|
|
10
|
+
Identity comparison is plain frozen-dataclass structural equality (an
|
|
11
|
+
earlier ``fingerprint`` digest was retired in favour of it).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Mapping
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"AgentSpec",
|
|
22
|
+
"BudgetSpec",
|
|
23
|
+
"Capabilities",
|
|
24
|
+
"ComponentRef",
|
|
25
|
+
"ToolRef",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True, order=True)
|
|
30
|
+
class ComponentRef:
|
|
31
|
+
"""A versioned reference to a wired component (policy / composer / skill /
|
|
32
|
+
guard / observer).
|
|
33
|
+
|
|
34
|
+
``version`` is the **behaviour** version, not a release tag:
|
|
35
|
+
it MUST bump whenever the component's behaviour changes, because that is the
|
|
36
|
+
only behaviour signal the spec's structural identity carries. ``order=True``
|
|
37
|
+
makes refs sort deterministically by ``(name, version)`` for normalisation.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
version: str = "1"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True, order=True)
|
|
45
|
+
class ToolRef:
|
|
46
|
+
"""A versioned reference to a tool, plus the metadata the runtime keys on
|
|
47
|
+
(``risk_level`` gates approval).
|
|
48
|
+
|
|
49
|
+
``order=True`` sorts by ``(name, version, risk_level)``.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
version: str = "1"
|
|
54
|
+
risk_level: str = "low"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, slots=True)
|
|
58
|
+
class BudgetSpec:
|
|
59
|
+
"""Declared default budget caps. Mirrors ``noeta.guards.budget.Budget`` 1:1
|
|
60
|
+
so a host can build the live guard straight from the spec. ``None`` ⇒ no
|
|
61
|
+
cap for that dimension."""
|
|
62
|
+
|
|
63
|
+
max_iterations: int | None = None
|
|
64
|
+
max_tool_calls: int | None = None
|
|
65
|
+
max_cost_usd: float | None = None
|
|
66
|
+
max_spawned_subtasks: int | None = None
|
|
67
|
+
max_subtask_depth: int | None = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True, slots=True)
|
|
71
|
+
class Capabilities:
|
|
72
|
+
"""Behaviour-shaping capabilities that are part of an Agent's **identity**
|
|
73
|
+
(not host config): which control surfaces the Agent may expose and whether
|
|
74
|
+
it may delegate. A capability change is a real identity change, not a
|
|
75
|
+
host-config tweak.
|
|
76
|
+
|
|
77
|
+
``spawnable`` lists the **names** of the subtask agents this Agent may
|
|
78
|
+
delegate to (stable identity strings, normalised to a sorted tuple).
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
todo_write: bool = False
|
|
82
|
+
ask_user_question: bool = False
|
|
83
|
+
delegation: bool = False
|
|
84
|
+
skill_invocation: bool = False
|
|
85
|
+
#: memory v1: the host wires the memory tool pack
|
|
86
|
+
#: (memory_write / memory_read), the index resident (content-channel
|
|
87
|
+
#: kind "memory", policy "evolving") and user-message recall.
|
|
88
|
+
memory: bool = False
|
|
89
|
+
#: whether a delegated subtask may inherit the parent task's
|
|
90
|
+
#: enabled MCP tool set. Per-spec opt-in: a child built off a spec with
|
|
91
|
+
#: ``mcp=True`` inherits the parent's enabled aliases (it connects its own
|
|
92
|
+
#: independent sessions, R-1 records its own specs); a child with
|
|
93
|
+
#: ``mcp=False`` gets no MCP tools at all. presets default: main /
|
|
94
|
+
#: general-purpose open it, explore / plan keep it closed.
|
|
95
|
+
mcp: bool = False
|
|
96
|
+
spawnable: tuple[str, ...] = ()
|
|
97
|
+
|
|
98
|
+
def __post_init__(self) -> None:
|
|
99
|
+
object.__setattr__(self, "spawnable", tuple(sorted(self.spawnable)))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True, slots=True)
|
|
103
|
+
class AgentSpec:
|
|
104
|
+
"""A named Agent's serializable identity.
|
|
105
|
+
|
|
106
|
+
Resolve target for the server/worker; recorded durably via ``AgentBound``.
|
|
107
|
+
Component lists are normalised to sorted tuples on construction so two specs
|
|
108
|
+
that differ only in author ordering are ``==`` (structural equality is the
|
|
109
|
+
identity comparison).
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
name: str
|
|
113
|
+
instructions: str
|
|
114
|
+
policy: ComponentRef
|
|
115
|
+
composer: ComponentRef = ComponentRef("three_segment")
|
|
116
|
+
tools: tuple[ToolRef, ...] = ()
|
|
117
|
+
skills: tuple[ComponentRef, ...] = ()
|
|
118
|
+
guards: tuple[ComponentRef, ...] = ()
|
|
119
|
+
observers: tuple[ComponentRef, ...] = ()
|
|
120
|
+
default_budget: BudgetSpec = field(default_factory=BudgetSpec)
|
|
121
|
+
#: Behaviour-shaping capabilities (control surfaces + delegation) that are
|
|
122
|
+
#: part of identity.
|
|
123
|
+
capabilities: Capabilities = field(default_factory=Capabilities)
|
|
124
|
+
#: Observational only (display name, owner, tags) — treated as cosmetic, not
|
|
125
|
+
#: behaviour-affecting agent identity.
|
|
126
|
+
metadata: Mapping[str, str] = field(default_factory=dict)
|
|
127
|
+
#: Preferred LLM model id for this agent. A host-config /
|
|
128
|
+
#: routing hint, **not** identity — swapping models must not change a
|
|
129
|
+
#: recording's agent identity. ``None`` ⇒ host default.
|
|
130
|
+
default_model: str | None = None
|
|
131
|
+
|
|
132
|
+
def __post_init__(self) -> None:
|
|
133
|
+
# Normalise to sorted tuples so identity is order-independent. Frozen
|
|
134
|
+
# dataclass ⇒ assign through object.__setattr__.
|
|
135
|
+
object.__setattr__(self, "tools", tuple(sorted(self.tools)))
|
|
136
|
+
object.__setattr__(self, "skills", tuple(sorted(self.skills)))
|
|
137
|
+
object.__setattr__(self, "guards", tuple(sorted(self.guards)))
|
|
138
|
+
object.__setattr__(self, "observers", tuple(sorted(self.observers)))
|
|
139
|
+
object.__setattr__(self, "metadata", dict(self.metadata))
|