Skip to content

2.2 Constructor Reference ​

What you'll learn

  • The smallest call that works: four parameters, or two with a pre-built llm_client=
  • All 33 parameters on one map, grouped by what they configure
  • The ones you'll typically pass in production (transport, permissions, MCP, …)
  • The advanced injection surface for hosts that need full control
  • The two factory paths (Agentao(...) direct vs. build_from_environment(...)) and when to pick each

Two construction paths ​

PathWhen to use
Agentao(...) directlyYou want explicit control — no env / disk side effects at construction time. The body of this page covers this.
agentao.embedding.build_from_environment(...)Your host follows the same project-directory conventions as the CLI. Reads .env, permissions.json, mcp.json, memory roots and builds Agentao for you. See § Factory.
SituationUse
A notebook or script that should behave like the CLI and read your local configbuild_from_environment
A multi-tenant web app with one agent per request or tenantAgentao(...)
A container or sandbox that ships no .envAgentao(...)
An IDE plugin whose host already owns the stateAgentao(...)
A test harness that needs reproducible runsAgentao(...)

Both produce an Agentao instance. They combine in one way: every Agentao keyword is also a factory override, and an override wins over what the factory discovers. So build_from_environment(working_directory=wd, permission_engine=my_engine) keeps the rest of the discovery and replaces only the engine. What does not work is a half-way mix: a direct Agentao(...) that tries to reproduce the factory's discovery by hand drifts from it on the next release.


All 33 parameters at a glance ​

Agentao.__init__ takes 33 parameters. Only working_directory has no default. The first five (api_key, base_url, model, temperature, max_tokens) may be passed by position; every other one is keyword-only. Below they are grouped by what they configure, so you can skip the groups you don't need.

GroupParametersCovered in
Session root (required)working_directoryTier 1
Model, raw settingsapi_key, base_url, model (all three required without llm_client); temperature, max_tokens, api_format, extra_body, prompt_cache, prompt_cache_ttlTiers 1–3
Model, pre-builtllm_client (replaces the whole raw-settings row; passing both raises ValueError)Tier 2
Permissions and interactiontransport, permission_mode or permission_engine, plan_sessionTiers 2–3
Context and compactionmax_context_tokens, compaction_controllerTiers 2–3
Toolsextra_tools, disable_tools or enabled_toolsTier 2
MCPmcp_manager, or extra_mcp_servers and mcp_registry (these two combine)Tiers 2–3
Promptproject_instructionsTier 2
Bring your own objectlogger, memory_manager, skill_manager, filesystem, shellTier 3
Opt-in subsystems (off by default)bg_store, sandbox_policy, replay_config, enable_builtin_agentsTier 3

Parameters joined by or, and the two Model rows, are mutually exclusive: passing both sides raises ValueError; see Mutual-exclusion rules.


Tier 1 · The smallest call that works ​

Four parameters. Everything else has a default.

python
from pathlib import Path
from agentao import Agentao

agent = Agentao(
    api_key="sk-...",
    base_url="https://api.openai.com/v1",
    model="gpt-5.4",
    working_directory=Path("/tmp/my-session"),
)
ParamTypeWhy
api_keystrLLM credential
base_urlstrThe endpoint: OpenAI, DeepSeek, a Gemini gateway, vLLM, … There is no built-in default
modelstrModel id
working_directoryPathThe session's project root. Frozen at construction — file / shell / memory all resolve against it

Without llm_client=, leaving out any of api_key, base_url or model raises ValueError. A direct Agentao(...) reads no environment variables: OPENAI_API_KEY and the rest are read only by build_from_environment() (see Factory path). The other smallest call is Agentao(llm_client=my_client, working_directory=...), with the credentials on the client.

Don't skip working_directory

In a Web server / multi-tenant process, Path.cwd() is process-global — concurrent sessions would cross-contaminate. Since 0.3.0 the keyword is required; calls without it raise TypeError from Python signature dispatch.


Tier 2 · Common production params ​

These cover most production embeddings:

python
from agentao import Agentao
from agentao.transport import SdkTransport
from agentao.permissions import PermissionEngine, PermissionMode

engine = PermissionEngine(project_root=workdir)
engine.set_mode(PermissionMode.WORKSPACE_WRITE)

transport = SdkTransport(on_event=..., confirm_tool=..., ask_user=...)

