dtaifm 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.
Files changed (72) hide show
  1. dtaifm-0.1.0/LICENSE +21 -0
  2. dtaifm-0.1.0/PKG-INFO +597 -0
  3. dtaifm-0.1.0/README.md +560 -0
  4. dtaifm-0.1.0/dtaifm/__init__.py +23 -0
  5. dtaifm-0.1.0/dtaifm/__main__.py +9 -0
  6. dtaifm-0.1.0/dtaifm/_demo/__init__.py +0 -0
  7. dtaifm-0.1.0/dtaifm/_demo/network_automation/__init__.py +0 -0
  8. dtaifm-0.1.0/dtaifm/_demo/network_automation/constraints.yaml +48 -0
  9. dtaifm-0.1.0/dtaifm/_demo/network_automation/state.json +10 -0
  10. dtaifm-0.1.0/dtaifm/_demo/smart_home/__init__.py +0 -0
  11. dtaifm-0.1.0/dtaifm/_demo/smart_home/constraints.yaml +45 -0
  12. dtaifm-0.1.0/dtaifm/_demo/smart_home/state.json +13 -0
  13. dtaifm-0.1.0/dtaifm/audit.py +229 -0
  14. dtaifm-0.1.0/dtaifm/bundle.py +481 -0
  15. dtaifm-0.1.0/dtaifm/cli.py +846 -0
  16. dtaifm-0.1.0/dtaifm/core/__init__.py +19 -0
  17. dtaifm-0.1.0/dtaifm/core/constraint.py +38 -0
  18. dtaifm-0.1.0/dtaifm/core/result.py +55 -0
  19. dtaifm-0.1.0/dtaifm/core/rule.py +69 -0
  20. dtaifm-0.1.0/dtaifm/core/ruleset.py +17 -0
  21. dtaifm-0.1.0/dtaifm/domains/__init__.py +23 -0
  22. dtaifm-0.1.0/dtaifm/domains/base.py +33 -0
  23. dtaifm-0.1.0/dtaifm/domains/network_automation/__init__.py +46 -0
  24. dtaifm-0.1.0/dtaifm/domains/network_automation/evaluators.py +73 -0
  25. dtaifm-0.1.0/dtaifm/domains/registry.py +35 -0
  26. dtaifm-0.1.0/dtaifm/domains/smart_home/__init__.py +38 -0
  27. dtaifm-0.1.0/dtaifm/io.py +84 -0
  28. dtaifm-0.1.0/dtaifm/runtimes/__init__.py +3 -0
  29. dtaifm-0.1.0/dtaifm/runtimes/python_runtime.py +123 -0
  30. dtaifm-0.1.0/dtaifm/schema.py +168 -0
  31. dtaifm-0.1.0/dtaifm/serialize.py +41 -0
  32. dtaifm-0.1.0/dtaifm/student/__init__.py +3 -0
  33. dtaifm-0.1.0/dtaifm/student/validator.py +203 -0
  34. dtaifm-0.1.0/dtaifm/teacher/__init__.py +49 -0
  35. dtaifm-0.1.0/dtaifm/teacher/adapters/__init__.py +0 -0
  36. dtaifm-0.1.0/dtaifm/teacher/adapters/_http.py +59 -0
  37. dtaifm-0.1.0/dtaifm/teacher/adapters/anthropic_adapter.py +160 -0
  38. dtaifm-0.1.0/dtaifm/teacher/adapters/lemonade_adapter.py +92 -0
  39. dtaifm-0.1.0/dtaifm/teacher/adapters/ollama_adapter.py +89 -0
  40. dtaifm-0.1.0/dtaifm/teacher/base.py +23 -0
  41. dtaifm-0.1.0/dtaifm/teacher/contract.py +62 -0
  42. dtaifm-0.1.0/dtaifm/teacher/diagnostics.py +124 -0
  43. dtaifm-0.1.0/dtaifm/teacher/feedback.py +119 -0
  44. dtaifm-0.1.0/dtaifm/teacher/mock_teacher.py +183 -0
  45. dtaifm-0.1.0/dtaifm/teacher/parser.py +146 -0
  46. dtaifm-0.1.0/dtaifm/teacher/prompt.py +200 -0
  47. dtaifm-0.1.0/dtaifm/teacher/registry.py +77 -0
  48. dtaifm-0.1.0/dtaifm.egg-info/PKG-INFO +597 -0
  49. dtaifm-0.1.0/dtaifm.egg-info/SOURCES.txt +70 -0
  50. dtaifm-0.1.0/dtaifm.egg-info/dependency_links.txt +1 -0
  51. dtaifm-0.1.0/dtaifm.egg-info/entry_points.txt +2 -0
  52. dtaifm-0.1.0/dtaifm.egg-info/requires.txt +10 -0
  53. dtaifm-0.1.0/dtaifm.egg-info/top_level.txt +1 -0
  54. dtaifm-0.1.0/pyproject.toml +90 -0
  55. dtaifm-0.1.0/setup.cfg +4 -0
  56. dtaifm-0.1.0/tests/test_anthropic_adapter.py +163 -0
  57. dtaifm-0.1.0/tests/test_bundle.py +412 -0
  58. dtaifm-0.1.0/tests/test_cli.py +352 -0
  59. dtaifm-0.1.0/tests/test_custom_domain_template.py +116 -0
  60. dtaifm-0.1.0/tests/test_demo.py +260 -0
  61. dtaifm-0.1.0/tests/test_diagnostics.py +198 -0
  62. dtaifm-0.1.0/tests/test_domains.py +236 -0
  63. dtaifm-0.1.0/tests/test_feedback_repropose.py +534 -0
  64. dtaifm-0.1.0/tests/test_io.py +120 -0
  65. dtaifm-0.1.0/tests/test_local_adapters.py +392 -0
  66. dtaifm-0.1.0/tests/test_network_automation.py +257 -0
  67. dtaifm-0.1.0/tests/test_parser.py +195 -0
  68. dtaifm-0.1.0/tests/test_propose_review.py +217 -0
  69. dtaifm-0.1.0/tests/test_runtime.py +137 -0
  70. dtaifm-0.1.0/tests/test_schema.py +133 -0
  71. dtaifm-0.1.0/tests/test_teacher_contract.py +117 -0
  72. dtaifm-0.1.0/tests/test_validator.py +192 -0
