Spaces:
Sleeping
Sleeping
| # Composing Custom Pipelines | |
| ACE is built on a composable pipeline engine. Every runner (`ACE`, `BrowserUse`, | |
| `LangChain`, `ClaudeCode`, `TraceAnalyser`) is a thin wrapper around a `Pipeline` | |
| made of steps. You can compose your own pipelines by mixing and matching these | |
| steps — or writing custom ones. | |
| ## Three Levels of ACE | |
| | Level | Pattern | Control | | |
| |-------|---------|---------| | |
| | **Zero-config** | `ACELiteLLM.from_model("gpt-4o-mini")` | Roles + pipeline auto-created | | |
| | **Role customisation** | `ACE.from_roles(agent=..., reflector=..., ...)` | Custom roles, pipeline auto-composed | | |
| | **Pipeline composition** | `Pipeline([AgentStep(...), ...])` | Full control over step ordering | | |
| This guide covers **Level 3** — composing pipelines directly. | |
| ## Anatomy of an ACE Pipeline | |
| Every ACE pipeline is a sequence of steps, each with a `requires`/`provides` | |
| contract that declares what context fields it reads and writes: | |
| ``` | |
| AgentStep ─────> EvaluateStep ─────> ReflectStep ─────> UpdateStep | |
| provides: provides: provides: provides: | |
| agent_output trace reflections skill_manager_output | |
| (also mutates skillbook | |
| via the SM's tools) | |
| ``` | |
| The pipeline validates these contracts at construction time — if a step requires | |
| a field that no earlier step provides, you'll get an error immediately. | |
| ## Composing from Steps | |
| All pipeline classes and ACE steps are importable from `ace`: | |
| ```python | |
| from ace import ( | |
| # Pipeline engine | |
| Pipeline, Branch, MergeStrategy, StepProtocol, SampleResult, | |
| # ACE context | |
| ACEStepContext, SkillbookView, | |
| # Roles | |
| Agent, Reflector, SkillManager, | |
| # Steps | |
| AgentStep, EvaluateStep, learning_tail, | |
| # Types | |
| Sample, Skillbook, SimpleEnvironment, | |
| ) | |
| skillbook = Skillbook() | |
| pipe = Pipeline([ | |
| AgentStep(Agent("gpt-4o-mini"), skillbook), | |
| EvaluateStep(SimpleEnvironment()), | |
| *learning_tail(Reflector("gpt-4o-mini"), SkillManager("gpt-4o-mini"), skillbook), | |
| ]) | |
| ``` | |
| ## Using `learning_tail()` | |
| The `learning_tail()` helper returns the standard learning step sequence: | |
| ```python | |
| from ace import learning_tail, Reflector, SkillManager, Skillbook | |
| steps = learning_tail( | |
| Reflector(llm), | |
| SkillManager(llm), | |
| Skillbook(), | |
| dedup_manager=my_dedup_manager, # optional | |
| checkpoint_dir="/tmp/checkpoints", # optional | |
| ) | |
| # Returns: [ReflectStep, UpdateStep, | |
| # DeduplicateStep, CheckpointStep] | |
| ``` | |
| Use it when building custom integrations that provide their own execute step but | |
| want the standard learning pipeline. | |
| ## Inspecting Runner Presets with `build_steps()` | |
| Every runner has a `build_steps()` classmethod that returns the step list it | |
| would use internally. This lets you inspect, modify, and recompose: | |
| ```python | |
| from ace import ACE, Pipeline, ACERunner, Skillbook | |
| # Get the default steps | |
| steps = ACE.build_steps( | |
| agent=my_agent, | |
| reflector=my_reflector, | |
| skill_manager=my_skill_manager, | |
| environment=my_env, | |
| ) | |
| # Insert a custom step after EvaluateStep | |
| steps.insert(2, MyLoggingStep()) | |
| # Build your own pipeline and runner | |
| skillbook = Skillbook() | |
| pipe = Pipeline(steps) | |
| runner = ACERunner(pipeline=pipe, skillbook=skillbook) | |
| results = runner.run(samples) | |
| ``` | |
| All runners support `build_steps()`: `ACE`, `BrowserUse`, `ClaudeCode`, | |
| `LangChain`, and `TraceAnalyser`. | |
| ## Writing Custom Steps | |
| A step is any object satisfying `StepProtocol` — no base class needed: | |
| ```python | |
| from ace import ACEStepContext | |
| class MyLoggingStep: | |
| requires = frozenset({"agent_output"}) | |
| provides = frozenset() | |
| def __call__(self, ctx: ACEStepContext) -> ACEStepContext: | |
| print(f"Agent answered: {ctx.agent_output.final_answer}") | |
| return ctx | |
| ``` | |
| Key rules: | |
| - `requires`: frozenset of context field names this step reads | |
| - `provides`: frozenset of context field names this step writes | |
| - `__call__`: receives and returns `ACEStepContext` (use `ctx.replace(...)` for updates) | |
| - Steps should be stateless — no internal counters | |
| ## Mixing Integrations | |
| You can compose steps from different integrations into one pipeline. For example, | |
| combining a browser-use execute step with custom learning: | |
| ```python | |
| from ace import Pipeline, learning_tail, Reflector, SkillManager, Skillbook | |
| from ace.integrations.browser_use import BrowserExecuteStep, BrowserToTrace | |
| skillbook = Skillbook() | |
| pipe = Pipeline([ | |
| BrowserExecuteStep(browser_llm), | |
| BrowserToTrace(), | |
| MyCustomFilterStep(), # your custom step | |
| *learning_tail(Reflector(llm), SkillManager(llm), skillbook), | |
| ]) | |
| ``` | |
| Integration steps live in `ace.integrations` since they have | |
| framework-specific dependencies. | |
| ## Running the Pipeline | |
| ### With a runner | |
| The simplest way to run a custom pipeline is through `ACERunner`: | |
| ```python | |
| from ace import ACERunner, Sample, Skillbook | |
| runner = ACERunner(pipeline=pipe, skillbook=skillbook) | |
| results = runner.run( | |
| [Sample(question="What is 2+2?", ground_truth="4")], | |
| epochs=1, | |
| ) | |
| ``` | |
| ### Directly | |
| You can also run the pipeline directly by constructing contexts yourself: | |
| ```python | |
| from ace import Pipeline, ACEStepContext, SkillbookView, Sample, Skillbook | |
| ctx = ACEStepContext( | |
| sample=Sample(question="What is 2+2?", ground_truth="4"), | |
| skillbook=SkillbookView(skillbook), | |
| ) | |
| results = pipe.run([ctx]) | |
| pipe.wait_for_background() # wait for async learning steps | |
| ``` | |
| ## Branching (Parallel Steps) | |
| The pipeline engine supports parallel branches for steps that can run | |
| concurrently: | |
| ```python | |
| from ace import Pipeline, Branch, MergeStrategy | |
| pipe = Pipeline([ | |
| AgentStep(agent, skillbook), | |
| Branch( | |
| [EvaluateStep(env_a), EvaluateStep(env_b)], | |
| merge=MergeStrategy.LAST, | |
| ), | |
| *learning_tail(reflector, skill_manager, skillbook), | |
| ]) | |
| ``` | |
| See the [Pipeline Engine docs](../pipeline/branching.md) for full branching | |
| and merge strategy details. | |
| ## Using RRStep (Recursive Reflector) | |
| `RRStep` satisfies both `StepProtocol` and `ReflectorLike`, so it can be used | |
| in two ways: | |
| ### As a drop-in reflector replacement | |
| Pass it anywhere a `Reflector` is expected: | |
| ```python | |
| from ace import ACELiteLLM | |
| from ace.rr import RRStep, RRConfig | |
| ace = ACELiteLLM.from_model("gpt-4o-mini", reflector=RRStep("gpt-4o-mini", config=RRConfig(max_requests=10))) | |
| ``` | |
| ### As a pipeline step | |
| Place it directly in a pipeline (it provides `reflections`): | |
| ```python | |
| from ace import Pipeline, learning_tail, SkillManager, Skillbook | |
| from ace.rr import RRStep, RRConfig | |
| skillbook = Skillbook() | |
| rr = RRStep("gpt-4o-mini", config=RRConfig(max_requests=15)) | |
| pipe = Pipeline([ | |
| MyExecuteStep(), | |
| MyToTrace(), | |
| rr, # replaces ReflectStep — provides "reflections" | |
| *learning_tail(None, SkillManager("gpt-4o-mini"), skillbook)[1:], # skip ReflectStep | |
| ]) | |
| ``` | |
| ### With recursion enabled | |
| Allow the RR to decompose large batch inputs via recursive child sessions: | |
| ```python | |
| from ace.rr import RRStep, RRConfig | |
| rr = RRStep( | |
| "gpt-4o", | |
| config=RRConfig(max_requests=40, max_depth=1), # depth=1 allows one level of recursion | |
| ) | |
| ``` | |
| ## Available Steps | |
| All steps are importable from `ace`: | |
| | Step | Purpose | | |
| |------|---------| | |
| | `AgentStep` | Execute Agent role | | |
| | `EvaluateStep` | Run TaskEnvironment evaluation | | |
| | `ReflectStep` | Run Reflector role (async boundary) | | |
| | `UpdateStep` | Run the agentic SkillManager; its tools mutate the skillbook directly | | |
| | `DeduplicateStep` | Merge near-duplicate skills | | |
| | `CheckpointStep` | Save skillbook to disk | | |
| | `LoadTracesStep` | Load JSONL trace files | | |
| | `ExportSkillbookMarkdownStep` | Export skillbook as markdown | | |
| | `ObservabilityStep` | Generic observability hook | | |
| | `PersistStep` | Persist step output | | |
| | `OpikStep` | Log traces to Opik | | |
| | `RRStep` | Recursive Reflector | | |
| Integration steps (in `ace.integrations`): | |
| | Step | Integration | | |
| |------|-------------| | |
| | `BrowserExecuteStep` / `BrowserToTrace` | browser-use | | |
| | `LangChainExecuteStep` / `LangChainToTrace` | LangChain | | |
| | `ClaudeCodeExecuteStep` / `ClaudeCodeToTrace` | Claude Code | | |
| | `ClaudeSDKExecuteStep` / `ClaudeSDKToTrace` | Anthropic Python SDK | | |
| | `OpenClawToTraceStep` | OpenClaw | | |