agent = Agentao(
    api_key="sk-...",
    base_url="https://api.openai.com/v1",
    model="gpt-5.4",
    temperature=0.1,
    working_directory=workdir,
    transport=transport,
    permission_engine=engine,
    max_context_tokens=128_000,
    extra_mcp_servers={...},
)

PermissionEngine(project_root=workdir), with no rules= and no user_root=, loads no rule file: a <workdir>/.agentao/permissions.json is never loaded, only warned about. So the two engine lines above give the same rules as passing permission_mode="workspace-write" instead of permission_engine=engine. Build an engine yourself when you have rules for it: rules=[...], or user_root= to load <user_root>/permissions.json.

ParamTypeDefaultWhat it does
max_tokensint | NoneNone — the client's 65,536Per-call output cap
temperaturefloat | NoneNoneSampling temperature. None sends no temperature, so the provider's default applies (0.5.7; was 0.2)
extra_bodyDict[str,Any]NoneForwarded verbatim to the LLM .create() as the SDK's extra_body — the escape hatch for params the closed request build does not expose (reasoning_effort / top_p / seed / response_format / provider-specific fields). Keyword-only. Sub-agents inherit it; logged with credential keys redacted. Mutually exclusive with llm_client. See below
api_formatstr"openai-completions"The wire protocol spoken to base_url (0.5.0): "openai-completions", "anthropic-messages" (Anthropic's Messages API over the official SDK) or "openai-responses" (OpenAI's Responses API, 0.5.3). Keyword-only, configured and never inferred from the URL or the model name, changed afterwards only by set_provider(..., api_format=), inherited by sub-agents, and mutually exclusive with llm_client=. An unknown value raises ValueError. On anthropic-messages, base_url is the API root, temperature is not sent, and extended thinking goes through extra_body. Env equivalent: {PROVIDER}_API_FORMAT — see Appendix B
transportCoreTransportNullTransport()UI bridge: events + confirm + ask_user + max-iter fallback. See Part 4
permission_enginePermissionEngineNone — no engine, no rule evaluated (build_from_environment builds one rooted at working_directory)Rule-based gating; modes and set_permission_mode need one. See 5.4
permission_modestrNone — builds nothing"read-only" / "workspace-write" / "full-access" ("plan" is refused): builds PermissionEngine(project_root=working_directory, rules=[]) in that mode, reading no rule file, and starts in it without emitting an event. Mutually exclusive with permission_engine. See 5.4
max_context_tokensint200_000Triggers conversation compression beyond this
extra_mcp_serversDict[str,Dict]NonePer-session MCP servers without touching .agentao/mcp.json. Same-name keys override. Useful for per-tenant tokens
extra_toolsSequence[Tool]NoneInject / replace tools (instances; register last, same name overrides a built-in). See 5.1
disable_toolsIterable[str]NoneSkip these built-ins by name (unknown name → ValueError). Mutually exclusive with enabled_tools
enabled_toolsIterable[str]NoneAllowlist of agentao-owned tools to keep; None=off, any iterable incl. set()=on. Mutually exclusive with disable_tools
llm_clientLLMClient(constructed from credentials)Inject a pre-built client to fully control logger / log file. Mutually exclusive with every raw model setting: api_key / base_url / model / temperature / max_tokens / extra_body / prompt_cache / prompt_cache_ttl / api_format (a host with its own client passes them to that client)
project_instructionsstr(read from <wd>/AGENTAO.md)Pass AGENTAO.md content directly — skips the disk read

Async hosts use arun()

agent.chat(...) is synchronous. Async hosts call await agent.arun(user_message), which runs the turn on Agentao's own worker pool, not on the event loop's default executor. Cancellation, replay, and max_iterations semantics are identical across both surfaces. Why the pool is separate, and what that means for sizing: 2.3 Async hosts.


Tier 3 · Advanced injections ​

Most embeddings never need these. Expand only what applies to you.

Capability protocols — filesystem, shell, mcp_registry, memory_manager

Four host→Agentao injection slots cover every IO surface tools touch. Replace any one to route IO through Docker exec, virtual filesystems, audit proxies, plugin-driven MCP discovery, or a remote memory backend. The defaults match Agentao's pre-0.2.16 byte-for-byte behavior.

