npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dreamtree-org/conductor

v0.3.0

Published

Collision control for parallel AI agent sessions sharing one working tree.

Readme

@dreamtree-org/conductor

Collision control for parallel Claude Code agents sharing one working tree.

When two Claude Code agents in two terminals edit the same repo, two failure modes follow: they clobber each other on the same file, or they edit dependency-linked files that each "succeeds" alone but breaks the build together. Conductor makes both visible. Each agent's working scope is recorded in a shared append-only ledger; a Claude Code PreToolUse hook gates every file mutation against it. Direct collisions are DENY'd outright. Dependency crossover — inferred from @dreamtree-org/graphify's knowledge graph — is escalated to the human through Claude Code's own permission prompt (ASK), because it's a judgment call.

Honest guarantee — read this first

Conductor gates tool-mediated file writes. The Bash escape hatch is wide open: sed -i, python - <<EOF, git checkout, mv all write files ungated. There is no way to gate them without parsing shell strings, which is the wrong fight. If an agent really wants to circumvent Conductor it can — the README says so in the first paragraph because honesty about that limit is the whole point. The provided guarantee is no tool-mediated collision through Write/Edit/MultiEdit/NotebookEdit, which is the common case and the source of most silent clobbering.

Install

# Inside the repo, with @dreamtree-org/conductor already in your dev deps:
npx conductor init
# Or if published: npm install --save-dev @dreamtree-org/conductor && npx conductor init

init writes:

  • .conductor/config.json — team-shared config (TTLs, strict paths, tier 2 toggle), tracked in git.
  • .conductor/hook.mjs — a ~20-line committed shim that require.resolves the installed implementation and exits 0 silently if Conductor isn't installed. Never npx — measured: node -e 0 is 40 ms, the hook bundle is ~8 ms, npx --offline is 970 ms. A hook on every edit cannot pay 970 ms.
  • .conductor/.gitignore — ignores the ledger, agent files, footprint sidecars, locks; keeps config.json + hook.mjs tracked.
  • Upserts conductor's hooks in .claude/settings.json (SessionStart / PreToolUse / SessionEnd), preserving every foreign key — graphify's, itprocurement's async/asyncRewake, devtools' broken hook-guard group.

Then restart Claude Code. Hooks load at SessionStart, so an init mid-session appears to do nothing until the next session begins.

Usage

# Take a scope up front — make your intent visible to other agents:
conductor claim "src/auth/**" --intent "refactoring session store"

# What's currently held?
conductor status

# Hand a claim back:
conductor release <claim-id>
conductor release --all                # every auto-claim this session holds
conductor steal src/auth/session.ts --intent "handoff"   # the deny-message primitive

# Tier 2 escalations (footprint crossovers, strictPaths asks):
conductor pending
conductor approve <escalation-id>
conductor deny <escalation-id> --reason "..."

# Repair: ledger growth, foreign PreToolUse collisions, fail-opens, stale locks:
conductor doctor
conductor doctor --fix   # reap stale locks + stale-released claims

You don't always need conductor claim. Writing a file nobody has claimed silently claims it for you (auto-claim). Auto-claims are shorter-TTL'd (15 min sliding vs 4 h explicit), released on SessionEnd, and visually distinguished in status. One incidental Write claims a file for the session — that's the design choice that makes invisible ownership visible, not a strict requirement to declare scopes up front.

How the gate decides

0. outside the repo root                        → ALLOW  (ignored)
1. matches config.ignore                        → ALLOW  (ignored)
2. active := live claims in this workspace
3. MY claim matches path                        → ALLOW  (tier:'own')  ← precedes every
                                                                    collision check, or
                                                                    an agent blocks itself
4. TIER 1 — another agent's scope.paths matches → DENY   (tier:1)         fail-CLOSED
5. TIER 2 — path ∈ another agent's footprint,
     and no approved escalation covers it        → ASK    (tier:2)         NEVER deny
6. matches config.strictPaths                    → ASK    (tier:'strict')
7. genuinely unclaimed                           → ALLOW  (tier:0)         + auto-claim

