Skip to content

feat: add background, effort, permissionMode to AgentDefinition#782

Merged
qing-ant merged 1 commit intomainfrom
qing/agent-definition-parity
Mar 31, 2026
Merged

feat: add background, effort, permissionMode to AgentDefinition#782
qing-ant merged 1 commit intomainfrom
qing/agent-definition-parity

Conversation

@qing-ant
Copy link
Copy Markdown
Contributor

@qing-ant qing-ant commented Mar 31, 2026

Summary

Adds 3 fields to AgentDefinition that are present in the TS SDK's AgentDefinitionSchema (coreSchemas.ts) but missing from the Python SDK:

  • background: bool | None -- Run this agent as a background task (non-blocking, fire-and-forget) when invoked. This is the key addition: it enables parallel subagent execution without spawning multiple SDK sessions.
  • effort: Literal["low", "medium", "high", "max"] | int | None -- Reasoning effort level for the agent. Accepts either a named level or an integer.
  • permissionMode: PermissionMode | None -- Permission mode controlling how tool executions are handled for this agent.

All three are optional pass-through fields that the CLI already supports via the AgentDefinitionSchema in the control protocol. The existing asdict() + None-filtering serialization in _internal/client.py handles them automatically -- no serialization changes needed.

permissionMode reuses the existing PermissionMode type alias already defined in types.py.

Tests

Added 5 new tests in TestAgentDefinition:

  • test_background_serializes_correctly -- verifies boolean serialization
  • test_effort_accepts_named_level -- verifies string effort ("high")
  • test_effort_accepts_integer -- verifies integer effort (32000)
  • test_permission_mode_serializes_as_camelcase -- verifies camelCase key
  • test_new_fields_omitted_when_none -- verifies all 3 fields absent when unset

All 44 tests pass.

@qing-ant qing-ant requested a review from a team March 31, 2026 00:53
@qing-ant qing-ant enabled auto-merge (squash) March 31, 2026 05:58
@qing-ant
Copy link
Copy Markdown
Contributor Author

E2E Proof

Script
"""E2E proof for PR #782: background, effort, permissionMode on AgentDefinition."""

import asyncio
import sys

from claude_agent_sdk import (
    AgentDefinition,
    ClaudeAgentOptions,
    ClaudeSDKClient,
    SystemMessage,
)


