Getting started¶
Five minutes from install to a running agent, and a map of where to go next.
Install¶
pip install chassis-harness # core lifecycle kernel
pip install "chassis-harness[langgraph]" # LangGraph adapter + langchain-core
pip install "chassis-harness[langsmith]" # LangSmith telemetry + evaluation
The import package is chassis; the distribution is chassis-harness. Python 3.12
or newer.
The core depends only on pydantic, packaging, and pyyaml; it imports and runs
without langgraph, langchain-core, or langsmith. This page uses the LangGraph
adapter and a scripted model, so install the langgraph extra — no credentials are
needed to follow it.
The smallest useful application¶
The complete file is examples/quickstart.py; it
asserts everything it prints.
from chassis import MODEL, Harness
from chassis.langgraph import AgentDefinition, GraphBuildInputs, LangGraphAgent
from chassis.runtime import HarnessRunContext
from chassis.testing import FakeChatModel
class ChatState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
async def assistant(state: ChatState, runtime: Runtime[HarnessRunContext]) -> dict:
model = runtime.context.require_capability(MODEL) # this run's model
return {"messages": [await model.ainvoke(state["messages"])]}
def build_agent(inputs: GraphBuildInputs) -> StateGraph:
graph = StateGraph(ChatState, context_schema=HarnessRunContext)
graph.add_node("assistant", assistant)
graph.add_edge(START, "assistant")
graph.add_edge("assistant", END)
return graph
async def main() -> None:
harness = Harness(name="quickstart")
harness.provide(MODEL, FakeChatModel(responses=["hello from Chassis"]))
harness.register_agent(
LangGraphAgent(
AgentDefinition(name="research-agent", version="1", state_schema=ChatState, build=build_agent),
checkpointer=InMemorySaver(),
)
)
async with harness:
result = await harness.agents.invoke(
"research-agent", {"messages": [HumanMessage("hi")]}, thread_id="thread-1"
)
print(result.text, result.generation_id)
Run it:
uv run python examples/quickstart.py
Expected output:
[invoke] text='hello from Chassis'
[invoke] generation=gen_0001 thread=thread-1
[invoke] snapshot=20528fce...
[status] {'desired': 1, 'mounted': 1, 'by_state': {'active': 1}} agents=1
What just happened¶
provide(MODEL, ...) a capability is registered under a key
register_agent(...) an agent runtime is registered with the harness
async with harness: RAII: start reconciles, stop drains and disposes
agents.invoke(...) the run acquires one immutable *generation* and keeps it
The run is attributed to gen_0001 and to a snapshot digest: the exact plugins,
capability versions, tool schema, and graph definition that produced the answer. If
you swap the model while a run is in flight, that run keeps the old generation and
its old objects — nothing is mutated underneath it.
Where to go next¶
| You want to | Read |
|---|---|
| build your first plugin | plugin-author-guide.md |
| wire this into a service, swap providers, set budgets | recipes.md |
| fix something that is not working | troubleshooting.md |
| understand ownership, generations, unload, shutdown | lifecycle.md |
| use LangGraph properly (tools, interrupts, streaming, caching) | langgraph.md |
| trace runs, hash snapshots, evaluate experiments | observability.md |
| know exactly what the security model does and does not promise | security.md |
| record and replay model/tool boundaries | replay.md |
| drive composition from YAML and reconcile drift | configuration.md |
| build hierarchical composition scopes and explain provider choices | scopes.md |
| see the design decisions and invariants | design.md |
| upgrade from an earlier release | migration.md |
The other examples isolate one idea each:
uv run python examples/basic_agent.py # tools, checkpointing, streaming, interrupts, tracing
uv run python examples/reactive_cascade.py # database → memory → extension, removed and restored
uv run python examples/safe_provider_replacement.py # generations across a provider swap
uv run python examples/scoped_composition.py # hierarchical composition scopes
Using it for real¶
- Credentials: read secrets through the provider (
ctx.secrets), never from the environment directly, so they stay out of traces, snapshots, and errors. - Durability: pass a persistent checkpointer (
langgraph.checkpoint.postgres) instead ofInMemorySaver(). Chassis does not duplicate graph durability. - Tracing: set
LANGSMITH_TRACING=trueand the usual LangSmith variables; the harness adds generation metadata on top of native LangGraph tracing. - Composition changes:
install/uninstall/providemark desired state dirty; the next run or explicitreconcile()publishes a new generation. - Diagnostics:
harness.diagnostics.status()andharness.diagnostics.explain("my-plugin")answer "why is this plugin not active" from authoritative state;explain_requirement,explain_scope, anddiff_generationsanswer the same for scoped composition and provider choice.