dtaifm-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dtaifm contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
dtaifm-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,597 @@
1
+ Metadata-Version: 2.4
2
+ Name: dtaifm
3
+ Version: 0.1.0
4
+ Summary: Deterministic-first Teaching AI Framework Middleware — AI proposes, the deterministic layer disposes.
5
+ Author: dtaifm contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/dtaifm/dtaifm
8
+ Project-URL: Repository, https://github.com/dtaifm/dtaifm
9
+ Project-URL: Issues, https://github.com/dtaifm/dtaifm/issues
10
+ Project-URL: Changelog, https://github.com/dtaifm/dtaifm/blob/main/CHANGELOG.md
11
+ Project-URL: Documentation, https://github.com/dtaifm/dtaifm/tree/main/docs
12
+ Keywords: ai,middleware,deterministic,validation,constraints,llm,ollama,anthropic,audit
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Requires-Python: >=3.11
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: pyyaml>=6.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8.0; extra == "dev"
31
+ Requires-Dist: jsonschema>=4.0; extra == "dev"
32
+ Requires-Dist: ruff>=0.6; extra == "dev"
33
+ Requires-Dist: build>=1.0; extra == "dev"
34
+ Provides-Extra: anthropic
35
+ Requires-Dist: anthropic>=0.30; extra == "anthropic"
36
+ Dynamic: license-file
37
+
38
+ # dtaifm — Deterministic-first Teaching AI Framework Middleware
39
+
40
+ [![tests](https://github.com/dtaifm/dtaifm/actions/workflows/tests.yml/badge.svg)](https://github.com/dtaifm/dtaifm/actions/workflows/tests.yml)
41
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
43
+ [![Version](https://img.shields.io/badge/version-0.1.0-blue.svg)](CHANGELOG.md)
44
+ [![Tests passing](https://img.shields.io/badge/tests-268%20passing-brightgreen.svg)](tests/)
45
+
46
+ **AI proposes. The deterministic layer disposes. AI output is an artifact, not an action.**
47
+
48
+ dtaifm is open-source middleware for systems where AI generates candidate logic (rules, configurations, strategies) and a deterministic, constraint-verified layer has the final say. No AI output executes until it passes a human-defined constraint check.
49
+
50
+ > **What dtaifm is not.** dtaifm is **not** a smart-home product or a network-automation product. `smart_home` and `network_automation` are domain packs that ship in the box to demonstrate the pattern. The framework itself is provider-agnostic (mock, Anthropic, Ollama, Lemonade, bring-your-own) and domain-agnostic (bring your own — see [`examples/custom_domain_template/`](examples/custom_domain_template/)).
51
+
52
+ All file formats are explicitly versioned (`schema_version`) and have published JSON Schemas, so producers and consumers can evolve independently.
53
+
54
+ ## 60-second demo
55
+
56
+ ```bash
57
+ pip install -e ".[dev]"
58
+ dtaifm demo smart_home
59
+ ```
60
+
61
+ That single command walks the entire pipeline — `propose → validate → execute → bundle → replay` — and prints a step-by-step report ending in `RESULT: PASSED`. Runs fully offline using the mock teacher; no API key needed.
62
+
63
+ Want to see the second built-in domain or a local LLM driving it?
64
+
65
+ ```bash
66
+ dtaifm demo network_automation # second built-in domain
67
+ dtaifm demo smart_home --teacher ollama --model llama3.2 # local model via Ollama
68
+ dtaifm demo smart_home --teacher lemonade --teacher-base-url http://192.0.2.10:13305 --model Qwen3-0.6B-GGUF
69
+ dtaifm demo smart_home --json # machine-readable
70
+ ```
71
+
72
+ The demo leaves the proposed rules, the audit bundle, and the hashes in a temp dir so you can `dtaifm inspect` / `dtaifm replay` them afterward.
73
+
74
+ ## Documentation
75
+
76
+ | Doc | What it covers |
77
+ |---|---|
78
+ | [docs/launch.md](docs/launch.md) | The pitch, what's in v0.1, where it fits and does not fit |
79
+ | [docs/quickstart.md](docs/quickstart.md) | Install, run the demo, exercise every CLI command |
80
+ | [docs/concepts.md](docs/concepts.md) | Three-layer architecture, trust boundary, core primitives |
81
+ | [docs/domains.md](docs/domains.md) | Built-in domain packs + how to write your own |
82
+ | [docs/local-teachers.md](docs/local-teachers.md) | Ollama and Lemonade adapters, configuration, diagnostics |
83
+ | [docs/audit-bundles.md](docs/audit-bundles.md) | `.dtaifm-review.json`, replay, tamper detection |
84
+ | [docs/reproposal-loop.md](docs/reproposal-loop.md) | Validator → feedback → teacher revision cycle |
85
+ | [docs/comparison.md](docs/comparison.md) | Where dtaifm fits vs LLM agents, workflow engines, guardrails, policy engines, orchestration |
86
+ | [docs/roadmap.md](docs/roadmap.md) | What shipped in v0.1; near-term and longer-term plans |
87
+ | [docs/release-checklist.md](docs/release-checklist.md) | Maintainer pre-release checklist |
88
+
89
+ For contributors: [CONTRIBUTING.md](CONTRIBUTING.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), [SECURITY.md](SECURITY.md), [CHANGELOG.md](CHANGELOG.md).
90
+
91
+ ## Why
92
+
93
+ Raw LLMs in production systems hallucinate. They are unpredictable at edge cases. Calling them on every user action is slow and expensive. dtaifm decouples the *intelligence* (the AI teacher) from the *execution* (the deterministic student) so you get AI-level optimization with deterministic-level safety and auditability.
94
+
95
+ ## Architecture
96
+
97
+ ```
98
+ Constraints (YAML) ← defined by humans; never changed by AI
99
+
100
+
101
+ Teacher.propose_rules() ← AI model or mock; returns a candidate RuleSet
102
+
103
+
104
+ Validator.validate_ruleset() ← deterministic; approves or rejects each rule
105
+
106
+
107
+ PythonRuntime.fire() ← executes approved rules only; predictable, auditable
108
+ ```
109
+
110
+ Three layers, one contract: **the AI never executes anything directly.**
111
+
112
+ ## Quickstart
113
+
114
+ ```bash
115
+ git clone <repo-url>
116
+ cd dtaifm
117
+ pip install -e ".[dev]"
118
+
119
+ # Run the smart home demo (Python script)
120
+ python examples/smart_rules/demo.py
121
+
122
+ # Run the test suite
123
+ pytest
124
+ ```
125
+
126
+ ## CLI
127
+
128
+ ```bash
129
+ # Emit a JSON Schema for one of the portable file kinds
130
+ dtaifm schema constraints
131
+ dtaifm schema rules
132
+ dtaifm schema state
133
+
134
+ # Run a teacher and write a portable proposed rule file (does NOT validate or execute)
135
+ dtaifm propose examples/smart_rules/constraints.yaml --teacher mock --out proposed.yaml
136
+
137
+ # Audit a rule file against a constraint file (exit 1 if any rule is rejected)
138
+ dtaifm validate examples/smart_rules/constraints.yaml examples/smart_rules/rules.yaml
139
+
140
+ # Validate, then execute approved rules against a state event
141
+ dtaifm run examples/smart_rules/constraints.yaml examples/smart_rules/rules.yaml \
142
+ --state examples/smart_rules/state.json
143
+
144
+ # Combined audit: proposal metadata + validation + execution trace + final actions
145
+ dtaifm review examples/smart_rules/constraints.yaml proposed.yaml \
146
+ --state examples/smart_rules/state.json --json
147
+
148
+ # Write a portable audit bundle alongside the review
149
+ dtaifm review examples/smart_rules/constraints.yaml proposed.yaml \
150
+ --state examples/smart_rules/state.json --bundle review.json
151
+
152
+ # Replay a bundle and verify it reproduces exactly (exit 1 on mismatch)
153
+ dtaifm replay review.json
154
+
155
+ # Read-only summary of a bundle — no execution
156
+ dtaifm inspect review.json
157
+
158
+ # Build a deterministic feedback artifact from validation failures (NO execution)
159
+ dtaifm feedback constraints.yaml rules.yaml --out feedback.json
160
+
161
+ # Hand the deterministic feedback to a teacher and write a revised rule file
162
+ # (the revised file is NOT validated or executed by repropose — only review/validate does that)
163
+ dtaifm repropose constraints.yaml rules.yaml --teacher mock --out revised.yaml
164
+ ```
165
+
166
+ `--json` is available on `validate`, `run`, `review`, `replay`, and `inspect`. If the `dtaifm` entry point isn't on your PATH after install, use `python -m dtaifm ...` instead.
167
+
168
+ ## Audit Bundles & Replay
169
+
170
+ `dtaifm review --bundle review.json` writes a self-contained audit artifact embedding the constraints, rules, state, validation result, and execution trace, with **SHA-256 hashes** over the canonical-JSON form of each. The bundle is portable across machines and survives YAML/JSON formatting differences.
171
+
172
+ `dtaifm replay review.json` re-executes the review on a fresh checkout and verifies:
173
+
174
+ 1. Embedded inputs match their recorded hashes (catches input tampering).
175
+ 2. Stored validation/execution results match their recorded hashes (catches result tampering).
176
+ 3. Recomputed validation/execution from inputs match the stored hashes (catches framework non-determinism or domain semantic drift).
177
+
178
+ A domain-version mismatch becomes a **warning** rather than a failure when results still match (a non-breaking domain change). Replay never invokes a teacher or provider adapter — it's a pure deterministic verification.
179
+
180
+ ### Public Python API
181
+
182
+ ```python
183
+ from dtaifm import review, replay, inspect_bundle
184
+
185
+ bundle = review(
186
+ constraints_path="constraints.yaml",
187
+ rules_path="rules.yaml",
188
+ state_path="state.json",
189
+ domain_id="smart_home",
190
+ bundle_path="review.json", # optional
191
+ )
192
+
193
+ result = replay("review.json") # or replay(bundle)
194
+ assert result.success
195
+ assert result.inputs_intact
196
+ assert result.validation_matches
197
+
198
+ summary = inspect_bundle("review.json") # pure read; no execution
199
+ ```
200
+
201
+ The CLI is a thin wrapper over these three functions.
202
+
203
+ ### Pipeline
204
+
205
+ ```
206
+ propose -> validate -> run (or `review` for all three in one report)
207
+ [teacher] [student] [runtime]
208
+ artifact gate execution
209
+ ```
210
+
211
+ `propose` only writes a file. `validate` only audits it. The runtime only ever sees rules that the validator approved. The principle: AI output is an artifact, not an action.
212
+
213
+ ### File formats (all versioned)
214
+
215
+ Every dtaifm file carries `schema_version: "0.1"` at the top level. Loaders reject files with a missing or unsupported version.
216
+
217
+ **constraints.yaml**
218
+
219
+ ```yaml
220
+ schema_version: "0.1"
221
+ constraints:
222
+ - id: no_auto_unlock
223
+ description: "Never unlock doors automatically."
224
+ type: absolute_prohibition
225
+ applies_to: [front_door]
226
+ action: unlock
227
+ ```
228
+
229
+ **rules.yaml** (proposed rules carry provenance; hand-written rules may omit it)
230
+
231
+ ```yaml
232
+ schema_version: "0.1"
233
+ rules:
234
+ - id: r_x
235
+ name: "Example"
236
+ trigger: { device: motion_sensor, event: motion_detected }
237
+ conditions: []
238
+ actions:
239
+ - { device: hallway_light, action: turn_on }
240
+ satisfies_constraints: [motion_light_hours]
241
+ explanation: "What the rule does."
242
+ rationale: "Why the teacher proposed it."
243
+ proposed_by: mock
244
+ proposal_id: 7f3c...
245
+ created_at: "2026-05-24T10:00:00+00:00"
246
+ ```
247
+
248
+ **state.json**
249
+
250
+ ```json
251
+ {
252
+ "schema_version": "0.1",
253
+ "event": { "device": "motion_sensor", "type": "motion_detected" },
254
+ "time": "2024-01-01T23:00:00",
255
+ "mode": "normal",
256
+ "devices": { "ac": "off", "heating": "off" }
257
+ }
258
+ ```
259
+
260
+ ### Execution trace
261
+
262
+ Every `run` produces a deterministic trace explaining why each approved rule fired or was skipped — the condition that failed, with its evaluated parameters. Rejected rules never reach the runtime and therefore never appear in the trace.
263
+
264
+ ```
265
+ FIRED [r_motion_night_light] trigger matched and all conditions passed
266
+ - time_range {'start_hour': 22, 'end_hour': 6} -> ok
267
+ - mode_not {'mode': 'security'} -> ok
268
+ SKIPPED [r_heating_cold] trigger did not match (rule expects thermostat.temperature_below_threshold)
269
+ ```
270
+
271
+ ### Expected demo output
272
+
273
+ ```
274
+ === dtaifm Smart Home Demo ===
275
+ AI proposes. The deterministic layer disposes.
276
+
277
+ ──────────────────────────────────────────────────
278
+ 1. Constraints (defined by humans)
279
+ ──────────────────────────────────────────────────
280
+ [no_auto_unlock] Never unlock doors automatically.
281
+ [no_hvac_conflict] Do not turn heating and AC on at the same time.
282
+ [motion_light_hours] Lights may turn on from motion only during configured night hours.
283
+ [security_override] Security mode overrides comfort automation.
284
+ [rule_must_explain] Every generated rule must explain which constraint it satisfies.
285
+
286
+ ──────────────────────────────────────────────────
287
+ 2. Teacher proposes rules (AI / mock)
288
+ ──────────────────────────────────────────────────
289
+ 3 rule(s) proposed:
290
+ [r_motion_night_light] Motion-Activated Night Light
291
+ [r_auto_unlock_door] Auto-Unlock on Arrival (UNSAFE)
292
+ [r_heating_cold] Activate Heating When Cold
293
+
294
+ ──────────────────────────────────────────────────
295
+ 3. Validator reviews each rule (deterministic)
296
+ ──────────────────────────────────────────────────
297
+ APPROVED [r_motion_night_light] Motion-Activated Night Light
298
+ REJECTED [r_auto_unlock_door] Auto-Unlock on Arrival (UNSAFE)
299
+ ! [no_auto_unlock] Rule 'r_auto_unlock_door' performs prohibited action 'unlock' on device 'front_door'.
300
+ ! [rule_must_explain] Rule 'r_auto_unlock_door' does not declare which constraints it satisfies.
301
+ APPROVED [r_heating_cold] Activate Heating When Cold
302
+
303
+ ──────────────────────────────────────────────────
304
+ 4. Runtime executes approved rules (deterministic)
305
+ ──────────────────────────────────────────────────
306
+
307
+ Event: motion_detected at 23:00 — normal mode
308
+ -> [r_motion_night_light] hallway_light: turn_on {'duration': 300}
309
+
310
+ Event: motion_detected at 23:00 — security mode
311
+ -> (no rules triggered)
312
+
313
+ Event: motion_detected at 14:00 — normal mode (outside night hours)
314
+ -> (no rules triggered)
315
+
316
+ Event: temperature_below_threshold — AC off, normal mode
317
+ -> [r_heating_cold] heating: turn_on
318
+ ```
319
+
320
+ ## Core Concepts
321
+
322
+ | Concept | Role |
323
+ |---|---|
324
+ | `Constraint` | A hard rule a system must never violate. Defined by humans in YAML. Immutable at runtime. |
325
+ | `Rule` | A candidate action proposed by the AI teacher. Contains trigger, conditions, actions, and a declaration of which constraints it satisfies. |
326
+ | `RuleSet` | A collection of proposed rules returned by a single teacher call. |
327
+ | `ValidationResult` | The outcome of checking one Rule against all Constraints. Carries violations with reasons. |
328
+ | `ExecutionResult` | The outcome of firing approved rules against a live system state event. |
329
+
330
+ ### Constraint types
331
+
332
+ | Type | Description |
333
+ |---|---|
334
+ | `absolute_prohibition` | A specific action on a specific device is never allowed. |
335
+ | `mutual_exclusion` | Two or more devices must never be activated simultaneously. |
336
+ | `temporal_restriction` | A device may only be controlled via a trigger within a time window. |
337
+ | `mode_override` | A named mode (e.g. `security`) supersedes comfort automation. |
338
+ | `metadata_requirement` | Every rule must carry specified metadata fields. |
339
+
340
+ ## Defining Your Own Constraints
341
+
342
+ ```yaml
343
+ # constraints.yaml
344
+ constraints:
345
+ - id: no_auto_unlock
346
+ description: "Never unlock doors automatically."
347
+ type: absolute_prohibition
348
+ applies_to:
349
+ - front_door
350
+ action: unlock
351
+ ```
352
+
353
+ Load them in Python:
354
+
355
+ ```python
356
+ import yaml
357
+ from dtaifm.core.constraint import Constraint
358
+
359
+ with open("constraints.yaml") as f:
360
+ data = yaml.safe_load(f)
361
+ constraints = [Constraint.from_dict(c) for c in data["constraints"]]
362
+ ```
363
+
364
+ ## Domains
365
+
366
+ dtaifm is **middleware**, not a smart-home engine. A *domain pack* declares what is possible in a given system — its allowed trigger events, condition types, action verbs, and any domain-specific constraint evaluators. Teachers propose only within that boundary; the validator and runtime both refuse out-of-vocabulary rules.
367
+
368
+ Two domains ship in the box:
369
+
370
+ | Domain id | What it covers |
371
+ |---|---|
372
+ | `smart_home` (default) | Residential automation: lights, HVAC, locks, sensors. |
373
+ | `network_automation` | Router/switch config, BGP, maintenance windows; includes custom evaluators for `companion_action_required`, `action_target_limit`, `mode_required`. |
374
+
375
+ Every CLI command takes `--domain`:
376
+
377
+ ```bash
378
+ # smart_home (default)
379
+ dtaifm validate examples/smart_rules/constraints.yaml examples/smart_rules/rules.yaml
380
+
381
+ # network_automation
382
+ dtaifm validate --domain network_automation \
383
+ examples/network_automation/constraints.yaml examples/network_automation/rules.yaml
384
+
385
+ dtaifm review --domain network_automation \
386
+ examples/network_automation/constraints.yaml examples/network_automation/rules.yaml \
387
+ --state examples/network_automation/state.json
388
+ ```
389
+
390
+ ### Adding your own domain
391
+
392
+ ```python
393
+ from dtaifm.domains.base import Domain
394
+ from dtaifm.domains.registry import register_domain
395
+
396
+ MY_DOMAIN = Domain(
397
+ id="my_domain",
398
+ version="0.1",
399
+ trigger_events=frozenset({"order_placed", "shipment_delayed"}),
400
+ condition_types=frozenset({"time_range", "mode_not", "device_state"}),
401
+ action_kinds=frozenset({"notify", "refund", "escalate"}),
402
+ extra_constraint_evaluators={
403
+ # "my_custom_type": my_evaluator, # (rule, constraint) -> ConstraintViolation | None
404
+ },
405
+ )
406
+ register_domain(MY_DOMAIN)
407
+ # Now: dtaifm validate --domain my_domain ...
408
+ ```
409
+
410
+ Provider adapters (Anthropic, OpenAI, etc.) contain **no domain logic**. They are translators that take a `TeacherRequest` (which carries the domain) and return a portable RuleSet. The domain's vocabulary is rendered into the prompt automatically.
411
+
412
+ ## Teachers
413
+
414
+ A **Teacher** translates a `TeacherRequest` (constraints + context) into a `TeacherResponse` carrying a portable `RuleSet` artifact. Teachers never validate or execute. **Provider adapters are translators, not trusted components.**
415
+
416
+ ### Mock teacher (always available, no install needed)
417
+
418
+ ```bash
419
+ dtaifm propose constraints.yaml --teacher mock --out proposed.yaml
420
+ ```
421
+
422
+ ### Anthropic Claude adapter (optional extra)
423
+
424
+ ```bash
425
+ pip install 'dtaifm[anthropic]'
426
+ export ANTHROPIC_API_KEY=sk-ant-...
427
+
428
+ # (optional) override the default model:
429
+ export ANTHROPIC_MODEL=claude-opus-4-7
430
+
431
+ dtaifm propose constraints.yaml --teacher anthropic --domain smart_home --out proposed.yaml
432
+ ```
433
+
434
+ The Anthropic SDK is **not** a core dependency. `pip install dtaifm` still works without it; an attempt to use `--teacher anthropic` without the extra installed fails with a clear install hint.
435
+
436
+ ### Local teachers — Ollama and Lemonade (no API keys)
437
+
438
+ Both adapters speak plain JSON over HTTP via stdlib — no extra install required. Defaults:
439
+
440
+ | Teacher | Default base URL | Endpoint | Env var |
441
+ |---|---|---|---|
442
+ | `ollama` | `http://localhost:11434` | `POST /api/chat` | `DTAIFM_OLLAMA_BASE_URL` |
443
+ | `lemonade` | `http://localhost:13305` | `POST /v1/chat/completions` (OpenAI-compatible) | `DTAIFM_LEMONADE_BASE_URL` |
444
+
445
+ Override precedence is **CLI flag > env var > default**, and trailing slashes are normalized.
446
+
447
+ ```bash
448
+ # Local Ollama (default endpoint, llama3.2 by default)
449
+ dtaifm propose constraints.yaml --teacher ollama --domain smart_home --out proposed.yaml
450
+
451
+ # Local Ollama with a specific model
452
+ dtaifm propose constraints.yaml --teacher ollama --model qwen3:0.6b --out proposed.yaml
453
+
454
+ # Lemonade on a remote workstation
455
+ dtaifm propose constraints.yaml \
456
+ --teacher lemonade \
457
+ --teacher-base-url http://192.0.2.10:13305 \
458
+ --model Qwen3-0.6B-GGUF \
459
+ --out proposed.yaml
460
+
461
+ # Or, via env var:
462
+ export DTAIFM_LEMONADE_BASE_URL=http://192.0.2.10:13305
463
+ dtaifm propose constraints.yaml --teacher lemonade --model Qwen3-0.6B-GGUF --out proposed.yaml
464
+ ```
465
+
466
+ The local adapters route the model's response through the same strict parser as the Anthropic adapter — malformed output fails clearly, narration outside the JSON block is tolerated, and provenance fields (`rationale`, `satisfies_constraints`) are required. **Local models improve privacy and adoption, but they are still untrusted teachers** — every proposed rule still has to pass `dtaifm review` before anything executes.
467
+
468
+ ### Diagnosing your local setup
469
+
470
+ ```bash
471
+ dtaifm teachers # list registered teachers + base URLs + env-var hints
472
+ dtaifm teachers --check # additionally ping local endpoints; offline servers are reported gracefully
473
+ dtaifm teachers --json # machine-readable
474
+ ```
475
+
476
+ ## Reproposal Loop
477
+
478
+ Teachers rarely produce a perfect first proposal. The reproposal loop lets any teacher (mock, Anthropic, Ollama, Lemonade) consume the validator's deterministic violation reasons and try again — without weakening the trust boundary.
479
+
480
+ ```bash
481
+ # 1. The teacher's first attempt
482
+ dtaifm propose constraints.yaml --teacher ollama --out v1.yaml
483
+
484
+ # 2. Inspect what failed (validator only — no execution)
485
+ dtaifm feedback constraints.yaml v1.yaml --out feedback.json
486
+ # feedback.json contains: schema_version, domain, rejected_rules
487
+ # [{rule_id, name, violations, allowed_triggers, allowed_conditions, allowed_actions}]
488
+
489
+ # 3. Repropose: the teacher receives the previous rules + the named violations
490
+ dtaifm repropose constraints.yaml v1.yaml --teacher ollama --out v2.yaml
491
+
492
+ # 4. Now run a real review on the revised file (THIS is where execution happens)
493
+ dtaifm review constraints.yaml v2.yaml --state state.json --bundle review.json
494
+ ```
495
+
496
+ **Guarantees enforced in code:**
497
+
498
+ - `dtaifm feedback` never instantiates the runtime (tests assert this).
499
+ - `dtaifm repropose` validates the original rules once to build feedback, calls the teacher, and writes the revised file — it does **not** validate or execute the revision. Even if the teacher returns an unsafe rule, repropose writes it. Only `dtaifm review` or `dtaifm validate` gates execution.
500
+ - The prompt's `REVISION REQUESTED` section uses stable, grep-able markers (`YOUR PREVIOUS RULES:`, `REJECTED RULES (must be fixed or removed):`, `Violations:`, `Allowed triggers:` / `conditions:` / `actions:`) so adapter tests can lock its format.
501
+
502
+ The principle: **the deterministic layer may teach the teacher, but it never lets the teacher grade itself.**
503
+
504
+ > **Warning:** Generated rules are an artifact, not a green light. Always run them through `dtaifm review` (or `dtaifm validate` + `dtaifm run`) before deploying — the validator is the only gate that authorizes execution.
505
+
506
+ ### Inspecting the prompt
507
+
508
+ ```bash
509
+ dtaifm prompt constraints.yaml --teacher anthropic --domain smart_home
510
+ ```
511
+
512
+ `dtaifm prompt` shows the exact text input the adapter would send. It requires no API key and makes no network calls.
513
+
514
+ ### Building your own teacher
515
+
516
+ ```python
517
+ from dtaifm.teacher.base import Teacher
518
+ from dtaifm.teacher.contract import TeacherRequest, TeacherResponse
519
+ from dtaifm.teacher.parser import parse_provider_payload
520
+ from dtaifm.teacher.registry import register_teacher
521
+
522
+ class CustomTeacher(Teacher):
523
+ def propose(self, request: TeacherRequest) -> TeacherResponse:
524
+ # 1. self.render_prompt(request) gives you the standard prompt
525
+ # 2. Call your provider, request JSON output that matches dtaifm.schema.RULES_SCHEMA
526
+ # 3. Run the response through parse_provider_payload for strict validation
527
+ payload = ... # dict from your provider
528
+ ruleset = parse_provider_payload(payload, source="custom")
529
+ return TeacherResponse(ruleset=ruleset, raw_provider_output=...)
530
+
531
+ register_teacher("custom", CustomTeacher)
532
+ # Now: dtaifm propose constraints.yaml --teacher custom --out proposed.yaml
533
+ ```
534
+
535
+ `dtaifm propose` stamps `proposed_by`, `proposal_id`, and `created_at` on every rule automatically. Teachers only need to supply the rule logic and a non-empty `rationale` for each rule.
536
+
537
+ ## Developer commands
538
+
539
+ ```bash
540
+ # Install with dev tools (pytest, jsonschema, ruff, build)
541
+ pip install -e ".[dev]"
542
+
543
+ # Run the test suite (256 tests, fully offline, no API keys)
544
+ pytest
545
+
546
+ # Lint and format
547
+ ruff check dtaifm tests
548
+ ruff format dtaifm tests
549
+
550
+ # Build a wheel
551
+ python -m build --wheel
552
+
553
+ # Smoke-test the wheel in a clean venv (also runs in CI)
554
+ python -m venv /tmp/wheel-test
555
+ /tmp/wheel-test/bin/pip install dist/dtaifm-0.1.0-py3-none-any.whl
556
+ /tmp/wheel-test/bin/dtaifm --help
557
+ ```
558
+
559
+ Optional type checking (`mypy dtaifm`) is supported but not enforced in CI.
560
+
561
+ ## Roadmap
562
+
563
+ - [x] CLI (`validate`, `run`) with text + JSON output
564
+ - [x] Portable rule files (YAML / JSON)
565
+ - [x] Deterministic execution trace
566
+ - [x] CI on Python 3.11–3.13 (core install + anthropic-extra install)
567
+ - [x] Schema versioning + published JSON Schemas (`dtaifm schema`)
568
+ - [x] `dtaifm propose` (teacher artifact) and `dtaifm review` (combined audit)
569
+ - [x] Rule provenance fields (`proposed_by`, `proposal_id`, `created_at`, `rationale`)
570
+ - [x] Teacher registry (adapter slot — no provider deps in core)
571
+ - [x] `TeacherRequest` / `TeacherResponse` / `PromptContext` provider-neutral contract
572
+ - [x] `dtaifm prompt` — inspect adapter input with no API key
573
+ - [x] Strict provider response parsing (`ProviderResponseError`)
574
+ - [x] Anthropic Claude teacher adapter (optional extra)
575
+ - [x] Domain pack abstraction with registry (`smart_home`, `network_automation`)
576
+ - [x] Domain-aware validator (rejects out-of-vocabulary triggers/conditions/actions)
577
+ - [x] Runtime defense-in-depth: refuses actions outside the active domain
578
+ - [x] Domain-aware prompts (every adapter receives the domain's vocabulary)
579
+ - [x] Replayable audit bundles (`.dtaifm-review.json`) with canonical-JSON SHA-256 hashes
580
+ - [x] `dtaifm replay` and `dtaifm inspect` commands; public Python API (`review`, `replay`, `inspect_bundle`)
581
+ - [x] Tamper detection across inputs, stored results, and recomputed results
582
+ - [x] Local teacher adapters: `ollama` and `lemonade` (no API keys, no extra deps)
583
+ - [x] `dtaifm teachers` / `dtaifm teachers --check` connectivity diagnostics
584
+ - [x] Deterministic feedback artifacts (`dtaifm feedback`) — validation-only, no execution
585
+ - [x] Reproposal loop (`dtaifm repropose`) — teachers consume named violations through the same `TeacherRequest` contract; the revision is written but not validated/executed
586
+ - [ ] OpenAI teacher adapter (optional extra)
587
+ - [ ] Persistent audit log of every propose → validate → execute cycle
588
+ - [ ] Telecom / network automation example
589
+ - [ ] Rust/WASM deterministic runtime
590
+
591
+ ## Contributing
592
+
593
+ Contributions welcome. The most valuable areas right now are teacher adapters for real AI providers and additional constraint types. Open an issue before large PRs.
594
+
595
+ ## License
596
+
597
+ MIT