activegraph 1.0.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.
- activegraph/__init__.py +222 -0
- activegraph/__main__.py +5 -0
- activegraph/behaviors/__init__.py +1 -0
- activegraph/behaviors/base.py +153 -0
- activegraph/behaviors/decorators.py +218 -0
- activegraph/cli/__init__.py +17 -0
- activegraph/cli/main.py +793 -0
- activegraph/cli/quickstart.py +556 -0
- activegraph/core/__init__.py +1 -0
- activegraph/core/clock.py +46 -0
- activegraph/core/event.py +33 -0
- activegraph/core/graph.py +846 -0
- activegraph/core/ids.py +135 -0
- activegraph/core/patch.py +47 -0
- activegraph/core/view.py +59 -0
- activegraph/errors.py +345 -0
- activegraph/frame.py +15 -0
- activegraph/llm/__init__.py +51 -0
- activegraph/llm/anthropic.py +339 -0
- activegraph/llm/cache.py +180 -0
- activegraph/llm/errors.py +257 -0
- activegraph/llm/prompt.py +420 -0
- activegraph/llm/provider.py +66 -0
- activegraph/llm/recorded.py +270 -0
- activegraph/llm/types.py +130 -0
- activegraph/observability/__init__.py +61 -0
- activegraph/observability/logging.py +205 -0
- activegraph/observability/metrics.py +219 -0
- activegraph/observability/migration.py +404 -0
- activegraph/observability/prometheus.py +101 -0
- activegraph/observability/status.py +83 -0
- activegraph/packs/__init__.py +999 -0
- activegraph/packs/diligence/__init__.py +86 -0
- activegraph/packs/diligence/behaviors.py +447 -0
- activegraph/packs/diligence/fixtures/__init__.py +364 -0
- activegraph/packs/diligence/fixtures/companies.py +511 -0
- activegraph/packs/diligence/object_types.py +163 -0
- activegraph/packs/diligence/prompts/document_researcher.md +25 -0
- activegraph/packs/diligence/prompts/memo_synthesizer.md +35 -0
- activegraph/packs/diligence/prompts/question_generator.md +25 -0
- activegraph/packs/diligence/prompts/risk_identifier.md +26 -0
- activegraph/packs/diligence/settings.py +60 -0
- activegraph/packs/diligence/tools.py +124 -0
- activegraph/packs/loader.py +709 -0
- activegraph/packs/scaffold.py +317 -0
- activegraph/policy.py +20 -0
- activegraph/runtime/__init__.py +1 -0
- activegraph/runtime/behavior_graph.py +151 -0
- activegraph/runtime/budget.py +120 -0
- activegraph/runtime/config_errors.py +124 -0
- activegraph/runtime/diff.py +145 -0
- activegraph/runtime/errors.py +216 -0
- activegraph/runtime/exec_errors.py +232 -0
- activegraph/runtime/patterns.py +946 -0
- activegraph/runtime/queue.py +27 -0
- activegraph/runtime/registration_errors.py +291 -0
- activegraph/runtime/registry.py +111 -0
- activegraph/runtime/runtime.py +2441 -0
- activegraph/runtime/scheduler.py +206 -0
- activegraph/runtime/view_builder.py +65 -0
- activegraph/store/__init__.py +41 -0
- activegraph/store/base.py +66 -0
- activegraph/store/conformance.py +161 -0
- activegraph/store/errors.py +84 -0
- activegraph/store/memory.py +118 -0
- activegraph/store/postgres.py +609 -0
- activegraph/store/serde.py +202 -0
- activegraph/store/sqlite.py +446 -0
- activegraph/store/url.py +230 -0
- activegraph/tools/__init__.py +64 -0
- activegraph/tools/base.py +57 -0
- activegraph/tools/cache.py +157 -0
- activegraph/tools/context.py +48 -0
- activegraph/tools/decorators.py +70 -0
- activegraph/tools/errors.py +320 -0
- activegraph/tools/graph_query.py +94 -0
- activegraph/tools/recorded.py +205 -0
- activegraph/tools/web_fetch.py +80 -0
- activegraph/trace/__init__.py +1 -0
- activegraph/trace/causal.py +123 -0
- activegraph/trace/printer.py +495 -0
- activegraph-1.0.0.dist-info/METADATA +282 -0
- activegraph-1.0.0.dist-info/RECORD +86 -0
- activegraph-1.0.0.dist-info/WHEEL +5 -0
- activegraph-1.0.0.dist-info/entry_points.txt +5 -0
- activegraph-1.0.0.dist-info/top_level.txt +1 -0
activegraph/__init__.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Active Graph Runtime. Public API surface.
|
|
2
|
+
|
|
3
|
+
The graph is the world. Behaviors are physics. The trace is the proof.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from activegraph.behaviors.base import Behavior, LLMBehavior, RelationBehavior
|
|
7
|
+
from activegraph.behaviors.decorators import (
|
|
8
|
+
behavior,
|
|
9
|
+
clear_registry,
|
|
10
|
+
get_registry,
|
|
11
|
+
llm_behavior,
|
|
12
|
+
relation_behavior,
|
|
13
|
+
)
|
|
14
|
+
from activegraph.core.clock import Clock, FrozenClock, TickingClock
|
|
15
|
+
from activegraph.core.event import Event
|
|
16
|
+
from activegraph.core.graph import Graph, Object, Relation
|
|
17
|
+
from activegraph.core.ids import IDGen
|
|
18
|
+
from activegraph.core.patch import Patch
|
|
19
|
+
from activegraph.core.view import View
|
|
20
|
+
from activegraph.errors import (
|
|
21
|
+
ActiveGraphError,
|
|
22
|
+
ConfigurationError,
|
|
23
|
+
ExecutionError,
|
|
24
|
+
MissingOptionalDependency,
|
|
25
|
+
PackError,
|
|
26
|
+
PatternError,
|
|
27
|
+
RegistrationError,
|
|
28
|
+
ReplayError,
|
|
29
|
+
StorageError,
|
|
30
|
+
)
|
|
31
|
+
from activegraph.runtime.registration_errors import (
|
|
32
|
+
AmbiguousBehaviorError,
|
|
33
|
+
AmbiguousToolError,
|
|
34
|
+
BehaviorNotFoundError,
|
|
35
|
+
InvalidToolRegistration,
|
|
36
|
+
ToolNotFoundError,
|
|
37
|
+
)
|
|
38
|
+
from activegraph.runtime.scheduler import InvalidActivateAfter
|
|
39
|
+
from activegraph.frame import Frame
|
|
40
|
+
from activegraph.llm.errors import LLMBehaviorError, MissingProviderError
|
|
41
|
+
from activegraph.policy import Policy
|
|
42
|
+
from activegraph.runtime.budget import Budget
|
|
43
|
+
from activegraph.runtime.diff import Diff, DivergentObject, DivergentRelation
|
|
44
|
+
from activegraph.runtime.config_errors import (
|
|
45
|
+
IncompatibleRuntimeState,
|
|
46
|
+
InvalidArgumentType,
|
|
47
|
+
InvalidRuntimeConfiguration,
|
|
48
|
+
)
|
|
49
|
+
from activegraph.runtime.errors import ReplayDivergenceError
|
|
50
|
+
from activegraph.runtime.exec_errors import (
|
|
51
|
+
ApprovalNotFoundError,
|
|
52
|
+
InternalEvaluatorError,
|
|
53
|
+
InvalidPatchLifecycleState,
|
|
54
|
+
RuntimeContextRequiredError,
|
|
55
|
+
)
|
|
56
|
+
from activegraph.runtime.patterns import UnsupportedPatternError
|
|
57
|
+
from activegraph.runtime.runtime import Runtime
|
|
58
|
+
from activegraph.store import (
|
|
59
|
+
CorruptedEventPayloadError,
|
|
60
|
+
DuplicateEventError,
|
|
61
|
+
EventNotFoundError,
|
|
62
|
+
EventStore,
|
|
63
|
+
InMemoryEventStore,
|
|
64
|
+
InvalidStoreURL,
|
|
65
|
+
NonSerializableEventError,
|
|
66
|
+
RunRecord,
|
|
67
|
+
SQLiteEventStore,
|
|
68
|
+
SchemaVersionMismatch,
|
|
69
|
+
open_store,
|
|
70
|
+
parse_store_url,
|
|
71
|
+
)
|
|
72
|
+
# v0.7 public surface for tools
|
|
73
|
+
from activegraph.tools import (
|
|
74
|
+
MissingToolError,
|
|
75
|
+
Tool,
|
|
76
|
+
ToolContext,
|
|
77
|
+
ToolError,
|
|
78
|
+
UnknownToolError,
|
|
79
|
+
clear_tool_registry,
|
|
80
|
+
get_tool_registry,
|
|
81
|
+
tool,
|
|
82
|
+
)
|
|
83
|
+
# v0.8 observability surface
|
|
84
|
+
from activegraph.observability import (
|
|
85
|
+
Metrics,
|
|
86
|
+
MigrationReport,
|
|
87
|
+
MigrationRunReport,
|
|
88
|
+
NoOpMetrics,
|
|
89
|
+
PrometheusMetrics,
|
|
90
|
+
RuntimeStatus,
|
|
91
|
+
configure_logging,
|
|
92
|
+
migrate,
|
|
93
|
+
)
|
|
94
|
+
# v0.9 packs surface (top-level: the API for *using* packs from user code).
|
|
95
|
+
# Pack-aware decorators live under `activegraph.packs` and are intentionally
|
|
96
|
+
# NOT re-exported here — pack authors must import them from `activegraph.packs`
|
|
97
|
+
# so the import path makes the boundary explicit. CONTRACT v0.9 #3.
|
|
98
|
+
from activegraph.packs import (
|
|
99
|
+
DiscoveredPack,
|
|
100
|
+
EmptySettings,
|
|
101
|
+
ObjectType,
|
|
102
|
+
Pack,
|
|
103
|
+
PackConflictError,
|
|
104
|
+
PackError,
|
|
105
|
+
PackNotFoundError,
|
|
106
|
+
PackPolicy,
|
|
107
|
+
PackPrompt,
|
|
108
|
+
PackPromptLoadError,
|
|
109
|
+
PackSchemaViolation,
|
|
110
|
+
PackSettingsMissingError,
|
|
111
|
+
PackValidationError,
|
|
112
|
+
PackVersionConflictError,
|
|
113
|
+
PendingApproval,
|
|
114
|
+
RelationType,
|
|
115
|
+
clear_discovery_cache,
|
|
116
|
+
discover,
|
|
117
|
+
load_by_name,
|
|
118
|
+
load_prompts_from_dir,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
__all__ = [
|
|
122
|
+
"ActiveGraphError",
|
|
123
|
+
"AmbiguousBehaviorError",
|
|
124
|
+
"AmbiguousToolError",
|
|
125
|
+
"ApprovalNotFoundError",
|
|
126
|
+
"Behavior",
|
|
127
|
+
"BehaviorNotFoundError",
|
|
128
|
+
"Budget",
|
|
129
|
+
"Clock",
|
|
130
|
+
"ConfigurationError",
|
|
131
|
+
"CorruptedEventPayloadError",
|
|
132
|
+
"Diff",
|
|
133
|
+
"DiscoveredPack",
|
|
134
|
+
"DivergentObject",
|
|
135
|
+
"DivergentRelation",
|
|
136
|
+
"DuplicateEventError",
|
|
137
|
+
"EmptySettings",
|
|
138
|
+
"Event",
|
|
139
|
+
"EventNotFoundError",
|
|
140
|
+
"EventStore",
|
|
141
|
+
"ExecutionError",
|
|
142
|
+
"Frame",
|
|
143
|
+
"FrozenClock",
|
|
144
|
+
"Graph",
|
|
145
|
+
"IDGen",
|
|
146
|
+
"InMemoryEventStore",
|
|
147
|
+
"IncompatibleRuntimeState",
|
|
148
|
+
"InternalEvaluatorError",
|
|
149
|
+
"InvalidActivateAfter",
|
|
150
|
+
"InvalidArgumentType",
|
|
151
|
+
"InvalidPatchLifecycleState",
|
|
152
|
+
"InvalidRuntimeConfiguration",
|
|
153
|
+
"InvalidStoreURL",
|
|
154
|
+
"InvalidToolRegistration",
|
|
155
|
+
"LLMBehavior",
|
|
156
|
+
"LLMBehaviorError",
|
|
157
|
+
"Metrics",
|
|
158
|
+
"MigrationReport",
|
|
159
|
+
"MigrationRunReport",
|
|
160
|
+
"MissingOptionalDependency",
|
|
161
|
+
"MissingProviderError",
|
|
162
|
+
"MissingToolError",
|
|
163
|
+
"NoOpMetrics",
|
|
164
|
+
"NonSerializableEventError",
|
|
165
|
+
"Object",
|
|
166
|
+
"ObjectType",
|
|
167
|
+
"Pack",
|
|
168
|
+
"PackConflictError",
|
|
169
|
+
"PackError",
|
|
170
|
+
"PackNotFoundError",
|
|
171
|
+
"PackPolicy",
|
|
172
|
+
"PackPromptLoadError",
|
|
173
|
+
"PackPrompt",
|
|
174
|
+
"PackSchemaViolation",
|
|
175
|
+
"PackSettingsMissingError",
|
|
176
|
+
"PackValidationError",
|
|
177
|
+
"PackVersionConflictError",
|
|
178
|
+
"PatternError",
|
|
179
|
+
"Patch",
|
|
180
|
+
"PendingApproval",
|
|
181
|
+
"Policy",
|
|
182
|
+
"PrometheusMetrics",
|
|
183
|
+
"Relation",
|
|
184
|
+
"RelationBehavior",
|
|
185
|
+
"RegistrationError",
|
|
186
|
+
"RelationType",
|
|
187
|
+
"ReplayDivergenceError",
|
|
188
|
+
"ReplayError",
|
|
189
|
+
"RunRecord",
|
|
190
|
+
"Runtime",
|
|
191
|
+
"RuntimeContextRequiredError",
|
|
192
|
+
"RuntimeStatus",
|
|
193
|
+
"SQLiteEventStore",
|
|
194
|
+
"SchemaVersionMismatch",
|
|
195
|
+
"StorageError",
|
|
196
|
+
"TickingClock",
|
|
197
|
+
"Tool",
|
|
198
|
+
"ToolContext",
|
|
199
|
+
"ToolError",
|
|
200
|
+
"ToolNotFoundError",
|
|
201
|
+
"UnknownToolError",
|
|
202
|
+
"UnsupportedPatternError",
|
|
203
|
+
"View",
|
|
204
|
+
"behavior",
|
|
205
|
+
"clear_discovery_cache",
|
|
206
|
+
"clear_registry",
|
|
207
|
+
"clear_tool_registry",
|
|
208
|
+
"configure_logging",
|
|
209
|
+
"discover",
|
|
210
|
+
"get_registry",
|
|
211
|
+
"get_tool_registry",
|
|
212
|
+
"llm_behavior",
|
|
213
|
+
"load_by_name",
|
|
214
|
+
"load_prompts_from_dir",
|
|
215
|
+
"migrate",
|
|
216
|
+
"open_store",
|
|
217
|
+
"parse_store_url",
|
|
218
|
+
"relation_behavior",
|
|
219
|
+
"tool",
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
__version__ = "1.0.0"
|
activegraph/__main__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Behavior decorators and base classes. Imports core only (CONTRACT #14)."""
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Behavior + RelationBehavior + LLMBehavior. Plain holders for metadata
|
|
2
|
+
plus the callable.
|
|
3
|
+
|
|
4
|
+
A Behavior is data, not magic. The decorator wraps a function in one of
|
|
5
|
+
these; class-based behaviors subclass directly. The runtime introspects
|
|
6
|
+
the metadata to match events and build views.
|
|
7
|
+
|
|
8
|
+
CONTRACT v0.6 #2: an LLMBehavior is structurally a Behavior whose
|
|
9
|
+
invocation lifecycle is owned by the runtime — the developer-supplied
|
|
10
|
+
function is the 4-arg `handler` (event, graph, ctx, llm_output), NOT
|
|
11
|
+
the 3-arg `fn`. Per CONTRACT v0.6 #20, `LLMBehavior.build_prompt` is
|
|
12
|
+
public so a developer can inspect the exact bytes that would be sent
|
|
13
|
+
to the model without making a call.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from activegraph.core.event import Event
|
|
23
|
+
from activegraph.core.graph import Graph
|
|
24
|
+
from activegraph.frame import Frame
|
|
25
|
+
from activegraph.llm.prompt import AssembledPrompt
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _llm_behavior_fn_placeholder(event, graph, ctx) -> None: # pragma: no cover
|
|
29
|
+
raise RuntimeError(
|
|
30
|
+
"LLMBehavior.fn invoked directly. The runtime owns LLM behavior "
|
|
31
|
+
"invocation via _invoke_llm; calling .run() bypasses prompt "
|
|
32
|
+
"assembly, the provider, and the LLM event log. This is a bug."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Behavior:
|
|
38
|
+
name: str
|
|
39
|
+
fn: Callable[..., None]
|
|
40
|
+
on: list[str] = field(default_factory=list)
|
|
41
|
+
where: Optional[dict[str, Any]] = None
|
|
42
|
+
view_spec: Optional[dict[str, Any]] = None
|
|
43
|
+
creates: list[str] = field(default_factory=list)
|
|
44
|
+
budget: Optional[dict[str, Any]] = None
|
|
45
|
+
priority: int = 0 # reserved; v0 ties resolved by registration order
|
|
46
|
+
# CONTRACT v0.7 #8 / #9 / #11. A Cypher subset pattern string.
|
|
47
|
+
# Compiled at registration time. None = event-type-only matching
|
|
48
|
+
# (CONTRACT #10).
|
|
49
|
+
pattern: Optional[str] = None
|
|
50
|
+
pattern_matcher: Any = None # PatternMatcher; set by Registry at startup
|
|
51
|
+
# CONTRACT v0.7 #13. Event-count delay. None = fire immediately.
|
|
52
|
+
activate_after: Optional[int] = None
|
|
53
|
+
|
|
54
|
+
def run(self, event, graph, ctx) -> None:
|
|
55
|
+
self.fn(event, graph, ctx)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class RelationBehavior:
|
|
60
|
+
name: str
|
|
61
|
+
fn: Callable[..., None]
|
|
62
|
+
relation_type: str
|
|
63
|
+
on: list[str] = field(default_factory=list)
|
|
64
|
+
where: Optional[dict[str, Any]] = None
|
|
65
|
+
view_spec: Optional[dict[str, Any]] = None
|
|
66
|
+
creates: list[str] = field(default_factory=list)
|
|
67
|
+
budget: Optional[dict[str, Any]] = None
|
|
68
|
+
priority: int = 0
|
|
69
|
+
# Pattern subscriptions also work on relation behaviors. The match
|
|
70
|
+
# fires once per (event, relation) pair when the pattern matches.
|
|
71
|
+
pattern: Optional[str] = None
|
|
72
|
+
pattern_matcher: Any = None
|
|
73
|
+
activate_after: Optional[int] = None
|
|
74
|
+
|
|
75
|
+
def run(self, relation, event, graph, ctx) -> None:
|
|
76
|
+
self.fn(relation, event, graph, ctx)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class LLMBehavior(Behavior):
|
|
81
|
+
"""A behavior whose body is an LLM call.
|
|
82
|
+
|
|
83
|
+
The runtime owns prompt assembly, cache lookup, the provider call,
|
|
84
|
+
event emission, and structured-output parsing. The developer's
|
|
85
|
+
`handler` is invoked only after the LLM has responded (or a cached
|
|
86
|
+
response was found) and the output was successfully parsed.
|
|
87
|
+
|
|
88
|
+
CONTRACT v0.7: `tools=` declares a list of `Tool` objects (or
|
|
89
|
+
strings naming globally-registered tools) the LLM can call during
|
|
90
|
+
the turn loop. The runtime orchestrates the loop; the handler
|
|
91
|
+
receives only the final structured output, never raw tool calls.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
handler: Optional[Callable[..., None]] = None
|
|
95
|
+
description: str = ""
|
|
96
|
+
model: str = "claude-sonnet-4-5"
|
|
97
|
+
output_schema: Optional[type] = None
|
|
98
|
+
deterministic: bool = False
|
|
99
|
+
max_tokens: int = 4096
|
|
100
|
+
temperature: float = 0.7
|
|
101
|
+
top_p: float = 1.0
|
|
102
|
+
timeout_seconds: float = 60.0
|
|
103
|
+
prompt_template: Optional[str] = None
|
|
104
|
+
# v0.7
|
|
105
|
+
tools: list = field(default_factory=list)
|
|
106
|
+
max_tool_turns: int = 6
|
|
107
|
+
|
|
108
|
+
def build_prompt(
|
|
109
|
+
self,
|
|
110
|
+
event: "Event",
|
|
111
|
+
graph: "Graph",
|
|
112
|
+
*,
|
|
113
|
+
frame: Optional["Frame"] = None,
|
|
114
|
+
) -> "AssembledPrompt":
|
|
115
|
+
"""Assemble the prompt that would be sent for this event.
|
|
116
|
+
|
|
117
|
+
CONTRACT v0.6 #20 — public so a developer can inspect prompts
|
|
118
|
+
without making an API call. Reproducible (pure over inputs);
|
|
119
|
+
cheap (no I/O).
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
from activegraph.llm.prompt import assemble_prompt
|
|
123
|
+
from activegraph.runtime.view_builder import (
|
|
124
|
+
DEFAULT_RECENT_EVENTS,
|
|
125
|
+
_resolve_event_path,
|
|
126
|
+
build_view,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
view = build_view(self, event, graph)
|
|
130
|
+
around_id: Optional[str] = None
|
|
131
|
+
depth: Optional[int] = None
|
|
132
|
+
if self.view_spec:
|
|
133
|
+
ar_expr = self.view_spec.get("around")
|
|
134
|
+
if ar_expr:
|
|
135
|
+
around_id = _resolve_event_path(ar_expr, event)
|
|
136
|
+
depth = self.view_spec.get("depth")
|
|
137
|
+
return assemble_prompt(
|
|
138
|
+
behavior_name=self.name,
|
|
139
|
+
description=self.description,
|
|
140
|
+
model=self.model,
|
|
141
|
+
output_schema=self.output_schema,
|
|
142
|
+
creates=self.creates,
|
|
143
|
+
view=view,
|
|
144
|
+
event=event,
|
|
145
|
+
frame=frame,
|
|
146
|
+
around=around_id,
|
|
147
|
+
depth=depth,
|
|
148
|
+
max_tokens=self.max_tokens,
|
|
149
|
+
temperature=self.temperature,
|
|
150
|
+
top_p=self.top_p,
|
|
151
|
+
deterministic=self.deterministic,
|
|
152
|
+
prompt_template=self.prompt_template,
|
|
153
|
+
)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""@behavior, @relation_behavior, and @llm_behavior decorators
|
|
2
|
+
plus the global registry.
|
|
3
|
+
|
|
4
|
+
CONTRACT #6: regular behavior signature is (event, graph, ctx) -> None.
|
|
5
|
+
CONTRACT v0.6 #2: @llm_behavior handler signature is
|
|
6
|
+
(event, graph, ctx, llm_output) -> None — the 4th arg is bound by the
|
|
7
|
+
decorator's wrapper, not by the runtime.
|
|
8
|
+
|
|
9
|
+
The global registry exists so the README quickstart works without an
|
|
10
|
+
explicit `behaviors=[...]` list. `Runtime(graph)` reads the global
|
|
11
|
+
registry by default; passing `behaviors=[...]` overrides it. Tests
|
|
12
|
+
that need isolation can call `clear_registry()`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any, Callable, Optional, Union
|
|
18
|
+
|
|
19
|
+
from activegraph.behaviors.base import (
|
|
20
|
+
Behavior,
|
|
21
|
+
LLMBehavior,
|
|
22
|
+
RelationBehavior,
|
|
23
|
+
_llm_behavior_fn_placeholder,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_REGISTRY: list[Union[Behavior, RelationBehavior]] = []
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def clear_registry() -> None:
|
|
31
|
+
_REGISTRY.clear()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_registry() -> list[Union[Behavior, RelationBehavior]]:
|
|
35
|
+
return list(_REGISTRY)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def behavior(
|
|
39
|
+
name: Optional[str] = None,
|
|
40
|
+
on: Optional[list[str]] = None,
|
|
41
|
+
where: Optional[dict[str, Any]] = None,
|
|
42
|
+
view: Optional[dict[str, Any]] = None,
|
|
43
|
+
creates: Optional[list[str]] = None,
|
|
44
|
+
budget: Optional[dict[str, Any]] = None,
|
|
45
|
+
priority: int = 0,
|
|
46
|
+
*,
|
|
47
|
+
pattern: Optional[str] = None,
|
|
48
|
+
activate_after: Any = None,
|
|
49
|
+
) -> Callable[[Callable], Behavior]:
|
|
50
|
+
"""Decorate a function as an event-driven behavior.
|
|
51
|
+
|
|
52
|
+
v0.7 additions (both keyword-only):
|
|
53
|
+
- `pattern=`: a Cypher subset pattern string. When set, the
|
|
54
|
+
behavior fires only when the pattern matches the post-event
|
|
55
|
+
graph state. Combined with `on=` both conditions must hold
|
|
56
|
+
(CONTRACT v0.7 #11). Matches are exposed as `ctx.matches`.
|
|
57
|
+
- `activate_after=`: int event count or "N events". Delays
|
|
58
|
+
invocation by N events; re-checks `where=` at fire time
|
|
59
|
+
(CONTRACT v0.7 #13).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
from activegraph.runtime.patterns import parse as _parse_pattern
|
|
63
|
+
from activegraph.runtime.scheduler import parse_activate_after as _parse_aa
|
|
64
|
+
|
|
65
|
+
compiled_matcher = None
|
|
66
|
+
if pattern is not None:
|
|
67
|
+
compiled_matcher = _parse_pattern(pattern).compile()
|
|
68
|
+
delay_n: Optional[int] = None
|
|
69
|
+
if activate_after is not None:
|
|
70
|
+
delay_n = _parse_aa(activate_after)
|
|
71
|
+
|
|
72
|
+
def wrap(fn: Callable) -> Behavior:
|
|
73
|
+
b = Behavior(
|
|
74
|
+
name=name or fn.__name__,
|
|
75
|
+
fn=fn,
|
|
76
|
+
on=list(on or []),
|
|
77
|
+
where=dict(where) if where else None,
|
|
78
|
+
view_spec=dict(view) if view else None,
|
|
79
|
+
creates=list(creates or []),
|
|
80
|
+
budget=dict(budget) if budget else None,
|
|
81
|
+
priority=priority,
|
|
82
|
+
pattern=pattern,
|
|
83
|
+
pattern_matcher=compiled_matcher,
|
|
84
|
+
activate_after=delay_n,
|
|
85
|
+
)
|
|
86
|
+
_REGISTRY.append(b)
|
|
87
|
+
return b
|
|
88
|
+
|
|
89
|
+
return wrap
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def llm_behavior(
|
|
93
|
+
*,
|
|
94
|
+
name: Optional[str] = None,
|
|
95
|
+
on: Optional[list[str]] = None,
|
|
96
|
+
where: Optional[dict[str, Any]] = None,
|
|
97
|
+
description: str = "",
|
|
98
|
+
model: str = "claude-sonnet-4-5",
|
|
99
|
+
output_schema: Optional[type] = None,
|
|
100
|
+
view: Optional[dict[str, Any]] = None,
|
|
101
|
+
creates: Optional[list[str]] = None,
|
|
102
|
+
budget: Optional[dict[str, Any]] = None,
|
|
103
|
+
deterministic: bool = False,
|
|
104
|
+
max_tokens: int = 4096,
|
|
105
|
+
temperature: float = 0.7,
|
|
106
|
+
top_p: float = 1.0,
|
|
107
|
+
timeout_seconds: float = 60.0,
|
|
108
|
+
prompt_template: Optional[str] = None,
|
|
109
|
+
priority: int = 0,
|
|
110
|
+
pattern: Optional[str] = None,
|
|
111
|
+
activate_after: Any = None,
|
|
112
|
+
tools: Optional[list] = None,
|
|
113
|
+
max_tool_turns: int = 6,
|
|
114
|
+
) -> Callable[[Callable], LLMBehavior]:
|
|
115
|
+
"""Decorate a function as an LLM-driven behavior.
|
|
116
|
+
|
|
117
|
+
The decorated function's signature is
|
|
118
|
+
`(event, graph, ctx, llm_output) -> None`. The runtime assembles
|
|
119
|
+
the prompt, calls the provider, parses the structured output, and
|
|
120
|
+
only then invokes the handler with the parsed result. Failures
|
|
121
|
+
flow as `behavior.failed` events with a `reason` from
|
|
122
|
+
CONTRACT v0.6 #11 (`llm.parse_error`, `llm.schema_violation`,
|
|
123
|
+
`llm.network_error`, `llm.rate_limited`, `budget.cost_exhausted`).
|
|
124
|
+
|
|
125
|
+
Keyword-only on purpose — `@llm_behavior` carries enough
|
|
126
|
+
parameters that positional binding would be a footgun.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
from activegraph.runtime.patterns import parse as _parse_pattern
|
|
130
|
+
from activegraph.runtime.scheduler import parse_activate_after as _parse_aa
|
|
131
|
+
|
|
132
|
+
compiled_matcher = None
|
|
133
|
+
if pattern is not None:
|
|
134
|
+
compiled_matcher = _parse_pattern(pattern).compile()
|
|
135
|
+
delay_n: Optional[int] = None
|
|
136
|
+
if activate_after is not None:
|
|
137
|
+
delay_n = _parse_aa(activate_after)
|
|
138
|
+
|
|
139
|
+
def wrap(fn: Callable) -> LLMBehavior:
|
|
140
|
+
b = LLMBehavior(
|
|
141
|
+
name=name or fn.__name__,
|
|
142
|
+
fn=_llm_behavior_fn_placeholder,
|
|
143
|
+
on=list(on or []),
|
|
144
|
+
where=dict(where) if where else None,
|
|
145
|
+
view_spec=dict(view) if view else None,
|
|
146
|
+
creates=list(creates or []),
|
|
147
|
+
budget=dict(budget) if budget else None,
|
|
148
|
+
priority=priority,
|
|
149
|
+
handler=fn,
|
|
150
|
+
description=description,
|
|
151
|
+
model=model,
|
|
152
|
+
output_schema=output_schema,
|
|
153
|
+
deterministic=deterministic,
|
|
154
|
+
max_tokens=max_tokens,
|
|
155
|
+
temperature=temperature,
|
|
156
|
+
top_p=top_p,
|
|
157
|
+
timeout_seconds=timeout_seconds,
|
|
158
|
+
prompt_template=prompt_template,
|
|
159
|
+
pattern=pattern,
|
|
160
|
+
pattern_matcher=compiled_matcher,
|
|
161
|
+
activate_after=delay_n,
|
|
162
|
+
tools=list(tools) if tools else [],
|
|
163
|
+
max_tool_turns=max_tool_turns,
|
|
164
|
+
)
|
|
165
|
+
_REGISTRY.append(b)
|
|
166
|
+
return b
|
|
167
|
+
|
|
168
|
+
return wrap
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def relation_behavior(
|
|
172
|
+
relation_type: str,
|
|
173
|
+
on: Optional[list[str]] = None,
|
|
174
|
+
name: Optional[str] = None,
|
|
175
|
+
where: Optional[dict[str, Any]] = None,
|
|
176
|
+
view: Optional[dict[str, Any]] = None,
|
|
177
|
+
creates: Optional[list[str]] = None,
|
|
178
|
+
budget: Optional[dict[str, Any]] = None,
|
|
179
|
+
priority: int = 0,
|
|
180
|
+
*,
|
|
181
|
+
pattern: Optional[str] = None,
|
|
182
|
+
activate_after: Any = None,
|
|
183
|
+
) -> Callable[[Callable], RelationBehavior]:
|
|
184
|
+
"""Decorate a function as a relation behavior — fires once per matching edge.
|
|
185
|
+
|
|
186
|
+
v0.7: also accepts `pattern=` and `activate_after=` per CONTRACT
|
|
187
|
+
v0.7 #8 / #11 / #13.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
from activegraph.runtime.patterns import parse as _parse_pattern
|
|
191
|
+
from activegraph.runtime.scheduler import parse_activate_after as _parse_aa
|
|
192
|
+
|
|
193
|
+
compiled_matcher = None
|
|
194
|
+
if pattern is not None:
|
|
195
|
+
compiled_matcher = _parse_pattern(pattern).compile()
|
|
196
|
+
delay_n: Optional[int] = None
|
|
197
|
+
if activate_after is not None:
|
|
198
|
+
delay_n = _parse_aa(activate_after)
|
|
199
|
+
|
|
200
|
+
def wrap(fn: Callable) -> RelationBehavior:
|
|
201
|
+
rb = RelationBehavior(
|
|
202
|
+
name=name or fn.__name__,
|
|
203
|
+
fn=fn,
|
|
204
|
+
relation_type=relation_type,
|
|
205
|
+
on=list(on or []),
|
|
206
|
+
where=dict(where) if where else None,
|
|
207
|
+
view_spec=dict(view) if view else None,
|
|
208
|
+
creates=list(creates or []),
|
|
209
|
+
budget=dict(budget) if budget else None,
|
|
210
|
+
priority=priority,
|
|
211
|
+
pattern=pattern,
|
|
212
|
+
pattern_matcher=compiled_matcher,
|
|
213
|
+
activate_after=delay_n,
|
|
214
|
+
)
|
|
215
|
+
_REGISTRY.append(rb)
|
|
216
|
+
return rb
|
|
217
|
+
|
|
218
|
+
return wrap
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""activegraph CLI. CONTRACT v0.8 #12.
|
|
2
|
+
|
|
3
|
+
A thin wrapper around library APIs. No business logic in the CLI — every
|
|
4
|
+
subcommand calls into the library and formats the result. Programmatic
|
|
5
|
+
users do exactly the same things the CLI does.
|
|
6
|
+
|
|
7
|
+
Entry point is wired via ``[project.scripts]`` in pyproject:
|
|
8
|
+
|
|
9
|
+
activegraph = "activegraph.cli.main:main"
|
|
10
|
+
|
|
11
|
+
So ``activegraph inspect ...`` runs ``activegraph.cli.main:main`` with
|
|
12
|
+
the rest of argv.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from activegraph.cli.main import main, EXIT_CODES
|
|
16
|
+
|
|
17
|
+
__all__ = ["main", "EXIT_CODES"]
|