| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
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.
| Back | FazBrowse Home | New Git URL |
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:
input model, Google-style docstring becomes the description and per-parameter docs,
return value is coerced into a ToolResult. The decorated function stays callable.
and auto-detects provider/model from whichever credentials are present.
Command routing, compile-time validation, event streaming, to_mermaid().
Each is inert unless enabled; no separate deep-agent class.
Change type
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):
(test_function_tool, test_agent_ergonomics, test_crew, test_workflow,
test_agent_long_horizon, test_introspect, test_public_api), ~2400 lines
tests/test_agent_long_horizon.py:10:53 F401 'anycode.TodoItem' imported but unused
Examples 45–48 were run against a live provider.
Documentation and release notes
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
since this branch also carries the version bump
Reviewer notes
_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.
spirit but is observable, so it is called out in the changelog under Changed.
anycode.types was previously masked by the eager package __init__. Each of
types, helpers, contracts, identity, core, memory now imports standalone.
from v0.9.0. Should be [Unreleased]: …/compare/v0.10.0...HEAD plus
[0.10.0]: …/compare/v0.9.0...v0.10.0.
merges usage into the parent result. Worth a look if you expect recursive delegation.