SlotProtocolDefaultBound at
filesystemFileSystemLocalFileSystemTool registration → tool.filesystem on every file/search tool
shellShellExecutorLocalShellExecutorTool registration → tool.shell on the shell tool
mcp_registryMCPRegistryNone: reads <wd>/.agentao/mcp.json and ~/.agentao/mcp.json, the same files as the factory's FileBackedMCPRegistryAgentao.__init__ reads list_servers() once during MCP init
memory_manager (wraps MemoryStore)MemoryStoreSQLiteMemoryStore under <wd>/.agentao/memory.dbHeld on agent._memory_manager; the save_memory tool delegates here
python
from agentao import Agentao
from agentao.host.protocols import FileSystem, ShellExecutor, MCPRegistry, MemoryStore
from agentao.memory import MemoryManager

agent = Agentao(
    working_directory=workdir,
    filesystem=MyDockerExecFileSystem(...),         # FileSystem
    shell=MyAuditingShellExecutor(...),             # ShellExecutor
    mcp_registry=MyPluginMCPRegistry(...),          # MCPRegistry
    memory_manager=MemoryManager(                   # MemoryStore wrapped in a manager
        project_store=MyRedisMemoryStore(...),
    ),
)

Always import the protocols from agentao.host.protocols (public surface). Default impls live in agentao.capabilities and agentao.memory. None for any slot means fall back to the local default, not disable; to disable a capability, inject an implementation that raises on call. For MCP, mcp_registry=InMemoryMCPRegistry() (from agentao.mcp) loads no servers at all.

Matching the method signatures is not the whole contract. FileSystem.write_text must replace an existing file atomically, so a process killed mid-write cannot leave the user's file truncated. A wrapper that delegates to LocalFileSystem gets this for free. A from-scratch implementation must stage the content and swap it in with one operation, as LocalFileSystem does with a sibling temp file and os.replace (host-api.md).

Runnable end-to-end example — examples/protocol-injection/

Replaces all four slots with small adapters (in-memory FS, audit-logging shell, dict-backed memory store, programmatic MCP registry) and asserts each one is consulted via 6 smoke tests. No OPENAI_API_KEY required. Run with uv sync --extra dev && PYTHONPATH=. uv run pytest tests/.

Multi-tenant FS isolation: 6.4. :::

Memory / Skills / MCP managers — memory_manager, skill_manager, mcp_manager, mcp_registry

Inject pre-built managers when you don't want Agentao to construct them from defaults — typically because the manager is shared across many sessions, or you want programmatic config rather than disk lookups.

ParamReplaces
memory_managerThe default MemoryManager opening <wd>/.agentao/memory.db. Yours to close: agent.close() leaves it open
skill_managerThe bundled-skill auto-discovery scan
mcp_manager.agentao/mcp.json discovery + lifecycle. Yours to disconnect: agent.close() leaves it connected. Mutually exclusive with extra_mcp_servers= and mcp_registry=
mcp_registryload_mcp_config(...) source. Use InMemoryMCPRegistry for programmatic registration. Mutually exclusive with mcp_manager=
Opt-in subsystems — bg_store, sandbox_policy, replay_config, enable_builtin_agents

Default is None (or False) = fully disabled. Pay zero cost if you don't use them.

ParamWhen None
bg_storeBackground-task tools (check_background_agent, cancel_background_agent) are not registered; sub-agent tool schemas drop the run_in_background field; /agent bg|dashboard|cancel|delete|logs|result CLI subcommands no-op with a warning
sandbox_policyShell runs without macOS sandbox-exec wrapper. The policy wraps only run_shell_command, only on macOS, and only when ~/.agentao/sandbox.json sets enabled: true (a <wd>/.agentao/sandbox.json is ignored); it does not restrict the file tools (sandbox/policy.py)
enable_builtin_agents (False)The built-in sub-agents (codebase-investigator, generalist) are not registered as delegation tools. The factory reads agents.enable_builtin from settings.json
replay_configThe replay block of <wd>/.agentao/settings.json is not read at construction; no ReplayManager is attached until start_replay() / reload_replay_config() creates one. start_replay() alone creates it with replay off (it returns None); reload_replay_config() reads that block but starts nothing: a start_replay() after it records only if the block has enabled true