Two rules generate the ordering. Uncertainty allows; certainty of collision blocks. A known tier-1 clash is fail-closed because it's a known collision. Tier-2 footprints are inference over a graph that may be stale (graphify's incremental updateCorpusGraph skips resolveCrossFileReferences, so cross-file edges can point at guessed ids) — so tier 2 is always ASK, never DENY. Loose relaxes "must I declare up front", never "may I stomp on someone" — auto-claim only happens after every collision check has passed. A lock-free read can be more permissive than reality; the auto-claim path closes that gap by re-reading under the append lock before writing.

Configuration reference

.conductor/config.json is tracked in git and shared across all agents. Every field has an env override so a single pane can deviate without editing the shared file. A malformed config falls back to defaults rather than wedging every agent in the repo.

Core settings

| Field | Type | Default | Description | |---|---|---|---| | strictPaths | string[] | [] | Paths that require a claim up front. Writing these without a claim triggers an ASK. | | ignore | string[] | 13 globs (see below) | Paths the hook ignores entirely — never gated, never auto-claimed. If present in the file, replaces the defaults; omit to keep defaults. | | ttlMinutes | number | 240 | TTL for explicit claims (conductor claim). 4 hours by default. | | autoTtlMinutes | number | 15 | TTL for auto-claims (the implicit claim from first Write). 15 minutes sliding. | | heartbeatIntervalSeconds | number | 120 | Interval at which heartbeats refresh active claims. Amortized onto the allow-own path. | | inlineFootprintMax | number | 200 | Max footprint entries stored inline in the claim event. Larger footprints spill to sidecar files in .conductor/footprints/. | | caseSensitivePaths | boolean | true | Path comparison mode for scope matching and footprint resolution. | | graphifyOutDir | string | "graphify-out" | Where graphify keeps its graph, repo-relative. |

Default ignore patterns: .conductor/**, graphify-out/**, node_modules/**, .git/**, dist/**, build/**, coverage/**, .next/**, **/*.lock, package-lock.json, pnpm-lock.yaml, yarn.lock.

Tier 2 (graphify) settings

Under "tier2": { ... } in config.json:

| Field | Type | Default | Description | |---|---|---|---| | enabled | boolean | true | Whether tier-2 footprint ASK is active. Disable to run tier 1 only. | | maxDepth | number | 2 | BFS depth in each direction (upstream + downstream). 2 is plenty; 3 turns most footprints into the whole repo. | | limit | number | 200 | Node cap per direction in the BFS walk. | | maxGlobFiles | number | 200 | If a claim's globs match more graph file-nodes than this, the footprint is marked truncated and falls back to tier 1 only. Without this cap, src/** produces a repo-sized footprint. | | maxAgeHours | number | 24 | Graph older than this is used anyway but marked stale — the classifier shows a warning in the ASK prompt. |

Example config.json

{
  "ttlMinutes": 120,
  "autoTtlMinutes": 10,
  "strictPaths": ["src/shared/**", "src/types.ts"],
  "tier2": {
    "enabled": true,
    "maxDepth": 2,
    "limit": 150
  }
}

Environment variables

All env overrides win over config.json. Pass them on the claude invocation line so hooks inherit them:

| Variable | Effect | |---|---| | CONDUCTOR_TTL_MINUTES | Overrides ttlMinutes | | CONDUCTOR_AUTO_TTL_MINUTES | Overrides autoTtlMinutes | | CONDUCTOR_TIER2 | Overrides tier2.enabled. Truthy: 1, true, on, yes. Falsy: 0, false, off, no. | | CONDUCTOR_STRICT_PATHS | Comma-separated list, overrides strictPaths | | CONDUCTOR_AGENT | Human-readable label for the agent (shown in conductor status). CONDUCTOR_AGENT=auth-refactor claude | | CONDUCTOR | Set to off to disable the hook for this invocation | | CONDUCTOR_HOOKS | Same as CONDUCTOR=off (legacy alias) |

Opt-outs

Three levels, in order of precedence:

  1. Per-invocation: prefix a claude command with CONDUCTOR=off (or CONDUCTOR_HOOKS=off)
  2. Per-clone: touch .conductor/disabled — survives across sessions until removed
  3. Per-repo: remove the hooks key from .claude/settings.json — the hook shim will still be present but never invoked

A tool that can't be turned off gets ripped out. Every level is a deliberate escape hatch.

.conductor/ directory layout

