Skip to content

Commit 5fcbba0

Browse files
docs: iterate README per human review
Six changes after the first pass: - Title capitalized: "openarmature" → "OpenArmature". - PyPI badge explicit blue color. - Python versions badge swapped from pypi/pyversions (which read empty because pyproject.toml has no Python classifiers, so it rendered "missing") to python/required-version-toml, which reads requires-python directly. Renders "python: >=3.12". A separate followup will add the classifiers list to pyproject.toml so the PyPI listing itself shows Python compat. - Hero tagline pulled into a centered h3 above the paragraph so it reads as the headline of the page. - "Why" bullets reformatted: bold header on its own line via <br>, body underneath, instead of inline header-then-body. - Hello World preamble rewritten with more energy: leads with "Forty lines that show the engine in action" and ends with "Copy it, run it, watch the events fire." - All em dashes scrubbed from the copy (eleven total) and replaced with commas, colons, or sentence splits. Next steps switched to "Label: body. [link]" format with colon + period.
1 parent 233bb59 commit 5fcbba0

1 file changed

Lines changed: 48 additions & 101 deletions

File tree

README.md

Lines changed: 48 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
# openarmature
1+
# OpenArmature
22

33
[![CI](https://github.com/LunarCommand/openarmature-python/actions/workflows/ci.yml/badge.svg)](https://github.com/LunarCommand/openarmature-python/actions/workflows/ci.yml)
4-
[![PyPI](https://img.shields.io/pypi/v/openarmature.svg)](https://pypi.org/project/openarmature/)
4+
[![PyPI](https://img.shields.io/pypi/v/openarmature.svg?color=blue)](https://pypi.org/project/openarmature/)
55
[![spec](https://img.shields.io/badge/dynamic/toml?url=https://raw.githubusercontent.com/LunarCommand/openarmature-python/main/pyproject.toml&query=%24.tool.openarmature.spec_version&label=spec&color=9D4EDD)](https://github.com/LunarCommand/openarmature-spec)
6-
[![Python versions](https://img.shields.io/pypi/pyversions/openarmature.svg)](https://pypi.org/project/openarmature/)
6+
[![python](https://img.shields.io/python/required-version-toml?tomlFilePath=https://raw.githubusercontent.com/LunarCommand/openarmature-python/main/pyproject.toml&label=python&color=blue)](https://pypi.org/project/openarmature/)
77
[![License](https://img.shields.io/pypi/l/openarmature.svg)](https://github.com/LunarCommand/openarmature-python/blob/main/LICENSE)
88

99
**Documentation:** [openarmature.ai](https://openarmature.ai)
1010

11-
OpenArmature is a workflow framework for LLM pipelines and tool-calling
12-
agents — typed state, compile-time topology checks, and observability
13-
and crash-safe checkpoints baked into the engine. The graph layer
14-
itself has no concept of LLMs or tools, so the same primitives drive
15-
deterministic ETL pipelines and tool-calling agents alike.
11+
<div align="center">
12+
<h3>OpenArmature is a workflow framework for LLM pipelines and tool-calling agents.</h3>
13+
</div>
1614

17-
This Python package is the reference implementation; the behavioral
18-
contract is specified in [openarmature-spec](https://github.com/LunarCommand/openarmature-spec)
19-
and verified by conformance fixtures.
15+
Typed state, compile-time topology checks, and observability and crash-safe checkpoints are baked into the engine. The graph layer itself has no concept of LLMs or tools, so the same primitives drive deterministic ETL pipelines and tool-calling agents alike.
16+
17+
This Python package is the reference implementation. The behavioral contract is specified in [openarmature-spec](https://github.com/LunarCommand/openarmature-spec) and verified by conformance fixtures.
2018

2119
## Install
2220

@@ -30,70 +28,36 @@ pip install 'openarmature[otel]'
3028

3129
## Why OpenArmature
3230

33-
**State you can't accidentally mutate.**
34-
State schemas are frozen Pydantic models. Nodes return partial
35-
updates; the engine merges. The snapshot a node holds can't change
36-
mid-execution, and assignment into state raises rather than silently
37-
writing.
38-
39-
**Schema validation at every merge.**
40-
Fields outside the declared schema fail at the merge boundary instead
41-
of silently dropping. A node returning `{"plann": "..."}` (typo)
42-
raises `StateValidationError` immediately, not three nodes downstream
43-
when the field is read and doesn't exist.
44-
45-
**Merge policy on the schema, not the call site.**
46-
Each state field declares its reducer (`last_write_wins`, `append`,
47-
`merge`, or a user-defined callable) as part of the schema. Two nodes
48-
writing the same field compose via the field's policy — once,
49-
declaratively, instead of duplicated across call sites.
50-
51-
**Subgraphs compose with explicit data seams.**
52-
Subgraphs run against their own state schema with `inputs` (additive
53-
— opt in to share parent fields) and `outputs` (replacement — name
54-
exactly what comes back) mappings. Parent fields don't leak in by
55-
accident; subgraph fields don't slip out unless declared.
56-
57-
**Bad graphs don't compile.**
58-
Dangling edges, unreachable nodes, conflicting reducers, no declared
59-
entry, mappings to undeclared fields, multiple outgoing edges from
60-
one node — six categories of structural error all fail at
61-
`.compile()`, not at runtime mid-execution. The graph either
62-
constructs cleanly or it doesn't reach `invoke()`.
63-
64-
**The graph engine has no concept of LLMs or tools.**
65-
Validation, retry, recovery, structured output — those are
66-
node-internal or middleware concerns. The same engine runs
67-
deterministic ETL pipelines and tool-calling agents; the topology
68-
layer doesn't pick a side.
69-
70-
**Determinism is a contract.**
71-
Same input, same node implementations, same edge functions → same
72-
final state and same observed node-execution order. The spec mandates
73-
it; conformance fixtures verify it across every implementation.
74-
Replay an audit run and get byte-identical state.
75-
76-
**Checkpoint saves are synchronous-by-contract.**
77-
The engine awaits each save before advancing — a crash immediately
78-
after a `completed` event cannot have lost the corresponding write.
79-
Resume mints a fresh `invocation_id` (audit trail) while preserving
80-
`correlation_id` (cross-system join key), so a recovered run is
81-
traceable as a new attempt without losing the thread to the original
82-
request.
83-
84-
**Observability that doesn't double-export.**
85-
The OpenTelemetry mapping mandates a private `TracerProvider`
86-
preventing the trap where global-provider auto-instrumentation
87-
libraries (OpenInference, Langfuse v3, etc.) emit duplicate spans
88-
alongside the framework's. Your spans flow exactly where you point
89-
them; no surprise fan-out to vendor backends you didn't configure.
31+
**State you can't accidentally mutate.**<br>
32+
State schemas are frozen Pydantic models. Nodes return partial updates; the engine merges. The snapshot a node holds can't change mid-execution, and assignment into state raises rather than silently writing.
33+
34+
**Schema validation at every merge.**<br>
35+
Fields outside the declared schema fail at the merge boundary instead of silently dropping. A node returning `{"plann": "..."}` (typo) raises `StateValidationError` immediately, not three nodes downstream when the field is read and doesn't exist.
36+
37+
**Merge policy on the schema, not the call site.**<br>
38+
Each state field declares its reducer (`last_write_wins`, `append`, `merge`, or a user-defined callable) as part of the schema. Two nodes writing the same field compose via the field's policy: once, declaratively, instead of duplicated across call sites.
39+
40+
**Subgraphs compose with explicit data seams.**<br>
41+
Subgraphs run against their own state schema with `inputs` (additive, opt in to share parent fields) and `outputs` (replacement, name exactly what comes back) mappings. Parent fields don't leak in by accident; subgraph fields don't slip out unless declared.
42+
43+
**Bad graphs don't compile.**<br>
44+
Dangling edges, unreachable nodes, conflicting reducers, no declared entry, mappings to undeclared fields, multiple outgoing edges from one node. Six categories of structural error all fail at `.compile()`, not at runtime mid-execution. The graph either constructs cleanly or it doesn't reach `invoke()`.
45+
46+
**The graph engine has no concept of LLMs or tools.**<br>
47+
Validation, retry, recovery, structured output: those are node-internal or middleware concerns. The same engine runs deterministic ETL pipelines and tool-calling agents; the topology layer doesn't pick a side.
48+
49+
**Determinism is a contract.**<br>
50+
Same input, same node implementations, same edge functions, same final state, and same observed node-execution order. The spec mandates it; conformance fixtures verify it across every implementation. Replay an audit run and get byte-identical state.
51+
52+
**Checkpoint saves are synchronous-by-contract.**<br>
53+
The engine awaits each save before advancing. A crash immediately after a `completed` event cannot have lost the corresponding write. Resume mints a fresh `invocation_id` (audit trail) while preserving `correlation_id` (cross-system join key), so a recovered run is traceable as a new attempt without losing the thread to the original request.
54+
55+
**Observability that doesn't double-export.**<br>
56+
The OpenTelemetry mapping mandates a private `TracerProvider`. That prevents the trap where global-provider auto-instrumentation libraries (OpenInference, Langfuse v3, etc.) emit duplicate spans alongside the framework's. Your spans flow exactly where you point them; no surprise fan-out to vendor backends you didn't configure.
9057

9158
## Hello World
9259

93-
A three-node classification pipeline. Three different reducer
94-
policies on one state class, conditional routing as a pure function
95-
of state, and an observer that sees every node boundary — without
96-
any LLM setup. Requires Python ≥ 3.12.
60+
Forty lines that show the engine in action. Three reducer policies declared on one state class. Routing as a pure function of state, not a hidden state machine. An observer attached at compile time that sees every node boundary the engine emits. No LLM, no API key, no boilerplate. Copy it, run it, watch the events fire. Requires Python 3.12 or later.
9761

9862
```python
9963
import asyncio
@@ -170,35 +134,18 @@ final = asyncio.run(graph.invoke(PipelineState(query="what is RAG?")))
170134
# research: sources=['wikipedia', 'arxiv']
171135
```
172136

173-
A few things to notice in ~40 lines:
174-
175-
- **Three reducer policies on one state schema.** `query` and
176-
`classification` get the default `last_write_wins`. `sources` is
177-
`Annotated[list[str], append]` — successive writes concatenate.
178-
`metadata` is `Annotated[dict[str, str], merge]` — successive
179-
writes shallow-merge. The merge policy lives on the schema, once.
180-
- **Conditional routing as a state function.** `route` reads
181-
`state.classification` and returns a node name. The graph engine
182-
doesn't care that this happens to be deterministic; it would
183-
accept an LLM-driven router with the same shape.
184-
- **Observer sees both phases.** `trace` filters to `completed` events
185-
for brevity; the engine also delivers `started` events.
186-
- **The graph either compiles or it doesn't.** Remove `.set_entry()`
187-
and `.compile()` raises `NoDeclaredEntry` before `invoke()` runs.
137+
A few things to notice in those forty lines:
138+
139+
- **Three reducer policies on one state schema.** `query` and `classification` get the default `last_write_wins`. `sources` is `Annotated[list[str], append]`, so successive writes concatenate. `metadata` is `Annotated[dict[str, str], merge]`, so successive writes shallow-merge. The merge policy lives on the schema, once.
140+
- **Conditional routing as a state function.** `route` reads `state.classification` and returns a node name. The graph engine doesn't care that this happens to be deterministic; it would accept an LLM-driven router with the same shape.
141+
- **Observer sees both phases.** `trace` filters to `completed` events for brevity; the engine also delivers `started` events.
142+
- **The graph either compiles or it doesn't.** Remove `.set_entry()` and `.compile()` raises `NoDeclaredEntry` before `invoke()` runs.
188143

189144
## Next steps
190145

191-
- **Quickstart** — build your first graph end-to-end:
192-
[openarmature.ai/getting-started](https://openarmature.ai/getting-started/)
193-
- **Concepts** — typed state, reducers, composition, fan-out,
194-
checkpointing, observability:
195-
[openarmature.ai/concepts](https://openarmature.ai/concepts/)
196-
- **Model Providers** — implement the Provider Protocol for a
197-
custom LLM backend:
198-
[openarmature.ai/model-providers/authoring](https://openarmature.ai/model-providers/authoring/)
199-
- **API reference** — auto-generated from docstrings:
200-
[openarmature.ai/reference](https://openarmature.ai/reference/)
201-
- **Examples** — runnable demos:
202-
[openarmature-python/examples/](https://github.com/LunarCommand/openarmature-python/tree/main/examples)
203-
- **Spec** — behavioral contract this implementation conforms to:
204-
[LunarCommand/openarmature-spec](https://github.com/LunarCommand/openarmature-spec)
146+
- **Quickstart**: build your first graph end-to-end. [openarmature.ai/getting-started](https://openarmature.ai/getting-started/)
147+
- **Concepts**: typed state, reducers, composition, fan-out, checkpointing, observability. [openarmature.ai/concepts](https://openarmature.ai/concepts/)
148+
- **Model Providers**: implement the Provider Protocol for a custom LLM backend. [openarmature.ai/model-providers/authoring](https://openarmature.ai/model-providers/authoring/)
149+
- **API reference**: auto-generated from docstrings. [openarmature.ai/reference](https://openarmature.ai/reference/)
150+
- **Examples**: runnable demos. [openarmature-python/examples/](https://github.com/LunarCommand/openarmature-python/tree/main/examples)
151+
- **Spec**: behavioral contract this implementation conforms to. [LunarCommand/openarmature-spec](https://github.com/LunarCommand/openarmature-spec)

0 commit comments

Comments
 (0)