Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bd53625
Clean Ruff violations in core
proboscis Jun 11, 2026
cb449d5
Clean Ruff violations in doeff-agentic
proboscis Jun 11, 2026
1363ca5
Clean Ruff violations in doeff-agents
proboscis Jun 11, 2026
5697584
Clean Ruff violations in doeff-conductor
proboscis Jun 11, 2026
7bdb1b5
Clean Ruff violations in doeff-core-effects
proboscis Jun 11, 2026
20e84e6
Clean Ruff violations in doeff-docker
proboscis Jun 11, 2026
9f38c74
Clean Ruff violations in doeff-events
proboscis Jun 11, 2026
e67454a
Clean Ruff violations in doeff-flow
proboscis Jun 11, 2026
7becc13
Clean Ruff violations in doeff-gemini
proboscis Jun 11, 2026
02739b0
Clean Ruff violations in doeff-git
proboscis Jun 11, 2026
f1ee312
Clean Ruff violations in doeff-google-secret-manager
proboscis Jun 11, 2026
122a266
Clean Ruff violations in doeff-hy
proboscis Jun 11, 2026
8bfac2a
Clean Ruff violations in doeff-image
proboscis Jun 11, 2026
45c5bec
Clean Ruff violations in doeff-indexer
proboscis Jun 11, 2026
7fe979c
Clean Ruff violations in doeff-linter
proboscis Jun 11, 2026
aedd05c
Clean Ruff violations in doeff-llm
proboscis Jun 11, 2026
d8224d9
Clean Ruff violations in doeff-ml-nexus
proboscis Jun 11, 2026
07ad647
Clean Ruff violations in doeff-notify
proboscis Jun 11, 2026
e03d0ad
Clean Ruff violations in doeff-openai
proboscis Jun 11, 2026
e147a2b
Clean Ruff violations in doeff-openrouter
proboscis Jun 11, 2026
5dead2d
Clean Ruff violations in doeff-preset
proboscis Jun 11, 2026
66ee387
Clean Ruff violations in doeff-seedream
proboscis Jun 11, 2026
2500c83
Clean Ruff violations in doeff-test-target
proboscis Jun 11, 2026
12fa648
Clean Ruff violations in doeff-time
proboscis Jun 11, 2026
57d93be
Clean Ruff violations in doeff-traverse
proboscis Jun 11, 2026
2e39a91
Clean Ruff violations in doeff-vm
proboscis Jun 11, 2026
12504e5
Clean Ruff violations in doeff-vm-core
proboscis Jun 11, 2026
5b50bed
Document Ruff noqa reasons in core tests
proboscis Jun 11, 2026
95cd3d9
Document Ruff noqa reasons in doeff-hy
proboscis Jun 11, 2026
7720e6f
Document Ruff noqa reasons in doeff-gemini
proboscis Jun 11, 2026
dceed27
Document Ruff noqa reasons in doeff-google-secret-manager
proboscis Jun 11, 2026
e22f321
Document Ruff noqa reasons in doeff-ml-nexus
proboscis Jun 11, 2026
5867ecf
Document remaining Ruff noqa reasons in doeff-hy
proboscis Jun 11, 2026
51afe94
Format Ruff noqa import in doeff-gemini
proboscis Jun 11, 2026
852bc45
Format Ruff noqa import in doeff-google-secret-manager
proboscis Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
116 changes: 70 additions & 46 deletions doeff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,78 @@
Backed by a Rust VM with OCaml 5-aligned effect handler architecture.
"""

# ruff: noqa: I001 - import order avoids doeff_core_effects circular imports.
from collections.abc import Generator
from typing import Any, Callable, ParamSpec, TypeVar

from doeff.do import do
from doeff.program import (
Apply,
Expand,
GetExecutionContext,
GetHandlers,
GetOuterHandlers,
GetTraceback,
Pass,
Perform,
Pure,
Resume,
Transfer,
WithHandler,
WithHandlerType,
WithObserve as WithObserveRaw,
program,
)
from doeff_vm import Callable as _VmCallable
from typing import Any


def WithObserve(observer, body):
from doeff_vm import Callable as Callable
from doeff_vm import Callable as _VmCallable
from doeff_vm import EffectBase
from doeff_vm import K as K
from doeff_vm import PyVM as PyVM
from doeff_vm import UnhandledEffect as UnhandledEffect

from doeff.cli.run_services import DoeffRunContext as DoeffRunContext
from doeff.do import do as do
from doeff.mcp import McpParamSchema as McpParamSchema
from doeff.mcp import McpToolDef as McpToolDef
from doeff.program import Apply as Apply
from doeff.program import Expand as Expand
from doeff.program import GetExecutionContext as GetExecutionContext
from doeff.program import GetHandlers as GetHandlers
from doeff.program import GetOuterHandlers as GetOuterHandlers
from doeff.program import GetTraceback as GetTraceback
from doeff.program import Pass as Pass
from doeff.program import Perform as Perform
from doeff.program import Pure as Pure
from doeff.program import Resume as Resume
from doeff.program import ResumeThrow as ResumeThrow
from doeff.program import Transfer as Transfer
from doeff.program import TransferThrow as TransferThrow
from doeff.program import WithHandler as WithHandler
from doeff.program import WithHandlerType as WithHandlerType
from doeff.program import WithObserve as WithObserveRaw
from doeff.program import program as program
from doeff.result import Err as Err
from doeff.result import Maybe as Maybe
from doeff.result import Nothing as Nothing
from doeff.result import Ok as Ok
from doeff.result import Some as Some
from doeff.run import run as run

from doeff_core_effects.effects import Ask as Ask
from doeff_core_effects.effects import Await as Await
from doeff_core_effects.effects import Get as Get
from doeff_core_effects.effects import Listen as Listen
from doeff_core_effects.effects import Local as Local
from doeff_core_effects.effects import Put as Put
from doeff_core_effects.effects import Slog as Slog
from doeff_core_effects.effects import Tell as Tell
from doeff_core_effects.effects import Try as Try
from doeff_core_effects.effects import WriterTellEffect as WriterTellEffect
from doeff_core_effects.effects import slog as slog
from doeff_core_effects.scheduler import PRIORITY_HIGH as PRIORITY_HIGH
from doeff_core_effects.scheduler import PRIORITY_IDLE as PRIORITY_IDLE
from doeff_core_effects.scheduler import PRIORITY_NORMAL as PRIORITY_NORMAL
from doeff_core_effects.scheduler import AcquireSemaphore as AcquireSemaphore
from doeff_core_effects.scheduler import Cancel as Cancel
from doeff_core_effects.scheduler import CompletePromise as CompletePromise
from doeff_core_effects.scheduler import CreateExternalPromise as CreateExternalPromise
from doeff_core_effects.scheduler import CreatePromise as CreatePromise
from doeff_core_effects.scheduler import CreateSemaphore as CreateSemaphore
from doeff_core_effects.scheduler import FailPromise as FailPromise
from doeff_core_effects.scheduler import Future as Future
from doeff_core_effects.scheduler import Gather as Gather
from doeff_core_effects.scheduler import Promise as Promise
from doeff_core_effects.scheduler import Race as Race
from doeff_core_effects.scheduler import ReleaseSemaphore as ReleaseSemaphore
from doeff_core_effects.scheduler import Semaphore as Semaphore
from doeff_core_effects.scheduler import Spawn as Spawn
from doeff_core_effects.scheduler import Task as Task
from doeff_core_effects.scheduler import Wait as Wait


def WithObserve(observer, body): # noqa: N802 - public compatibility constructor
"""Install observer and run body under it.

