Skip to main content
Observation retains its dict compatibility but stores each field once. obs.task = value and obs["task"] = value update the same authority; mapping step and step_id name the step_id attribute. Contradictory aliases, invalid field types and deletion of schema fields reject atomically. update and |= validate the whole mutation first; extension keys support pop, del, setdefault and popitem. clear is rejected because it would remove required identity. State/metadata nested edits are shared between attribute and mapping access. Constructor inputs are copied; to_dict and to_legacy_dict are independent snapshots. Attribute action results are canonical ToolResult objects and to_dict serializes their canonical shape. Mapping action results remain a fresh legacy projection for existing reducers; assign obs["action_results"] to validate and replace them. Unbound dict mutators are outside this contract. Legacy host Read uses a zero-based offset and a positive line limit: offset 2, limit 2 selects only lines 3/4. Empty files and EOF return empty output; negative or non-integer windows fail. Selection metadata reports truncation and more lines. Edit requires a unique match unless replace_all=True; no match, ambiguous match, permission denial and explicit expected_sha256 conflicts leave the file unchanged. These two aliases now return canonical ToolResult (read numbered text in .output, status/error in their named fields), rather than successful strings containing errors. Direct callers expecting strings should use .output after checking .status. The existing validation/permission pipeline remains authoritative. Host compatibility remains host execution; Env read_file/edit_file keep their Env-only operations and one-based line_offset interface. No fallback to host or new isolation guarantee is implied.

Goal and prerequisites

Register a decorated function with ToolRegistry before an Agent can call it. The name and typed parameters form the callable interface; read_only and concurrency_safe describe this tool’s behavior. These are commitments by the tool author, not an automatic permission grant. First run the sequential and parallel notes example. Then run parallel.py: the first declared tool waits for an explicit in-memory event from the second. Two workers therefore finish in reverse order, while the reducer still receives results in declaration order. The event and list are visible, process-local teaching instrumentation. The five-second wait is a fixture deadline, not a performance test. A single worker cannot satisfy it. Tool errors, timeout, partial outputs and unknown external effects must be inspected through ToolResult rather than interpreted as successful text. Basic Python is required. The package declares Python ≥3.10; local qualification uses Python 3.12.7. Each chapter runs independently; reuse the environment and matching files when continuing your project. Commands below use a macOS/Linux shell.

Prepare the project

Save the complete files below in this directory. No repository clone, editable install, or copied tests are needed.

Notes and tools

Run and verify

Expected output fragments (generated IDs vary). Each run verifies tool results or persistence assertions and exits 0 on success.

Behavior and support boundaries

Timeout is not hard cancellation. worker_still_running and outcome_unknown require reconciliation; do not automatically replay an operation with unknown effects.

Exercise and answer

Set max_concurrency=1 in the reverse-order fixture and explain the failure. Restore 2 to pass; do not increase the timeout to hide the dependency.

Common errors and cleanup

ModuleNotFoundError: activate the environment with the specified installation and save every file on this page. Existing run root: choose a new --root instead of overwriting evidence. Assertion failure: inspect the first failed tool or typed error, not only the final text. After all processes and the board stop, remove only this lesson’s newly created run directories if no longer needed; retain SQLite, journals and reports you want to debug.

Complete files: save in the project root

notes.py
agent.yaml
custom_agent.py
parallel.py

Next step and API

API Reference · Configuration · Learning path · Next Source file (optional; all required code is already on this page).