- Random port via net.Listen(":0") replaces hardcoded 33310 (was the
primary failure mode under parallel test load).
- Bump waitForPort deadline 5s → 30s — `go build` under -race can exceed
5s on a loaded machine.
- Replace osPath() (always returned empty PATH because exec.Command("env").Env
is the *child's* env, not the parent's) with explicit PATH+HOME via
os.Getenv. Don't inherit full env: would leak ROUTING_MCP_TOKEN from the
parent shell and flip the routing pod into auth-required mode, breaking
the test.
Closes#15. Verified: 10 cold-cache test runs pass, 3 consecutive task check
runs pass.
mcpclient.New previously accepted an empty token and silently omitted
the Authorization header at request time. When the env var sourcing
the token was missing from a Kubernetes Secret (envFrom doesn't warn
on missing keys), this surfaced as an opaque 401 from the upstream
MCP server with no log trail — see hyperguild #13 and brain entry
"mcpclient-empty-token-silent-401-envfrom-missing-key".
mcpclient.New now returns ErrTokenRequired when token is empty.
The routing pod's project_create init checks the error and exits
with a clear message pointing at routing-secrets, turning a runtime
401 storm into a startup crashloop the operator can fix immediately.
Tests pass a dummy "test" token (httptest servers don't enforce
bearer auth, so any non-empty value works). Added a regression
test asserting empty-token construction returns ErrTokenRequired.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gitea's push-mirror cannot push to a non-existent remote — it just
runs 'git push' against whatever URL it's given. So a project_create
flow that only configures the mirror leaves the GitHub side as an
unfulfillable URL.
New internal/githubclient package: single-purpose client that POSTs
/user/repos to create an empty private repo (auto_init=false so the
first mirror push doesn't conflict with a generated README). Treats
422 'name already exists' as idempotent success via ErrAlreadyExists.
401/403 are surfaced as 'PAT missing repo scope or invalid' so the
operator sees the real cause instead of a vague upstream error.
Skill wiring:
- New stepCreateGitHub between stepCreateRepo and stepMirror in the
orchestrator.
- Skipped entirely when Config.GitHub is nil (degraded mode — the
routing pod runs without GITHUB_PAT, mirror config still lands,
but the actual sync to github fails until the repo exists).
- cmd/routing/main.go constructs githubclient.New(GitHubPAT) only
when the PAT is set; the skill receives nil otherwise.
Tests:
- happy path: fake github 201 + assertions that the 'reached' array
is [create_repo, create_github_repo, mirror, infra_commit, issue].
- github 422 already-exists: idempotent, all gitea steps still run.
- github 401: returns failed_step=create_github_repo, no mirror or
later steps.
- degraded mode (Config.GitHub nil): reached omits create_github_repo,
rest of the flow runs unchanged.
Updated existing tests to read [skill, gh] from newSkill instead of
just skill, and adjusted reached-array expectations to include the
new step.
Tracks #10.
Adds the project_create tool to the routing pod that automates the
"new project" bootstrap end-to-end from claude.ai. Gitea-first
architecture: GitHub receives the repo only via push-mirror, never
via a direct GitHub API call from this server.
Four sequential calls to the gitea-mcp server (configured via
GITEA_MCP_URL):
1. create_project_from_template — Gitea repo from
template-go-{agent,web} per the 'stack' arg
2. repo_mirror_push (action=add) — push-mirror to
github.com/<GITHUB_OWNER>/<name>.git, interval 8h, sync_on_commit
3. file_write_branch — k3s/staging/<name>/namespace.yaml committed
on a staging/<name> branch in the infra repo
4. issue_create — experiment brief (hypothesis + description + stack
+ provisioning log) on the new repo, returns the issue_url
Returns gitea_url, github_url, issue_url, next_steps. The next_steps
string is the exact shell sequence the operator runs locally to
clone, scaffold via local-dev 'task new-project', and push.
Idempotency: create_project_from_template + repo_mirror_push +
file_write_branch all return JSON-RPC code -32003 (Conflict) when
their target already exists; the orchestrator swallows the conflict
and continues. Re-running on an existing repo restates the brief in
a fresh issue.
Error handling: on any non-conflict downstream failure the response
returns {reached: ["<step>",...], failed_step: "<step>"} alongside
a JSON-RPC error. No rollback — partial state stays so the operator
can resume manually.
New env vars (all optional except GITEA_MCP_URL):
GITEA_MCP_URL enables the tool
GITEA_MCP_TOKEN bearer auth for gitea-mcp
GITEA_OWNER default mathias
GITHUB_OWNER default mathiasb
INFRA_REPO default infra
GITHUB_PAT repo scope, used as mirror remote_password; never logged
Without GITEA_MCP_URL set, the tool is not registered and the
routing pod starts normally (degrades open).
internal/mcpclient/: new minimal JSON-RPC tools/call client with
bearer auth, used by project_create. Unwraps MCP's
content[0].text envelope and surfaces typed errors via mcpclient.Error.
Tests: table-driven against an httptest fake gitea-mcp covering happy
path (4-step success + correct PATCH-style arg shapes), idempotent
repo-exists, mirror failure (partial-success response with reached=
[create_repo] + failed_step=mirror), infra-commit failure (reached up
to mirror + failed_step=infra_commit), and validation errors.
Closes#10
Removes the supervisor binary and its two exclusive skill packages (tdd,
spec) now that all functionality is covered by SKILL.md files, the routing
pod, and the brain MCP. Routing pod reuses review/debug/retrospective/trainer
skill packages which are intentionally preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The routing decision is about reasoning capacity, not cost or provider.
Fast model (koala/qwen35-9b-fast) handles high-pass-rate calls; thinking
model (iguana/gemma4-26b) handles low-pass-rate calls. Removes the
implicit Anthropic dependency from the routing pod — both models go
through LiteLLM.
Renames: HYPERGUILD_LOCAL_MODEL → HYPERGUILD_FAST_MODEL,
HYPERGUILD_CLAUDE_MODEL → HYPERGUILD_THINKING_MODEL,
Router.LocalModel → FastModel, Router.ClaudeModel → ThinkingModel,
log decision "claude_fallback" → "thinking_fallback".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plan 6 is now deployed; replace the _routing_pending placeholder in the
routing MCP entry with a real headers block carrying X-Hyperguild-Mode:
client-local. The pod treats absent or unknown values as client-local,
so this is forward-compat for future modes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wires Config → LiteLLMExecutor → Router → four skills (review, debug,
retrospective, trainer) → Registry → MCP server with bearer auth and
/healthz. Each skill's CompleteFunc is wrapped so the Router decides
local-vs-Claude per call and logs every decision to the brain /mcp.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enables exposing the supervisor MCP via Tailscale Funnel for claude.ai
custom-connector tests. Auth is opt-in: empty SUPERVISOR_MCP_TOKEN
preserves the existing unauthenticated behavior for tailnet-internal
callers and local dev.
When the token is set, every request must carry
"Authorization: Bearer <token>" or it is rejected with HTTP 401 and a
JSON-RPC -32001 error. Comparison uses crypto/subtle.ConstantTimeCompare;
the token value and the supplied header are never logged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds pass-rate to the CLI README's subcommand block. Updates CLAUDE.md
to note the new /pass-rate endpoint alongside the existing brain
HTTP REST API surface. Updates the session_log MCP tool's
final_status description to reflect the new pass|fail|skip vocabulary
introduced by Plan 5's SKILL.md instrumentation; the aggregator
still accepts legacy ok|error|skipped values for backwards compat.
Adds 'hyperguild brain pass-rate <skill> [--window 7d] [--json]'
calling the new /pass-rate endpoint. Human output:
tdd: 47 / 50 = 94% (window: 7d)
or 'no data (window: 7d)' when pass_rate is null.
PassRateResult mirrors the response envelope; PassRate is *float64
so null is preserved across decode.
dispatch() already prefixes errors with 'hyperguild <subcmd>: ', so
handlers re-prefixing with their own name produced stuttered output
like 'hyperguild brain: brain query: topic required'. Strip the
redundant prefixes from the seven affected errors.New / fmt.Errorf
calls in brain.go and mode.go.
Also fix the LITELLM_BASE_URL usage text — it's optional (empty
falls through to airplane tier), not required, matching the
README.
The brain HTTP REST /query endpoint accepts POST with JSON
{query, limit}, not GET with URL query string. Surfaced by
Task 7 smoke testing — GET returned 405 Method Not Allowed.
The response shape ({results:[...]}) is unchanged; only the
request side flips to POST + JSON body. brainClient.Write was
already using POST + JSON body and is unaffected.
Tests updated to assert POST + JSON body on the Query path.
Adds cmd/hyperguild/README.md (subcommands, env vars, install path)
and three Taskfile targets:
task hyperguild:dev — go run from source
task hyperguild:build — build into ./bin/hyperguild
task hyperguild:install — go install into $GOBIN
Concludes Plan 4 of the hyperguild migration. The binary replaces
the supervisor's tier MCP and surfaces brain HTTP REST access plus
mode bootstrap to shell pipelines and ad-hoc agent prompts.
'hyperguild mode <cloud|client-local|sovereign>' writes a per-mode
.mcp.json template:
- cloud: brain MCP only
- client-local: brain + routing placeholder with _routing_pending
pointer to Plan 6
- sovereign: brain only + top-level _mode_note explaining Crush
is primary; .mcp.json is Claude Code fallback
Default output is ./.mcp.json; --out overrides; --force overwrites.
Brain URL sourced from BRAIN_URL (default http://koala:30330) so the
template stays in lockstep with the user's brain host.
All three subcommands now wired; notYet/errNotImplemented removed
from main.go.
Reads markdown from stdin, POSTs to the brain's /write endpoint with
type + slug, prints the resulting path. Pairs with 'brain query' for
shell-friendly read/write access to the brain HTTP REST API.
Tests cover success, missing args, backend error propagation, and
empty stdin (which produces an empty content payload — the brain
server's responsibility to validate).
Adds 'hyperguild brain query <topic>' against the brain HTTP REST
/query endpoint. Default human output prints path + score + title;
--json passes through the response envelope. --limit overrides the
default 5-result cap.
runBrainWrite remains a stub for Task 5.
Adds brainClient with Query and Write methods against the brain's
HTTP REST endpoints (/query, /write). Constructor reads BRAIN_URL env
var, defaulting to http://koala:30330 — the Tailscale-exposed
NodePort that serves both MCP and REST.
Tests cover success, transport error, and non-200 cases via
httptest fakes; URL override is verified via t.Setenv.
Adds the tier subcommand to the hyperguild CLI. Reuses
internal/tier.Detect verbatim, sources probe URLs from
ANTHROPIC_PROBE_URL (default https://api.anthropic.com) and
LITELLM_BASE_URL (no default — empty triggers airplane).
Human-readable output by default; --json emits the same Info struct
as the supervisor's tier MCP returns. Tests cover all three tier
states via httptest fakes.
Lays down the cmd/hyperguild/ entry point. Defines the subcommand
contract (ctx, args, stdin, stdout, stderr) error, the dispatch()
function that's testable without os.Exit, and stubs for tier / brain /
mode that return errNotImplemented. Subsequent commits replace each
stub.
Part of Plan 4 (hyperguild CLI) of the hyperguild migration.
Claude Code now supports MCP servers over HTTP natively (type: "http"
in .mcp.json). The stdio↔HTTP bridge binary was a workaround for the
older stdio-only constraint and is no longer needed — the supervisor
NodePort on koala (30320) is reachable over Tailscale from any client
machine.
Removed:
- cmd/bridge/ (Go source, ~60 lines)
- bin/supervisor-bridge artifact
- Taskfile bridge:build target and the build aggregate's reference
- README "Build the bridge binary" instruction
Updated:
- .mcp.json switched to {type:"http", url:"http://koala:30320/mcp"}
- README architecture diagram and "Connect a project" section
Behavioural prerequisite for this change shipped in 7f7524c
(notifications fix). Verified end-to-end: tier tool call returns
{tier:2, label:"lan-only"} via direct HTTP, no shim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the three-layer Claude subprocess orchestration (local model →
Claude verifier → cloud escalation). Skills now call LiteLLM directly
and return plain text to Claude Code, which decides what to do with it.
- Delete executor, orchestrator, verifier, result, attempts packages
- Simplify LiteLLMExecutor: Run(Request)→Result becomes Complete(model,sys,user)→(string,int64,error)
- Replace ExecutorFn with CompleteFunc in all 6 skill configs
- Rewrite all skill handlers to call Complete and return {"text","model","duration_ms"}
- Simplify config/models: remove Verifier/LlamaSwapURL, add ModelFor
- Bump version to v0.5.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
buildOrch now returns a closure instead of *Orchestrator. Each invocation
calls models.ChainFor(skill, req.Model) so a non-empty caller override
collapses to a single-entry chain (no escalation). The attempts slice is
also allocated fresh per call, preventing unbounded growth across requests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each skill now gets its own Orchestrator built from its ChainFor entry,
with LiteLLM for local tiers and Claude for cloud tiers. Removes the
defunct models.Resolve calls and single shared executor.Run pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reader agent scans session logs for SFT/DPO candidates; writer receives
reader output and formats+writes training pairs to brain/training-data/.
Adds trainer-reader.md and trainer-writer.md discipline prompts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the spec skill that generates structured implementation specs from
requirements and writes them to a configurable output path in the project.
Follows the same pattern as review/debug skills with session history injection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the debug skill following the same pattern as review. The skill
accepts project_root + error (+ optional context/model/session_id), prepends
session history, and calls the executor to produce 3-5 ordered hypotheses —
diagnosis only, no fixes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the review skill following the same pattern as retrospective/tdd.
Validates project_root and files args, prepends session history when a
session_id is provided, and delegates to the executor with Read,Bash tools.
Iron-law discipline prompt enforces CRITICAL/WARNING/SUGGESTION output format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds SessionsDir to tdd.Config, session_id to tool input schemas, and a
prependHistory method that reads the session JSONL log and prepends a
formatted history block to the task prompt before worker invocation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--json-schema combined with --output-format text produces empty stdout.
The structured result is in the "structured_output" field of the json
envelope. Updated executor to unwrap the envelope.
Also removes --bare flag which disables OAuth keychain reads, causing
silent auth failure when ANTHROPIC_API_KEY is not set.
Adds goreman Procfile + stdio bridge (cmd/bridge) for Claude Code MCP
integration. Task start/stop replaced with goreman + port-kill.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove cmd/supervisor/supervisor binary from git (was accidentally
committed) and add it to .gitignore. Move LITELLM_API_KEY from the
prompt string into the subprocess env, preventing it from appearing
in error log output when JSON parsing fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the stub with the actual supervisor entrypoint: loads config,
reads supervisor CLAUDE.md and tdd.md, constructs the executor and TDD
skill, registers them, and starts the HTTP/MCP server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>