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.
Files changed (38) hide show
  1. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/PKG-INFO +1 -1
  2. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/cli.py +17 -2
  3. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/server.py +33 -0
  4. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/static/monitor.html +109 -1
  5. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/pyproject.toml +1 -1
  6. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/.github/workflows/ci.yml +0 -0
  7. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/.github/workflows/publish.yml +0 -0
  8. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/.gitignore +0 -0
  9. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/CHANGELOG.md +0 -0
  10. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/CONTRIBUTING.md +0 -0
  11. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/Dockerfile +0 -0
  12. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/LICENSE +0 -0
  13. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/README.md +0 -0
  14. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/__init__.py +0 -0
  15. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/__init__.py +0 -0
  16. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/bedrock.py +0 -0
  17. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/cohere.py +0 -0
  18. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/gemini.py +0 -0
  19. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/groq.py +0 -0
  20. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/litellm.py +0 -0
  21. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/mistral.py +0 -0
  22. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/adapters/together.py +0 -0
  23. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/client.py +0 -0
  24. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/core.py +0 -0
  25. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/learning.py +0 -0
  26. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/persistence.py +0 -0
  27. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/testing.py +0 -0
  28. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/axel/universal.py +0 -0
  29. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/docker-compose.yml +0 -0
  30. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/docs/message-schemas.md +0 -0
  31. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/examples/demo_live.py +0 -0
  32. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/install.sh +0 -0
  33. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/__init__.py +0 -0
  34. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/test_adapters.py +0 -0
  35. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/test_client.py +0 -0
  36. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/test_core.py +0 -0
  37. {axel_protocol-2.3.1 → axel_protocol-2.3.2}/tests/test_new_features.py +0 -0
  38. {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.1
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 = _TOPICS[(run_count - 1) % len(_TOPICS)]
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;
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "axel-protocol"
7
- version = "2.3.1"
7
+ version = "2.3.2"
8
8
  description = "AXEL — Agent eXchange Language: a universal protocol for multi-LLM networks"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
File without changes
File without changes
File without changes
File without changes
File without changes