async def main():
    results = []

    # Test 1: AgentDefinition with effort field (string)
    print("Test 1: effort field (string) accepted by CLI")
    try:
        options = ClaudeAgentOptions(
            agents={
                "effort-agent": AgentDefinition(
                    description="Agent with effort field",
                    prompt="You are a helpful assistant. Reply with one short sentence.",
                    effort="low",
                )
            },
            max_turns=1,
        )
        async with ClaudeSDKClient(options=options) as client:
            await client.query("Say hello in exactly 3 words")
            found_agent = False
            async for msg in client.receive_response():
                if isinstance(msg, SystemMessage) and msg.subtype == "init":
                    agents = msg.data.get("agents", [])
                    found_agent = "effort-agent" in agents
                    print(f"  Agents in init: {agents}")
                    break
            print(f"  {chr(39)}PASS{chr(39)} if found_agent else {chr(39)}FAIL{chr(39)}")
            results.append(found_agent)
    except Exception as e:
        print(f"  FAIL: {e}")
        results.append(False)

    # Test 2: AgentDefinition with effort as integer
    print("Test 2: effort as integer accepted by CLI")
    try:
        options = ClaudeAgentOptions(
            agents={
                "effort-int-agent": AgentDefinition(
                    description="Agent with integer effort",
                    prompt="You are a helpful assistant. Reply with one short sentence.",
                    effort=32000,
                )
            },
            max_turns=1,
        )
        async with ClaudeSDKClient(options=options) as client:
            await client.query("Say goodbye in exactly 3 words")
            found_agent = False
            async for msg in client.receive_response():
                if isinstance(msg, SystemMessage) and msg.subtype == "init":
                    agents = msg.data.get("agents", [])
                    found_agent = "effort-int-agent" in agents
                    print(f"  Agents in init: {agents}")
                    break
            results.append(found_agent)
    except Exception as e:
        print(f"  FAIL: {e}")
        results.append(False)

    # Test 3: AgentDefinition with permissionMode
    print("Test 3: permissionMode field accepted by CLI")
    try:
        options = ClaudeAgentOptions(
            agents={
                "perm-agent": AgentDefinition(
                    description="Agent with permissionMode",
                    prompt="You are a helpful assistant. Reply with one short sentence.",
                    permissionMode="bypassPermissions",
                )
            },
            max_turns=1,
        )
        async with ClaudeSDKClient(options=options) as client:
            await client.query("Say thanks in exactly 3 words")
            found_agent = False
            async for msg in client.receive_response():
                if isinstance(msg, SystemMessage) and msg.subtype == "init":
                    agents = msg.data.get("agents", [])
                    found_agent = "perm-agent" in agents
                    print(f"  Agents in init: {agents}")
                    break
            results.append(found_agent)
    except Exception as e:
        print(f"  FAIL: {e}")
        results.append(False)

    # Test 4: AgentDefinition with background=True
    print("Test 4: background field accepted by CLI")
    try:
        options = ClaudeAgentOptions(
            agents={
                "bg-agent": AgentDefinition(
                    description="Background agent",
                    prompt="You are a helpful assistant. Reply with one short sentence.",
                    background=True,
                )
            },
            max_turns=1,
        )
        async with ClaudeSDKClient(options=options) as client:
            await client.query("Say yes in exactly 2 words")
            found_agent = False
            async for msg in client.receive_response():
                if isinstance(msg, SystemMessage) and msg.subtype == "init":
                    agents = msg.data.get("agents", [])
                    found_agent = "bg-agent" in agents
                    print(f"  Agents in init: {agents}")
                    break
            results.append(found_agent)
    except Exception as e:
        print(f"  FAIL: {e}")
        results.append(False)

    # Test 5: All three new fields combined
    print("Test 5: all new fields combined accepted by CLI")
    try:
        options = ClaudeAgentOptions(
            agents={
                "full-agent": AgentDefinition(
                    description="Fully-configured agent",
                    prompt="You are a helpful assistant. Reply with one short sentence.",
                    background=True,
                    effort="high",
                    permissionMode="bypassPermissions",
                )
            },
            max_turns=1,
        )
        async with ClaudeSDKClient(options=options) as client:
            await client.query("Say wow in exactly 2 words")
            found_agent = False
            async for msg in client.receive_response():
                if isinstance(msg, SystemMessage) and msg.subtype == "init":
                    agents = msg.data.get("agents", [])
                    found_agent = "full-agent" in agents
                    print(f"  Agents in init: {agents}")
                    break
            results.append(found_agent)
    except Exception as e:
        print(f"  FAIL: {e}")
        results.append(False)

    # Summary
    passed = sum(results)
    total = len(results)
    print(f"Results: {passed}/{total} passed")
    if passed == total:
        print("ALL TESTS PASSED")
    else:
        print("SOME TESTS FAILED")
        sys.exit(1)


asyncio.run(main())
Output
Test 1: effort field (string) accepted by CLI
  Agents in init: ["worker", "test-agent", "effort-agent"]
  PASS
Test 2: effort as integer accepted by CLI
  Agents in init: ["worker", "test-agent", "effort-int-agent"]
  PASS
Test 3: permissionMode field accepted by CLI
  Agents in init: ["worker", "test-agent", "perm-agent"]
  PASS
Test 4: background field accepted by CLI
  Agents in init: ["worker", "test-agent", "bg-agent"]
  PASS
Test 5: all new fields combined accepted by CLI
  Agents in init: ["worker", "test-agent", "full-agent"]
  PASS

========================================
Results: 5/5 passed
ALL TESTS PASSED

Copy link
Copy Markdown
Collaborator

@bogini bogini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stamped 🤖

@qing-ant qing-ant merged commit 7c6902b into main Mar 31, 2026
10 checks passed
@qing-ant qing-ant deleted the qing/agent-definition-parity branch March 31, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants