agent-collab-kit 0.1.1__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.
- agent_collab.py +234 -0
- agent_collab_kit-0.1.1.dist-info/METADATA +159 -0
- agent_collab_kit-0.1.1.dist-info/RECORD +7 -0
- agent_collab_kit-0.1.1.dist-info/WHEEL +5 -0
- agent_collab_kit-0.1.1.dist-info/entry_points.txt +2 -0
- agent_collab_kit-0.1.1.dist-info/licenses/LICENSE +21 -0
- agent_collab_kit-0.1.1.dist-info/top_level.txt +1 -0
agent_collab.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Cooperative mail and exact-resource leases for a trusted local machine."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.1"
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
import sqlite3
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
import uuid
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Conflict(ValueError):
|
|
16
|
+
"""An existing message or lease conflicts with this request."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def text_value(value, name):
|
|
20
|
+
if not isinstance(value, str) or not value.strip() or "\x00" in value:
|
|
21
|
+
raise ValueError(name + " must be a nonempty string without NUL")
|
|
22
|
+
return value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def ttl_value(value):
|
|
26
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
27
|
+
raise ValueError("ttl must be numeric")
|
|
28
|
+
if not math.isfinite(value) or not 0 < value <= 86400:
|
|
29
|
+
raise ValueError("ttl must be finite, positive and <= 86400 seconds")
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Store:
|
|
34
|
+
"""Every mutating call uses one BEGIN IMMEDIATE transaction.
|
|
35
|
+
|
|
36
|
+
clock is injectable for tests; production assumes a trusted wall clock.
|
|
37
|
+
Opening requires an explicitly initialized database.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, db, clock=time.time):
|
|
41
|
+
text_value(str(db), "db")
|
|
42
|
+
self.clock = clock
|
|
43
|
+
self.conn = sqlite3.connect(str(db), timeout=10, isolation_level=None)
|
|
44
|
+
self.conn.row_factory = sqlite3.Row
|
|
45
|
+
self.conn.execute("PRAGMA busy_timeout=10000")
|
|
46
|
+
version = self.conn.execute("PRAGMA user_version").fetchone()[0]
|
|
47
|
+
if version != 1:
|
|
48
|
+
self.conn.close()
|
|
49
|
+
raise ValueError("database is not initialized; run init")
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def initialize(db):
|
|
53
|
+
text_value(str(db), "db")
|
|
54
|
+
conn = sqlite3.connect(str(db), timeout=10, isolation_level=None)
|
|
55
|
+
try:
|
|
56
|
+
conn.execute("PRAGMA busy_timeout=10000")
|
|
57
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
58
|
+
version = conn.execute("PRAGMA user_version").fetchone()[0]
|
|
59
|
+
if version not in (0, 1):
|
|
60
|
+
raise ValueError("unsupported database version")
|
|
61
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS messages (
|
|
62
|
+
id TEXT PRIMARY KEY, idempotency_key TEXT NOT NULL UNIQUE,
|
|
63
|
+
sender TEXT NOT NULL, recipient TEXT NOT NULL, body TEXT NOT NULL,
|
|
64
|
+
created_at REAL NOT NULL, acknowledged_at REAL)""")
|
|
65
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS leases (
|
|
66
|
+
resource TEXT PRIMARY KEY COLLATE BINARY, owner TEXT NOT NULL,
|
|
67
|
+
generation TEXT NOT NULL, expires_at REAL NOT NULL)""")
|
|
68
|
+
conn.execute("PRAGMA user_version=1")
|
|
69
|
+
conn.execute("COMMIT")
|
|
70
|
+
except Exception:
|
|
71
|
+
if conn.in_transaction:
|
|
72
|
+
conn.execute("ROLLBACK")
|
|
73
|
+
raise
|
|
74
|
+
finally:
|
|
75
|
+
conn.close()
|
|
76
|
+
|
|
77
|
+
def close(self):
|
|
78
|
+
self.conn.close()
|
|
79
|
+
|
|
80
|
+
def now(self):
|
|
81
|
+
value = self.clock()
|
|
82
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
83
|
+
raise ValueError("clock must return a finite number")
|
|
84
|
+
if not math.isfinite(value):
|
|
85
|
+
raise ValueError("clock must return a finite number")
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
@contextmanager
|
|
89
|
+
def transaction(self):
|
|
90
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
91
|
+
try:
|
|
92
|
+
yield
|
|
93
|
+
self.conn.execute("COMMIT")
|
|
94
|
+
except Exception:
|
|
95
|
+
self.conn.execute("ROLLBACK")
|
|
96
|
+
raise
|
|
97
|
+
|
|
98
|
+
def send(self, sender, recipient, body, key):
|
|
99
|
+
for name, value in (("sender", sender), ("recipient", recipient),
|
|
100
|
+
("body", body), ("key", key)):
|
|
101
|
+
text_value(value, name)
|
|
102
|
+
with self.transaction():
|
|
103
|
+
old = self.conn.execute(
|
|
104
|
+
"SELECT * FROM messages WHERE idempotency_key=?", (key,)
|
|
105
|
+
).fetchone()
|
|
106
|
+
if old is not None:
|
|
107
|
+
if (old["sender"], old["recipient"], old["body"]) != (
|
|
108
|
+
sender, recipient, body):
|
|
109
|
+
raise Conflict("idempotency key has a different payload")
|
|
110
|
+
return dict(old)
|
|
111
|
+
identifier = str(uuid.uuid4())
|
|
112
|
+
self.conn.execute(
|
|
113
|
+
"INSERT INTO messages VALUES (?,?,?,?,?,?,NULL)",
|
|
114
|
+
(identifier, key, sender, recipient, body, self.now()))
|
|
115
|
+
return dict(self.conn.execute(
|
|
116
|
+
"SELECT * FROM messages WHERE id=?", (identifier,)).fetchone())
|
|
117
|
+
|
|
118
|
+
def inbox(self, recipient):
|
|
119
|
+
text_value(recipient, "recipient")
|
|
120
|
+
return [dict(row) for row in self.conn.execute(
|
|
121
|
+
"SELECT * FROM messages WHERE recipient=? AND acknowledged_at IS NULL "
|
|
122
|
+
"ORDER BY created_at, id", (recipient,))]
|
|
123
|
+
|
|
124
|
+
def ack(self, identifier, recipient):
|
|
125
|
+
text_value(identifier, "id")
|
|
126
|
+
text_value(recipient, "recipient")
|
|
127
|
+
with self.transaction():
|
|
128
|
+
row = self.conn.execute(
|
|
129
|
+
"SELECT * FROM messages WHERE id=? AND recipient=?",
|
|
130
|
+
(identifier, recipient)).fetchone()
|
|
131
|
+
if row is None:
|
|
132
|
+
raise Conflict("message not found for recipient")
|
|
133
|
+
if row["acknowledged_at"] is None:
|
|
134
|
+
self.conn.execute(
|
|
135
|
+
"UPDATE messages SET acknowledged_at=? WHERE id=?",
|
|
136
|
+
(self.now(), identifier))
|
|
137
|
+
return dict(self.conn.execute(
|
|
138
|
+
"SELECT * FROM messages WHERE id=?", (identifier,)).fetchone())
|
|
139
|
+
|
|
140
|
+
def claim(self, resource, owner, ttl):
|
|
141
|
+
text_value(resource, "resource")
|
|
142
|
+
text_value(owner, "owner")
|
|
143
|
+
ttl_value(ttl)
|
|
144
|
+
with self.transaction():
|
|
145
|
+
now = self.now()
|
|
146
|
+
row = self.conn.execute(
|
|
147
|
+
"SELECT * FROM leases WHERE resource=?", (resource,)).fetchone()
|
|
148
|
+
if row is not None and row["expires_at"] > now:
|
|
149
|
+
raise Conflict("resource has an active lease")
|
|
150
|
+
generation = str(uuid.uuid4())
|
|
151
|
+
self.conn.execute("INSERT OR REPLACE INTO leases VALUES (?,?,?,?)",
|
|
152
|
+
(resource, owner, generation, now + ttl))
|
|
153
|
+
return {"resource": resource, "owner": owner,
|
|
154
|
+
"generation": generation, "expires_at": now + ttl}
|
|
155
|
+
|
|
156
|
+
def _owned(self, resource, owner, generation, now):
|
|
157
|
+
for name, value in (("resource", resource), ("owner", owner),
|
|
158
|
+
("generation", generation)):
|
|
159
|
+
text_value(value, name)
|
|
160
|
+
row = self.conn.execute(
|
|
161
|
+
"SELECT * FROM leases WHERE resource=? AND owner=? AND generation=?",
|
|
162
|
+
(resource, owner, generation)).fetchone()
|
|
163
|
+
if row is None or row["expires_at"] <= now:
|
|
164
|
+
raise Conflict("lease token does not identify an active lease")
|
|
165
|
+
return row
|
|
166
|
+
|
|
167
|
+
def renew(self, resource, owner, generation, ttl):
|
|
168
|
+
ttl_value(ttl)
|
|
169
|
+
with self.transaction():
|
|
170
|
+
now = self.now()
|
|
171
|
+
self._owned(resource, owner, generation, now)
|
|
172
|
+
self.conn.execute("UPDATE leases SET expires_at=? WHERE resource=?",
|
|
173
|
+
(now + ttl, resource))
|
|
174
|
+
return dict(self.conn.execute(
|
|
175
|
+
"SELECT * FROM leases WHERE resource=?", (resource,)).fetchone())
|
|
176
|
+
|
|
177
|
+
def release(self, resource, owner, generation):
|
|
178
|
+
with self.transaction():
|
|
179
|
+
self._owned(resource, owner, generation, self.now())
|
|
180
|
+
self.conn.execute("DELETE FROM leases WHERE resource=?", (resource,))
|
|
181
|
+
return {"released": resource}
|
|
182
|
+
|
|
183
|
+
def leases(self):
|
|
184
|
+
now = self.now()
|
|
185
|
+
return [dict(row) for row in self.conn.execute(
|
|
186
|
+
"SELECT * FROM leases WHERE expires_at>? ORDER BY resource", (now,))]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def main(argv=None):
|
|
190
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
191
|
+
parser.add_argument("--db", required=True, help="explicit local SQLite file")
|
|
192
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
193
|
+
commands.add_parser("init")
|
|
194
|
+
send = commands.add_parser("send")
|
|
195
|
+
for name in ("sender", "recipient", "body", "key"):
|
|
196
|
+
send.add_argument("--" + name, required=True)
|
|
197
|
+
inbox = commands.add_parser("inbox")
|
|
198
|
+
inbox.add_argument("--recipient", required=True)
|
|
199
|
+
ack = commands.add_parser("ack")
|
|
200
|
+
ack.add_argument("--id", required=True)
|
|
201
|
+
ack.add_argument("--recipient", required=True)
|
|
202
|
+
for command in ("claim", "renew", "release"):
|
|
203
|
+
sub = commands.add_parser(command)
|
|
204
|
+
sub.add_argument("--resource", required=True)
|
|
205
|
+
sub.add_argument("--owner", required=True)
|
|
206
|
+
if command != "claim":
|
|
207
|
+
sub.add_argument("--generation", required=True)
|
|
208
|
+
if command != "release":
|
|
209
|
+
sub.add_argument("--ttl", required=True, type=float)
|
|
210
|
+
commands.add_parser("leases")
|
|
211
|
+
args = vars(parser.parse_args(argv))
|
|
212
|
+
db, command = args.pop("db"), args.pop("command")
|
|
213
|
+
store = None
|
|
214
|
+
try:
|
|
215
|
+
if command == "init":
|
|
216
|
+
Store.initialize(db)
|
|
217
|
+
result = {"initialized": db}
|
|
218
|
+
else:
|
|
219
|
+
store = Store(db)
|
|
220
|
+
if command == "ack":
|
|
221
|
+
args["identifier"] = args.pop("id")
|
|
222
|
+
result = getattr(store, command)(**args)
|
|
223
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
224
|
+
return 0
|
|
225
|
+
except (ValueError, sqlite3.Error, OSError) as exc:
|
|
226
|
+
print(str(exc), file=sys.stderr)
|
|
227
|
+
return 1
|
|
228
|
+
finally:
|
|
229
|
+
if store is not None:
|
|
230
|
+
store.close()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
sys.exit(main())
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-collab-kit
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Local SQLite mailbox, acknowledgements and cooperative leases for AI agents on one trusted machine. Python standard library only.
|
|
5
|
+
Author: ALTA VISTA360, S.L.U.
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kanibal-studio/agent-collab-kit
|
|
8
|
+
Project-URL: Issues, https://github.com/kanibal-studio/agent-collab-kit/issues
|
|
9
|
+
Project-URL: PyPI, https://pypi.org/project/agent-collab-kit/
|
|
10
|
+
Keywords: ai-agents,multi-agent,coordination,sqlite,mailbox,leases,claude-code
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Agent Collab Kit
|
|
28
|
+
|
|
29
|
+
A small Python 3.10+ standard-library tool for cooperative work on one trusted
|
|
30
|
+
machine: durable messages, explicit acknowledgements, and leases for exact
|
|
31
|
+
resource names. Inspired by operational patterns, this is an original standalone
|
|
32
|
+
implementation, not an export of a production system. It runs without a provider,
|
|
33
|
+
agent framework, cloud service, Git execution, or automatic orchestration.
|
|
34
|
+
It does not claim a new coordination model.
|
|
35
|
+
|
|
36
|
+
## Try it with an isolated database
|
|
37
|
+
|
|
38
|
+
Run from this directory on a local disk. The example uses a temporary directory
|
|
39
|
+
and fictitious participants; never point a demonstration at a live project DB.
|
|
40
|
+
Shell commands below use a POSIX shell:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
DEMO_DIR=$(mktemp -d)
|
|
44
|
+
DB="$DEMO_DIR/demo.db"
|
|
45
|
+
python3 agent_collab.py --db "$DB" init
|
|
46
|
+
python3 agent_collab.py --db "$DB" send --sender worker-a --recipient integrator --key demo-result-1 --body 'Addition is ready for review'
|
|
47
|
+
python3 agent_collab.py --db "$DB" inbox --recipient integrator
|
|
48
|
+
# Copy the returned message id into MESSAGE_ID.
|
|
49
|
+
python3 agent_collab.py --db "$DB" ack --recipient integrator --id MESSAGE_ID
|
|
50
|
+
python3 agent_collab.py --db "$DB" claim --resource src/add.py --owner worker-a --ttl 300
|
|
51
|
+
# Copy the returned generation UUID into GENERATION.
|
|
52
|
+
python3 agent_collab.py --db "$DB" renew --resource src/add.py --owner worker-a --generation GENERATION --ttl 300
|
|
53
|
+
python3 agent_collab.py --db "$DB" leases
|
|
54
|
+
python3 agent_collab.py --db "$DB" release --resource src/add.py --owner worker-a --generation GENERATION
|
|
55
|
+
python3 -m unittest discover -s tests -v
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
All commands require `--db`. `init` is repeatable. Commands return JSON on success,
|
|
59
|
+
exit 0, or an error on stderr, exit 1 (invalid CLI syntax exits 2). An idempotency
|
|
60
|
+
key is unique across the database: repeating the same sender, recipient and body
|
|
61
|
+
returns the existing message, including its acknowledgement state. Reusing a key
|
|
62
|
+
for different content fails. `inbox` shows only pending mail for the named recipient.
|
|
63
|
+
`ack` requires that recipient name and is repeatable. Acknowledgement means the
|
|
64
|
+
caller marked the message received; it does not prove work was delivered or tested.
|
|
65
|
+
|
|
66
|
+
## Cooperative leases and recovery
|
|
67
|
+
|
|
68
|
+
Use the fictitious [collaboration board](examples/COLLAB.md) to agree on distinct
|
|
69
|
+
responsibilities and one integrator. Claim every exact resource before editing;
|
|
70
|
+
use a shared resource such as `__commit__` to serialize integration by agreement.
|
|
71
|
+
Resource names are opaque and case-sensitive. `src`, `src/a.py`, and `src/A.py`
|
|
72
|
+
are separate resources. Paths are not normalized, folders have no recursive
|
|
73
|
+
coverage, and aliases such as `./src/a.py` require a naming convention.
|
|
74
|
+
|
|
75
|
+
A claim creates a fresh generation UUID. An active claim conflicts even for the
|
|
76
|
+
same owner; renew using the current generation. TTL must be finite, positive and
|
|
77
|
+
at most 86400 seconds. Renew and release require the matching owner and generation
|
|
78
|
+
and an unexpired lease. At the exact expiration time, a new claim may take over.
|
|
79
|
+
An expired token cannot renew or release its successor, even with the same owner.
|
|
80
|
+
`leases` lists active leases only; expired rows remain until replaced.
|
|
81
|
+
|
|
82
|
+
After a crash, reopen the same database, inspect pending messages and leases,
|
|
83
|
+
check the actual work and running processes, then agree on a takeover. There is
|
|
84
|
+
no forced release. Lease expiry does not stop the previous worker or undo edits:
|
|
85
|
+
a paused worker may resume after takeover. Workers must check ownership and stop
|
|
86
|
+
on loss; this tool provides no filesystem fencing. The test suite demonstrates
|
|
87
|
+
expiry, stale tokens, persistence and two real processes racing for one claim.
|
|
88
|
+
|
|
89
|
+
Every mutation uses SQLite `BEGIN IMMEDIATE` and a 10-second busy timeout.
|
|
90
|
+
A timeout is an error, not a successful claim; inspect state before retrying.
|
|
91
|
+
The API accepts an injected clock for tests; normal operation trusts the wall
|
|
92
|
+
clock. Clock corrections can shorten or prolong leases. Close connections before
|
|
93
|
+
making a consistent backup, and retain SQLite sidecars if present. No background
|
|
94
|
+
service, watcher, automatic renewal, pruning or deletion is supplied.
|
|
95
|
+
|
|
96
|
+
## Trust and limits
|
|
97
|
+
|
|
98
|
+
Use a single trusted machine and a local disk. Network filesystems and separate
|
|
99
|
+
hosts are unsupported. Names and UUIDs are cooperative tokens, not authenticated
|
|
100
|
+
identities or security boundaries: anyone with database access can impersonate a
|
|
101
|
+
participant, read messages, or alter state. Use operating-system permissions and
|
|
102
|
+
keep secrets out of message bodies. Coordination does not guarantee exclusive
|
|
103
|
+
writes, safe Git operations, exactly-once external effects, or code quality.
|
|
104
|
+
The tool reads no production configuration and invokes no external commands.
|
|
105
|
+
|
|
106
|
+
Database files and common SQLite sidecars are ignored by Git; keep any custom
|
|
107
|
+
DB filename outside the repository or extend the ignore rules yourself.
|
|
108
|
+
|
|
109
|
+
## Help project agents improve
|
|
110
|
+
|
|
111
|
+
The [supervised improvement method](examples/AGENT_IMPROVEMENT.md) shows how
|
|
112
|
+
business agents can contribute evidence and proposals to development, with an
|
|
113
|
+
isolated reproduction, regression and counterfactual checks, cost/quality
|
|
114
|
+
measurement, an integrator's adoption or rejection decision, and learning saved
|
|
115
|
+
in artifacts the agent actually consumes. Its triage example is fictional.
|
|
116
|
+
The kit coordinates this loop through mail and exact-resource claims; it does
|
|
117
|
+
not train models or authorize operational actions automatically.
|
|
118
|
+
|
|
119
|
+
## Use with Claude Code
|
|
120
|
+
|
|
121
|
+
Run one Claude Code session per worker on the same machine and give every session
|
|
122
|
+
the same database, kept outside any repository:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
mkdir -p ~/.agent-collab
|
|
126
|
+
python3 /path/to/agent_collab.py --db ~/.agent-collab/team.db init
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
A short block in each project's `CLAUDE.md` is enough for the sessions to use it:
|
|
130
|
+
|
|
131
|
+
```markdown
|
|
132
|
+
## Coordination
|
|
133
|
+
- At the start of a task, read your mail:
|
|
134
|
+
`python3 /path/to/agent_collab.py --db ~/.agent-collab/team.db inbox --recipient <your-name>`,
|
|
135
|
+
then acknowledge each message you have handled with `ack`.
|
|
136
|
+
- Before editing a file, claim its exact path with `claim --resource <path> --owner <your-name> --ttl 900`,
|
|
137
|
+
renew with the returned generation while you work, and `release` it when done.
|
|
138
|
+
- If a claim is refused, do not edit that file: `send` a message to the current owner instead.
|
|
139
|
+
- Only the integrator commits, after claiming `__commit__`.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Each session runs these commands through its usual shell tool, so your permission
|
|
143
|
+
settings still apply. The leases stay cooperative: they guide sessions that follow
|
|
144
|
+
the instruction and do not stop one that ignores it.
|
|
145
|
+
|
|
146
|
+
## Project context and license
|
|
147
|
+
|
|
148
|
+
Prepared by [Kanibal Studio](https://github.com/kanibal-studio), associated with
|
|
149
|
+
ALTA VISTA360, S.L.U. Our public sites are [K-Pulse](https://infos.kanibal-studio.com/),
|
|
150
|
+
[Altavista360](https://altavista360.com), and
|
|
151
|
+
[Altavista Conseil](https://altavista-conseil.com). These links provide context;
|
|
152
|
+
this local tool has no integration with those sites.
|
|
153
|
+
|
|
154
|
+
This package is released under the [MIT license](LICENSE).
|
|
155
|
+
|
|
156
|
+
**En français :** cet outil local facilite la coordination volontaire de plusieurs
|
|
157
|
+
agents : messages persistants, accusés de réception explicites et baux sur noms
|
|
158
|
+
exacts. Il ne contrôle ni les identités ni les fichiers. Un seul intégrateur
|
|
159
|
+
vérifie et intègre le travail. Les exemples et tests utilisent des données fictives.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
agent_collab.py,sha256=0N5_ghMNO5iT785QC5fAPUoKYdtoE3zNqr5V9NZNlwI,9314
|
|
2
|
+
agent_collab_kit-0.1.1.dist-info/licenses/LICENSE,sha256=Bwbo0cToS5HWt5DDFX2RrK-xVE-t0pFi7zJqnyeGMc4,1078
|
|
3
|
+
agent_collab_kit-0.1.1.dist-info/METADATA,sha256=0WfPFfvk53BeAuTRevmUvISpUf4iAOfHxEca8FmwacA,8470
|
|
4
|
+
agent_collab_kit-0.1.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
5
|
+
agent_collab_kit-0.1.1.dist-info/entry_points.txt,sha256=yvU9u_pZalj3QHA_RY3Irzylz44TWdk946S_bEJnf90,51
|
|
6
|
+
agent_collab_kit-0.1.1.dist-info/top_level.txt,sha256=dXuak1gY0WOhxY3xm_xnpnY6qWASpraPJkLhDwZV6r4,13
|
|
7
|
+
agent_collab_kit-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ALTA VISTA360, S.L.U.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agent_collab
|