Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and "used to be true" — the two things a reader most needs kept apart.

Entries are newest-last within a release, matching the order they were written.

## 0.1.7

- **admission accepted a sentinel pointing the wrong way.** `START` is the graph's entry and `END` its exit, but the endpoint check tested only *whether* an endpoint was a sentinel, never which side of the edge it sat on. So a proposal carrying `END -> x` or `x -> START` was admitted and then died in `Materializer` with `StateGraph`'s own "END cannot be a start node". The run does not proceed either way; what was wrong is where the failure landed. `GovernedLoop` charges a `MaterializationError` to `max_consecutive_execution_failures` (2) rather than `max_consecutive_rejections` (3), so a planner got *fewer* retries for a mistake admission is supposed to catch than for one it does catch, and two in a row ended the run as `EXECUTION_FAILED` — a stop reason claiming the graph ran when nothing had. And a rejection is meant to be data: `feedback()` hands the planner codes and remedies, where this handed it prose scraped off an exception, with nothing on the `admission` event's failed-check list because admission had not failed. The prompt already told models the rule; the gate is what did not hold when one ignored it. Refused now under `Check.REGISTRY` as `sentinel_wrong_direction`, with a remedy naming the side the sentinel belongs on, and both endpoints still reported rather than the first.
- **a sandboxed child that died without sending escaped as a bare `EOFError`.** `poll()` is true when the pipe is *readable*, and a closed pipe is readable — so a child killed rather than raising (`os._exit`, a segfault, an OOM-kill) fell through the timeout guard into `recv()` and raised `EOFError('')`. That is neither of the two failure shapes `SandboxedExecutor.run` documents, and it names nothing: not the tool, not the cause. `AgentNode` catches it under its blanket `except Exception` and renders `f"TOOL_ERROR: {exc}"`, so the model was handed `TOOL_ERROR:` with *nothing after the colon* — told its call failed, given no way to tell why or whether a retry could help — and the same empty text went into the trace, so the audit trail could not explain the failure either. The same shape as the curtailed-phase defect closed in 0.1.6, one layer down. Now a `RuntimeError` naming the tool and the exit code, a negative one rendered as the signal that killed it, which is what tells an OOM-kill from a deliberate `_exit`. Deliberately not a `SandboxViolation`: a child dying is not evidence it tried to escape confinement, and a violation is a specific accusation that lands in the trace as one.

## 0.1.6

- **the supervised loop had no exit from Slack.** A Slack-launched `plan --approve` parked, showed a human the run, was approved — and then returned `stopped: planned (awaiting `grapharc go`)`, into a subcommand the bot's gate does not carry and cannot be talked into carrying. So the one exchange the bot exists for (ask for work, see the graph, let it run) stopped one step short of running anything, and the approval it collected authorised nothing. `plan --go` is admitted from Slack now, and admitting it is only safe because of the rule that came with it: **from Slack, `--go` forces `--approve`**, on every registry, whether or not the requester typed it. Anyone in a workspace can type into this bot; without that rule one message would take a model's proposal straight to execution on the host with the graph visible only afterwards. It is forced rather than refused so the useful command stays one message — propose it, show me, run it if I say yes.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,4 @@ The edges are documented, not denied — the full list with mechanisms is in the
- Policy documents govern planning; the tool plane still reads CLI flags.
- The MCP gate binds the MCP surface, not the host: an agent with its own file tools in the run directory could forge the approval decision. The trust boundary is the working directory, as it is for the Slack workspace.