Accepts a plain Python callable as observer — automatically wraps it
Expand All @@ -43,30 +90,9 @@ def WithObserve(observer, body):
f"WithObserve: observer must be callable, got {type(observer).__name__}"
)
return WithObserveRaw(_VmCallable(observer), body)
from doeff.program import ResumeThrow, TransferThrow
from doeff.run import run
from doeff.result import Ok, Err, Some, Nothing, Maybe # noqa: F811
from doeff.mcp import McpToolDef, McpParamSchema
from doeff_vm import Callable, EffectBase, K, PyVM, UnhandledEffect

Effect = EffectBase

# --- Compat re-exports (old API names) ---
# Effects (now in doeff_core_effects.effects)
from doeff_core_effects.effects import ( # noqa: E402
Ask, Get, Put, Local, Listen, Await, Try,
WriterTellEffect, Slog, slog, Tell,
)
# Scheduler effects (now in doeff_core_effects.scheduler)
from doeff_core_effects.scheduler import ( # noqa: E402
Spawn, Gather, Wait, Race, Cancel,
CreatePromise, CompletePromise, FailPromise,
CreateSemaphore, AcquireSemaphore, ReleaseSemaphore,
CreateExternalPromise,
Task, Future, Promise, Semaphore,
PRIORITY_IDLE, PRIORITY_NORMAL, PRIORITY_HIGH,
)

