Skip to content

stabem/graphhelm

v0.1.23MIT

GraphHelm methodology, host setup guidance, and evidence-based next-step selection.

GraphHelm guide

GraphHelm is an experimental, local-first agent operating system. Its plugin skills help an agent decide what to do; the CLI and Runtime provide typed contracts, deterministic checks, execution control, and evidence. A skill's text is never authority to bypass permissions, publish a graph, or claim an unobserved result.

flowchart LR
  A[User promise] --> B[Scope and risk]
  B --> C{Adequate observer?}
  C -->|No| M[Keep proof unresolved]
  C -->|Yes| D[Keel: smallest scoped change]
  D --> E[Run reached checks]
  E --> F[Independent review]
  F --> G[Merge and verify]
  G --> H[Resume: two next options]

For a small reversible change with a clear observer, use the direct route. For unclear proof, persistence, permissions, compatibility, security, or external effects, use Journey-Proven Development (JPD): describe the user journey, compile each promise into an observable obligation, and refuse a success claim if the required observer is missing. Keel keeps context and new code surface proportional, then asks for the lowest total cost per proven delivery after quality is preserved. Count repairs, reviews, and repeated work, not tokens alone. Neither method replaces the repository's review and merge rules.

GraphHelm's Governor owns operational graph publication. Agents send typed signals and proposals; deterministic code enforces schemas, policies, permissions, and state transitions. A plugin install does not start the Runtime, install the CLI, activate an extension package, or change a user's existing instructions. graphhelm init creates a project MCP entry that selects the authenticated Runtime from the project identity, without pinning a port or token path; graphhelm setup --resolve home/.claude.json=register-mcp can register the same project-aware command at user scope. Direct --url and --token-file connections remain available when an operator deliberately wants to pin one Runtime. graphhelm setup starts with inventory and a preview; applying a reviewed plan requires explicit acceptance and creates a backup before supported host files change. graphhelm restore previews recovery before applying it. See the CLI preview, getting started, skills map, and current delivery process.

Install from the repository

The graphhelm plugin contains this guide, graphhelm-setup, and graphhelm-resume. The same marketplace also lists the existing graphhelm-jpd and graphhelm-development-contracts plugins. Install those companions only when their skill families are needed; both can connect to the same local Runtime and require a separately configured trusted CLI, token file, and per-session actor. Installing any plugin alone does not install the GraphHelm executable.

In Claude Code:

claude plugin marketplace add stabem/GraphHelm
claude plugin install graphhelm@graphhelm

In Codex CLI:

codex plugin marketplace add stabem/GraphHelm
codex plugin add graphhelm@graphhelm
codex plugin add graphhelm-codex-hooks@graphhelm

After installation, start a fresh session. In Claude Code, use /graphhelm:graphhelm-guide, /graphhelm:graphhelm-setup, or /graphhelm:graphhelm-resume; Claude namespaces plugin skills, so the installed commands are not bare /graphhelm-setup or /graphhelm-resume. In Codex, invoke $graphhelm-guide, $graphhelm-setup, or $graphhelm-resume. The setup skill guides the separate graphhelm setup CLI through inventory, a reviewed plan, backup, and restore; invoking the skill alone changes no host files. The resume skill reads current evidence and offers exactly two next actions with one recommendation; it does not take either action for you.

Session hooks (version 0.1.20)

This installed plugin includes command hooks for Claude Code. Affected Codex versions use the separate graphhelm-codex-hooks compatibility companion, which is the only Codex hook registration. Unbound sessions are silent by default. When the session is explicitly bound to a GraphHelm execution, SessionStart injects a short Keel reminder, and startup and resume read one Runtime briefing and return a bounded summary of its next action and pending work. There is no unconditional second briefing read: request detailed evidence only when the next action needs it. Compaction restores cached context labeled as not refreshed, without an HTTP read. A cached summary never authorizes a state transition; the Runtime still validates the current state. SessionEnd records an attributed agent_session_ended signal. It never marks a task or node complete. The hook does not create a run, pick a run by project name, or forward the host's raw payload.

Each hook process accepts one bounded JSON object frame. It returns after the first complete object, even when the caller keeps stdin open; a process performs one operation and does not consume a second object. EOF before a complete object, invalid UTF-8 or JSON, a non-object value, and an oversized frame remain invalid input.

