Spaces:
Sleeping
Sleeping
| """AgentAZAll HuggingFace Spaces Demo β Dual-Agent Live Demo. | |
| Two AI agents (Qwen2.5-3B + SmolLM2-1.7B) collaborate in real-time. | |
| The center panel shows the raw filesystem as files are created β proving | |
| that AgentAZAll's memory and messaging is just plain text files. | |
| """ | |
| import sys | |
| import time | |
| from pathlib import Path | |
| from typing import Generator | |
| # Ensure src/ is importable | |
| sys.path.insert(0, str(Path(__file__).parent / "src")) | |
| import gradio as gr | |
| # Gradio version compatibility | |
| _GRADIO_MAJOR = int(gr.__version__.split(".")[0]) | |
| from seed_data import ( | |
| AGENT_NAMES, | |
| AGENTS, | |
| MAILBOXES, | |
| make_demo_config, | |
| reset_demo_data, | |
| seed_demo_data, | |
| ) | |
| from llm_bridge import ( | |
| _tool_inbox, | |
| _tool_recall, | |
| _tool_whoami, | |
| _tool_doing, | |
| generate_response, | |
| ) | |
| from agentazall.helpers import today_str | |
| from agentazall.messages import parse_headers_only | |
| # --------------------------------------------------------------------------- | |
| # Initialize | |
| # --------------------------------------------------------------------------- | |
| seed_demo_data() | |
| ALPHA_CFG = make_demo_config("agent-alpha@localhost") | |
| BETA_CFG = make_demo_config("agent-beta@localhost") | |
| # --------------------------------------------------------------------------- | |
| # Filesystem browser functions | |
| # --------------------------------------------------------------------------- | |
| def build_tree_string() -> str: | |
| """Generate a Unix tree-style view of demo_data/mailboxes/ with annotations.""" | |
| if not MAILBOXES.exists(): | |
| return "(no data yet)" | |
| lines = ["mailboxes/"] | |
| agents = sorted(d for d in MAILBOXES.iterdir() if d.is_dir() and not d.name.startswith(".")) | |
| for ai, agent_dir in enumerate(agents): | |
| is_last_agent = (ai == len(agents) - 1) | |
| agent_prefix = "βββ " if is_last_agent else "βββ " | |
| agent_indent = " " if is_last_agent else "β " | |
| lines.append(f"{agent_prefix}{agent_dir.name}/") | |
| # Date directories | |
| date_dirs = sorted( | |
| (d for d in agent_dir.iterdir() | |
| if d.is_dir() and not d.name.startswith(".")), | |
| reverse=True, | |
| ) | |
| for di, date_dir in enumerate(date_dirs): | |
| is_last_date = (di == len(date_dirs) - 1) | |
| date_prefix = f"{agent_indent}βββ " if is_last_date else f"{agent_indent}βββ " | |
| date_indent = f"{agent_indent} " if is_last_date else f"{agent_indent}β " | |
| lines.append(f"{date_prefix}{date_dir.name}/") | |
| # Subdirectories (inbox, sent, remember, etc.) | |
| subdirs = sorted(d for d in date_dir.iterdir() if d.is_dir()) | |
| for si, sub_dir in enumerate(subdirs): | |
| is_last_sub = (si == len(subdirs) - 1) | |
| sub_prefix = f"{date_indent}βββ " if is_last_sub else f"{date_indent}βββ " | |
| sub_indent = f"{date_indent} " if is_last_sub else f"{date_indent}β " | |
| # Count files | |
| files = sorted(f for f in sub_dir.iterdir() if f.is_file() and f.suffix == ".txt") | |
| if not files: | |
| lines.append(f"{sub_prefix}{sub_dir.name}/ (empty)") | |
| continue | |
| lines.append(f"{sub_prefix}{sub_dir.name}/") | |
| for fi, fpath in enumerate(files): | |
| is_last_file = (fi == len(files) - 1) | |
| file_prefix = f"{sub_indent}βββ " if is_last_file else f"{sub_indent}βββ " | |
| # Annotate based on directory type | |
| annotation = "" | |
| if sub_dir.name in ("inbox", "sent", "outbox"): | |
| try: | |
| headers = parse_headers_only(fpath) | |
| if headers: | |
| fr = headers.get("From", "?").split("@")[0] | |
| to = headers.get("To", "?").split("@")[0] | |
| subj = headers.get("Subject", "")[:40] | |
| if sub_dir.name == "inbox": | |
| annotation = f" β From: {fr} | {subj}" | |
| else: | |
| annotation = f" β To: {to} | {subj}" | |
| except Exception: | |
| pass | |
| elif sub_dir.name == "remember": | |
| try: | |
| preview = fpath.read_text(encoding="utf-8").strip()[:50] | |
| annotation = f" [{preview}...]" if len(preview) >= 50 else f" [{preview}]" | |
| except Exception: | |
| pass | |
| lines.append(f"{file_prefix}{fpath.name}{annotation}") | |
| return "\n".join(lines) | |
| def list_all_files() -> list[str]: | |
| """Return list of all file paths relative to MAILBOXES for the dropdown.""" | |
| if not MAILBOXES.exists(): | |
| return [] | |
| files = [] | |
| for fpath in sorted(MAILBOXES.rglob("*.txt")): | |
| rel = fpath.relative_to(MAILBOXES) | |
| files.append(str(rel)) | |
| return files | |
| def read_file_content(rel_path: str) -> str: | |
| """Read raw content of a file selected from the dropdown.""" | |
| if not rel_path: | |
| return "(select a file from the dropdown)" | |
| fpath = MAILBOXES / rel_path | |
| if not fpath.exists(): | |
| return f"(file not found: {rel_path})" | |
| try: | |
| content = fpath.read_text(encoding="utf-8") | |
| return f"# {rel_path}\n# Size: {fpath.stat().st_size} bytes\n{'β' * 60}\n{content}" | |
| except Exception as e: | |
| return f"Error reading {rel_path}: {e}" | |
| def get_latest_modified_file() -> tuple[str, str]: | |
| """Find the most recently modified .txt file, return (rel_path, content).""" | |
| if not MAILBOXES.exists(): | |
| return "", "(no files yet)" | |
| latest = None | |
| latest_time = 0 | |
| for fpath in MAILBOXES.rglob("*.txt"): | |
| mtime = fpath.stat().st_mtime | |
| if mtime > latest_time: | |
| latest_time = mtime | |
| latest = fpath | |
| if latest is None: | |
| return "", "(no files yet)" | |
| rel = str(latest.relative_to(MAILBOXES)) | |
| return rel, read_file_content(rel) | |
| def refresh_filesystem(): | |
| """Return updated tree, file list, and latest file content.""" | |
| tree = build_tree_string() | |
| files = list_all_files() | |
| latest_path, latest_content = get_latest_modified_file() | |
| return tree, gr.Dropdown(choices=files, value=latest_path), latest_content | |
| # --------------------------------------------------------------------------- | |
| # Chat functions | |
| # --------------------------------------------------------------------------- | |
| def chat_alpha(message: str, alpha_history: list): | |
| """Send message to Agent Alpha, return updated state.""" | |
| if not message or not message.strip(): | |
| tree, file_dd, file_content = refresh_filesystem() | |
| return "", alpha_history, tree, file_dd, file_content | |
| try: | |
| response = generate_response("alpha", message.strip(), alpha_history, ALPHA_CFG) | |
| except Exception as e: | |
| response = f"Error: {e}\n\n(GPU quota may be exhausted. Try again later.)" | |
| alpha_history = alpha_history + [ | |
| {"role": "user", "content": message}, | |
| {"role": "assistant", "content": response}, | |
| ] | |
| tree, file_dd, file_content = refresh_filesystem() | |
| return "", alpha_history, tree, file_dd, file_content | |
| def chat_beta(message: str, beta_history: list): | |
| """Send message to Agent Beta, return updated state.""" | |
| if not message or not message.strip(): | |
| tree, file_dd, file_content = refresh_filesystem() | |
| return "", beta_history, tree, file_dd, file_content | |
| try: | |
| response = generate_response("beta", message.strip(), beta_history, BETA_CFG) | |
| except Exception as e: | |
| response = f"Error: {e}\n\n(GPU quota may be exhausted. Try again later.)" | |
| beta_history = beta_history + [ | |
| {"role": "user", "content": message}, | |
| {"role": "assistant", "content": response}, | |
| ] | |
| tree, file_dd, file_content = refresh_filesystem() | |
| return "", beta_history, tree, file_dd, file_content | |
| # --------------------------------------------------------------------------- | |
| # Autopilot | |
| # --------------------------------------------------------------------------- | |
| def _build_autopilot_prompt(cfg: dict, turn: int, is_first: bool) -> str: | |
| """Build the prompt that drives an agent's autopilot turn.""" | |
| if is_first: | |
| return ( | |
| "Check your inbox for messages and respond to the most recent one. " | |
| "Share your thoughts on the topic, ask a follow-up question, and " | |
| "use [TOOL: remember] to store any important insights. " | |
| "Send your reply with [TOOL: send]." | |
| ) | |
| return ( | |
| "Check your inbox for new messages and respond to the latest one. " | |
| "Build on the previous discussion β share a new idea or observation. " | |
| "Remember important insights. Keep the conversation productive and " | |
| "send a thoughtful reply." | |
| ) | |
| def autopilot_loop( | |
| alpha_history: list, | |
| beta_history: list, | |
| max_turns: int, | |
| ) -> Generator: | |
| """Alternating agent conversation. Yields UI updates after each turn.""" | |
| for turn in range(int(max_turns)): | |
| # --- Alpha's turn --- | |
| prompt = _build_autopilot_prompt(ALPHA_CFG, turn, turn == 0) | |
| status = f"β³ Turn {turn + 1}/{int(max_turns)}: Agent Alpha thinking..." | |
| try: | |
| alpha_resp = generate_response("alpha", prompt, alpha_history, ALPHA_CFG) | |
| except Exception as e: | |
| alpha_resp = f"Error: {e}" | |
| alpha_history = alpha_history + [ | |
| {"role": "user", "content": f"[Autopilot turn {turn + 1}]"}, | |
| {"role": "assistant", "content": alpha_resp}, | |
| ] | |
| tree, file_dd, file_content = refresh_filesystem() | |
| yield ( | |
| alpha_history, beta_history, tree, file_dd, file_content, | |
| f"β³ Turn {turn + 1}/{int(max_turns)}: Alpha done. Beta thinking..." | |
| ) | |
| # --- Beta's turn --- | |
| prompt = _build_autopilot_prompt(BETA_CFG, turn, turn == 0) | |
| try: | |
| beta_resp = generate_response("beta", prompt, beta_history, BETA_CFG) | |
| except Exception as e: | |
| beta_resp = f"Error: {e}" | |
| beta_history = beta_history + [ | |
| {"role": "user", "content": f"[Autopilot turn {turn + 1}]"}, | |
| {"role": "assistant", "content": beta_resp}, | |
| ] | |
| tree, file_dd, file_content = refresh_filesystem() | |
| yield ( | |
| alpha_history, beta_history, tree, file_dd, file_content, | |
| f"β Turn {turn + 1}/{int(max_turns)} complete." | |
| ) | |
| tree, file_dd, file_content = refresh_filesystem() | |
| yield ( | |
| alpha_history, beta_history, tree, file_dd, file_content, | |
| f"β Autopilot finished ({int(max_turns)} turns). Browse the files above!" | |
| ) | |
| def do_reset(): | |
| """Reset demo data and return clean state.""" | |
| reset_demo_data() | |
| tree, file_dd, file_content = refresh_filesystem() | |
| return [], [], tree, file_dd, file_content, "Demo reset. Fresh data seeded." | |
| # --------------------------------------------------------------------------- | |
| # How It Works documentation | |
| # --------------------------------------------------------------------------- | |
| HOW_IT_WORKS_MD = """\ | |
| ## How AgentAZAll Works | |
| AgentAZAll is a **persistent memory and multi-agent communication system** for LLM agents. | |
| Every piece of state β memories, messages, identity, tasks β is a **plain text file** | |
| on the filesystem. No database. No vector store. Just files you can read with `cat`. | |
| ### What You Just Saw | |
| In the Live Demo tab, two AI agents (Qwen2.5-3B and SmolLM2-1.7B) collaborate by | |
| sending messages and storing memories. The center panel shows the **raw filesystem** | |
| β every file created by the agents is visible and readable. | |
| ### Three Transports, One Interface | |
| | Transport | Protocol | Self-Host | Best For | | |
| |-----------|----------|-----------|----------| | |
| | **AgentTalk** | HTTPS REST API | `agentazall server --agenttalk` | Modern setups, zero config | | |
| | **Email** | SMTP + IMAP + POP3 | `agentazall server --email` | Universal compatibility | | |
| | **FTP** | FTP/FTPS | `agentazall server --ftp` | File-heavy workflows | | |
| All three are **open**, **self-hostable**, and **interchangeable**. Switch transports | |
| by changing one line in `config.json`. Agents don't care which one delivers their messages. | |
| ### File-Based Storage | |
| ``` | |
| data/mailboxes/ | |
| my-agent@localhost/ | |
| 2026-03-13/ | |
| inbox/ # received messages (plain text) | |
| sent/ # delivered messages | |
| who_am_i/ # identity.txt | |
| what_am_i_doing/ # tasks.txt | |
| remember/ # persistent memories (*.txt) | |
| notes/ # working notes | |
| ``` | |
| ### Key Features | |
| | Feature | Commands | Description | | |
| |---------|----------|-------------| | |
| | **Persistent Memory** | `remember`, `recall` | Store and search memories that survive context resets | | |
| | **Inter-Agent Messaging** | `send`, `inbox`, `reply` | Agents communicate via any transport | | |
| | **Identity Continuity** | `whoami`, `doing` | Maintain identity and task state across sessions | | |
| | **Ed25519 Signing** | Built-in | Messages are cryptographically signed | | |
| | **Trust Binding** | `trust-gen`, `trust-bind` | Cryptographic owner-agent binding | | |
| | **Zero Dependencies** | Python stdlib only | No external packages for the core | | |
| ### Install & Run | |
| ```bash | |
| pip install agentazall | |
| # Quick start with public relay: | |
| agentazall register --agent myagent | |
| # Or self-host everything: | |
| agentazall server --agenttalk # modern HTTPS API | |
| agentazall server --email # SMTP/IMAP/POP3 | |
| agentazall server --ftp # FTP (yes, from 1971) | |
| agentazall server --all # all three at once | |
| ``` | |
| ### Links | |
| - [GitHub Repository](https://github.com/cronos3k/AgentAZAll) β source, issues, Rust fast relay | |
| - [Project Page](https://agentazall.ai) β research paper, documentation | |
| - [PyPI Package](https://pypi.org/project/agentazall/) β `pip install agentazall` | |
| - License: AGPL-3.0-or-later | |
| """ | |
| # --------------------------------------------------------------------------- | |
| # Build Gradio UI | |
| # --------------------------------------------------------------------------- | |
| CSS = """ | |
| /* Monospace for filesystem displays */ | |
| .tree-view textarea, .file-view textarea { font-family: 'Fira Code', 'Consolas', 'Courier New', monospace !important; font-size: 0.82em !important; } | |
| /* Center panel subtle highlight */ | |
| .center-panel { border-left: 2px solid #6366f1 !important; border-right: 2px solid #6366f1 !important; padding: 0 8px !important; } | |
| /* Compact chatbot */ | |
| .compact-chat .message { font-size: 0.9em !important; } | |
| /* Status bar */ | |
| .status-bar { font-weight: bold; } | |
| /* Hide Gradio footer */ | |
| footer { display: none !important; } | |
| """ | |
| def _chatbot_kwargs(**extra) -> dict: | |
| """Build Chatbot kwargs compatible with Gradio 5 and 6.""" | |
| kw = dict(extra) | |
| if _GRADIO_MAJOR < 6: | |
| kw["type"] = "messages" | |
| return kw | |
| def build_demo() -> gr.Blocks: | |
| """Build the complete Gradio demo interface.""" | |
| blocks_kw: dict = {"title": "AgentAZAll β Dual-Agent Live Demo"} | |
| if _GRADIO_MAJOR < 6: | |
| blocks_kw["theme"] = gr.themes.Soft() | |
| blocks_kw["css"] = CSS | |
| with gr.Blocks(**blocks_kw) as demo: | |
| gr.Markdown( | |
| "# π§ AgentAZAll v1.0.23 β Dual-Agent Live Demo\n" | |
| "Two AI agents collaborate in real-time. **Watch the filesystem** in the center " | |
| "as they create memories, send messages, and build shared knowledge β " | |
| "all as plain text files.\n\n" | |
| "*Powered by [Qwen2.5-3B](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct) " | |
| "and [SmolLM2-1.7B](https://huggingface.co/HuggingFaceTB/SmolLM2-1.7B-Instruct) " | |
| "on ZeroGPU.*" | |
| ) | |
| # ============================================================== | |
| # Tab 1: Live Demo (three-panel) | |
| # ============================================================== | |
| with gr.Tab("Live Demo", id="demo"): | |
| with gr.Row(): | |
| # --- LEFT: Agent Alpha --- | |
| with gr.Column(scale=2): | |
| gr.Markdown("### π΅ Agent Alpha\n*Qwen2.5-3B Β· Research Director*") | |
| alpha_chatbot = gr.Chatbot( | |
| **_chatbot_kwargs( | |
| label="Agent Alpha", | |
| height=400, | |
| elem_classes=["compact-chat"], | |
| ) | |
| ) | |
| alpha_input = gr.Textbox( | |
| label="Message Alpha", | |
| placeholder="Ask Alpha something...", | |
| lines=2, | |
| ) | |
| alpha_send = gr.Button("Send to Alpha", variant="primary", size="sm") | |
| # --- CENTER: Filesystem Browser --- | |
| with gr.Column(scale=3, elem_classes=["center-panel"]): | |
| gr.Markdown("### π Live Filesystem\n*Every agent action creates real files*") | |
| tree_display = gr.Textbox( | |
| label="Directory Tree", | |
| lines=20, | |
| max_lines=30, | |
| interactive=False, | |
| elem_classes=["tree-view"], | |
| ) | |
| file_select = gr.Dropdown( | |
| label="Select file to view", | |
| choices=[], | |
| interactive=True, | |
| ) | |
| file_content = gr.Textbox( | |
| label="File Content (raw)", | |
| lines=10, | |
| max_lines=20, | |
| interactive=False, | |
| elem_classes=["file-view"], | |
| ) | |
| refresh_btn = gr.Button("π Refresh", size="sm") | |
| # --- RIGHT: Agent Beta --- | |
| with gr.Column(scale=2): | |
| gr.Markdown("### π’ Agent Beta\n*SmolLM2-1.7B Β· Creative Developer*") | |
| beta_chatbot = gr.Chatbot( | |
| **_chatbot_kwargs( | |
| label="Agent Beta", | |
| height=400, | |
| elem_classes=["compact-chat"], | |
| ) | |
| ) | |
| beta_input = gr.Textbox( | |
| label="Message Beta", | |
| placeholder="Ask Beta something...", | |
| lines=2, | |
| ) | |
| beta_send = gr.Button("Send to Beta", variant="primary", size="sm") | |
| # --- Control Bar --- | |
| gr.Markdown("---") | |
| with gr.Row(): | |
| autopilot_btn = gr.Button( | |
| "βΆ Start Autopilot", variant="primary", scale=2, | |
| ) | |
| stop_btn = gr.Button("β Stop", variant="stop", scale=1) | |
| turn_slider = gr.Slider( | |
| minimum=1, maximum=10, value=3, step=1, | |
| label="Max turns", scale=1, | |
| ) | |
| status_display = gr.Textbox( | |
| label="Status", | |
| value="Ready. Chat with either agent or click Start Autopilot.", | |
| interactive=False, | |
| scale=3, | |
| elem_classes=["status-bar"], | |
| ) | |
| reset_btn = gr.Button("π Reset Demo", variant="stop", scale=1) | |
| # --- Event Wiring --- | |
| # Manual chat with Alpha | |
| alpha_send.click( | |
| chat_alpha, | |
| [alpha_input, alpha_chatbot], | |
| [alpha_input, alpha_chatbot, tree_display, file_select, file_content], | |
| ) | |
| alpha_input.submit( | |
| chat_alpha, | |
| [alpha_input, alpha_chatbot], | |
| [alpha_input, alpha_chatbot, tree_display, file_select, file_content], | |
| ) | |
| # Manual chat with Beta | |
| beta_send.click( | |
| chat_beta, | |
| [beta_input, beta_chatbot], | |
| [beta_input, beta_chatbot, tree_display, file_select, file_content], | |
| ) | |
| beta_input.submit( | |
| chat_beta, | |
| [beta_input, beta_chatbot], | |
| [beta_input, beta_chatbot, tree_display, file_select, file_content], | |
| ) | |
| # File viewer | |
| file_select.change( | |
| read_file_content, | |
| [file_select], | |
| [file_content], | |
| ) | |
| # Refresh button | |
| refresh_btn.click( | |
| refresh_filesystem, | |
| [], | |
| [tree_display, file_select, file_content], | |
| ) | |
| # Autopilot | |
| autopilot_event = autopilot_btn.click( | |
| autopilot_loop, | |
| [alpha_chatbot, beta_chatbot, turn_slider], | |
| [alpha_chatbot, beta_chatbot, tree_display, file_select, file_content, status_display], | |
| ) | |
| stop_btn.click(None, cancels=[autopilot_event]) | |
| # Reset | |
| reset_btn.click( | |
| do_reset, | |
| [], | |
| [alpha_chatbot, beta_chatbot, tree_display, file_select, file_content, status_display], | |
| ) | |
| # Auto-load filesystem tree on page load | |
| demo.load( | |
| refresh_filesystem, | |
| [], | |
| [tree_display, file_select, file_content], | |
| ) | |
| # ============================================================== | |
| # Tab 2: How It Works | |
| # ============================================================== | |
| with gr.Tab("How It Works", id="docs"): | |
| gr.Markdown(HOW_IT_WORKS_MD) | |
| return demo | |
| # --------------------------------------------------------------------------- | |
| # Launch | |
| # --------------------------------------------------------------------------- | |
| def _find_free_port(start: int = 7860, end: int = 7960) -> int: | |
| """Find a free port in the given range.""" | |
| import socket | |
| for port in range(start, end + 1): | |
| try: | |
| with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: | |
| s.bind(("127.0.0.1", port)) | |
| return port | |
| except OSError: | |
| continue | |
| return start # fallback | |
| if __name__ == "__main__": | |
| port = _find_free_port() | |
| demo = build_demo() | |
| launch_kw: dict = { | |
| "server_name": "0.0.0.0", | |
| "server_port": port, | |
| "share": False, | |
| } | |
| if _GRADIO_MAJOR >= 6: | |
| launch_kw["theme"] = gr.themes.Soft() | |
| launch_kw["css"] = CSS | |
| demo.launch(**launch_kw) | |