Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions .github/actions/checkout-eyrie/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Checkout eyrie
description: Clone eyrie as a sibling repo for hawk go.work (../eyrie)

inputs:
ref:
description: Git ref to checkout (branch or tag)
required: false
default: main

runs:
using: composite
steps:
- name: Clone eyrie
shell: bash
run: |
set -euo pipefail
dest="${GITHUB_WORKSPACE}/../eyrie"
if [ -d "$dest/.git" ]; then
echo "eyrie already present at $dest"
exit 0
fi
git clone --depth=1 --branch "${{ inputs.ref }}" \
"https://github.com/GrayCodeAI/eyrie.git" "$dest"
2 changes: 1 addition & 1 deletion .github/actions/setup-deps/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ runs:
- name: Create workspace
shell: bash
run: |
printf 'go 1.26.1\n\nuse .\n\nreplace (\n\tgithub.com/GrayCodeAI/eyrie => ../eyrie\n\tgithub.com/GrayCodeAI/tok => ../tok\n\tgithub.com/GrayCodeAI/yaad => ../yaad\n\tgithub.com/GrayCodeAI/inspect => ../inspect\n\tgithub.com/GrayCodeAI/sight => ../sight\n)\n' > go.work
printf 'go 1.26.3\n\nuse (\n\t.\n\t../eyrie\n\t../tok\n\t../yaad\n\t../inspect\n\t../sight\n)\n' > go.work
38 changes: 12 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
Expand All @@ -56,22 +54,21 @@ jobs:
fi

# -------------------------------------------------------------------------
# 2. Module hygiene — tidy, verify (Herm-style: submodule + go.work, no go.mod replace).
# 2. Module hygiene — tidy, verify (hawk + sibling eyrie via go.work + go.mod replace).
# -------------------------------------------------------------------------
module:
name: module hygiene
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: ./.github/actions/checkout-eyrie
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: go work sync + module consistency
run: |
# Herm uses submodule + go.work only (no go.mod replace). go mod tidy can mis-resolve
# Eyrie is a sibling checkout (go.work + replace ../eyrie). go mod tidy can mis-resolve
# workspace modules here; go work sync is the supported workspace hygiene step.
go work sync
go build -mod=readonly -o /dev/null .
Expand All @@ -82,10 +79,10 @@ jobs:
fi
- name: go mod verify
run: go mod verify
- name: no replace directives in go.mod
- name: eyrie replace points at sibling
run: |
if grep -qE '^\s*replace\s' go.mod; then
echo "::error::go.mod must not use replace (Eyrie comes from submodule + go.work; see Herm / LangDAG)."
if ! grep -qE 'replace github\.com/GrayCodeAI/eyrie => \.\./eyrie' go.mod; then
echo "::error::go.mod must replace eyrie with ../eyrie (sibling checkout)."
grep -nE '^\s*replace\s' go.mod || true
exit 1
fi
Expand All @@ -98,8 +95,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: ./.github/actions/checkout-eyrie
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
Expand All @@ -116,8 +112,7 @@ jobs:
needs: [format, vet]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: ./.github/actions/checkout-eyrie
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
Expand All @@ -136,8 +131,7 @@ jobs:
needs: [format, vet]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: ./.github/actions/checkout-eyrie
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
Expand Down Expand Up @@ -171,8 +165,7 @@ jobs:
needs: [format, vet]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: ./.github/actions/checkout-eyrie
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
Expand All @@ -194,8 +187,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: trufflesecurity/trufflehog@0fa069c12f0c7baf431041cd1e564a9c5058846c # main 2026-05-18
with:
extra_args: --only-verified
Expand All @@ -209,8 +200,6 @@ jobs:
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0