SessionStart allows one request up to five seconds of socket inactivity, within Claude's ten-second hook declaration, including startup and durable cache publication. Runtime's five-second operator read budget is a progress check and does not guarantee a response within five seconds. SessionEnd also allows five seconds of socket inactivity; both plugins declare ten seconds for that event. Older Codex versions may clamp larger declarations to three seconds. Inspect the installed host's effective budget rather than assuming it honors a declaration.

Claude has a separate shared shutdown budget, which defaults to 1.5 seconds. A plugin's SessionEnd timeout does not raise that shared budget. Set CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=10000 in the environment that launches Claude when using the bound end hook. This is a supported host setting; changing it inside a hook cannot change its parent's budget. See the Claude SessionEnd reference. This setting permits more shutdown time, not more requests or tokens. A desktop process already running must be restarted from the configured launch environment.

A socket inactivity limit is not a wall-time deadline. Host cancellation can stop the hook before its acknowledgement is observed, while a separately recorded Runtime effect remains durable. An unavailable briefing or acknowledgement stays UNOBSERVED; it never becomes task success and is never retried by this hook.

Python 3 must be on the host's PATH. Set these variables in the environment that launches the agent host:

  • GRAPHHELM_EXECUTION_ID: exact execution ID to bind. Without it, the hook makes no Runtime request and injects no context by default.
  • GRAPHHELM_KEEL_CONTEXT=1: explicitly enable the short Keel reminder in an unbound session.
  • GRAPHHELM_TOKEN_FILE: path to the local Runtime token file, required for a bound run.
  • GRAPHHELM_RUNTIME_URL: Runtime API origin; defaults to http://127.0.0.1:8791.
  • GRAPHHELM_SESSION_ID: optional expected host session ID. A mismatch leaves the operation unobserved rather than reading or writing a different session's binding.
  • GRAPHHELM_NODE_ID: optional explicit node reference. It is labeled as configured; the hook does not infer an assignment from the next pending node or claim that the Runtime assigned it.

For example, in PowerShell before launching an agent in a terminal:

$env:GRAPHHELM_EXECUTION_ID = 'run-example'
$env:GRAPHHELM_TOKEN_FILE = 'C:/path/to/events.token'
$env:GRAPHHELM_RUNTIME_URL = 'http://127.0.0.1:8793'
$env:CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS = '10000'
claude

The same environment variables apply to Codex. A desktop app that was already running will not inherit variables set later in a terminal. Codex asks the user to review and trust new plugin hooks before running them. Updating the plugin does not override that host decision.

Third-party agent hosts can use the same script as a portable adapter. Pass an explicit bounded host identity such as --host my-agent; unknown hosts use the graphhelm-portable-v1 JSON contract automatically. start returns a bounded context, end returns its acknowledged delivery state, and inspect returns local observations. A portable response reports the adapter boundary and activation: "unobserved"; it does not claim that the host loaded the hook or accepted a task. Use --format portable when the host wants this shared machine-readable contract; the built-in claude and codex hooks keep their native envelopes by default.

The token stays in its file and travels only to a loopback HTTP or HTTPS Runtime. A redirect is refused. The hook prints UNOBSERVED if a bound read or write fails, without blocking the agent session or claiming a record landed. End signals require a Runtime keyring; this hook does not write an unsealed evidenceOut file. A full signal budget also leaves the end unobserved.

A repeated end hook uses the same signal body and idempotency key. Once the Runtime acknowledges the matching execution and signal IDs, the hook saves that confirmation and skips later sends. An unconfirmed request remains retryable. A rejected graph mutation does not mean the signal was not recorded: those are different results in the Runtime contract.

State is scoped to host, Runtime origin, execution and session, outside the repository under the user's local state directory; GRAPHHELM_HOOK_STATE_DIR can override that path. Actors carry a session-specific identity rather than only a host name. The end request also sends the host session ID as X-GraphHelm-Actor-Session for the Runtime's structured presence record. These observations do not assign or complete operational nodes, and session end is not proof that the work passed.

Since version 0.1.3, hooks use a new state and signal-key namespace: 0.1.2 timestamp files have no delivery acknowledgment and are not migrated as proof. A corrupt record stays unobserved; it is not silently replaced with a new signal under the same key. The local lock is released by the OS if the host terminates the hook. Delivery deduplication is scoped to this protocol, not an exactly-once claim across plugin upgrades or deletion of local state.

The hook calls no model. Cached compaction avoids a Runtime request, and confirmed repeated end delivery avoids another request. These are request-count guarantees covered by local HTTP tests, not a measured reduction in a complete task's billed tokens. The hook returns only bounded typed summary fields, never the full objective, transcript or Runtime command text.