Version `0.1.6` · [changelog](CHANGELOG.md) · [roadmap](ROADMAP.md) · [website](https://codegraphcontext.github.io/GraphARC/) · MIT
Version `0.1.7` · [changelog](CHANGELOG.md) · [roadmap](ROADMAP.md) · [website](https://codegraphcontext.github.io/GraphARC/) · MIT
4 changes: 2 additions & 2 deletions docs/cookbook/01-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ projection instead and says which fields it dropped.
`tests/test_cookbook_basics.py` reproduces every recipe here and asserts these
exact strings, so the page cannot rot quietly.

Verified against `grapharc 0.1.6`, Python 3.14.6, `langgraph 1.2.9`,
Verified against `grapharc 0.1.7`, Python 3.14.6, `langgraph 1.2.9`,
`langchain-core 1.5.1`, `pydantic 2.13.4`.

Each snippet is a complete file. Save it and run it; nothing carries over between
Expand All @@ -41,7 +41,7 @@ uv run grapharc --version
Output:

```
grapharc 0.1.6
grapharc 0.1.7
```

Everything below uses only the base install — no API key, no network, no optional
Expand Down
4 changes: 2 additions & 2 deletions docs/cookbook/06-serving-and-ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,7 @@ with TestClient(app) as client:
```

```
health : {'status': 'ok', 'version': '0.1.6', 'graphs': ['qa']}
health : {'status': 'ok', 'version': '0.1.7', 'graphs': ['qa']}
created: 201 queued
status : succeeded
answer : Budgets cap iterations, tokens and time.
Expand Down Expand Up @@ -1259,7 +1259,7 @@ graphs : qa
ctrl-c to stop

$ curl -s localhost:8124/healthz
{"status":"ok","version":"0.1.6","graphs":["qa"]}
{"status":"ok","version":"0.1.7","graphs":["qa"]}

$ curl -s -X POST localhost:8124/sessions -H 'content-type: application/json' \
-d '{"graph":"qa","input":{"question":"how do budgets work?"}}'
Expand Down
2 changes: 1 addition & 1 deletion docs/deep-dive.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ A stable system is not one that claims to have no edges — it is one whose edge
- **`.env` and `grapharc.toml` follow the same discovery rule: the working directory, and nowhere else.** Neither searches parent directories — a run must not be governed by a file you did not know about, and must not be *billed* to one either. **This is a behaviour change:** the credential loader used to walk up to `/`, so a `.env` in an ancestor directory (a `$HOME` one on a shared box, a client project one above a demo checkout) was picked up silently. If you relied on that, move the file into the directory you run from, `export` the variable, or pass `env_file=` to name it explicitly. A real environment variable still beats any file.
- **`grapharc run` has no budget unless you give it one.** Set any of `--max-tokens`, `--max-iterations`, `--max-seconds`, or `--max-concurrency`; without them each dimension is unlimited and the gate admits a topology of any worst-case cost.

**Verified this pass:** `pytest` → green, 2,151 selected and 13 deselected (the live ones); `ruff check .` clean; all eight `grapharc demo` stages green, plus the `trace` / `metrics` / `viz` / `replay` tour against a freshly recorded demo trace; the wheel builds and imports all submodules in a clean virtualenv with `[all]`, and `0.1.6` on PyPI is that wheel. The counts are a snapshot, not a property of the project — `pytest` re-derives them in one command, which is the only reason they are quoted, and `tests/test_deep_dive.py` fails this line rather than letting it drift.
**Verified this pass:** `pytest` → green, 2,151 selected and 13 deselected (the live ones); `ruff check .` clean; all eight `grapharc demo` stages green, plus the `trace` / `metrics` / `viz` / `replay` tour against a freshly recorded demo trace; the wheel builds and imports all submodules in a clean virtualenv with `[all]`, and `0.1.7` on PyPI is that wheel. The counts are a snapshot, not a property of the project — `pytest` re-derives them in one command, which is the only reason they are quoted, and `tests/test_deep_dive.py` fails this line rather than letting it drift.

[ROADMAP.md](../ROADMAP.md) tracks what is built and what is not, item by item.

Expand Down
2 changes: 1 addition & 1 deletion grapharc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from grapharc.runtime.graph import GraphARC, WritePermissionError
from grapharc.runtime.state import GraphARCState

__version__ = "0.1.6"
__version__ = "0.1.7"

__all__ = [
"GraphARC",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "grapharc"
version = "0.1.6"
version = "0.1.7"
description = "A graph engineering toolkit on LangGraph: typed state contracts, per-node write permissions, enforced budgets, and JSONL traces that double as replay points."
readme = "README.md"
license = "MIT"
Expand Down
Loading