Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ddefcb7
fix(startup): declare configured repos before listening
zzet Oct 28, 2026
99e6caa
test(startup): assert loading instructions on first initialize
zzet Oct 28, 2026
70b0b3d
test(startup): cover file-save events and real cold-track failure
zzet Oct 28, 2026
33d8735
test(startup): register CLI fixtures through production tracking
zzet Oct 28, 2026
28d7ed1
test(startup): register MCP path fixtures through production tracking
zzet Oct 28, 2026
14aa44f
test(startup): register MCP scope fixtures through tracking
zzet Oct 28, 2026
837fcc2
test(startup): pass complete entries to fixture registration
zzet Oct 28, 2026
1c06f89
test(startup): finalize contract fixtures through production registra…
zzet Oct 28, 2026
64a6909
test(startup): build retrieval fixtures through registered repositories
zzet Oct 28, 2026
ad94bd8
test(startup): seed mutation and worktree fixtures through tracking
zzet Oct 28, 2026
ef8376e
refactor(startup): remove obsolete production batch indexing path
zzet Oct 28, 2026
d10e885
test(startup): bound nonterminal CLI loading over the real socket
zzet Oct 28, 2026
ff19a80
test(startup): cover registration retry for 24 checkout coordinators
zzet Oct 28, 2026
7251b34
test(startup): exercise MCP through a held SQLite migration
zzet Oct 28, 2026
523311a
test(startup): migrate remaining scoped MCP fixture registration
zzet Oct 28, 2026
be65c4c
test(startup): restore the owned migration fixture in MCP helper
zzet Oct 28, 2026
a812639
test(startup): isolate migration proxy from path alias coverage
zzet Oct 28, 2026
cfc6738
fix(indexer): publish error-aware configured identities outside lock
zzet Oct 28, 2026
c418576
fix(indexer): retain identity uncertainty across registration races
zzet Oct 28, 2026
d95fddd
fix(startup): defer negative admission until configured identities re…
zzet Oct 28, 2026
32b7bff
fix(startup): keep unbound diagnostics scoped and fence stale identit…
zzet Oct 28, 2026
68f5e5d
test(startup): preserve registered checkout alias admission during id…
zzet Oct 28, 2026
2e2d742
test(startup): distinguish unresolved from proven linked-family loading
zzet Oct 28, 2026
5659be9
test(startup): verify generic loading until socket identity owner com…
zzet Oct 28, 2026
2d7f63c
fix(indexer): classify final startup admission from one snapshot
zzet Oct 28, 2026
239e3b9
fix(startup): recheck published ownership before negative admission
zzet Oct 28, 2026
ba776aa
fix(cli): keep newly published scope admission retryable without trac…
zzet Oct 28, 2026
ed102df
fix(indexer): preserve pending family priority in final admission
zzet Oct 28, 2026
55daec0
fix(startup): preserve verified family discovery classification after…
zzet Oct 28, 2026
cc9bdd6
test: remove obsolete coordinated bulk capability checklist entry
zzet Oct 28, 2026
0a99042
test: guard the Darwin temporary directory alias before probing it
zzet Oct 28, 2026
8cd1c1e
test: guard the Darwin repository identity alias fixture
zzet Oct 28, 2026
da10f2c
Merge remote-tracking branch 'origin/main' into fix/startup-repo-load…
zzet Oct 31, 2026
252b9d2
test(startup): register the janitor ownership fixture through tracking
zzet Oct 31, 2026
ac0447a
feat(bench): probe startup admission from before the socket exists
zzet Oct 31, 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
38 changes: 38 additions & 0 deletions bench/load/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,44 @@ index; queryability and enrichment are separate observed upper bounds with a
recorded polling interval. Older daemons without `loaded_roots` retain their
legacy receipt and must still pass source-backed per-root checks.

## Startup admission

