sutra-agent 0.3.0__py3-none-any.whl

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.
sutra/__init__.py ADDED
@@ -0,0 +1,157 @@
1
+ """SUTRA — deterministic compression of oversized tool output, with recovery.
2
+
3
+ Hermes spends most of its tokens re-sending context on every turn, and tool
4
+ output is the part that grows without bound. This plugin sits on the
5
+ ``tool_execution`` middleware path and shrinks a tool result *after* it has run,
6
+ before the model ever sees it.
7
+
8
+ The compression itself lives in :mod:`core`, the host-agnostic seam shared with
9
+ the Claude Code hook, the CLI and the MCP server. This module is the Hermes
10
+ adapter: it registers the middleware and the recovery tool, and delegates every
11
+ decision below that to the seam.
12
+
13
+ What makes the shrinking safe is that the original is kept: every rewrite
14
+ quotes an id the agent can hand back to ``sutra_restore`` and get the exact
15
+ bytes that were left out. Correctness first, compression second.
16
+
17
+ Design rules, in priority order:
18
+
19
+ 1. **Never invent content.** Only remove, collapse or elide, and always leave a
20
+ marker saying what was removed, how much, and how to get it back.
21
+ 2. **Never change a payload's own format.** Diffs are returned byte-identical,
22
+ a JSON array stays an array, a traceback keeps its error end.
23
+ 3. **Fail open.** Any unexpected shape, parse error or exception returns the
24
+ original result untouched.
25
+ 4. **Measurable.** Every decision is counted, every rewrite appends a line to a
26
+ local savings log, and every retained original is a file the agent can ask
27
+ for by id.
28
+
29
+ Configuration comes from the environment so it can be tuned without a code
30
+ change:
31
+
32
+ SUTRA_ENABLED "0" disables compression entirely (default "1")
33
+ SUTRA_MAX_BYTES only results larger than this are rewritten (default 24576)
34
+ SUTRA_HEAD_BYTES bytes kept from the start (default 12288)
35
+ SUTRA_TAIL_BYTES bytes kept from the end (default 6144)
36
+ SUTRA_STATS_FILE where the saving log lives (default $HERMES_HOME/cache/sutra-stats.jsonl)
37
+ SUTRA_STATS_MAX_BYTES rotate the log at this size (default 5 MiB)
38
+ SUTRA_STORE "0" stops retaining originals (default "1")
39
+ SUTRA_STORE_DIR where originals live (default $HERMES_HOME/cache/sutra-store)
40
+ SUTRA_STORE_TTL_HOURS how long an original is kept (default 168 = 7 days)
41
+ SUTRA_STORE_MAX_BYTES total size of the store (default 128 MiB)
42
+ SUTRA_STORE_MAX_ENTRY_BYTES largest single original retained (default 32 MiB)
43
+ SUTRA_STORE_MAX_ENTRIES most originals retained (default 2000)
44
+ SUTRA_RECOVERY_MAX_BYTES response cap for sutra_restore (default 8192)
45
+ SUTRA_JSON_ITEMS retired; ignored (array elision changed the payload's type)
46
+ """
47
+
48
+ from __future__ import annotations
49
+
50
+ from typing import Any
51
+
52
+ from . import config as _config
53
+ from . import metrics
54
+ from . import recovery
55
+ from . import store as _store
56
+ from .core import (
57
+ NO_COMPRESS_TOOLS,
58
+ Outcome,
59
+ outcome_json,
60
+ process_result,
61
+ process_text,
62
+ restore,
63
+ should_bypass,
64
+ )
65
+ from .engine import ( # re-exported: this was a single-module plugin before 0.2.0
66
+ _DEFAULT_MAX_BYTES as _DEFAULT_MAX_BYTES,
67
+ Classification,
68
+ Ref,
69
+ classify,
70
+ collapse_repeats,
71
+ compress,
72
+ config_from_env,
73
+ strip_ansi,
74
+ )
75
+
76
+ __version__ = "0.3.0"
77
+
78
+ # Backward-compatible name: before the seam extraction this was the plugin's
79
+ # own function. It is now the seam's result-shaped entry point.
80
+ compress_result = process_result
81
+
82
+
83
+ def configuration() -> _config.Config:
84
+ """The resolved configuration every component reads from."""
85
+ return _config.resolve()
86
+
87
+
88
+ def enabled() -> bool:
89
+ return _config.resolve().enabled
90
+
91
+
92
+ def stats_path() -> str:
93
+ return _config.resolve().stats_path
94
+
95
+
96
+ def metrics_snapshot(store=None) -> dict[str, Any]:
97
+ """One surface for compression, restores, cleanup, and the store's size."""
98
+ if store is None:
99
+ try:
100
+ store = _store.Store(_store.StoreConfig.from_config(_config.resolve()))
101
+ except Exception:
102
+ store = None
103
+ return metrics.snapshot(store)
104
+
105
+
106
+ def tool_execution(*, next_call, tool_name: str | None = None, args: Any = None,
107
+ session_id: str | None = None, **_context: Any) -> Any:
108
+ """The middleware frame: run the tool, then compress what comes back.
109
+
110
+ The seam owns the bypass list, so a host never needs its own copy of it.
111
+ ``next_call`` runs before the try: a downstream tool error must propagate
112
+ unchanged, not be mistaken for a compression failure.
113
+ """
114
+ result = next_call(args)
115
+ try:
116
+ return process_result(result, tool_name=tool_name, session_id=session_id)
117
+ except Exception: # fail open: an exotic result shape is never worth a broken call
118
+ return result
119
+
120
+
121
+ def register(ctx) -> None:
122
+ """Hermes plugin entry point: middleware plus the recovery tool."""
123
+ ctx.register_middleware("tool_execution", tool_execution)
124
+ ctx.register_tool(
125
+ name=recovery.TOOL_NAME,
126
+ toolset="sutra",
127
+ schema=recovery.TOOL_SCHEMA,
128
+ handler=recovery.handle_tool_call,
129
+ description="Retrieve the omitted part of a tool result SUTRA compressed.",
130
+ emoji="🔎",
131
+ )
132
+
133
+
134
+ __all__ = [
135
+ "Classification",
136
+ "NO_COMPRESS_TOOLS",
137
+ "Outcome",
138
+ "Ref",
139
+ "classify",
140
+ "collapse_repeats",
141
+ "compress",
142
+ "compress_result",
143
+ "config_from_env",
144
+ "configuration",
145
+ "enabled",
146
+ "metrics_snapshot",
147
+ "outcome_json",
148
+ "process_result",
149
+ "process_text",
150
+ "register",
151
+ "restore",
152
+ "should_bypass",
153
+ "stats_path",
154
+ "strip_ansi",
155
+ "tool_execution",
156
+ "__version__",
157
+ ]