Skip to main content

Vendor Agnostic Agent SDK

Project description

glyph

Minimal vendor-agnostic async SDK that normalizes Claude Agent SDK and OpenAI Agents SDK output into one event stream.

Install

pip install glyph-agents
# or for local development
pip install -e .

Requires Python >=3.10.

Quickstart (query helper)

import asyncio

from glyph import AgentOptions, AgentQueryCompleted, AgentText, query


async def main() -> None:
    options = AgentOptions(
        model="gpt-4.1-mini",  # or "claude-haiku-4-5"
        instructions="You are concise and accurate.",
    )

    async for event in query(
        prompt="In one sentence, explain what an API is.",
        options=options,
    ):
        if isinstance(event, AgentText):
            print(event.text, end="")
        elif isinstance(event, AgentQueryCompleted):
            print("\n\nis_error:", event.is_error)
            print("usage:", event.usage)
            print("total_cost_usd:", event.total_cost_usd)


if __name__ == "__main__":
    asyncio.run(main())

Streaming with GlyphClient

Use GlyphClient when you want explicit control of turn lifecycle methods:

  • query(...) then receive_response(...): send one prompt now, stream that prompt's events right after.
  • query_streamed(...): same behavior as above, but in one call.
  • query_and_receive_response(...): run one prompt and return all events at once (no streaming loop).
  • receive_messages(...): use this when you queued multiple prompts with query(...) first and want to drain them in order from a single stream.
import asyncio

from glyph import AgentOptions, AgentQueryCompleted, AgentText, AgentThinking, GlyphClient


async def main() -> None:
    options = AgentOptions(model="gpt-4.1-mini")

    async with GlyphClient(options) as client:
        async for event in client.query_streamed("List two benefits of unit tests."):
            if isinstance(event, AgentThinking):
                print("[thinking]", event.text)
            elif isinstance(event, AgentText):
                print(event.text, end="")
            elif isinstance(event, AgentQueryCompleted):
                print("\n\n[done]", event.is_error, event.stop_reason, event.usage)


if __name__ == "__main__":
    asyncio.run(main())

Event Types

All APIs stream normalized AgentEvent values:

  • AgentText: visible assistant text segments
  • AgentThinking: reasoning/thinking segments when available
  • AgentToolCall: structured tool invocation requests
  • AgentToolResult: structured tool invocation results
  • AgentQueryCompleted: end-of-turn status (is_error, stop_reason, usage, total_cost_usd, extra)

Backend failures are surfaced as AgentQueryCompleted(is_error=True, ...).

AgentOptions

AgentOptions is the shared configuration surface:

  • model (required): determines backend automatically
  • instructions: system prompt / instructions
  • name: OpenAI agent display name (default: "Assistant")
  • cwd: workspace root for tool access
  • allowed_tools: activation allow-list using Claude-style tool names (Read, Write, Edit, Glob, Grep, Bash, WebSearch, WebFetch).
    • Any tool not listed is disabled.
    • None/empty means no built-in tools are activated.
  • permission: PermissionPolicy(edit_ask=True, execute_ask=True, web_ask=True) enables interactive confirmation per capability.
    • edit_ask applies to file mutation actions (Write / Edit).
    • execute_ask applies to command actions (Bash).
    • web_ask applies to web actions (WebSearch / WebFetch) (WebSearch ask is not supported for OpenAI models).
    • Flags default to False, so capabilities are auto-allowed when the corresponding tool is active.
  • approval_handler_edit: custom approval callback for edit/write actions
  • approval_handler_execute: custom approval callback for command execution actions
  • approval_handler_web: custom approval callback for web actions (WebSearch / WebFetch)
  • max_turns: backend turn cap override
  • reasoning_effort / reasoning_summary: OpenAI-only reasoning controls

Approval Handlers (edit vs execute)

When permissions are set to ask, Glyph can call capability-specific approval handlers:

  • approval_handler_edit: used for Write / Edit style operations
  • approval_handler_execute: used for Bash style operations
  • approval_handler_web: used for WebSearch / WebFetch style operations

If a handler is missing, Glyph falls back to interactive TTY approval prompts. In non-interactive contexts (server/worker/CI), missing handlers will cause the action to be denied with a clear error message.

from glyph import AgentOptions, ApprovalDecision, PermissionPolicy