Explicit task handoff

hooks/task_handoff.py is a provider-neutral command adapter for any host that can run Python. It is explicit and is never called automatically by SessionStart or SessionEnd. offer records a sealed task_handoff_offer signal for one exact recipient host and session. receive reads the original journal event, opens its sealed envelope, reads a fresh bounded briefing, and records a sealed task_handoff_received signal with replyTo pointing to the offer. status reconstructs offers and matching receipts from the Runtime journal.

When status names an offer ID, it opens only that offer's sealed content. It still validates the current recipient's receipt records to determine which reference that offer; receipt matching and integrity checks are unchanged. Unfiltered status continues opening all offer records. An unavailable unrelated offer cannot block a targeted read, while unavailable or corrupt selected evidence is refused.

A timed-out or disconnected write can have committed before its response was lost. The adapter does not repeat that POST. It reads the journal and validates the exact sealed offer or receipt, including content hash, execution, actor and addressing. Only verified durable evidence permits state: recorded; the result retains writeAcknowledgement: unobserved for that attempt. Missing, invalid or unavailable evidence remains unobserved. Explicit HTTP refusals are not reconciled into success. This proves retrieval only: accepted: false, task completion and native host activation remain separate. Socket timeouts still bound inactivity rather than total wall time; the caller must bound the whole operation, including reconciliation reads.

python <installed-plugin>/hooks/task_handoff.py offer --host claude --session-id <sender-session> --recipient-host codex --recipient-session-id <receiver-session> --handoff-id <bounded-id>
python <installed-plugin>/hooks/task_handoff.py receive --host codex --session-id <receiver-session> --offer-id <offer-id>
python <installed-plugin>/hooks/task_handoff.py status --host codex --session-id <receiver-session> --offer-id <offer-id>

The adapter requires the Runtime to seal the signal evidence. It re-reads the journal and sealed evidence on retries, so losing local hook state does not create a second offer. It validates the execution, source actor, target actor, protocol metadata, evidence hash, and replyTo; bounded incomplete pagination or missing evidence stays unobserved. A receipt proves that the receiving adapter retrieved the offer and current briefing. It does not prove model comprehension, task acceptance, node ownership, permission transfer, or completion. Runtime owner credentials authorize declared actor labels; they do not cryptographically authenticate the host or model.

Each explicit handoff request allows up to five seconds of socket inactivity per Runtime request; the caller must still impose its own whole-process deadline. A timeout remains unobserved and is not retried automatically. --handoff-id distinguishes multiple offers between the same two sessions in one execution; repeating the same id replays the journal record after local state loss.

The same adapter has an optional, explicitly configured stdio MCP mode. The Codex compatibility companion registers it with a plugin-relative working directory and script path; the main plugin does not register a second server. The server makes no Runtime request while starting. Its legacy environment session mode uses GRAPHHELM_MCP_HOST, GRAPHHELM_SESSION_ID, GRAPHHELM_EXECUTION_ID, GRAPHHELM_TOKEN_FILE, and (when needed) GRAPHHELM_RUNTIME_URL. For normal Codex calls, opt into native metadata mode with GRAPHHELM_MCP_SESSION_SOURCE=codex_metadata; it requires GRAPHHELM_MCP_HOST=codex and each tools/call _meta object to contain equal, valid threadId and sessionId values. GRAPHHELM_SESSION_ID, when set, pins that per-call identity. The server does not retain a session between calls. Initialize and tool discovery can run unbound; an actual tool call without fixed execution, Runtime, and token configuration remains unobserved. The companion explicitly forwards the host's task execution, Runtime URL, token-file path, optional session pin, and optional node binding through env_vars. It never forwards the token contents or accepts those settings from model arguments. Starting Codex without these task settings leaves the server discoverable but its task calls unobserved.

Configure the trusted server process and run directly when needed:

python <installed-plugin>/hooks/task_handoff.py --mcp-stdio

The server exposes offer, receive, and status through standard newline-delimited MCP JSON-RPC. Host, execution, Runtime URL, and token path come only from trusted server configuration. In native Codex mode, the host supplies the session identity in per-call metadata; tool arguments can name the recipient or offer, but cannot replace any binding. Missing, malformed, conflicting, or pinned-mismatched metadata fails before Runtime I/O. In legacy mode, the environment session contract is unchanged. Requests are bounded and unknown fields fail closed. A tool failure returns MCP isError: true and an unobserved result. An oversized or unterminated frame closes the transport after a sanitized error; its remaining bytes cannot become another request. This local transport proves adapter behavior only; native host trust and activation still need a separate fresh-session observation.

