cairnkit 0.5.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 (76) hide show
  1. cairnkit-0.5.0/.github/workflows/ci.yml +27 -0
  2. cairnkit-0.5.0/.github/workflows/release.yml +55 -0
  3. cairnkit-0.5.0/.gitignore +20 -0
  4. cairnkit-0.5.0/AGENTS.md +24 -0
  5. cairnkit-0.5.0/CHANGELOG.md +99 -0
  6. cairnkit-0.5.0/CONTRIBUTING.md +31 -0
  7. cairnkit-0.5.0/LICENSE +22 -0
  8. cairnkit-0.5.0/NOTICE.md +31 -0
  9. cairnkit-0.5.0/PKG-INFO +234 -0
  10. cairnkit-0.5.0/README.md +205 -0
  11. cairnkit-0.5.0/README.zh-CN.md +67 -0
  12. cairnkit-0.5.0/SECURITY.md +21 -0
  13. cairnkit-0.5.0/cairnkit/__init__.py +143 -0
  14. cairnkit-0.5.0/cairnkit/agents.py +111 -0
  15. cairnkit-0.5.0/cairnkit/compression.py +973 -0
  16. cairnkit-0.5.0/cairnkit/curator.py +217 -0
  17. cairnkit-0.5.0/cairnkit/delegate.py +99 -0
  18. cairnkit-0.5.0/cairnkit/documents.py +421 -0
  19. cairnkit-0.5.0/cairnkit/guardrails.py +124 -0
  20. cairnkit-0.5.0/cairnkit/llm/__init__.py +17 -0
  21. cairnkit-0.5.0/cairnkit/llm/base.py +34 -0
  22. cairnkit-0.5.0/cairnkit/llm/errors.py +185 -0
  23. cairnkit-0.5.0/cairnkit/llm/fallback.py +107 -0
  24. cairnkit-0.5.0/cairnkit/llm/openai_compat.py +281 -0
  25. cairnkit-0.5.0/cairnkit/llm/scripted.py +54 -0
  26. cairnkit-0.5.0/cairnkit/loop.py +310 -0
  27. cairnkit-0.5.0/cairnkit/memory.py +185 -0
  28. cairnkit-0.5.0/cairnkit/memory_ops.py +201 -0
  29. cairnkit-0.5.0/cairnkit/pricing.py +37 -0
  30. cairnkit-0.5.0/cairnkit/prompt.py +27 -0
  31. cairnkit-0.5.0/cairnkit/py.typed +0 -0
  32. cairnkit-0.5.0/cairnkit/review.py +55 -0
  33. cairnkit-0.5.0/cairnkit/search.py +130 -0
  34. cairnkit-0.5.0/cairnkit/security.py +215 -0
  35. cairnkit-0.5.0/cairnkit/skills.py +184 -0
  36. cairnkit-0.5.0/cairnkit/titles.py +75 -0
  37. cairnkit-0.5.0/cairnkit/tools.py +175 -0
  38. cairnkit-0.5.0/cairnkit/types.py +58 -0
  39. cairnkit-0.5.0/docs/approval-demo.md +76 -0
  40. cairnkit-0.5.0/docs/assets/approval-demo.gif +0 -0
  41. cairnkit-0.5.0/docs/assets/approval-poster.png +0 -0
  42. cairnkit-0.5.0/docs/assets/cairnkit-approval-demo.mp4 +0 -0
  43. cairnkit-0.5.0/docs/hermes-porting.md +211 -0
  44. cairnkit-0.5.0/docs/launch/README.md +91 -0
  45. cairnkit-0.5.0/docs/launch/demo.zh-CN.srt +24 -0
  46. cairnkit-0.5.0/docs/launch/release-v0.4.1.md +42 -0
  47. cairnkit-0.5.0/docs/launch/show-hn-en.md +39 -0
  48. cairnkit-0.5.0/docs/launch/v2ex-zh.md +38 -0
  49. cairnkit-0.5.0/docs/launch/x-en.md +21 -0
  50. cairnkit-0.5.0/docs/launch/x-zh.md +19 -0
  51. cairnkit-0.5.0/docs/upgrading-hermes.md +66 -0
  52. cairnkit-0.5.0/examples/approval-demo/index.html +79 -0
  53. cairnkit-0.5.0/examples/approval-demo/replay.js +87 -0
  54. cairnkit-0.5.0/examples/approval-demo/style.css +595 -0
  55. cairnkit-0.5.0/examples/approval-demo/trace.js +84 -0
  56. cairnkit-0.5.0/examples/approval.py +230 -0
  57. cairnkit-0.5.0/examples/minimal.py +61 -0
  58. cairnkit-0.5.0/examples/read_document.py +31 -0
  59. cairnkit-0.5.0/examples/verify_citations.py +109 -0
  60. cairnkit-0.5.0/pyproject.toml +65 -0
  61. cairnkit-0.5.0/reference/README.md +20 -0
  62. cairnkit-0.5.0/scripts/hermes_changes.py +75 -0
  63. cairnkit-0.5.0/scripts/render_approval_video.py +85 -0
  64. cairnkit-0.5.0/tests/conftest.py +12 -0
  65. cairnkit-0.5.0/tests/test_approval_example.py +128 -0
  66. cairnkit-0.5.0/tests/test_compression.py +433 -0
  67. cairnkit-0.5.0/tests/test_curator.py +108 -0
  68. cairnkit-0.5.0/tests/test_documents.py +309 -0
  69. cairnkit-0.5.0/tests/test_llm.py +263 -0
  70. cairnkit-0.5.0/tests/test_loop.py +189 -0
  71. cairnkit-0.5.0/tests/test_memory_tool.py +158 -0
  72. cairnkit-0.5.0/tests/test_misc.py +47 -0
  73. cairnkit-0.5.0/tests/test_search_delegate_review.py +179 -0
  74. cairnkit-0.5.0/tests/test_skills_agents.py +77 -0
  75. cairnkit-0.5.0/tests/test_stream_compress_price.py +165 -0
  76. cairnkit-0.5.0/uv.lock +246 -0
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ python: ["3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: astral-sh/setup-uv@v6
20
+ with:
21
+ python-version: ${{ matrix.python }}
22
+ - run: uv sync --all-extras --frozen
23
+ - run: uv run pytest -q
24
+ - run: uv run ruff check .
25
+ - run: uv run ruff format --check .
26
+ - run: uv run python examples/minimal.py
27
+ - run: uv run python examples/approval.py
@@ -0,0 +1,55 @@
1
+ # Push a tag such as v0.5.0 to publish to PyPI. The version in pyproject.toml must match the tag.
2
+ # PyPI side (once): a trusted publisher for this repo, workflow release.yml, environment "pypi".
3
+ name: Release
4
+ on:
5
+ push:
6
+ tags: ["v*"]
7
+ permissions:
8
+ contents: read
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: astral-sh/setup-uv@v6
15
+ with:
16
+ python-version: "3.12"
17
+ - run: uv sync --all-extras --frozen
18
+ - run: uv run pytest -q
19
+ - name: Tag matches the package version
20
+ run: |
21
+ version=$(uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
22
+ test "v$version" = "$GITHUB_REF_NAME"
23
+ - run: uv build
24
+ - uses: actions/upload-artifact@v4
25
+ with:
26
+ name: dist
27
+ path: dist/
28
+ pypi:
29
+ needs: build
30
+ runs-on: ubuntu-latest
31
+ environment: pypi
32
+ permissions:
33
+ id-token: write
34
+ steps:
35
+ - uses: actions/download-artifact@v4
36
+ with:
37
+ name: dist
38
+ path: dist/
39
+ - uses: pypa/gh-action-pypi-publish@release/v1
40
+ github-release:
41
+ needs: pypi
42
+ runs-on: ubuntu-latest
43
+ permissions:
44
+ contents: write
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: actions/download-artifact@v4
48
+ with:
49
+ name: dist
50
+ path: dist/
51
+ - name: Release notes from CHANGELOG.md
52
+ run: awk -v t="## $GITHUB_REF_NAME " '/^## /{if (p) exit; if (index($0, t) == 1) {p=1; next}} p' CHANGELOG.md > notes.md
53
+ - run: gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 || gh release create "$GITHUB_REF_NAME" dist/* --title "cairnkit $GITHUB_REF_NAME" --notes-file notes.md
54
+ env:
55
+ GH_TOKEN: ${{ github.token }}
@@ -0,0 +1,20 @@
1
+ # Upstream Hermes source: a separate checkout, see reference/README.md
2
+ reference/hermes-agent/
3
+
4
+ .venv/
5
+ __pycache__/
6
+ *.py[cod]
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .env
10
+ .env.*
11
+ *.sqlite*
12
+ *.db
13
+ *.log
14
+ .DS_Store
15
+ dist/
16
+ build/
17
+ *.egg-info/
18
+ data/
19
+ logs/
20
+ .run/
@@ -0,0 +1,24 @@
1
+ # Guidelines for contributors and AI coding assistants
2
+
3
+ Read this before changing the repository.
4
+
5
+ - **Stay independent.** Only general runtime capabilities belong here: no business code,
6
+ databases or web frameworks. The core depends only on the standard library and `httpx`.
7
+ Features that need more go in optional modules (for example `documents`, installed with
8
+ `cairnkit[documents]`); core modules must not import them. Storage, permissions, price
9
+ tables and wording come from the host through parameters, callbacks or configuration.
10
+ - **Identity and permissions come from the host** through `ToolContext`; the model never
11
+ decides who is acting. Prefer `access="propose"` for writes.
12
+ - **Keep Hermes' design where it fits.** Ported files name their Hermes version, commit and
13
+ source file in the header, and [NOTICE.md](NOTICE.md) keeps the MIT notice. Record
14
+ differences and their reasons in [docs/hermes-porting.md](docs/hermes-porting.md).
15
+ Follow [docs/upgrading-hermes.md](docs/upgrading-hermes.md) for new Hermes releases.
16
+ - A conversation's system prompt does not change, except through context compression.
17
+ - **Tests never use the network.** Use `ScriptedModel`. Real-model checks need a stated
18
+ purpose and the maintainer's agreement.
19
+ - Never commit keys, runtime data, logs or virtual environments.
20
+ - Adding or upgrading dependencies needs the maintainer's agreement.
21
+ - **Public API changes need a release**: bump the version as described in the
22
+ [README](README.md#versioning), update [CHANGELOG.md](CHANGELOG.md), tag the commit.
23
+ - Before committing: `uv run pytest -q && uv run ruff check . && uv run ruff format --check .`
24
+ - Keep other people's uncommitted work; stage files one by one.
@@ -0,0 +1,99 @@
1
+ # Changelog
2
+
3
+ Versioning rules: see [README](README.md#versioning).
4
+
5
+ ## v0.5.0 (2026-11-09)
6
+
7
+ Follows Hermes Agent v0.23.0 (`2bd378e`). What was taken, skipped or left for later, and why:
8
+ [docs/hermes-porting.md](https://github.com/wasabihu/cairnkit/blob/v0.5.0/docs/hermes-porting.md#v0230-2bd378e-checked-2026-11-09).
9
+
10
+ - **On PyPI.** Install with `pip install cairnkit` or `uv add cairnkit`; releases are
11
+ published from a tag (`.github/workflows/release.yml`).
12
+ - New example `examples/verify_citations.py`: a read tool that checks a draft's DOIs and PMIDs
13
+ with [claimcite](https://github.com/wasabihu/claimcite) before a person reviews it.
14
+
15
+ New (optional, existing code keeps working):
16
+
17
+ - `ContextCompressor(fallback_model=...)`: when a separate summary model fails (dead
18
+ endpoint, quota, unknown model, bad key), the summary is retried once on this model,
19
+ usually the agent's main model. `CompressionResult.fallback_model_used` and
20
+ `summary_model_error` say it happened, so the host can warn that the summary model is broken.
21
+ - `ClassifiedError.context["timeout_phase"]` (`"connect"` or `"read"`), and timeout messages
22
+ that say whether a connection was made and what to change. `AllModelsFailed` repeats them.
23
+
24
+ Changed behaviour:
25
+
26
+ - Compression summaries have a `## Unverified / Working Hypotheses` section and the prompt asks
27
+ for uncertain claims as `UNVERIFIED: <claim> — <basis>` lines, so guesses no longer turn into
28
+ facts. Hosts with their own `summary_sections` get the label rule without the section.
29
+ - The lean compression tail never exceeds half the trigger, so a small `threshold_tokens` on a
30
+ large window can get below the trigger again.
31
+ - A 400 that names a request-body byte cap (Novita, Z.AI, "content-length ... bytes") is
32
+ `payload_too_large` and triggers compression instead of a format error.
33
+ - Streamed tool calls: a new id at an index already in use starts a new call only when it is
34
+ named and the previous call's arguments are complete. Ollama's parallel calls (all at index
35
+ 0) are now separate calls; LM Studio, Kimi and MiniMax fragments stay one call.
36
+ - Tool results: a JSON result counts as failed when it has a non-empty `error`, a `status` of
37
+ `failed`/`error`, or a `results` list where every item has an error (before, only
38
+ `success: false`). This affects loop protection and `ToolRecord.failed`. A rate-limited
39
+ failure no longer counts toward the identical-arguments warning and block.
40
+ - Memory tool: the schema lists only `target` and `operations` (a single change is a one-item
41
+ list). The single-change shape and the `new_text` alias are still accepted. Batches that
42
+ only remove, or a lone duplicate add, are no longer refused when the store is over a lowered
43
+ limit, and a lone add over the limit says how many characters to free.
44
+ - `history_search` with no arguments lists 10 documents by default (a search still returns 3).
45
+ - `REVIEW_INSTRUCTION` tells the reviewer to save only facts grounded in the conversation, never
46
+ inferred traits or one-off requests as standing preferences.
47
+
48
+ ## v0.4.1 (2026-10-22)
49
+
50
+ - `documents`: text and Markdown files saved as Simplified Chinese by older Windows tools
51
+ (GBK / GB18030) are now read correctly instead of as Windows-1252 garbage. The result
52
+ carries a warning saying how the text was read. Western legacy text is unchanged.
53
+
54
+ ## v0.4.0 (2026-10-22)
55
+
56
+ - `make_delegate_tool(..., on_child_run=...)`: a hook that sees every finished child run
57
+ (agent, result, parent context), so child model calls can be billed. The parent's
58
+ `RunResult.usage` still covers only its own calls.
59
+ - `generate_title(..., on_usage=...)`: receives the title call's token usage, also when the
60
+ title is rejected.
61
+ - `RunResult.compression_usage` and `CompressionResult.usage`: what the summary model used
62
+ when compaction ran, so it can be billed too.
63
+
64
+ All are additions (optional arguments or new fields); existing code keeps working unchanged.
65
+
66
+ ## v0.3.0 (2026-10-21)
67
+
68
+ First public release. The project was developed privately as `agentcore`.
69
+
70
+ - **Renamed to cairnkit.** The package, the import name and the repository are now
71
+ `cairnkit`. To upgrade from `agentcore` 0.2: change the dependency to
72
+ `cairnkit` (or `cairnkit[documents]`), point the source at
73
+ `https://github.com/wasabihu/cairnkit` tag `v0.3.0`, and replace `agentcore` with
74
+ `cairnkit` in imports. Nothing else changed in the API.
75
+ - English documentation, contribution and security guides, CI on Python 3.12 and 3.13,
76
+ `py.typed`, and two more examples (`approval.py`, `read_document.py`).
77
+
78
+ ## v0.2.0 (2026-10-21)
79
+
80
+ - Optional module `documents`: PDF, Word (.docx), Excel (.xlsx) and text to Markdown
81
+ (`extract_text`), with limits on size, pages, rows and unzipped size, stable error codes
82
+ (`DocumentError.code`) and replaceable readers (`register_extractor`).
83
+ - Optional dependency group `documents` (pypdf, openpyxl). Core dependencies unchanged.
84
+
85
+ ## v0.1.0 (2026-10-21)
86
+
87
+ First version as a standalone package, based on Hermes Agent v0.22.2 (`d923a5e`).
88
+
89
+ - Agent loop (streaming, tool rounds, loop protection, forced final answer)
90
+ - Tool registry with access levels and roles; three-layer system prompt; skills shown on
91
+ demand with injection scanning
92
+ - Memory editing rules (add / patch / remove), history search, background review,
93
+ delegation to sub-agents
94
+ - Context compression following Hermes' full implementation (token anchors, cooldowns,
95
+ anti-thrashing, incremental summaries, forced compaction and retry on overflow)
96
+ - Skill curation (returns a plan; the host decides)
97
+ - Models: OpenAI-compatible client, OpenRouter, error classification, fallback chain,
98
+ offline scripted model; cost estimates; automatic titles
99
+ - `scripts/hermes_changes.py` and the Hermes upgrade guide
@@ -0,0 +1,31 @@
1
+ # Contributing
2
+
3
+ Thanks for helping. cairnkit is small on purpose, so please open an issue to discuss a new
4
+ feature before writing it. Bug fixes, tests and documentation improvements are welcome
5
+ directly as pull requests.
6
+
7
+ ## Set up
8
+
9
+ ```bash
10
+ git clone https://github.com/wasabihu/cairnkit
11
+ cd cairnkit
12
+ uv sync --all-extras
13
+ uv run pytest -q && uv run ruff check . && uv run ruff format --check .
14
+ ```
15
+
16
+ ## What a good change looks like
17
+
18
+ - It follows [AGENTS.md](AGENTS.md): the core stays dependency-light, identity comes from
19
+ the host, and tests run offline with `ScriptedModel`.
20
+ - It has tests for the behaviour it adds or fixes.
21
+ - Public API changes are described in [CHANGELOG.md](CHANGELOG.md) under an "Unreleased"
22
+ heading.
23
+ - Code ported from Hermes Agent names its source in the file header.
24
+
25
+ ## Reporting bugs
26
+
27
+ Include the cairnkit version, Python version, a minimal script that shows the problem
28
+ (with `ScriptedModel` if possible) and what you expected. For security problems, see
29
+ [SECURITY.md](SECURITY.md) instead of opening a public issue.
30
+
31
+ By contributing you agree that your contribution is licensed under the MIT License.
cairnkit-0.5.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wasabihu
4
+ Copyright (c) 2025 Nous Research (portions adapted from Hermes Agent, see NOTICE.md)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,31 @@
1
+ # Notice
2
+
3
+ cairnkit ports the design and several algorithms of **Hermes Agent v0.23.0**
4
+ (<https://github.com/NousResearch/hermes-agent>, commit `2bd378e`).
5
+
6
+ ```
7
+ MIT License
8
+
9
+ Copyright (c) 2025 Nous Research
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ ```
29
+
30
+ Files that contain ported or adapted code say so in their header, naming the
31
+ Hermes source file.
@@ -0,0 +1,234 @@
1
+ Metadata-Version: 2.5
2
+ Name: cairnkit
3
+ Version: 0.5.0
4
+ Summary: A small agent runtime for business apps: the host owns identity, writes become drafts a person approves.
5
+ Project-URL: Homepage, https://github.com/wasabihu/cairnkit
6
+ Project-URL: Repository, https://github.com/wasabihu/cairnkit
7
+ Project-URL: Issues, https://github.com/wasabihu/cairnkit/issues
8
+ Project-URL: Changelog, https://github.com/wasabihu/cairnkit/blob/main/CHANGELOG.md
9
+ Author-email: wasabihu <wasabi.net@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ License-File: NOTICE.md
13
+ Keywords: agent-runtime,agents,ai,context-compression,hermes-agent,human-in-the-loop,llm,memory
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.12
24
+ Requires-Dist: httpx<1,>=0.27
25
+ Provides-Extra: documents
26
+ Requires-Dist: openpyxl<4,>=3.1; extra == 'documents'
27
+ Requires-Dist: pypdf<7,>=6; extra == 'documents'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # cairnkit
31
+
32
+ [![CI](https://github.com/wasabihu/cairnkit/actions/workflows/ci.yml/badge.svg)](https://github.com/wasabihu/cairnkit/actions/workflows/ci.yml)
33
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
34
+ ![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)
35
+
36
+ **A small agent runtime for business applications.** Give an agent useful tools while
37
+ keeping identity, permissions and persistence in your application. Use proposal tools
38
+ and a host-controlled review step when a person should decide what gets saved.
39
+
40
+ A cairn is a pile of stones that marks a trail so the next walker can follow it. cairnkit
41
+ does the same for agents: approved memory, searchable history and curated skills that the
42
+ next conversation builds on.
43
+
44
+ [中文说明](README.zh-CN.md)
45
+
46
+ ## See the approval boundary
47
+
48
+ A fictional support ticket asks to change an invoice email. The agent reads it and
49
+ creates a draft. The saved record changes only after the host's reviewer approves it.
50
+ Rejecting the draft leaves the record untouched.
51
+
52
+ ![Five steps: source ticket, read, pending draft, human review, host save](docs/assets/approval-demo.gif)
53
+
54
+ [Watch the 35-second walkthrough](docs/assets/cairnkit-approval-demo.mp4) ·
55
+ [Run the example and choose approve or reject](docs/approval-demo.md)
56
+
57
+ ```bash
58
+ git clone https://github.com/wasabihu/cairnkit.git
59
+ cd cairnkit
60
+ uv run python examples/approval.py
61
+ ```
62
+
63
+ **Offline, fictional data, no API key.** The model uses predetermined `ScriptedModel`
64
+ responses. The video replays snapshots from a real execution of that example, including
65
+ a scripted approval choice. It demonstrates the host integration, not model quality.
66
+ The in-memory review function is example application code, not a built-in approval service.
67
+
68
+ ## Why another agent runtime
69
+
70
+ In a business application, several people may share records, each with different roles.
71
+ The application needs to retain control over which tools an agent can use and when a
72
+ proposed change becomes a saved record. cairnkit provides building blocks for that:
73
+
74
+ - **Identity comes from the host, never from the model.** Tools receive the signed-in
75
+ person, their roles and the record being worked on in a `ToolContext`.
76
+ - **Make changes reviewable.** Tools declare an access level (`read`, `propose`, `write`)
77
+ and allowed roles. Your proposal handler creates drafts; your application implements
78
+ approval and persistence. The access label alone does not enforce human review.
79
+ - **Memory with guard rails.** Add / patch / remove edits located by quoted text, size
80
+ limits, prompt-injection scanning and frozen snapshots, ported from Hermes Agent.
81
+ - **Compact long conversations.** A port of Hermes Agent's context compression:
82
+ incremental structured summaries, real token anchors, cooldowns and verbatim user quotes.
83
+ - **Small.** The core depends only on the standard library and `httpx`. No web framework,
84
+ no database, no vector store: storage and permissions stay in your application.
85
+
86
+ ## What is inside
87
+
88
+ | Area | Module | What it does |
89
+ | -------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------- |
90
+ | Agent loop | `loop.py` | Model → tools → model, streaming, a forced plain answer when the step budget runs out |
91
+ | Tools | `tools.py` | Schema, handler, access level, allowed roles; identity is injected, never taken from the model |
92
+ | Loop protection | `guardrails.py` | Warns or stops on repeated failing calls and identical results |
93
+ | System prompt | `prompt.py` | Stable / context / volatile layers, assembled once per conversation for prompt caching |
94
+ | Memory | `memory.py`, `memory_ops.py` | Hermes' add / patch / remove rules; your store decides whether changes need approval |
95
+ | History search | `search.py` | One tool, four modes (search, read, context, browse); no model calls |
96
+ | Skills | `skills.py` | `SKILL.md` folders, shown on demand, scanned for injection when loaded |
97
+ | Context compression | `compression.py` | Four-phase compaction with incremental summaries and per-conversation `CompressionState` |
98
+ | Background review | `review.py` | After a turn, a forked agent decides whether anything should be remembered |
99
+ | Delegation | `delegate.py` | Hand a focused task to a specialist agent and get back only its answer (one level deep) |
100
+ | Skill curation | `curator.py` | Marks unused skills stale, suggests archives and merges; returns a plan, you decide |
101
+ | Injection scanning | `security.py` | Prompt injection, hidden characters and exfiltration patterns |
102
+ | Titles | `titles.py` | Instant title, upgraded by a small model; never overrides a title the user typed |
103
+ | Models | `llm/` | OpenAI-compatible client (streaming), OpenRouter, error classification, fallback chain, offline scripted model |
104
+ | Cost | `pricing.py` | Uses the provider's reported cost, otherwise a price table |
105
+ | Agent definitions | `agents.py` | One folder per agent: `agent.toml` + `SOUL.md` |
106
+ | Documents (optional) | `documents.py` | PDF, Word, Excel and text to Markdown, with size and zip-bomb limits |
107
+
108
+ ## Install
109
+
110
+ ```bash
111
+ pip install cairnkit
112
+ # or
113
+ uv add cairnkit
114
+ # with document reading (adds pypdf and openpyxl):
115
+ pip install "cairnkit[documents]" # or: uv add "cairnkit[documents]"
116
+ ```
117
+
118
+ Python 3.12 or newer.
119
+
120
+ ## Quick start
121
+
122
+ An agent is a folder:
123
+
124
+ ```
125
+ agents/
126
+ greeter/
127
+ agent.toml
128
+ SOUL.md
129
+ ```
130
+
131
+ ```toml
132
+ # agents/greeter/agent.toml
133
+ description = "Answers questions about opening hours."
134
+ model = "default" # a name your application maps to a model
135
+ tools = ["opening_hours"] # the only tools this agent may use
136
+ skills = [] # optional: skills from a skills/ folder
137
+ max_iterations = 4
138
+ temperature = 0.2
139
+ ```
140
+
141
+ `SOUL.md` holds the persona and rules; it goes into the stable layer of the system prompt.
142
+
143
+ ```python
144
+ from cairnkit import Agent, ToolContext, ToolRegistry, load_agents, openrouter, tool_result
145
+
146
+ tools = ToolRegistry()
147
+
148
+ @tools.tool(name="opening_hours", description="Opening hours for a weekday.",
149
+ parameters={"type": "object", "properties": {"day": {"type": "string"}},
150
+ "required": ["day"]})
151
+ def opening_hours(args, ctx):
152
+ return tool_result(day=args["day"], hours="7am-7pm")
153
+
154
+ specs = load_agents("agents")
155
+ agent = Agent(specs["greeter"], model=openrouter("openai/gpt-4.1-mini", api_key="..."),
156
+ tools=tools)
157
+ result = await agent.run("When do you open on Sunday?", ToolContext(principal={"id": "u1"}))
158
+ print(result.text)
159
+ ```
160
+
161
+ Runnable examples, no API key needed:
162
+
163
+ | Example | Shows |
164
+ | ------------------------------------------------------ | ---------------------------------------------------------- |
165
+ | [examples/minimal.py](examples/minimal.py) | The smallest complete agent with one tool |
166
+ | [examples/approval.py](examples/approval.py) | Read a ticket, propose a change, approve or reject in the host |
167
+ | [examples/read_document.py](examples/read_document.py) | Reading a PDF, Word or Excel file into Markdown |
168
+ | [examples/verify_citations.py](examples/verify_citations.py) | A read tool that checks a draft's DOIs and PMIDs with [claimcite](https://github.com/wasabihu/claimcite) before review (needs network: `uv run --with claimcite python examples/verify_citations.py`) |
169
+
170
+ ## Rules worth keeping
171
+
172
+ - **Identity is not the model's to decide.** Put the user and the record in `ToolContext`.
173
+ - **Assemble the system prompt once per conversation.** Pass `RunResult.system_prompt` back
174
+ as `run(system_prompt=...)` on later turns so the provider's prompt cache keeps working.
175
+ - **Prefer `access="propose"`** for anything that changes data, and let a person approve.
176
+ - **Test offline** with `ScriptedModel`; automated tests should never call a real model.
177
+ - **Text from users, files and tools is data.** Keep it out of the system prompt.
178
+
179
+ ## Reading documents (optional)
180
+
181
+ ```python
182
+ from cairnkit.documents import DocumentError, Limits, extract_text
183
+
184
+ try:
185
+ doc = extract_text(data, "price-list.xlsx", limits=Limits(max_chars=40_000))
186
+ except DocumentError as exc:
187
+ exc.code # too_large, empty, unsupported, old_format, image, encrypted, no_text, unreadable, needs_extra
188
+ else:
189
+ doc.text, doc.kind, doc.parts, doc.truncated, doc.warnings
190
+ ```
191
+
192
+ - **Output** is Markdown. Word keeps headings, lists and tables; Excel becomes one table per
193
+ visible sheet (dates as `2026-11-07`); PDF text is marked `[Page N]`.
194
+ - **The format is judged by content**, not only the file name. Old `.doc`/`.xls`, pictures
195
+ and scanned PDFs get their own error codes so your UI can explain what to do.
196
+ - **Limits** on file size, PDF pages, Excel rows and unzipped size. Only text is read;
197
+ macros and formulas never run.
198
+ - **Replaceable**: `register_extractor("pdf", fn)` swaps in another reader (MarkItDown,
199
+ Docling, an OCR service) without changing callers.
200
+
201
+ ## Relationship to Hermes Agent
202
+
203
+ Many mechanisms and algorithms are ported from
204
+ [Hermes Agent](https://github.com/NousResearch/hermes-agent) v0.23.0 (MIT, Nous Research):
205
+ the prompt layers, the tool loop, memory editing rules, history search, skills, context
206
+ compression, background review, curation and delegation. Every ported file names its
207
+ Hermes source in its header. Hermes is a personal assistant with files, terminals and
208
+ dozens of platforms; cairnkit keeps the algorithms and replaces storage, configuration and
209
+ permissions so they fit a multi-user web application. See
210
+ [docs/hermes-porting.md](docs/hermes-porting.md) for the map and
211
+ [docs/upgrading-hermes.md](docs/upgrading-hermes.md) for following new Hermes releases.
212
+ cairnkit is not affiliated with or endorsed by Nous Research.
213
+
214
+ ## Versioning
215
+
216
+ `MAJOR.MINOR.PATCH`. Before 1.0:
217
+
218
+ - **Patch** (0.3.0 → 0.3.1): fixes, tests and docs; upgrade freely.
219
+ - **Minor** (0.3 → 0.4): new features, Hermes updates, or anything that needs changes in
220
+ your code. [CHANGELOG.md](CHANGELOG.md) says what to change.
221
+
222
+ ## Development
223
+
224
+ ```bash
225
+ uv sync --all-extras
226
+ uv run pytest -q && uv run ruff check . && uv run ruff format --check .
227
+ ```
228
+
229
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Security reports: [SECURITY.md](SECURITY.md).
230
+
231
+ ## License
232
+
233
+ MIT, see [LICENSE](LICENSE). Ported Hermes Agent code keeps its MIT notice in
234
+ [NOTICE.md](NOTICE.md).