def approve_edit(req):
    # req.capability == "edit"
    return ApprovalDecision(allow=True)


def approve_execute(req):
    # req.capability == "execute"
    commands = (req.payload or {}).get("commands", [])
    allowed = all("rm -rf" not in c for c in commands)
    return ApprovalDecision(
        allow=allowed,
        reason=None if allowed else "Dangerous command blocked",
    )


options = AgentOptions(
    model="gpt-5.4",
    permission=PermissionPolicy(edit_ask=True, execute_ask=True, web_ask=True),
    approval_handler_edit=approve_edit,
    approval_handler_execute=approve_execute,
)

Backend Resolution

resolve_backend(options) infers backend from model:

  • Claude if model contains claude or anthropic
  • OpenAI if model starts with gpt-, o1, o3, o4, or chatgpt

If inference fails, resolve_backend raises ValueError.

Prompt Input Shape

  • Claude backend accepts str or async iterable prompt blocks (Claude-compatible content blocks).
  • OpenAI backend currently supports str prompts only.

Workflows

GlyphWorkflow lets you compose multi-step flows where each step receives the previous step result. Define workflow steps with @step, and run the class with MyWorkflow.run(...).

import asyncio
import os

from glyph import AgentOptions, AgentQueryCompleted, GlyphWorkflow, step


class MyWorkflow(GlyphWorkflow):
    options = AgentOptions(model=os.getenv("GLYPH_MODEL", "gpt-4.1-mini"))

    @step
    async def load_topic(self) -> str:
        return "sea turtles"

    @step(prompt="Write one short sentence about {topic}.")
    async def ask_model(self, topic: str) -> None:
        self.fill_prompt(topic=topic)
        result: AgentQueryCompleted = yield
        print(result.message)


async def main() -> None:
    await MyWorkflow.run()


if __name__ == "__main__":
    asyncio.run(main())

Workflow notes:

  • @step marks a normal Python step.
  • @step(prompt=..., model=...) marks an LLM step; model can override the default model for that step.
  • In LLM steps, use yield to execute the query, then optionally process AgentQueryCompleted after the yield.
  • Use self.fill_prompt(...) to render prompt templates safely while preserving missing placeholders.
  • GlyphWorkflow.run(options=..., initial_input=..., session_id=...) supports runtime overrides and first-step input injection.

Examples

Run from repository root:

python examples/01_query_helper.py
python examples/02_query_streamed.py
python examples/03_query_then_receive_response.py
python examples/04_query_and_receive_response.py
python examples/05_receive_messages_multiple_turns.py
python examples/06_sessions.py
python examples/07_tools_and_permissions.py
python examples/08_openai_reasoning.py
python examples/09_resolve_backend.py
python examples/10_claude_async_prompt_iterable.py
python examples/11_websearch_tool_calls.py
python examples/12_webfetch_tool_calls.py
python examples/13_basic_workflow.py
python examples/14_workflow_context.py
python examples/15_workflow_init_override.py

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

glyph_agents-0.1.0a2.tar.gz (32.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

glyph_agents-0.1.0a2-py3-none-any.whl (37.4 kB view details)

Uploaded Python 3

File details

Details for the file glyph_agents-0.1.0a2.tar.gz.

File metadata

  • Download URL: glyph_agents-0.1.0a2.tar.gz
  • Upload date:
  • Size: 32.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for glyph_agents-0.1.0a2.tar.gz
Algorithm Hash digest
SHA256 be7520f63903c4d765e9aba3091e64c12c451a561af099f877d5592754cf9d29
MD5 938b31b3263b5fb36170c6f80b45455e
BLAKE2b-256 f04d5abbebb6cd0de049474a0dcec5191417378032b23b8a30cb6f89477a7a33

See more details on using hashes here.

File details

Details for the file glyph_agents-0.1.0a2-py3-none-any.whl.

File metadata

  • Download URL: glyph_agents-0.1.0a2-py3-none-any.whl
  • Upload date:
  • Size: 37.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for glyph_agents-0.1.0a2-py3-none-any.whl
Algorithm Hash digest
SHA256 10f538d9b354c498f7e475e1afdc796951e1c8853155384ec271028da12aabf9
MD5 88668b1c9759375d38e89b9846d86242
BLAKE2b-256 c4b14db6494899e2bf66619ca78505c7dcad368e896d74d2d32e445796d31c09

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page