{"id":44429649,"url":"https://github.com/adhityaravi/theow","last_synced_at":"2026-03-12T04:01:11.369Z","repository":{"id":338000954,"uuid":"1151827648","full_name":"adhityaravi/theow","owner":"adhityaravi","description":"A programmatic autonomous LLM agent","archived":false,"fork":false,"pushed_at":"2026-03-08T23:26:35.000Z","size":1878,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-09T04:13:28.216Z","etag":null,"topics":["auto-failover","failure-recovery","llm","python3","rule-based-system","rules-engine","semantic-search","vector-database"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adhityaravi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-07T00:24:04.000Z","updated_at":"2026-03-08T23:25:56.000Z","dependencies_parsed_at":null,"dependency_job_id":"17474025-f701-48b6-9ea4-5665f11aa3ed","html_url":"https://github.com/adhityaravi/theow","commit_stats":null,"previous_names":["adhityaravi/theow"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/adhityaravi/theow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adhityaravi%2Ftheow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adhityaravi%2Ftheow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adhityaravi%2Ftheow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adhityaravi%2Ftheow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adhityaravi","download_url":"https://codeload.github.com/adhityaravi/theow/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adhityaravi%2Ftheow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30415037,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-12T00:40:14.898Z","status":"online","status_checked_at":"2026-03-12T02:00:07.260Z","response_time":114,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auto-failover","failure-recovery","llm","python3","rule-based-system","rules-engine","semantic-search","vector-database"],"created_at":"2026-02-12T12:19:34.046Z","updated_at":"2026-03-12T04:01:11.356Z","avatar_url":"https://github.com/adhityaravi.png","language":"Python","readme":"\u003cdiv align=\"center\"\u003e\n\n# theow\n\n*þēow - Old English for \"servant\" or \"bondman.\"*\n\n[![PyPI](https://img.shields.io/pypi/v/theow)](https://pypi.org/project/theow/)\n[![Python](https://img.shields.io/pypi/pyversions/theow)](https://pypi.org/project/theow/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![PydanticAI](https://img.shields.io/badge/built%20with-PydanticAI-6F42C1)](https://ai.pydantic.dev/)\n[![Logfire](https://img.shields.io/badge/observable%20with-Logfire-F97316)](https://logfire.pydantic.dev/)\n\n\u003c/div\u003e\n\n---\n\nWhat if an agent could live inside your running process? Not a chatbot you prompt, not a linter scanning cold output, but something that's *there* when the exception fires. It sees the state, investigates the failure, fixes it or reports what it found. Leashed, under control, following your rules, but autonomous.\n\nImagine a CI that heals itself by running an agent inside a live integration test. Or a deployment pipeline that recovers from config drift without human input. Theow is the framework that makes this possible.\n\n---\n\nTheow is an observable, programmatic LLM agent that auto-heals failing Python functions at runtime. Wrap any function with `@theow.mark()`, and when it raises, theow intercepts the exception, diagnoses it, and retries transparently. Every LLM call, tool execution, and token spend is [traced via OpenTelemetry](docs/observability.md). Zero prompt engineering. Zero code changes beyond the decorator.\n\n```python\nfrom theow import Theow\nfrom theow.tools import read_file, write_file, run_command\n\nagent = Theow(llm=\"anthropic/claude-sonnet-4-20250514\")\nagent.tool()(read_file)\nagent.tool()(write_file)\n\n@agent.mark(\n    context_from=lambda task, exc: {\"stage\": \"deploy\", \"error\": str(exc)},\n    explorable=True,\n)\ndef deploy(task):\n    ...  # when this fails, theow heals it\n```\n\n## Why theow\n\nTheow simplifies programmatic LLM agents through three complementing layers.\n\n```mermaid\nflowchart LR\n    D[\"@mark'd function\"] --\u003e|raises| R[\"Resolver\"]\n    R --\u003e|rule found| A[\"Execute action\"]\n    R --\u003e|no rule| E[\"Explorer\"]\n    E --\u003e|LLM diagnoses \u0026 writes rule| A\n    A --\u003e V[\"Re-run function\"]\n    V --\u003e|pass| Done\n    V --\u003e|new error| R\n```\n\n### Layer 1: Conversational agent\n\nTheow wraps [PydanticAI](https://ai.pydantic.dev/) and the [GitHub Copilot SDK](https://github.com/features/copilot) into a single interface. Give it a prompt, a set of [tools](docs/tools.md), and a token/call budget. Theow runs a conversation loop with the LLM, executing tool calls until the task is done or budget runs out.\n\n```python\nagent = Theow(llm=\"anthropic/claude-sonnet-4-20250514\")\nagent.tool()(read_file)\nagent.tool()(run_command)\n\nagent.run(\"Fix the broken config file in ./config/\", tools=agent.get_tools())\n```\n\nPydanticAI can do this on its own. Theow wraps it into a simpler API, adds a unified interface across the PydanticAI providers (15+) and the Copilot SDK (which is a different protocol entirely), and manages a custom conversation loop with [signals, budget tracking, and nudging](docs/architecture.md). You can optionally enable [middleware](docs/middleware.md) for guardrails on LLM input/output and [Logfire](docs/configuration.md#logfire--opentelemetry) for OpenTelemetry instrumentation. The next two layers are where theow diverges from plain LLM wrappers.\n\n### Layer 2: Explorer\n\nThe explorer takes an error context and diagnoses the problem using internal prompts and whatever tools you've registered. No prompt engineering required.\n\nBeyond finding a fix, the explorer converts the LLM's solution into a rule-action pair: a [YAML rule](docs/rules-and-actions.md) that pattern-matches the error, paired with a Python action that fixes it. These pairs persist to disk and get indexed in ChromaDB for retrieval. Remote store support is planned. See [how exploration works](docs/exploration.md) for the full flow.\n\n```python\nagent = Theow(llm=\"anthropic/claude-sonnet-4-20250514\")\nagent.tool()(read_file)\nagent.tool()(write_file)\n\ncontext = {\n    \"error\": \"FileNotFoundError: config.yaml not found\",\n    \"stderr\": \"Traceback ...\\nFileNotFoundError: config.yaml not found\",\n}\n\nrule = agent.explore(context, tools=agent.get_tools())\n# rule is a validated Rule object, or None if exploration failed\n```\n\nSee [`explore()` API reference](docs/configuration.md#theowexplorecontext-tools-collection-tracing---rule--none).\n\n### Layer 3: Resolver\n\nThe resolver checks if a matching rule already exists before calling the LLM. It tries explicit name/tag filtering first, then semantic search over the rule database. If a rule matches, its action runs immediately. No LLM call, no tokens spent.\n\n```python\nagent = Theow(theow_dir=\".theow\")\n\ncontext = {\n    \"error\": \"FileNotFoundError: config.yaml not found\",\n    \"stderr\": \"Traceback ...\\nFileNotFoundError: config.yaml not found\",\n}\n\nrule = agent.resolve(context)\nif rule:\n    agent.execute_rule(rule, context)\n```\n\nSee [`resolve()` API reference](docs/configuration.md#theowresolvecontext---rule--none) and [`execute_rule()` API reference](docs/configuration.md#theowexecute_rulerule-context-escalation_context---bool).\n\nThe resolver can optionally invoke the explorer when no rule matches, creating a closed loop: **fail -\u003e resolve -\u003e (miss) -\u003e explore -\u003e create rule -\u003e resolve next time**. First failure: the LLM investigates and writes a rule. Second failure of the same kind: the rule fires instantly. As rules accumulate, LLM calls decrease. Since failure modes are finite, they may reach zero.\n\nRules can also define LLM actions. Instead of running Python code, the matched rule triggers a conversation with a pre-stored prompt from your prompt library. This gives you deterministic routing with dynamic execution. For structural awareness during exploration, you can plug in [CodeGraph](docs/codegraph.md), a tree-sitter based code graph that lets the LLM query symbols, call chains, and class hierarchies instead of reading entire files.\n\n### The decorator\n\nThe resolver-explorer pair is assembled into `@theow.mark()`, which wraps any Python function for automatic recovery:\n\n```python\n@agent.mark(\n    context_from=lambda task, exc: {\"error\": str(exc), \"task_id\": task.id},\n    explorable=True,    # allow LLM exploration on novel errors\n    max_retries=3,      # rules to try per error\n    max_depth=3,        # chase cascading errors\n)\ndef process(task):\n    ...\n```\n\nWhen `process()` raises, the decorator intercepts the exception, calls `context_from` with the original arguments and the exception to build a context dict, automatically enriches it with the full traceback and exception type, then hands it to the resolver. If no rule matches and `explorable=True`, the explorer takes over. If a fix works, the function is retried transparently within the same call stack, so the caller never knows recovery happened.\n\nThe decorator also handles [deep recovery](docs/hooks.md#deep-recovery) (when a fix reveals a new error underneath, theow keeps the changes and continues against the new error), [model escalation](docs/configuration.md#model-routing) (cheap model first, strong model as fallback), and [lifecycle hooks](docs/hooks.md) (setup/teardown callbacks around each recovery attempt). For the full set of [configuration options](docs/configuration.md) including provider setup, see the linked docs. Theow also ships with a [CLI](docs/cli.md) for running explorations from the command line.\n\n## Quick start\n\n```bash\npip install theow\n```\n\n```python\nfrom theow import Theow\nfrom theow.tools import read_file, write_file, run_command\n\nagent = Theow(\n    theow_dir=\".theow\",\n    llm=\"anthropic/claude-sonnet-4-20250514\",\n)\n\nagent.tool()(read_file)\nagent.tool()(write_file)\nagent.tool()(run_command)\n\n@agent.mark(\n    context_from=lambda task, exc: {\"error\": str(exc)},\n    explorable=True,\n)\ndef process(task):\n    ...\n```\n\nSet your provider's API key and enable exploration:\n\n```bash\nANTHROPIC_API_KEY=sk-... THEOW_EXPLORE=1 python my_script.py\n```\n\n## Sounds good, does it work?\n\n[Parrot](https://github.com/charmarr/parrot) is a CI auto-healing agent built on theow. It wraps `tox` in a GitHub Actions pipeline. Each test stage (lint, static, unit, integration) is a function wrapped with `@theow.mark()`.\n\nWhen a test fails, parrot intercepts the failure right there in the CI process. The agent has the full error output, the codebase on disk, and tools to read files, write fixes, and re-run commands. It investigates the failure, applies a fix, and retries. For integration tests, it can inspect the live Juju model, query workload logs, and debug a running environment, not a static snapshot of what went wrong.\n\nIf the fix works, parrot opens a PR with the changes. If it can't fix it, it comments on the original PR with what it found.\n\n**Example:** [PR #51](https://github.com/charmarr/charmarr/pull/51) introduced a typo in a config path (`/confgi/config.xml`). Parrot caught it in two stages: the linter flagged the spelling errors, and the unit tests failed because the path didn't resolve. Parrot fixed both independently and opened [PR #52](https://github.com/charmarr/charmarr/pull/52) (lint fix) and [PR #53](https://github.com/charmarr/charmarr/pull/53) (unit test fix), then commented on the original PR with links to both.\n\nCI logs from the unit test run:\n\n```\n# tox runs, test fails\nFAILED tests/unit/test_actions.py::test_rotate_api_key_action\nassert 'newkey__123456789012345678901234' in '\u003c?xml version=\"1.0\" ...\n  \u003cApiKey\u003etestkey123456789012345678901234\u003c/ApiKey\u003e ...'\n\n# parrot intercepts the failure, matches a rule, starts the LLM\n[info] Failure captured                 [Parrot:recovery]\n[info] Attempting recovery              [Parrot:recovery] rule=unit_llm\n[info] Starting LLM action              [Parrot:explorer] session=1/10\n[info] Code graph built                 [Parrot:codegraph] edges=2906 files=101 nodes=846\n\n# agent fixes the typo, parrot re-runs tox — 26/26 pass\ntests/unit/test_actions.py::test_rotate_api_key_action PASSED\n...\n============================== 26 passed in 8.75s ==============================\n\n# teardown creates the fix PR\n[info] Fix PR created                   [Parrot:lifecycle] url=https://github.com/charmarr/charmarr/pull/53\nHealed by parrot. PR: https://github.com/charmarr/charmarr/pull/53\n```\n\n[Full trace on Logfire](https://logfire-eu.pydantic.dev/ivdi/charmarr?q=trace_id%3D%27019cd558ccd57d17b8d175f70da21773%27+and+span_id%3D%2720ede65e763d7b36%27\u0026spanId=20ede65e763d7b36\u0026traceId=019cd558ccd57d17b8d175f70da21773\u0026env=-clear-\u0026since=2026-03-10T00%3A55%3A55.112283Z\u0026until=2026-03-10T01%3A55%3A55.112283Z) (requires a Logfire account).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadhityaravi%2Ftheow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadhityaravi%2Ftheow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadhityaravi%2Ftheow/lists"}