From 396caa63d68a96ebecb1a2e292e57a51f2323848 Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 14 Sep 2026 13:40:11 +0700 Subject: [PATCH] docs(thesis): resolve review findings and verify examples --- docs/thesis/assets/comparison/README.md | 20 ++ .../assets/comparison/n8n-merge-append.png | Bin 0 -> 35731 bytes .../comparison/zapier-path-condition.png | Bin 0 -> 41236 bytes docs/thesis/header-includes.tex | 15 +- docs/thesis/system-design-implementation.md | 191 ++++++++++++++---- tests/docs/test_thesis_front_matter.py | 43 ++++ tests/docs/test_thesis_langgraph_example.py | 28 +++ .../test_thesis_python_walkthrough.py | 21 +- 8 files changed, 273 insertions(+), 45 deletions(-) create mode 100644 docs/thesis/assets/comparison/README.md create mode 100644 docs/thesis/assets/comparison/n8n-merge-append.png create mode 100644 docs/thesis/assets/comparison/zapier-path-condition.png create mode 100644 tests/docs/test_thesis_langgraph_example.py diff --git a/docs/thesis/assets/comparison/README.md b/docs/thesis/assets/comparison/README.md new file mode 100644 index 00000000..7b081254 --- /dev/null +++ b/docs/thesis/assets/comparison/README.md @@ -0,0 +1,20 @@ +# Comparison screenshots + +Author-provided captures used in the thesis's related-systems discussion. +These are exact pixel crops; no interface text or results were redrawn. +The originals remain in the author's local `random shit/` folder and are +not needed to build the thesis. + +| Asset | Original | Crop (x, y, width, height), pixels | +| --- | --- | --- | +| `n8n-merge-append.png` | `a9fb75.png` | 475, 100, 1125, 630 | +| `zapier-path-condition.png` | `filter.png` | 974, 111, 657, 762 | + +Coordinates use the top-left corner of the 1644 × 985 originals. The n8n +crop retains configuration and output. The Zapier crop retains the condition +and test result while excluding the account header and unrelated graph nodes. + +The screenshots illustrate configuration and sample results. Execution +ordering and synchronization claims use the documentation cited in the +manuscript. The Edit Fields capture (`fca1d3.png`) was omitted because its +field-assignment example adds a separate topic to this comparison. diff --git a/docs/thesis/assets/comparison/n8n-merge-append.png b/docs/thesis/assets/comparison/n8n-merge-append.png new file mode 100644 index 0000000000000000000000000000000000000000..ff1f95780ade4e180de1745edddc07755b92895e GIT binary patch literal 35731 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 k%P2qB>7s7Q^WN>^G4 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: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 literal 0 HcmV?d00001 diff --git a/docs/thesis/header-includes.tex b/docs/thesis/header-includes.tex index d9610cdd..9ce676f0 100644 --- a/docs/thesis/header-includes.tex +++ b/docs/thesis/header-includes.tex @@ -6,13 +6,16 @@ \usepackage[dvipsnames]{xcolor} \usepackage{fancyhdr} \usepackage{float} +% Let screenshots share a page with their explanation before using float pages. +\floatplacement{figure}{htbp} +\renewcommand{\topfraction}{0.85} +\renewcommand{\textfraction}{0.1} +\renewcommand{\floatpagefraction}{0.75} \pagestyle{fancy} -\usepackage{seqsplit} - -% Pandoc emits inline code as \texttt{...}. This blunt wrapper keeps long -% paths and commands from overflowing PDF table cells. -\let\origtexttt\texttt -\renewcommand{\texttt}[1]{{\origtexttt{\seqsplit{#1}}}} +% Keep inline identifiers intact. Move unusually long paths to a code block +% or shorten their prose label instead of splitting callable names mid-word. +% Libertinus Mono permits ordinary word hyphenation, so disable that too. +\DeclareTextFontCommand{\texttt}{\ttfamily\hyphenchar\font=-1\relax} \usepackage{fvextra} \fvset{breaklines=true, breaknonspaceingroup=true, breakanywhere=true} diff --git a/docs/thesis/system-design-implementation.md b/docs/thesis/system-design-implementation.md index 68f44b87..60867c8b 100644 --- a/docs/thesis/system-design-implementation.md +++ b/docs/thesis/system-design-implementation.md @@ -67,10 +67,9 @@ diagram: Preparing reports, transforming documents, and collecting workspace information often involve procedures that must be repeated with new inputs. An AI assistant can help perform such work. Repeating it requires preserving the operations -and their data connections as an executable procedure. This thesis presents -`lda.chat`, a -programmable workflow platform for defining, checking, running, and inspecting -reusable workspace procedures. +and their data connections as an executable procedure. This thesis develops +the programmable workflow subsystem of `lda.chat` for defining, checking, +running, and inspecting reusable workspace procedures. A workflow describes the operations to perform, the data they exchange, and the decisions that select the next step. An **artifact** is an immutable saved @@ -88,10 +87,10 @@ choice to separate a node's data output from its routing outcome. The implementation includes a workflow runtime, Python authoring objects, a workflow service, and adapters for trusted Python functions and external tools. -Evidence includes -a deterministic case study and focused tests of validation, -execution, and persistence. Authoring usability remains to be evaluated as -the interaction design develops. +The evaluation focuses on workflow construction and execution through the +Python client. It uses a deterministic case study and identifies focused +tests of validation, execution, and persistence. Agent-driven authoring and +authoring usability remain to be evaluated as the interaction design develops. # Introduction @@ -327,6 +326,13 @@ or produce combinations. The node's configuration and worked examples make these operations distinguishable. Append waits for connected inputs and emits their items in input order [@n8n-merge-2026]. +[@fig:n8n-merge-append] shows the Append setting and the resulting two +output records. The fields remain in separate records; selecting Append +does not combine them into one record. + + +![n8n Merge configured to append two inputs, with its output preview. Author's screenshot.](assets/comparison/n8n-merge-append.png){#fig:n8n-merge-append width=100%} + Selecting a merge mode changes which records appear in the output. For the report task, joining actions by owner is different from appending two action lists. @@ -356,6 +362,14 @@ distinguish complete records from records needing attention. Multiple paths can qualify, so exclusivity must follow from the rules rather than the branching appearance alone [@zapier-paths-2026]. +[@fig:zapier-path-condition] shows a rule testing whether the selected +`Hello` field contains `world`. The test record matches, and the interface +reports that this path would have continued. This preview lets the author +check a rule against a concrete value before running the workflow. + + +![A Zapier Paths condition and its matching test record. Author's screenshot.](assets/comparison/zapier-path-condition.png){#fig:zapier-path-condition width=75%} + The same documentation describes sequential execution of qualifying paths. The rules can permit several branches to execute in one run. An exclusive choice requires conditions that cannot qualify together. @@ -367,6 +381,10 @@ The interface therefore needs to communicate both the condition being tested and the consequence of a match. In lda.chat, an ordinary outcome selects one successor, whereas several Zapier Paths may qualify. +```{=latex} +\begin{minipage}{\linewidth} +``` + ## LangGraph: describing decisions in Python LangGraph's Graph API lets an author define state, add node functions and @@ -374,6 +392,47 @@ routing, compile the graph, and invoke it. For the report task, a developer can represent extracted fields in state and write a routing function that selects what happens next [@langgraph-graph-api-2026]. +In this small example, `clean` returns an update to the `text` field. +The separate `choose` function reads that updated state and selects either +the rendering step or the end of the graph: + + +```python +from typing import TypedDict +from langgraph.graph import START, END, StateGraph + +class ReportState(TypedDict): + text: str + report: str + +def clean(state: ReportState): + return {"text": state["text"].strip()} + +def choose(state: ReportState): + return "render" if state["text"] else END + +def render(state: ReportState): + return {"report": "# Report\n\n" + state["text"]} + +builder = StateGraph(ReportState) +builder.add_node("clean", clean) +builder.add_node("render", render) +builder.add_edge(START, "clean") +builder.add_conditional_edges("clean", choose, ["render", END]) +builder.add_edge("render", END) +graph = builder.compile() +``` + +```{=latex} +\end{minipage} +``` + +Calling `graph.invoke({"text": " Notes ", "report": ""})` cleans the text, +then produces a report. Whitespace-only input ends after `clean`. The state +update and the routing decision are expressed in separate functions here; +LangGraph also offers `Command` to return both from a node +[@langgraph-graph-api-2026]. + This authoring style exposes more behavior as code. Nodes produce state updates, reducers determine how updates combine, and conditional routes select subsequent execution. The documented model supports graph loops and super-step @@ -417,8 +476,8 @@ mapping selects a configured provider that must satisfy the saved source requirements. An app connection or credential is therefore only a partial analogy: the source also supplies operations and their contracts. -The design contribution is the composition of these established lifecycle -responsibilities with the typed graph and client interface. Each system must +The system combines these lifecycle responsibilities with its typed graph +and Python client. Each system must distinguish an edit to future work from the recorded definition and data of a past execution. @@ -485,7 +544,7 @@ flowchart LR Read["Read
notes"] -->|ok| Extract["Extract
report"] Extract -->|ok| Check{Complete?} Check -->|ready| Render["Render
report"] - Check -->|needs_information| Ask["Request
information"] + Check -->|needs_information| Ask["Request information
(interrupt)"] Ask -->|submitted| Render Render -->|ok| Finish([End]) ``` @@ -611,7 +670,7 @@ classDiagram Deployment "1" <-- "0..*" Run : started from ``` -## Available operations and environment binding +## Available operations and deployment binding A **capability** describes functionality available from a source. The report example uses operation capabilities, such as extraction and rendering. A @@ -622,7 +681,7 @@ A **source** groups capabilities under a configured identity. The extraction operation might come from trusted Python code, while another operation or document is supplied by an external service. -An **environment binding**, also called a **deployment binding**, connects a +A **deployment binding** connects a logical source requirement in the workflow to a concrete source in the environment. Validation checks whether that source exists and matches the saved requirements. **Source drift** means those @@ -767,6 +826,15 @@ Refreshing it requests a new snapshot. This makes network activity explicit, although applications must decide when to refresh and how to present progress. +A local/static server can also start deployment runs on a schedule. The +optional scheduler supports one-time and recurring starts and records each +scheduled occurrence for inspection. The Python client's `App.create_schedule()` +registers a schedule for an existing deployment; `App.schedule()` retrieves +its configuration, and `App.schedule_occurrences()` retrieves its history. +The server must have scheduling enabled to start runs without a connected +client. The Limitations chapter discusses scheduling's storage and +concurrency constraints. + The draft API and CLI also support persisted editing workspaces, which retain unfinished definitions between requests. Python authoring can save directly from its editable object. Both routes produce a saved definition for deployment @@ -775,7 +843,8 @@ and execution. ## Executing the graph The execution core is the runtime that runs the saved graph. It holds the -workflow input, working state, and current execution positions. To execute +workflow input, working state, and the active node for each in-flight item +or call. To execute an ordinary operation node, it resolves the node's input bindings, calls the selected operation, checks its result, applies output bindings, and follows the edge selected by the outcome. Repeating that cycle advances the run until @@ -818,8 +887,8 @@ flowchart TB A declared outcome such as `needs_information` is a workflow decision. A handler exception or exhausted node-execution allowance is an execution -failure. A workflow can therefore complete with a non-success business outcome -without being a failed runtime execution. Where iteration supports collecting item +failure. A workflow can complete with an outcome such as `needs_information` +without failing the run. Where iteration supports collecting item errors, that policy must be explicit; errors do not automatically become ordinary outcome edges. @@ -841,7 +910,8 @@ a time. An output binding appends each rendered report to a `reports` state field, which the assembly step reads. [@fig:foreach-graph] shows the authored graph. The `loop` edge enters the -item body. Its return edge, `render.ok -> each`, ends that item's work. +item body. Its return edge (a back-edge), `render.ok -> each`, ends that +item's work. The `done` edge leads to the continuation after the collection is processed. @@ -862,7 +932,9 @@ controller from `Prepare documents` starts an invocation over the collection. [@fig:foreach-region] follows one invocation from start to finish. Read downward for time. Solid arrows request work or apply a data binding; -dashed arrows report completion. The labels identify the data and routes. +dashed arrows report completion. Here line style distinguishes requests from +completion; in [@fig:report-state], it distinguishes execution from data +bindings. The labels identify the data and routes. ```{.mermaid #fig:foreach-region width=95% caption="Serial iteration finishes both documents before assembly."} @@ -888,7 +960,7 @@ The same authored render node executes twice. The runtime distinguishes those executions so that finishing A advances to B, while finishing B permits assembly. Each item returns to the foreach invocation that started it. -The back-edge in [@fig:foreach-graph] records the item's return. Its output +The return edge in [@fig:foreach-graph] records the item's return. Its output binding appends the report before that return. A node in the body belongs to that loop's control region. The validator rejects using the same node both inside and outside the body, or returning past the immediate enclosing loop. @@ -915,7 +987,7 @@ The parent item then continues to its own return edge. sequenceDiagram participant Item as Parent item A participant Child as Child workflow - participant Result as Item A reports writes + participant Result as A's pending report writes participant Each as Foreach Item->>Child: Input binding: document = A.md activate Child @@ -941,7 +1013,7 @@ its child does not permit assembly while B is still running. The foreach waits for its required item completions before following `done`. Concurrent items retain separate pending writes until their results combine. -In [@fig:scope-boundaries], “Item A reports writes” represents these pending +In [@fig:scope-boundaries], “A's pending report writes” represents these pending writes. In serial mode, the output binding updates enclosing state so the next item can read it. @@ -1005,9 +1077,9 @@ re-inspects the exact artifact version. The resulting artifact object is therefore reconstructed from the stored definition. Identity checks reject responses identifying a different artifact or deployment. -The service's `inspect_run` operation retrieves a stored run by its identifier. -The Python client's `Run.refresh()` method calls that operation, verifies the -run and deployment identities, and returns a new run snapshot. `Run.resume()` +The Python client's `Run.refresh()` method retrieves a stored run by its +identifier through the service's `inspect_run` operation. It verifies the +run and deployment identities and returns a new run snapshot. `Run.resume()` submits the requested response for an interrupted run and also returns a new snapshot. The caller retains the returned object to see the updated status and result. @@ -1059,7 +1131,8 @@ document's separate view until the outer foreach combines the documents. Publishing every serial section's write directly into shared workflow state would expose unfinished document results to the other concurrent items. -The runtime records where each execution belongs. A **frame** records one +The runtime tracks the active node, the workflow invocation that owns its +data, and the updates held separately by concurrent items. A **frame** records one execution position, including its current node and owning foreach when it is an iteration item. A **scope** contains the input, working state, and context of one workflow invocation. A child workflow has its own scope. A **lineage** @@ -1095,8 +1168,14 @@ unnamed current-item value would leave it unclear which one the node receives. **Execution context** supplies runtime information to expressions and input bindings. Its foreach entries are keyed by the authored controller's identity. -Inside the inner body, `context.foreach.documents.item` selects the enclosing -document, while `context.foreach.actions.item` selects the current action. +Inside the inner body, two paths distinguish the enclosing document from +the current action: + +```text +context.foreach.documents.item # enclosing document +context.foreach.actions.item # current action +``` + The runtime constructs these entries from the active frames. Repeated executions of the same node therefore resolve the paths to their own items. @@ -1198,9 +1277,10 @@ Their Pydantic models describe the input and output contracts. The author consumes those operations from the service inventory; the client does not import their implementations to execute them locally. -The following blocks form one asynchronous Python session. The retained test -executes these blocks against the real service in process, replacing only -the HTTP connection assignment. It does not verify remote server startup. +The Python blocks form one asynchronous session. The retained test runs that +session against the real service in process. It substitutes an in-process +connection for the HTTP connection assignment; remote server startup is not +tested. For HTTP use, the blocks assume a server using `examples/report_workflow/wf.config.json`, reachable at its configured address, and a fresh artifact name or unused version. The fixture @@ -1444,9 +1524,9 @@ sequenceDiagram Client->>API: Bind deployment to version API-->>Client: Deployment and readiness Client->>API: Run with input - API->>Provider: Invoke graph steps - Provider-->>API: Outputs and outcomes - API-->>Client: Stopped run snapshot + API->>Provider: Invoke bound operation + Provider-->>API: Output and outcome + API-->>Client: Run snapshot Client->>API: Inspect run and bounded trace API-->>Client: Stored execution evidence Client-->>Author: Result or diagnostic @@ -1506,6 +1586,42 @@ recorded risks and followups, and a Markdown report headed example's data reached the intended outputs. The extraction contract requires the sectioned notes format described at the start of this chapter. +## Registering a scheduled run + +The same deployment can be invoked later by the server's scheduler. With +scheduling enabled on the local/static server, the client can register a +one-time start and inspect its stored configuration and occurrence history: + +```python +from datetime import UTC, datetime, timedelta + +schedule = await app.create_schedule( + schedule_id="report-once", + deployment_id=deployment.deployment_id, + trigger={ + "kind": "oneshot", + "at": (datetime.now(UTC) + timedelta(hours=1)).isoformat(), + }, + input_bindings=[{ + "target": "text", + "expression": {"kind": "literal", "value": notes}, + }], + max_steps=100, +) +schedule = await app.schedule(schedule.schedule_id) +history = await app.schedule_occurrences(schedule.schedule_id, limit=10) +``` + +The input binding stores the current contents of `notes`. The scheduled run +receives that text even if the original file changes. A recurring workflow +that needs fresh notes would need an operation that retrieves them when it +runs. Occurrence history records what happened to scheduled starts, including +admission and completion. Immediately after registration, it can be empty. + +The session check verifies registration and inspection without waiting for +the scheduled time. Scheduler execution is exercised separately in +[`test_scheduled_deployment_example.py`](../../tests/examples/test_scheduled_deployment_example.py). + ## What this case demonstrates The executable check in @@ -1520,7 +1636,7 @@ discovery and invocation, and the artifact/deployment/run lifecycle using the raw-plan fixture. Those tests are evidence for the report operations and lifecycle, complementing the Python session check. -The Python presentation makes the current authoring experience concrete: +The Python session shows the current authoring steps: inspect operations, declare contracts, connect data and outcomes, save, select bindings, and inspect an execution. The CLI and draft surface offer alternative interfaces to related lifecycle operations. @@ -1540,8 +1656,9 @@ are needed to evaluate the interaction beyond this scripted session. Evaluation distinguishes three questions: whether the runtime follows its contract, whether the public lifecycle composes correctly, and whether an -author can use that lifecycle effectively. The current evidence addresses -the first two through controlled tests and an adapted in-process walkthrough. +author can use that lifecycle effectively. The reported in-process walkthrough +exercises the public lifecycle. Targeted tests for runtime contracts are +mapped in the Evidence Index. The authoring assessment identifies the operations available through the interface. Usability remains a separate evaluation question. @@ -1845,7 +1962,7 @@ different from promising that an earlier action can be undone. This report examined how a useful procedure can become a reusable workflow that an author can define and an operator can inspect. The implemented -system separates the saved definition, its environment bindings, and each +system separates the saved definition, its deployment bindings, and each execution into artifacts, deployments, and runs. Those distinctions give workflow use a record beyond the lifetime of an editing session or a single script invocation. diff --git a/tests/docs/test_thesis_front_matter.py b/tests/docs/test_thesis_front_matter.py index 1fed0026..7c2e4083 100644 --- a/tests/docs/test_thesis_front_matter.py +++ b/tests/docs/test_thesis_front_matter.py @@ -58,3 +58,46 @@ def test_abbreviations_compile_without_a_dummy_float(tmp_path: Path) -> None: assert "API" in rendered assert "Application Programming Interface" in rendered assert "None 1" not in rendered + + +@pytest.mark.skipif( + any(shutil.which(tool) is None for tool in ("pandoc", "xelatex", "pdftotext")), + reason="Pandoc, XeLaTeX, and pdftotext are needed for the inline-code build", +) +def test_inline_identifier_moves_to_next_line_intact(tmp_path: Path) -> None: + """A prose line ending must not split a callable's name into fragments.""" + output = tmp_path / "inline.pdf" + identifier = "local.report.render_markdown_report" + result = subprocess.run( + [ + "pandoc", + "--standalone", + "--pdf-engine=xelatex", + "--variable=geometry:textwidth=10cm", + "--variable=monofont:Libertinus Mono", + "--include-in-header", + str(THESIS / "header-includes.tex"), + "--output", + str(output), + ], + # Shift the identifier along the line to expose both forced splitting + # and font-dependent automatic hyphenation near the right margin. + input="\n\n".join( + f"{'word ' * count}`{identifier}`." for count in range(1, 16) + ), + text=True, + encoding="utf-8", + capture_output=True, + check=False, + timeout=90, + ) + assert result.returncode == 0, result.stderr + rendered = subprocess.run( + ["pdftotext", "-layout", str(output), "-"], + capture_output=True, + text=True, + encoding="utf-8", + check=True, + timeout=15, + ).stdout + assert rendered.count(identifier) == 15 diff --git a/tests/docs/test_thesis_langgraph_example.py b/tests/docs/test_thesis_langgraph_example.py new file mode 100644 index 00000000..7d6e70b2 --- /dev/null +++ b/tests/docs/test_thesis_langgraph_example.py @@ -0,0 +1,28 @@ +"""Exercise the comparison snippet when the optional LangGraph library exists.""" + +from pathlib import Path + +import pytest + + +@pytest.mark.parametrize( + ("text", "expected_report"), + [(" Notes ", "# Report\n\nNotes"), (" ", "")], +) +def test_langgraph_comparison_routes_updated_state( + text: str, expected_report: str +) -> None: + """Check the rendered example without making LangGraph a product dependency.""" + pytest.importorskip("langgraph.graph") + manuscript = ( + Path(__file__).resolve().parents[2] + / "docs/thesis/system-design-implementation.md" + ).read_text(encoding="utf-8") + section = manuscript.split("", 1)[1] + snippet = section.split("```python\n", 1)[1].split("```", 1)[0] + namespace = {} + # This is trusted repository prose, executed exactly as displayed. + exec(compile(snippet, "thesis-langgraph-example", "exec"), namespace) + result = namespace["graph"].invoke({"text": text, "report": ""}) + assert result["text"] == text.strip() + assert result["report"] == expected_report diff --git a/tests/examples/test_thesis_python_walkthrough.py b/tests/examples/test_thesis_python_walkthrough.py index 4d3d7bda..3e4a2df0 100644 --- a/tests/examples/test_thesis_python_walkthrough.py +++ b/tests/examples/test_thesis_python_walkthrough.py @@ -10,9 +10,11 @@ from typing import Any, cast import pytest -from wf_client import App, Deployment, Run +from wf_api.durable_context import durable_workflow_api +from wf_client import App, Deployment, Run, Schedule from wf_client.protocols import WorkflowClientPort from wf_config import load_workflow_config +from wf_scheduling.store import FileScheduleStore from wf_server.config import build_workflow_server_from_workflow_config ROOT = Path(__file__).resolve().parents[2] @@ -32,7 +34,11 @@ async def test_thesis_python_session_preserves_report_and_repair( config = load_workflow_config(ROOT / "examples/report_workflow/wf.config.json") config.server.store.root = tmp_path / "store" server = build_workflow_server_from_workflow_config(config) - app = App._from_port(cast(WorkflowClientPort, server.api)) + # Enable real schedule persistence without starting a background scheduler: + # this session verifies registration, while another test exercises dispatch. + schedule_store = FileScheduleStore(config.server.store.root) + api = durable_workflow_api(server.context, schedule_store=schedule_store) + app = App._from_port(cast(WorkflowClientPort, api)) monkeypatch.chdir(ROOT) manuscript = THESIS.read_text(encoding="utf-8") @@ -84,3 +90,14 @@ async def test_thesis_python_session_preserves_report_and_repair( for displayed in displayed_outputs: assert displayed in diagnostic, "Displayed output differs from the session" assert namespace["trace"].frames + schedule = namespace["schedule"] + assert isinstance(schedule, Schedule) + assert schedule.deployment_id == deployment.deployment_id + assert schedule.max_steps == 100 + assert schedule.trigger["kind"] == "oneshot" + persisted = schedule_store.get_schedule(schedule.schedule_id) + assert persisted is not None + expression = persisted.input_bindings[0].expression + assert expression.kind == "literal" + assert expression.value == namespace["notes"] + assert namespace["history"]["occurrences"] == []