ag2

ag2aiApache-2.0活跃维护

开源多智能体编排框架,前身是 AutoGen,支持构建、协作和调度多个 LLM 智能体,并集成 MCP、A2A 等协议。

AG2 (formerly AutoGen): The Open-Source AgentOS.Join us at: https://discord.gg/sNGSwQME3x

v1.3.0发布于 2026-11-04 09:21 UTC5,054star +2 / 7 天788fork同步于 2026-11-10 17:37 UTC

怎么上手

由 README 提炼,执行前请确认
pip install ag2[openai]
硬件要求按 README 说明

README 未说明

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

AG2 是开源 AgentOS,用异步 Agent 和 Network 编排多智能体协作

开源多智能体编排框架,前身是 AutoGen,支持构建、协作和调度多个 LLM 智能体,并集成 MCP、A2A 等协议。

核心能力

  • 用 Agent.ask 发起对话轮次并保留历史上下文
  • 用 @tool 装饰 Python 函数,自动完成工具调用循环
  • 通过 Network 的 hub 与类型化 channel 编排多智能体
  • 用 context.input 与 hitl_hook 实现人工介入审批
  • 提供知识持久化、上下文组装与历史压缩的 harness 原语

容易踩的坑

需要 Python >= 3.11;需按模型厂商安装对应 extra 并设置 OPENAI_API_KEY 等环境变量;v1.0 不再提供 autogen 命名空间,旧代码需改装 ag2-classic

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v1.3.0
  1. v1.3.0最新正式版2026-11-04 · 2026-11-04 09:21 UTC

    AG2 1.3 adds observation across delegation trees, explicit variable scopes, dynamic model context, and a shared task lifecycle.

    Deprecations and migration to 1.4

    The following APIs continue to work in 1.3 with deprecation warnings and will be removed in 1.4.0:

    Deprecated APIReplacement and migration guide
    TaskConfig, Agent(tasks=...), Agent.tasks, and the automatically injected run_subtask / run_subtasks toolsConstruct workers explicitly and delegate through Agent.as_tool(). Migration guide
    Agent(assembly=...)Register AssemblerMiddleware through middleware=; add HaltCheckMiddleware when using AlertPolicy. Assembly guide
    Agent(knowledge=...)Use plugins=[KnowledgePlugin(...)]. Migration guide
    Agent.task()Construct Task(owner_id=..., spec=TaskSpec(...)) directly for standalone work. Task API

    KnowledgeConfig, assembly policies, and Task remain supported; the deprecations apply to the entry points above.

    As previously announced, OpenAIConfig will become an alias for OpenAIResponsesConfig in 1.4. Use OpenAIResponsesConfig or OpenAICompletionsConfig explicitly. Model configuration.

    Python requirement

    AG2 1.3 requires Python 3.11 or newer. Upgrade Python before updating an environment that still uses 3.10.

    The examples below use OpenAIResponsesConfig. Install ag2[openai] and set OPENAI_API_KEY as described in the Quick Start. Run statements containing await inside an async function. Later fragments reuse config from the first example.

    Observe the entire delegation tree

    Ancestors now receive events from every descendant, including streamed chunks. Operational events arrive as ChildEvent observations: they do not enter the ancestor's model transcript or trigger its tools, model loop, or HITL handlers.

    Typed @observer(...) callbacks let a coordinator monitor the whole tree. An observed event's live event.origin addresses the invocation that authored it; use await event.origin.send(...) to deliver an event back through that invocation's normal handlers. Handles expire when the invocation ends and are not available when replaying saved history.

    Documentation: event observation and addressed delivery.

    Explicit variable scopes

    ScopedValue defines how state crosses a delegation boundary:

    ScopeBehavior
    COPIEDDeep-copied for each branch; the default for plain values
    LOCALNot inherited by descendants
    SHAREDOne binding across the invocation's tree, including replacement of its value

    Declare shared names at the root. Applications provide synchronization for concurrent updates; locks, clients, and connections belong in dependencies. Fresh root invocations copy their input values, so read results from reply.context.variables rather than the caller's original containers.

    Documentation: variable scopes.

    Use event observation and scoped state together:

    from ag2 import Agent, Context, Scope, ScopedValue, observer, tool
    from ag2.config import OpenAIResponsesConfig
    from ag2.events import ChildEvent, ModelMessageChunk
    
    config = OpenAIResponsesConfig(model="gpt-5", streaming=True)
    
    @tool
    def save_finding(finding: str, context: Context) -> str:
        """Save a finding and update this worker's draft."""
        context.variables["findings"].append(finding)
        context.variable
    
  2. v1.2.22026-10-27 · 2026-10-27 20:53 UTC
    查看更新说明

    Highlights

    • Timeouts for code that can hang: @tool(timeout=...) limits each function tool call, and timeout= on stream subscribers, @observer, BaseObserver, LoopDetector and TokenMonitor limits each callback. A timed-out tool returns a TimeoutError to the model, which can continue.
    • Anthropic SDK 2.x support, plus new AnthropicConfig options: a typed speed and open_telemetry to configure or disable the SDK's own OpenTelemetry spans.
    • OpenAIDecisionsConfig accepts question_instructions (strings, ag2 inputs or SDK parts), appended to the agent prompt.
    • A2UI v1.0 updated to the current spec, including callAgentFunction / agentFunctionResponse and renderer capabilities.
    • New Glasser paid-data toolkit extension.
    • More accurate token usage: Anthropic continuation tokens, Gemini thinking tokens and xAI reasoning tokens are now counted, and OpenAI cache_write_tokens maps to cache_creation_input_tokens.
    • Fixes for Mistral streaming, A2A empty text artifacts, eval dataset tags and failure attribution, SKILL.md files with a BOM, and update_file with empty old_content.

    Upgrade notes

    • OpenAIConfig is deprecated and becomes the Responses API in 1.4.0. The current Chat Completions behavior is now OpenAICompletionsConfig; OpenAIConfig still works unchanged but emits a DeprecationWarning from create(). Switch to OpenAICompletionsConfig to keep Chat Completions, or to OpenAIResponsesConfig to move now. Renaming the class does not translate API-specific options.
    • temperature, top_p and top_k on AnthropicConfig are deprecated until 1.4.0. The API version used by anthropic 2.x rejects them, so with SDK 2.x they warn and are dropped; with 1.x they warn and are still sent. The extra now requires anthropic>=1.15.0,<3.
    • OpenAI now requires openai>=3.37.0,<4.

    What's Changed

  3. v1.2.12026-10-21 · 2026-10-21 20:18 UTC
    查看更新说明

    Highlights

    • Mistral voice for ag2.live: offline and realtime speech-to-text (MistralTranscriber, MistralRealtimeTranscriber) and text-to-speech (MistralTTSConfig, MistralStreamingTTSConfig), on top of mistralai 3.2 with its new chat parameters.
    • OpenAIResponsesConfig accepts a reasoning option for effort and summaries.
    • New search toolkits: FirecrawlToolkit and KeenableSearchToolkit.
    • New compaction_verifier extension: measures whether a CompactStrategy makes the agent's next actions worse, not just how many tokens it saves.
    • TypeSafe can locate objects in images: ResponseSchema(BoundingBox) sends a Box question.
    • ACP and the network layer keep multimodal input parts instead of flattening them to text.
    • Fixes for Ollama, Gemini and xAI history, paginated file listings, and atomic SQLite and Redis knowledge appends.

    Upgrade notes

    • Mistral uses httpx2. mistralai 3.x moved from httpx to httpx2, so MistralConfig.async_client is now an httpx2.AsyncClient and the extra requires mistralai>=3.2.0,<4. An httpx.AsyncClient still works at runtime but no longer type-checks, and httpx.HTTPError does not catch httpx2 errors.
    • ElevenLabs now requires elevenlabs>=2.73.0,<3.
    • Input parts are no longer joined into one string in ACP and the network layer. ACP sends each part as its own content block, based on what the agent advertises; before, every non-text part was dropped. ChannelAdapter.render_envelope and extract_turn_input can now return an Input or a sequence of them, so check custom channel adapters.
    • MCP tool calls are pinned to the configuration used for discovery, so a Variable(default_factory=...) can no longer route a call to a different server than the one that advertised the tool.
    • OpenAI Chat Completions rejects files uploaded to another provider with UnsupportedInputError, like the Responses, Anthropic and Gemini mappers.

    What's Changed

  4. v1.2.02026-10-11 · 2026-10-11 09:26 UTC
    查看更新说明

    Highlights

    • OpenAI Decisions API support with OpenAIDecisionsConfig and shared decision schemas using the Question marker.
    • A2A servers can run on multiple replicas, with cacheable agent cards, opt-in input media type validation, and configurable push notification URL validation.
    • Anthropic cache diagnostics and explicit API keys for OpenAI voice configs.
    • Ollama API keys, custom HTTP clients, tool names, and fixes for duplicate streaming tool-call IDs.
    • Fixes for MCP discovery, knowledge stores, evaluation suite loading, skills, A2UI, and network graph loading.

    Toolkit composition

    left | right now creates an independent toolkit collection containing the original tool objects and their existing middleware. Tools from the right no longer receive middleware from the left, and later additions to the combined toolkit do not inherit either source's middleware. Replacement by name still uses the right-hand tool.

    If you relied on composition to attach middleware, attach it explicitly through toolkit construction or per-tool configuration.

    What's Changed

  5. v1.1.22026-10-03 · 2026-10-03 06:25 UTC
    查看更新说明

    What's Changed

    Security:

    • fix shell command-filter bypasses in SandboxShellTool / ShellAdapter (GHSA-42mf-vpmr-gw5r).

    New Contributors

    Full Changelog: https://github.com/ag2ai/ag2/compare/v1.1.1...v1.1.2

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    [!IMPORTANT] Looking for ConversableAgent, GroupChat, or import autogen? That's now AG2 Classic.

    As of AG2 v1.0, the protocol-driven framework is the top-level package, imported as ag2. The classic framework has moved to its own repository — ag2ai/ag2-classic, documented at classic.docs.ag2.ai. It is still maintained and installable; nothing you have built stops working.

    This repository (pip install ag2) no longer ships the autogen import name or the classic agent classes.

    AG2: Open-Source AgentOS for AI Agents

    AG2 is an open-source programming framework for building AI agents and facilitating cooperation among multiple agents to solve tasks. AG2 aims to streamline the development and research of agentic AI. It offers features such as agents capable of interacting with each other, facilitates the use of various large language models (LLMs) and tool use support, autonomous and human-in-the-loop workflows, and multi-agent conversation patterns.

    The project is currently maintained by a dynamic group of volunteers from several organizations. Contact project administrators Chi Wang and Qingyun Wu via [email protected] if you are interested in becoming a maintainer.

    Table of contents

    AG2 Classic (the autogen.* namespace)

    AG2 Classic is the original AutoGen-derived framework: the autogen.* import namespace and its agent classes — ConversableAgent, AssistantAgent, UserProxyAgent, GroupChat / GroupChatManager, swarms, register_function, LLMConfig / OAI_CONFIG_LIST, and the nested- and sequential-chat patterns.

    It now lives in its own repository and has its own documentation site:

    AG2 ClassicAG2 (this repo)
    Repositoryag2ai/ag2-classicag2ai/ag2
    Documentationclassic.docs.ag2.aidocs.ag2.ai
    Importimport autogenimport ag2
    Core agentConversableAgentAgent
    Multi-agentGroupChat, swarms, nested chatsNetwork (hub + channels)

    Are you using AG2 Classic?

    If any of the following appear in your code, you are on Classic — stay on it, and use classic.docs.ag2.ai:

    import autogen  # the autogen.* namespace
    from autogen import ConversableAgent, GroupChat  # classic agent classes
    from autogen import AssistantAgent, UserProxyAgent
    

    Classic remains maintained and installable. Your existing code keeps working — pin the classic distribution instead of ag2>=1.0:

    pip install ag2-classic
    

    [!NOTE] AG2 v1.0 (pip install ag2) is not a drop-in upgrade from Classic. The agent model, orchestration, and imports all changed. See the group chat migration guide.

    The rest of this README covers AG2 v1.0 (import ag2).

    Getting started

    For a step-by-step walk through of AG2 concepts and code, see the Quick Start in our documentation.

    Installation

    AG2 requires Python version >= 3.11. AG2 is available as ag2 on PyPI.

    Windows/Linux:

    pip install ag2[openai]
    

    Mac:

    pip install 'ag2[openai]'
    

    Minimal dependencies are installed by default. Install the extra that matches your model provider — ag2[openai], ag2[anthropic], ag2[gemini], ag2[ollama], and so on.

    Setup your API keys

    Each provider config reads its standard environment variable, so keys never need to be hardcoded or checked in:

    export OPENAI_API_KEY="<your-api-key>"     # or ANTHROPIC_API_KEY, GEMINI_API_KEY, ...
    

    You can also pass a key explicitly with OpenAICompletionsConfig(model="gpt-4o-mini", api_key=...) — useful when each request brings its own key.

    Run your first agent

    AG2 is async throughout. Agent.ask(...) starts a turn and returns an AgentReply; the text is in reply.body.

    import asyncio
    
    from ag2 import Agent
    from ag2.config import OpenAICompletionsConfig
    
    agent = Agent(
        "assistant",
        prompt="You are a helpful assistant.",
        config=OpenAICompletionsConfig(model="gpt-4o-mini"),
    )
    
    
    async def main() -> None:
        reply = await agent.ask("Summarize the main differences between Python lists and tuples.")
        print(reply.body)
    
    
    asyncio.run(main())
    

    Example applications

    We maintain both a live playground and a dedicated repository with a wide range of applications to help you get started with various use cases, and a set of runnable code examples in the documentation.

    Introduction of different agent concepts

    We have several agent concepts in AG2 to help you build your AI agents. We introduce the most common ones here.

    • Agents: Agent is the core building block — it talks to a model provider, calls tools, and returns a reply.
    • Tools: Plain Python functions, decorated with @tool, that the agent can invoke.
    • Human in the loop: Pause a run to collect confirmation or missing information from a person.
    • Orchestrating multiple agents: Coordinate several agents over a hub and typed channels using the Network.
    • The agent harness: Opt-in primitives layered onto an agent — persistent knowledge, context assembly, and history compaction.
    • Advanced Concepts: Structured outputs, middleware, observers, telemetry, evaluation, and more.

    Agents

    The Agent is the fundamental building block of AG2. ask() runs a turn; calling ask() on the returned reply continues the same conversation, preserving its history.

    import asyncio
    
    from ag2 import Agent
    from ag2.config import OpenAICompletionsConfig
    
    reviewer = Agent(
        "reviewer",
        prompt=(
            "You are a code reviewer. Analyze the provided code and suggest improvements. "
            "Do not generate code, only suggest improvements."
        ),
        config=OpenAICompletionsConfig(model="gpt-4o-mini"),
    )
    
    
    async def main() -> None:
        reply = await reviewer.ask("def fib(n): return n if n < 2 else fib(n-1) + fib(n-2)")
        print(reply.body)
    
        # Continue the same conversation — prior turns stay in scope.
        follow_up = await reply.ask("Which of those matters most for large n?")
        print(follow_up.body)
    
    
    asyncio.run(main())
    

    Tools

    Agents gain significant utility through tools, which extend their capabilities with external data, APIs, or functions. Decorate a Python function with @tool and pass it to the agent — AG2 runs the full tool-calling loop: the model decides when to call it, AG2 executes it, and the result is fed back.

    import asyncio
    from datetime import datetime
    
    from ag2 import Agent, tool
    from ag2.config import OpenAICompletionsConfig
    
    
    @tool
    async def get_weekday(date_string: str) -> str:
        """Get the day of the week for a given date, formatted as YYYY-MM-DD."""
        return datetime.strptime(date_string, "%Y-%m-%d").strftime("%A")
    
    
    date_agent = Agent(
        "date_agent",
        prompt="You find the day of the week for a given date.",
        config=OpenAICompletionsConfig(model="gpt-4o-mini"),
        tools=[get_weekday],
    )
    
    
    async def main() -> None:
        reply = await date_agent.ask("I was born on 1995-03-25, what day was it?")
        print(reply.body)
    
    
    asyncio.run(main())
    

    Human in the Loop

    Human oversight is often essential for validating or guiding AI outputs. Call context.input(...) inside a tool to pause the run and ask a person — your hitl_hook decides how that question is answered (CLI prompt, web UI, queue, …).

    import asyncio
    
    from ag2 import Agent, Context, tool
    from ag2.config import OpenAICompletionsConfig
    from ag2.events import HumanInputRequest, HumanMessage
    
    
    @tool
    async def publish_lesson_plan(context: Context, plan: str) -> str:
        """Publish a lesson plan, once a human educator has approved it."""
        answer = await context.input(f"Approve this lesson plan?\n\n{plan}")
        if answer.strip().lower().startswith("y"):
            return "Published."
        return f"Rejected by the educator: {answer}"
    
    
    def hitl_hook(event: HumanInputRequest) -> HumanMessage:
        # Block here for real input — a CLI prompt, a web UI, a queue.
        return HumanMessage(content=input(f"{event.content}\n> "))
    
    
    teacher = Agent(
        "teacher",
        prompt=(
            "Draft a short lesson plan, then call the publish_lesson_plan tool to have it published. "
            "Approval is collected by the tool — never ask for approval in plain text."
        ),
        config=OpenAICompletionsConfig(model="gpt-4o-mini"),
        tools=[publish_lesson_plan],
        hitl_hook=hitl_hook,
    )
    
    
    async def main() -> None:
        reply = await teacher.ask("Let's introduce our kids to the solar system.")
        print(reply.body)
    
    
    asyncio.run(main())
    

    Orchestrating Multiple Agents

    When two or more agents need to work together, AG2 uses the Network: a Hub that owns the registry, the write-ahead log, and the audit trail, with agents talking over typed channels. This replaces the classic GroupChat / swarm / nested-chat patterns.

    Here a conversation channel — a free-form two-party session where either side may speak at any time — connects a planner and a reviewer:

    import asyncio
    
    from ag2 import Agent
    from ag2.config import OpenAICompletionsConfig
    from ag2.knowledge import MemoryKnowledgeStore
    from ag2.network import EV_TEXT, Hub
    
    MAX_MESSAGES = 4
    
    
    async def wait_for_messages(hub: Hub, channel_id: str, expected: int, timeout: float = 120.0) -> None:
        """Poll the hub's write-ahead log until `expected` messages have been exchanged."""
        deadline = asyncio.get_event_loop().time() + timeout
        while asyncio.get_event_loop().time() < deadline:
            wal = await hub.read_wal(channel_id)
            if sum(1 for e in wal if e.event_type == EV_TEXT) >= expected:
                return
            await asyncio.sleep(0.05)
        raise TimeoutError(f"only reached {expected} messages before timing out")
    
    
    async def main() -> None:
        config = OpenAICompletionsConfig(model="gpt-4o-mini")
        hub = await Hub.open(MemoryKnowledgeStore(), ttl_sweep_interval=0)
    
        planner = await hub.register(
            Agent("planner", prompt="You plan school lessons. Reply in one short sentence.", config=config),
        )
        reviewer = await hub.register(
            Agent("reviewer", prompt="You critique lesson plans. Reply in one short sentence.", config=config),
        )
    
        # Free-form 2-party channel: either side may speak at any time.
        channel = await planner.open(type="conversation", target="reviewer")
        await channel.send("What should a fourth-grade solar system lesson lead with?", audience=[reviewer.agent_id])
    
        # Both default handlers reply to every inbound message, so the conversation
        # auto-drives. `conversation` never auto-closes — we bound it and close it.
        await wait_for_messages(hub, channel.channel_id, expected=MAX_MESSAGES)
        await channel.close()
    
        # Replay the exchange from the hub's write-ahead log.
        names = {planner.agent_id: "planner", reviewer.agent_id: "reviewer"}
        for envelope in await hub.read_wal(channel.channel_id):
            if envelope.event_type == EV_TEXT:
                print(f"{names[envelope.sender_id]}: {envelope.event_data['text']}")
    
        await hub.close()
    
    
    asyncio.run(main())
    

    Channels come in several shapes — conversation (free-form, two parties), consulting (strict one-question-one-reply, auto-closing), discussion (round-robin across N agents), and workflow (a declarative TransitionGraph for conditional handoffs, which is the closest analogue to a classic GroupChat). See the Network guide.

    The agent harness: knowledge and compaction

    A bare Agent is just a model loop. The harness is the set of opt-in primitives you compose onto it. Two of the most useful:

    • KnowledgePlugin — a KnowledgeStore the agent can read and write, so it remembers across runs.
    • compact= — a strategy that caps history growth. SummarizeCompact folds the dropped turns into a summary rather than discarding them outright.

    Pair the store with a WorkingMemoryPolicy through AssemblerMiddleware and the agent's memory is injected into the system prompt on every turn — recall no longer depends on the model choosing to look it up.

    import asyncio
    from pathlib import Path
    
    from ag2 import Agent, MemoryStream
    from ag2.assembly import AssemblerMiddleware
    from ag2.compact import CompactTrigger, SummarizeCompact
    from ag2.config import OpenAICompletionsConfig
    from ag2.events import CompactionCompleted, CompactionFailed
    from ag2.knowledge import KnowledgePlugin, DiskKnowledgeStore
    from ag2.middleware import Middleware
    from ag2.policies import WorkingMemoryPolicy
    
    config = OpenAICompletionsConfig(model="gpt-4o-mini")
    
    # Disk-backed, so anything the agent remembers survives the process exiting.
    STORE_DIR = Path("./knowledge_demo")
    store = DiskKnowledgeStore(STORE_DIR)
    
    agent = Agent(
        "tutor",
        prompt=(
            "You are a science tutor for a fourth-grade teacher. "
            "Whenever you learn a durable fact about the teacher or their class, use the knowledge "
            "tool to `read` `memory/working.md`, then `write` it back with the new fact appended as "
            "a bullet. Never drop a fact that is already there. "
            "Keep every reply to one or two sentences."
        ),
        config=config,
        plugins=[
            KnowledgePlugin(
                store=store,
                # Summarize the dropped history instead of forgetting it outright.
                compact=SummarizeCompact(target=4, config=config),
                compact_trigger=CompactTrigger(max_events=8),
            )
        ],
        # Injects memory/working.md into the system prompt on every turn, so recall
        # does not depend on the model choosing to call the knowledge tool.
        middleware=[Middleware(AssemblerMiddleware, policies=[WorkingMemoryPolicy()])],
    )
    
    stream = MemoryStream()
    
    
    # Subscribe to both outcomes — watching only Completed would hide a failing strategy.
    @stream.where(CompactionCompleted).subscribe()
    async def on_compacted(event: CompactionCompleted) -> None:
        print(f"  [compacted: {event.events_before} -> {event.events_after} events via {event.strategy}]")
    
    
    @stream.where(CompactionFailed).subscribe()
    async def on_compaction_failed(event: CompactionFailed) -> None:
        print(f"  [compaction FAILED: {event.strategy}]")
    
    
    async def main() -> None:
        # A returning session: no conversation history at all, so anything the agent
        # recalls here came off disk, via the knowledge store.
        if STORE_DIR.exists():
            reply = await agent.ask("What do you remember about me and my class?", stream=stream)
            print(f"tutor: {reply.body}")
            return
    
        reply = await agent.ask("I'm Dana. I teach 4th grade at Rosewood Elementary, 26 students.", stream=stream)
        print(f"tutor: {reply.body}")
    
        for turn in ["My class struggles most with why we have seasons.", "Suggest one hands-on demo for that."]:
            reply = await reply.ask(turn)
            print(f"tutor: {reply.body}")
    
    
    asyncio.run(main())
    

    Run it twice. The first run fills memory/working.md and trips compaction; the second starts with an empty history and still knows who Dana is:

      [compacted: 18 -> 3 events via SummarizeCompact]
    tutor: I remember that you, Dana, teach 4th grade at Rosewood Elementary with 26 students,
           and your class struggles the most with understanding why we have seasons.
    

    See the Agent Harness guide for context policies, delegation, aggregation, and the full turn lifecycle.

    Advanced agentic design patterns

    AG2 supports more advanced concepts to help you build your AI agent workflows. You can find more information in the documentation.

    Code style and linting

    This project uses prek hooks to maintain code quality. Before contributing:

    1. Install prek:
    pip install prek
    prek install
    
    1. The hooks will run automatically on commit, or you can run them manually:
    prek run --all-files
    

    Contributors Wall

    License

    This project is licensed under the Apache License, Version 2.0 (Apache-2.0).

    • Modifications and additions made in this fork are licensed under the Apache License, Version 2.0. See the LICENSE file for the full license text.

    We have documented these changes for clarity and to ensure transparency with our user and contributor community. For more details, please see the NOTICE file.