Skip to content

feat(graph): ship Graph V2 workflow engine - #3

Merged
SeasonsChange422 merged 10 commits into
mainfrom
release/graph-v2
Oct 14, 2026
Merged

SeasonsChange422 merged 10 commits into
mainfrom
release/graph-v2

Conversation

@SeasonsChange422

Copy link
Copy Markdown
Member

Summary

Brings Graph V2 (codex/graph-production) into main, together with the fixes found while running it end to end against real Codex and Claude runtimes.

  • V2 workflow engine: branch, parallel, join, loop, subgraph, delay, http, script and approval nodes (schema_version 2), durable run document, artifacts and managed git refs, stop receipts, visual editor, AI authoring.
  • Acceptance fixes on top of the branch: Claude mcpServers record for agents without MCP servers, empty start-form query no longer searches issues.
  • The WebSocket instance-id, stop-evidence and artifact-UUID fixes were already merged to main in v0.1.2; the sources are deduplicated in the merge (4 conflicts, all "keep both").
  • Migrations 358–371 are new and apply on top of 357. Per CLAUDE.md they use no foreign keys and concurrent indexes.

Verification

  • Real runs on a local build of the branch: Codex (gpt-6-sol) and Claude (claude-haiku-5-5) executed a V2 graph with produce_commit / verify_commit, approval, and cancel.
  • Merge result: go build ./..., go vet on touched packages, pnpm typecheck (6/6) pass locally.
  • Failing Go tests on this Windows host: 189 on this branch vs 192 on main; the only one not failing on main is the OpenClaw adapter test, which needs the OpenClaw CLI (not installed here).

Known gaps — shipped deliberately

Security and guardrail work was explicitly deferred:

  • HTTP node headers are stored in plaintext; script nodes have no sandbox and accept custom env; no provider allowlist.
  • No per-run budgets, approval timeouts or trigger rate limits.
  • Claude runs use --bare, which does not work with some API gateways.
  • Codex drops a bearer token in Graph's private config.
  • Not exercised end to end: HTTP node, subgraph, quorum, triggers, AI generation, retry, restart recovery, canvas interactions, narrow screens.

Rollout notes

  • Migrations are not reversed by the deploy script's container rollback; take a database backup before tagging.
  • Ship server and CLI together; the daemon side of V2 (execution, stop evidence) only reaches users on the new CLI.

Test plan

  • CI green on Linux
  • After deploy: create and run a V2 graph with an agent node and an approval node
  • Cancel a running node; the run reaches cancelled within seconds

🤖 Generated with Claude Code

SeasonsChange422 and others added 10 commits October 13, 2026 22:26
- Added graphProcessScope to manage process execution and termination for Graph commands.
- Introduced graphProcessBackend to handle execution with built-in runtime overrides.
- Created graphhttp package for executing HTTP requests with strict validation and response handling.
- Implemented tests for graph HTTP execution, ensuring proper handling of private destinations, headers, and response limits.
- Added GraphRuntimeSnapshot struct to encapsulate runtime selection and command semantics.
- Established constants for daemon capabilities related to graph execution.
The WS claim path rebuilt the HTTP request from the connection identity but
dropped X-Daemon-Instance-ID. Graph claims bind an attempt to the daemon
process identity and refuse a claim without it, so every Graph task claimed
over WebSocket was rejected with 409 and requeued in a loop; it only recovered
when the daemon fell back to HTTP.

Capture the header at connect, forward it when rebuilding the claim request,
and cover the forwarded headers with a regression test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ervers

A published agent with no MCP servers freezes its configuration as `{}`. The
Graph path only substituted an empty server set for an absent or null value, so
Claude received `{}` and exited with "mcpServers: Invalid input: expected
record, received undefined".

Normalize the frozen configuration so mcpServers is always present, and cover
the absent, null, empty, partial and invalid cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d timed-out attempts

When the server cancels a task, executeAndDrain returns from its drainCtx.Done
branch immediately with a bare cancelled result, before the backend has finished
tearing down its process tree. The backend computes ExecutionStopped only after
that, so the result reached the daemon with no stop evidence. A Graph attempt is
settled only from a stop receipt, so the daemon withheld the receipt
(execution_unknown) and the engine, by design, kept the run blocked: a run
cancelled mid-flight stayed in cancel_requested indefinitely, even though both
processes had already been reaped within seconds. The idle-watchdog and
drain-timeout branches had the same gap.

For Graph attempts (DisableWholeRunRetry), wait up to 30s for the backend's
terminal result in those branches and carry its ExecutionStopped. Ordinary
tasks never read the evidence and are not delayed.

Verified against real Codex and Claude runs: a run cancelled while both agent
nodes were executing now reaches cancelled in about 6s; before the change it
stayed in cancel_requested for more than 10 minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The start form mounts with an empty issue search box and graphIssueSearchOptions
was enabled whenever a workspace was present, so it called /api/issues/search
with q="". The endpoint rejects an empty q with 400, and the form renders
issues.error, so every open of the start form surfaced a bogus error.

Only query once the trimmed search text is non-empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s what to use

A produce_commit agent declared its commit SHA as the git_commit artifact id.
The prompt never said what the id must be, and ValidateResult only checked that
it was non-empty, so the submission was accepted. The id is persisted in a uuid
column, so reconciliation then failed with "invalid_id: invalid Graph resource
UUID" every 3 seconds, permanently, and the run could not advance; only
cancelling it recovered.

Validate the artifact id as a UUID when the result is submitted, so the agent
gets a correctable error, and state the requirement in the node prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Merges the Graph V2 branch plus the acceptance fixes (Claude mcpServers record, empty start-form query, WebSocket instance id, stop evidence, artifact UUID). Where the hotfix already on main and the branch both carried the same fix, the sources are deduplicated.
… HTTP after termination

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Publish decoded the canonical definition over the draft in place. Canonical ordering can put a node where a differently configured one used to be, and encoding/json keeps fields absent from the JSON, so a loop node could inherit a stale http config and fail validation. Decode into a fresh value instead.

An HTTP effect prepared in a scheduling pass was still dispatched after the commit even when the same pass requested termination (for example an exhausted loop budget). Cancel the effects prepared in that pass and release their slots. Both are covered by the existing dispatch-fence Postgres tests, which could not reach their assertions before.
…handler

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Fixes the no-unexpected-multiline lint error that failed frontend-build.
…CI runner

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

The fake-CLI tests re-exec the test binary and bounded it with 250ms to 1s. On a busy runner the process did not start in time and the attempt was reported as cancelled or timed out. The openclaw case also failed locally for the same reason (its version check inherits the execution timeout). Raise the execution timeouts and the outer wait; the tests still assert the same outcomes.
@SeasonsChange422
SeasonsChange422 merged commit d9ad072 into main Oct 14, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants