scarwise 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.
- scarwise-0.1.0/PKG-INFO +6 -0
- scarwise-0.1.0/pyproject.toml +13 -0
- scarwise-0.1.0/scarwise/__init__.py +117 -0
- scarwise-0.1.0/scarwise.egg-info/PKG-INFO +6 -0
- scarwise-0.1.0/scarwise.egg-info/SOURCES.txt +6 -0
- scarwise-0.1.0/scarwise.egg-info/dependency_links.txt +1 -0
- scarwise-0.1.0/scarwise.egg-info/top_level.txt +1 -0
- scarwise-0.1.0/setup.cfg +4 -0
scarwise-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "scarwise"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Your AI agents stop repeating the same tool mistakes – Python SDK (needs the scarwise CLI: npm install -g scarwise)."
|
|
5
|
+
requires-python = ">=3.9"
|
|
6
|
+
license = { text = "Proprietary – see LICENSE" }
|
|
7
|
+
|
|
8
|
+
[build-system]
|
|
9
|
+
requires = ["setuptools>=68"]
|
|
10
|
+
build-backend = "setuptools.build_meta"
|
|
11
|
+
|
|
12
|
+
[tool.setuptools]
|
|
13
|
+
packages = ["scarwise"]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Scarwise for Python applications that call tools themselves (socket C).
|
|
2
|
+
|
|
3
|
+
from scarwise import guard, ScarwiseStop
|
|
4
|
+
|
|
5
|
+
@guard("run_command")
|
|
6
|
+
def run_command(command: str): ...
|
|
7
|
+
|
|
8
|
+
Before each call: a known mistake raises ScarwiseStop (the tool does not run); a verified
|
|
9
|
+
correction changes the input. After: a known error gets the fix appended to its message.
|
|
10
|
+
Any problem inside Scarwise lets the tool run normally. The decisions come from the same
|
|
11
|
+
engine as every other agent (`scarwise decide`), so lessons are shared.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import functools
|
|
16
|
+
import inspect
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import uuid
|
|
22
|
+
|
|
23
|
+
__all__ = ["guard", "ScarwiseStop"]
|
|
24
|
+
__version__ = "0.1.0"
|
|
25
|
+
|
|
26
|
+
_SESSION = "sdk-python:" + uuid.uuid4().hex
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ScarwiseStop(Exception):
|
|
30
|
+
"""A known mistake was stopped before the tool ran; the message says what worked here."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, reason: str, lesson_id: int | None = None):
|
|
33
|
+
super().__init__(reason)
|
|
34
|
+
self.lesson_id = lesson_id
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _command() -> list[str] | None:
|
|
38
|
+
cli = os.environ.get("SCARWISE_CLI")
|
|
39
|
+
if cli:
|
|
40
|
+
return ["node", cli] if cli.endswith((".mjs", ".js", ".ts")) else [cli]
|
|
41
|
+
found = shutil.which("scarwise")
|
|
42
|
+
return [found] if found else None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _decide(event: dict) -> dict:
|
|
46
|
+
cmd = _command()
|
|
47
|
+
if not cmd:
|
|
48
|
+
return {"kind": "none"}
|
|
49
|
+
try:
|
|
50
|
+
r = subprocess.run(cmd + ["decide"], input=json.dumps(event), capture_output=True, text=True, timeout=10)
|
|
51
|
+
return json.loads(r.stdout or '{"kind":"none"}')
|
|
52
|
+
except Exception: # fail-open: Scarwise never blocks a tool by accident
|
|
53
|
+
return {"kind": "none"}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _bind(fn, args, kwargs) -> dict:
|
|
57
|
+
try:
|
|
58
|
+
bound = inspect.signature(fn).bind(*args, **kwargs)
|
|
59
|
+
bound.apply_defaults()
|
|
60
|
+
return {k: v for k, v in bound.arguments.items() if isinstance(v, (str, int, float, bool, list, dict, type(None)))}
|
|
61
|
+
except TypeError:
|
|
62
|
+
return {"args": [a for a in args if isinstance(a, (str, int, float, bool))]}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def guard(name: str, agent: str = "sdk-python", session: str | None = None):
|
|
66
|
+
"""Decorator: guards a tool function (sync or async) with Scarwise lessons."""
|
|
67
|
+
|
|
68
|
+
def deco(fn):
|
|
69
|
+
sess = session or _SESSION
|
|
70
|
+
|
|
71
|
+
def before(args, kwargs):
|
|
72
|
+
data = _bind(fn, args, kwargs)
|
|
73
|
+
d = _decide({"agent": agent, "phase": "pre", "session": sess, "tool": name, "input": data})
|
|
74
|
+
if d.get("kind") == "deny":
|
|
75
|
+
raise ScarwiseStop(d.get("reason", "Scarwise: known mistake"), d.get("lessonId"))
|
|
76
|
+
if d.get("kind") == "rewrite" and isinstance(d.get("updatedInput"), dict):
|
|
77
|
+
params = inspect.signature(fn).parameters
|
|
78
|
+
new = {k: v for k, v in d["updatedInput"].items() if k in params}
|
|
79
|
+
if new:
|
|
80
|
+
data = {**data, **new}
|
|
81
|
+
return (), data, data
|
|
82
|
+
return args, kwargs, data
|
|
83
|
+
|
|
84
|
+
def after_error(e, data):
|
|
85
|
+
d = _decide({"agent": agent, "phase": "failure", "session": sess, "tool": name, "input": data, "error": str(e)})
|
|
86
|
+
if d.get("kind") == "context" and e.args:
|
|
87
|
+
e.args = (f"{e.args[0]}\n{d.get('reason', '')}",) + tuple(e.args[1:])
|
|
88
|
+
|
|
89
|
+
def after_ok(result, data):
|
|
90
|
+
_decide({"agent": agent, "phase": "post", "session": sess, "tool": name, "input": data, "output": str(result)[:4000]})
|
|
91
|
+
|
|
92
|
+
if inspect.iscoroutinefunction(fn):
|
|
93
|
+
@functools.wraps(fn)
|
|
94
|
+
async def awrapper(*args, **kwargs):
|
|
95
|
+
args, kwargs, data = before(args, kwargs)
|
|
96
|
+
try:
|
|
97
|
+
result = await fn(*args, **kwargs)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
after_error(e, data)
|
|
100
|
+
raise
|
|
101
|
+
after_ok(result, data)
|
|
102
|
+
return result
|
|
103
|
+
return awrapper
|
|
104
|
+
|
|
105
|
+
@functools.wraps(fn)
|
|
106
|
+
def wrapper(*args, **kwargs):
|
|
107
|
+
args, kwargs, data = before(args, kwargs)
|
|
108
|
+
try:
|
|
109
|
+
result = fn(*args, **kwargs)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
after_error(e, data)
|
|
112
|
+
raise
|
|
113
|
+
after_ok(result, data)
|
|
114
|
+
return result
|
|
115
|
+
return wrapper
|
|
116
|
+
|
|
117
|
+
return deco
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
scarwise
|
scarwise-0.1.0/setup.cfg
ADDED