namel3ss 0.1.0a0__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 (157) hide show
  1. namel3ss/__init__.py +4 -0
  2. namel3ss/ast/__init__.py +5 -0
  3. namel3ss/ast/agents.py +13 -0
  4. namel3ss/ast/ai.py +23 -0
  5. namel3ss/ast/base.py +10 -0
  6. namel3ss/ast/expressions.py +55 -0
  7. namel3ss/ast/nodes.py +86 -0
  8. namel3ss/ast/pages.py +43 -0
  9. namel3ss/ast/program.py +22 -0
  10. namel3ss/ast/records.py +27 -0
  11. namel3ss/ast/statements.py +107 -0
  12. namel3ss/ast/tool.py +11 -0
  13. namel3ss/cli/__init__.py +2 -0
  14. namel3ss/cli/actions_mode.py +39 -0
  15. namel3ss/cli/app_loader.py +22 -0
  16. namel3ss/cli/commands/action.py +27 -0
  17. namel3ss/cli/commands/run.py +43 -0
  18. namel3ss/cli/commands/ui.py +26 -0
  19. namel3ss/cli/commands/validate.py +23 -0
  20. namel3ss/cli/format_mode.py +30 -0
  21. namel3ss/cli/io/json_io.py +19 -0
  22. namel3ss/cli/io/read_source.py +16 -0
  23. namel3ss/cli/json_io.py +21 -0
  24. namel3ss/cli/lint_mode.py +29 -0
  25. namel3ss/cli/main.py +135 -0
  26. namel3ss/cli/new_mode.py +146 -0
  27. namel3ss/cli/runner.py +28 -0
  28. namel3ss/cli/studio_mode.py +22 -0
  29. namel3ss/cli/ui_mode.py +14 -0
  30. namel3ss/config/__init__.py +4 -0
  31. namel3ss/config/dotenv.py +33 -0
  32. namel3ss/config/loader.py +83 -0
  33. namel3ss/config/model.py +49 -0
  34. namel3ss/errors/__init__.py +2 -0
  35. namel3ss/errors/base.py +34 -0
  36. namel3ss/errors/render.py +22 -0
  37. namel3ss/format/__init__.py +3 -0
  38. namel3ss/format/formatter.py +18 -0
  39. namel3ss/format/rules.py +97 -0
  40. namel3ss/ir/__init__.py +3 -0
  41. namel3ss/ir/lowering/__init__.py +4 -0
  42. namel3ss/ir/lowering/agents.py +42 -0
  43. namel3ss/ir/lowering/ai.py +45 -0
  44. namel3ss/ir/lowering/expressions.py +49 -0
  45. namel3ss/ir/lowering/flow.py +21 -0
  46. namel3ss/ir/lowering/pages.py +48 -0
  47. namel3ss/ir/lowering/program.py +34 -0
  48. namel3ss/ir/lowering/records.py +25 -0
  49. namel3ss/ir/lowering/statements.py +122 -0
  50. namel3ss/ir/lowering/tools.py +16 -0
  51. namel3ss/ir/model/__init__.py +50 -0
  52. namel3ss/ir/model/agents.py +33 -0
  53. namel3ss/ir/model/ai.py +31 -0
  54. namel3ss/ir/model/base.py +20 -0
  55. namel3ss/ir/model/expressions.py +50 -0
  56. namel3ss/ir/model/pages.py +43 -0
  57. namel3ss/ir/model/program.py +28 -0
  58. namel3ss/ir/model/statements.py +76 -0
  59. namel3ss/ir/model/tools.py +11 -0
  60. namel3ss/ir/nodes.py +88 -0
  61. namel3ss/lexer/__init__.py +2 -0
  62. namel3ss/lexer/lexer.py +152 -0
  63. namel3ss/lexer/tokens.py +98 -0
  64. namel3ss/lint/__init__.py +4 -0
  65. namel3ss/lint/engine.py +125 -0
  66. namel3ss/lint/semantic.py +45 -0
  67. namel3ss/lint/text_scan.py +70 -0
  68. namel3ss/lint/types.py +22 -0
  69. namel3ss/parser/__init__.py +3 -0
  70. namel3ss/parser/agent.py +78 -0
  71. namel3ss/parser/ai.py +113 -0
  72. namel3ss/parser/constraints.py +37 -0
  73. namel3ss/parser/core.py +166 -0
  74. namel3ss/parser/expressions.py +105 -0
  75. namel3ss/parser/flow.py +37 -0
  76. namel3ss/parser/pages.py +76 -0
  77. namel3ss/parser/program.py +45 -0
  78. namel3ss/parser/records.py +66 -0
  79. namel3ss/parser/statements/__init__.py +27 -0
  80. namel3ss/parser/statements/control_flow.py +116 -0
  81. namel3ss/parser/statements/core.py +66 -0
  82. namel3ss/parser/statements/data.py +17 -0
  83. namel3ss/parser/statements/letset.py +22 -0
  84. namel3ss/parser/statements.py +1 -0
  85. namel3ss/parser/tokens.py +35 -0
  86. namel3ss/parser/tool.py +29 -0
  87. namel3ss/runtime/__init__.py +3 -0
  88. namel3ss/runtime/ai/http/client.py +24 -0
  89. namel3ss/runtime/ai/mock_provider.py +5 -0
  90. namel3ss/runtime/ai/provider.py +29 -0
  91. namel3ss/runtime/ai/providers/__init__.py +18 -0
  92. namel3ss/runtime/ai/providers/_shared/errors.py +20 -0
  93. namel3ss/runtime/ai/providers/_shared/parse.py +18 -0
  94. namel3ss/runtime/ai/providers/anthropic.py +55 -0
  95. namel3ss/runtime/ai/providers/gemini.py +50 -0
  96. namel3ss/runtime/ai/providers/mistral.py +51 -0
  97. namel3ss/runtime/ai/providers/mock.py +23 -0
  98. namel3ss/runtime/ai/providers/ollama.py +39 -0
  99. namel3ss/runtime/ai/providers/openai.py +55 -0
  100. namel3ss/runtime/ai/providers/registry.py +38 -0
  101. namel3ss/runtime/ai/trace.py +18 -0
  102. namel3ss/runtime/executor/__init__.py +3 -0
  103. namel3ss/runtime/executor/agents.py +91 -0
  104. namel3ss/runtime/executor/ai_runner.py +90 -0
  105. namel3ss/runtime/executor/api.py +54 -0
  106. namel3ss/runtime/executor/assign.py +40 -0
  107. namel3ss/runtime/executor/context.py +31 -0
  108. namel3ss/runtime/executor/executor.py +77 -0
  109. namel3ss/runtime/executor/expr_eval.py +110 -0
  110. namel3ss/runtime/executor/records_ops.py +64 -0
  111. namel3ss/runtime/executor/result.py +13 -0
  112. namel3ss/runtime/executor/signals.py +6 -0
  113. namel3ss/runtime/executor/statements.py +99 -0
  114. namel3ss/runtime/memory/manager.py +52 -0
  115. namel3ss/runtime/memory/profile.py +17 -0
  116. namel3ss/runtime/memory/semantic.py +20 -0
  117. namel3ss/runtime/memory/short_term.py +18 -0
  118. namel3ss/runtime/records/service.py +105 -0
  119. namel3ss/runtime/store/__init__.py +2 -0
  120. namel3ss/runtime/store/memory_store.py +62 -0
  121. namel3ss/runtime/tools/registry.py +13 -0
  122. namel3ss/runtime/ui/__init__.py +2 -0
  123. namel3ss/runtime/ui/actions.py +124 -0
  124. namel3ss/runtime/validators/__init__.py +2 -0
  125. namel3ss/runtime/validators/constraints.py +126 -0
  126. namel3ss/schema/__init__.py +2 -0
  127. namel3ss/schema/records.py +52 -0
  128. namel3ss/studio/__init__.py +4 -0
  129. namel3ss/studio/api.py +115 -0
  130. namel3ss/studio/edit/__init__.py +3 -0
  131. namel3ss/studio/edit/ops.py +80 -0
  132. namel3ss/studio/edit/selectors.py +74 -0
  133. namel3ss/studio/edit/transform.py +39 -0
  134. namel3ss/studio/server.py +175 -0
  135. namel3ss/studio/session.py +11 -0
  136. namel3ss/studio/web/app.js +248 -0
  137. namel3ss/studio/web/index.html +44 -0
  138. namel3ss/studio/web/styles.css +42 -0
  139. namel3ss/templates/__init__.py +3 -0
  140. namel3ss/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  141. namel3ss/templates/ai_assistant/.gitignore +1 -0
  142. namel3ss/templates/ai_assistant/README.md +10 -0
  143. namel3ss/templates/ai_assistant/app.ai +30 -0
  144. namel3ss/templates/crud/.gitignore +1 -0
  145. namel3ss/templates/crud/README.md +10 -0
  146. namel3ss/templates/crud/app.ai +26 -0
  147. namel3ss/templates/multi_agent/.gitignore +1 -0
  148. namel3ss/templates/multi_agent/README.md +10 -0
  149. namel3ss/templates/multi_agent/app.ai +43 -0
  150. namel3ss/ui/__init__.py +2 -0
  151. namel3ss/ui/manifest.py +220 -0
  152. namel3ss/utils/__init__.py +2 -0
  153. namel3ss-0.1.0a0.dist-info/METADATA +123 -0
  154. namel3ss-0.1.0a0.dist-info/RECORD +157 -0
  155. namel3ss-0.1.0a0.dist-info/WHEEL +5 -0
  156. namel3ss-0.1.0a0.dist-info/entry_points.txt +2 -0
  157. namel3ss-0.1.0a0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, Optional
