Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
574a3fd
feat: helix-org prototype with MCP, prompt-driven CLI, and Role/Ident…
philwinder Apr 25, 2026
e60b4bf
feat: add MLOps newsletter demo (3 roles, 1 prompt, no scaffolding)
philwinder Apr 26, 2026
c2821fe
feat: add helix-org tail command for streaming events with channel globs
philwinder Apr 26, 2026
dbc23b7
docs: switch newsroom and getting-started demos to helix-org tail
philwinder Apr 26, 2026
9f451c2
refactor: unify Channel and Stream into single Stream abstraction wit…
philwinder Apr 26, 2026
65d1041
refactor: remove /tail HTTP endpoint and helix-org CLI, unify on MCP
philwinder Apr 27, 2026
e96291c
feat: add helix-org chat subcommand and update demos to interactive flow
philwinder Apr 27, 2026
385cb4f
fix: only pass --continue when claude has a prior session for the cwd
philwinder Apr 27, 2026
e50307e
fix: resume chat by explicit session ID instead of --continue
philwinder Apr 27, 2026
d79a392
feat: add dm and invite_workers tools for direct messaging
philwinder Apr 27, 2026
1fd048b
feat: per-Worker activation streams and worker_log tool
philwinder Apr 27, 2026
027138e
feat: add webhook transport with secretary demo and comprehensive tests
philwinder Apr 27, 2026
4766b7b
feat: add outbound webhook transport with bidirectional bridge demo
philwinder Apr 27, 2026
1cf2cb6
feat: canonical Message envelope as the only Event.Body shape
philwinder Apr 27, 2026
92fae06
feat: email transport via Postmark + DB-stored operational config
philwinder Apr 27, 2026
6567cc2
feat: two-worker email demo (Sam + Lee email each other)
philwinder Apr 28, 2026
638fa01
feat: render full Message envelope in worker activation prompts
philwinder Apr 28, 2026
ecfb525
feat: add inbound github transport with HMAC-verified webhooks
philwinder Apr 28, 2026
b01d634
docs: add github transport demo and design doc
philwinder Apr 28, 2026
6fd1302
refactor: move Role and Worker identity to domain (DB source of truth)
philwinder Apr 28, 2026
e01d8a6
feat: add MCP prompts system with /help and /role commands, chat type…
philwinder Apr 29, 2026
ee842ba
feat: fix cascading AI activations, add markdown rendering, real-time…
philwinder Apr 30, 2026
59ce87a
chore: add opinionated defaults to make targets and improve developer…
philwinder Apr 30, 2026
88f4d37
feat: batch queued triggers into single spawner activation
philwinder Apr 30, 2026
074c563
docs: add github-engineer demo and escalate setup-level issues via DM
philwinder Apr 30, 2026
2253f24
feat: per-Worker Helix projects with desktops, MCP wiring, and warm s…
philwinder May 1, 2026
4a6cb33
feat: republish role/identity on every activation + always git-pull i…
philwinder May 2, 2026
284d86b
docs: sharpen role.md template with default-quiet, trigger naming, an…
philwinder May 4, 2026
63e7a78
refactor: split helix runtime out of domain + tools, unify session shape
philwinder May 6, 2026
3d58083
docs: add manufacturing NCR demo with webhook cascades
philwinder May 6, 2026
da6a783
docs: harden manufacturing demo against local-default stream creation
philwinder May 7, 2026
a1b90f2
fix(tools): surface valid TransportKind values in create_stream schema
philwinder May 7, 2026
15ade47
docs: simplify manufacturing hire prompt now that schema enums kinds
philwinder May 7, 2026
8d19b7a
docs: rewrite manufacturing hire prompt in plain English
philwinder May 7, 2026
4f94775
fix(tools): accept bare-string transport shorthand in create_stream
philwinder May 7, 2026
09b114f
docs: inline the role markdown in the manufacturing hire prompt
philwinder May 7, 2026
2e6850f
fix(tools): clear Types when narrowing transport schema to object form
philwinder May 7, 2026
e835d6d
chore(gitignore): anchor `helix` rule to specific build-artifact paths
philwinder May 10, 2026
088e2b4
feat(helix-org): use new /sessions/{id}/messages queue + validate pro…
philwinder May 10, 2026
56620db
feat(helix-org): pre-flight desktop quota check before opening sessions
philwinder May 10, 2026
0548792
demo fix
philwinder May 10, 2026
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
10 changes: 6 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ charts/helix-controlplane/charts/*.tgz
# Source-of-truth is the .tmpl file; the rendered Chart.yaml is build output.
charts/*/Chart.yaml
# Anchor helix-runner to repo root so it does not shadow charts/helix-runner/.
# Other unanchored patterns below (helix, zed-build, tmp) are left as-is to
# avoid regressing the ignoring of build artifacts in subdirectories such as
# api/cmd/helix/helix or runner-cmd/helix-runner/helix-runner.
# `helix` and friends are anchored to specific build-artifact paths because a
# bare `helix` pattern matches any directory named `helix` at any depth — which
# accidentally swallowed helix-org/helix/ and helix-org/agent/helix/ entirely.
/helix-runner
zed-build
helix
/helix
/api/helix
/api/cmd/helix/helix
tmp
zed-config/development_credentials