Explicit run team and shared mailbox

The optional run-team protocol is bound to one GRAPHHELM_EXECUTION_ID. Joining is explicit; installing the plugin or opening a host chat does not attach it to a run. The Codex compatibility companion exposes team_join, team_report, team_send, team_inbox, and team_acknowledge through its existing metadata-bound MCP server. Each tool call uses the native Codex session ID from matching threadId/sessionId transport metadata. The member identity is the same codex-session-<id> label used by the session hooks. The plugin's general graphhelm MCP server still uses the shared agent-chat label and is not a team identity source.

An environment-bound Claude session can use the provider-neutral CLI with GRAPHHELM_EXECUTION_ID, GRAPHHELM_RUNTIME_URL, GRAPHHELM_TOKEN_FILE, and a trusted GRAPHHELM_SESSION_ID set by its launcher. For example, from the installed plugin directory:

python hooks/run_team.py join --host claude
python hooks/run_team.py report --host claude --update-id progress-1 --task "Review UI" --activity "Checking the rendered view" --state working
python hooks/run_team.py inbox --host claude
python hooks/run_team.py acknowledge --host claude --message-id <recorded-message-id>
python hooks/run_team.py send --host claude --message-id reply-1 --to <sender-actor-id> --reply-to <recorded-message-id> --text "Review complete"

team_send writes either an addressed message to an already joined actor or a room message. The caller supplies a stable messageId so a retry cannot duplicate it. A direct reply must return to the sender of a message addressed to this session. team_inbox reads a bounded set of room and unacknowledged addressed messages; a read does not acknowledge anything. A separate call by the addressed session records team_acknowledge. Studio reconstructs members, public work reports, messages, and acknowledgements from sealed evidence in that exact run. A report is the session's statement, not independent proof of active work or acceptance. Silence is shown as missing fresh activity; only a recorded SessionEnd says an end was observed.

The mailbox is pull-based. A busy or closed host is not interrupted or restarted, and a recorded message does not imply that another host read it. The current Studio operator note is a separate run-log message, not an automatic team-mailbox delivery. Runtime bearer credentials authorize declared actor labels; they do not cryptographically authenticate native host identity. Use a trusted launcher to pin the Claude session and never accept session IDs from a model argument in place of that pin. Existing sessions need an explicit safe migration before using these tools.

Setup inspection

The graphhelm-setup skill includes a hook inspection step. Resolve the actual installed plugin directory from the host's plugin inventory, then run the Claude hook from graphhelm or the Codex hook from the installed graphhelm-codex-hooks companion:

python <installed-plugin>/hooks/session_hook.py inspect --host claude
python <installed-codex-hooks>/hooks/session_hook.py inspect --host codex --session-id <actual-session-id>

Inspection is read-only, makes no Runtime request, and does not read token contents. It reports configuration and local script observations. It cannot establish that the host trusted or loaded the hook; a manual invocation can also create a local observation. Keep activation unverified until the host's own fresh-session event is observed. Do not register a second copy in settings. The adoption CLI does not migrate opaque hook commands; the setup skill identifies legacy registrations and requires an explicit, backed-up migration for those entries.

The optional SubagentStart and SubagentStop hooks record a child identity under the host session bound to the current execution. They do not receive the direct nested delegator, task title, result, or acceptance verdict. Studio shows only the verified host-session link and the observed lifecycle events; a missing stop never means the child is still working. Claude's TaskCreated and TaskCompleted hooks separately record the bounded task subject, native task id, optional teammate name, and observed task phase. They do not link that task to a subagent id or certify that its output was accepted. Task descriptions and transcripts are not uploaded. Installing updated source does not prove host activation; verify a fresh native session separately.

To add a methodology companion, install graphhelm-jpd@graphhelm or graphhelm-development-contracts@graphhelm with the host's plugin install / plugin add command. Install graphhelm-codex-hooks@graphhelm for Codex hook compatibility as shown above. The skill catalog shows when each is useful. Their MCP registration needs GRAPHHELM_CLI to be an absolute trusted executable path, GRAPHHELM_TOKEN_FILE to name a local token file, and GRAPHHELM_ACTOR to identify the chat session. Never paste the token value into a manifest or prompt.