# -------------------------------------------------------------------------
Expand All @@ -221,12 +210,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- name: Run markdownlint-cli2
run: |
npm install -g markdownlint-cli2
printf '%s\n' '{"ignores":["external/**"],"config":{"default":true,"line-length":false,"no-inline-html":false,"first-line-h1":false,"no-duplicate-heading":false,"no-emphasis-as-heading":false,"blanks-around-headings":false,"blanks-around-lists":false,"blanks-around-fences":false,"fenced-code-language":false,"table-column-style":false,"no-space-in-emphasis":false,"ol-prefix":false,"link-fragments":false,"blanks-around-tables":false,"table-column-count":false,"single-trailing-newline":false}}' > .markdownlint-cli2.jsonc
printf '%s\n' '{"config":{"default":true,"line-length":false,"no-inline-html":false,"first-line-h1":false,"no-duplicate-heading":false,"no-emphasis-as-heading":false,"blanks-around-headings":false,"blanks-around-lists":false,"blanks-around-fences":false,"fenced-code-language":false,"table-column-style":false,"no-space-in-emphasis":false,"ol-prefix":false,"link-fragments":false,"blanks-around-tables":false,"table-column-count":false,"single-trailing-newline":false}}' > .markdownlint-cli2.jsonc
markdownlint-cli2 '**/*.md'

# -------------------------------------------------------------------------
Expand All @@ -246,8 +233,7 @@ jobs:
goarch: arm64
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: ./.github/actions/checkout-eyrie
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # goreleaser needs full history for changelog
submodules: recursive

- uses: ./.github/actions/checkout-eyrie

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
Expand Down
3 changes: 0 additions & 3 deletions .gitmodules

This file was deleted.

28 changes: 23 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,28 +70,29 @@ go test -race ./... # Run all tests

| Module | In `go.mod` | In-repo checkout | Used from |
|--------|-------------|------------------|-----------|
| eyrie | ✓ | **`external/eyrie`** submodule + **`go.work`** | Provider client, setup, streaming |
| eyrie | ✓ | sibling **`../eyrie`** + **`go.work`** + **`replace` in `go.mod`** | Provider client, setup, streaming |
| sight | ✓ | proxy (optional local `replace`) | `hawk sight`, `internal/bridge/sight` |
| inspect | ✓ | proxy | Inspect bridges |
| tok | ✓ | proxy | Tokenizer pipeline |
| yaad | ✓ | proxy | Memory bridge |
| trace | — | separate **`trace` CLI** | Session capture only; not a Go import |

**Eyrie submodule** (Herm / LangDAG-style):
**Eyrie sibling checkout** (hawk + eyrie):

```bash
git submodule update --init --recursive
# hawk-eco layout: clone eyrie next to hawk, then:
cd hawk && go work sync
```

Committed **`go.work`** lists `.` and **`./external/eyrie`** only. **`go.mod` must not contain `replace` directives** for Eyrie (CI enforces this).
Committed **`go.work`** lists `.` and **`../eyrie`**. **`go.mod`** includes **`replace github.com/GrayCodeAI/eyrie => ../eyrie`** (CI enforces this path).

**`shared/types`** forwards **`internal/types`** for **sight**, **inspect**, **tok**, and friends so they never import hawk `internal/` directly.

For sibling clones on one machine, use a **personal** parent **`go.work`** or temporary **`replace`** — do not commit those **`replace`** lines into **`go.mod`**.

### CI

- Checkout uses **`submodules: recursive`** so `external/eyrie` is populated
- CI clones **eyrie** to **`../eyrie`** via **`.github/actions/checkout-eyrie`**
- Module hygiene: **`go work sync`** and **`go build -mod=readonly`** (not `go mod tidy`, which mis-resolves workspace Eyrie)
- golangci-lint with errcheck, staticcheck, gosec, unused, misspell
- Multi-platform builds (linux/darwin/windows × amd64/arm64)
Expand All @@ -105,3 +106,20 @@ For sibling clones on one machine, use a **personal** parent **`go.work`** or te
- Landlock: filesystem access restrictions
- seccomp-bpf: blocks 21 dangerous syscalls
- Fallback: no-op on non-Linux (`internal/sandbox/landlock_other.go`)

## Milestone: API key → model → sandbox

Active branch: **`feature/secure-credentials-sandbox`** (hawk + eyrie sibling).

