A human-centric multi-agent system (MAS) framework for scientific discovery — providing base classes, streaming, human-in-the-loop (HITL), guardrails, and out-of-box agents and tools.
akd-core is the foundation layer that drives the entire AKD ecosystem:
- akd-core (this repo) — base classes, streaming infrastructure, HITL, guardrails, and out-of-box agents/tools for scientific discovery
- akd-framework — the AKD backend application, built on akd-core
- akd-ext — community extensions that use akd-core's base agents and tools to build domain-specific capabilities
akd-core is standalone and pip-installable. Everything downstream inherits its streaming, HITL, and guardrail infrastructure.
- Human-in-the-loop control — researchers direct the discovery process; AI augments, never replaces
- Scientific integrity — deep attribution, evidence validation, and rigorous guardrails
- Transparent and reproducible — every workflow is a shareable, inspectable artifact
- Open collaboration — community-driven framework for shared scientific advancement
See Design Philosophy for the full set of principles and golden rules.
- Async-first — all agents and tools implement
async def _arun()with fullastream()support - Streaming-native — 11 typed event types covering tokens, reasoning, tool calls, and HITL
- Type-safe — Pydantic v2 schemas with required docstrings for all inputs and outputs
- Composable — tools combine via Composite patterns (search, resolvers, guardrails)
- HITL built-in — pause, save state, get human input, resume seamlessly
- Guardrails — pluggable safety layer with decorator API
- LLM-agnostic — works with any provider via LiteLLM (OpenAI, Anthropic, Ollama, etc.)
from akd.agents import BaseAgent
agent = BaseAgent(config={"model_name": "gpt-4o-mini"})
async for event in agent.astream(input_params):
match event.event_type:
case "streaming": print(event.token, end="")
case "tool_calling": print(f"Calling {event.tool_name}...")
case "human_input_required": response = input(event.human_prompt)
case "completed": result = event.outputEverything in akd-core is a stream of typed events. Agents emit StreamEvent objects as they execute:
| Event | Description |
|---|---|
STARTING |
Agent/tool begins execution |
RUNNING |
Progress update |
STREAMING |
Raw LLM tokens as they arrive |
THINKING |
Reasoning tokens (Claude extended thinking, o1/o3) |
PARTIAL |
Partial structured output as it streams |
TOOL_CALLING |
Agent invokes a tool |
TOOL_RESULT |
Tool returns its result |
HUMAN_INPUT_REQUIRED |
Agent needs human input — execution pauses |
HUMAN_RESPONSE |
Resumed with human input |
COMPLETED |
Execution finished successfully |
FAILED |
Execution failed with error details |
Each event carries typed data (e.g., CompletedEventData[T] includes the output, FailedEventData includes the error) and a run_context for execution state.
HITL is a first-class concept, not an afterthought. The HumanTool enables any agent to pause execution, request human input, and resume:
- Agent calls
HumanToolduring its tool loop - Framework emits
HUMAN_INPUT_REQUIREDevent with the question and full message history - Caller saves state and collects human response
- Resume with
RunContext(messages=saved_history, human_response=HumanResponse(...)) - Agent continues exactly where it left off
This works across any transport — REST APIs, WebSockets, CLI — because the pause/resume is state-based, not connection-based.
| Category | Agent | Description |
|---|---|---|
| Utility | RelevancyAgent |
Binary relevance classification |
MultiRubricRelevancyAgent |
Multi-dimensional relevance scoring | |
| Base | BaseAgent |
Core agent with streaming, tool calling, HITL, message trimming |
LiteLLMInstructorBaseAgent |
Structured Pydantic output via Instructor |
Domain-specific agents live in downstream packages and can be registered at runtime via AgentRegistry.register_agent(YourAgent).
| Category | Tool | Description |
|---|---|---|
| Search | SearxNGSearchTool |
Web search via SearxNG |
SerperSearchTool |
Web search via Serper API | |
SemanticScholarSearchTool |
Academic paper search | |
CompositeSearchTool |
Multi-source search (combines backends) | |
SearchPipeline |
Full pipeline: search + resolve + scrape | |
| Scraping | WebScraper |
Web content extraction |
PDFScraper |
PDF content extraction | |
DoclingScraper |
Advanced document parsing (tables, structure) | |
| Resolvers | CrossRefDoiResolver |
DOI resolution via CrossRef |
ArxivResolver |
arXiv paper lookup | |
ADSResolver |
NASA ADS paper lookup | |
UnpaywallResolver |
Open access paper lookup | |
CompositeResolver |
Chain multiple resolvers | |
| Evaluation | RelevancyTool |
Content relevance scoring |
RerankerTool |
Result reranking | |
SourceValidator |
Source credibility assessment | |
| Special | HumanTool |
Human-in-the-loop interaction |
OutputTool |
Structured output capture |
akd-core includes a pluggable guardrail system with a unified GuardrailProtocol interface:
Providers:
GraniteGuardianTool— IBM Granite Guardian model (local or cloud)RiskAgent— LLM-based risk assessment with configurable criteriaCompositeGuardrail— chain multiple providers (AND, OR, CONSENSUS modes)
Risk categories: Granite built-in categories, Atlas dynamic taxonomy, and science-specific risks (misinformation, bias, attribution).
from akd.guardrails import guardrail
from akd.guardrails.providers import GraniteGuardianTool
@guardrail(input_guardrail=GraniteGuardianTool(), fail_on_input_risk=True)
class SafeAgent(BaseAgent):
...The planner converts natural language research goals into executable multi-agent workflows:
from akd.planner.llm_planner import create_planner
planner = await create_planner()
session = await planner.plan_workflow("Find papers on AlphaFold and identify research gaps")
response = await session.start()
while not response.ready_to_generate:
user_input = input(f"{response.message}\nYour response: ")
response = await session.respond(user_input)
workflow = await session.generate_workflow()The planner uses an AgentRegistry with auto-discovery, field mapping between agent inputs/outputs, and generates executable WorkflowFormat definitions.
akd-core is designed to be extended. Every agent and tool follows a consistent 4-part pattern:
- InputSchema — Pydantic model defining what goes in (requires docstring)
- OutputSchema — Pydantic model defining what comes out (requires docstring)
- Config —
BaseAgentConfigorBaseToolConfigwith settings - Implementation — subclass
BaseAgent[In, Out]orBaseTool[In, Out], implement_arun()
from akd._base import InputSchema, OutputSchema
from akd.agents._base import AKDAgent, BaseAgentConfig
class MyInput(InputSchema):
"""What goes in."""
query: str
class MyOutput(OutputSchema):
"""What comes out."""
answer: str
class MyAgent(AKDAgent[MyInput, MyOutput]):
input_schema = MyInput
output_schema = MyOutput
async def _arun(self, params: MyInput, run_context=None, **kwargs) -> MyOutput:
... # your logic hereAKDAgent is the default batteries-included agent — it comes with LiteLLM + Instructor, ReAct tool calling, HITL, streaming, and output routing. Your agent inherits all of it. See CONTRIBUTING.md for the full guide with tool examples, guardrail integration, and planner registration.
This is exactly how akd-ext builds on akd-core — importing base classes and creating domain-specific agents and tools.
- Python 3.12+
uvpackage manager
As a dependency (for akd-ext, akd-framework, or your own project):
uv pip install "akd @ git+https://github.com/NASA-IMPACT/accelerated-discovery.git@develop"Or add to your pyproject.toml:
dependencies = [
"akd @ git+https://github.com/NASA-IMPACT/accelerated-discovery.git@develop",
]The core install is lightweight. A bare pip install akd pulls only the
schema/protocol layer — pydantic, pydantic-settings, loguru, httpx plus
small utilities (rapidfuzz, jsonpath-ng, dateparser). No ML, browser, or
LLM-SDK weight. Heavy dependencies live in the extras below and are lazy-imported
at their call sites, so importing akd, akd.guardrails, akd.agents, etc.
stays fast and free of the heavy trees until you actually use a feature that
needs them. (Regression-tested in tests/base/test_lazy_imports.py.)
Optional extras — each sits on top of core and composes with the others
(akd[risk_agent] automatically pulls akd[agents] + akd[guardrails]):
| Extra | Adds on top of core | Install when you... |
|---|---|---|
guardrails |
pyyaml |
code against the guardrail contracts — GuardrailInput/Output/Protocol, RiskCategory (e.g. a downstream guardrails service) |
gg |
(core httpx only) |
use GraniteGuardianTool (talks to a served model over HTTP) |
risk_agent |
akd[guardrails] + akd[agents] + deepeval |
use RiskAgent (deepeval DAG-metric risk evaluation) |
agents |
litellm, instructor, openai, tiktoken |
run LLM agents (akd.agents) |
scrapers |
crawl4ai, playwright, pymupdf, markdownify, readability-lxml, beautifulsoup4, aiohttp, pypaperbot, gdown |
scrape web/PDF pages or use the search pipeline |
omni |
akd[scrapers] + docling, docling-core |
use the docling-backed DoclingScraper (torch/transformers) |
search |
numpy, beautifulsoup4, requests, pyyaml |
use search tools + resolvers |
ml |
numpy, sentence-transformers, pandas |
use ML-backed rerankers / embeddings |
serializer |
langgraph, numpy |
use AKDSerializer as a langgraph checkpoint serde (e.g. AsyncPostgresSaver(serde=AKDSerializer())) |
all |
every runtime extra above | want the previous batteries-included install |
dev |
pytest, pytest-asyncio, pytest-cov, pytest-xdist, pre-commit, memray, scalene |
run the test suite or hack on akd itself |
local |
marimo, jupyter, ipykernel, ipywidgets |
run the marimo notebooks under notebooks/ |
Migrating from a previous install: the default
akdno longer bundles scrapers/agents/ML. If you relied on those, install the matching extra (akd[agents],akd[scrapers],akd[ml], …) orakd[all]for the old behaviour. Note:crawl4ai/playwright(inakd[scrapers]) also need a one-timeplaywright installto fetch browsers.
# As a dependency, with one or more extras:
uv pip install "akd[guardrails] @ git+https://github.com/NASA-IMPACT/accelerated-discovery.git@develop"
uv pip install "akd[gg,risk_agent] @ git+https://github.com/NASA-IMPACT/accelerated-discovery.git@develop"# In your pyproject.toml (e.g. a downstream guardrails service):
dependencies = [
"akd[guardrails] @ git+https://github.com/NASA-IMPACT/accelerated-discovery.git@develop",
]For local development:
# Create and activate virtual environment
uv venv --python 3.12
source .venv/bin/activate
# Install the lightweight core only
uv sync
# With development tooling (pytest, pre-commit, profilers)
uv sync --extra dev
# Guardrails contracts / Granite Guardian / risk agent
uv sync --extra guardrails
uv sync --extra gg
uv sync --extra risk_agent
# Agents, scrapers, search, ML
uv sync --extra agents --extra scrapers --extra search --extra ml
# Everything (previous batteries-included behaviour) + dev + notebooks
uv sync --extra all --extra dev --extra local
# Setup environment variables
cp .env.example .env
# Edit .env with your API keysSee the notebooks directory for examples.
akd/
_base/ # AbstractBase, schemas, streaming, HITL, tool calling, sessions
agents/ # Out-of-box agents (search, analysis, utility)
tools/ # Out-of-box tools (search, scraping, resolvers, evaluation)
guardrails/ # GuardrailProtocol, providers, risk categories, decorators
planner/ # LLM planner, agent registry, workflow builder
configs/ # Project configuration and prompts
docs/ # Design philosophy and specs
notebooks/ # Usage examples (Jupyter, Marimo)
scripts/ # Utility scripts and demos
tests/ # Test suite (mirrors akd/ structure)
See CONTRIBUTING.md for setup, style guide, branch conventions, and how to create agents and tools.
Apache License 2.0 — see LICENSE.