FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Feat/dx overhaul by AliMuhammadOfficial · Pull Request #9 · Quantlix/anycode · GitHub

Feat/dx overhaul - #9

Merged
AliMuhammadOfficial merged 11 commits into
mainfrom
feat/dx-overhaul
Jul 25, 2026
Merged

Feat/dx overhaul#9
AliMuhammadOfficial merged 11 commits into
mainfrom
feat/dx-overhaul

Conversation

Copy link
Copy Markdown
Member

Pull Request

Summary

import anycode cost ~1.9 s and 1312 modules, and a working agent took three objects
(registry + executor + agent) to assemble. This PR adds the 0.10 developer-experience
layer on top of the 0.9 runtime without touching a single existing signature.

Observable changes:

  • @tool turns any Python function into an agent tool — signature becomes the Pydantic
    input model, Google-style docstring becomes the description and per-parameter docs,
    return value is coerced into a ToolResult. The decorated function stays callable.
  • Agent(name=..., instructions=..., tools=[...]) wires its own registry and executor
    and auto-detects provider/model from whichever credentials are present.
  • Crew — one-import multi-agent facade over the existing wavefront scheduler.
  • Workflow — state-graph runtime with branching, looping, retry, fan-out, reducers,
    Command routing, compile-time validation, event streaming, to_mermaid().
  • Long-horizon switches on Agent: planning=True, subagents=[...], workspace=....
    Each is inert unless enabled; no separate deep-agent class.
  • Blocking entry points: run_sync, prompt_sync, stream_sync, call_tool_sync.
  • anycode.describe() and anycode api emit the public surface as machine-readable data.
  • Lazy public API: import drops to ~33 ms / 73 modules; first Agent use 1758 ms → ~414 ms.

Change type

  • Bug fix
  • Backward-compatible feature
  • Breaking change
  • Documentation
  • Test or internal maintenance
  • Security-sensitive change handled through a private advisory

Compatibility

  • Python API: additive only. New exports (tool, Crew, Workflow, SubAgentSpec,
    TodoItem, describe, …) and new optional keyword arguments. No 0.9.0 symbol was
    removed, renamed, or re-typed. One behavior refinement: Agent(tools=[]) now means
    no tools — previously an empty list was indistinguishable from None. tools=None
    is still the default and still means every built-in tool.

  • Import semantics: attribute access on anycode is now lazy. Names resolve on first
    access and cache into the module namespace; a TYPE_CHECKING block keeps every name
    statically resolvable for pyright/IDEs. Unknown attributes raise AttributeError with
    a did-you-mean suggestion instead of the bare message.

  • CLI: one new command group, anycode api (--core, --compact, --json, or a
    single symbol). No existing command changed.

  • YAML/TOML: none.

  • Checkpoints / durable run data: Task gained an optional expected_output field
    that round-trips through the queue and checkpoints. Older checkpoints deserialize with
    it unset; newer checkpoints read by 0.9.0 ignore the extra key.

  • Provider/tool protocols: unchanged. as_tool_definition() normalizes a
    ToolDefinition, a decorated function, a plain function, or a built-in tool name, so
    existing ToolDefinition producers keep working untouched.

  • New stop reason: max_steps, emitted only by the new workflow runtime.

  • Version impact: minor (0.9.0 → 0.10.0, already bumped in pyproject.toml)

  • Deprecation or migration path: nothing deprecated. site_docs/contributing/migration-0.10.md
    covers opting into the new ergonomics; existing programs need no changes.

  • Rollback or persisted-data impact: safe. Rolling back to 0.9.0 leaves checkpoints
    readable (expected_output is dropped); no schema migration, no on-disk format change.

Verification

Commands run on this branch (Windows, Python 3.14):

  • Regression or behavior tests added or updated — 6 new test modules
    (test_function_tool, test_agent_ergonomics, test_crew, test_workflow,
    test_agent_long_horizon, test_introspect, test_public_api), ~2400 lines
  • uv run python -m pytest — 1146 tests, exit 0 (13 deselected)
  • uv run python -m ruff check . — 1 error:
    tests/test_agent_long_horizon.py:10:53 F401 'anycode.TodoItem' imported but unused
  • uv run python -m ruff format --check src/
  • uv run python -m pyright
  • uv run python -m mkdocs build --strict

Examples 45–48 were run against a live provider.

Documentation and release notes

  • Public docstrings and reference pages match the implementation
  • Guides, examples, README, and site_docs/llms.txt updated where needed —
    new guides for function tools, crews, workflows, long-horizon agents; a recipes
    page; reference/llm-guide.md for AI coding agents; CLI reference for anycode api
  • Changelog entry added — under ## [0.10.0] - 2026-07-25, not [Unreleased],
    since this branch also carries the version bump
  • Security, configuration, or release documentation updated where needed