Expand Down
33 changes: 33 additions & 0 deletions helix-org/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Binaries
/bin/
/dist/
*.exe
*.test
*.out

# Coverage
coverage.out
coverage.html

# Editor / OS
.DS_Store
.idea/
.vscode/
*.swp

# Env
.env
.env.local

# Media
*.mp4
*.gif
*.png

# Notes
TODO.md
design/

# Data
*.db
envs/
38 changes: 38 additions & 0 deletions helix-org/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
version: "2"

run:
timeout: 5m
tests: true

linters:
default: none
enable:
- bodyclose
- errcheck
- errorlint
- gosec
- govet
- ineffassign
- misspell
- nolintlint
- revive
- staticcheck
- unused
settings:
revive:
rules:
- name: exported
disabled: true

formatters:
enable:
- gofmt
- goimports
settings:
goimports:
local-prefixes:
- github.com/helixml/helix-org

issues:
max-issues-per-linter: 0
max-same-issues: 0
146 changes: 146 additions & 0 deletions helix-org/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project

Helix Org — a Go prototype run independently from the `helix` monorepo. It has its own tooling and does not share build/test infrastructure with the parent repo. Rules in the parent `helix/CLAUDE.md` do **not** apply here; treat this directory as its own project.

**Current year: 2026** — include "2026" in web searches for documentation and browser APIs.

## Design Philosophy (read this before writing code)

The ultimate goal is that the system is configured and used **almost entirely via the prompts of specific Roles and Positions**. Behaviour lives in the profile/prompt, not in the codebase. The code is scaffolding that lets this prompt-driven ecosystem thrive.

Practical consequences when choosing between alternatives:

- **Prefer data and text over code.** If a feature can be expressed as a profile edit, a scope value, or a tool description, do that before adding Go logic.
- **Keep the core generic.** Tool definitions, scope shapes, and enforcement decisions are owned by individual tools — not hard-coded in the registry, server, or domain layer. New tools (including MCP tools later) must be addable without editing the core.
- **Keep the MCP surface small.** MCP tools are reserved for org-graph primitives — both reads and mutations of the structural state (Workers, Positions, Roles, Channels, Grants, Streams). Anything else a Worker needs to do should go through the shell tools provisioned in their Environment (`bash`, `curl`, `git`, `gh`, `python`, etc.). If you're tempted to add an MCP wrapper like `publish_to_blog` or `fetch_url`, stop — the Role text describes how to use the shell directly, and if the workflow changes, only the Role changes. The test: does this operation read or mutate org-graph state? If yes, MCP. If no, shell.
- **No workflow in code.** Tools do exactly one thing. The code does not orchestrate multi-step sequences on behalf of an agent — it does not subscribe Workers to channels, grant tools implicitly, auto-create related records, or otherwise chain steps together. Orchestration lives in the *prompt* of the Worker invoking the tool. If a Role declares `DefaultTools` or `DefaultStreams`, those fields are **reference data the hiring manager's prompt reads**, not triggers the code acts on. When writing or reviewing a tool, ask: "is the code making a decision that the agent should be making?" If yes, remove it.
- **Write the smallest thing that works.** No speculative abstractions, no optional plumbing that isn't exercised today. If two tools share code, extract it then — not in advance.
- **Social enforcement first.** The default is that a Worker reads their scope from their prompt and complies. Only reach for hard enforcement when the cost of a violation is high.

