axel-protocol 2.3.1__tar.gz → 2.3.2__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.
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/PKG-INFO +1 -1
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/cli.py +17 -2
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/server.py +33 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/static/monitor.html +109 -1
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/pyproject.toml +1 -1
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/.github/workflows/ci.yml +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/.github/workflows/publish.yml +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/.gitignore +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/CHANGELOG.md +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/CONTRIBUTING.md +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/Dockerfile +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/LICENSE +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/README.md +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/__init__.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/__init__.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/bedrock.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/cohere.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/gemini.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/groq.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/litellm.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/mistral.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/together.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/client.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/core.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/learning.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/persistence.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/testing.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/universal.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/docker-compose.yml +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/docs/message-schemas.md +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/examples/demo_live.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/install.sh +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/__init__.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/test_adapters.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/test_client.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/test_core.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/test_new_features.py +0 -0
- {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axel-protocol
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
4
4
|
Summary: AXEL — Agent eXchange Language: a universal protocol for multi-LLM networks
|
|
5
5
|
Project-URL: Homepage, https://github.com/sectorx/axel-protocol
|
|
6
6
|
Project-URL: Repository, https://github.com/sectorx/axel-protocol
|
|
@@ -687,6 +687,15 @@ def cmd_demo(args): # noqa: C901
|
|
|
687
687
|
except Exception:
|
|
688
688
|
return None
|
|
689
689
|
|
|
690
|
+
def _fetch_topic_library(srv):
|
|
691
|
+
"""Fetch user-defined learning topics from the server. Returns list or []."""
|
|
692
|
+
try:
|
|
693
|
+
resp = _ur.urlopen(f"{srv}/topics", timeout=3)
|
|
694
|
+
data = json.loads(resp.read())
|
|
695
|
+
return data.get("topics", [])
|
|
696
|
+
except Exception:
|
|
697
|
+
return []
|
|
698
|
+
|
|
690
699
|
def _fetch_memory_context(srv):
|
|
691
700
|
"""Pull top lessons from shared memory and format as context."""
|
|
692
701
|
import json
|
|
@@ -748,13 +757,19 @@ def cmd_demo(args): # noqa: C901
|
|
|
748
757
|
while True:
|
|
749
758
|
run_count += 1
|
|
750
759
|
step_counter[0] = 0 # reset step counter for new run
|
|
751
|
-
# Check if user submitted a task from the dashboard
|
|
760
|
+
# Check if user submitted a one-off task from the dashboard
|
|
752
761
|
user_task = _check_user_queue(server)
|
|
753
762
|
if user_task:
|
|
754
763
|
topic = user_task
|
|
755
764
|
print(f"\n ⌨️ User task received: \"{topic}\"")
|
|
756
765
|
else:
|
|
757
|
-
topic
|
|
766
|
+
# Pull user-defined topic library — use it if populated
|
|
767
|
+
user_topics = _fetch_topic_library(server)
|
|
768
|
+
if user_topics:
|
|
769
|
+
topic = user_topics[(run_count - 1) % len(user_topics)]
|
|
770
|
+
print(f"\n 📚 Topic Library ({len(user_topics)} topics) — exploring: \"{topic}\"")
|
|
771
|
+
else:
|
|
772
|
+
topic = _TOPICS[(run_count - 1) % len(_TOPICS)]
|
|
758
773
|
|
|
759
774
|
# Fetch lessons learned so far — injected into prompts so agents remember
|
|
760
775
|
mem_ctx = _fetch_memory_context(server) if run_count > 1 else ""
|
|
@@ -378,6 +378,9 @@ class AXELServer:
|
|
|
378
378
|
# User task queue — dashboard POSTs tasks here; demo loop pops them
|
|
379
379
|
self._task_queue: list = []
|
|
380
380
|
self._task_queue_lock = threading.Lock()
|
|
381
|
+
# User-defined topic library — agents explore these instead of built-in topics
|
|
382
|
+
self._topic_library: list = []
|
|
383
|
+
self._topic_lock = threading.Lock()
|
|
381
384
|
# Restore persisted agents/memory if db is attached
|
|
382
385
|
if self.persistence:
|
|
383
386
|
self._restore_from_db()
|
|
@@ -951,6 +954,36 @@ class AXELServer:
|
|
|
951
954
|
task = self._task_queue.pop(0) if self._task_queue else None
|
|
952
955
|
return {"task": task, "remaining": len(self._task_queue)}
|
|
953
956
|
|
|
957
|
+
# ── Topic Library ─────────────────────────────────────────
|
|
958
|
+
@app.post("/topics")
|
|
959
|
+
async def add_topic(request: Request):
|
|
960
|
+
"""Add a topic to the learning library."""
|
|
961
|
+
body = await request.json()
|
|
962
|
+
topic = (body.get("topic") or "").strip()
|
|
963
|
+
if not topic:
|
|
964
|
+
return {"ok": False, "error": "topic is required"}
|
|
965
|
+
with self._topic_lock:
|
|
966
|
+
if topic not in self._topic_library:
|
|
967
|
+
self._topic_library.append(topic)
|
|
968
|
+
self.event_bus.publish("topics.updated", {"topics": list(self._topic_library)})
|
|
969
|
+
return {"ok": True, "topics": list(self._topic_library)}
|
|
970
|
+
|
|
971
|
+
@app.get("/topics")
|
|
972
|
+
async def list_topics():
|
|
973
|
+
"""Return all user-defined topics."""
|
|
974
|
+
with self._topic_lock:
|
|
975
|
+
return {"topics": list(self._topic_library)}
|
|
976
|
+
|
|
977
|
+
@app.delete("/topics/{idx}")
|
|
978
|
+
async def remove_topic(idx: int):
|
|
979
|
+
"""Remove a topic by index."""
|
|
980
|
+
with self._topic_lock:
|
|
981
|
+
if idx < 0 or idx >= len(self._topic_library):
|
|
982
|
+
return {"ok": False, "error": "index out of range"}
|
|
983
|
+
removed = self._topic_library.pop(idx)
|
|
984
|
+
self.event_bus.publish("topics.updated", {"topics": list(self._topic_library)})
|
|
985
|
+
return {"ok": True, "removed": removed, "topics": list(self._topic_library)}
|
|
986
|
+
|
|
954
987
|
# ── SSE Event Stream ─────────────────────────────────────
|
|
955
988
|
@app.get("/stream")
|
|
956
989
|
async def event_stream(request: Request):
|
|
@@ -223,6 +223,37 @@ header {
|
|
|
223
223
|
.conf-bar { height: 2px; background: var(--border); border-radius: 2px; margin-top: 5px; }
|
|
224
224
|
.conf-fill { height: 100%; background: var(--green); border-radius: 2px; transition: width .5s; }
|
|
225
225
|
|
|
226
|
+
/* ── Topic Library bar ── */
|
|
227
|
+
.topic-bar {
|
|
228
|
+
display: flex; align-items: flex-start; gap: 10px;
|
|
229
|
+
padding: 8px 18px; background: rgba(251,191,36,.03);
|
|
230
|
+
border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
231
|
+
}
|
|
232
|
+
.topic-bar-label { font-size: 10px; text-transform: uppercase; letter-spacing: .7px; color: var(--yellow); font-weight: 700; }
|
|
233
|
+
.topic-bar-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
|
|
234
|
+
.topic-bar-row { display: flex; align-items: center; gap: 8px; }
|
|
235
|
+
.topic-input {
|
|
236
|
+
flex: 1; background: var(--bg); border: 1px solid var(--border); color: var(--text);
|
|
237
|
+
padding: 5px 10px; border-radius: 6px; font-size: 12px; min-width: 0;
|
|
238
|
+
}
|
|
239
|
+
.topic-input:focus { outline: none; border-color: var(--yellow); }
|
|
240
|
+
.topic-add-btn {
|
|
241
|
+
padding: 5px 12px; border-radius: 6px; border: 1px solid rgba(251,191,36,.4);
|
|
242
|
+
background: rgba(251,191,36,.08); color: var(--yellow); font-size: 12px; cursor: pointer;
|
|
243
|
+
white-space: nowrap; transition: background .15s;
|
|
244
|
+
}
|
|
245
|
+
.topic-add-btn:hover { background: rgba(251,191,36,.18); }
|
|
246
|
+
.topic-chips { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
247
|
+
.topic-chip {
|
|
248
|
+
display: inline-flex; align-items: center; gap: 5px;
|
|
249
|
+
background: rgba(251,191,36,.1); border: 1px solid rgba(251,191,36,.25);
|
|
250
|
+
border-radius: 20px; padding: 3px 8px 3px 10px; font-size: 11px; color: var(--text);
|
|
251
|
+
}
|
|
252
|
+
.topic-chip-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
|
|
253
|
+
.topic-chip-x { cursor: pointer; color: var(--muted); font-size: 14px; line-height: 1; flex-shrink: 0; transition: color .15s; }
|
|
254
|
+
.topic-chip-x:hover { color: var(--red); }
|
|
255
|
+
.topic-empty { font-size: 11px; color: var(--muted); }
|
|
256
|
+
|
|
226
257
|
/* ── Settings overlay ── */
|
|
227
258
|
.settings-overlay {
|
|
228
259
|
position: fixed; inset: 0; z-index: 200;
|
|
@@ -320,6 +351,20 @@ header {
|
|
|
320
351
|
<span class="mission-status" id="missionStatus">agents running autonomously</span>
|
|
321
352
|
</div>
|
|
322
353
|
|
|
354
|
+
<div class="topic-bar">
|
|
355
|
+
<span style="font-size:15px;line-height:1;padding-top:1px">📚</span>
|
|
356
|
+
<div class="topic-bar-body">
|
|
357
|
+
<div class="topic-bar-label">Learning Topics <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--muted)">— agents will cycle through these instead of built-in topics</span></div>
|
|
358
|
+
<div class="topic-bar-row">
|
|
359
|
+
<input class="topic-input" id="topicInput" placeholder="Add a topic… e.g. 'The ethics of autonomous AI systems'" />
|
|
360
|
+
<button class="topic-add-btn" onclick="addTopic()">+ Add Topic</button>
|
|
361
|
+
</div>
|
|
362
|
+
<div class="topic-chips" id="topicChips">
|
|
363
|
+
<span class="topic-empty">No topics yet — agents use built-in rotation</span>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
323
368
|
<div class="progress-strip" id="progressStrip">
|
|
324
369
|
<div class="prog-label" id="progLabel">Run #1</div>
|
|
325
370
|
<div class="prog-dots">
|
|
@@ -453,7 +498,7 @@ function reconnect() {
|
|
|
453
498
|
function connect() {
|
|
454
499
|
setConn(false);
|
|
455
500
|
sse = new EventSource(serverUrl + '/stream');
|
|
456
|
-
sse.onopen = () => { setConn(true); poll(); };
|
|
501
|
+
sse.onopen = () => { setConn(true); poll(); loadTopics(); };
|
|
457
502
|
sse.onerror = () => {
|
|
458
503
|
setConn(false);
|
|
459
504
|
if (document.getElementById('toggleAR').classList.contains('on')) {
|
|
@@ -574,6 +619,10 @@ function onEvent(d) {
|
|
|
574
619
|
const b = p.body || p;
|
|
575
620
|
updateProgress(b.run ?? 1, b.step ?? 1, b.total ?? 5, b.agent || '', b.action || '');
|
|
576
621
|
|
|
622
|
+
} else if (type === 'topics.updated') {
|
|
623
|
+
topicLibrary = p.topics || [];
|
|
624
|
+
renderTopics();
|
|
625
|
+
|
|
577
626
|
} else if (type.includes('error')) {
|
|
578
627
|
const msg = p.body?.error || p.error || '';
|
|
579
628
|
addFeed('error', '❌', `Error from <b>${p.fr||'?'}</b>`, msg, { rawType:'ERR', from:p.fr, fullText:msg });
|
|
@@ -981,8 +1030,67 @@ function submitMission() {
|
|
|
981
1030
|
// Allow Enter key to submit
|
|
982
1031
|
document.addEventListener('keydown', e => {
|
|
983
1032
|
if (e.key === 'Enter' && document.activeElement.id === 'missionInput') submitMission();
|
|
1033
|
+
if (e.key === 'Enter' && document.activeElement.id === 'topicInput') addTopic();
|
|
984
1034
|
});
|
|
985
1035
|
|
|
1036
|
+
// ─── Topic Library ─────────────────────────────────────────────
|
|
1037
|
+
let topicLibrary = [];
|
|
1038
|
+
|
|
1039
|
+
function renderTopics() {
|
|
1040
|
+
const chips = document.getElementById('topicChips');
|
|
1041
|
+
if (!chips) return;
|
|
1042
|
+
if (topicLibrary.length === 0) {
|
|
1043
|
+
chips.innerHTML = '<span class="topic-empty">No topics yet — agents use built-in rotation</span>';
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
chips.innerHTML = topicLibrary.map((t, i) =>
|
|
1047
|
+
`<span class="topic-chip">
|
|
1048
|
+
<span class="topic-chip-text" title="${esc(t)}">${esc(t)}</span>
|
|
1049
|
+
<span class="topic-chip-x" onclick="removeTopic(${i})" title="Remove">×</span>
|
|
1050
|
+
</span>`
|
|
1051
|
+
).join('');
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function addTopic() {
|
|
1055
|
+
const input = document.getElementById('topicInput');
|
|
1056
|
+
const topic = (input.value || '').trim();
|
|
1057
|
+
if (!topic) { input.focus(); return; }
|
|
1058
|
+
fetch(serverUrl + '/topics', {
|
|
1059
|
+
method: 'POST',
|
|
1060
|
+
headers: {'Content-Type':'application/json'},
|
|
1061
|
+
body: JSON.stringify({topic})
|
|
1062
|
+
})
|
|
1063
|
+
.then(r => r.json())
|
|
1064
|
+
.then(d => {
|
|
1065
|
+
if (d.ok) {
|
|
1066
|
+
topicLibrary = d.topics;
|
|
1067
|
+
input.value = '';
|
|
1068
|
+
renderTopics();
|
|
1069
|
+
addFeed('announce', '📚', `<b>Topic added:</b> ${esc(topic.slice(0,80))}`, 'Agents will explore this on the next run', {rawType:'TOPIC'});
|
|
1070
|
+
}
|
|
1071
|
+
})
|
|
1072
|
+
.catch(() => {});
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function removeTopic(idx) {
|
|
1076
|
+
fetch(serverUrl + `/topics/${idx}`, { method: 'DELETE' })
|
|
1077
|
+
.then(r => r.json())
|
|
1078
|
+
.then(d => {
|
|
1079
|
+
if (d.ok !== false) {
|
|
1080
|
+
topicLibrary = d.topics;
|
|
1081
|
+
renderTopics();
|
|
1082
|
+
}
|
|
1083
|
+
})
|
|
1084
|
+
.catch(() => {});
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function loadTopics() {
|
|
1088
|
+
fetch(serverUrl + '/topics')
|
|
1089
|
+
.then(r => r.json())
|
|
1090
|
+
.then(d => { topicLibrary = d.topics || []; renderTopics(); })
|
|
1091
|
+
.catch(() => {});
|
|
1092
|
+
}
|
|
1093
|
+
|
|
986
1094
|
// ─── Settings ──────────────────────────────────────────────────
|
|
987
1095
|
function openSettings() {
|
|
988
1096
|
document.getElementById('settingsUrl').value = serverUrl;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|