rotguard 0.1.0a1__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 (123) hide show
  1. rotguard-0.1.0a1/.github/CODEOWNERS +3 -0
  2. rotguard-0.1.0a1/.github/FUNDING.yml +3 -0
  3. rotguard-0.1.0a1/.github/ISSUE_TEMPLATE/bug_report.yml +23 -0
  4. rotguard-0.1.0a1/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. rotguard-0.1.0a1/.github/ISSUE_TEMPLATE/feature_request.yml +14 -0
  6. rotguard-0.1.0a1/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  7. rotguard-0.1.0a1/.github/SECURITY.md +24 -0
  8. rotguard-0.1.0a1/.github/dependabot.yml +15 -0
  9. rotguard-0.1.0a1/.github/workflows/ci.yml +33 -0
  10. rotguard-0.1.0a1/.github/workflows/nightly-eval.yml +41 -0
  11. rotguard-0.1.0a1/.github/workflows/release.yml +47 -0
  12. rotguard-0.1.0a1/.gitignore +234 -0
  13. rotguard-0.1.0a1/.pre-commit-config.yaml +13 -0
  14. rotguard-0.1.0a1/CHANGELOG.md +184 -0
  15. rotguard-0.1.0a1/CODE_OF_CONDUCT.md +69 -0
  16. rotguard-0.1.0a1/CONTRIBUTING.md +54 -0
  17. rotguard-0.1.0a1/LICENSE +661 -0
  18. rotguard-0.1.0a1/PKG-INFO +371 -0
  19. rotguard-0.1.0a1/README.md +333 -0
  20. rotguard-0.1.0a1/docs/advanced.md +47 -0
  21. rotguard-0.1.0a1/docs/audit.md +76 -0
  22. rotguard-0.1.0a1/docs/cli.md +65 -0
  23. rotguard-0.1.0a1/docs/evaluation.md +110 -0
  24. rotguard-0.1.0a1/docs/index.md +40 -0
  25. rotguard-0.1.0a1/docs/policies.md +104 -0
  26. rotguard-0.1.0a1/docs/quickstart.md +120 -0
  27. rotguard-0.1.0a1/docs/recall.md +126 -0
  28. rotguard-0.1.0a1/evals/__init__.py +0 -0
  29. rotguard-0.1.0a1/evals/baselines.py +121 -0
  30. rotguard-0.1.0a1/evals/core.py +162 -0
  31. rotguard-0.1.0a1/evals/e2e_langchain.py +78 -0
  32. rotguard-0.1.0a1/evals/gate1.py +122 -0
  33. rotguard-0.1.0a1/evals/gate2.py +202 -0
  34. rotguard-0.1.0a1/evals/gate3.py +310 -0
  35. rotguard-0.1.0a1/evals/lc_ollama.py +43 -0
  36. rotguard-0.1.0a1/evals/models.py +220 -0
  37. rotguard-0.1.0a1/evals/nightly.py +97 -0
  38. rotguard-0.1.0a1/evals/rescore.py +28 -0
  39. rotguard-0.1.0a1/evals/results/.gitkeep +0 -0
  40. rotguard-0.1.0a1/evals/run.py +76 -0
  41. rotguard-0.1.0a1/evals/suites/__init__.py +11 -0
  42. rotguard-0.1.0a1/evals/suites/_util.py +14 -0
  43. rotguard-0.1.0a1/evals/suites/goal_drift.py +43 -0
  44. rotguard-0.1.0a1/evals/suites/holdout.py +118 -0
  45. rotguard-0.1.0a1/evals/suites/holdout2.py +72 -0
  46. rotguard-0.1.0a1/evals/suites/planted_facts.py +51 -0
  47. rotguard-0.1.0a1/evals/suites/superseded.py +57 -0
  48. rotguard-0.1.0a1/mkdocs.yml +22 -0
  49. rotguard-0.1.0a1/pyproject.toml +83 -0
  50. rotguard-0.1.0a1/refix.md +577 -0
  51. rotguard-0.1.0a1/schemas/README.md +9 -0
  52. rotguard-0.1.0a1/schemas/audit_event.schema.json +96 -0
  53. rotguard-0.1.0a1/schemas/ledger_entry.schema.json +89 -0
  54. rotguard-0.1.0a1/schemas/message.schema.json +209 -0
  55. rotguard-0.1.0a1/schemas/recall_event.schema.json +89 -0
  56. rotguard-0.1.0a1/schemas/report.schema.json +348 -0
  57. rotguard-0.1.0a1/src/rotguard/__init__.py +1 -0
  58. rotguard-0.1.0a1/src/rotguard/adapters/__init__.py +0 -0
  59. rotguard-0.1.0a1/src/rotguard/adapters/anthropic.py +194 -0
  60. rotguard-0.1.0a1/src/rotguard/adapters/langchain.py +532 -0
  61. rotguard-0.1.0a1/src/rotguard/adapters/langgraph.py +118 -0
  62. rotguard-0.1.0a1/src/rotguard/adapters/mcp.py +95 -0
  63. rotguard-0.1.0a1/src/rotguard/adapters/openai_agents.py +129 -0
  64. rotguard-0.1.0a1/src/rotguard/cli/__init__.py +0 -0
  65. rotguard-0.1.0a1/src/rotguard/cli/__main__.py +3 -0
  66. rotguard-0.1.0a1/src/rotguard/cli/callers.py +71 -0
  67. rotguard-0.1.0a1/src/rotguard/cli/main.py +120 -0
  68. rotguard-0.1.0a1/src/rotguard/core/__init__.py +21 -0
  69. rotguard-0.1.0a1/src/rotguard/core/messages.py +74 -0
  70. rotguard-0.1.0a1/src/rotguard/core/observer.py +68 -0
  71. rotguard-0.1.0a1/src/rotguard/handoff.py +89 -0
  72. rotguard-0.1.0a1/src/rotguard/ledger/__init__.py +3 -0
  73. rotguard-0.1.0a1/src/rotguard/ledger/ledger.py +80 -0
  74. rotguard-0.1.0a1/src/rotguard/log.py +38 -0
  75. rotguard-0.1.0a1/src/rotguard/policies/__init__.py +5 -0
  76. rotguard-0.1.0a1/src/rotguard/policies/dedup.py +44 -0
  77. rotguard-0.1.0a1/src/rotguard/policies/offload.py +87 -0
  78. rotguard-0.1.0a1/src/rotguard/policies/staged.py +113 -0
  79. rotguard-0.1.0a1/src/rotguard/probes/__init__.py +27 -0
  80. rotguard-0.1.0a1/src/rotguard/probes/audit.py +173 -0
  81. rotguard-0.1.0a1/src/rotguard/probes/caller.py +17 -0
  82. rotguard-0.1.0a1/src/rotguard/probes/extract.py +118 -0
  83. rotguard-0.1.0a1/src/rotguard/probes/pool.py +39 -0
  84. rotguard-0.1.0a1/src/rotguard/probes/quiz.py +413 -0
  85. rotguard-0.1.0a1/src/rotguard/probes/recall.py +179 -0
  86. rotguard-0.1.0a1/src/rotguard/probes/signals.py +73 -0
  87. rotguard-0.1.0a1/src/rotguard/replay.py +72 -0
  88. rotguard-0.1.0a1/src/rotguard/report/__init__.py +3 -0
  89. rotguard-0.1.0a1/src/rotguard/report/html.py +177 -0
  90. rotguard-0.1.0a1/src/rotguard/report/models.py +96 -0
  91. rotguard-0.1.0a1/src/rotguard/report/query.py +62 -0
  92. rotguard-0.1.0a1/src/rotguard/schemas.py +43 -0
  93. rotguard-0.1.0a1/src/rotguard/session.py +343 -0
  94. rotguard-0.1.0a1/src/rotguard/store/__init__.py +10 -0
  95. rotguard-0.1.0a1/src/rotguard/store/protocol.py +32 -0
  96. rotguard-0.1.0a1/src/rotguard/store/sqlite.py +123 -0
  97. rotguard-0.1.0a1/src/rotguard/telemetry.py +92 -0
  98. rotguard-0.1.0a1/src/rotguard/testing.py +61 -0
  99. rotguard-0.1.0a1/src/rotguard/tokens.py +67 -0
  100. rotguard-0.1.0a1/tests/test_anthropic.py +214 -0
  101. rotguard-0.1.0a1/tests/test_audit.py +395 -0
  102. rotguard-0.1.0a1/tests/test_baselines.py +111 -0
  103. rotguard-0.1.0a1/tests/test_cli.py +123 -0
  104. rotguard-0.1.0a1/tests/test_extract.py +119 -0
  105. rotguard-0.1.0a1/tests/test_gate1.py +49 -0
  106. rotguard-0.1.0a1/tests/test_gate2.py +15 -0
  107. rotguard-0.1.0a1/tests/test_gate3.py +114 -0
  108. rotguard-0.1.0a1/tests/test_handoff.py +101 -0
  109. rotguard-0.1.0a1/tests/test_harness.py +85 -0
  110. rotguard-0.1.0a1/tests/test_langchain.py +467 -0
  111. rotguard-0.1.0a1/tests/test_langchain_policies.py +215 -0
  112. rotguard-0.1.0a1/tests/test_langgraph.py +109 -0
  113. rotguard-0.1.0a1/tests/test_ledger.py +34 -0
  114. rotguard-0.1.0a1/tests/test_mcp.py +58 -0
  115. rotguard-0.1.0a1/tests/test_nightly.py +47 -0
  116. rotguard-0.1.0a1/tests/test_observer_report.py +81 -0
  117. rotguard-0.1.0a1/tests/test_openai_agents.py +115 -0
  118. rotguard-0.1.0a1/tests/test_policies.py +201 -0
  119. rotguard-0.1.0a1/tests/test_recall.py +307 -0
  120. rotguard-0.1.0a1/tests/test_schemas.py +30 -0
  121. rotguard-0.1.0a1/tests/test_store_protocol.py +59 -0
  122. rotguard-0.1.0a1/tests/test_telemetry.py +174 -0
  123. rotguard-0.1.0a1/uv.lock +3586 -0