When a design choice looks like it could go either way, pick the one that pushes more responsibility into prompts/configuration and less into Go code.

## Architecture at a Glance

- **Storage**: SQLite, driven by GORM with `AutoMigrate`. The database file lives alongside the binary by default and is configurable via flag/env. No raw SQL migration files.
- **Interface**: One HTTP endpoint. `/workers/{id}/mcp` (Streamable HTTP transport, no auth yet) carries every read and mutation of the org graph; the worker ID in the URL identifies the caller, and the server exposes only the tools that worker holds grants for. There is no other HTTP surface — even the human "what's happening?" view runs over MCP via `subscribe` + `read_events(wait=…)` from a `claude` session.
- **CLI**: A thin client binary with three subcommands. `helix-org bootstrap` opens the SQLite store directly and seeds the initial owner Worker — the one operation that cannot use MCP, because there is no Worker yet. Pass `--install-claude-mcp` to bootstrap to register the owner's MCP endpoint with the local `claude` CLI; from then on plain `claude` sessions can drive the org. `helix-org serve` runs the HTTP listener. `helix-org chat` exec's an interactive `claude` session pointed at a chosen Worker's MCP endpoint (default `w-owner`), with `--continue` so the per-directory conversation is restorable across invocations. After bootstrap, the CLI never opens the database again — humans drive the org through `claude` (directly or via `helix-org chat`), and Workers do the same when activated by the dispatcher.
- **Auth**: Deferred. Treat all callers as the root owner for now; real authentication is a later phase.

## Setup

Install required development tools before doing anything else:

```bash
make tools
```

## Build, Test, and Check

**Always prefer `make` targets over raw shell commands.** The Makefile sets required build tags, CGO flags, environment variables, and opinionated defaults (envs dir, DB path, listen address) that ad-hoc `go run` / `go test` / `golangci-lint` invocations miss. Running raw commands silently drifts from how the project actually builds and runs.

If you find yourself reaching for a multi-step shell incantation to build, run, test, format, lint, clean, or seed local state — **add a `make` target for it instead**, then call that target. Future-you and other agents will reuse it; one-off shell strings rot. Keep targets discoverable via `make help`.

```bash
make build # Build the binary into ./bin
make run # Run `helix-org serve` with opinionated defaults (./envs, ./helix-org.db, :8080)
make run ARGS="--model sonnet" # Run with extra flags appended after the defaults
make test # Run all tests (race + -count=1)
make test PKG=./domain/... # Test a specific package
make test-cover # Run tests + write coverage.out / coverage.html
make check # Format, vet, lint, and test (modifies files)
make ci # CI-safe: fmt-check, vet, lint, test (no writes)
make clean # Kill running servers, remove ./bin, ./envs, *.db, coverage files
```

`make check` is for local use — it runs `goimports -w` and may modify files. `make ci` runs `fmt-check` instead, failing if anything is unformatted without touching files. CI must use `make ci`; contributors must pass `make check` locally before pushing.

## Running the Project End-to-End

```bash
make run # opinionated defaults: serve, ./envs, ./helix-org.db, :8080
make run ARGS="--model opus" # append extra flags
make clean && make run # nuke local state (DB + envs + running server) and start fresh
make build && ./bin/helix-org --help # compiled binary (matches what CI ships)
```

`make run` is for fast iteration. Before pushing or tagging a release, exercise the compiled binary (`make build && ./bin/helix-org ...`) — the `go run` path can mask build-tag or linker differences.

`make clean` is destructive on purpose: it kills any `helix-org serve` process it can find, deletes `./bin`, `./envs`, every `*.db` in the project root, and the coverage artefacts. Use it whenever local state has drifted (stale Workers, half-bootstrapped DB, lingering server holding port 8080).

## Shipping Code