Reviewer notes

  • Highest-risk file is src/anycode/__init__.py (+1412 / -~700). The lazy resolver in
    _lazy.py maps every public name to its defining module; test_public_api.py asserts
    the lazy map and the TYPE_CHECKING block agree with __all__, so a name added to one
    and not the other fails CI rather than failing at a user's first attribute access.
  • tools=[] semantics is the only behavior refinement in the PR. It is a bug fix in
    spirit but is observable, so it is called out in the changelog under Changed.
  • Circular import fix: anycode.types → identity → contracts → helpers →
    anycode.types was previously masked by the eager package __init__. Each of
    types, helpers, contracts, identity, core, memory now imports standalone.
  • Two loose ends before merge:
    1. The ruff check F401 above (one-line fix).
    2. CHANGELOG.md has no [0.10.0]: link reference, and [Unreleased] still compares
      from v0.9.0. Should be [Unreleased]: …/compare/v0.10.0...HEAD plus
      [0.10.0]: …/compare/v0.9.0...v0.10.0.
  • Sub-agent depth is hard-capped at one; delegation runs on a fresh conversation and
    merges usage into the parent result. Worth a look if you expect recursive delegation.

Any Python function can now be an agent tool. The signature becomes the
Pydantic input model, the Google-style docstring becomes the tool
description and per-parameter descriptions, and the return value is
coerced into a ToolResult. Sync functions run off the event loop via
asyncio.to_thread; a ToolUseContext parameter is injected rather than
exposed in the schema.

as_tool_definition() normalizes every accepted tool form -- a
ToolDefinition, a decorated function, a plain function, or the name of a
bundled tool -- so later layers can accept tools=[fn, "bash", definition].

Also fixes a clock-resolution flake in FilesystemRunStore.mark_interrupted_runs:
the staleness cutoff is now inclusive, so stale_after_seconds=0 means
"every running run is stale" even when the platform clock has not ticked.
Agent gains a keyword constructor that wires its own registry and
executor, so a working agent is one object instead of three:

    Agent(name="researcher", instructions="...", tools=[search, "bash"])

Provider and model are auto-detected from whichever credentials are
present (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY,
AZURE_OPENAI_API_KEY, AWS_DEFAULT_REGION, OLLAMA_BASE_URL), removing the
detection block every example copy-pastes. ANYCODE_DEFAULT_MODEL and
ANYCODE_DEFAULT_PROVIDER override it.

CrewAI-style role/goal/backstory framing composes into a system prompt
when instructions= is absent. tools= accepts decorated functions, plain
functions, ToolDefinitions, and built-in names; tools=[] now expresses
"no tools", which was previously inexpressible.

Adds blocking entry points for scripts and notebooks -- run_sync,
prompt_sync, stream_sync (incremental, via a worker loop), and
call_tool/call_tool_sync for exercising a tool without the LLM -- plus a
tools property and a useful __repr__.

The three-positional-argument constructor is unchanged; passing a config
object alongside a conflicting field keyword raises AgentConfigError
naming the conflict.
Crew is a one-import multi-agent entry point: it owns an AnyCode engine
and a Team, and contributes no scheduling logic of its own.

    crew = Crew(agents=[researcher, writer], tasks=[...])
    result = crew.run_sync()

Tasks accept a TaskSpec, a dict of TaskSpec arguments, or a bare string
title. process="sequential" chains tasks that declare no dependency of
their own; process="dependency" (the default) honors declared edges only.
With no tasks at all, run(goal) falls through to the existing coordinator
decomposition. Unknown orchestrator options are rejected with the valid
list rather than a Pydantic traceback.

TaskSpec gains agent=, expected_output=, and context=, and its description
defaults to the title. Task carries expected_output through the queue and
checkpoints so it reaches the agent prompt as an explicit "Expected
output:" line.

AnyCode.register_agent adopts a pre-built Agent, so a crew member keeps
the tool registry it was constructed with instead of being rebuilt from
its config.

CrewResult exposes success/output/outputs/usage/cost and keeps the full
TeamRunResult attached; str(result) is the final task's output.
Workflow gives AnyCode explicit control flow -- branching, looping, retry,
fan-out -- which the wavefront scheduler cannot express, since it only
fans out on task dependencies. Crew stays the declarative path; Workflow
is the imperative one, and a Crew or Agent can be a node in a graph.

    wf = Workflow(ReviewState)
    wf.add_node("write", write); wf.add_node("review", review)
    wf.add_edge(START, "write"); wf.add_edge("write", "review")
    wf.add_conditional_edge("review", gate)
    result = await wf.compile().run(ReviewState(topic="..."))

State is a frozen Pydantic model (or a dict when no schema is given).
Nodes return a patch, a full state instance, a Command, or None. Fields
annotated Annotated[T, add] accumulate rather than being replaced;
merge, keep_first, and keep_last ship alongside add.

Nodes may be async functions, sync functions (run off the event loop),
an Agent, a Crew, or another compiled workflow. Routing is a static edge,
a conditional edge with or without a path map, or a Command returned by
the node. add_node(goto=[...]) declares Command targets so reachability
analysis stays accurate.

