noeta-runtime 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- noeta_runtime-0.1.0/.gitignore +50 -0
- noeta_runtime-0.1.0/PKG-INFO +36 -0
- noeta_runtime-0.1.0/README.md +14 -0
- noeta_runtime-0.1.0/noeta/agent/registry.py +71 -0
- noeta_runtime-0.1.0/noeta/agent/spec.py +139 -0
- noeta_runtime-0.1.0/noeta/context/__init__.py +5 -0
- noeta_runtime-0.1.0/noeta/context/composer.py +922 -0
- noeta_runtime-0.1.0/noeta/context/content_channel.py +142 -0
- noeta_runtime-0.1.0/noeta/context/environment.py +199 -0
- noeta_runtime-0.1.0/noeta/context/instructions.py +156 -0
- noeta_runtime-0.1.0/noeta/context/memory.py +196 -0
- noeta_runtime-0.1.0/noeta/context/skills/__init__.py +20 -0
- noeta_runtime-0.1.0/noeta/context/skills/_frontmatter.py +225 -0
- noeta_runtime-0.1.0/noeta/context/skills/indexer.py +385 -0
- noeta_runtime-0.1.0/noeta/core/__init__.py +5 -0
- noeta_runtime-0.1.0/noeta/core/_decision_handlers.py +2035 -0
- noeta_runtime-0.1.0/noeta/core/composer.py +38 -0
- noeta_runtime-0.1.0/noeta/core/engine.py +1392 -0
- noeta_runtime-0.1.0/noeta/core/fold.py +896 -0
- noeta_runtime-0.1.0/noeta/core/hooks.py +87 -0
- noeta_runtime-0.1.0/noeta/core/observers.py +221 -0
- noeta_runtime-0.1.0/noeta/core/snapshot.py +86 -0
- noeta_runtime-0.1.0/noeta/core/wiring.py +41 -0
- noeta_runtime-0.1.0/noeta/execution/__init__.py +134 -0
- noeta_runtime-0.1.0/noeta/execution/background_subagent.py +294 -0
- noeta_runtime-0.1.0/noeta/execution/builder.py +1101 -0
- noeta_runtime-0.1.0/noeta/execution/commands.py +156 -0
- noeta_runtime-0.1.0/noeta/execution/driver.py +1887 -0
- noeta_runtime-0.1.0/noeta/execution/environment.py +179 -0
- noeta_runtime-0.1.0/noeta/execution/host.py +170 -0
- noeta_runtime-0.1.0/noeta/execution/instructions.py +134 -0
- noeta_runtime-0.1.0/noeta/execution/memory.py +218 -0
- noeta_runtime-0.1.0/noeta/execution/multi_turn.py +102 -0
- noeta_runtime-0.1.0/noeta/execution/resolver.py +772 -0
- noeta_runtime-0.1.0/noeta/execution/runner.py +523 -0
- noeta_runtime-0.1.0/noeta/execution/skills.py +482 -0
- noeta_runtime-0.1.0/noeta/execution/subtask_drain.py +806 -0
- noeta_runtime-0.1.0/noeta/execution/title.py +171 -0
- noeta_runtime-0.1.0/noeta/guards/__init__.py +31 -0
- noeta_runtime-0.1.0/noeta/guards/budget.py +139 -0
- noeta_runtime-0.1.0/noeta/guards/hook.py +126 -0
- noeta_runtime-0.1.0/noeta/guards/permission.py +287 -0
- noeta_runtime-0.1.0/noeta/guards/repetition.py +122 -0
- noeta_runtime-0.1.0/noeta/observers/__init__.py +43 -0
- noeta_runtime-0.1.0/noeta/observers/audit.py +362 -0
- noeta_runtime-0.1.0/noeta/observers/fanout.py +234 -0
- noeta_runtime-0.1.0/noeta/observers/hook.py +299 -0
- noeta_runtime-0.1.0/noeta/observers/metrics.py +79 -0
- noeta_runtime-0.1.0/noeta/observers/trace_export.py +208 -0
- noeta_runtime-0.1.0/noeta/policies/__init__.py +5 -0
- noeta_runtime-0.1.0/noeta/policies/_control_translate.py +35 -0
- noeta_runtime-0.1.0/noeta/policies/_workflow_sandbox.py +188 -0
- noeta_runtime-0.1.0/noeta/policies/control_semantics.py +1346 -0
- noeta_runtime-0.1.0/noeta/policies/control_tools.py +86 -0
- noeta_runtime-0.1.0/noeta/policies/descriptions/__init__.py +49 -0
- noeta_runtime-0.1.0/noeta/policies/descriptions/ask_user_question.md +31 -0
- noeta_runtime-0.1.0/noeta/policies/descriptions/run_workflow.md +71 -0
- noeta_runtime-0.1.0/noeta/policies/descriptions/skill.md +27 -0
- noeta_runtime-0.1.0/noeta/policies/descriptions/spawn_subagent.md +40 -0
- noeta_runtime-0.1.0/noeta/policies/descriptions/todo_write.md +28 -0
- noeta_runtime-0.1.0/noeta/policies/orchestration.py +509 -0
- noeta_runtime-0.1.0/noeta/policies/react.py +873 -0
- noeta_runtime-0.1.0/noeta/policies/skill_tools.py +127 -0
- noeta_runtime-0.1.0/noeta/policies/stub.py +41 -0
- noeta_runtime-0.1.0/noeta/presets/__init__.py +195 -0
- noeta_runtime-0.1.0/noeta/presets/prompts/__init__.py +15 -0
- noeta_runtime-0.1.0/noeta/presets/prompts/explore.md +9 -0
- noeta_runtime-0.1.0/noeta/presets/prompts/general-purpose.md +13 -0
- noeta_runtime-0.1.0/noeta/presets/prompts/main.md +18 -0
- noeta_runtime-0.1.0/noeta/presets/prompts/plan.md +13 -0
- noeta_runtime-0.1.0/noeta/protocols/__init__.py +55 -0
- noeta_runtime-0.1.0/noeta/protocols/canonical.py +136 -0
- noeta_runtime-0.1.0/noeta/protocols/composer.py +20 -0
- noeta_runtime-0.1.0/noeta/protocols/content_store.py +73 -0
- noeta_runtime-0.1.0/noeta/protocols/context_plan.py +67 -0
- noeta_runtime-0.1.0/noeta/protocols/decisions.py +425 -0
- noeta_runtime-0.1.0/noeta/protocols/dispatcher.py +221 -0
- noeta_runtime-0.1.0/noeta/protocols/engine.py +184 -0
- noeta_runtime-0.1.0/noeta/protocols/errors.py +235 -0
- noeta_runtime-0.1.0/noeta/protocols/event_log.py +326 -0
- noeta_runtime-0.1.0/noeta/protocols/events.py +1242 -0
- noeta_runtime-0.1.0/noeta/protocols/hooks.py +173 -0
- noeta_runtime-0.1.0/noeta/protocols/messages.py +321 -0
- noeta_runtime-0.1.0/noeta/protocols/policy.py +29 -0
- noeta_runtime-0.1.0/noeta/protocols/resources.py +48 -0
- noeta_runtime-0.1.0/noeta/protocols/step_context.py +40 -0
- noeta_runtime-0.1.0/noeta/protocols/step_transition.py +67 -0
- noeta_runtime-0.1.0/noeta/protocols/task.py +373 -0
- noeta_runtime-0.1.0/noeta/protocols/token_estimate.py +109 -0
- noeta_runtime-0.1.0/noeta/protocols/tool.py +162 -0
- noeta_runtime-0.1.0/noeta/protocols/tool_args.py +114 -0
- noeta_runtime-0.1.0/noeta/protocols/values.py +114 -0
- noeta_runtime-0.1.0/noeta/protocols/view.py +119 -0
- noeta_runtime-0.1.0/noeta/protocols/wake.py +231 -0
- noeta_runtime-0.1.0/noeta/providers/__init__.py +25 -0
- noeta_runtime-0.1.0/noeta/providers/anthropic.py +795 -0
- noeta_runtime-0.1.0/noeta/providers/catalog.py +276 -0
- noeta_runtime-0.1.0/noeta/providers/codecs.py +132 -0
- noeta_runtime-0.1.0/noeta/providers/openai_compat.py +491 -0
- noeta_runtime-0.1.0/noeta/providers/openai_responses.py +1006 -0
- noeta_runtime-0.1.0/noeta/read_models/__init__.py +18 -0
- noeta_runtime-0.1.0/noeta/read_models/sessions.py +96 -0
- noeta_runtime-0.1.0/noeta/runtime/__init__.py +10 -0
- noeta_runtime-0.1.0/noeta/runtime/background_shell.py +894 -0
- noeta_runtime-0.1.0/noeta/runtime/cancellation.py +46 -0
- noeta_runtime-0.1.0/noeta/runtime/compaction.py +226 -0
- noeta_runtime-0.1.0/noeta/runtime/file_checkpoint.py +64 -0
- noeta_runtime-0.1.0/noeta/runtime/llm.py +379 -0
- noeta_runtime-0.1.0/noeta/runtime/tool.py +253 -0
- noeta_runtime-0.1.0/noeta/runtime/worker.py +1097 -0
- noeta_runtime-0.1.0/noeta/storage/__init__.py +7 -0
- noeta_runtime-0.1.0/noeta/storage/_wake_match.py +36 -0
- noeta_runtime-0.1.0/noeta/storage/memory.py +859 -0
- noeta_runtime-0.1.0/noeta/storage/sqlite/__init__.py +35 -0
- noeta_runtime-0.1.0/noeta/storage/sqlite/_connection.py +133 -0
- noeta_runtime-0.1.0/noeta/storage/sqlite/_transaction.py +50 -0
- noeta_runtime-0.1.0/noeta/storage/sqlite/contentstore.py +101 -0
- noeta_runtime-0.1.0/noeta/storage/sqlite/dispatcher.py +823 -0
- noeta_runtime-0.1.0/noeta/storage/sqlite/eventlog.py +700 -0
- noeta_runtime-0.1.0/noeta/storage/sqlite/migrations.py +312 -0
- noeta_runtime-0.1.0/noeta/storage/sqlite/readonly.py +147 -0
- noeta_runtime-0.1.0/noeta/storage/stacks.py +73 -0
- noeta_runtime-0.1.0/noeta/testing/__init__.py +13 -0
- noeta_runtime-0.1.0/noeta/testing/composer.py +89 -0
- noeta_runtime-0.1.0/noeta/testing/fake_llm.py +71 -0
- noeta_runtime-0.1.0/noeta/testing/profile.py +321 -0
- noeta_runtime-0.1.0/noeta/testing/stub_provider.py +96 -0
- noeta_runtime-0.1.0/noeta/tools/__init__.py +12 -0
- noeta_runtime-0.1.0/noeta/tools/_env.py +38 -0
- noeta_runtime-0.1.0/noeta/tools/_invocation.py +152 -0
- noeta_runtime-0.1.0/noeta/tools/_limits.py +85 -0
- noeta_runtime-0.1.0/noeta/tools/_refs.py +26 -0
- noeta_runtime-0.1.0/noeta/tools/app/__init__.py +14 -0
- noeta_runtime-0.1.0/noeta/tools/app/_gateway.py +54 -0
- noeta_runtime-0.1.0/noeta/tools/app/open_app.py +105 -0
- noeta_runtime-0.1.0/noeta/tools/decorator.py +146 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/__init__.py +48 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/apply_patch.md +8 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/edit.md +7 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/glob.md +5 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/grep.md +7 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/open_app.md +21 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/read.md +8 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/shell_kill.md +6 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/shell_poll.md +5 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/shell_run.md +8 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/web_search.md +6 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/webfetch.md +6 -0
- noeta_runtime-0.1.0/noeta/tools/descriptions/write.md +6 -0
- noeta_runtime-0.1.0/noeta/tools/fake.py +96 -0
- noeta_runtime-0.1.0/noeta/tools/fs/__init__.py +142 -0
- noeta_runtime-0.1.0/noeta/tools/fs/_diff.py +61 -0
- noeta_runtime-0.1.0/noeta/tools/fs/_subprocess.py +111 -0
- noeta_runtime-0.1.0/noeta/tools/fs/_workspace.py +165 -0
- noeta_runtime-0.1.0/noeta/tools/fs/edit.py +428 -0
- noeta_runtime-0.1.0/noeta/tools/fs/patch.py +579 -0
- noeta_runtime-0.1.0/noeta/tools/fs/read.py +611 -0
- noeta_runtime-0.1.0/noeta/tools/fs/shell.py +730 -0
- noeta_runtime-0.1.0/noeta/tools/fs/skill_script.py +249 -0
- noeta_runtime-0.1.0/noeta/tools/mcp/__init__.py +81 -0
- noeta_runtime-0.1.0/noeta/tools/mcp/_client.py +312 -0
- noeta_runtime-0.1.0/noeta/tools/mcp/_http_client.py +254 -0
- noeta_runtime-0.1.0/noeta/tools/mcp/prompts.py +204 -0
- noeta_runtime-0.1.0/noeta/tools/mcp/resources.py +158 -0
- noeta_runtime-0.1.0/noeta/tools/mcp/tool.py +484 -0
- noeta_runtime-0.1.0/noeta/tools/memory.py +239 -0
- noeta_runtime-0.1.0/noeta/tools/web/__init__.py +46 -0
- noeta_runtime-0.1.0/noeta/tools/web/fetch.py +255 -0
- noeta_runtime-0.1.0/noeta/tools/web/search.py +237 -0
- noeta_runtime-0.1.0/pyproject.toml +36 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Claude Code local private config (skills travel with the project; not ignored)
|
|
2
|
+
.claude/settings.local.json
|
|
3
|
+
.claude/*.local.*
|
|
4
|
+
.claude/worktrees/
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
*.so
|
|
11
|
+
*.egg-info/
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.import_linter_cache/
|
|
19
|
+
.tox/
|
|
20
|
+
htmlcov/
|
|
21
|
+
.coverage
|
|
22
|
+
.coverage.*
|
|
23
|
+
dist/
|
|
24
|
+
build/
|
|
25
|
+
*.egg
|
|
26
|
+
node_modules/
|
|
27
|
+
|
|
28
|
+
# IDE / Editor
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
*~
|
|
34
|
+
|
|
35
|
+
# OS
|
|
36
|
+
.DS_Store
|
|
37
|
+
Thumbs.db
|
|
38
|
+
|
|
39
|
+
# Local demo / scratch artifacts
|
|
40
|
+
*.sqlite
|
|
41
|
+
*.sqlite-shm
|
|
42
|
+
*.sqlite-wal
|
|
43
|
+
*.tmp.xml
|
|
44
|
+
*.tmp
|
|
45
|
+
|
|
46
|
+
# Local runtime config (contains api_key); only the noeta.config.example.json template is committed
|
|
47
|
+
/noeta.config.json
|
|
48
|
+
|
|
49
|
+
# Local artifacts from Playwright MCP test runs (console logs / page snapshots)
|
|
50
|
+
/.playwright-mcp/
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: noeta-runtime
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Noeta runtime engine: the kernel (protocols / core / runtime / storage / guards / observers / read_models) plus the agent materials (policies / tools / providers / context), the execution machinery, the agent identity layer (spec / registry) and the official presets — everything needed to run an agent in-process.
|
|
5
|
+
Project-URL: Homepage, https://github.com/initxy/noeta
|
|
6
|
+
Project-URL: Repository, https://github.com/initxy/noeta
|
|
7
|
+
Author: The Noeta Authors
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: httpx>=0.28.1
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# noeta-runtime
|
|
24
|
+
|
|
25
|
+
The Noeta **engine**: the pure kernel (`protocols`, `core` = Engine + fold +
|
|
26
|
+
snapshot, and the kernel services — Worker / Dispatcher / ToolRuntime /
|
|
27
|
+
RuntimeLLMClient, storage, guards, observers, read models) **plus the agent
|
|
28
|
+
materials** that run on it (`policies`, `tools`, `providers`, `context`), the
|
|
29
|
+
execution machinery, the agent identity layer (`agent` = AgentSpec / registry)
|
|
30
|
+
and the official preset quartet. Everything needed to run an agent in-process.
|
|
31
|
+
|
|
32
|
+
Part of the [Noeta](https://github.com/initxy/noeta) workspace. Apache-2.0.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install -e packages/noeta-runtime
|
|
36
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# noeta-runtime
|
|
2
|
+
|
|
3
|
+
The Noeta **engine**: the pure kernel (`protocols`, `core` = Engine + fold +
|
|
4
|
+
snapshot, and the kernel services — Worker / Dispatcher / ToolRuntime /
|
|
5
|
+
RuntimeLLMClient, storage, guards, observers, read models) **plus the agent
|
|
6
|
+
materials** that run on it (`policies`, `tools`, `providers`, `context`), the
|
|
7
|
+
execution machinery, the agent identity layer (`agent` = AgentSpec / registry)
|
|
8
|
+
and the official preset quartet. Everything needed to run an agent in-process.
|
|
9
|
+
|
|
10
|
+
Part of the [Noeta](https://github.com/initxy/noeta) workspace. Apache-2.0.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install -e packages/noeta-runtime
|
|
14
|
+
```
|
|
@@ -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)
|
|
@@ -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))
|