| Concern | Where |
|---------|--------|
| First-run `/config`, setup guards | `internal/config/setup_status.go`, `cmd/chat.go` |
| Keychain + `PersistAPIKey` | `internal/config/credentials_store.go`, eyrie `credentials/` |
| Catalog discover + routing only on disk | `internal/config/eyrie_apply.go`, eyrie `setup/apply_credentials.go` |
| No API keys in `provider.json` | eyrie `SanitizeDeploymentConfigForDisk`, hawk `MigrateProviderSecrets` |
| Verification tests | `internal/config/milestone_verify_test.go`, `./scripts/verify-milestone.sh` |
| Plan + phase status | `plans/MILESTONE-api-key-model-sandbox.md` |

**Not in this milestone:** conversation DAG as source of truth, langdag Go import.

**`/sandbox` vs Docker:** `/sandbox` toggles **approval mode** in the TUI. **Docker container mode** is the default for bash (`shouldUseContainer`); use `--no-container` for host execution.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ hawk works with any LLM provider. Set your API key via environment variable or `
| Ollama | `OLLAMA_BASE_URL` (no key) |

Provider routing, model resolution, and retries are handled by [eyrie](https://github.com/GrayCodeAI/eyrie).
For deployment-aware routing, set `"deployment_routing": true` in `.hawk/settings.json`
or export `HAWK_DEPLOYMENT_ROUTING=true`. Hawk will route canonical model IDs through
Eyrie's deployment catalog, so new models can be exposed by refreshing the catalog
instead of changing Hawk. In chat, run `/refresh-model-catalog` to fetch the latest
deployment-aware catalog into `~/.eyrie/model_catalog.json`.

## Architecture

Expand Down Expand Up @@ -201,12 +206,12 @@ hawk/
hawk integrates these GrayCodeAI repos in three ways:

- **`go.mod` modules:** **eyrie**, **sight**, **inspect**, **tok**, **yaad** — pinned versions from the module proxy (same semver story across CI).
- **Submodule + `go.work`:** **eyrie** only — checked out under **`external/eyrie`** (`git submodule update --init --recursive`) so CI/builds always see the same Eyrie source layout as Herm-style repos.
- **Sibling + `go.work` + `replace`:** **eyrie** — clone [eyrie](https://github.com/GrayCodeAI/eyrie) next to hawk (`../eyrie`). `go.mod` uses `replace github.com/GrayCodeAI/eyrie => ../eyrie`. CI clones the same layout via **`.github/actions/checkout-eyrie`**.
- **Optional CLI (no Go import):** **trace** — installed separately; `hawk` shells into `trace` for session capture when present.

Cross-repo types (severity, etc.) are exported from **`github.com/GrayCodeAI/hawk/shared/types`** so **sight** / **inspect** / **tok** do not import **`internal/`**.

You may keep a **personal** parent **`go.work`** that lists sibling clones on disk (`../sight`, …); nothing besides **`external/eyrie`** is committed as a submodule in hawk.
You may keep a **personal** parent **`go.work`** that lists sibling clones on disk (`../sight`, …) for multi-repo development.

| Component | Repository | Purpose |
|---|---|---|
Expand Down
43 changes: 43 additions & 0 deletions cmd/catalog_startup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package cmd

import (
"context"
"os"

hawkconfig "github.com/GrayCodeAI/hawk/internal/config"
"github.com/GrayCodeAI/hawk/internal/onboarding"
)

var (
refreshCatalogFlag bool
skipCatalogRefreshFlag bool
)

func ensureFirstRunSetup() error {
if !onboarding.NeedsSetup() {
return nil
}
onboarding.Welcome(version)
return onboarding.RunSetup()
}

func ensureCatalogBeforeAgent(ctx context.Context, strict bool) error {
_ = hawkconfig.MigrateProviderConfig()
opts := hawkconfig.CatalogStartupOptions{
ForceRefresh: refreshCatalogFlag,
SkipAutoRefresh: skipCatalogRefreshFlag,
VerboseOutput: refreshCatalogFlag,
}
if strict {
return hawkconfig.PrepareCatalogForSession(ctx, os.Stderr, opts)
}
hawkconfig.StartupCatalogPrefetch(ctx)
return nil
}

func startBackgroundCatalogRefresh(ctx context.Context) {
if skipCatalogRefreshFlag {
return
}
hawkconfig.ScheduleBackgroundCatalogRefresh(ctx)
}
Loading
Loading