compile() aggregates every structural problem into one message: missing
entry, unregistered edge targets, unreachable nodes, non-terminating
cycles, and bad path-map targets. Execution streams node_start/node_end/
route/done events, caps work at max_steps (reported as the new max_steps
StopReason code), merges concurrent patches and rejects conflicting plain
writes, and renders itself with to_mermaid()/to_dict().
Importing anycode pulled in every subsystem -- vector stores, redis,
OpenTelemetry, the MCP SDK, provider clients -- whether a program touched
them or not. Exports now resolve on first attribute access through a
name -> (module, attribute) map and cache into the module namespace, so
repeat lookups are a plain dict hit.

    import anycode          1913 ms / 1312 modules  ->  33 ms / 73 modules
    anycode.Agent           1758 ms                 ->  414 ms
    anycode.Crew            1855 ms                 ->  476 ms

No public symbol moved or changed name. A TYPE_CHECKING block mirrors the
map so pyright and IDEs still resolve everything statically, and tests
assert that __all__ and the export map stay in sync, that every name
resolves, and that a bare import loads no optional dependency.

The same treatment applies to the core, mcp, telemetry, memory, and
helpers subpackages, whose eager __init__ re-exports were what actually
dragged the heavy dependencies in, and AnyCode.connect_mcp_servers now
imports the MCP SDK at the point of use.

Fixes a latent import cycle the eager package __init__ was masking:
anycode.types -> identity -> contracts -> helpers -> anycode.types meant
`import anycode.types` on its own raised ImportError. Each of
anycode.types, .helpers, .contracts, .identity, .core, and .memory is now
importable standalone, and a test holds that line.

Unknown attributes raise AttributeError with a did-you-mean suggestion.
Missing optional memory backends report the extra to install instead of
silently vanishing from the namespace.
Long-horizon behavior arrives as three opt-in keywords on the existing
Agent rather than a second agent type. A plain agent is untouched: every
capability is inert unless switched on.

    Agent(
        name="researcher",
        instructions="...",
        tools=[search],
        planning=True,
        subagents=[SubAgentSpec(name="critic", instructions="...")],
        workspace="./.anycode/workspace",
    )

planning= registers write_todos, which returns the rendered checklist so
the plan re-enters the context on every update, and exposes it as
agent.todos. More than one in_progress step returns an error result
telling the model to fix it -- a model mistake is not a program fault.

subagents= registers a delegate tool. Each sub-agent is a full Agent
built lazily, inheriting the parent's model, provider, security policy,
and execution context. It runs on a fresh conversation, so only the task
and the context the caller passes cross the boundary; sub-agents never
receive delegate themselves, so depth is one by construction. Their token
usage is merged into the parent's AgentRunResult.

workspace= creates the directory and confines the file tools to it via
the existing ToolSecurityPolicy, with shell access following whether bash
was requested. An explicitly passed tool_security always wins.

Each capability appends one prompt clause, snapshot-tested so prompt
drift shows up as a reviewable diff.

Also fixes Agent.call_tool, which could not invoke a side-effecting tool
at all: it now supplies a fresh idempotency key when the arguments do not
carry one.
An AI coding agent should not have to read 27k lines of source to learn
what AnyCode offers. anycode.describe() renders the public surface as
data, and `anycode api` prints it.

    anycode api --core        the 15 symbols covering most use  (4.6 KB)
    anycode api --compact     every symbol, no signatures      (29 KB)
    anycode api Agent         one symbol in full
    anycode api --json        machine-readable, stable key order

Entries carry name, kind, module, signature, and docstring summary.
Pydantic models render their real field list instead of (**data: Any),
constants show their value rather than their type's docstring, and the
generic BaseModel docstring is suppressed.

Tests hold the core surface inside a character budget and assert that
describe() covers exactly __all__, so a new export cannot slip in
undocumented.
New guides for function tools, crews, workflows, and long-horizon agents.
A recipes page of complete runnable snippets, and an orientation page
written for AI coding agents that points at `anycode api --core` instead
of the source tree.

The quickstart is rebuilt as three levels -- one agent with a tool, a
crew with dependent tasks, a workflow with a review loop -- and the
README hero shows the same progression. The public API reference opens
with the core-surface table; the tools and multi-agent-team guides now
point at the shorter path first while keeping the engine-level API they
document.

Adds a migration page stating plainly that nothing is required, with
old-to-new snippets and the one behavioral note: Agent(tools=[]) now
means no tools.

AGENTS.md tells agents working in this repo to discover the API with
`anycode api` rather than reading src/.

Version 0.10.0; mkdocs builds clean under --strict.
llms.txt gains the four new guides, the recipes and LLM-guide pages, and
a note telling an AI agent to read those two first. The CLI reference
documents `anycode api` with its options and real output sizes.

Also fixes examples/33_mcp_http_auth.py, which demonstrated fail-closed
auth resolution but did not handle it: resolve_auth_headers raises when a
config declares auth_token_env and that variable is unset, so section C
crashed instead of showing the refusal. The example now catches and
prints it, which is what the section was meant to demonstrate.
AliMuhammadOfficial merged commit 8f5d15d into main Jul 25, 2026
14 of 35 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant


Back | FazBrowse Home | New Git URL