Deterministic-first Teaching AI Framework Middleware — AI proposes, the deterministic layer disposes.
Project description
dtaifm — Deterministic-first Teaching AI Framework Middleware
AI proposes. The deterministic layer disposes. AI output is an artifact, not an action.
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.
What dtaifm is not. dtaifm is not a smart-home product or a network-automation product.
smart_homeandnetwork_automationare 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 — seeexamples/custom_domain_template/).
All file formats are explicitly versioned (schema_version) and have published JSON Schemas, so producers and consumers can evolve independently.
60-second demo
pip install dtaifm
dtaifm demo smart_home
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.
Alternate source install — if you want to develop against the latest
main, install from GitHub instead:pip install git+https://github.com/dtaifm/dtaifm.git. For pinned source installs, append@v0.1.0(or any tag).
Want to see the second built-in domain or a local LLM driving it?
dtaifm demo network_automation # second built-in domain
dtaifm demo smart_home --teacher ollama --model llama3.2 # local model via Ollama
dtaifm demo smart_home --teacher lemonade --teacher-base-url http://192.0.2.10:13305 --model Qwen3-0.6B-GGUF
dtaifm demo smart_home --json # machine-readable
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.
Documentation
| Doc | What it covers |
|---|---|
| docs/launch.md | The pitch, what's in v0.1, where it fits and does not fit |
| docs/quickstart.md | Install, run the demo, exercise every CLI command |
| docs/concepts.md | Three-layer architecture, trust boundary, core primitives |
| docs/domains.md | Built-in domain packs + how to write your own |
| docs/local-teachers.md | Ollama and Lemonade adapters, configuration, diagnostics |
| docs/audit-bundles.md | .dtaifm-review.json, replay, tamper detection |
| docs/reproposal-loop.md | Validator → feedback → teacher revision cycle |
| docs/comparison.md | Where dtaifm fits vs LLM agents, workflow engines, guardrails, policy engines, orchestration |
| docs/roadmap.md | What shipped in v0.1; near-term and longer-term plans |
| docs/release-checklist.md | Maintainer pre-release checklist |
For contributors: CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, CHANGELOG.md.
Why
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.
Architecture
Constraints (YAML) ← defined by humans; never changed by AI
│
▼
Teacher.propose_rules() ← AI model or mock; returns a candidate RuleSet
│
▼
Validator.validate_ruleset() ← deterministic; approves or rejects each rule
│
▼
PythonRuntime.fire() ← executes approved rules only; predictable, auditable
Three layers, one contract: the AI never executes anything directly.
Quickstart
git clone <repo-url>
cd dtaifm
pip install -e ".[dev]"
# Run the smart home demo (Python script)
python examples/smart_rules/demo.py
# Run the test suite
pytest
CLI
# Emit a JSON Schema for one of the portable file kinds
dtaifm schema constraints
dtaifm schema rules
dtaifm schema state
# Run a teacher and write a portable proposed rule file (does NOT validate or execute)
dtaifm propose examples/smart_rules/constraints.yaml --teacher mock --out proposed.yaml
# Audit a rule file against a constraint file (exit 1 if any rule is rejected)
dtaifm validate examples/smart_rules/constraints.yaml examples/smart_rules/rules.yaml
# Validate, then execute approved rules against a state event
dtaifm run examples/smart_rules/constraints.yaml examples/smart_rules/rules.yaml \
--state examples/smart_rules/state.json
# Combined audit: proposal metadata + validation + execution trace + final actions
dtaifm review examples/smart_rules/constraints.yaml proposed.yaml \
--state examples/smart_rules/state.json --json
# Write a portable audit bundle alongside the review
dtaifm review examples/smart_rules/constraints.yaml proposed.yaml \
--state examples/smart_rules/state.json --bundle review.json
# Replay a bundle and verify it reproduces exactly (exit 1 on mismatch)
dtaifm replay review.json
# Read-only summary of a bundle — no execution
dtaifm inspect review.json
# Build a deterministic feedback artifact from validation failures (NO execution)
dtaifm feedback constraints.yaml rules.yaml --out feedback.json
# Hand the deterministic feedback to a teacher and write a revised rule file
# (the revised file is NOT validated or executed by repropose — only review/validate does that)
dtaifm repropose constraints.yaml rules.yaml --teacher mock --out revised.yaml
--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.
Audit Bundles & Replay
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.
dtaifm replay review.json re-executes the review on a fresh checkout and verifies:
- Embedded inputs match their recorded hashes (catches input tampering).
- Stored validation/execution results match their recorded hashes (catches result tampering).
- Recomputed validation/execution from inputs match the stored hashes (catches framework non-determinism or domain semantic drift).
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.
Public Python API
from dtaifm import review, replay, inspect_bundle
bundle = review(
constraints_path="constraints.yaml",
rules_path="rules.yaml",
state_path="state.json",
domain_id="smart_home",
bundle_path="review.json", # optional
)
result = replay("review.json") # or replay(bundle)
assert result.success
assert result.inputs_intact
assert result.validation_matches
summary = inspect_bundle("review.json") # pure read; no execution
The CLI is a thin wrapper over these three functions.
Pipeline
propose -> validate -> run (or `review` for all three in one report)
[teacher] [student] [runtime]
artifact gate execution
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.
File formats (all versioned)
Every dtaifm file carries schema_version: "0.1" at the top level. Loaders reject files with a missing or unsupported version.
constraints.yaml
schema_version: "0.1"
constraints:
- id: no_auto_unlock
description: "Never unlock doors automatically."
type: absolute_prohibition
applies_to: [front_door]
action: unlock
rules.yaml (proposed rules carry provenance; hand-written rules may omit it)
schema_version: "0.1"
rules:
- id: r_x
name: "Example"
trigger: { device: motion_sensor, event: motion_detected }
conditions: []
actions:
- { device: hallway_light, action: turn_on }
satisfies_constraints: [motion_light_hours]
explanation: "What the rule does."
rationale: "Why the teacher proposed it."
proposed_by: mock
proposal_id: 7f3c...
created_at: "2026-05-24T10:00:00+00:00"
state.json
{
"schema_version": "0.1",
"event": { "device": "motion_sensor", "type": "motion_detected" },
"time": "2024-01-01T23:00:00",
"mode": "normal",
"devices": { "ac": "off", "heating": "off" }
}
Execution trace
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.
FIRED [r_motion_night_light] trigger matched and all conditions passed
- time_range {'start_hour': 22, 'end_hour': 6} -> ok
- mode_not {'mode': 'security'} -> ok
SKIPPED [r_heating_cold] trigger did not match (rule expects thermostat.temperature_below_threshold)
Expected demo output
=== dtaifm Smart Home Demo ===
AI proposes. The deterministic layer disposes.
──────────────────────────────────────────────────
1. Constraints (defined by humans)
──────────────────────────────────────────────────
[no_auto_unlock] Never unlock doors automatically.
[no_hvac_conflict] Do not turn heating and AC on at the same time.
[motion_light_hours] Lights may turn on from motion only during configured night hours.
[security_override] Security mode overrides comfort automation.
[rule_must_explain] Every generated rule must explain which constraint it satisfies.
──────────────────────────────────────────────────
2. Teacher proposes rules (AI / mock)
──────────────────────────────────────────────────
3 rule(s) proposed:
[r_motion_night_light] Motion-Activated Night Light
[r_auto_unlock_door] Auto-Unlock on Arrival (UNSAFE)
[r_heating_cold] Activate Heating When Cold
──────────────────────────────────────────────────
3. Validator reviews each rule (deterministic)
──────────────────────────────────────────────────
APPROVED [r_motion_night_light] Motion-Activated Night Light
REJECTED [r_auto_unlock_door] Auto-Unlock on Arrival (UNSAFE)
! [no_auto_unlock] Rule 'r_auto_unlock_door' performs prohibited action 'unlock' on device 'front_door'.
! [rule_must_explain] Rule 'r_auto_unlock_door' does not declare which constraints it satisfies.
APPROVED [r_heating_cold] Activate Heating When Cold
──────────────────────────────────────────────────
4. Runtime executes approved rules (deterministic)
──────────────────────────────────────────────────
Event: motion_detected at 23:00 — normal mode
-> [r_motion_night_light] hallway_light: turn_on {'duration': 300}
Event: motion_detected at 23:00 — security mode
-> (no rules triggered)
Event: motion_detected at 14:00 — normal mode (outside night hours)
-> (no rules triggered)
Event: temperature_below_threshold — AC off, normal mode
-> [r_heating_cold] heating: turn_on
Core Concepts
| Concept | Role |
|---|---|
Constraint |
A hard rule a system must never violate. Defined by humans in YAML. Immutable at runtime. |
Rule |
A candidate action proposed by the AI teacher. Contains trigger, conditions, actions, and a declaration of which constraints it satisfies. |
RuleSet |
A collection of proposed rules returned by a single teacher call. |
ValidationResult |
The outcome of checking one Rule against all Constraints. Carries violations with reasons. |
ExecutionResult |
The outcome of firing approved rules against a live system state event. |
Constraint types
| Type | Description |
|---|---|
absolute_prohibition |
A specific action on a specific device is never allowed. |
mutual_exclusion |
Two or more devices must never be activated simultaneously. |
temporal_restriction |
A device may only be controlled via a trigger within a time window. |
mode_override |
A named mode (e.g. security) supersedes comfort automation. |
metadata_requirement |
Every rule must carry specified metadata fields. |
Defining Your Own Constraints
# constraints.yaml
constraints:
- id: no_auto_unlock
description: "Never unlock doors automatically."
type: absolute_prohibition
applies_to:
- front_door
action: unlock
Load them in Python:
import yaml
from dtaifm.core.constraint import Constraint
with open("constraints.yaml") as f:
data = yaml.safe_load(f)
constraints = [Constraint.from_dict(c) for c in data["constraints"]]
Domains
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.
Two domains ship in the box:
| Domain id | What it covers |
|---|---|
smart_home (default) |
Residential automation: lights, HVAC, locks, sensors. |
network_automation |
Router/switch config, BGP, maintenance windows; includes custom evaluators for companion_action_required, action_target_limit, mode_required. |
Every CLI command takes --domain:
# smart_home (default)
dtaifm validate examples/smart_rules/constraints.yaml examples/smart_rules/rules.yaml
# network_automation
dtaifm validate --domain network_automation \
examples/network_automation/constraints.yaml examples/network_automation/rules.yaml
dtaifm review --domain network_automation \
examples/network_automation/constraints.yaml examples/network_automation/rules.yaml \
--state examples/network_automation/state.json
Adding your own domain
from dtaifm.domains.base import Domain
from dtaifm.domains.registry import register_domain
MY_DOMAIN = Domain(
id="my_domain",
version="0.1",
trigger_events=frozenset({"order_placed", "shipment_delayed"}),
condition_types=frozenset({"time_range", "mode_not", "device_state"}),
action_kinds=frozenset({"notify", "refund", "escalate"}),
extra_constraint_evaluators={
# "my_custom_type": my_evaluator, # (rule, constraint) -> ConstraintViolation | None
},
)
register_domain(MY_DOMAIN)
# Now: dtaifm validate --domain my_domain ...
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.
Teachers
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.
Mock teacher (always available, no install needed)
dtaifm propose constraints.yaml --teacher mock --out proposed.yaml
Anthropic Claude adapter (optional extra)
pip install 'dtaifm[anthropic]'
export ANTHROPIC_API_KEY=sk-ant-...
# (optional) override the default model:
export ANTHROPIC_MODEL=claude-opus-4-7
dtaifm propose constraints.yaml --teacher anthropic --domain smart_home --out proposed.yaml
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.
Local teachers — Ollama and Lemonade (no API keys)
Both adapters speak plain JSON over HTTP via stdlib — no extra install required. Defaults:
| Teacher | Default base URL | Endpoint | Env var |
|---|---|---|---|
ollama |
http://localhost:11434 |
POST /api/chat |
DTAIFM_OLLAMA_BASE_URL |
lemonade |
http://localhost:13305 |
POST /v1/chat/completions (OpenAI-compatible) |
DTAIFM_LEMONADE_BASE_URL |
Override precedence is CLI flag > env var > default, and trailing slashes are normalized.
# Local Ollama (default endpoint, llama3.2 by default)
dtaifm propose constraints.yaml --teacher ollama --domain smart_home --out proposed.yaml
# Local Ollama with a specific model
dtaifm propose constraints.yaml --teacher ollama --model qwen3:0.6b --out proposed.yaml
# Lemonade on a remote workstation
dtaifm propose constraints.yaml \
--teacher lemonade \
--teacher-base-url http://192.0.2.10:13305 \
--model Qwen3-0.6B-GGUF \
--out proposed.yaml
# Or, via env var:
export DTAIFM_LEMONADE_BASE_URL=http://192.0.2.10:13305
dtaifm propose constraints.yaml --teacher lemonade --model Qwen3-0.6B-GGUF --out proposed.yaml
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.
Diagnosing your local setup
dtaifm teachers # list registered teachers + base URLs + env-var hints
dtaifm teachers --check # additionally ping local endpoints; offline servers are reported gracefully
dtaifm teachers --json # machine-readable
Reproposal Loop
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.
# 1. The teacher's first attempt
dtaifm propose constraints.yaml --teacher ollama --out v1.yaml
# 2. Inspect what failed (validator only — no execution)
dtaifm feedback constraints.yaml v1.yaml --out feedback.json
# feedback.json contains: schema_version, domain, rejected_rules
# [{rule_id, name, violations, allowed_triggers, allowed_conditions, allowed_actions}]
# 3. Repropose: the teacher receives the previous rules + the named violations
dtaifm repropose constraints.yaml v1.yaml --teacher ollama --out v2.yaml
# 4. Now run a real review on the revised file (THIS is where execution happens)
dtaifm review constraints.yaml v2.yaml --state state.json --bundle review.json
Guarantees enforced in code:
dtaifm feedbacknever instantiates the runtime (tests assert this).dtaifm reproposevalidates 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. Onlydtaifm reviewordtaifm validategates execution.- The prompt's
REVISION REQUESTEDsection 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.
The principle: the deterministic layer may teach the teacher, but it never lets the teacher grade itself.
Warning: Generated rules are an artifact, not a green light. Always run them through
dtaifm review(ordtaifm validate+dtaifm run) before deploying — the validator is the only gate that authorizes execution.
Inspecting the prompt
dtaifm prompt constraints.yaml --teacher anthropic --domain smart_home
dtaifm prompt shows the exact text input the adapter would send. It requires no API key and makes no network calls.
Building your own teacher
from dtaifm.teacher.base import Teacher
from dtaifm.teacher.contract import TeacherRequest, TeacherResponse
from dtaifm.teacher.parser import parse_provider_payload
from dtaifm.teacher.registry import register_teacher
class CustomTeacher(Teacher):
def propose(self, request: TeacherRequest) -> TeacherResponse:
# 1. self.render_prompt(request) gives you the standard prompt
# 2. Call your provider, request JSON output that matches dtaifm.schema.RULES_SCHEMA
# 3. Run the response through parse_provider_payload for strict validation
payload = ... # dict from your provider
ruleset = parse_provider_payload(payload, source="custom")
return TeacherResponse(ruleset=ruleset, raw_provider_output=...)
register_teacher("custom", CustomTeacher)
# Now: dtaifm propose constraints.yaml --teacher custom --out proposed.yaml
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.
Developer commands
# Install with dev tools (pytest, jsonschema, ruff, build)
pip install -e ".[dev]"
# Run the test suite (256 tests, fully offline, no API keys)
pytest
# Lint and format
ruff check dtaifm tests
ruff format dtaifm tests
# Build a wheel
python -m build --wheel
# Smoke-test the wheel in a clean venv (also runs in CI)
python -m venv /tmp/wheel-test
/tmp/wheel-test/bin/pip install dist/dtaifm-0.1.0-py3-none-any.whl
/tmp/wheel-test/bin/dtaifm --help
Optional type checking (mypy dtaifm) is supported but not enforced in CI.
Roadmap
- CLI (
validate,run) with text + JSON output - Portable rule files (YAML / JSON)
- Deterministic execution trace
- CI on Python 3.11–3.13 (core install + anthropic-extra install)
- Schema versioning + published JSON Schemas (
dtaifm schema) -
dtaifm propose(teacher artifact) anddtaifm review(combined audit) - Rule provenance fields (
proposed_by,proposal_id,created_at,rationale) - Teacher registry (adapter slot — no provider deps in core)
-
TeacherRequest/TeacherResponse/PromptContextprovider-neutral contract -
dtaifm prompt— inspect adapter input with no API key - Strict provider response parsing (
ProviderResponseError) - Anthropic Claude teacher adapter (optional extra)
- Domain pack abstraction with registry (
smart_home,network_automation) - Domain-aware validator (rejects out-of-vocabulary triggers/conditions/actions)
- Runtime defense-in-depth: refuses actions outside the active domain
- Domain-aware prompts (every adapter receives the domain's vocabulary)
- Replayable audit bundles (
.dtaifm-review.json) with canonical-JSON SHA-256 hashes -
dtaifm replayanddtaifm inspectcommands; public Python API (review,replay,inspect_bundle) - Tamper detection across inputs, stored results, and recomputed results
- Local teacher adapters:
ollamaandlemonade(no API keys, no extra deps) -
dtaifm teachers/dtaifm teachers --checkconnectivity diagnostics - Deterministic feedback artifacts (
dtaifm feedback) — validation-only, no execution - Reproposal loop (
dtaifm repropose) — teachers consume named violations through the sameTeacherRequestcontract; the revision is written but not validated/executed - OpenAI teacher adapter (optional extra)
- Persistent audit log of every propose → validate → execute cycle
- Telecom / network automation example
- Rust/WASM deterministic runtime
Contributing
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.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dtaifm-0.1.1.tar.gz.
File metadata
- Download URL: dtaifm-0.1.1.tar.gz
- Upload date:
- Size: 91.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
68b42503fa5547dc194e9d60ab06b0c5babef00ef0bfe66da7a40f1c39c9a86d
|
|
| MD5 |
1491f141bf0fbfc222667146f47d8579
|
|
| BLAKE2b-256 |
e559c20b5ca6c89c3cf5f6bae2d7f383f58ed4b39fc41d25839ab34f361341b6
|
File details
Details for the file dtaifm-0.1.1-py3-none-any.whl.
File metadata
- Download URL: dtaifm-0.1.1-py3-none-any.whl
- Upload date:
- Size: 64.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
088ce1206bdeac757bcb58417b5c61dbe9ac5e8d0a02f3447fa45ace74dbfd48
|
|
| MD5 |
9a4211848b0922bf47d81523c57c4ac0
|
|
| BLAKE2b-256 |
8b37e6529e38cb5367d197c796cf2e41beaa11fe8f306972647b6b3ae50818fb
|