| Path | Tracked in git? | Purpose | |---|---|---| | config.json | Yes | Team-shared configuration | | hook.mjs | Yes | ~20-line shim that loads the installed hook implementation | | .gitignore | Yes | Ignores runtime files, keeps config + shim tracked | | ledger.jsonl | No | Append-only JSONL ledger of all claim/release/heartbeat/footprint/escalation events | | .lock/ | No | Advisory append lock (directory-based, mkdir O_EXCL) | | agents/*.json | No | Agent identity files, one per session | | footprints/*.json | No | Footprint sidecar files for claims with large blast radii | | conductor.log | No | Durable observability log | | disabled | No | Marker file — if present, the hook is disabled (per-clone opt-out) |

The dead-lock-free guarantee

Every read is lock-free; only first-touch of a new file takes the lock, and only briefly. The lock is a directory (mkdir with O_EXCL semantics on every filesystem, zero deps).

TTL & heartbeat

SessionEnd does not fire on kill -9 (verified against claude-agent-sdk v0.3.177 — ExitReason covers only clear/resume/logout/...), so TTL + heartbeat is the primary liveness mechanism:

| Claim type | TTL | Created by | Released by | |---|---|---|---| | Explicit | 4 hours (configurable) | conductor claim | conductor release, SessionEnd (clean exit), or TTL expiry | | Auto-claim | 15 minutes sliding (configurable) | First Write to an unclaimed file | SessionEnd (clean exit), or TTL expiry |

Heartbeats refresh the lastSeenMs on every claim this session holds, amortized onto the allow-own path — so an actively editing agent never ages out mid-session. Two writers racing the same unclaimed file serialize on the lock — one wins, the other re-classifies into tier-1 DENY.

A kill -9'd terminal leaves auto-claims alive for up to 15 min and explicit claims for up to 4 hours. Run conductor doctor --fix to reap them immediately. Stale locks (dead pid, impossibly old started_at) are reaped automatically at 30s; doctor --fix reaps them manually.

Troubleshooting

"Hook not firing"

  1. Is Conductor installed? npm ls @dreamtree-org/conductor
  2. Did you restart Claude Code after conductor init? Hooks load at SessionStart.
  3. Is the hook shim intact? Check .conductor/hook.mjs — it should be a ~20-line file.
  4. Is it disabled? Check for CONDUCTOR=off in env, or .conductor/disabled marker.
  5. Run conductor doctor — it reports foreign PreToolUse collisions (another hook might be intercepting first).

Stuck claims after crash

When an agent session is killed (kill -9, terminal closed, SSH disconnect), its claims survive until TTL expiry. Run:

conductor doctor --fix   # reaps stale locks + stale-released claims
conductor status          # see what's still live

Use --fix to immediately release claims from dead sessions (TTL-expired auto-claims and explicit claims) and remove stale lock directories.

Stale locks

Lock directories at .conductor/.lock/ contain pid and started_at files. If the owning process is dead (detected via kill(pid, 0)ESRCH) or the lock is impossibly old, the lock is reaped automatically at 30s. doctor --fix reaps them manually on demand.

Ledger bloat

The ledger is append-only; every claim, release, heartbeat, footprint, and escalation adds a line. Over weeks of active use this can grow large. The fold reads the full ledger on every edit, so check the byte count:

wc -c .conductor/ledger.jsonl

If it's >10 MB, consider an archival cycle: move ledger.jsonl to ledger-archive-$(date).jsonl and start a fresh ledger. This is a manual step for now; see conductor reset (planned).

"graphify not installed" warnings

If graphify isn't installed (@dreamtree-org/graphify optional peer dep), every claim will have footprintStatus: 'no-graph' and tier 2 is a no-op. Direct collision detection (tier 1 DENY) still works. Install graphify to enable dependency-crossover ASK:

npm install --save-dev @dreamtree-org/graphify
# Then run graphify to produce graphify-out/graph.json

Programmatic API

Conductor can be imported directly — the pure functions are useful for building tooling around the ledger:

import {
  // Classification
  classify, compileScope,
  // Ledger
  foldLedger, activeClaims, readLedger, appendEvents, makeClaim, makeRelease,
  // Lock
  acquireLock, withLock,
  // Config
  loadConfig, defaultConfig, isDisabled,
  // Workspace
  resolveWorkspace, workspaceId,
  // Identity
  resolveAgentIdentity, readAgentIdentity,
} from '@dreamtree-org/conductor';

// Graph operations (separate subpath):
import {
  loadGraphSnapshot, computeFootprint, forwardClosure, decideFootprintCarrier,
} from '@dreamtree-org/conductor/graph';
import type { ReadonlyGraph, GraphSnapshot, FootprintResult } from '@dreamtree-org/conductor/graph';

Key categories:

| Category | Exports | Description | |---|---|---| | classify | classify, compileScope, isGlob | The three-tier overlap engine (pure, injectable) | | ledger | foldLedger, activeClaims, readLedger, appendEvents, makeClaim, makeRelease, makeHeartbeat, makeFootprint, makeEscalation, makeDecision | Ledger fold, read, write, identity helpers | | lock | acquireLock, reapStaleLock, withLock | Advisory append lock (directory-based, zero deps) | | config | loadConfig, defaultConfig, isDisabled, ttlMs, autoTtlMs | Config load, merge with env, opt-out checks | | graph | loadGraphSnapshot, computeFootprint, forwardClosure, decideFootprintCarrier | Graphify bridge + blast-radius BFS (import from @dreamtree-org/conductor/graph) | | workspace | resolveWorkspace, workspaceId, currentBranch | Worktree root discovery + stable workspace hashing | | identity | resolveAgentIdentity, readAgentIdentity, fallbackIdentity | Agent label resolution (4-tier priority) | | hooks | runHook, isHookEvent | Hook harness entry point (import from @dreamtree-org/conductor/hooks) |

The full library exports ~60 symbols from the main entry. A proper API reference is planned for the VitePress docs site.

Performance

  • Hook hot path: ~8 ms after node startup (~40 ms). Bundle is a single ~100 KB ESM file.
  • Lock-free reads: every classification reads the ledger without taking a lock. Only the auto-claim append locks briefly.
  • Lock mechanism: mkdir with O_EXCL semantics, zero dependencies, works on every filesystem.
  • Graphify stays off the hot path: the ~330 ms await import('@dreamtree-org/graphify') cost is paid only in the detached footprint worker spawned at claim time, never during PreToolUse. The hook bundle is regression-tested in CI to never contain @dreamtree-org/graphify or commander.
  • No shared chunks: tsup bundles each entry independently. The few KB of duplication across index / cli / hook / graph entries is cheaper than a second module resolution on every edit.

Phase status

  • Phase 0 (collision control via Write/Edit/MultiEdit/NotebookEdit): shipped. Pure ledger + classifier + lock + auto-claim + TTL filtering + init/claim/release/status/steal/doctor and the PreToolUse/SessionStart/SessionEnd hooks.
  • Phase 1 (graphify-backed Tier 2 ASK + escalation workflow): shipped alongside as an optional peer dep. If graphify is installed (graphify-out/graph.json on disk) conductor binds to it; footprints are computed at claim time, snapshotted into the claim event (or a sidecar over inlineFootprintMax), and used to ASK when an edit lands inside another agent's dependency blast radius. If graphify isn't installed, every claim is footprintStatus: 'no-graph' and tier 2 is a no-op — DENY still fires on direct collisions so the core guarantee is unaffected.

Verification

npm run lint && npm run typecheck && npm test && npm run test:race && npm run build is the CI ladder. test:race runs the hammer fixture: 8 parallel sessions x 20 iterations each against one contested path; the lock-guarded re-classify must produce exactly one winner and DENY for everyone else, no corrupt ledger line, no orphan lock, skipped === 0. The tests/build-shape.test.ts regression enforces the latency budget: dist/hooks/run.js must not contain the strings commander or @dreamtree-org/graphify (the hook bundle stays a single ~100 KB file; the graphify barrel alone drags in @anthropic-ai/sdk, @modelcontextprotocol/sdk, mysql2, web-tree-sitter).

Related

  • DreamtreeTech/graphify — the knowledge graph Conductor's tier 2 reads. Independent tools; both can be installed in the same repo.
  • Claude Code's PreToolUse hook contracthook_event_name: 'PreToolUse', exit 0, JSON on stdout. Conductor's hook returns null on the allow path (silent; a permissionDecision: 'allow' skips the user's own permission prompt and can preempt another hook's deny — auto-approving every Write in the repo is a security regression nobody asked for).

License

MIT. See LICENSE.