4
+
5
+ from namel3ss.config.loader import load_config
6
+ from namel3ss.config.model import AppConfig
7
+ from namel3ss.ir import nodes as ir
8
+ from namel3ss.runtime.ai.mock_provider import MockProvider
9
+ from namel3ss.runtime.ai.provider import AIProvider
10
+ from namel3ss.runtime.executor.context import ExecutionContext
11
+ from namel3ss.runtime.executor.result import ExecutionResult
12
+ from namel3ss.runtime.executor.signals import _ReturnSignal
13
+ from namel3ss.runtime.executor.statements import execute_statement
14
+ from namel3ss.runtime.memory.manager import MemoryManager
15
+ from namel3ss.runtime.store.memory_store import MemoryStore
16
+ from namel3ss.schema.records import RecordSchema
17
+
18
+
19
+ class Executor:
20
+ def __init__(
21
+ self,
22
+ flow: ir.Flow,
23
+ schemas: Optional[Dict[str, RecordSchema]] = None,
24
+ initial_state: Optional[Dict[str, object]] = None,
25
+ store: Optional[MemoryStore] = None,
26
+ input_data: Optional[Dict[str, object]] = None,
27
+ ai_provider: Optional[AIProvider] = None,
28
+ ai_profiles: Optional[Dict[str, ir.AIDecl]] = None,
29
+ memory_manager: Optional[MemoryManager] = None,
30
+ agents: Optional[Dict[str, ir.AgentDecl]] = None,
31
+ config: Optional[AppConfig] = None,
32
+ ) -> None:
33
+ resolved_config = config or load_config()
34
+ default_ai_provider = ai_provider or MockProvider()
35
+ provider_cache = {"mock": default_ai_provider}
36
+ self.ctx = ExecutionContext(
37
+ flow=flow,
38
+ schemas=schemas or {},
39
+ state=initial_state or {},
40
+ locals={"input": input_data or {}},
41
+ constants=set(),
42
+ last_value=None,
43
+ store=store or MemoryStore(),
44
+ ai_provider=default_ai_provider,
45
+ ai_profiles=ai_profiles or {},
46
+ agents=agents or {},
47
+ traces=[],
48
+ memory_manager=memory_manager or MemoryManager(),
49
+ agent_calls=0,
50
+ config=resolved_config,
51
+ provider_cache=provider_cache,
52
+ )
53
+ self.flow = self.ctx.flow
54
+ self.schemas = self.ctx.schemas
55
+ self.state = self.ctx.state
56
+ self.locals = self.ctx.locals
57
+ self.constants = self.ctx.constants
58
+ self.last_value = self.ctx.last_value
59
+ self.store = self.ctx.store
60
+ self.ai_provider = self.ctx.ai_provider
61
+ self.ai_profiles = self.ctx.ai_profiles
62
+ self.agents = self.ctx.agents
63
+ self.traces = self.ctx.traces
64
+ self.memory_manager = self.ctx.memory_manager
65
+ self.agent_calls = self.ctx.agent_calls
66
+ self.config = self.ctx.config
67
+ self.provider_cache = self.ctx.provider_cache
68
+
69
+ def run(self) -> ExecutionResult:
70
+ try:
71
+ for stmt in self.ctx.flow.body:
72
+ execute_statement(self.ctx, stmt)
73
+ except _ReturnSignal as signal:
74
+ self.ctx.last_value = signal.value
75
+ self.last_value = self.ctx.last_value
76
+ self.agent_calls = self.ctx.agent_calls
77
+ return ExecutionResult(state=self.ctx.state, last_value=self.ctx.last_value, traces=self.ctx.traces)
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.errors.base import Namel3ssError
4
+ from namel3ss.ir import nodes as ir
5
+ from namel3ss.runtime.executor.context import ExecutionContext
6
+
7
+
8
+ def evaluate_expression(ctx: ExecutionContext, expr: ir.Expression) -> object:
9
+ if isinstance(expr, ir.Literal):
10
+ return expr.value
11
+ if isinstance(expr, ir.VarReference):
12
+ if expr.name not in ctx.locals:
13
+ raise Namel3ssError(
14
+ f"Unknown variable '{expr.name}'",
15
+ line=expr.line,
16
+ column=expr.column,
17
+ )
18
+ return ctx.locals[expr.name]
19
+ if isinstance(expr, ir.AttrAccess):
20
+ if expr.base not in ctx.locals:
21
+ raise Namel3ssError(
22
+ f"Unknown variable '{expr.base}'",
23
+ line=expr.line,
24
+ column=expr.column,
25
+ )
26
+ value = ctx.locals[expr.base]
27
+ for attr in expr.attrs:
28
+ if isinstance(value, dict):
29
+ if attr not in value:
30
+ raise Namel3ssError(
31
+ f"Missing attribute '{attr}'",
32
+ line=expr.line,
33
+ column=expr.column,
34
+ )
35
+ value = value[attr]
36
+ continue
37
+ if not hasattr(value, attr):
38
+ raise Namel3ssError(
39
+ f"Missing attribute '{attr}'",
40
+ line=expr.line,
41
+ column=expr.column,
42
+ )
43
+ value = getattr(value, attr)
44
+ return value
45
+ if isinstance(expr, ir.StatePath):
46
+ return resolve_state_path(ctx, expr)
47
+ if isinstance(expr, ir.UnaryOp):
48
+ operand = evaluate_expression(ctx, expr.operand)
49
+ if expr.op == "not":
50
+ if not isinstance(operand, bool):
51
+ raise Namel3ssError("Operand to 'not' must be boolean", line=expr.line, column=expr.column)
52
+ return not operand
53
+ raise Namel3ssError(f"Unsupported unary op '{expr.op}'", line=expr.line, column=expr.column)
54
+ if isinstance(expr, ir.BinaryOp):
55
+ if expr.op == "and":
56
+ left = evaluate_expression(ctx, expr.left)
57
+ if not isinstance(left, bool):
58
+ raise Namel3ssError("Left operand of 'and' must be boolean", line=expr.line, column=expr.column)
59
+ if not left:
60
+ return False
61
+ right = evaluate_expression(ctx, expr.right)
62
+ if not isinstance(right, bool):
63
+ raise Namel3ssError("Right operand of 'and' must be boolean", line=expr.line, column=expr.column)
64
+ return left and right
65
+ if expr.op == "or":
66
+ left = evaluate_expression(ctx, expr.left)
67
+ if not isinstance(left, bool):
68
+ raise Namel3ssError("Left operand of 'or' must be boolean", line=expr.line, column=expr.column)
69
+ if left:
70
+ return True
71
+ right = evaluate_expression(ctx, expr.right)
72
+ if not isinstance(right, bool):
73
+ raise Namel3ssError("Right operand of 'or' must be boolean", line=expr.line, column=expr.column)
74
+ return bool(right)
75
+ raise Namel3ssError(f"Unsupported binary op '{expr.op}'", line=expr.line, column=expr.column)
76
+ if isinstance(expr, ir.Comparison):
77
+ left = evaluate_expression(ctx, expr.left)
78
+ right = evaluate_expression(ctx, expr.right)
79
+ if expr.kind in {"gt", "lt"}:
80
+ if not isinstance(left, (int, float)) or not isinstance(right, (int, float)):
81
+ raise Namel3ssError(
82
+ "Greater/less comparisons require numbers",
83
+ line=expr.line,
84
+ column=expr.column,
85
+ )
86
+ return left > right if expr.kind == "gt" else left < right
87
+ if expr.kind == "eq":
88
+ return left == right
89
+ raise Namel3ssError(f"Unsupported comparison '{expr.kind}'", line=expr.line, column=expr.column)
90
+
91
+ raise Namel3ssError(f"Unsupported expression type: {type(expr)}", line=expr.line, column=expr.column)
92
+
93
+
94
+ def resolve_state_path(ctx: ExecutionContext, expr: ir.StatePath) -> object:
95
+ cursor: object = ctx.state
96
+ for segment in expr.path:
97
+ if not isinstance(cursor, dict):
98
+ raise Namel3ssError(
99
+ f"State path '{'.'.join(expr.path)}' is not a mapping",
100
+ line=expr.line,
101
+ column=expr.column,
102
+ )
103
+ if segment not in cursor:
104
+ raise Namel3ssError(
105
+ f"Unknown state path '{'.'.join(expr.path)}'",
106
+ line=expr.line,
107
+ column=expr.column,
108
+ )
109
+ cursor = cursor[segment]
110
+ return cursor
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.errors.base import Namel3ssError
4
+ from namel3ss.ir import nodes as ir
5
+ from namel3ss.runtime.executor.context import ExecutionContext
6
+ from namel3ss.runtime.executor.expr_eval import evaluate_expression
7
+ from namel3ss.runtime.records.service import save_record_or_raise
8
+ from namel3ss.schema.records import RecordSchema
9
+
10
+
11
+ def handle_save(ctx: ExecutionContext, stmt: ir.Save) -> None:
12
+ state_key = stmt.record_name.lower()
13
+ data_obj = ctx.state.get(state_key)
14
+ if not isinstance(data_obj, dict):
15
+ raise Namel3ssError(
16
+ f"Expected state.{state_key} to be a record dictionary",
17
+ line=stmt.line,
18
+ column=stmt.column,
19
+ )
20
+ validated = dict(data_obj)
21
+ saved = save_record_or_raise(
22
+ stmt.record_name,
23
+ validated,
24
+ ctx.schemas,
25
+ ctx.state,
26
+ ctx.store,
27
+ line=stmt.line,
28
+ column=stmt.column,
29
+ )
30
+ ctx.last_value = saved
31
+
32
+
33
+ def handle_find(ctx: ExecutionContext, stmt: ir.Find) -> None:
34
+ schema = get_schema(ctx, stmt.record_name, stmt)
35
+
36
+ def predicate(record: dict) -> bool:
37
+ backup_locals = ctx.locals.copy()
38
+ try:
39
+ ctx.locals.update(record)
40
+ result = evaluate_expression(ctx, stmt.predicate)
41
+ if not isinstance(result, bool):
42
+ raise Namel3ssError(
43
+ "Find predicate must evaluate to boolean",
44
+ line=stmt.line,
45
+ column=stmt.column,
46
+ )
47
+ return result
48
+ finally:
49
+ ctx.locals = backup_locals
50
+
51
+ results = ctx.store.find(schema, predicate)
52
+ result_name = f"{stmt.record_name.lower()}_results"
53
+ ctx.locals[result_name] = results
54
+ ctx.last_value = results
55
+
56
+
57
+ def get_schema(ctx: ExecutionContext, record_name: str, stmt: ir.Statement) -> RecordSchema:
58
+ if record_name not in ctx.schemas:
59
+ raise Namel3ssError(
60
+ f"Unknown record '{record_name}'",
61
+ line=stmt.line,
62
+ column=stmt.column,
63
+ )
64
+ return ctx.schemas[record_name]
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Dict, Optional
5
+
6
+ from namel3ss.runtime.ai.trace import AITrace
7
+
8
+
9
+ @dataclass
10
+ class ExecutionResult:
11
+ state: Dict[str, object]
12
+ last_value: Optional[object]
13
+ traces: list[AITrace]
@@ -0,0 +1,6 @@
1
+ class _ReturnSignal(Exception):
2
+ """Internal control flow for return."""
3
+
4
+ def __init__(self, value: object) -> None:
5
+ super().__init__("return")
6
+ self.value = value
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.errors.base import Namel3ssError
4
+ from namel3ss.ir import nodes as ir
5
+ from namel3ss.runtime.executor.ai_runner import execute_ask_ai
6
+ from namel3ss.runtime.executor.agents import execute_run_agent, execute_run_agents_parallel
7
+ from namel3ss.runtime.executor.assign import assign
8
+ from namel3ss.runtime.executor.context import ExecutionContext
9
+ from namel3ss.runtime.executor.expr_eval import evaluate_expression
10
+ from namel3ss.runtime.executor.records_ops import handle_find, handle_save
11
+ from namel3ss.runtime.executor.signals import _ReturnSignal
12
+
13
+
14
+ def execute_statement(ctx: ExecutionContext, stmt: ir.Statement) -> None:
15
+ if isinstance(stmt, ir.Let):
16
+ value = evaluate_expression(ctx, stmt.expression)
17
+ ctx.locals[stmt.name] = value
18
+ if stmt.constant:
19
+ ctx.constants.add(stmt.name)
20
+ ctx.last_value = value
21
+ return
22
+ if isinstance(stmt, ir.Set):
23
+ value = evaluate_expression(ctx, stmt.expression)
24
+ assign(ctx, stmt.target, value, stmt)
25
+ ctx.last_value = value
26
+ return
27
+ if isinstance(stmt, ir.If):
28
+ condition_value = evaluate_expression(ctx, stmt.condition)
29
+ if not isinstance(condition_value, bool):
30
+ raise Namel3ssError(
31
+ "Condition must evaluate to a boolean",
32
+ line=stmt.line,
33
+ column=stmt.column,
34
+ )
35
+ branch = stmt.then_body if condition_value else stmt.else_body
36
+ for child in branch:
37
+ execute_statement(ctx, child)
38
+ return
39
+ if isinstance(stmt, ir.Return):
40
+ value = evaluate_expression(ctx, stmt.expression)
41
+ raise _ReturnSignal(value)
42
+ if isinstance(stmt, ir.Repeat):
43
+ count_value = evaluate_expression(ctx, stmt.count)
44
+ if not isinstance(count_value, int):
45
+ raise Namel3ssError("Repeat count must be an integer", line=stmt.line, column=stmt.column)
46
+ if count_value < 0:
47
+ raise Namel3ssError("Repeat count cannot be negative", line=stmt.line, column=stmt.column)
48
+ for _ in range(count_value):
49
+ for child in stmt.body:
50
+ execute_statement(ctx, child)
51
+ return
52
+ if isinstance(stmt, ir.ForEach):
53
+ iterable_value = evaluate_expression(ctx, stmt.iterable)
54
+ if not isinstance(iterable_value, list):
55
+ raise Namel3ssError("For-each expects a list", line=stmt.line, column=stmt.column)
56
+ for item in iterable_value:
57
+ ctx.locals[stmt.name] = item
58
+ for child in stmt.body:
59
+ execute_statement(ctx, child)
60
+ return
61
+ if isinstance(stmt, ir.Match):
62
+ subject = evaluate_expression(ctx, stmt.expression)
63
+ matched = False
64
+ for case in stmt.cases:
65
+ pattern_value = evaluate_expression(ctx, case.pattern)
66
+ if subject == pattern_value:
67
+ matched = True
68
+ for child in case.body:
69
+ execute_statement(ctx, child)
70
+ break
71
+ if not matched and stmt.otherwise is not None:
72
+ for child in stmt.otherwise:
73
+ execute_statement(ctx, child)
74
+ return
75
+ if isinstance(stmt, ir.TryCatch):
76
+ try:
77
+ for child in stmt.try_body:
78
+ execute_statement(ctx, child)
79
+ except Namel3ssError as err:
80
+ ctx.locals[stmt.catch_var] = err
81
+ for child in stmt.catch_body:
82
+ execute_statement(ctx, child)
83
+ return
84
+ if isinstance(stmt, ir.AskAIStmt):
85
+ execute_ask_ai(ctx, stmt)
86
+ return
87
+ if isinstance(stmt, ir.RunAgentStmt):
88
+ execute_run_agent(ctx, stmt)
89
+ return
90
+ if isinstance(stmt, ir.RunAgentsParallelStmt):
91
+ execute_run_agents_parallel(ctx, stmt)
92
+ return
93
+ if isinstance(stmt, ir.Save):
94
+ handle_save(ctx, stmt)
95
+ return
96
+ if isinstance(stmt, ir.Find):
97
+ handle_find(ctx, stmt)
98
+ return
99
+ raise Namel3ssError(f"Unsupported statement type: {type(stmt)}", line=stmt.line, column=stmt.column)
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List, Optional
4
+
5
+ from namel3ss.ir import nodes as ir
6
+ from namel3ss.runtime.memory.profile import ProfileMemory
7
+ from namel3ss.runtime.memory.semantic import SemanticMemory
8
+ from namel3ss.runtime.memory.short_term import ShortTermMemory
9
+
10
+
11
+ class MemoryManager:
12
+ def __init__(self) -> None:
13
+ self.short_term = ShortTermMemory()
14
+ self.profile = ProfileMemory()
15
+ self.semantic = SemanticMemory()
16
+
17
+ def _session(self, state: Dict[str, object]) -> str:
18
+ if isinstance(state.get("user"), dict) and "id" in state["user"]:
19
+ return str(state["user"]["id"])
20
+ return "anonymous"
21
+
22
+ def recall_context(self, ai: ir.AIDecl, user_input: str, state: Dict[str, object]) -> dict:
23
+ session = self._session(state)
24
+ memory = ai.memory
25
+ context = {"short_term": [], "semantic": [], "profile": []}
26
+ if memory.short_term > 0:
27
+ context["short_term"] = self.short_term.recall(session, memory.short_term)
28
+ if memory.semantic:
29
+ context["semantic"] = self.semantic.recall(session, user_input, top_k=3)
30
+ if memory.profile:
31
+ context["profile"] = self.profile.recall(session)
32
+ return context
33
+
34
+ def record_interaction(
35
+ self,
36
+ ai: ir.AIDecl,
37
+ state: Dict[str, object],
38
+ user_input: str,
39
+ ai_output: str,
40
+ tool_events: List[dict],
41
+ ) -> None:
42
+ session = self._session(state)
43
+ message = {"role": "user", "content": user_input}
44
+ self.short_term.record(session, message)
45
+ ai_message = {"role": "ai", "content": ai_output}
46
+ self.short_term.record(session, ai_message)
47
+ if ai.memory.semantic:
48
+ snippet = f"user:{user_input} ai:{ai_output}"
49
+ if tool_events:
50
+ snippet += f" tools:{tool_events}"
51
+ self.semantic.record(session, snippet)
52
+
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+
5
+
6
+ class ProfileMemory:
7
+ def __init__(self) -> None:
8
+ self._facts: Dict[str, Dict[str, str]] = {}
9
+
10
+ def set_fact(self, session: str, key: str, value: str) -> None:
11
+ facts = self._facts.setdefault(session, {})
12
+ facts[key] = value
13
+
14
+ def recall(self, session: str, limit: int = 20) -> List[dict]:
15
+ facts = self._facts.get(session, {})
16
+ items = list(facts.items())[:limit]
17
+ return [{"key": k, "value": v} for k, v in items]
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+
5
+
6
+ class SemanticMemory:
7
+ def __init__(self) -> None:
8
+ self._snippets: Dict[str, List[str]] = {}
9
+
10
+ def record(self, session: str, snippet: str) -> None:
11
+ self._snippets.setdefault(session, []).append(snippet)
12
+
13
+ def recall(self, session: str, query: str, top_k: int = 3) -> List[dict]:
14
+ snippets = self._snippets.get(session, [])
15
+ matches = []
16
+ for text in snippets:
17
+ score = 1 if query and query.lower() in text.lower() else 0
18
+ matches.append({"text": text, "score": score})
19
+ matches.sort(key=lambda x: x["score"], reverse=True)
20
+ return matches[:top_k]
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+
5
+
6
+ class ShortTermMemory:
7
+ def __init__(self) -> None:
8
+ self._messages: Dict[str, List[dict]] = {}
9
+
10
+ def record(self, session: str, message: dict) -> None:
11
+ messages = self._messages.setdefault(session, [])
12
+ messages.append(message)
13
+
14
+ def recall(self, session: str, limit: int) -> List[dict]:
15
+ messages = self._messages.get(session, [])
16
+ if limit <= 0:
17
+ return []
18
+ return messages[-limit:]
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List, Optional, Tuple
4
+
5
+ from namel3ss.errors.base import Namel3ssError
6
+ from namel3ss.ir import nodes as ir
7
+ from namel3ss.runtime.store.memory_store import MemoryStore
8
+ from namel3ss.runtime.validators.constraints import collect_validation_errors, validate_record_instance
9
+ from namel3ss.schema.records import RecordSchema
10
+
11
+
12
+ def save_record_or_raise(
13
+ record_name: str,
14
+ values: Dict[str, object],
15
+ schemas: Dict[str, RecordSchema],
16
+ state: Dict[str, object],
17
+ store: MemoryStore,
18
+ line: int | None = None,
19
+ column: int | None = None,
20
+ ) -> dict:
21
+ saved, errors = save_record_with_errors(record_name, values, schemas, state, store)
22
+ if errors:
23
+ first = errors[0]
24
+ raise Namel3ssError(first["message"], line=line, column=column)
25
+ return saved
26
+
27
+
28
+ def save_record_with_errors(
29
+ record_name: str,
30
+ values: Dict[str, object],
31
+ schemas: Dict[str, RecordSchema],
32
+ state: Dict[str, object],
33
+ store: MemoryStore,
34
+ ) -> Tuple[Optional[dict], List[Dict[str, str]]]:
35
+ schema = _get_schema(record_name, schemas)
36
+ type_errors = _type_errors(schema, values)
37
+ if type_errors:
38
+ return None, type_errors
39
+
40
+ constraint_errors = collect_validation_errors(schema, values, _literal_eval)
41
+ if constraint_errors:
42
+ return None, constraint_errors
43
+
44
+ conflict_field = store.check_unique(schema, values)
45
+ if conflict_field:
46
+ return None, [
47
+ {
48
+ "field": conflict_field,
49
+ "code": "unique",
50
+ "message": f"Field '{conflict_field}' in record '{record_name}' must be unique",
51
+ }
52
+ ]
53
+ try:
54
+ saved = store.save(schema, values)
55
+ return saved, []
56
+ except Namel3ssError as exc:
57
+ # Fallback for any residual unique enforcement
58
+ return None, [
59
+ {
60
+ "field": conflict_field or "",
61
+ "code": "unique",
62
+ "message": str(exc),
63
+ }
64
+ ]
65
+
66
+
67
+ def _type_errors(schema: RecordSchema, data: Dict[str, object]) -> List[Dict[str, str]]:
68
+ errors: List[Dict[str, str]] = []
69
+ for field in schema.fields:
70
+ value = data.get(field.name)
71
+ if value is None:
72
+ continue
73
+ expected = field.type_name
74
+ if expected == "string" and not isinstance(value, str):
75
+ errors.append(_type_error(field.name, schema.name, "string"))
76
+ elif expected == "int" and not isinstance(value, int):
77
+ errors.append(_type_error(field.name, schema.name, "int"))
78
+ elif expected == "number" and not isinstance(value, (int, float)):
79
+ errors.append(_type_error(field.name, schema.name, "number"))
80
+ elif expected == "boolean" and not isinstance(value, bool):
81
+ errors.append(_type_error(field.name, schema.name, "boolean"))
82
+ return errors
83
+
84
+
85
+ def _type_error(field: str, record: str, expected: str) -> Dict[str, str]:
86
+ return {
87
+ "field": field,
88
+ "code": "type",
89
+ "message": f"Field '{field}' in record '{record}' must be a {expected}",
90
+ }
91
+
92
+
93
+ def _get_schema(name: str, schemas: Dict[str, RecordSchema]) -> RecordSchema:
94
+ if name not in schemas:
95
+ raise Namel3ssError(f"Unknown record '{name}'")
96
+ return schemas[name]
97
+
98
+
99
+ def _literal_eval(expr: ir.Expression | None) -> object:
100
+ if expr is None:
101
+ return None
102
+ if isinstance(expr, ir.Literal):
103
+ return expr.value
104
+ raise Namel3ssError("Only literal expressions supported in schema constraints for forms")
105
+
@@ -0,0 +1,2 @@
1
+ """Runtime store implementations."""
2
+
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List, Callable
4
+
5
+ from namel3ss.errors.base import Namel3ssError
6
+ from namel3ss.schema.records import RecordSchema
7
+
8
+
9
+ class MemoryStore:
10
+ def __init__(self) -> None:
11
+ self._data: Dict[str, List[dict]] = {}
12
+ self._unique_indexes: Dict[str, Dict[str, Dict[object, dict]]] = {}
13
+ self._counters: Dict[str, int] = {}
14
+
15
+ def save(self, schema: RecordSchema, record: dict) -> dict:
16
+ rec_name = schema.name
17
+ if rec_name not in self._data:
18
+ self._data[rec_name] = []
19
+ self._unique_indexes[rec_name] = {}
20
+ self._counters[rec_name] = 1
21
+
22
+ # Handle auto id
23
+ if "id" in schema.field_map:
24
+ record.setdefault("id", self._counters[rec_name])
25
+ else:
26
+ record.setdefault("_id", self._counters[rec_name])
27
+ self._counters[rec_name] += 1
28
+
29
+ conflict_field = self.check_unique(schema, record)
30
+ if conflict_field:
31
+ raise Namel3ssError(f"Record '{rec_name}' violates unique constraint on '{conflict_field}'")
32
+ for field in schema.unique_fields:
33
+ value = record.get(field)
34
+ if value is None:
35
+ continue
36
+ idx = self._unique_indexes[rec_name].setdefault(field, {})
37
+ idx[value] = record
38
+
39
+ self._data[rec_name].append(record)
40
+ return record
41
+
42
+ def find(self, schema: RecordSchema, predicate: Callable[[dict], bool]) -> List[dict]:
43
+ records = self._data.get(schema.name, [])
44
+ return [rec for rec in records if predicate(rec)]
45
+
46
+ def check_unique(self, schema: RecordSchema, record: dict) -> str | None:
47
+ rec_name = schema.name
48
+ indexes = self._unique_indexes.setdefault(rec_name, {})
49
+ for field in schema.unique_fields:
50
+ value = record.get(field)
51
+ if value is None:
52
+ continue
53
+ idx = indexes.setdefault(field, {})
54
+ if value in idx:
55
+ return field
56
+ return None
57
+
58
+ def list_records(self, schema: RecordSchema, limit: int = 20) -> List[dict]:
59
+ records = list(self._data.get(schema.name, []))
60
+ key_order = "id" if "id" in schema.field_map else "_id"
61
+ records.sort(key=lambda rec: rec.get(key_order, 0))
62
+ return records[:limit]
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict
4
+
5
+ from namel3ss.errors.base import Namel3ssError
6
+
7
+
8
+ def execute_tool(name: str, args: Dict[str, object]) -> Dict[str, object]:
9
+ if name == "echo":
10
+ if not isinstance(args, dict):
11
+ raise Namel3ssError("Tool args must be a dictionary")
12
+ return {"echo": args}
13
+ raise Namel3ssError(f"Unknown tool '{name}'")
@@ -0,0 +1,2 @@
1
+ """UI runtime utilities."""
2
+