`--scenario startup` (never part of `all`) measures what agents see while a
daemon starts. Every manifest repository is configured; `--worktrees` linked
checkouts of the first one are discovered through it. Probing starts before
the first daemon start, so the window before the socket exists is observed
too. `--seed-store <file>` copies a store (for example one written by an
older schema) into the private run first, which makes the initial boot an
upgrade; the source is only read. `--restarts N` adds N warm restarts. Each
boot lasts until every configured repository is ready and enriched, plus
`--duration`.

Every second, from the primary, the first linked worktree, and a directory
whose `.gortex.yaml` names a workspace that a private `servers.toml` roster
claims, the probe opens fresh sessions: `initialize` (the instructions
variant is recorded), `search_symbols` and `resources/read
gortex://index-health` on the legacy surface, and `workspace(operation:
"index")` on the compact one. The roster's only server never listens, so a
routed call falls back to the daemon itself. From the same directories it
asks the PreToolUse hook about a native Read every second, and runs `gortex
call search_symbols` through a pipe (from the start) and through a pty
(`script(1)`, once the socket answers), each until its first success in that
boot. Every answer keeps `error_code`, `retryable`, phase, percent, latency,
exit code, and whether any refusal text advises `gortex track` or
`gortex repos reconcile`. A watcher polls the readiness probe every 100 ms
and, for 2 s after the primary registers, the checkout coordinator count.

`startup-probe.jsonl` holds every answer. The phase gate `startup_probe`
counts answers per surface and directory in three windows (no socket,
loading, ready) and records each boot's time to ready and time to first
success. Headline metrics include `startup_repo_not_tracked_before_ready`,
`startup_nonconforming_loading_refusals`, the track-advice and hook-deny
counts, `startup_restart_ready_to_first_ok_p95_ms`,
`startup_restart_time_to_ready_p95_ms`, `startup_initial_time_to_ready_ms`,
`startup_initial_first_ok_ms` and `startup_coordinators_within_2s_min`.
Restart figures are unavailable unless every restart produced them, and
`delta` compares two startup reports like any other.

## Measurements and artifacts

One JSON file contains per-operation successful p50/p95/p99/max, successful
Expand Down
8 changes: 7 additions & 1 deletion bench/load/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ func readWireLine(reader *bufio.Reader) ([]byte, error) {
}

func dialSocket(ctx context.Context, socket, cwd, mode string) (net.Conn, *bufio.Reader, string, error) {
return dialSocketSurface(ctx, socket, cwd, mode, "facade-v1", "hide")
}