# DoExpr — virtual base type for all program nodes.
# Enables isinstance(x, DoExpr) to check if a value is any program node.
_DOEXPR_TYPES = (
Expand Down Expand Up @@ -151,6 +177,4 @@ def __getattr__(self, attr):
async_run = _Removed("async_run", "use run() with scheduled()")
default_async_handlers = _Removed("default_async_handlers", "compose handlers explicitly with WithHandler")

from doeff.cli.run_services import DoeffRunContext

__version__ = "0.4.1"
6 changes: 3 additions & 3 deletions doeff/cli/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import importlib
import inspect
import logging
from typing import Any, Protocol
from typing import Any, Protocol, cast

from doeff.cli.profiling import profile

Expand Down Expand Up @@ -54,8 +54,8 @@ class IndexerBasedDiscovery:
def __init__(self, symbol_loader: SymbolLoader | None = None):
with profile("Import doeff_indexer", indent=1):
try:
from doeff_indexer import Indexer
self.indexer_class = Indexer
indexer_module = cast(Any, importlib.import_module("doeff_indexer"))
self.indexer_class = indexer_module.Indexer
except ImportError as e:
raise ImportError(
"doeff-indexer not found. Install with: pip install doeff-indexer"
Expand Down
1 change: 0 additions & 1 deletion doeff/cli/hy_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from dataclasses import dataclass
from typing import Any


_REQUIRE_PRELUDE = (
"(require doeff-hy.macros [do! <-])\n"
"(require doeff-hy.handle [defhandler handle])"
Expand Down
17 changes: 11 additions & 6 deletions doeff/cli/run_services.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Run services — symbol resolution, interpreter discovery, program execution."""

import contextlib
import importlib
import importlib.util
import sys
Expand All @@ -12,10 +13,8 @@
# Without this, importlib.import_module for a .hy module silently creates an
# empty entry in sys.modules, and subsequent getattr(mod, name) fails with
# "module '<parent>' has no attribute '<child>'".
try:
with contextlib.suppress(ImportError):
import hy.importer # noqa: F401 — registers sys.meta_path hook
except ImportError:
pass

from doeff.cli.profiling import profile

Expand Down Expand Up @@ -173,13 +172,19 @@ def resolve_context(ctx: RunContext) -> ResolvedRunContext:

def default_interpreter(program: Any) -> Any:
"""Default interpreter: run with standard handlers + scheduler."""
from doeff import run, WithHandler
from doeff_core_effects.handlers import (
lazy_ask, state, writer, try_handler, slog_handler,
listen_handler, await_handler,
await_handler,
lazy_ask,
listen_handler,
slog_handler,
state,
try_handler,
writer,
)
from doeff_core_effects.scheduler import scheduled

from doeff import WithHandler, run

handlers = [
lazy_ask(), state(), writer(), try_handler, slog_handler(),
listen_handler, await_handler(),
Expand Down
5 changes: 3 additions & 2 deletions doeff/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from doeff.program import Expand
Expand Down Expand Up @@ -82,7 +83,7 @@ class McpToolDef:
name: str
description: str
params: tuple[McpParamSchema, ...]
handler: Callable[..., "Expand"]
handler: Callable[..., Expand]

def input_schema(self) -> dict[str, Any]:
"""Generate the ``inputSchema`` object for the MCP ``tools/list`` response."""
Expand Down
37 changes: 18 additions & 19 deletions doeff/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,21 @@
The VM classifies them via downcast (not tag-based getattr).
"""

from doeff_vm import (
Pure,
Perform,
Resume,
Transfer,
Apply,
Expand,
Pass,
WithHandler as _WithHandlerNode,
ResumeThrow,
TransferThrow,
WithObserve,
GetTraceback,
GetExecutionContext,
GetHandlers,
GetOuterHandlers,
)
from doeff_vm import Apply as Apply
from doeff_vm import Expand as Expand
from doeff_vm import GetExecutionContext as GetExecutionContext
from doeff_vm import GetHandlers as GetHandlers
from doeff_vm import GetOuterHandlers as GetOuterHandlers
from doeff_vm import GetTraceback as GetTraceback
from doeff_vm import Pass as Pass
from doeff_vm import Perform as Perform
from doeff_vm import Pure as Pure
from doeff_vm import Resume as Resume
from doeff_vm import ResumeThrow as ResumeThrow
from doeff_vm import Transfer as Transfer
from doeff_vm import TransferThrow as TransferThrow
from doeff_vm import WithHandler as _WithHandlerNode
from doeff_vm import WithObserve as WithObserve

WithHandlerType = _WithHandlerNode

Expand All @@ -41,7 +39,7 @@
)


def WithHandler(h, body, *args, **kwargs):
def WithHandler(h, body, *args, **kwargs): # noqa: N802 - public compatibility constructor
"""Install handler ``h`` around ``body`` — **deprecated**.

New-style handlers built with ``defhandler`` are already Program →
Expand Down Expand Up @@ -78,7 +76,8 @@ def program(gen_fn, *args):

The factory calls gen_fn and wraps the generator as IRStream explicitly.
"""
from doeff_vm import Callable as VmCallable, IRStream
from doeff_vm import Callable as VmCallable
from doeff_vm import IRStream

def factory(*inner_args):
gen = gen_fn(*inner_args)
Expand Down
5 changes: 2 additions & 3 deletions doeff/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
case Nothing: ...
"""

from doeff_vm import Ok, Err # noqa: F401 — re-export Rust types
from doeff_vm import Err, Ok # noqa: F401 — re-export Rust types


class Some:
Expand Down Expand Up @@ -91,5 +91,4 @@ def _get_nothing():


# Maybe is the union type (for type hints)
from typing import Union
Maybe = Union[Some, _NothingType]
Maybe = Some | _NothingType
13 changes: 6 additions & 7 deletions doeff/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def run(doexpr):
def _enrich_and_print(exception):
"""Enrich exception with doeff traceback and print to stderr."""
try:
if hasattr(exception, '__doeff_traceback__'):
if hasattr(exception, "__doeff_traceback__"):
# VM already set __doeff_traceback__ (e.g. handler chain),
# but pure Python frames from __traceback__ may be missing.
_merge_python_frames(exception)
Expand All @@ -38,7 +38,7 @@ def _enrich_and_print(exception):
pass # don't mask the original error


def _merge_python_frames(exc):
def _merge_python_frames(exc): # noqa: PLR0912 - baseline cleanup keeps existing control flow unchanged
"""Merge pure Python __traceback__ frames into existing __doeff_traceback__.

When the Rust VM sets __doeff_traceback__ (e.g. handler chain entries),
Expand All @@ -52,8 +52,8 @@ def _merge_python_frames(exc):
py_frames = []
for fs in tb_mod.extract_tb(tb):
fn = fs.filename
if any(p in fn for p in ('/doeff_vm/', '/doeff/do.py', '/doeff/run.py',
'/doeff_core_effects/')):
if any(p in fn for p in ("/doeff_vm/", "/doeff/do.py", "/doeff/run.py",
"/doeff_core_effects/")):
continue
py_frames.append([fs.name, fs.filename, fs.lineno])
if not py_frames:
Expand Down Expand Up @@ -91,10 +91,9 @@ def _enrich_exception_traceback(exc):
frames = []
for fs in tb_mod.extract_tb(tb):
fn = fs.filename
name = fs.name
# Skip doeff VM/framework internals
if any(p in fn for p in ('/doeff_vm/', '/doeff/do.py', '/doeff/run.py',
'/doeff_core_effects/')):
if any(p in fn for p in ("/doeff_vm/", "/doeff/do.py", "/doeff/run.py",
"/doeff_core_effects/")):
continue
frames.append([fs.name, fs.filename, fs.lineno])
if frames:
Expand Down
16 changes: 8 additions & 8 deletions doeff/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
import linecache
import os

_INTERNAL_PATHS = ('/doeff_core_effects/', '/doeff/do.py', '/doeff/run.py', '/doeff_vm/')
_INTERNAL_PATHS = ("/doeff_core_effects/", "/doeff/do.py", "/doeff/run.py", "/doeff_vm/")


def format_default(exception):
"""Render doeff traceback from __doeff_traceback__."""
tb_data = getattr(exception, '__doeff_traceback__', None)
tb_data = getattr(exception, "__doeff_traceback__", None)
if not tb_data:
return None

Expand Down Expand Up @@ -69,7 +69,7 @@ def _render_frame(func_name, source_file, source_line, count=1):
source_text = _get_source_line(source_file, source_line)
result = f"\n {func_name}() {short_file}:{source_line}"
if count > 1:
result += f" [×{count}]"
result += f" [×{count}]" # noqa: RUF001 - traceback format intentionally uses multiplication sign.
if source_text:
result += f"\n {source_text}"
return result
Expand All @@ -78,11 +78,11 @@ def _render_frame(func_name, source_file, source_line, count=1):
def _render_handler_chain(handler_chain):
names = []
for name in handler_chain:
name = str(name)
if ".<locals>." in name:
name = name.split(".<locals>.")[0]
if not names or names[-1] != name:
names.append(name)
rendered_name = str(name)
if ".<locals>." in rendered_name:
rendered_name = rendered_name.split(".<locals>.")[0]
if not names or names[-1] != rendered_name:
names.append(rendered_name)
if not names:
return ""
lines = ["\n handlers:"]
Expand Down
1 change: 1 addition & 0 deletions packages/doeff-agentic/examples/01_hello_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def hello_agent():

if __name__ == "__main__":
import asyncio

from doeff import WithHandler, async_run, default_handlers

async def main():
Expand Down
Loading
Loading