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.
Files changed (169) hide show
  1. noeta/agent/registry.py +71 -0
  2. noeta/agent/spec.py +139 -0
  3. noeta/context/__init__.py +5 -0
  4. noeta/context/composer.py +922 -0
  5. noeta/context/content_channel.py +142 -0
  6. noeta/context/environment.py +199 -0
  7. noeta/context/instructions.py +156 -0
  8. noeta/context/memory.py +196 -0
  9. noeta/context/skills/__init__.py +20 -0
  10. noeta/context/skills/_frontmatter.py +225 -0
  11. noeta/context/skills/indexer.py +385 -0
  12. noeta/core/__init__.py +5 -0
  13. noeta/core/_decision_handlers.py +2035 -0
  14. noeta/core/composer.py +38 -0
  15. noeta/core/engine.py +1392 -0
  16. noeta/core/fold.py +896 -0
  17. noeta/core/hooks.py +87 -0
  18. noeta/core/observers.py +221 -0
  19. noeta/core/snapshot.py +86 -0
  20. noeta/core/wiring.py +41 -0
  21. noeta/execution/__init__.py +134 -0
  22. noeta/execution/background_subagent.py +294 -0
  23. noeta/execution/builder.py +1101 -0
  24. noeta/execution/commands.py +156 -0
  25. noeta/execution/driver.py +1887 -0
  26. noeta/execution/environment.py +179 -0
  27. noeta/execution/host.py +170 -0
  28. noeta/execution/instructions.py +134 -0
  29. noeta/execution/memory.py +218 -0
  30. noeta/execution/multi_turn.py +102 -0
  31. noeta/execution/resolver.py +772 -0
  32. noeta/execution/runner.py +523 -0
  33. noeta/execution/skills.py +482 -0
  34. noeta/execution/subtask_drain.py +806 -0
  35. noeta/execution/title.py +171 -0
  36. noeta/guards/__init__.py +31 -0
  37. noeta/guards/budget.py +139 -0
  38. noeta/guards/hook.py +126 -0
  39. noeta/guards/permission.py +287 -0
  40. noeta/guards/repetition.py +122 -0
  41. noeta/observers/__init__.py +43 -0
  42. noeta/observers/audit.py +362 -0
  43. noeta/observers/fanout.py +234 -0
  44. noeta/observers/hook.py +299 -0
  45. noeta/observers/metrics.py +79 -0
  46. noeta/observers/trace_export.py +208 -0
  47. noeta/policies/__init__.py +5 -0
  48. noeta/policies/_control_translate.py +35 -0
  49. noeta/policies/_workflow_sandbox.py +188 -0
  50. noeta/policies/control_semantics.py +1346 -0
  51. noeta/policies/control_tools.py +86 -0
  52. noeta/policies/descriptions/__init__.py +49 -0
  53. noeta/policies/descriptions/ask_user_question.md +31 -0
  54. noeta/policies/descriptions/run_workflow.md +71 -0
  55. noeta/policies/descriptions/skill.md +27 -0
  56. noeta/policies/descriptions/spawn_subagent.md +40 -0
  57. noeta/policies/descriptions/todo_write.md +28 -0
  58. noeta/policies/orchestration.py +509 -0
  59. noeta/policies/react.py +873 -0
  60. noeta/policies/skill_tools.py +127 -0
  61. noeta/policies/stub.py +41 -0
  62. noeta/presets/__init__.py +195 -0
  63. noeta/presets/prompts/__init__.py +15 -0
  64. noeta/presets/prompts/explore.md +9 -0
  65. noeta/presets/prompts/general-purpose.md +13 -0
  66. noeta/presets/prompts/main.md +18 -0
  67. noeta/presets/prompts/plan.md +13 -0
  68. noeta/protocols/__init__.py +55 -0
  69. noeta/protocols/canonical.py +136 -0
  70. noeta/protocols/composer.py +20 -0
  71. noeta/protocols/content_store.py +73 -0
  72. noeta/protocols/context_plan.py +67 -0
  73. noeta/protocols/decisions.py +425 -0
  74. noeta/protocols/dispatcher.py +221 -0
  75. noeta/protocols/engine.py +184 -0
  76. noeta/protocols/errors.py +235 -0
  77. noeta/protocols/event_log.py +326 -0
  78. noeta/protocols/events.py +1242 -0
  79. noeta/protocols/hooks.py +173 -0
  80. noeta/protocols/messages.py +321 -0
  81. noeta/protocols/policy.py +29 -0
  82. noeta/protocols/resources.py +48 -0
  83. noeta/protocols/step_context.py +40 -0
  84. noeta/protocols/step_transition.py +67 -0
  85. noeta/protocols/task.py +373 -0
  86. noeta/protocols/token_estimate.py +109 -0
  87. noeta/protocols/tool.py +162 -0
  88. noeta/protocols/tool_args.py +114 -0
  89. noeta/protocols/values.py +114 -0
  90. noeta/protocols/view.py +119 -0
  91. noeta/protocols/wake.py +231 -0
  92. noeta/providers/__init__.py +25 -0
  93. noeta/providers/anthropic.py +795 -0
  94. noeta/providers/catalog.py +276 -0
  95. noeta/providers/codecs.py +132 -0
  96. noeta/providers/openai_compat.py +491 -0
  97. noeta/providers/openai_responses.py +1006 -0
  98. noeta/read_models/__init__.py +18 -0
  99. noeta/read_models/sessions.py +96 -0
  100. noeta/runtime/__init__.py +10 -0
  101. noeta/runtime/background_shell.py +894 -0
  102. noeta/runtime/cancellation.py +46 -0
  103. noeta/runtime/compaction.py +226 -0
  104. noeta/runtime/file_checkpoint.py +64 -0
  105. noeta/runtime/llm.py +379 -0
  106. noeta/runtime/tool.py +253 -0
  107. noeta/runtime/worker.py +1097 -0
  108. noeta/storage/__init__.py +7 -0
  109. noeta/storage/_wake_match.py +36 -0
  110. noeta/storage/memory.py +859 -0
  111. noeta/storage/sqlite/__init__.py +35 -0
  112. noeta/storage/sqlite/_connection.py +133 -0
  113. noeta/storage/sqlite/_transaction.py +50 -0
  114. noeta/storage/sqlite/contentstore.py +101 -0
  115. noeta/storage/sqlite/dispatcher.py +823 -0
  116. noeta/storage/sqlite/eventlog.py +700 -0
  117. noeta/storage/sqlite/migrations.py +312 -0
  118. noeta/storage/sqlite/readonly.py +147 -0
  119. noeta/storage/stacks.py +73 -0
  120. noeta/testing/__init__.py +13 -0
  121. noeta/testing/composer.py +89 -0
  122. noeta/testing/fake_llm.py +71 -0
  123. noeta/testing/profile.py +321 -0
  124. noeta/testing/stub_provider.py +96 -0
  125. noeta/tools/__init__.py +12 -0
  126. noeta/tools/_env.py +38 -0
  127. noeta/tools/_invocation.py +152 -0
  128. noeta/tools/_limits.py +85 -0
  129. noeta/tools/_refs.py +26 -0
  130. noeta/tools/app/__init__.py +14 -0
  131. noeta/tools/app/_gateway.py +54 -0
  132. noeta/tools/app/open_app.py +105 -0
  133. noeta/tools/decorator.py +146 -0
  134. noeta/tools/descriptions/__init__.py +48 -0
  135. noeta/tools/descriptions/apply_patch.md +8 -0
  136. noeta/tools/descriptions/edit.md +7 -0
  137. noeta/tools/descriptions/glob.md +5 -0
  138. noeta/tools/descriptions/grep.md +7 -0
  139. noeta/tools/descriptions/open_app.md +21 -0
  140. noeta/tools/descriptions/read.md +8 -0
  141. noeta/tools/descriptions/shell_kill.md +6 -0
  142. noeta/tools/descriptions/shell_poll.md +5 -0
  143. noeta/tools/descriptions/shell_run.md +8 -0
  144. noeta/tools/descriptions/web_search.md +6 -0
  145. noeta/tools/descriptions/webfetch.md +6 -0
  146. noeta/tools/descriptions/write.md +6 -0
  147. noeta/tools/fake.py +96 -0
  148. noeta/tools/fs/__init__.py +142 -0
  149. noeta/tools/fs/_diff.py +61 -0
  150. noeta/tools/fs/_subprocess.py +111 -0
  151. noeta/tools/fs/_workspace.py +165 -0
  152. noeta/tools/fs/edit.py +428 -0
  153. noeta/tools/fs/patch.py +579 -0
  154. noeta/tools/fs/read.py +611 -0
  155. noeta/tools/fs/shell.py +730 -0
  156. noeta/tools/fs/skill_script.py +249 -0
  157. noeta/tools/mcp/__init__.py +81 -0
  158. noeta/tools/mcp/_client.py +312 -0
  159. noeta/tools/mcp/_http_client.py +254 -0
  160. noeta/tools/mcp/prompts.py +204 -0
  161. noeta/tools/mcp/resources.py +158 -0
  162. noeta/tools/mcp/tool.py +484 -0
  163. noeta/tools/memory.py +239 -0
  164. noeta/tools/web/__init__.py +46 -0
  165. noeta/tools/web/fetch.py +255 -0
  166. noeta/tools/web/search.py +237 -0
  167. noeta_runtime-0.1.0.dist-info/METADATA +36 -0
  168. noeta_runtime-0.1.0.dist-info/RECORD +169 -0
  169. noeta_runtime-0.1.0.dist-info/WHEEL +4 -0
@@ -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))
@@ -0,0 +1,5 @@
1
+ """L2 context layer: Composer and (Phase 1+) compaction / skills."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__: list[str] = []