With a value set:

  • bg_store enables background sub-agents. BackgroundTaskStore(persistence_dir=wd) keeps its records in <wd>/.agentao/background_tasks.json. After a restart, a task that was still running is marked failed: the record survives, the work does not. At most max_concurrent run at once (default 6, None for no cap), and a launch past that is refused, not queued. Leave it None in a one-shot process, as agentao run does, because the workers are daemon threads that die at exit. close() cancels the tasks this agent launched. Details: Appendix A.1.
  • replay_config records to <wd>/.agentao/replays/*.jsonl only after the host calls agent.start_replay(). The CLI and ACP do this per session. Neither the factory nor Agentao(...) does. Replay records a session; it does not re-run one (6.6).
Logger injection — logger

Pass logger=app.logger to skip Agentao's package-root level / handler mutation in LLMClient.__init__. Your logging stack stays untouched, and the default rolling <wd>/agentao.log file is not created (the file-handler branch is skipped along with the rest of the mutation).

To control the file-handler axis independently, build LLMClient yourself and pass log_file=None (skip file) or log_file="custom.log" (redirect). Note: passing only log_file=None without logger= still elevates getLogger("agentao") to DEBUG. See 6.6 Observability → Take over Agentao's logger for the full matrix and a fully-silent recipe.

LLM request passthrough — extra_body

Agentao builds the OpenAI-compatible request from a closed set of fields (model / messages / temperature / tools / max_tokens). When you need a param that set does not expose — reasoning_effort, top_p, seed, response_format, or any provider-specific body field (top_k, repetition_penalty, vendor extensions) — pass extra_body. It is forwarded verbatim to the SDK's .create(extra_body=...), which merges it into the JSON request body, bypassing the typed signature.

python
agent = Agentao(
    api_key="sk-...",
    base_url="https://api.openai.com/v1",
    model="gpt-5.4",
    working_directory=workdir,
    extra_body={"reasoning_effort": "high", "seed": 7},
)
  • Keyword-only. Unlike api_key / temperature, extra_body is declared after *, so it never shifts the legacy positional arguments.
  • The host owns the values. Agentao does not validate them — the SDK / provider does. You are configuring your own endpoint, so this is not third-party proxying.
  • Sub-agents inherit it, the same way they inherit temperature / max_tokens.
  • No auto-recovery on model switch. Unlike temperature (which auto-drops when a model rejects it), a stale extra_body key — e.g. reasoning_effort after switching to a non-reasoning model — makes every later call 400 until you clear it. Dropping model-specific keys on switch is the host's responsibility.
  • Logged with credentials redacted. If extra_body nests credential-like keys (api_key, authorization, x-api-key, …), their values are masked (***) in agentao.log.
  • CLI / factory path: set the LLM_EXTRA_BODY env var to a JSON object (e.g. LLM_EXTRA_BODY='{"reasoning_effort":"high"}'). Malformed or non-object values warn and are skipped; empty is treated as unset. See Appendix B.
  • Mutually exclusive with llm_client=. A host injecting its own LLMClient passes extra_body= to that client's constructor instead.

extra_headers and a settings.json file layer are intentionally deferred; see docs/design/host-llm-extra-params.md.

Rarely set — prompt_cache, prompt_cache_ttl, compaction_controller, plan_session
ParamDefaultWhat it does
prompt_cacheNone (off)"anthropic" puts explicit prompt-cache breakpoints on each request (at most 3). Off by default because endpoint support is not verified. Never inferred from the URL or model name. Env equivalent: LLM_PROMPT_CACHE, read only by the factory
prompt_cache_ttlNone (provider default, "5m")"5m" or "1h". Ignored while prompt_cache is off. Env equivalent: LLM_PROMPT_CACHE_TTL
compaction_controllerNoneA synchronous callable that allows, cancels, or supplies the summary for a compaction. Fail-open: a raise counts as allow. See host-api.md
plan_sessionNone (a fresh PlanSession)Plan-mode state. The CLI passes its own; an embedded host normally leaves it alone
The 8 legacy callbacks (removed in 0.5.0)

Pre-0.2.10 API. They warned through 0.4.x and are no longer parameters: passing one raises TypeError. Either go through Transport, or keep the callbacks and wrap them — agentao.embedding.compat.build_compat_transport(...) takes the same eight names and is not deprecated: Agentao(transport=build_compat_transport(confirmation_callback=...), ...).

Removed paramReplacement
confirmation_callbackSdkTransport(confirm_tool=...)
step_callbackon_event= + TOOL_START / TURN_START
thinking_callbackon_event= + THINKING
ask_user_callbackSdkTransport(ask_user=...)
output_callbackon_event= + TOOL_OUTPUT
tool_complete_callbackon_event= + TOOL_COMPLETE
llm_text_callbackon_event= + LLM_TEXT
on_max_iterations_callbackSdkTransport(on_max_iterations=...)

⚠️ Only the first five parameters are positional (api_key, base_url, model, temperature, max_tokens). The callbacks used to sit between max_context_tokens, permission_engine, transport and plan_session, so those four are keyword-only now — a call still passing a callback sixth is a TypeError, not a silent re-binding. Full guide: docs/migration/0.4.x-to-0.5.0.md.


Mutual-exclusion rules ​

Violating any of these raises ValueError at construction:

Cannot combineReason
llm_client= + any of api_key / base_url / model / temperature / max_tokens / extra_body / prompt_cache / prompt_cache_ttl / api_formatThe injected client is already the credential source — pass those to that client directly instead
mcp_manager= + extra_mcp_servers=Per-session merge needs a manager Agentao constructs
mcp_manager= + mcp_registry=Registry is the config source; manager is the construction outcome
permission_engine= + permission_mode=One is an engine, the other builds one — set the mode on your engine, or call set_permission_mode() after construction

Factory path: build_from_environment() ​

When your host follows CLI conventions (project-rooted .env, .agentao/ configs, memory dirs):

python
from pathlib import Path
from agentao.embedding import build_from_environment

agent = build_from_environment(
    working_directory=Path("/data/tenant-acme"),
    transport=my_transport,
    max_context_tokens=128_000,
)

What it does, in order (embedding/factory.py):

#StepIf it goes wrong
1Resolves working_directory to an absolute path and freezes it. Omitted, it is Path.cwd(), which an embedded host should not rely on—
2Reads <wd>/.agentao/settings.json for factory toggles such as agents.enable_builtinA malformed file is logged and ignored
3Loads <wd>/.env into os.environ. Without one, it loads the nearest .env from the process cwd upward. A non-empty variable already in the environment wins—
4Reads the LLM settings from LLM_PROVIDER (default OPENAI) and {PROVIDER}_API_KEY / _BASE_URL / _MODEL / _API_FORMAT, plus LLM_TEMPERATURE, LLM_MAX_TOKENS, LLM_EXTRA_BODY, LLM_PROMPT_CACHE, LLM_PROMPT_CACHE_TTL. Skipped when you pass llm_client=A non-numeric LLM_TEMPERATURE / LLM_MAX_TOKENS raises. A bad LLM_EXTRA_BODY is warned about and skipped
5Reads ~/.agentao/permissions.json and builds PermissionEngine(project_root=wd, user_root=~/.agentao) with its rules. Its shell block picks the shell executorFails closed: a file that exists but is unreadable or invalid raises PermissionConfigError. Dropping it would turn a deny rule into an ask, or into no prompt at all for an mcp_* tool. <wd>/.agentao/permissions.json is never loaded, only warned about
6Wires the opt-in subsystems: BackgroundTaskStore(persistence_dir=wd), SandboxPolicy(project_root=wd), replay_config from the replay block of settings.json, and enable_builtin_agents from step 2A malformed replay block means no replay
7Uses FileBackedMCPRegistry(project_root=wd, user_root=~/.agentao) unless you pass mcp_registry= or mcp_manager=—
8Opens memory: the project store at <wd>/.agentao/memory.db and the user store at ~/.agentao/memory.dbThe project store falls back to an in-memory database. If the user store cannot be opened, the session has none: existing user memories are not read, and a new user-scope write is saved to the project store, with a log entry (5.5)
9Calls Agentao(...) with all of the above as explicit keywords. Your overrides win over everything discoveredIf construction raises, the factory closes the memory stores it opened

An explicit None for bg_store, sandbox_policy or replay_config disables that subsystem. permission_mode= is applied to the engine from step 5, so the user's rules stay. The CLI and ACP session/new build their agents through this factory.

What reads the environment. The factory does the CLI-style discovery at construction in one place: environment variables, .env, permissions.json, settings.json and sandbox.json. A direct Agentao(...) skips that discovery, but it still touches some files under its defaults:

DefaultReadsTo avoid it, pass
project_instructions=None<wd>/AGENTAO.mdproject_instructions=
skill_manager=None~/.agentao/skills/, <wd>/.agentao/skills/, <wd>/skills/, <wd>/.agentao/skills_config.json. It also copies the bundled skills into ~/.agentao/skills/ on first runskill_manager=
memory_manager=NoneOpens <wd>/.agentao/memory.db (project scope only)memory_manager=
mcp_registry=None<wd>/.agentao/mcp.json and ~/.agentao/mcp.jsonmcp_registry= or mcp_manager=
logger=None (with llm_client=None)Creates and writes <wd>/agentao.loglogger= or llm_client= (6.6)

With all five passed, constructing the agent creates no file. That covers construction only: a turn can still write, for example a replay, a tool output spilled to .agentao/tool-outputs/, or what the tools themselves do.


Full production template ​

python
from pathlib import Path
import os
from agentao import Agentao
from agentao.transport import SdkTransport
from agentao.permissions import PermissionEngine, PermissionMode

def make_agent_for_session(
    tenant_id: str,
    tenant_workdir: Path,
    tenant_token: str,
    on_event,
    confirm_tool,
) -> Agentao:
    engine = PermissionEngine(project_root=tenant_workdir)
    engine.set_mode(PermissionMode.WORKSPACE_WRITE)

    transport = SdkTransport(
        on_event=on_event,
        confirm_tool=confirm_tool,
        on_max_iterations=lambda n, _msgs: {"action": "stop"},
    )

    return Agentao(
        api_key=os.environ["OPENAI_API_KEY"],
        base_url=os.environ["OPENAI_BASE_URL"],
        model="gpt-5.4",
        temperature=0.1,
        transport=transport,
        working_directory=tenant_workdir,
        max_context_tokens=128_000,
        permission_engine=engine,
        extra_mcp_servers={
            "gh": {
                "command": "npx",
                "args": ["-y", "@modelcontextprotocol/server-github"],
                "env": {"GITHUB_TOKEN": tenant_token},
            },
        },
    )

Or, if your host already follows CLI conventions:

python
from agentao.embedding import build_from_environment

agent = build_from_environment(
    working_directory=tenant_workdir,
    transport=transport,
    max_context_tokens=128_000,
)

Version note

  • 0.5.0 — The 8 legacy callback kwargs (confirmation_callback, step_callback, thinking_callback, ask_user_callback, output_callback, tool_complete_callback, llm_text_callback, on_max_iterations_callback) were removed from the Agentao(...) signature, and every parameter after max_tokens became keyword-only. Use transport=SdkTransport(...), or wrap the old callbacks with agentao.embedding.compat.build_compat_transport(...).
  • 0.4.x — The 8 legacy callbacks emitted a single DeprecationWarning per construction (from 0.4.5).
  • 0.3.4 — Capability protocols (FileSystem, ShellExecutor) re-exported on agentao.host.protocols. Always import from there, not internal agentao.capabilities.*.
  • 0.3.0 — working_directory= became a required keyword (calls without it raise TypeError). mcp_registry= introduced as a stable config-source surface; default FileBackedMCPRegistry matches the pre-#17 disk read.
  • 0.2.16 — Explicit-injection surface added (memory_manager, skill_manager, mcp_manager, filesystem, shell, …); replay_config, sandbox_policy, bg_store defaulted to None.
  • 0.2.10 — Decoupled core runtime; the 8 legacy callbacks remain accepted via build_compat_transport().

Upgrading from 0.2.15: docs/migration/0.2.15-to-0.3.3.md.

TL;DR ​

  • 33 parameters, one required: working_directory (Path, frozen at construction). The map above groups the rest by purpose.
  • Smallest working call: api_key + base_url + model + working_directory, or llm_client + working_directory. A direct Agentao(...) reads no environment variables.
  • Typical production additions: transport, permission_mode (or permission_engine), max_context_tokens, extra_mcp_servers, project_instructions, temperature.
  • Everything else is opt-in or advanced — capability protocols, custom managers, prompt caching, compaction control, sandbox / replay / background / sub-agent subsystems.
  • Two factories: build_from_environment() for CLI conventions; direct Agentao(...) for explicit control. To change one part of the factory's result, pass that keyword to the factory as an override.

→ Next: 2.3 Lifecycle