Skip to main content

Factory Droid SDK for Python

Async Python 3.10+ SDK for local Factory Droid sessions.

Install and authenticate

pip install droid-sdk

Use an authenticated local Droid CLI session, or set FACTORY_API_KEY.

The droid executable must be on PATH. Keys are passed through the child process environment, never command arguments.

One turn or a conversation

Use run() for one turn:

import asyncio
from droid_sdk import run


async def main() -> None:
    result = await run("Summarize this repository.", timeout=60)
    print(result.text if result.success else result.subtype)


asyncio.run(main())

Use Session for shared history and operations:

import asyncio

from droid_sdk import AssistantMessage, Session


async def main() -> None:
    async with Session() as session:
        async with session.stream("What does this project do?") as stream:
            async for event in stream:
                if isinstance(event, AssistantMessage):
                    print(event.text)
        print(stream.result.subtype, session.id)


asyncio.run(main())

Sessions are lazy. async with opens and closes them. A manually opened session must be closed by its caller. A session permits one active stream; different sessions can run concurrently. Breaking iteration inside the stream context, timeout, or task cancellation performs a best-effort interrupt and detaches subscriptions. run() always closes everything it creates.

Results, errors, and typing

Terminal outcomes are immutable RunSuccess, RunInterrupted, or RunFailure values. Execution, interruption, and structured-output failures do not raise. Setup, connection, process, protocol, timeout, and cancellation failures do. asyncio.CancelledError is never wrapped.

The wheel contains py.typed. Result and event unions narrow with isinstance(). stream.result is cached after completion and raises StreamIncompleteError before then.

Structured output and attachments

import asyncio

from pydantic import BaseModel
from droid_sdk import Document, Image, run


class Summary(BaseModel):
    title: str
    risks: list[str]


async def main() -> None:
    result = await run(
        "Summarize these inputs.",
        output=Summary,
        images=[Image.from_path("screen.png")],
        files=[Document.from_text("notes", name="notes.txt")],
    )
    if result.output is not None:
        print(result.output.title)


asyncio.run(main())

JsonSchema provides raw object-shaped JSON Schema output. Local constructors support PNG/JPEG/GIF/WebP images, text documents, and PDFs. Invalid inputs raise InvalidAttachmentError before a turn starts.

Discover available models

Use the sessionless model catalog before choosing a model:

import asyncio

from droid_sdk import list_models


async def main() -> None:
    for model in await list_models():
        print(model.id, model.default_reasoning_effort)


asyncio.run(main())

Disabled models are hidden by default. Pass include_disabled=True to include them with disabled_reason, and pass cwd= when project settings should participate in discovery. The one-shot Droid process is closed before the function returns.

Interactions and controls

Attach InteractionHandlers(on_permission=..., on_question=...). Permission responses must choose an option offered by Droid; invalid responses and handler failures safely cancel. Typed action classes are available from droid_sdk.permissions.

SessionConfig and update_settings() support model, reasoning, mode, autonomy, tags, and all four native-tool controls:

  • additional_tools: add catalog IDs
  • enabled_tools: enable available IDs
  • disabled_tools: subtract IDs
  • restrict_tools: restrictive allowlist; it never elevates permission

model="auto" selects the Factory Router, which routes each task to the best model automatically.

Use list_tools(), list_skills(), MCP operations, context(), enter_spec()/leave_spec(), rename(), and raw filtered on_notification() subscriptions for ongoing sessions.

SDK attribution

The SDK owns its producer attribution. It sets spawned Droid processes to sdk and python/<installed droid-sdk version>, attaches that identity to every JSON-RPC request, and marks new root sessions and user messages with the SDK origin.

New root sessions receive exactly one canonical sdk tag. Caller-provided tags with that name are replaced, while other tags retain their order. Resuming a session does not rewrite its stored creation tags. Runtime.env cannot override these SDK-owned fields.

Low-level integrations can import FACTORY_SDK_HEADER, ClientType, SessionOrigin, SdkClientMetadata, and ClientRequestAttribution from droid_sdk.schemas.

Custom system prompts

Configure a system prompt when creating a session. A string replaces Droid's standard behavioral prompt:

from droid_sdk import SessionConfig

config = SessionConfig(
    system_prompt="Act as a focused dependency-analysis agent.",
)

Use the Droid preset to retain the effective built-in prompt and append instructions:

config = SessionConfig(
    system_prompt={
        "type": "preset",
        "preset": "droid",
        "append": "Prioritize security findings and cite relevant files.",
    }
)

Mandatory identity and model/tool guidance remain in both cases. The prompt is fixed at creation and persists locally across resume, fork, and compaction. Treat it as sensitive session data.

The SDK raises SessionError instead of silently continuing if the installed Droid version does not support custom system prompts.

Resume and replacement ownership

async with Session.resume(saved_id) as session:
    ...

Resume restores persisted history, cwd, title, and settings; handlers, runtime, tool policy, observability, and session-scoped MCP servers must be attached again.

fork(), compact(), and rewind() return an already-open successor that owns the existing connection. The source is retired: identity remains readable, active methods raise SessionReplacedError, and source close() is a no-op.

MCP and custom runtime

External stdio, HTTP, and SSE configs are importable from the package root. Annotated Python functions can be exposed through an authenticated loopback-only Streamable HTTP server; this in-process server support lives in droid_sdk.mcp and requires the mcp extra (pip install "droid-sdk[mcp]"):

from droid_sdk import SessionConfig
from droid_sdk.mcp import create_sdk_mcp_server, tool


@tool("lookup", "Look up a local value.")
def lookup(key: str) -> str:
    return f"value:{key}"


server = create_sdk_mcp_server("local-tools", [lookup])
config = SessionConfig(mcp_servers=[server])

The session starts and stops SDK MCP servers. Every start uses an ephemeral port and fresh bearer token.

Runtime configures the executable, extra args, environment, and privacy-safe observability.

Saved sessions and examples

await list_sessions() reads local session files without starting Droid. Use all_workspaces=True, cwd=, or limit=.

Runnable examples are under examples/; offline examples require no credentials, while model examples use bounded prompts and finite timeouts. See the complete command matrix and API contract in the Python SDK documentation.

Limitations

  • Local droid subprocesses only
  • asyncio only
  • one active turn per session
  • hooks remain file-configured
  • image URLs are unsupported

Apache-2.0. See LICENSE.

Release files for droid-sdk 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for droid-sdk 0.4.0
File Size Uploaded
droid_sdk-0.4.0.tar.gz 216.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for droid-sdk 0.4.0
File Interpreter ABI Platform
droid_sdk-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 359.2 kB

Release files / droid_sdk-0.4.0.tar.gz

Download URL droid_sdk-0.4.0.tar.gz
Size 216.9 kB
Tags Source
SHA-256 checksum
How to use checksums
2a5f8fecf89efba0c4e50e4432fc4fbd5d12d44e4dd767c7ec72c3f72a444c98
BLAKE2b-256 checksum
How to use checksums
a76024edc00713fb37e5652775a55e573455238fde04f63ffd05fedaed3d3016
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release files / droid_sdk-0.4.0-py3-none-any.whl

Download URL droid_sdk-0.4.0-py3-none-any.whl
Size 142.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
da1fe6371c552ea9d7c55e373830e4b93b0336dd3f5a46f8e176ce5f730acd3b
BLAKE2b-256 checksum
How to use checksums
2d8ae917915688af43729e19cdb091d7d19764eb2e6f08206c6dcaf4d77a0892
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.0

2 release files

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page