metis-memory 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 (112) hide show
  1. metis_memory-0.1.0/LICENSE +94 -0
  2. metis_memory-0.1.0/PKG-INFO +205 -0
  3. metis_memory-0.1.0/README.md +167 -0
  4. metis_memory-0.1.0/metis/__init__.py +16 -0
  5. metis_memory-0.1.0/metis/api/__init__.py +6 -0
  6. metis_memory-0.1.0/metis/api/routes.py +167 -0
  7. metis_memory-0.1.0/metis/api/server.py +19 -0
  8. metis_memory-0.1.0/metis/audit/__init__.py +4 -0
  9. metis_memory-0.1.0/metis/audit/export.py +28 -0
  10. metis_memory-0.1.0/metis/audit/replay.py +40 -0
  11. metis_memory-0.1.0/metis/capture/__init__.py +12 -0
  12. metis_memory-0.1.0/metis/capture/confirm.py +46 -0
  13. metis_memory-0.1.0/metis/capture/infer.py +64 -0
  14. metis_memory-0.1.0/metis/capture/loop.py +191 -0
  15. metis_memory-0.1.0/metis/capture/observe.py +43 -0
  16. metis_memory-0.1.0/metis/capture/remember.py +64 -0
  17. metis_memory-0.1.0/metis/capture/whisper.py +73 -0
  18. metis_memory-0.1.0/metis/cli/__init__.py +3 -0
  19. metis_memory-0.1.0/metis/cli/commands/__init__.py +1 -0
  20. metis_memory-0.1.0/metis/cli/commands/audit.py +35 -0
  21. metis_memory-0.1.0/metis/cli/commands/capture.py +22 -0
  22. metis_memory-0.1.0/metis/cli/commands/config.py +22 -0
  23. metis_memory-0.1.0/metis/cli/commands/demo.py +39 -0
  24. metis_memory-0.1.0/metis/cli/commands/fragment.py +32 -0
  25. metis_memory-0.1.0/metis/cli/commands/init.py +23 -0
  26. metis_memory-0.1.0/metis/cli/commands/memory.py +55 -0
  27. metis_memory-0.1.0/metis/cli/commands/model.py +55 -0
  28. metis_memory-0.1.0/metis/cli/commands/retrieve.py +22 -0
  29. metis_memory-0.1.0/metis/cli/commands/workspace.py +15 -0
  30. metis_memory-0.1.0/metis/cli/main.py +52 -0
  31. metis_memory-0.1.0/metis/cli/state.py +98 -0
  32. metis_memory-0.1.0/metis/conditions/__init__.py +4 -0
  33. metis_memory-0.1.0/metis/conditions/context.py +46 -0
  34. metis_memory-0.1.0/metis/conditions/exclusions.py +25 -0
  35. metis_memory-0.1.0/metis/conditions/matcher.py +70 -0
  36. metis_memory-0.1.0/metis/consent/__init__.py +9 -0
  37. metis_memory-0.1.0/metis/consent/contestability.py +27 -0
  38. metis_memory-0.1.0/metis/consent/model.py +61 -0
  39. metis_memory-0.1.0/metis/consent/revocation.py +37 -0
  40. metis_memory-0.1.0/metis/engine.py +118 -0
  41. metis_memory-0.1.0/metis/fragment/__init__.py +15 -0
  42. metis_memory-0.1.0/metis/fragment/events.py +13 -0
  43. metis_memory-0.1.0/metis/fragment/model.py +152 -0
  44. metis_memory-0.1.0/metis/fragment/schema.py +28 -0
  45. metis_memory-0.1.0/metis/fragment/store.py +56 -0
  46. metis_memory-0.1.0/metis/governance/__init__.py +14 -0
  47. metis_memory-0.1.0/metis/governance/authority.py +38 -0
  48. metis_memory-0.1.0/metis/governance/lifecycle.py +296 -0
  49. metis_memory-0.1.0/metis/governance/policy.py +59 -0
  50. metis_memory-0.1.0/metis/governance/runtime_rules.py +14 -0
  51. metis_memory-0.1.0/metis/integrations/__init__.py +1 -0
  52. metis_memory-0.1.0/metis/integrations/chap/__init__.py +20 -0
  53. metis_memory-0.1.0/metis/integrations/chap/adapter.py +337 -0
  54. metis_memory-0.1.0/metis/integrations/chap/artefacts.py +95 -0
  55. metis_memory-0.1.0/metis/integrations/chap/compliance.py +79 -0
  56. metis_memory-0.1.0/metis/integrations/chap/participants.py +54 -0
  57. metis_memory-0.1.0/metis/integrations/chap/review.py +23 -0
  58. metis_memory-0.1.0/metis/memory/__init__.py +17 -0
  59. metis_memory-0.1.0/metis/memory/agent_context.py +59 -0
  60. metis_memory-0.1.0/metis/memory/broker.py +132 -0
  61. metis_memory-0.1.0/metis/memory/episodic.py +35 -0
  62. metis_memory-0.1.0/metis/memory/procedural.py +28 -0
  63. metis_memory-0.1.0/metis/memory/semantic.py +51 -0
  64. metis_memory-0.1.0/metis/memory/tacit.py +153 -0
  65. metis_memory-0.1.0/metis/models/__init__.py +20 -0
  66. metis_memory-0.1.0/metis/models/model_config.py +78 -0
  67. metis_memory-0.1.0/metis/models/ollama_client.py +149 -0
  68. metis_memory-0.1.0/metis/models/prompts.py +41 -0
  69. metis_memory-0.1.0/metis/models/structured_outputs.py +82 -0
  70. metis_memory-0.1.0/metis/py.typed +0 -0
  71. metis_memory-0.1.0/metis/resources.py +46 -0
  72. metis_memory-0.1.0/metis/retrieval/__init__.py +9 -0
  73. metis_memory-0.1.0/metis/retrieval/blocked_reasons.py +33 -0
  74. metis_memory-0.1.0/metis/retrieval/decision.py +40 -0
  75. metis_memory-0.1.0/metis/retrieval/explain.py +31 -0
  76. metis_memory-0.1.0/metis/retrieval/gate.py +167 -0
  77. metis_memory-0.1.0/metis/scenarios.py +279 -0
  78. metis_memory-0.1.0/metis/storage/__init__.py +4 -0
  79. metis_memory-0.1.0/metis/storage/jsonl_store.py +25 -0
  80. metis_memory-0.1.0/metis/storage/sqlite_store.py +71 -0
  81. metis_memory-0.1.0/metis/taxonomy/__init__.py +20 -0
  82. metis_memory-0.1.0/metis/taxonomy/categories.py +176 -0
  83. metis_memory-0.1.0/metis/taxonomy/mapping.py +40 -0
  84. metis_memory-0.1.0/metis/validation/__init__.py +14 -0
  85. metis_memory-0.1.0/metis/validation/mission_group.py +28 -0
  86. metis_memory-0.1.0/metis/validation/promotion.py +22 -0
  87. metis_memory-0.1.0/metis/validation/re_elicitation.py +17 -0
  88. metis_memory-0.1.0/metis/validation/rejection.py +17 -0
  89. metis_memory-0.1.0/metis/validation/states.py +42 -0
  90. metis_memory-0.1.0/metis/validation/tier1.py +27 -0
  91. metis_memory-0.1.0/metis/validation/tier2.py +52 -0
  92. metis_memory-0.1.0/metis_memory.egg-info/PKG-INFO +205 -0
  93. metis_memory-0.1.0/metis_memory.egg-info/SOURCES.txt +110 -0
  94. metis_memory-0.1.0/metis_memory.egg-info/dependency_links.txt +1 -0
  95. metis_memory-0.1.0/metis_memory.egg-info/entry_points.txt +2 -0
  96. metis_memory-0.1.0/metis_memory.egg-info/requires.txt +19 -0
  97. metis_memory-0.1.0/metis_memory.egg-info/top_level.txt +1 -0
  98. metis_memory-0.1.0/pyproject.toml +63 -0
  99. metis_memory-0.1.0/setup.cfg +4 -0
  100. metis_memory-0.1.0/tests/test_agent_context.py +25 -0
  101. metis_memory-0.1.0/tests/test_capture_loop.py +46 -0
  102. metis_memory-0.1.0/tests/test_chap_evidence.py +39 -0
  103. metis_memory-0.1.0/tests/test_chap_integration.py +45 -0
  104. metis_memory-0.1.0/tests/test_consent_revocation.py +46 -0
  105. metis_memory-0.1.0/tests/test_demo.py +26 -0
  106. metis_memory-0.1.0/tests/test_fragment_schema.py +39 -0
  107. metis_memory-0.1.0/tests/test_memory_broker.py +40 -0
  108. metis_memory-0.1.0/tests/test_model_assist_records.py +32 -0
  109. metis_memory-0.1.0/tests/test_ollama_client_mock.py +33 -0
  110. metis_memory-0.1.0/tests/test_retrieval_gate.py +97 -0
  111. metis_memory-0.1.0/tests/test_taxonomy.py +38 -0
  112. metis_memory-0.1.0/tests/test_validation_state_machine.py +63 -0
