Skip to main content

Vendor Agnostic Agent SDK

Project description

Glyph

Glyph is a vendor-agnostic Python SDK and CLI for building agent workflows with OpenAI and Anthropic models.

It is designed for flows where some steps should stay deterministic and only the parts that actually need an LLM should call one.

Glyph has two core use cases:

  • A vendor-agnostic agent SDK
  • A workflow builder that can act like a SKILL.md executor

Markdown Workflow Example

This example demonstrates how Glyph can execute workflows described entirely in Markdown.

Each step in your workflow can do one of the following:

  • If it includes an execute: key, or contains a Python or Bash code block, Glyph will run the code automatically for you.
  • If the step contains plain text, Glyph treats it as a prompt to the selected language model and generates a response.

This lets you blend deterministic logic and LLM-powered actions seamlessly in a single document.

---
name: writePostcard
description: if the user asks for a postcard, follow these steps
options:
  model: gpt-5.4-mini
  reasoning_effort: medium
  allowed_tools: [Read, Glob, Grep]
---

## Step: loadTripContext

```python
return {
  "city": "Lisbon",
  "mood": "warm and nostalgic",
  "memory": "the yellow tram climbing the hill at sunset",
}
```

<!-- Each step receives the previous step result automatically. -->

## Step: draftPostcard

<!-- Glyph fills template variables from previous step output. -->

Write a short postcard message from {{ city }}.

The mood should feel {{ mood }}.
Mention this memory: {{ memory }}.
Keep it to 3 sentences maximum.

## Step: savePostcard

```python
from pathlib import Path

output_path = Path(__file__).with_name("postcard.txt")
output_path.write_text(previous_result.message, encoding="utf-8")
return {"file_path": str(output_path)}
```

<!-- This is a hint for readibility purpose and is not required. -->
returns:
  file_path: str

Run the workflow with:

glyph workflow.md

Glyph is vendor-agnostic: change options.model and Glyph will infer the backend from the model name.

Markdown workflows support inline deterministic steps with fenced python or bash blocks. Inline bash steps run in the workflow directory and expose the previous step result as GLYPH_PREVIOUS_RESULT_JSON.

Install

pip install glyph-agents  # install in a virtualenv if you want the SDK
pipx install glyph-agents  # install only the glyph CLI

Requires Python >=3.10.

Glyph Python SDK

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
  • bash_timeout_ms: OpenAI Bash tool default timeout 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,
)

Workflows

GlyphWorkflow lets you compose multi-step flows where each step receives the previous step result. Define steps with @step, or put the workflow in Markdown and run it with glyph workflow.md.

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())

Python workflows (@step)

  • @step — plain Python step.
  • @step(prompt=..., model=..., is_streaming=...) — LLM step; model overrides the workflow default for that step only.
  • LLM steps: set up self.prompt in the method body (fill_prompt, etc.). If the method does not use yield, the query runs after it returns. If it uses yield (async generator), that starts the turn: with is_streaming=False you typically get AgentQueryCompleted after the first yield; with is_streaming=True each yield receives the next streamed AgentEvent until AgentQueryCompleted (see examples/16_workflow_streaming.py).
  • self.fill_prompt(...) — render prompt templates without failing on missing placeholders (they stay in the text).
  • self.next_step(self.some_step, value) — jump to another step with an explicit input.
  • self.stop_workflow(value) — end the workflow immediately; GlyphWorkflow.run(...) returns value.
  • GlyphWorkflow.run(options=..., initial_input=..., session_id=...) — runtime overrides and optional first-step input.

Markdown workflows (## Step:)

  • GlyphWorkflow.from_markdown(path) and run_markdown_workflow(path, ...) load a linear workflow from ## Step: sections.
  • The first ## Step: is the entrypoint.
  • Each step can be an LLM prompt, an inline Python block, or an execute: mapping (file plus optional function).
  • <!-- ... --> comments are ignored by the loader.

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
python examples/16_workflow_streaming.py
glyph examples/17_workflow_markdown/workflow.md
glyph examples/18_workflow_mardown_python/workflow.md

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.0.tar.gz (45.6 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.0-py3-none-any.whl (51.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: glyph_agents-0.1.0.tar.gz
  • Upload date:
  • Size: 45.6 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.0.tar.gz
Algorithm Hash digest
SHA256 b8a3e9687d108830258c439b135da47b1689cfbcf9a5b03cc83894443bbb7194
MD5 5a31661fab8e79e2a5bc2c5e6408849f
BLAKE2b-256 f24ea33d81ee2a537382b949c6f929b9b6387e86a6722a438940aae5eda220f1

See more details on using hashes here.

File details

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

File metadata

  • Download URL: glyph_agents-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 51.3 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bd198e8911fbcc38e7bf746a46dff67bcd429b728f90bec020063db98c8c1530
MD5 eede39eda3295beeed12ab3ba1e1ea1a
BLAKE2b-256 33677c92e04f65b8c928d7a3b3a8fb84359ddf2c6d1dd355b4b0a7908979cbc1

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