Spontaneous AI councils¶
lazytools.skills.council provides a free-form multi-agent council built on
LazyBridge AgentPool. It is intended for questions where several independent
perspectives should challenge one another before a recommendation is written.
Unlike a fixed debate pipeline, the council does not schedule speakers or manufacture rounds. Members choose whom to engage next, may update their vote at any point, and invite the moderator when they believe the discussion can close. Only the moderator receives the closing tool, and that tool refuses to close until the configured quorum is actually present.
Generic council¶
from lazybridge import Agent, LLMEngine
from lazytools.skills import WizengAImot
optimist = Agent(
name="optimist",
engine=LLMEngine(
"medium",
provider="anthropic",
system="Find opportunities without concealing risks.",
),
)
critic = Agent(
name="critic",
engine=LLMEngine(
"medium",
provider="deepseek",
system="Test assumptions and surface material downside.",
),
)
result = (
WizengAImot("Should we enter this market?", quorum=0.7)
.add(optimist)
.add(critic)
.run()
)
print(result.text())
Research and opening positions use Agent.parallel. The discussion itself is
an AgentPool: participants call route(agent_name, task) based on the
conversation rather than a predetermined order.
Shared debate history¶
Every debater and the moderator has a private Memory for their own
engine turns, but that alone would force them to recap each other from
scratch — nobody automatically sees what anyone else said, so the natural
move is to restate it, which duplicates the same content across several
members' memories and inflates cost on a long debate.
route() is wrapped per member so every exchange is also recorded once to
a single shared Memory (strategy="summary", same summarizer as the
private memories). That shared history is attached to every participant's
sources=[...] — LazyBridge's "shared use" mode for Memory/Store,
read live on every call, never a stale snapshot. The council protocol
tells members not to restate it. Memory.text() only ever renders a
running summary plus the last 5 exchanges, so sharing it doesn't reopen
unbounded growth. The final transcript is built from this same recording
as it happens, so it reflects the debate's real chronological order —
not just each agent's own turn order.
Reasoning level¶
Every member's engine is your own Agent, so you control its reasoning
directly (LLMEngine(thinking=...), ClaudeCodeEngine(reasoning_effort=...)).
For the default moderator and synthesiser — used whenever you don't pass
moderator=/synthesiser= yourself — WizengAImot(..., reasoning=True)
enables extended thinking on both. It's ignored once you supply your own
moderator/synthesiser agents; configure their engines directly instead.
standard_council(..., reasoning=True) forwards the same switch to its four
built-in members (not their researchers, which stay fast/cheap) and to the
default moderator/synthesiser.
deepseek_claude_news_council(..., reasoning="high") takes a graduated level
— "low", "medium" (default), "high", "xhigh", or "max" — applied to
the debater, moderator, and synthesiser only; the two fast evidence/risk
analysts always run with reasoning disabled, by design. DeepSeek's thinking
only distinguishes off ("low") from on (everything else); Claude's
reasoning_effort receives the level directly.
DeepSeek + Claude subscription news council¶
The current-news preset combines API-backed DeepSeek models with Claude Code agents authenticated through a Claude.ai subscription:
from lazytools.skills import deepseek_claude_news_council
council = deepseek_claude_news_council(
"How could the latest geopolitical developments affect European energy?",
news_db="/data/news.db",
)
result = council.run()
The roster is:
| Role | Engine | Reasoning |
|---|---|---|
| Evidence analyst | DeepSeek V4 Flash | disabled |
| Risk analyst | DeepSeek V4 Flash | disabled |
| Fast analyst | Claude Code Haiku | disabled |
| Senior debater | DeepSeek V4 Flash | max |
| Senior debater | Claude Code Sonnet | medium, adaptive thinking |
| Moderator | DeepSeek V4 Flash | max |
| Synthesiser | Claude Code Sonnet | medium, adaptive thinking |
The debater/moderator/synthesiser rows reflect the default reasoning="medium"
— pass reasoning="low"/"high"/"xhigh"/"max" to deepseek_claude_news_council
to change all four at once; the two analyst rows never change (see
Reasoning level).
Every participant receives the same LazyCrawler tool set backed by the same
SQLite news database. Claude Code agents additionally have native
WebSearch/WebFetch enabled.
Requirements¶
Install current LazyBridge with Claude Code support and LazyTools with web support:
pip install "lazybridge[claude-code]"
pip install "lazytoolkit[web] @ git+https://github.com/selvaz/LazyTools.git"
ClaudeCodeEngine release status
ClaudeCodeEngine is merged to LazyBridge main but not yet in a
tagged PyPI release as of this writing (the latest tag, 1.0.2,
predates it). Until a release ships, install LazyBridge from source —
e.g. pip install "lazybridge[claude-code] @ git+https://github.com/selvaz/LazyBridge.git@main"
— or pin to whichever tag first includes it once released.
Configure DeepSeek normally:
For Claude, do not set ANTHROPIC_API_KEY when subscription billing is
intended. Install Claude Code, run claude, and choose the Claude App / Claude.ai
subscription login. ClaudeCodeEngine then reuses that local login through the
Claude Agent SDK.
Set the news database path or pass it explicitly:
The preset fails loudly when the database is missing; it never replaces the requested current-news source with an empty in-memory cache.
Haiku availability
Claude Code exposes the haiku alias through the Agent SDK, but actual
availability depends on the installed Claude Code version and subscription.
Validate it with the target account before production use.
Debate memory¶
A free-form debate has no fixed number of turns — route() hops until the
moderator closes it. Left uncompressed, that can push the final synthesis
call past the model's context window on a long debate. Every debater's
memory and the moderator's memory therefore run
Memory(strategy="summary", summarizer=...), compressing older turns once
past 10 while keeping the last 10 verbatim. The summarizer defaults to a
cheap, non-reasoning DeepSeek agent (WizengAImot._default_memory_summarizer);
pass your own with memory_summarizer=:
from lazybridge import Agent, LLMEngine
fast_summarizer = Agent(
engine=LLMEngine("super_cheap", provider="deepseek", thinking=False),
name="summarizer",
)
council = WizengAImot(question, memory_summarizer=fast_summarizer)
Knowledge bases¶
Use knowledge(..., mode="static") for direct document context or
mode="skill" for a BM25 documentation bundle:
Static mode supports text, PDF, DOCX, and HTML through LazyTools document readers. Skill mode indexes text-oriented formats and exposes the resulting retriever to every participant.
Result contract¶
CouncilResult contains:
synthesis: final decision-ready report;quorum_reached: whether the latest votes meet the configured threshold;votes: latest structured vote from each member that voted;transcript: assistant contributions captured from council memories;question: the original question.
The recursion max_depth is a safety brake, not a debate schedule. The legacy
max_rounds option is retained only to derive a default depth when
max_depth is omitted.
API reference¶
lazytools.skills.council.WizengAImot ¶
WizengAImot(question: str = '', *, quorum: float = 0.7, max_rounds: int = 3, max_depth: int | None = None, synthesiser: Agent | None = None, moderator: Agent | None = None, reasoning: bool = False, memory_summarizer: Agent | None = None, session: Session | None = None)
Run a free-form council of pre-built LazyBridge agents.
max_rounds is retained for compatibility, but it does not schedule
rounds. Unless max_depth is supplied, it only helps derive a generous
AgentPool recursion limit. The conversation itself has no speaker order.
reasoning enables extended thinking on the default moderator
and synthesiser only (ignored once moderator=/synthesiser=
supply your own agents — configure their engines directly instead).
memory_summarizer compresses the free-form debate as it runs
(every debater's memory and the moderator's memory use
Memory(strategy="summary", summarizer=...)). A long, unscripted
debate has no fixed number of turns, so leaving it uncompressed
risks the final synthesis call exceeding the model's context
window. Defaults to a cheap, non-reasoning DeepSeek agent.
Source code in src/lazytools/skills/council.py
add ¶
Add a member and, optionally, a separate research agent.
Source code in src/lazytools/skills/council.py
knowledge ¶
knowledge(path: str | list[str], *, name: str = 'knowledge', description: str = 'Council knowledge base.', mode: str = 'skill', output_root: str = '/tmp/wizengaimot_skills', rebuild: bool = False, extensions: str = 'txt,md,pdf,docx,html', max_chars: int = 20000) -> WizengAImot
Attach grounded knowledge to every participant.
Source code in src/lazytools/skills/council.py
lazytools.skills.council.CouncilResult
dataclass
¶
CouncilResult(question: str, quorum_reached: bool, votes: list[dict], transcript: list[dict], synthesis: str)
lazytools.skills.council.standard_council ¶
Return a ready-made four-member, multi-provider council.
reasoning enables extended thinking on the four debating members
(not their researchers, which stay fast/cheap) and on the default
moderator and synthesiser.
Source code in src/lazytools/skills/council.py
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 | |
lazytools.skills.council.deepseek_claude_news_council ¶
deepseek_claude_news_council(question: str = '', *, news_db: str | Path | None = None, reasoning: str = 'medium', **kwargs) -> WizengAImot
Build the DeepSeek + Claude Code subscription council.
Composition:
- two DeepSeek V4 Flash analysts with reasoning disabled;
- one Claude Haiku analyst via Claude Agent SDK with reasoning disabled;
- one DeepSeek V4 Flash debater with maximum reasoning;
- one Claude Sonnet debater via Claude Agent SDK with medium reasoning.
Every member, moderator, and synthesiser receives the same LazyCrawler
tools backed by the same current-news database. Claude uses the local
Claude Code/Claude.ai subscription login, never ANTHROPIC_API_KEY.
reasoning (one of "low", "medium", "high", "xhigh",
"max") controls the debater, moderator, and synthesiser only — the
two fast evidence/risk analysts always run with reasoning disabled,
by design.
Requires current LazyBridge with the claude-code extra, LazyTools with
the web extra, an authenticated Claude Code CLI, DEEPSEEK_API_KEY,
and either news_db=... or LAZYCRAWLER_NEWS_DB.
Source code in src/lazytools/skills/council.py
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 | |