@@ -0,0 +1,3 @@
1
+ # Every file in the repo requires review/approval from an owner before merge.
2
+ # See CONTRIBUTING.md for the full PR workflow.
3
+ * @nikhilvdev
@@ -0,0 +1,3 @@
1
+ # These are supported funding model platforms
2
+
3
+ custom: ["https://buymeacoffee.com/nikhilvdev"]
@@ -0,0 +1,23 @@
1
+ name: Bug report
2
+ description: Something in rotguard behaves wrongly
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: What happened, and what did you expect?
9
+ validations:
10
+ required: true
11
+ - type: textarea
12
+ id: repro
13
+ attributes:
14
+ label: Minimal reproduction
15
+ description: Ideally a short script. For middleware issues, include your `middleware=[...]` list in order.
16
+ render: python
17
+ - type: input
18
+ id: versions
19
+ attributes:
20
+ label: Versions
21
+ description: rotguard, langchain, Python
22
+ validations:
23
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Report a security vulnerability
4
+ url: https://github.com/nikhilvdev/rotguard/security/advisories/new
5
+ about: Please don't file security vulnerabilities as public issues. Use private reporting instead.
@@ -0,0 +1,14 @@
1
+ name: Feature request
2
+ description: Suggest an improvement
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What problem are you trying to solve?
9
+ validations:
10
+ required: true
11
+ - type: textarea
12
+ id: idea
13
+ attributes:
14
+ label: What would you like rotguard to do?
@@ -0,0 +1,19 @@
1
+ ## Summary
2
+
3
+ <!-- What does this change, and why? -->
4
+
5
+ ## Checklist
6
+
7
+ - [ ] `ruff check .` passes
8
+ - [ ] `mypy` passes (`--strict`)
9
+ - [ ] `pytest` passes, and new surface area has test coverage
10
+ - [ ] Public API additions are documented in `README.md` with a runnable example
11
+ - [ ] `CHANGELOG.md` has an entry under `Unreleased`
12
+ - [ ] If this changes the message model or ledger entry shape, `schemas/` is regenerated
13
+ (`uv run python -m rotguard.schemas`)
14
+ - [ ] Any claim about quality or cost is backed by a number from `evals/`
15
+
16
+ ## Scope
17
+
18
+ <!-- One concern per PR where possible. Call out here if this
19
+ intentionally spans more than one. -->
@@ -0,0 +1,24 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ `rotguard` is pre-release. Security fixes target the latest released version only. Please upgrade
6
+ before reporting an issue to confirm it is still present.
7
+
8
+ ## Reporting a Vulnerability
9
+
10
+ **Please do not open a public GitHub issue for security vulnerabilities.**
11
+
12
+ Use GitHub's private vulnerability reporting instead:
13
+ [Report a vulnerability](https://github.com/nikhilvdev/rotguard/security/advisories/new)
14
+ (the "Security" tab of this repository, then "Report a vulnerability"). This opens a private
15
+ advisory visible only to maintainers until a fix is ready.
16
+
17
+ Please include:
18
+
19
+ - A description of the vulnerability and its potential impact
20
+ - Steps to reproduce, or a minimal proof-of-concept
21
+ - The affected version(s)
22
+
23
+ Note that rotguard stores message text in a local SQLite file (ledger entries, provenance, and the
24
+ text of context items). Treat that file as sensitive as the conversations it came from.
@@ -0,0 +1,15 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ commit-message:
8
+ prefix: "chore"
9
+
10
+ - package-ecosystem: "github-actions"
11
+ directory: "/"
12
+ schedule:
13
+ interval: "weekly"
14
+ commit-message:
15
+ prefix: "chore"
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v7
18
+
19
+ - uses: astral-sh/setup-uv@v7
20
+
21
+ - run: uv python install ${{ matrix.python-version }}
22
+
23
+ - run: >-
24
+ uv sync --python ${{ matrix.python-version }}
25
+ --extra langchain --extra anthropic --extra openai --extra mcp
26
+
27
+ - run: uv run ruff check .
28
+
29
+ - run: uv run ruff format --check .
30
+
31
+ - run: uv run mypy
32
+
33
+ - run: uv run pytest
@@ -0,0 +1,41 @@
1
+ name: Nightly eval
2
+
3
+ # A small fixed eval (one scenario per suite, one seed) on a cheap hosted model. It catches a
4
+ # break in goal retention or audit validity, not a small drift. It costs money on every run.
5
+ # Setup: add a repository secret ANTHROPIC_API_KEY. To record the first baseline, run this
6
+ # workflow by hand with "write_baseline" set, then commit evals/nightly_baseline.json.
7
+
8
+ on:
9
+ schedule:
10
+ - cron: "0 3 * * *"
11
+ workflow_dispatch:
12
+ inputs:
13
+ write_baseline:
14
+ description: "Record today's numbers as the baseline instead of comparing"
15
+ type: boolean
16
+ default: false
17
+
18
+ jobs:
19
+ eval:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v7
23
+
24
+ - uses: astral-sh/setup-uv@v7
25
+
26
+ - run: uv python install 3.12
27
+
28
+ - run: uv sync --python 3.12 --extra langchain --extra anthropic --extra eval
29
+
30
+ - name: Run the nightly eval
31
+ env:
32
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
33
+ run: >-
34
+ uv run python -m evals.nightly --model anthropic:claude-haiku-5-5 --out nightly-results
35
+ ${{ inputs.write_baseline && '--write-baseline' || '' }}
36
+
37
+ - uses: actions/upload-artifact@v4
38
+ if: always()
39
+ with:
40
+ name: nightly-results
41
+ path: nightly-results/
@@ -0,0 +1,47 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI when a version tag such as v0.1.0a1 is pushed.
4
+ # Uses PyPI trusted publishing (OIDC): no API token is stored in this repository.
5
+ # One-time setup on pypi.org: add a (pending) trusted publisher for project "rotguard" with
6
+ # owner "nikhilvdev", repository "rotguard", workflow "release.yml" and environment "pypi".
7
+
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v7
17
+
18
+ - uses: astral-sh/setup-uv@v7
19
+
20
+ - name: The tag must match the version in pyproject.toml
21
+ run: |
22
+ version=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
23
+ test "v$version" = "$GITHUB_REF_NAME" || {
24
+ echo "tag $GITHUB_REF_NAME does not match version $version"; exit 1; }
25
+
26
+ - run: uv build
27
+
28
+ - run: uvx twine check dist/*
29
+
30
+ - uses: actions/upload-artifact@v4
31
+ with:
32
+ name: dist
33
+ path: dist/
34
+
35
+ publish:
36
+ needs: build
37
+ runs-on: ubuntu-latest
38
+ environment: pypi
39
+ permissions:
40
+ id-token: write # required for trusted publishing
41
+ steps:
42
+ - uses: actions/download-artifact@v4
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+
47
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,234 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ *.lcov
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ # Pipfile.lock
97
+
98
+ # uv
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ # uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ # poetry.lock
110
+ # poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
115
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
116
+ # pdm.lock
117
+ # pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # pixi
122
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
123
+ # pixi.lock
124
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
125
+ # in the .venv directory. It is recommended not to include this directory in version control.
126
+ .pixi/*
127
+ !.pixi/config.toml
128
+
129
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
130
+ __pypackages__/
131
+
132
+ # Celery stuff
133
+ celerybeat-schedule*
134
+ celerybeat.pid
135
+
136
+ # Redis
137
+ *.rdb
138
+ *.aof
139
+ *.pid
140
+
141
+ # RabbitMQ
142
+ mnesia/
143
+ rabbitmq/
144
+ rabbitmq-data/
145
+
146
+ # ActiveMQ
147
+ activemq-data/
148
+
149
+ # SageMath parsed files
150
+ *.sage.py
151
+
152
+ # Environments
153
+ .env
154
+ .envrc
155
+ .venv
156
+ env/
157
+ venv/
158
+ ENV/
159
+ env.bak/
160
+ venv.bak/
161
+
162
+ # Spyder project settings
163
+ .spyderproject
164
+ .spyproject
165
+
166
+ # Rope project settings
167
+ .ropeproject
168
+
169
+ # mkdocs/Zensical documentation
170
+ /site
171
+
172
+ # mypy
173
+ .mypy_cache/
174
+ .dmypy.json
175
+ dmypy.json
176
+
177
+ # Pyre type checker
178
+ .pyre/
179
+
180
+ # pytype static type analyzer
181
+ .pytype/
182
+
183
+ # Cython debug symbols
184
+ cython_debug/
185
+
186
+ # PyCharm
187
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
188
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
189
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
190
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
191
+ # .idea/
192
+
193
+ # Abstra
194
+ # Abstra is an AI-powered process automation framework.
195
+ # Ignore directories containing user credentials, local state, and settings.
196
+ # Learn more at https://abstra.io/docs
197
+ .abstra/
198
+
199
+ # Visual Studio Code
200
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
201
+ # can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
202
+ # and can be added to the global gitignore or merged into this file. However, if you prefer, you
203
+ # could uncomment the following to ignore the entire vscode folder
204
+ # .vscode/
205
+ # Temporary file for partial code execution
206
+ tempCodeRunnerFile.py
207
+
208
+ # Ruff stuff:
209
+ .ruff_cache/
210
+
211
+ # PyPI configuration file
212
+ .pypirc
213
+
214
+ # Marimo
215
+ marimo/_static/
216
+ marimo/_lsp/
217
+ __marimo__/
218
+
219
+ # Streamlit
220
+ .streamlit/secrets.toml
221
+
222
+ # eval outputs
223
+ evals/results/*
224
+ !evals/results/.gitkeep
225
+ rotguard.md
226
+ # Claude Code project notes: kept local, never committed
227
+ CLAUDE.md
228
+ .claude
229
+ graphify-out/
230
+ .agents
231
+ # editor and agent-tool files
232
+ .vscode/
233
+ skills-lock.json
234
+ .agents/
@@ -0,0 +1,13 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.6.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/pre-commit/pre-commit-hooks
10
+ rev: v4.6.0
11
+ hooks:
12
+ - id: trailing-whitespace
13
+ - id: end-of-file-fixer
@@ -0,0 +1,184 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ ## Unreleased
6
+
7
+ - `refix.md` work, M1 to M4 (code and tests; nothing here has run on a hosted model):
8
+ - **Recall probe redesign (R1 to R4).** The default probe (`style="cued"`) asks one
9
+ fill-in-the-blank question per entry instead of one "list everything" question (`style="list"`
10
+ keeps the old one). `target="auto"` asks about facts from the conversation that the ledger does
11
+ not cover when the ledger is shown to the model on every call, and about the ledger otherwise;
12
+ the probed context now includes the injected ledger note and marker, as the model sees them.
13
+ A session-long `FactPool` keeps conversation facts after their source is compacted away.
14
+ Answers that give a replaced value are `stale` and count as a miss. The Gate 3 harness now
15
+ compounds compaction, runs a no-compaction control arm, reports calibrated `pass_at`, per-fact
16
+ lead and overhead, and compares styles and targets on identical simulations.
17
+ - **Result (qwen2.5 3B, `holdout`, 30 turns, 1 seed, 9 scenarios).** Recall score before any
18
+ compaction went from 0.03 (old probe) to 0.82 (cued), meeting the 0.7 bar, and false alarms in
19
+ control runs whose task passed went from 6 of 6 to 0 of 6. Early warning did **not** follow: the
20
+ alert came at or after the task failure in 7 of 8 failing runs (median lead -1 turn), with one
21
+ 10-turn lead. The `history` target scored 0 of 510 turns because the oracle ledger covers every
22
+ planted fact. Gate 3 is still not met. Details in `docs/evaluation.md`.
23
+ - **Policies (F5, F4a).** `OffloadPolicy` stores large tool outputs and leaves a stub with the same
24
+ `tool_call_id`, read back with `recall_output`; `DedupPolicy` collapses repeated outputs in the
25
+ request only; `Session.pin` / `RotguardMiddleware.pin` / `ledger_record(pin=True)` keep a message
26
+ through cleanup and re-attach it if another tool compacted it away; `StagedCleanup` clears old
27
+ tool outputs first and summarizes only if still over budget (stage 2 turns itself off when another
28
+ summarizer is active); `RecallPolicy(action="compact")` runs it once on an alert. A tail that
29
+ must start on a user message is no longer required when a summary leads (a pure tool loop could
30
+ never be summarized before). `TokenCounter` uses a provider's counting API when given one and
31
+ otherwise estimates characters / 4 and says so.
32
+ - **Ledger extractor (F1x).** `LedgerExtractor` proposes entries from each reply; an entry must
33
+ share a distinctive token with the conversation, `confirm` can veto it, and entries carry
34
+ `origin` (`tool`, `manual`, `extractor`).
35
+ - **Architecture (A1 to A3).** Every stored event is also emitted as an OpenTelemetry span event
36
+ when `rotguard[otel]` is installed (text excluded unless `telemetry.configure(include_text=True)`);
37
+ a `Store` protocol with `open_store`; JSON schemas for the report and the stored `audit` and
38
+ `recall` events, validated against the real producers.
39
+ - **Evaluation tooling (E1, E2, E7, E8).** Eval clients count calls and tokens by purpose
40
+ (provider-reported where available) and every entry point has `--estimate`; baselines
41
+ `lc-summarize`, `anthropic-compaction` and `rg-staged`; stale-decision rate per result;
42
+ `assert_history_integrity`; a nightly workflow and `evals/nightly.py`. `anthropic-compaction`
43
+ was written from the SDK's type definitions and tested against a fake client, **not the live
44
+ API**. ContextForge is not wired in.
45
+ - Tests: 158 pass (99 before), ruff and strict mypy clean.
46
+
47
+ - License changed again, to a dual license: GNU AGPL-3.0 (`AGPL-3.0-only`, the full text in
48
+ `LICENSE`) for everyone, plus a commercial license from the author for those who can't or won't
49
+ comply with the AGPL. This replaces the PolyForm Noncommercial license below, which kept company
50
+ teams from even trying the audit in CI. Copies obtained under MIT or PolyForm keep those terms.
51
+ `CONTRIBUTING.md` now says contributions may be offered under both.
52
+ - Housekeeping: `ruff format --check` added to CI; `.vscode/` and `skills-lock.json` are no longer
53
+ tracked; references to the private plan file replaced; package metadata (URLs, keywords,
54
+ classifiers) and a tag-triggered release workflow added in preparation for a first PyPI alpha.
55
+
56
+ - (Superseded by the entry above.) License changed from MIT to the PolyForm Noncommercial License 1.0.0 (`LICENSE`, `pyproject.toml`
57
+ SPDX metadata, README, CONTRIBUTING). Noncommercial use is free; commercial use needs a separate
58
+ license. This is a source-available license, not an OSI open source one. Copies already obtained
59
+ under MIT keep MIT.
60
+
61
+ - Phase 4 (pre-release, in progress): CLI and MCP server.
62
+ - `rotguard` command (`sessions`, `report`, `blame`, `replay`, `mcp`) and `python -m rotguard.cli`.
63
+ - `report`: one self-contained HTML page per session (recall curve with compactions marked,
64
+ compaction and audit tables, ledger timeline); all stored text is escaped.
65
+ - `replay`: rebuilds each turn's context from a saved session and re-runs only rotguard's side
66
+ calls (audit, recall probes) under another model or policy (`--model anthropic|openai|ollama:`,
67
+ `--audit`, `--recall-every`, `--pass-at`, `--inject-ledger`); the agent's own calls are not
68
+ re-run. The source trace is untouched; results go to a new session.
69
+ - `rotguard.adapters.mcp` (`rotguard[mcp]`, needs `mcp>=2`): tools `ledger_record`, `recall`,
70
+ `blame`, `report` over stdio. The agent loads the ledger at start, so entries added here reach
71
+ it on its next run.
72
+ - `SQLiteStore.sessions()`.
73
+ - Fresh-context handoff (F7): `rotguard.handoff.handoff` builds system messages, a ledger note
74
+ and the last `keep` messages, widened so the tail starts on a user message and every tool
75
+ result keeps its call. `RecallPolicy(action="handoff", handoff_keep=)`: when an alert fires,
76
+ `RotguardMiddleware` and `RotguardGraph` replace the history on the next model call (the only
77
+ place rotguard rewrites stored history, and opt-in). It waits while it would not shrink the
78
+ context, and a passing probe cancels it. The Anthropic and OpenAI wrappers do not own the
79
+ history; call `session.take_handoff(context)` and replace it yourself.
80
+ - Tests: 99 pass (78 before Phase 4), ruff and strict mypy clean. The stdio server was checked
81
+ with a raw `initialize` and `tools/list` exchange. The provider callers (`anthropic`, `openai`,
82
+ `ollama`) are not exercised by tests; only their message shaping is.
83
+ - Docs: a MkDocs Material site in `docs/` (home, quickstart, audit, recall and handoff, CLI and
84
+ MCP, evaluation), built with `uv sync --group docs && uv run mkdocs build --strict`. The
85
+ Evaluation page restates the measured results and says Gate 3 is unmet. Install instructions say
86
+ "from source" because the package is not on PyPI.
87
+ - Not done: benchmark report and launch posts. Gate 3 is
88
+ not met (see Phase 3), so the early-warning claim must not be in launch material.
89
+
90
+ - Phase 3 (pre-release): recall checks.
91
+ - `rotguard.probes.recall`: tier 3 recall probes. Every `every` turns a side call over the
92
+ context the agent would use asks the model to restate sampled active ledger entries, graded
93
+ per entry; `RecallMonitor` keeps a rolling score and alerts after `alert_after` failed probes
94
+ in a row, never on one miss. `calibrate()` picks a per-model `pass_at` from clean-context
95
+ probes. Off by default; probes never enter history.
96
+ - `rotguard.probes.signals`: tier 1 passive signals (goal-word overlap, repeated replies, tool
97
+ error rate), no model call. A flag runs a probe now instead of on schedule. Overlap is lexical,
98
+ not embedding based.
99
+ - Tier 2 marker: `RecallPolicy(marker=...)` adds a standing instruction to the injected ledger
100
+ note; `Session.observe_reply` forces a probe when a reply lacks it. LangChain and LangGraph
101
+ only (`RotguardGraph.observe_reply`); the Anthropic and OpenAI wrappers inject nothing.
102
+ - `Session(recall=, on_alert=)`; `recall=` and `on_alert=` on `RotguardMiddleware`,
103
+ `RotguardGraph` and `RotguardAnthropic`. Probes run on the audit's background worker.
104
+ - `rotguard.adapters.openai_agents.RotguardSession`: wraps any Agents SDK session (trimming or
105
+ summarizing), sees compactions at `get_items`, audits and probes through `responses.create`.
106
+ Duck-typed; tested against a fake session, not the real SDK.
107
+ - `evals/gate3.py`: replays a scenario turn by turn and reports the lead of the recall alert
108
+ over the first task failure, misses, false alarms and probe calls per turn.
109
+ - Test status (2026-10-19, branch `feat/phase-3-recall-checks`): 78 tests pass (was 60 before
110
+ Phase 3; the new ones cover recall, signals, the marker, all four adapters and the Gate 3
111
+ summary); ruff, ruff format and strict mypy are clean. All tests use fake models. The Agents
112
+ SDK wrapper is tested against a fake session, not the real SDK. Gate 3 on a real model: see
113
+ the Gate 3 entry below and `docs/evaluation.md`.
114
+ - Gate 3, first run (qwen2.5 3B, `holdout`, 30 turns, 1 seed, 9 scenarios): not met. The recall
115
+ score before any compaction averaged 0.11, so the alert fired at turn 1 in 8 of 9 runs; 3 of 9
116
+ task probes failed before any compaction. The probe is not usable on this model as it stands,
117
+ and the "lead over task failure" is not evidence of early warning. No hosted-model run yet.
118
+
119
+ - Audit hardening after Phase 2 (uncommitted work on top of PR #2):
120
+ - Long extracted answers are graded on their distinctive tokens (identifiers and numbers), not
121
+ as an exact sentence. A cloze fallback (`AuditPolicy.cloze`, default 2) builds facts straight
122
+ from user and agent text when extraction finds fewer than `max_facts`: a sentence with an
123
+ identifier or number is blanked and asked back. It needs no model call and works on models too
124
+ weak to return the extraction JSON.
125
+ - Fix: a quiz question that contained another fact's answer let the model copy it from the
126
+ prompt, so a lost fact read as kept. Such facts are now dropped (`drop_leaky`). Matching is
127
+ whole-token (`45` no longer matches inside `ref4558`). Both have regression tests.
128
+ - `loss` now scores facts that did not come from tool output; `loss_all` keeps the old definition.
129
+ - Evidence (qwen2.5 3B, 30 turns; one run per cell, so treat as indicative): on the tuning suites
130
+ AUC rose from 0.61 to 0.79. A first held-out set then exposed the leakage bug above (AUC 0.55,
131
+ one "kept" verdict followed by a failure). After the fix, a second held-out set of 9 new
132
+ templates, never run before the configuration was fixed, gave AUC 0.90 (18 scored scenarios),
133
+ "kept" 0 failures of 8, "lost" 8 failures of 10. The 1.5B model now runs the audit (24 of 30
134
+ scenarios scored, from 1) but fails 24 of 27 probes, so its AUC (0.63) says little.
135
+ - Not fixed: coverage. The audit never checked the probed fact in 5 of the 3B's 13 failures
136
+ on the second held-out set, and in 11 of the 1.5B's 24. Nine templates, three seeds each: the
137
+ effective sample is nine, so the p-values are optimistic.
138
+ - Coverage: `run_audit` keeps at most one tool-origin fact, so small models no longer spend the
139
+ fact slots on tool listings and the cloze fallback can fill them. qwen2.5 3B, 30 turns, 3 seeds,
140
+ `holdout` + `holdout2` (54 scenarios, one run per cell): probed fact checked in 39 of 54 (was
141
+ 33), AUC 0.96 over 35 scored (was 0.90 over 31). These suites informed this change, so they are
142
+ no longer clean held-out sets.
143
+ - `evals`: two held-out suites (`holdout`, `holdout2`, selected by name only), `--cloze`,
144
+ `ROTGUARD_OPENAI_API_KEY` for hosted runs (a placeholder `OPENAI_API_KEY` is rejected).
145
+
146
+ - Phase 2 (pre-release): compaction audit.
147
+ - `rotguard.probes`: the audit. Facts come from the removed span (extracted by the model, kept
148
+ only if they appear in it literally) and from the ledger; the same quiz is asked of the old and
149
+ the new context and graded as text. Report: loss, facts lost, and suggestions. Policy modes
150
+ `off` / `sampled` / `every` / `ci`, with a per-session budget. Opt-in.
151
+ - `rotguard.session.Session`: observation, store writes and audit scheduling shared by all
152
+ adapters (audits run on a background thread, or synchronously in `ci` mode).
153
+ - `RotguardMiddleware(audit=...)`; in-place context-editing clearing is audited once.
154
+ - `rotguard.adapters.anthropic.RotguardAnthropic`: recognises threshold and on-demand
155
+ compaction blocks, audits them, and sends side calls with the request's tools and betas.
156
+ - `rotguard.adapters.langgraph.RotguardGraph`: a node, a ledger tool, and a ledger kept in graph
157
+ state so checkpointers persist it.
158
+ - `rotguard.testing.assert_audit_loss` for CI.
159
+ - `evals/gate2.py`: does the loss score predict later probe failures? Result on local models
160
+ (qwen2.5 3B, qwen2.5-coder 7B, 30 turns, 50 scenarios, naive summarizer): the aggregate `loss`
161
+ does not predict probe failure (AUC 0.61 and 0.44). Two exploratory follow-ups: excluding
162
+ tool-origin facts (about 70% of what gets checked, mostly random ids) gives AUC 0.71 and 0.75
163
+ (p about 0.09 and 0.10); and the audit's verdict on the probed fact itself was "kept" for 13
164
+ scenarios, none of which failed, against 10 failures in 23 "lost" (pooled Fisher p = 0.0045,
165
+ unadjusted). Treat the per-fact verdict and the list of lost facts as the useful output, not
166
+ the single number. The 1.5B model cannot run the audit at all.
167
+ - Fix: the fact-extraction prompt could be hijacked by an instruction inside the transcript
168
+ (a transcript ending in "Summarize in one line" made a 3B model summarize instead of
169
+ extracting). The transcript is now delimited and the task restated after it.
170
+
171
+ - Phase 1 core (pre-release, not published):
172
+ - Neutral message model with provenance (`rotguard.core`), and the observer that diffs
173
+ consecutive contexts and recognises compactions: removed items, rewritten items, summaries.
174
+ - Ledger of goals, decisions, constraints and open questions with supersession
175
+ (`rotguard.ledger`). Entries are never deleted.
176
+ - SQLite store, plus `diff` and `blame` over recorded provenance (`rotguard.report`).
177
+ - `RotguardMiddleware` for LangChain v1 (`rotguard.adapters.langchain`): a `ledger_record`
178
+ tool, per-call ledger injection that never touches stored history, and fail-open hooks.
179
+ - Verified middleware ordering against langchain 1.4.4: list rotguard after
180
+ `SummarizationMiddleware` and `ContextEditingMiddleware` (plan section 7.2).
181
+ - JSON schemas for the message and ledger entry (`schemas/`).
182
+ - Phase 0 evaluation harness (`evals/`): planted-facts, superseded-decision and goal-drift suites,
183
+ `none` / `truncate` / `summarize` strategies, a `+ledger` variant, and `evals.rescore`.
184
+ - Logging through [logquill](https://pypi.org/project/logquill/).