Before committing:

1. Run `make check` and confirm it passes (or at minimum `make test` for affected packages). Do not commit code that has not been validated.
2. Fix any failures before committing — do not skip or work around them.

Commits and PRs use **Conventional Commits**:

- Prefix: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`, `test:`, etc.
- Example commit: `feat: add webhook retry logic`
- PR titles follow the same format: `feat: add webhook retry logic`

When pushing additional commits to an existing PR, update the PR title and description to reflect the full set of changes in the branch.

## Go

- Fail fast: `return fmt.Errorf("failed: %w", err)` — never log and continue
- Error on missing configuration — fail with an error, don't log a warning and continue
- Use structs, not `map[string]interface{}`
- GORM AutoMigrate only — no SQL migration files
- Use gomock, not testify/mock
- No fallbacks — one approach, no fallback code paths
- No type aliases — update all references when moving or renaming types
- No panics — return errors; rewrite methods to support error returns if needed
- Log errors once at the top level — domain code returns errors, only handlers/workers log them

## Testing

When the user says "tdd", follow red-green strictly:

1. **Red**: Write a failing test. Run it, confirm it fails.
2. **Green**: Minimal fix. Run test, confirm it passes.
3. Run the full test suite for regressions.

Practices:

- **Tests live next to the code**: `foo.go` → `foo_test.go` in the same directory. Use package `foo` for whitebox tests and `foo_test` only when the test must exercise the public API in isolation (e.g. to avoid import cycles).
- **Table-driven tests** for anything with multiple input cases. Use `t.Run(name, ...)` per case so failures name the case.
- **`t.Parallel()`** in tests that don't share global state.
- **Race detector is always on** (`make test` adds `-race`). Treat race failures as hard bugs, never flakes.
- **`-count=1`** is set so tests never use the build cache — all runs are fresh.
- **Mocks**: use `gomock` generated via `mockgen`. Place generated mocks in `mocks/<pkg>/` and regenerate them as part of `make tools` updates, not by hand. Prefer hand-rolled fakes where the interface is small enough that a mock adds no value.
- **Fixtures**: put reusable test data under `testdata/` (Go ignores it during builds).
- **Coverage**: run `make test-cover` before large PRs. Treat coverage as a diagnostic, not a gate — low coverage on a file means "go look," not "write busywork tests."
- **No `testing.Short()` skips by default**. If a test must be slow or external, gate it on an explicit build tag (e.g. `//go:build integration`) and document how to run it.

## Linting

`golangci-lint` config lives in `.golangci.yml`. The enabled linters (`errcheck`, `govet`, `ineffassign`, `staticcheck`, `unused`, `gofmt`, `goimports`, `misspell`, `revive`, `gosec`, `bodyclose`, `errorlint`, `nolintlint`) catch a deliberate, narrow set. Do not disable linters to silence findings — fix the code.

- **Fix the finding, don't suppress it.** `//nolint:<linter>` is only acceptable with a trailing comment explaining *why* the rule is wrong for this site (`nolintlint` enforces this). Unexplained `//nolint` directives fail the lint run.
- **Formatting is non-negotiable**: `goimports` with `-local github.com/helixml/helix-org` groups local imports separately. Run `make fmt` before committing; CI runs `make fmt-check` and will fail on any drift.
- **Error wrapping**: `errorlint` enforces `%w` instead of `%v` for errors, and forbids type-assertion on errors — use `errors.As` / `errors.Is`.
- **`gosec`** flags raw SQL string concatenation, weak crypto, and command injection. If one of these fires, the fix is almost always to restructure the code, not to suppress the warning.
- **New linters** are added by editing `.golangci.yml` in a dedicated `chore: enable <linter>` PR that also fixes all findings it surfaces — never in the same PR as unrelated changes.

## Software Engineering

Object-oriented design principles:

