API reference¶
Autogenerated from docstrings.
Safety¶
lazytools.safety.Allowlist ¶
Case-insensitive, string-normalized target allow-list.
None means "no allow-list configured" → permits everything. An empty
iterable means "deny everything".
Source code in src/lazytools/safety/allowlist.py
lazytools.safety.ConfirmationGate ¶
One-shot, target-bound confirmation grants for dangerous actions.
Not a sticky boolean: each grant authorizes exactly one action, so an
approved single message can never silently authorize a flood. Grants are
matched from most to least specific: a target+scope grant before a
target-only one, then an any-target+scope grant before an any-target one. A
scope-bound grant is never spendable when no scope (None) is supplied at
consume time. No process-global mutable state — grants live on the instance.
The gate is scope-agnostic: the caller decides what scope means (in
LazyPulse it is the running task id, read from
:func:lazytools.safety.current_scope) and passes it in. This is what keeps
the safety layer free of any orchestration dependency.
Source code in src/lazytools/safety/gates.py
grant ¶
grant_any ¶
consume ¶
Spend one matching grant for target in scope; True if found.
Returns True immediately when the gate is disabled. A scope-bound
grant is only matched when the same scope is supplied here.
Source code in src/lazytools/safety/gates.py
lazytools.safety.ActionBlocked ¶
Bases: PermissionError
Base for dangerous-action denials (allow-list / confirmation).
Subclasses PermissionError so existing except PermissionError
handlers keep working. Carries an audit-friendly message that names the
action and the reason and never leaks secrets.
lazytools.safety.current_scope ¶
Gmail¶
lazytools.connectors.gmail.GmailTools ¶
GmailTools(client: GmailService, *, allowed_recipients: list[str] | None = None, require_confirmation: bool = True)
A ToolProvider wrapping a :class:GmailService for the worker.
Exposes four tools: gmail_list_emails, gmail_get_email,
gmail_create_draft, and gmail_send.
The underlying :class:~lazytools.connectors.gmail.client.GmailClient
is thread-safe (serialises calls through an internal lock), so all four
tools are safe to invoke from concurrent PulseAgent task workers.
Source code in src/lazytools/connectors/gmail/tools.py
require_confirmation
property
¶
Whether a send needs an outstanding confirmation (public attribute).
confirm_once ¶
Authorize exactly one send to any recipient (subject to the
allow-list). Call once per approved message. Pass task_id= to bind
the grant to a single task so a concurrent task cannot consume it.
Source code in src/lazytools/connectors/gmail/tools.py
confirm_send ¶
Authorize exactly one send to a specific recipient — the tighter,
preferred grant. Pass task_id= to also bind it to a single task.
Source code in src/lazytools/connectors/gmail/tools.py
lazytools.connectors.gmail.GmailClient ¶
Production :class:GmailService backed by googleapiclient.
All methods acquire a per-instance lock before touching the underlying
googleapiclient resource so the client is safe to call from multiple
threads concurrently (e.g. parallel PulseAgent task workers).
Source code in src/lazytools/connectors/gmail/client.py
from_credentials
classmethod
¶
Build a client from an OAuth client-secret + cached token file.
Imports the Google libraries lazily; raises a friendly
ImportError if the gmail extra is not installed.
Source code in src/lazytools/connectors/gmail/client.py
lazytools.connectors.gmail.parse_authentication_results ¶
parse_authentication_results(header: str | None, *, trusted_authserv_id: str | None = None) -> dict[str, bool]
Return {"dkim": bool, "spf": bool, "dmarc": bool}.
True means an authoritative result token for the method was pass.
A missing or empty header yields all-False.
When trusted_authserv_id is set (e.g. "mx.google.com"), the
header is accepted only if its leading authserv-id is exactly that
value. A forged header with a different authserv-id (or no authserv-id at
all) is rejected as all-False. The match is exact rather than a
prefix, so neither evil-mx.google.com nor mx.google.com.evil.com
is accepted. The caller is responsible for passing the first / top-most
Authentication-Results header from the message — the one prepended by
the receiving MTA — rather than a later one.
Source code in src/lazytools/connectors/gmail/auth.py
Telegram¶
lazytools.connectors.telegram.TelegramTools ¶
TelegramTools(client: TelegramService, *, allowed_chat_ids: list[int | str] | None = None, require_confirmation: bool = True)
A ToolProvider wrapping a :class:TelegramService for the worker.
Source code in src/lazytools/connectors/telegram/tools.py
require_confirmation
property
¶
Whether a send needs an outstanding confirmation (public attribute).
confirm_once ¶
Authorize exactly one send to any chat (subject to the allow-list).
Pass task_id= to bind the grant to a single task so a concurrent
task cannot consume it.
Source code in src/lazytools/connectors/telegram/tools.py
confirm_send ¶
Authorize exactly one send to a specific chat — the tighter grant.
Pass task_id= to also bind it to a single task.
Source code in src/lazytools/connectors/telegram/tools.py
lazytools.connectors.telegram.TelegramClient ¶
Production :class:TelegramService backed by the Bot API over HTTPS.
Source code in src/lazytools/connectors/telegram/client.py
from_token
classmethod
¶
Build a client from a bot token (obtained from @BotFather).
Imports httpx lazily; raises a friendly ImportError if the
telegram extra is not installed.
Source code in src/lazytools/connectors/telegram/client.py
MCP¶
lazytools.connectors.mcp.MCP ¶
Public factory for :class:MCPServer instances.
stdio
classmethod
¶
stdio(name: str, *, command: str, args: list[str] | None = None, env: dict[str, str] | None = None, namespace: bool = True, prefix: str | None = None, allow: Iterable[str] | None = None, deny: Iterable[str] | None = None, cache_tools_ttl: float | None = MCPServer._DEFAULT_CACHE_TTL) -> MCPServer
Build an MCP server bound to a stdio (subprocess) transport.
allow= (or deny=) is required — same deny-by-default
posture as :meth:http. A warn-and-proceed default would expose
every advertised tool to the LLM silently; that's a non-trivial
blast radius for filesystem / git / shell MCP servers, so we fail
at construction instead. Pass allow=["*"] to opt every
advertised tool in explicitly after auditing the surface.
Source code in src/lazytools/connectors/mcp/server.py
http
classmethod
¶
http(name: str, url: str, *, headers: dict[str, str] | None = None, namespace: bool = True, prefix: str | None = None, allow: Iterable[str] | None = None, deny: Iterable[str] | None = None, cache_tools_ttl: float | None = MCPServer._DEFAULT_CACHE_TTL) -> MCPServer
Build an MCP server bound to a Streamable HTTP transport.
allow= is required. Omitting it raises ValueError because
a remote server could advertise any number of tools and silently
exposing them all to the LLM is a security mistake. Pass an explicit
list of the tools you want to expose::
MCP.http("github", url, allow=["create_issue", "list_prs"])
To permit all tools advertised by a server you fully control::
MCP.http("internal", url, allow=["*"])
Source code in src/lazytools/connectors/mcp/server.py
from_transport
classmethod
¶
from_transport(name: str, transport: _Transport, *, namespace: bool = True, prefix: str | None = None, allow: Iterable[str] | None = None, deny: Iterable[str] | None = None, cache_tools_ttl: float | None = MCPServer._DEFAULT_CACHE_TTL) -> MCPServer
Build an MCP server from a custom :class:_Transport.
Useful for tests (in-process fake transport) or for adapters to
non-standard MCP variants. The transport must implement the
abstract :class:_Transport interface.
Source code in src/lazytools/connectors/mcp/server.py
lazytools.connectors.mcp.MCPServer ¶
MCPServer(name: str, transport: _Transport, *, namespace: bool = True, prefix: str | None = None, allow: Iterable[str] | None = None, deny: Iterable[str] | None = None, cache_tools_ttl: float | None = _DEFAULT_CACHE_TTL)
A tool provider backed by an MCP server.
Add it directly to Agent(tools=[...]); the framework calls
:meth:as_tools to expand it into individual :class:Tool entries.
Tool names are namespaced as "<server-name>.<mcp-tool-name>" by
default; pass namespace=False to keep the raw names, or
prefix="..." to override.
The transport connects lazily on first :meth:as_tools. For explicit
cleanup, use the server as an async context manager::
async with MCP.stdio("fs", command="...", args=[...]) as fs:
agent = Agent("claude-opus-4-8", tools=[fs])
await agent.run("...")
Without that, the transport stays open for the process lifetime; the underlying subprocess is normally cleaned up when the parent exits.
Closure is terminal. Once :meth:aclose (or the async with
block) finishes, the server is single-shot: a subsequent
:meth:aconnect / :meth:as_tools raises RuntimeError.
Construct a new MCPServer if you need to re-use the same
transport configuration.
Source code in src/lazytools/connectors/mcp/server.py
aconnect
async
¶
Connect the underlying transport. Idempotent.
Source code in src/lazytools/connectors/mcp/server.py
alist_tools
async
¶
Discover and wrap the server's tools.
Cached for cache_tools_ttl seconds (default 60 s). Once the
cache expires the next call re-fetches from the upstream
transport so an MCP server that hot-loads or unloads tools is
eventually reflected in the agent's tool list.
Pass cache_tools_ttl=None to disable expiry entirely and
:meth:invalidate_tools_cache to flush explicitly.
Source code in src/lazytools/connectors/mcp/server.py
invalidate_tools_cache ¶
Drop the cached tool list so the next call re-fetches.
Use this when an out-of-band signal tells you the MCP server's tool registry has changed (plugin install / uninstall, hot reload). No-op when nothing is cached yet.
Source code in src/lazytools/connectors/mcp/server.py
aclose
async
¶
Close the underlying transport. Idempotent.
Source code in src/lazytools/connectors/mcp/server.py
as_tools ¶
Sync wrapper around :meth:alist_tools. Called by build_tool_map.
Triggers a lazy connect on first use. If the call happens inside
an already-running event loop, the work is dispatched to a worker
thread (mirrors :meth:lazybridge.Tool.run_sync).
Source code in src/lazytools/connectors/mcp/server.py
Code Support Agent¶
lazytools.connectors.code_support.claude_code ¶
claude_code(task: str, *, mode: str = 'read', cwd: str | None = None, session_id: str | None = None, timeout: float = 300.0) -> str
Delegate a task to Claude Code CLI and return the result as a string.
Parameters¶
task:
Instruction for Claude Code.
mode:
"read" (default) — read-only analysis (Read, Bash, Grep, Glob).
"write" — may edit files (acceptEdits permission mode).
"plan" — plan mode, no file modifications.
cwd:
Working directory for the subprocess.
session_id:
If given, resumes an existing Claude Code session via --resume.
timeout:
Maximum seconds for the subprocess. Set tool_timeout=None on
LLMEngine so the engine never cancels before the subprocess
finishes (zombie-process hazard when engine fires first).
Notes¶
Auth is left to the Claude Code CLI itself: it reads its own on-disk
login (~/.claude/.credentials.json), and the subprocess inherits the
current environment, so CLAUDE_CODE_OAUTH_TOKEN (from
claude setup-token) or ANTHROPIC_API_KEY are honoured if set. We do
not synthesize CLAUDE_CODE_OAUTH_TOKEN from the credentials file — that
env var is a token string, not the JSON store, and overriding it would
break a valid disk login.
Source code in src/lazytools/connectors/code_support/_claude_code.py
lazytools.connectors.code_support.claude_code_mcp ¶
claude_code_mcp(*, name: str = 'claude_code', allow: Iterable[str] | None = None, deny: Iterable[str] | None = None, args: list[str] | None = None, env: dict[str, str] | None = None, namespace: bool = True, prefix: str | None = None, cache_tools_ttl: float | None = 60.0) -> MCPServer
Claude Code as an MCP server (claude mcp serve).
Returns an :class:~lazytools.connectors.mcp.MCPServer exposing Claude
Code's own tools (View, Edit, LS, Bash, …) over stdio. Drop it straight into
Agent(tools=[claude_code_mcp(allow=["*"])]).
allow= (or deny=) is required — deny-by-default, the same
posture as :meth:MCP.stdio. The patterns match the namespaced tool
names, e.g. allow=["claude_code.View", "claude_code.LS"] (or
allow=["*"] after auditing the surface). Tool names are not hardcoded
here because they are owned by the Claude Code version you have installed;
discover them by running with allow=["*"] once and inspecting the map.
Parameters¶
name:
Server name and default namespace prefix ("claude_code").
allow / deny:
fnmatch globs against the namespaced tool name (deny-by-default).
args:
Extra args appended after mcp serve (rarely needed).
env:
Extra environment for the subprocess. Auth is otherwise inherited
from the parent environment / the CLI's own on-disk login.
namespace / prefix / cache_tools_ttl:
Forwarded to :meth:MCP.stdio unchanged.
Requires the mcp extra: pip install lazytoolkit[mcp].
Source code in src/lazytools/connectors/code_support/_claude_code.py
lazytools.connectors.code_support.codex ¶
codex(task: str, *, mode: str = 'read', cwd: str | None = None, resume_last: bool = False, timeout: float = 300.0, skip_git_check: bool = True) -> str
Delegate a task to the Codex CLI and return the result as a string.
Parameters¶
task:
Instruction for Codex.
mode:
"read" (default) — read-only sandbox (-s read-only).
"write" — workspace-write sandbox, full-auto (no interactive
confirmation prompts). Ideally run inside a git repo.
cwd:
Working directory for the subprocess.
resume_last:
If True, continues the most recent Codex session in the working
directory via exec resume --last.
timeout:
Maximum seconds for the subprocess. Set tool_timeout=None on
LLMEngine so the engine never cancels before the subprocess
finishes (zombie-process hazard when engine fires first).
skip_git_check:
Pass --skip-git-repo-check. Required outside a git repo.
In mode="write" a git repo is recommended for reliable behaviour.
Source code in src/lazytools/connectors/code_support/_codex.py
lazytools.connectors.code_support.codex_mcp ¶
codex_mcp(*, name: str = 'codex', allow: Iterable[str] | None = None, deny: Iterable[str] | None = None, args: list[str] | None = None, env: dict[str, str] | None = None, namespace: bool = True, prefix: str | None = None, cache_tools_ttl: float | None = 60.0) -> MCPServer
Codex as an MCP server (codex mcp-server).
Returns an :class:~lazytools.connectors.mcp.MCPServer exposing Codex's
MCP interface over stdio. Drop it into
Agent(tools=[codex_mcp(allow=["*"])]).
Warning
Codex's MCP-server interface is experimental (per OpenAI's docs) and may change without notice. Pin your Codex version if you depend on the exposed tool shape.
allow= (or deny=) is required — deny-by-default. Patterns match
the namespaced tool name (codex.*). Tool names are not hardcoded because
they depend on the installed Codex version; discover them with
allow=["*"] once.
Parameters¶
name:
Server name and default namespace prefix ("codex").
allow / deny:
fnmatch globs against the namespaced tool name (deny-by-default).
args:
Extra args appended after mcp-server (rarely needed).
env:
Extra environment for the subprocess. Auth is otherwise inherited
from codex login / the current shell environment.
namespace / prefix / cache_tools_ttl:
Forwarded to :meth:MCP.stdio unchanged.
Requires the mcp extra: pip install lazytoolkit[mcp].
Source code in src/lazytools/connectors/code_support/_codex.py
lazytools.connectors.code_support.build_cli_collaboration ¶
build_cli_collaboration(*, name: str = 'cli_collaboration', description: str | None = None, claude_model: str = 'claude-opus-4-8', codex_model: str = 'gpt-5.4', synthesizer_model: str = 'claude-opus-4-8', executor_model: str = 'claude-opus-4-8', execute: bool = True) -> Agent
Build the Claude Code + Codex collaboration pipeline as a reusable tool.
Returns a named :class:~lazybridge.Agent (Plan engine) that you drop
straight into Agent(tools=[build_cli_collaboration()]) — the same way you
pass the :func:claude_code / :func:codex function tools. Because an
Agent is a tool in LazyBridge, the whole multi-agent pipeline appears
to the parent agent as a single callable taking one task string.
Parameters¶
name:
Tool name the parent agent sees. Must be explicit (used as the tool-map
key); defaults to "cli_collaboration".
description:
Tool description shown to the parent LLM. Defaults to a summary of the
pipeline's behaviour.
claude_model:
Model driving the Claude-Code analyst (step 1).
codex_model:
Model driving the Codex analyst/critic (step 2).
synthesizer_model:
Model that merges the two analyses into one plan (step 3).
executor_model:
Model that implements the plan via claude_code(mode='write') (step 4).
execute:
When True (default) the pipeline ends by implementing the plan
(writes files). When False it stops after synthesis — a read-only
"analyse + plan" pipeline that never modifies the codebase.
Notes¶
The claude_analyst writes its analysis into a shared Memory that the
codex_analyst reads via sources=. This is safe because Plan runs
steps strictly sequentially — there is no concurrent writer/reader race.
Source code in src/lazytools/connectors/code_support/_collaboration.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
lazytools.connectors.code_support.check_clis_available ¶
Return availability of 'claude' and 'codex' in PATH.
Returns a {"claude": bool, "codex": bool} dict. Call this at startup
to surface missing CLIs immediately rather than at the first tool call.
Source code in src/lazytools/connectors/code_support/__init__.py
Gateway¶
lazytools.connectors.gateway.ExternalToolProvider ¶
ExternalToolProvider(client: ExternalToolClient, *, specs: Iterable[ExternalToolSpec | Mapping[str, Any]] | None = None, include: Iterable[str] | None = None, exclude: Iterable[str] | None = None, name_prefix: str = '', strict: bool | None = None)
Expose an external tool registry as a LazyBridge tool provider.
Agent(tools=[ExternalToolProvider(client)]) expands the provider
into normal :class:Tool objects through LazyBridge's existing
_is_lazy_tool_provider hook.
Source code in src/lazytools/connectors/gateway/__init__.py
lazytools.connectors.gateway.JsonHttpExternalToolClient ¶
JsonHttpExternalToolClient(base_url: str, *, api_key: str | None = None, headers: Mapping[str, str] | None = None, timeout: float = 30.0, tools_path: str = '/tools', call_path_template: str = '/tools/{name}/call')
Small stdlib HTTP client for a JSON external-tool gateway.
Default endpoint contract:
- GET {base_url}/tools returns either [{...}] or {"tools": [{...}]}.
- POST {base_url}/tools/{name}/call with {"arguments": {...}} returns JSON.
This class is intentionally narrow. For Pipedream/Composio/Arcade,
wrap their SDK/API behind :class:ExternalToolClient when their HTTP
shape differs from the default contract.
Source code in src/lazytools/connectors/gateway/__init__.py
lazytools.connectors.gateway.ExternalToolSpec
dataclass
¶
A remotely hosted tool definition.
parameters is the provider-agnostic JSON Schema object used by
LazyBridge providers when advertising tools to an LLM.
from_mapping
classmethod
¶
Build a spec from common external registry shapes.
Accepted inputs:
- {"name", "description", "parameters"}
- OpenAI-style {"function": {"name", "description", "parameters"}}
Source code in src/lazytools/connectors/gateway/__init__.py
Documents¶
lazytools.documents.read_folder_docs ¶
read_folder_docs(path: str, extensions: str = 'txt,md,pdf,docx,html', html_mode: str = 'parsed', recursive: bool = False, output_format: str = 'text', *, base_dir: str | None = None, max_file_bytes: int | None = DEFAULT_MAX_FILE_BYTES, max_files: int | None = DEFAULT_MAX_FILES) -> str
Read documents from a file or folder and return their text content.
Accepts either a single file path or a folder path. When given a folder, scans for all matching files (optionally recursive). When given a file, reads that file directly regardless of the extensions filter.
Supported formats: .txt, .md, .pdf, .docx, .html/.htm. HTML files can be returned as clean extracted body text, raw HTML, or both.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Path to a single file OR a folder to scan. File example: "/reports/q4.pdf" Folder example: "/reports" |
required |
extensions
|
str
|
Comma-separated list of file extensions to include when scanning a folder. Ignored when path points to a single file. Supported values: txt, md, pdf, docx, html. Default: "txt,md,pdf,docx,html" (all formats). Example: "pdf,docx" to read only PDFs and Word files. |
'txt,md,pdf,docx,html'
|
html_mode
|
str
|
How to process HTML and HTM files. "parsed" — clean readable text extracted by trafilatura (default). "full" — raw HTML source, unmodified. "both" — parsed body text first, then raw HTML source. |
'parsed'
|
recursive
|
bool
|
Whether to search subfolders recursively when path is a folder. False (default) — top-level files only. True — all files in all subfolders. Ignored when path points to a single file. |
False
|
output_format
|
str
|
How to format the combined output.
"text" (default) — a single human/LLM-readable string with headers.
"json" — a JSON object with a "records" array (one entry per file,
each with per-file metadata and content) plus truncation
fields: "truncated" (bool), "max_files" (the cap applied), and
"total_found" (matches discovered before the cap). Parse the
output with |
'text'
|
Returns:
| Type | Description |
|---|---|
str
|
A single string. For |
str
|
human/LLM-readable document text; for |
str
|
serialized JSON object described above. Two cases always return a plain |
str
|
(non-JSON) description string regardless of |
str
|
path does not exist, and when a scanned folder contains no files |
str
|
matching |
Source code in src/lazytools/documents/read_docs.py
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | |
lazytools.documents.read_docs_tools ¶
read_docs_tools(*, base_dir: str, max_file_bytes: int | None = DEFAULT_MAX_FILE_BYTES, max_files: int | None = DEFAULT_MAX_FILES) -> list[Tool]
Return a single-element list with read_folder_docs wrapped as a Tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_dir
|
str
|
Sandbox directory — required. |
required |
max_file_bytes
|
int | None
|
Per-file size ceiling; a larger file is reported as
skipped instead of read. Defaults to |
DEFAULT_MAX_FILE_BYTES
|
max_files
|
int | None
|
Ceiling on the number of files read per folder scan.
Defaults to |
DEFAULT_MAX_FILES
|
Source code in src/lazytools/documents/read_docs.py
Skills¶
lazytools.skills.build_skill ¶
build_skill(source_dirs: Annotated[list[str], 'One or more folders containing documentation to index.'], skill_name: Annotated[str, 'Skill name — used as the bundle folder name and title.'], output_root: Annotated[str, 'Parent directory for the generated bundle.'] = './generated_skills', description: Annotated[str, 'What this skill covers (used in SKILL.md and tool description).'] = '', usage_notes: Annotated[str, 'Extra operational rules appended to SKILL.md.'] = '', include_extensions: Annotated[list[str], 'File extensions to index.'] = list(DEFAULT_EXTENSIONS), chunk_size: Annotated[int, 'Maximum characters per chunk.'] = 1800, chunk_overlap: Annotated[int, 'Overlap between char-mode chunks.'] = 180, copy_sources: Annotated[bool, 'Copy original docs into the bundle under sources/.'] = False, overwrite: Annotated[bool, 'Replace an existing bundle with the same name.'] = True, max_chars_per_file: Annotated[int, 'Safety cap on characters read per file.'] = 200000) -> dict[str, Any]
Index documentation folders and write a portable skill bundle to disk.
The bundle contains SKILL.md (LLM instructions), manifest.json (metadata + avgdl for BM25), vocab.json (Robertson IDF weights), and chunks.jsonl. Returns a metadata dict: skill_dir, indexed_files, total_chunks, avgdl.
Source code in src/lazytools/skills/doc_skills.py
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 | |
lazytools.skills.query_skill ¶
query_skill(skill_dir: Annotated[str, 'Path to a skill bundle created by build_skill().'], task: Annotated[str, 'Question or task to answer from the indexed documentation.'], mode: Annotated[Literal['auto', 'answer', 'extract', 'locate', 'summarize'], "Execution mode. 'auto' detects intent from the task wording."] = 'auto', top_k: Annotated[int, 'Number of chunks to retrieve.'] = 8, max_chars: Annotated[int, 'Maximum characters in the returned context brief.'] = 10000, include_quotes: Annotated[bool, 'Append full excerpts after evidence bullets.'] = True) -> str
Retrieve the most relevant chunks via BM25 and return a grounded context brief ready to be injected into an LLM's context window.
Source code in src/lazytools/skills/doc_skills.py
lazytools.skills.skill_tools ¶
skill_tools(*, skill_dir: Annotated[str, 'Path to a skill bundle created by build_skill().'], name: Annotated[str | None, 'Tool name exposed to the agent.'] = None, description: Annotated[str | None, 'Tool description.'] = None, strict: Annotated[bool, 'Strict JSON schema validation.'] = False) -> list[Tool]
Return a single-element list containing a query_skill() Tool ready to be passed to any agent or pipeline.
Source code in src/lazytools/skills/doc_skills.py
lazytools.skills.skill_builder_tools ¶
skill_builder_tools(*, name: Annotated[str, 'Tool name.'] = 'build_doc_skill', description: Annotated[str, 'Tool description.'] = 'Index documentation folders into a reusable local skill bundle. Call this to transform one or more documentation folders into a queryable local skill.', strict: Annotated[bool, 'Strict JSON schema validation.'] = False) -> list[Tool]
Return a single-element list containing a Tool that builds skill bundles.
Source code in src/lazytools/skills/doc_skills.py
lazytools.skills.skill_pipeline ¶
skill_pipeline(*, skill_dir: Annotated[str, 'Path to a skill bundle.'], provider: Annotated[str | Any, 'LazyBridge provider alias or instance.'] = 'anthropic', router_model: Annotated[str | None, 'Model for the task-sharpening router.'] = None, executor_model: Annotated[str | None, 'Model for the grounded-answer executor.'] = None, session: Annotated[Any, 'Optional Session. Created if omitted.'] = None, native_tools: Annotated[list | None, 'Provider-native tools for the executor.'] = None) -> Tool
Two-step pipeline exposed as a single Tool.
- Router — rewrites the user task into a retrieval-optimised query.
- Executor — calls skill_tools() and synthesises a grounded answer.
Returns an Agent.chain(router, executor).as_tool().
Source code in src/lazytools/skills/doc_skills.py
Planners¶
These ship in the LazyBridge core (lazybridge.ext.planners), not in
lazytoolkit — documented here for completeness. See the
Planners guide. orchestrator_agent /
blackboard_orchestrator_agent are the canonical names; the
make_* symbols below are the same callables.
The blackboard planner (
make_blackboard_planner/blackboard_orchestrator_agent) takes the same arguments — see the Blackboard guide for its full reference.
lazybridge.ext.planners.make_planner ¶
make_planner(agents: list[Agent], *, model: str = 'claude-opus-4-7', system: str | None = None, name: str = 'planner', verbose: bool = False, verify: Agent | None = None, max_verify: int = 3) -> Agent
Build a planner :class:Agent over the given sub-agents.
The returned agent has
- each sub-agent in
agentsas a direct tool (so it can call one when that's enough); - five builder tools (
create_plan,add_step,inspect_plan,run_plan,discard_plan) that compose a :class:Planone step at a time, with local validation per step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agents
|
list[Agent]
|
The sub-agents the planner may dispatch to. Each must have
a unique |
required |
model
|
str
|
Provider model id for the planner LLM. Default
|
'claude-opus-4-7'
|
system
|
str | None
|
Override the planner's system prompt. By default we prepend
"You are a generalist assistant." to :data: |
None
|
name
|
str
|
Display name for the planner agent. |
'planner'
|
verbose
|
bool
|
If True, print event traces to stdout. |
False
|
verify
|
Agent | None
|
Optional judge :class: |
None
|
max_verify
|
int
|
Max judge attempts when |
3
|
Returns:
| Type | Description |
|---|---|
Agent
|
A configured planner :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
Source code in lazybridge/ext/planners/builder.py
lazybridge.ext.planners.make_plan_builder_tools ¶
Five builder tools that share state via closure.
Returns [create_plan, add_step, inspect_plan, run_plan, discard_plan].
The state is per-factory-instance — call make_plan_builder_tools
fresh for each planner agent (or each session) if you want isolated
blackboards. run_plan and discard_plan consume the plan from
the dict, so memory stays bounded as long as the planner finishes its
plans. max_plans is a hard cap on concurrent in-progress plans
(oldest-evicted on overflow) so a misbehaving planner can't leak memory.
Source code in lazybridge/ext/planners/builder.py
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 | |
lazybridge.ext.planners.PlanSpec ¶
Bases: BaseModel
The argument shape of execute_plan.
lazybridge.ext.planners.StepSpec ¶
Bases: BaseModel
One node in the plan DAG.
Composition sugar¶
chain and parallel are Agent classmethods in the LazyBridge core. See the
Composition sugar guide.
lazybridge.Agent.chain
classmethod
¶
Run agents sequentially: output of each becomes input to the next.
Source code in lazybridge/agent.py
lazybridge.Agent.parallel
classmethod
¶
parallel(*agents: Agent, concurrency_limit: int | None = None, step_timeout: float | None = None, **kwargs: Any) -> ParallelAgent
Deterministic fan-out: run agents concurrently on the same task.
Returns a :class:ParallelAgent whose __call__ produces a
single :class:Envelope — labelled-text join of every branch's
output, with transitive cost rollup. For typed access to per-branch
envelopes call ParallelAgent.run_branches(task) (async).
Use this when you know you want N things to happen in
parallel. If you want the LLM to decide whether to call agents
in parallel (and which, and how), don't use this — pass them as
tools=[...] on a regular Agent instead; the engine emits
parallel tool calls automatically when the model requests them.
Source code in lazybridge/agent.py
lazybridge.ParallelAgent ¶
ParallelAgent(agents: list[Agent], *, concurrency_limit: int | None = None, step_timeout: float | None = None, name: str = 'parallel', description: str | None = None, session: Any | None = None)
Deterministic fan-out over N agents — the shape behind :meth:Agent.parallel.
Pre-scripted parallel runner. Every input agent receives the same
task; the N branch results are folded into a single :class:Envelope
via labelled-text join — same shape as :class:Plan's
from_parallel_all aggregator. Cost roll-up is transitive.
The first non-None branch error propagates as the wrapper's
error so downstream consumers can short-circuit.
Prefer :class:Agent with tools=[...] when you want the engine
(LLM, Supervisor, Plan) to decide dynamically which tools to invoke
and when — parallel execution is automatic on that path.
Per-branch typed access: call :meth:run_branches (async) when you
need list[Envelope] rather than the joined wrapper.
Source code in lazybridge/agent.py
run_branches
async
¶
Async per-branch entry point — returns one Envelope per
input agent in input order. Use this when you need typed
access to individual branch results; for the framework-uniform
single-Envelope view, use :meth:run or __call__.
Source code in lazybridge/agent.py
run
async
¶
Run every branch and return one folded :class:Envelope.
The wrapper's payload is the labelled-text join of every
branch's .text(); metadata.nested_* rolls every branch's
cost up so the outer envelope reports total spend. The first
non-None branch error propagates as the wrapper's error.
For typed per-branch access, call :meth:run_branches.
Source code in lazybridge/agent.py
as_tool ¶
Expose the fan-out runner as a single :class:Tool.
Just delegates to :meth:run — same labelled-text Envelope as
every direct caller sees, so a ParallelAgent passed in
tools=[...] produces output identical to a hand-call.
Source code in lazybridge/agent.py
Human-in-the-loop¶
These ship in lazybridge.ext.hil. The *_agent factories return an Agent;
HumanEngine / SupervisorEngine are Engines you pass via Agent(engine=…).
See the Human-in-the-loop guide.
lazybridge.ext.hil.human_agent ¶
human_agent(*, timeout: float | None = None, ui: Literal['terminal', 'web'] | Any = 'terminal', default: str | None = None, **agent_kwargs: Any) -> Agent
Build a human-input :class:Agent (approval gate / form-style HIL).
Symmetric counterpart of Agent.from_<kind>(...) for the
:class:HumanEngine. Use this for synchronous human input —
a prompt at the terminal or a web form — rather than the full REPL
of :func:supervisor_agent.
Engine kwargs (timeout, ui, default) configure the
:class:HumanEngine; remaining **agent_kwargs flow to the
unified Agent constructor::
from lazybridge.ext.hil import human_agent
human_agent(timeout=60.0, default="approve")("Approve deploy?")
Source code in lazybridge/ext/hil/__init__.py
lazybridge.ext.hil.supervisor_agent ¶
supervisor_agent(*, tools: list[Any] | None = None, agents: list[Any] | None = None, store: Any | None = None, input_fn: Callable[[str], str] | None = None, ainput_fn: Callable[[str], Awaitable[str]] | None = None, timeout: float | None = None, default: str | None = None, **agent_kwargs: Any) -> Agent
Build a human-supervised :class:Agent (REPL + tool dispatch + retry).
Symmetric counterpart of Agent.from_<kind>(...) for the
:class:SupervisorEngine. Kept on the ext side rather than as
Agent.from_supervisor to respect the core/ext import boundary
(see docs/guides/core-vs-ext.md).
Engine kwargs (tools, agents, store, input_fn /
ainput_fn, timeout, default) configure the
:class:SupervisorEngine; remaining **agent_kwargs (memory= /
session= / output= / verify= / fallback= / guard= /
name= / etc.) flow to the unified Agent constructor::
from lazybridge.ext.hil import supervisor_agent
supervisor_agent(
tools=[search],
agents=[researcher], # human can `retry researcher: <feedback>`
session=sess,
name="ops-supervisor",
)("publish a policy brief")
Source code in lazybridge/ext/hil/__init__.py
lazybridge.ext.hil.HumanEngine ¶
HumanEngine(*, timeout: float | None = None, ui: Literal['terminal', 'web'] | _UIProtocol = 'terminal', default: str | None = None)
Presents the task to a human and returns their response as an Envelope.
With output=PydanticModel, terminal prompts each field; web renders a form. Emits the same 8 event types as LLMEngine for transparent observability.
Source code in lazybridge/ext/hil/human.py
lazybridge.ext.hil.SupervisorEngine ¶
SupervisorEngine(*, tools: list[Tool | Callable | Any] | None = None, agents: list[Any] | None = None, store: Store | None = None, input_fn: Callable[[str], str] | None = None, ainput_fn: Callable[[str], Awaitable[str]] | None = None, timeout: float | None = None, default: str | None = None)
Human-in-the-loop engine with tool-calling and agent retry.