a11oy / ayllu /lounge.py
betterwithage's picture
chore(sync): mirror backend .py + Dockerfile to Space (hf-sync-backend)
8fc7ac7 verified
Raw
History Blame Contribute Delete
5.01 kB
"""ayllu.lounge — the collaboration surface, learned from the tribe lounge/bus.
The tribe let souls talk in a shared room. a11oy keeps that, in-memory and honest: each
posted message carries a source label ("model-unverified" when a model answered,
else "persona-fallback"), so a reader never mistakes direct model text for grounded
Brain retrieval. `deliberate()` runs a BOUNDED council round — one honest turn
per persona — and never fabricates when no backend is injected. Publishing is
explicit opt-in; public ask/council calls keep their output out of this feed.
"""
from __future__ import annotations
import asyncio
import time
from typing import Any
class Lounge:
def __init__(self, capacity: int = 200) -> None:
self.capacity = int(capacity)
self.feed: list[dict[str, Any]] = []
def post(self, persona: str, text: str, *, source: str = "persona") -> dict[str, Any]:
entry = {"ts": time.time(), "persona": persona, "text": text, "source": source}
self.feed.append(entry)
if len(self.feed) > self.capacity:
self.feed = self.feed[-self.capacity:]
return entry
def recent(self, n: int = 50) -> list[dict[str, Any]]:
return self.feed[-int(n):]
async def deliberate(
self,
prompt: str,
personas: list,
*,
model_complete=None,
difficulty: float = 0.6,
two_person_attested: bool = False,
debate: bool = False,
publish_to_lounge: bool = False,
) -> dict[str, Any]:
from .loop import run_turn
async def _one_turn(p, turn_prompt: str):
return await run_turn(
p, turn_prompt, model_complete=model_complete,
difficulty=difficulty,
two_person_attested=two_person_attested)
# A council round is independent fan-out. Preserve input order in the
# evidence, but spend one bounded deadline instead of N sequential ones.
rounds = list(await asyncio.gather(*(
_one_turn(p, prompt) for p in personas
)))
for p, turn in zip(personas, rounds):
turn["round"] = 1
src = ("model-unverified" if turn.get("answer") is not None
and not turn.get("stub") else "persona-fallback")
if publish_to_lounge:
self.post(p.name, turn.get("answer") or turn.get("honesty"), source=src)
mode = "single-round"
# Debate-then-converge (after arXiv:2305.14325, Multiagent Debate): one
# bounded extra round where each persona reads its peers' round-1 positions,
# states dissent or agreement explicitly, and revises. EXACTLY two rounds —
# never an open-ended loop. Runs only when a real/stub backend is injected
# and at least two personas answered (an honest debate needs peers).
if debate and model_complete is not None:
positions = [
(r.get("persona"), r.get("answer"))
for r in rounds if r.get("answer")
]
if len(positions) >= 2:
mode = "debate"
debate_jobs = []
for p in personas:
peers = "\n\n".join(
f"[{name}] {ans[:1200]}" for name, ans in positions
if name != p.name
)
debate_prompt = (
"COUNCIL DEBATE ROUND (bounded: this is the final round).\n"
f"Original question:\n{prompt}\n\n"
f"Your peers' opening positions:\n{peers}\n\n"
"State explicitly where you agree or dissent, then give "
"your revised answer. Honest dissent beats false consensus."
)
debate_jobs.append((p, debate_prompt))
revised = list(await asyncio.gather(*(
_one_turn(p, turn_prompt) for p, turn_prompt in debate_jobs
)))
for (p, _turn_prompt), turn in zip(debate_jobs, revised):
turn["round"] = 2
src = ("model-unverified" if turn.get("answer") is not None
and not turn.get("stub") else "persona-fallback")
if publish_to_lounge:
self.post(p.name, turn.get("answer") or turn.get("honesty"),
source=src)
rounds.append(turn)
else:
mode = "single-round"
return {
"prompt": prompt,
"participants": [p.name for p in personas],
"rounds": rounds,
"mode": mode,
"published_to_lounge": publish_to_lounge,
"note": "bounded council; each turn honest (no fabrication when a model "
"backend is absent); debate mode = exactly two rounds, "
"after arXiv:2305.14325",
}