chore: release v0.1.2 - #3
Merged
Merged
Conversation
An audit of docs/guide against the user-operator-guide standard found the structure sound but turned up four content gaps and two structural deviations. Content: - POLICY_COLLECTION (config.py, default "policies") was documented nowhere. Added to the configuration reference and .env.example, noting that it carries no GATE_ prefix and that changing it needs a re-ingest. - The "invalid sample PR id" FixtureError added with the path-traversal fix landed after the guide was written. The runbook now tables all five 422 messages, and corrects two: the id pattern allows hyphens, and an invalid meta.json is a JSON parse failure only (a missing pr_id or title falls back to the directory name). Broadened that section's heading accordingly. - The "Review not found." and "Connection issue: …" banners were missing from the reviewer troubleshooting table, along with the invalid-id banner. - README had no link to the guide at all. Structure: - 00-index.md -> index.md, the entry point the standard expects. - Added a glossary; the domain jargon (risk gate, specialist, reconcile, escalation, checkpoint) was assumed rather than defined. - Renamed the how-to and operations pages to HT-01..04 and OP-01..05 so each folder numbers from 01, and dropped the numeric prefixes from the root pages. Headings, cross-links, and the index follow. Everything else in the guide was checked against source and is accurate: all GATE_* defaults, the seven enums, the seven endpoints and their status codes, the six-rule reconcile ladder, the log event table, the UI labels, and the demo script's beats. Generated with [Claude Code](https://claude.com/claude-code) by CEH
…et 5 Adds GATE_MODEL_EFFORT (low|medium|high|xhigh|max, default high) as a closed-set Literal so an invalid value fails at startup rather than at call time, mid-review. Effort is withheld from any model id containing "haiku": Haiku has no effort control and the API rejects the parameter. The test is on the configured id, not on which factory was called, so a Haiku model under any GATE_MODEL_* setting is covered and pointing a tier at Sonnet opts it in. One consequence worth calling out: langchain-anthropic turns on adaptive thinking when reasoning_effort is set on a model declaring xhigh support, and passes temperature through unchanged. Since a non-default temperature is not accepted alongside thinking, _chat now sends no temperature when effort is applied and keeps temperature=0 on the tiers that take none. Specialist wording is therefore no longer reproducible run to run; routing is unaffected, as reconcile is plain code. See DECISION_LOG entry 14. Default specialist and investigator model moves to claude-sonnet-5. Its PRICE_TABLE rate is carried over from claude-sonnet-4-6 and is unverified — it equals the existing fallback, so the displayed estimate is unchanged. The Agent SDK's ClaudeAgentOptions also accepts an effort field; the investigator's tool loop is left untouched, as the request covered the chat model. Not verified against the live Anthropic API: the default suite makes no API calls, so the effort payload was checked only where the request is built. Generated with [Claude Code](https://claude.com/claude-code) by CEH
…SDK loop Replaces the single GATE_MODEL_EFFORT with one setting per tier, so each GATE_MODEL_<TIER> now has a GATE_MODEL_<TIER>_EFFORT beside it: GATE_MODEL_CLASSIFIER / _EFFORT GATE_MODEL_SPECIALIST / _EFFORT GATE_MODEL_INVESTIGATOR / _EFFORT The Haiku rule is unchanged and still keyed on the configured id, not the tier: effort_for() returns None for any id containing "haiku", so a tier pointed at Haiku is covered wherever it appears and one pointed at Sonnet opts in. This is why the classifier's effort is inert at its Haiku default. The investigator's effort now also reaches the Agent SDK tool loop as ClaudeAgentOptions.effort, not just the final summarizing turn. effort_for() became public so both call sites resolve it the same way; ClaudeAgentOptions exposes no temperature, so the adaptive-thinking caveat from entry 14 applies only to the chat-model path. DECISION_LOG entry 14 amended: its per-tier and SDK-scope decisions are superseded here by explicit instruction. The temperature handling stands and remains unverified against the live API. Generated with [Claude Code](https://claude.com/claude-code) by CEH
Setting an effort broke the specialists:
ValidationError: 1 validation error for _FindingList
findings
Input should be a valid list [type=list_type,
input_value='{"findings": []}', input_type=str]
An effort turns on adaptive thinking, and the Anthropic API rejects a
tool_choice that forces tool use while thinking is on. langchain-anthropic
therefore drops the forcing (chat_models.py:2025 and
_get_llm_for_structured_output_when_thinking_is_enabled), and
with_structured_output's default function_calling method silently loses its
guarantee -- the model answered in prose, and the parser fed the raw JSON text
into the schema's only field.
structured_call now picks the method from the effort: json_schema, Claude's
dedicated structured output, which thinking does not restrict, when an effort
is set; function_calling otherwise. Tiers without an effort -- Haiku, where
effort_for() returns None -- are unchanged.
Not verified against the live API: the default suite makes no API calls, so
this was checked only where the method is selected. See DECISION_LOG entry 15
for the two rejected alternatives.
Generated with [Claude Code](https://claude.com/claude-code) by CEH
Every checkpoint read logged a deprecation warning per domain type: Deserializing unregistered type gate.graph.state.Finding from checkpoint. This will be blocked in a future version. LangGraph's msgpack deserializer reconstructs only allowlisted types. Ours are permitted-with-a-warning under today's default and the library states they will be refused outright later -- at which point checkpointed state comes back as bare dicts instead of models. Both checkpointers now declare them through the library's own API, saver.with_allowlist(CHECKPOINT_ALLOWLIST), in api/app.py and examples/demo.py. CHECKPOINT_ALLOWLIST sits at the bottom of graph/state.py and names each model and StrEnum explicitly rather than deriving them from vars(): an allowlist is a security boundary and is worth reading at a glance. A new test fails if the two drift apart, so a type added to state.py is caught at test time rather than at runtime. compile_graph now takes BaseCheckpointSaver[Any]; with_allowlist returns a shallow clone typed as the base class, and the graph never needed the concrete SQLite saver. Verified with LANGGRAPH_STRICT_MSGPACK=true, which enables the future behavior now: an allowlisted saver round-trips a populated ReviewState with its nested findings and enums intact, while an unwrapped one returns a plain dict. Generated with [Claude Code](https://claude.com/claude-code) by CEH
…rst-review race Three defects, one of them mine from 900a920. 1. The allowlist never took effect. BaseCheckpointSaver.with_allowlist MERGES into the existing allowlist, and the default is the sentinel True, so JsonPlusSerializer.with_msgpack_allowlist returns self unchanged and the warnings continued. It only appeared to work because the verification ran under LANGGRAPH_STRICT_MSGPACK=true, where the base is None and the merge does happen. The allowlist has to be given to the serializer at construction, which is what the new graph/checkpoint.py does. Confirmed against a real checkpoints.db: thirteen warnings before, none after, with HumanDecision and InvestigationReport arriving as models. The new test asserts both halves -- that a round trip warns nothing, and that an undeclared type is refused. Only the second would have caught the no-op. 2. The investigator ran unauthenticated. The SDK spawns the Claude Code CLI, which inherits os.environ; pydantic-settings reads ANTHROPIC_API_KEY from .env and never writes it there. Every other component takes the key as an argument, so this failed in exactly one place. Now passed through ClaudeAgentOptions.env. 3. The first review after a server start died with "Review not found.". A review is registered before its background run writes a checkpoint, and the UI polls as soon as POST returns; get_review 404'd, and the page stops polling on 404. Later reviews raced better because the first one pays for the lazy imports. A thread the app knows about now reads as running until its checkpoint exists. CLAUDE.md records the with_allowlist trap and the os.environ gap, both of which are invisible until something downstream breaks. Generated with [Claude Code](https://claude.com/claude-code) by CEH
…he sub-agent The reported failure was: event='investigation_error' error='Failed to start Claude Code: ' with nothing after the colon, 17ms after the human decision. That empty string is the whole diagnosis: Windows' SelectorEventLoop raises a bare NotImplementedError for subprocess creation, and the SDK interpolates its empty message into its own wrapper. Reproduced directly: ProactorEventLoop spawn OK SelectorEventLoop NotImplementedError str='' uvicorn picks that loop whenever use_subprocess is set -- which --reload does -- even on Windows, where it is precisely the loop that cannot spawn subprocesses (uvicorn/loops/asyncio.py). So `uvicorn ... --reload`, the command this repo documented everywhere, disabled the investigator and nothing else: classify, risk, and the specialists never spawn anything, so the pipeline looked healthy right up to the point the sub-agent ran. --reload is now gone from the README, CLAUDE.md, and the install, configure, and routine-operations pages, each with a note on why. run_investigation preflights the loop and returns an inconclusive report naming the cause, so the same mistake reads as a sentence rather than an empty error. The preflight sits with the existing no-snapshot check: still no raising into the graph. This supersedes the ClaudeAgentOptions.env fix in b1ab652 as the explanation for the reported failure. That change was still correct on its own terms -- the key genuinely never reached the subprocess -- but it was not what broke this. Generated with [Claude Code](https://claude.com/claude-code) by CEH
Ships per-tier reasoning effort (GATE_MODEL_<TIER>_EFFORT), moves the specialist and investigator tiers to claude-sonnet-5, and fixes the defects that surfaced from running the pipeline for real — two of which disabled the investigator outright: uvicorn --reload selects an event loop that cannot spawn the Claude Code CLI, and the CLI never received the API key because pydantic-settings reads .env without touching os.environ. - Bump: PATCH — the release is dominated by fixes; the new effort settings are additive and default to the model's existing behaviour, so nothing that worked before behaves differently unless a tier is retuned. - Manifests: pyproject.toml 0.1.1 -> 0.1.2 (the only manifest this project ships). - Docs: CHANGELOG [0.1.2] section written; README updated (Documentation section, GATE_MODEL_*_EFFORT, the --reload warning); CLAUDE.md updated (the with_allowlist trap, the os.environ credential gap, the --reload rule, and the start command); the user/operator guide renumbered to HT-/OP- prefixes with a glossary and four audit fixes. Generated with [Claude Code](https://claude.com/claude-code) by CEH
Owner
Author
|
Reopening to re-fire CI (no workflow run was created for this PR). |
CI failed the 100% coverage gate on both matrix jobs at 99.80%, with a single missed line: src/gate/agents/investigator.py 156 1 50 1 99% 254 Line 254 is the isinstance(loop, SelectorEventLoop) check inside _loop_can_spawn_subprocess, which sits behind an early return for sys.platform != "win32". On Windows the harness tests reach it incidentally; on the Linux runner nothing can. Coverage was host-dependent, so the suite read 100% locally and 99.80% in CI -- the gap only surfaced once Actions started running again. The sibling test already pins the POSIX direction by faking the platform, so this makes the pair symmetric: monkeypatch sys.platform to "win32" and stub asyncio.get_running_loop with a Mock(spec=asyncio.SelectorEventLoop), and the win32 branch executes regardless of the host. Rejected `# pragma: no cover` and a coverage exclusion -- both hide a real branch of a documented platform workaround rather than testing it. Test-only change; src/ is untouched. 114 passed at 100% statement and branch coverage, ruff check and format clean. Generated with [Claude Code](https://claude.com/claude-code) by CEH
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Release v0.1.2. Adds per-tier reasoning effort, moves the specialist and investigator tiers to
claude-sonnet-5, and fixes six defects — two of which disabled the investigator entirely.Why
The pipeline had not been run end to end against the real API since v0.1.1. Doing so surfaced
failures that no test could catch, because each came from a library or platform default rather
than from this project's logic:
uvicorn --reload— the command this repo documented everywhere — selects aSelectorEventLoopon Windows, which cannot spawn a subprocess. The Agent SDK drives the Claude Code CLI as one, so
every investigation failed with
Failed to start Claude Code:and an empty message. Nothingelse in the pipeline spawns a process, so the failure was invisible until the sub-agent ran.
pydantic-settingsreadsANTHROPIC_API_KEYfrom.envand never writesos.environ. Thespawned CLI inherits
os.environ, so the investigator alone ran unauthenticated.while thinking is on — so
with_structured_output's defaultfunction_callingsilently lostits guarantee and specialists died on a
ValidationError.with_allowlistmerges into the existing allowlist, and the default is thesentinel
True, so merging is a no-op. An earlier fix on this branch appeared to work onlybecause it was verified under
LANGGRAPH_STRICT_MSGPACK=true.How
GATE_MODEL_<TIER>_EFFORTbeside eachGATE_MODEL_<TIER>.effort_for()withholdsit from any model id containing
haiku, keyed on the configured id rather than the tier, andthe investigator's value reaches both its SDK tool loop and its summarizing turn.
json_schemaon effort-carrying tiers,function_callingelsewhere.use_state_allowlist(newgraph/checkpoint.py) constructs the serializerwith
CHECKPOINT_ALLOWLISTinstead of merging into a permissive default.ClaudeAgentOptions.env.--reloadremoved from every documented command.runninguntil its first checkpoint, instead of 404ing the UI out ofthe page on the first review after a start.
HT-/OP-prefixes withindex.mdas the entry point, aglossary, and four fixes from an audit against the source.
Testing
uv run pytest --cov— 113 passed, 100% statement and branch coverage ofsrc/gate.uv run mypy src— clean understrict.uv run ruff check .— clean.checkpoints.db: 13 deserializationwarnings before, none after, with
HumanDecisionandInvestigationReportarriving as models.ProactorEventLoopspawns,SelectorEventLoopraises
NotImplementedErrorwith an empty message.Not verified against the live API: the default suite makes no API calls, so the
json_schemastructured-output path and the effort/temperature combination were checked only where the request
is built.
uv run python examples/demo.py 003_auth_changewith a key is the end-to-end check.Checklist
pyproject.tomlis the only manifest, at 0.1.2[0.1.2]section writtenGenerated with Claude Code by CEH