- **Naming**: Classes by what they are, not what they do (avoid -er suffixes). Methods are builders (noun) or manipulators (verb), rarely both. Variables should be explainable as single/plural nouns; prefer simple names over compound ones.
- **Constructors**: One primary constructor, secondaries delegate to it. Keep constructors light. Prefer `new` only in secondary constructors.
- **Methods**: Prefer fewer than five public methods per class. Avoid static methods. Avoid null arguments and return values. Prefer richer encapsulation over getters/setters.
- **Encapsulation**: Prefer four or fewer encapsulated objects per class. Favour composition over inheritance.
- **Interfaces**: Prefer interfaces. Keep them small.
- **Immutability**: Default to immutable classes. Avoid type introspection and reflection unless the language idiom demands it.
- **No globals**: Prefer classes over public constants or enums.
- **Testing**: Prefer fakes over mocks.
- **Design**: Think in objects, not algorithms. Tell objects what you want; don't ask for data.
- **Boolean parameters**: Don't use a boolean to switch between fundamentally different behaviours (split the method or use polymorphism). Booleans are fine for orthogonal modifiers like filters or formatting options.
- **Dependency injection**: Use constructor injection. When a constructor accumulates many parameters, group related ones into a parameter/options object.
- **Language idioms**: Where these principles conflict with strong language conventions (e.g. Go exported struct fields), follow the language idiom and note the deviation.
69 changes: 69 additions & 0 deletions helix-org/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Helix Org Makefile — always drive builds/tests/lint through make.

BINARY := helix-org
MODULE := github.com/helixml/helix-org
CMD_PKG := ./cmd/$(BINARY)
BIN_DIR := bin
PKG ?= ./...
ARGS ?=

GOLANGCI_LINT_VERSION := v2.11.4

GO := go
GOFLAGS :=
CGO_ENABLED ?= 0

export CGO_ENABLED

.PHONY: help tools build run test test-cover test-e2e-helix fmt fmt-check vet lint check ci clean

help: ## Show available targets
@awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_-]+:.*##/ {printf " %-12s %s\n", $$1, $$2}' $(MAKEFILE_LIST)

tools: ## Install required development tools
$(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
$(GO) install golang.org/x/tools/cmd/goimports@latest
$(GO) install go.uber.org/mock/mockgen@latest

build: ## Build the binary into ./bin
mkdir -p $(BIN_DIR)
$(GO) build $(GOFLAGS) -o $(BIN_DIR)/$(BINARY) $(CMD_PKG)

run: ## Run `helix-org serve` with opinionated defaults; pass extra flags via ARGS="..."
$(GO) run $(CMD_PKG) serve --envs-dir ./envs --db ./helix-org.db --addr :8080 $(ARGS)

test: ## Run tests (use PKG=./path/... to scope)
CGO_ENABLED=1 $(GO) test $(GOFLAGS) -race -count=1 $(PKG)

test-e2e-helix: ## Run the //go:build integration helix-end-to-end smoke (requires HELIX_URL+HELIX_API_KEY)
CGO_ENABLED=1 $(GO) test -tags=integration -count=1 ./tools/helixclient/... ./tools/...

test-cover: ## Run tests and produce coverage.out + coverage.html
CGO_ENABLED=1 $(GO) test $(GOFLAGS) -race -count=1 -coverprofile=coverage.out -covermode=atomic $(PKG)
$(GO) tool cover -func=coverage.out | tail -n 1
$(GO) tool cover -html=coverage.out -o coverage.html
@echo "Wrote coverage.html"

fmt: ## Format and organise imports
goimports -w -local $(MODULE) .

fmt-check: ## Fail if files are not formatted (does not modify)
@unformatted=$$(goimports -l -local $(MODULE) . | grep -v '^vendor/' || true); \
if [ -n "$$unformatted" ]; then \
echo "Unformatted files (run 'make fmt'):"; echo "$$unformatted"; exit 1; \
fi

vet: ## Run go vet
$(GO) vet $(PKG)

lint: ## Run golangci-lint
golangci-lint run $(PKG)

check: fmt vet lint test ## Format, vet, lint, and test (modifies files)

ci: fmt-check vet lint test ## CI-safe: checks formatting without modifying

clean: ## Remove build artefacts, kill running servers, wipe local DBs and envs
@pkill -f '[h]elix-org serve' 2>/dev/null || true
rm -rf $(BIN_DIR) coverage.out coverage.html envs
rm -f *.db
Loading