@@ -0,0 +1,94 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity.
18
+
19
+ "You" (or "Your") shall mean an individual or Legal Entity
20
+ exercising permissions granted by this License.
21
+
22
+ "Source" form shall mean the preferred form for making modifications.
23
+
24
+ "Object" form shall mean any form resulting from mechanical
25
+ transformation or translation of a Source form.
26
+
27
+ "Work" shall mean the work of authorship made available under the
28
+ License, as indicated by a copyright notice that is included in or
29
+ attached to the work.
30
+
31
+ "Derivative Works" shall mean any work that is based on (or derived
32
+ from) the Work.
33
+
34
+ "Contribution" shall mean any work of authorship submitted to
35
+ Licensor for inclusion in the Work.
36
+
37
+ "Contributor" shall mean Licensor and any individual or Legal Entity
38
+ on behalf of whom a Contribution has been received by Licensor and
39
+ subsequently incorporated within the Work.
40
+
41
+ 2. Grant of Copyright License. Subject to the terms and conditions of
42
+ this License, each Contributor hereby grants to You a perpetual,
43
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
44
+ copyright license to reproduce, prepare Derivative Works of,
45
+ publicly display, publicly perform, sublicense, and distribute the
46
+ Work and such Derivative Works in Source or Object form.
47
+
48
+ 3. Grant of Patent License. Subject to the terms and conditions of
49
+ this License, each Contributor hereby grants to You a perpetual,
50
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
51
+ (except as stated in this section) patent license to make, have
52
+ made, use, offer to sell, sell, import, and otherwise transfer the
53
+ Work.
54
+
55
+ 4. Redistribution. You may reproduce and distribute copies of the Work
56
+ or Derivative Works thereof in any medium, with or without
57
+ modifications, provided that You meet the conditions of this License.
58
+
59
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
60
+ any Contribution intentionally submitted for inclusion in the Work
61
+ shall be under the terms and conditions of this License.
62
+
63
+ 6. Trademarks. This License does not grant permission to use the trade
64
+ names, trademarks, service marks, or product names of the Licensor.
65
+
66
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed
67
+ to in writing, Licensor provides the Work on an "AS IS" BASIS,
68
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
69
+ implied.
70
+
71
+ 8. Limitation of Liability. In no event and under no legal theory shall
72
+ any Contributor be liable to You for damages arising from the use of
73
+ the Work.
74
+
75
+ 9. Accepting Warranty or Additional Liability. You may choose to offer,
76
+ and charge a fee for, acceptance of support, warranty, indemnity, or
77
+ other liability obligations and/or rights consistent with this
78
+ License.
79
+
80
+ END OF TERMS AND CONDITIONS
81
+
82
+ Copyright 2026 Metis contributors
83
+
84
+ Licensed under the Apache License, Version 2.0 (the "License");
85
+ you may not use this file except in compliance with the License.
86
+ You may obtain a copy of the License at
87
+
88
+ http://www.apache.org/licenses/LICENSE-2.0
89
+
90
+ Unless required by applicable law or agreed to in writing, software
91
+ distributed under the License is distributed on an "AS IS" BASIS,
92
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
93
+ See the License for the specific language governing permissions and
94
+ limitations under the License.
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: metis-memory
3
+ Version: 0.1.0
4
+ Summary: A CHAP-aligned, local-first toolkit for capturing, validating, governing, and retrieving tacit fragments as governed tacit memory for AI agents.
5
+ Author-email: Brightbeam AI <oss@brightbeam.ai>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/BrightbeamAI/metis
8
+ Project-URL: Documentation, https://github.com/BrightbeamAI/metis/tree/main/docs
9
+ Project-URL: CHAP protocol, https://github.com/BrightbeamAI/chap
10
+ Project-URL: Paper, https://www.preprints.org/manuscript/202608.0927
11
+ Keywords: tacit-knowledge,governance,agentic-ai,memory,chap,audit
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: chap-coordinator>=0.2.9
21
+ Requires-Dist: pydantic>=2.5
22
+ Requires-Dist: typer>=0.9
23
+ Requires-Dist: PyYAML>=6.0
24
+ Requires-Dist: cryptography>=41.0
25
+ Requires-Dist: httpx>=0.24
26
+ Provides-Extra: api
27
+ Requires-Dist: fastapi>=0.110; extra == "api"
28
+ Requires-Dist: uvicorn>=0.23; extra == "api"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7; extra == "dev"
31
+ Requires-Dist: ruff>=0.4; extra == "dev"
32
+ Requires-Dist: jsonschema>=4; extra == "dev"
33
+ Requires-Dist: fastapi>=0.110; extra == "dev"
34
+ Requires-Dist: uvicorn>=0.23; extra == "dev"
35
+ Requires-Dist: build>=1; extra == "dev"
36
+ Requires-Dist: twine>=5; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ <p align="center">
40
+ <img src="docs/assets/brightbeam-logo.png" alt="Brightbeam" width="210">
41
+ </p>
42
+
43
+ <h1 align="center">Metis: Governed Tacit Memory for AI Agents</h1>
44
+
45
+ <p align="center"><b>Capture how expert work actually gets done, govern it, and serve it to AI agents as memory they are allowed to use.</b></p>
46
+
47
+ <p align="center">
48
+ <img src="https://img.shields.io/badge/python-3.10%2B-1f6feb">
49
+ <img src="https://img.shields.io/badge/license-Apache--2.0-2ea043">
50
+ <img src="https://img.shields.io/badge/tests-passing-2ea043">
51
+ <img src="https://img.shields.io/badge/local--first-no%20cloud%20APIs-5A5A5A">
52
+ <img src="https://img.shields.io/badge/built%20on-CHAP-EA4700">
53
+ </p>
54
+
55
+ ---
56
+
57
+ AI agents now act inside real workflows, but the knowledge that makes work go right was often never
58
+ written down. A technician hears a pump sounds wrong before any alarm. An inspector sees a batch
59
+ "looks off" before the lab confirms it. Manuals do not capture this, and naively mining it from
60
+ workers is unsafe and easy to get wrong.
61
+
62
+ Metis is a local-first Python toolkit that captures these moments as **governed tacit
63
+ fragments**, has a human group validate them, and serves only the validated ones to an AI agent,
64
+ under the exact conditions where they hold, with a full audit trail. It implements the governed
65
+ tacit-memory layer from the paper *Tacit Fragments: Operationalising Tacit Knowledge as a Governed
66
+ Memory Layer for Agentic AI*, and it runs on [CHAP](https://github.com/BrightbeamAI/chap) so every step is recorded
67
+ on a hash-linked, replayable evidence chain.
68
+
69
+ <p align="center"><img src="docs/assets/capture_loop.svg" alt="The capture loop" width="100%"></p>
70
+
71
+ ## What is a tacit fragment?
72
+
73
+ Most of what makes someone good at their job never reaches a document. A tacit fragment is a small,
74
+ structured, governed record of one such piece of practice. It is deliberately partial: it is not a
75
+ worker's whole expertise, and it is never treated as fact.
76
+
77
+ Every fragment carries the things that make it safe to reuse:
78
+
79
+ - **what** was observed and worker-confirmed, and its category (the K1 to K17 taxonomy of tacit knowledge),
80
+ - **the conditions** under which it applies (site, equipment, operating mode, shift, role, risk, and exclusions),
81
+ - **where it came from**: provenance, the worker, and their consent,
82
+ - **the evidence** behind it (recurrence, supporting cases, counterexamples),
83
+ - **an authority layer**: Evidence (learning only), Advisory (conditional guidance), or Controlled (formal instruction),
84
+ - **use constraints** that travel with it.
85
+
86
+ That structure is the point. A document chunk has no conditions, consent, or authority, and a
87
+ training example is treated as ground truth. A tacit fragment is neither. It is situated guidance an
88
+ agent may use only where it applies, and must stop using the moment it does not.
89
+
90
+ ## A concrete example
91
+
92
+ A pump SOP says: reduce load only when the alarm threshold is crossed. Experienced operators reduce
93
+ throughput earlier, when high load coincides with low-frequency vibration and a dull acoustic cue.
94
+ Metis captures that gap, a human group promotes it to an advisory cue, and an agent can then use
95
+ it, but only on the right pump in the right state.
96
+
97
+ ```python
98
+ from metis import MetisEngine
99
+ from metis.conditions.context import TacitContext
100
+ from metis.consent.model import ConsentRecord, ConsentStatus
101
+
102
+ eng = MetisEngine() # local and deterministic, no cloud APIs
103
+ eng.join_default_participants()
104
+
105
+ # Capture what the operator does that the SOP does not say.
106
+ frag = eng.capture_observation(
107
+ {
108
+ "observation_id": "OBS-1",
109
+ "work_as_imagined": "Reduce load only when the alarm threshold is crossed.",
110
+ "work_as_done": "Reduce throughput earlier on low-frequency vibration and a dull acoustic cue.",
111
+ "context": TacitContext(equipment_family="centrifugal_pump", operating_mode="high_load"),
112
+ },
113
+ consent=ConsentRecord(consent_status=ConsentStatus.granted),
114
+ category="K7_sensory",
115
+ ).fragment # lands in the Evidence layer, not yet usable
116
+
117
+ # A human Mission Group promotes it. A model never makes this call.
118
+ eng.tier2_review(frag.fragment_id, "promoted_to_advisory", summary="advisory cue only")
119
+
120
+ # An agent asks for guidance. The gate returns it only when the context matches.
121
+ match = TacitContext(equipment_family="centrifugal_pump", operating_mode="high_load", risk_class="moderate")
122
+ other = TacitContext(equipment_family="gear_pump", operating_mode="low_load", risk_class="moderate")
123
+
124
+ print(len(eng.retrieve(match).eligible)) # 1 (returned, with its use constraints)
125
+ print(eng.retrieve(other).blocked[0].reason) # conditions_do_not_match
126
+ ```
127
+
128
+ Change the pump, raise the risk class, or withdraw consent, and the same fragment is withheld with a
129
+ recorded reason. Retrieval is a governance decision, not a similarity search.
130
+
131
+ <p align="center"><img src="docs/assets/retrieval_gate.svg" alt="The condition-aware retrieval gate" width="100%"></p>
132
+
133
+ ## Quickstart
134
+
135
+ No cloud and no GPU. The demo and tests run without any model using deterministic fixtures.
136
+
137
+ ```bash
138
+ git clone https://github.com/BrightbeamAI/metis && cd metis
139
+ pip install -e .
140
+ metis demo manufacturing-pump-vibration
141
+ ```
142
+
143
+ The installable package name is `metis-memory` (import `metis`, CLI `metis`). The official
144
+ [`chap-coordinator`](https://pypi.org/project/chap-coordinator/) dependency installs from PyPI
145
+ automatically.
146
+
147
+ The demo runs the whole flow locally and writes a replayable evidence chain. Inspect it with
148
+ `metis fragment list`, `metis memory list`, `metis retrieve --context <file>`, and
149
+ `metis audit read`.
150
+
151
+ Prefer to click through it? Open the **[interactive demo](docs/demo.html)**: pick a scenario, step
152
+ through the loop, and drive the gate yourself by editing the context and watching it allow or block.
153
+ For a guided tour, open the illustrated **[explainer](docs/explainer.html)**.
154
+
155
+ ## How it works
156
+
157
+ **Capture loop.** Observe a work event, infer a candidate (a hypothesis, never trusted), whisper one
158
+ short bounded question to the worker, confirm with them (descriptive fidelity only), and remember the
159
+ result as an Evidence-layer fragment.
160
+
161
+ **Governance.** A human Mission Group reviews each fragment across fidelity, operational relevance,
162
+ normative alignment, and risk, then promotes it to Advisory or Controlled, or holds, rejects, or
163
+ re-elicits it. Evidence-layer fragments can never drive a decision or reach an agent. A local model
164
+ may draft a review summary, but it never decides.
165
+
166
+ **Memory and retrieval.** A promoted fragment becomes a governed memory object. A broker assembles an
167
+ agent context from procedural, semantic, episodic, and tacit memory, and tacit memory is reached only
168
+ through the condition-aware gate, which carries the use constraints with it.
169
+
170
+ ## Learn more
171
+
172
+ - **[Interactive demo](docs/demo.html)** and **[explainer](docs/explainer.html)**: the fastest way to get it.
173
+ - **[Documentation](docs/README.md)**: architecture, governance, retrieval, the K1 to K17 taxonomy, agent use.
174
+ - **[ABOUT.md](ABOUT.md)**: repository map, the four memory stores, the CHAP relationship, and how to develop.
175
+ - **[CHAP](https://github.com/BrightbeamAI/chap)**: the protocol Metis runs on.
176
+
177
+ ## Ethical use
178
+
179
+ Metis captures fragments of human work. Do not use it for covert worker monitoring. It records no
180
+ audio, video, biometrics, screenshots, or keystrokes, fragments are never treated as fact, and the
181
+ audit chain is append-only. Production use needs worker consultation, legal review, and domain
182
+ validation. Read [ETHICAL_USE.md](ETHICAL_USE.md) first.
183
+
184
+ ## License
185
+
186
+ Apache-2.0. See [LICENSE](LICENSE).
187
+
188
+ ## Citation
189
+
190
+ Metis is the reference implementation for the paper *Tacit Fragments: Operationalising Tacit
191
+ Knowledge as a Governed Memory Layer for Agentic AI*
192
+ ([preprint](https://www.preprints.org/manuscript/202608.0927), also included in this repository as
193
+ [docs/tacit_fragments_preprint.pdf](docs/tacit_fragments_preprint.pdf)). If you use Metis in
194
+ research, please cite:
195
+
196
+ ```bibtex
197
+ @article{shahid2026tacitfragments,
198
+ title = {Tacit Fragments: Operationalising Tacit Knowledge as a Governed Memory Layer for Agentic AI},
199
+ author = {Shahid, Arsalan and Suttie, Gordon and Black, Philip and Garz{\'o}n-Vico, Antonio},
200
+ journal = {Preprints},
201
+ year = {2026},
202
+ doi = {10.20944/preprints202608.0927.v1},
203
+ url = {https://www.preprints.org/manuscript/202608.0927}
204
+ }
205
+ ```
@@ -0,0 +1,167 @@
1
+ <p align="center">
2
+ <img src="docs/assets/brightbeam-logo.png" alt="Brightbeam" width="210">
3
+ </p>
4
+
5
+ <h1 align="center">Metis: Governed Tacit Memory for AI Agents</h1>
6
+
7
+ <p align="center"><b>Capture how expert work actually gets done, govern it, and serve it to AI agents as memory they are allowed to use.</b></p>
8
+
9
+ <p align="center">
10
+ <img src="https://img.shields.io/badge/python-3.10%2B-1f6feb">
11
+ <img src="https://img.shields.io/badge/license-Apache--2.0-2ea043">
12
+ <img src="https://img.shields.io/badge/tests-passing-2ea043">
13
+ <img src="https://img.shields.io/badge/local--first-no%20cloud%20APIs-5A5A5A">
14
+ <img src="https://img.shields.io/badge/built%20on-CHAP-EA4700">
15
+ </p>
16
+
17
+ ---
18
+
19
+ AI agents now act inside real workflows, but the knowledge that makes work go right was often never
20
+ written down. A technician hears a pump sounds wrong before any alarm. An inspector sees a batch
21
+ "looks off" before the lab confirms it. Manuals do not capture this, and naively mining it from
22
+ workers is unsafe and easy to get wrong.
23
+
24
+ Metis is a local-first Python toolkit that captures these moments as **governed tacit
25
+ fragments**, has a human group validate them, and serves only the validated ones to an AI agent,
26
+ under the exact conditions where they hold, with a full audit trail. It implements the governed
27
+ tacit-memory layer from the paper *Tacit Fragments: Operationalising Tacit Knowledge as a Governed
28
+ Memory Layer for Agentic AI*, and it runs on [CHAP](https://github.com/BrightbeamAI/chap) so every step is recorded
29
+ on a hash-linked, replayable evidence chain.
30
+
31
+ <p align="center"><img src="docs/assets/capture_loop.svg" alt="The capture loop" width="100%"></p>
32
+
33
+ ## What is a tacit fragment?
34
+
35
+ Most of what makes someone good at their job never reaches a document. A tacit fragment is a small,
36
+ structured, governed record of one such piece of practice. It is deliberately partial: it is not a
37
+ worker's whole expertise, and it is never treated as fact.
38
+
39
+ Every fragment carries the things that make it safe to reuse:
40
+
41
+ - **what** was observed and worker-confirmed, and its category (the K1 to K17 taxonomy of tacit knowledge),
42
+ - **the conditions** under which it applies (site, equipment, operating mode, shift, role, risk, and exclusions),
43
+ - **where it came from**: provenance, the worker, and their consent,
44
+ - **the evidence** behind it (recurrence, supporting cases, counterexamples),
45
+ - **an authority layer**: Evidence (learning only), Advisory (conditional guidance), or Controlled (formal instruction),
46
+ - **use constraints** that travel with it.
47
+
48
+ That structure is the point. A document chunk has no conditions, consent, or authority, and a
49
+ training example is treated as ground truth. A tacit fragment is neither. It is situated guidance an
50
+ agent may use only where it applies, and must stop using the moment it does not.
51
+
52
+ ## A concrete example
53
+
54
+ A pump SOP says: reduce load only when the alarm threshold is crossed. Experienced operators reduce
55
+ throughput earlier, when high load coincides with low-frequency vibration and a dull acoustic cue.
56
+ Metis captures that gap, a human group promotes it to an advisory cue, and an agent can then use
57
+ it, but only on the right pump in the right state.
58
+
59
+ ```python
60
+ from metis import MetisEngine
61
+ from metis.conditions.context import TacitContext
62
+ from metis.consent.model import ConsentRecord, ConsentStatus
63
+
64
+ eng = MetisEngine() # local and deterministic, no cloud APIs
65
+ eng.join_default_participants()
66
+
67
+ # Capture what the operator does that the SOP does not say.
68
+ frag = eng.capture_observation(
69
+ {
70
+ "observation_id": "OBS-1",
71
+ "work_as_imagined": "Reduce load only when the alarm threshold is crossed.",
72
+ "work_as_done": "Reduce throughput earlier on low-frequency vibration and a dull acoustic cue.",
73
+ "context": TacitContext(equipment_family="centrifugal_pump", operating_mode="high_load"),
74
+ },
75
+ consent=ConsentRecord(consent_status=ConsentStatus.granted),
76
+ category="K7_sensory",
77
+ ).fragment # lands in the Evidence layer, not yet usable
78
+
79
+ # A human Mission Group promotes it. A model never makes this call.
80
+ eng.tier2_review(frag.fragment_id, "promoted_to_advisory", summary="advisory cue only")
81
+
82
+ # An agent asks for guidance. The gate returns it only when the context matches.
83
+ match = TacitContext(equipment_family="centrifugal_pump", operating_mode="high_load", risk_class="moderate")
84
+ other = TacitContext(equipment_family="gear_pump", operating_mode="low_load", risk_class="moderate")
85
+
86
+ print(len(eng.retrieve(match).eligible)) # 1 (returned, with its use constraints)
87
+ print(eng.retrieve(other).blocked[0].reason) # conditions_do_not_match
88
+ ```
89
+
90
+ Change the pump, raise the risk class, or withdraw consent, and the same fragment is withheld with a
91
+ recorded reason. Retrieval is a governance decision, not a similarity search.
92
+
93
+ <p align="center"><img src="docs/assets/retrieval_gate.svg" alt="The condition-aware retrieval gate" width="100%"></p>
94
+
95
+ ## Quickstart
96
+
97
+ No cloud and no GPU. The demo and tests run without any model using deterministic fixtures.
98
+
99
+ ```bash
100
+ git clone https://github.com/BrightbeamAI/metis && cd metis
101
+ pip install -e .
102
+ metis demo manufacturing-pump-vibration
103
+ ```
104
+
105
+ The installable package name is `metis-memory` (import `metis`, CLI `metis`). The official
106
+ [`chap-coordinator`](https://pypi.org/project/chap-coordinator/) dependency installs from PyPI
107
+ automatically.
108
+
109
+ The demo runs the whole flow locally and writes a replayable evidence chain. Inspect it with
110
+ `metis fragment list`, `metis memory list`, `metis retrieve --context <file>`, and
111
+ `metis audit read`.
112
+
113
+ Prefer to click through it? Open the **[interactive demo](docs/demo.html)**: pick a scenario, step
114
+ through the loop, and drive the gate yourself by editing the context and watching it allow or block.
115
+ For a guided tour, open the illustrated **[explainer](docs/explainer.html)**.
116
+
117
+ ## How it works
118
+
119
+ **Capture loop.** Observe a work event, infer a candidate (a hypothesis, never trusted), whisper one
120
+ short bounded question to the worker, confirm with them (descriptive fidelity only), and remember the
121
+ result as an Evidence-layer fragment.
122
+
123
+ **Governance.** A human Mission Group reviews each fragment across fidelity, operational relevance,
124
+ normative alignment, and risk, then promotes it to Advisory or Controlled, or holds, rejects, or
125
+ re-elicits it. Evidence-layer fragments can never drive a decision or reach an agent. A local model
126
+ may draft a review summary, but it never decides.
127
+
128
+ **Memory and retrieval.** A promoted fragment becomes a governed memory object. A broker assembles an
129
+ agent context from procedural, semantic, episodic, and tacit memory, and tacit memory is reached only
130
+ through the condition-aware gate, which carries the use constraints with it.
131
+
132
+ ## Learn more
133
+
134
+ - **[Interactive demo](docs/demo.html)** and **[explainer](docs/explainer.html)**: the fastest way to get it.
135
+ - **[Documentation](docs/README.md)**: architecture, governance, retrieval, the K1 to K17 taxonomy, agent use.
136
+ - **[ABOUT.md](ABOUT.md)**: repository map, the four memory stores, the CHAP relationship, and how to develop.
137
+ - **[CHAP](https://github.com/BrightbeamAI/chap)**: the protocol Metis runs on.
138
+
139
+ ## Ethical use
140
+
141
+ Metis captures fragments of human work. Do not use it for covert worker monitoring. It records no
142
+ audio, video, biometrics, screenshots, or keystrokes, fragments are never treated as fact, and the
143
+ audit chain is append-only. Production use needs worker consultation, legal review, and domain
144
+ validation. Read [ETHICAL_USE.md](ETHICAL_USE.md) first.
145
+
146
+ ## License
147
+
148
+ Apache-2.0. See [LICENSE](LICENSE).
149
+
150
+ ## Citation
151
+
152
+ Metis is the reference implementation for the paper *Tacit Fragments: Operationalising Tacit
153
+ Knowledge as a Governed Memory Layer for Agentic AI*
154
+ ([preprint](https://www.preprints.org/manuscript/202608.0927), also included in this repository as
155
+ [docs/tacit_fragments_preprint.pdf](docs/tacit_fragments_preprint.pdf)). If you use Metis in
156
+ research, please cite:
157
+
158
+ ```bibtex
159
+ @article{shahid2026tacitfragments,
160
+ title = {Tacit Fragments: Operationalising Tacit Knowledge as a Governed Memory Layer for Agentic AI},
161
+ author = {Shahid, Arsalan and Suttie, Gordon and Black, Philip and Garz{\'o}n-Vico, Antonio},
162
+ journal = {Preprints},
163
+ year = {2026},
164
+ doi = {10.20944/preprints202608.0927.v1},
165
+ url = {https://www.preprints.org/manuscript/202608.0927}
166
+ }
167
+ ```
@@ -0,0 +1,16 @@
1
+ """Metis, a reference toolkit for governed tacit fragment capture.
2
+
3
+ Metis captures situated human practice as partial, validated, context-bound fragments
4
+ tied to provenance, consent, authority, review state, and retrieval constraints, and turns
5
+ validated fragments into governed tacit memory objects that AI agents can use alongside
6
+ procedural, semantic, and episodic memory, exposed only through condition-aware governance
7
+ gates. It uses local Ollama/Gemma models for bounded assistance and CHAP as its protocol
8
+ foundation.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ __version__ = "0.1.0"
13
+
14
+ from .engine import MetisEngine
15
+
16
+ __all__ = ["MetisEngine", "__version__"]
@@ -0,0 +1,6 @@
1
+ """Optional local FastAPI server for Metis (install the ``api`` extra)."""
2
+ try:
3
+ from .server import app, create_app
4
+ __all__ = ["app", "create_app"]
5
+ except Exception: # fastapi not installed
6
+ __all__ = []
@@ -0,0 +1,167 @@
1
+ """FastAPI routes over the same engine, models, and evidence logic as the CLI.
2
+
3
+ Optional component (install with the ``api`` extra). Governance remains deterministic and
4
+ human-reviewed; nothing here lets a model or an agent promote or authorise a fragment.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from fastapi import APIRouter, HTTPException
11
+ from pydantic import BaseModel
12
+
13
+ from ..conditions.context import TacitContext
14
+ from ..consent.model import ConsentRecord, ConsentStatus
15
+ from ..consent.revocation import RevocationReason
16
+ from ..engine import MetisEngine
17
+ from ..scenarios import run_manufacturing
18
+
19
+ router = APIRouter()
20
+
21
+ # A single in-memory engine, seeded with the manufacturing scenario for immediate data.
22
+ ENGINE = run_manufacturing().engine
23
+
24
+
25
+ def reset_engine(engine: MetisEngine | None = None) -> None:
26
+ global ENGINE
27
+ ENGINE = engine or run_manufacturing().engine
28
+
29
+
30
+ class CaptureRequest(BaseModel):
31
+ observation_id: str
32
+ work_as_imagined: str | None = None
33
+ work_as_done: str | None = None
34
+ text: str | None = None
35
+ context: dict[str, Any] = {}
36
+ category: str | None = None
37
+ response: str = "confirm"
38
+ corrected_content: str | None = None
39
+ title: str | None = None
40
+
41
+
42
+ class ReviewRequest(BaseModel):
43
+ fragment_id: str
44
+ outcome: str
45
+ summary: str = ""
46
+ change_control: dict[str, Any] | None = None
47
+
48
+
49
+ class ContextRequest(BaseModel):
50
+ context: dict[str, Any]
51
+ role: str | None = None
52
+ task_id: str = "tsk_api_query"
53
+
54
+
55
+ class RevokeRequest(BaseModel):
56
+ fragment_id: str
57
+ reason: str = "retired"
58
+ by: str = "human:reviewer@plant_a"
59
+
60
+
61
+ @router.get("/workspace")
62
+ def get_workspace() -> dict[str, Any]:
63
+ return ENGINE.adapter.descriptor()
64
+
65
+
66
+ @router.get("/fragments")
67
+ def list_fragments() -> list[dict[str, Any]]:
68
+ return [f.model_dump(mode="json") for f in ENGINE.fragments.all()]
69
+
70
+
71
+ @router.get("/fragments/{fragment_id}")
72
+ def get_fragment(fragment_id: str) -> dict[str, Any]:
73
+ frag = ENGINE.fragments.get(fragment_id)
74
+ if not frag:
75
+ raise HTTPException(404, "fragment not found")
76
+ return frag.model_dump(mode="json")
77
+
78
+
79
+ @router.get("/memory")
80
+ def list_memory() -> list[dict[str, Any]]:
81
+ return [m.model_dump(mode="json") for m in ENGINE.tacit_store.all()]
82
+
83
+
84
+ @router.get("/memory/{memory_id}")
85
+ def get_memory(memory_id: str) -> dict[str, Any]:
86
+ m = ENGINE.tacit_store.get(memory_id)
87
+ if not m:
88
+ raise HTTPException(404, "memory object not found")
89
+ return m.model_dump(mode="json")
90
+
91
+
92
+ @router.post("/memory/query")
93
+ def memory_query(req: ContextRequest) -> dict[str, Any]:
94
+ amc = ENGINE.agent_context(req.task_id, TacitContext.model_validate(req.context), role=req.role, emit=True)
95
+ return amc.model_dump(mode="json")
96
+
97
+
98
+ @router.post("/capture")
99
+ def capture(req: CaptureRequest) -> dict[str, Any]:
100
+ consent = ConsentRecord(consent_status=ConsentStatus.granted)
101
+ result = ENGINE.capture_observation(
102
+ dict(observation_id=req.observation_id, work_as_imagined=req.work_as_imagined,
103
+ work_as_done=req.work_as_done, text=req.text,
104
+ context=TacitContext.model_validate(req.context), source="api"),
105
+ consent=consent, response=req.response, corrected_content=req.corrected_content,
106
+ category=req.category, title=req.title,
107
+ conditions=TacitContext.model_validate(req.context))
108
+ return {"fragment": result.fragment.model_dump(mode="json") if result.fragment else None,
109
+ "task_id": result.task_id,
110
+ "model_assist_records": [a.assist_id for a in result.model_assist_records]}
111
+
112
+
113
+ @router.post("/confirm")
114
+ def confirm(req: CaptureRequest) -> dict[str, Any]:
115
+ """Confirmation is captured as part of the loop; this is an alias for /capture."""
116
+ return capture(req)
117
+
118
+
119
+ @router.post("/review")
120
+ def review(req: ReviewRequest) -> dict[str, Any]:
121
+ out = ENGINE.tier2_review(req.fragment_id, req.outcome, summary=req.summary,
122
+ change_control=req.change_control)
123
+ return {"outcome": req.outcome, "memory_id": out.get("memory").memory_id if out.get("memory") else None}
124
+
125
+
126
+ @router.post("/promote")
127
+ def promote(req: ReviewRequest) -> dict[str, Any]:
128
+ if req.outcome not in ("promoted_to_advisory", "promoted_to_controlled"):
129
+ req.outcome = "promoted_to_advisory"
130
+ return review(req)
131
+
132
+
133
+ @router.post("/retrieve")
134
+ def retrieve(req: ContextRequest) -> dict[str, Any]:
135
+ decision = ENGINE.retrieve(TacitContext.model_validate(req.context), role=req.role)
136
+ return decision.model_dump(mode="json")
137
+
138
+
139
+ @router.post("/revoke")
140
+ def revoke(req: RevokeRequest) -> dict[str, Any]:
141
+ art = ENGINE.governance.revoke(req.fragment_id, reason=RevocationReason(req.reason), by=req.by)
142
+ return {"revocation_record_artefact": art}
143
+
144
+
145
+ @router.get("/audit")
146
+ def audit() -> list[dict[str, Any]]:
147
+ return ENGINE.adapter.evidence_records()
148
+
149
+
150
+ @router.post("/audit/export")
151
+ def audit_export(out: str = "evidence.jsonl") -> dict[str, Any]:
152
+ n = ENGINE.export_audit(out)
153
+ return {"exported": n, "path": out, "verified": ENGINE.verify().ok}
154
+
155
+
156
+ @router.get("/model/status")
157
+ def model_status() -> dict[str, Any]:
158
+ c = ENGINE.model_client
159
+ return {"provider": c.config.provider, "model": c.config.name, "url": c.config.url,
160
+ "available": c.available()}
161
+
162
+
163
+ @router.post("/model/run")
164
+ def model_run(prompt: str, purpose: str = "draft_whisper") -> dict[str, Any]:
165
+ res = ENGINE.model_client.run(purpose, prompt)
166
+ return {"used_live_model": res.used_live_model, "output": res.json(),
167
+ "note": "advisory draft only; not a governance decision"}
@@ -0,0 +1,19 @@
1
+ """Local FastAPI server. Run: ``uvicorn metis.api.server:app``. Optional component."""
2
+ from __future__ import annotations
3
+
4
+
5
+ def create_app():
6
+ from fastapi import FastAPI
7
+
8
+ from .routes import router
9
+
10
+ application = FastAPI(
11
+ title="Metis",
12
+ description="Governed tacit fragment capture, local-first, CHAP-aligned. "
13
+ "Tacit memory is exposed only through condition-aware governance gates.",
14
+ version="0.1.0")
15
+ application.include_router(router)
16
+ return application
17
+
18
+
19
+ app = create_app()