// dialSocketSurface negotiates the session's tool surface in the handshake,
// as the CLI does per connection; the load workload always uses the facade.
func dialSocketSurface(ctx context.Context, socket, cwd, mode, tools, toolsMode string) (net.Conn, *bufio.Reader, string, error) {
conn, err := (&net.Dialer{}).DialContext(ctx, "unix", socket)
if err != nil {
return nil, nil, "", err
Expand All @@ -80,7 +86,7 @@ func dialSocket(ctx context.Context, socket, cwd, mode string) (net.Conn, *bufio
}
stop := context.AfterFunc(ctx, func() { _ = conn.Close() })
defer stop()
handshake := map[string]any{"version": 1, "mode": mode, "cwd": cwd, "client": "gortex-load", "tools": "facade-v1", "tools_mode": "hide"}
handshake := map[string]any{"version": 1, "mode": mode, "cwd": cwd, "client": "gortex-load", "tools": tools, "tools_mode": toolsMode}
if err := json.NewEncoder(conn).Encode(handshake); err != nil {
_ = conn.Close()
return nil, nil, "", err
Expand Down
1 change: 1 addition & 0 deletions bench/load/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ func validateAgentCounts(scenario string, readers, editors int) error {
if readers < 1 {
return errors.New("zero pure readers are supported only for edit/many-edit")
}
case startupScenario: // probes only; reader and editor counts are unused
default:
return fmt.Errorf("unknown scenario %q", scenario)
}
Expand Down
8 changes: 8 additions & 0 deletions bench/load/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type runOptions struct {
KeepState bool
SweepRepetitions int
SweepBudget time.Duration
Startup startupOptions
pairedDriver func(context.Context, *privateRun, []*socketClient, []*socketClient, *Phase) error
}

Expand Down Expand Up @@ -71,6 +72,7 @@ func bindRunFlags(f *flag.FlagSet, o *runOptions) {
f.BoolVar(&o.KeepState, "keep-state", false, "retain private corpus/store for native diagnostics only (default removes completed scenario state)")
f.IntVar(&o.SweepRepetitions, "sweep-repetitions", 15, "samples per benchmark operation (smoke may explicitly use 1)")
f.DurationVar(&o.SweepBudget, "sweep-duration", 10*time.Minute, "separate total budget for the repeated paced sweep")
bindStartupFlags(f, &o.Startup)
}

func validateRunOptions(o runOptions) error {
Expand All @@ -80,6 +82,9 @@ func validateRunOptions(o runOptions) error {
if err := validateAgentCounts(o.Scenario, o.Agents, o.Editors); err != nil {
return err
}
if err := o.Startup.validate(o.Scenario); err != nil {
return err
}
if _, err := parseProfile(o.Profile); err != nil {
return err
}
Expand Down Expand Up @@ -111,6 +116,9 @@ func validateRunOptions(o runOptions) error {

func selectedScenarios(o runOptions) ([]string, error) {
all := []string{"read", "edit", "many", "many-edit", "worktrees", "cold", "restart", "sweep"}
if o.Scenario == startupScenario {
return []string{startupScenario}, nil
}
if o.Scenario == "all" {
if o.Host == "floor" {
return []string{"read", "edit", "worktrees", "cold", "restart", "sweep"}, nil
Expand Down
3 changes: 3 additions & 0 deletions bench/load/scenario.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (
)

func runPhase(ctx context.Context, o runOptions, scenario string, corpora []Corpus, group, outputRoot string) (phase Phase, sweep []SweepEntry, artifacts map[string]string, crashes map[string]int, version string, resultErr error) {
if scenario == startupScenario {
return runStartupPhase(ctx, o, corpora, outputRoot)
}
phase = Phase{Name: scenario, SessionModel: "per_agent", Started: time.Now().UTC(), Gate: make(map[string]json.RawMessage), Metrics: make(map[string]Metric), EditLatency: distribution(nil, "ms"), Visibility: distribution(nil, "ms")}
artifacts, crashes = make(map[string]string), make(map[string]int)
var scenarioGroupStart map[string]Metric
Expand Down
13 changes: 12 additions & 1 deletion bench/load/selftest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ func fixtureDaemon(mode string) int {
fmt.Fprintln(os.Stderr, "panic: synthetic daemon crash")
return 2
}
// The startup probe runs the CLI and the hook through the same binary;
// answer them as against a ready daemon instead of starting another one.
for _, arg := range os.Args[1:] {
switch arg {
case "call":
fmt.Println(`{"results":[]}`)
return 0
case "hook":
return 0
}
}
data, err := os.ReadFile(filepath.Join(os.Getenv("XDG_CONFIG_HOME"), "gortex", "config.yaml"))
if err != nil {
return 3
Expand Down Expand Up @@ -80,7 +91,7 @@ func fixtureDaemon(mode string) int {
}
result = map[string]any{"ready": true, "enriched": true, "tracked_repos": repos, "loaded_roots": roots}
case "status":
result = map[string]any{"ready": true, "enriched": true, "views": map[string]any{}, "storage": map[string]any{"wal_pending_frames": 0}}
result = map[string]any{"ready": true, "enriched": true, "views": map[string]any{"coordinators": 1}, "storage": map[string]any{"wal_pending_frames": 0}}
case "shutdown":
result = map[string]any{"stopping": true}
default:
Expand Down
Loading
Loading