Rules for AI coding agents working in this repository. Project introduction lives in README.md — this file is only about how to work here.
- Commit messages use Conventional Commits in English:
type(scope): subject(e.g.feat: add memory distiller consumer,fix(kernel): commit cursor after batch). - Keep subjects imperative and specific. One logical change per commit.
- Never commit secrets. Provider API keys live only in the user's
settings.json(mode 0600, underHIDANE_HOME) or the environment — never in the repo, the event log, logs, or any API response.
- One Go module at the root:
main.go(desktop app + CLI dispatch),gui_on.go/gui_off.go(build tagnoguidrops Wails and cgo),internal/*,cmd/fakeagent(test double for the agent CLIs). frontend/is the Svelte 5 SPA (the only pnpm workspace). Its build outputfrontend/distis embedded into the binary (frontend/assets.go).- Kernel code lives in
internal/kernel/— its invariants below apply there. Agent roles live ininternal/agents/, the CLI drivers ininternal/agentcli/, the capture-phase guard ininternal/guard/. docs/wails-migration.mdrecords why the Node/Postgres web version became this desktop app and the decisions real runs forced;docs/desktop-ux.mdrecords the desktop interaction decisions.- Before writing a helper or a component, look for the one that exists (gopls
go_symbol_references,rg): text clipping isinternal/clip, the answer and conversation event kinds arekernel.AnswerKinds/frontend/src/lib/kinds.ts, and how a task is shown (state dot, badge, branch line, question) is the set listed infrontend/DESIGN.md. Each was rebuilt in several places before it had one home.
- Go ≥ 1.26.8 (
gofmt,go vetclean). Node ≥ 24 with pnpm only for the frontend (never npm/yarn commands or lockfiles). - Wails v3 (
github.com/wailsapp/wails/v3, currentlyv3.0.0-beta.27); keep it on the latest release when bumping. - Prefer the latest stable version when adding a dependency; justify any pin. Prefer the standard library.
- SQLite through the pure-Go
modernc.org/sqlitedriver, so-tags noguibuilds stayCGO_ENABLED=0. - Frontend TypeScript strict mode is intentionally harsh (
exactOptionalPropertyTypes,noUncheckedIndexedAccess,verbatimModuleSyntax). Fix type errors properly; do not loosen tsconfig to make errors go away.
make test(go vet, also with-tags nogui;go test ./...;pnpm -C frontend check;pnpm -C frontend test) must pass before any commit. Unit tests guard kernel invariants; extend them when touchinginternal/kernel/.make e2eruns Playwright (chromium + webkit) against the real Go backend (hidane serve) withcmd/fakeagentstanding in for the CLIs.frontend/e2e/README.mdsays what each spec guards: extend the spec that covers what you change, and keep that table current when you add one.- After a UI change, run
make screenshotsand look at the affected pages (bin/screenshots/<page>-<zh|en>-<desktop|browser|phone>.png; desktop = the app's own chrome) — a diff does not show a broken layout. It fails on any uncaught page error. While iterating,make screenshots ONLY=run-as,settings-rolestakes only the pages whose names start with those prefixes; take the full set once before committing. make smoke-guilaunches the real Wails window; it must end withui ready (live transport: wails). Run it after touchinginternal/desktop,/boot.js, or the live transport.- After touching
scripts/package.sh,build/,.github/workflows/release.ymlor a platform-specific dependency, run.agents/skills/hidane-release/scripts/dry-run.sh: it builds every release artifact locally and opens the macOS and (in Docker) Linux packages. Windows is cross-compiled;GOOS=windows go vet ./...must stay clean (its dependencies live ingo.sumtoo). make smoke-live/make acceptancespend real tokens on the locally installed CLIs. The agent-driven acceptance (scripts/acceptance.shexecutingacceptance/scenarios.md) is the end-to-end source of truth: when behavior changes, update the natural-language scenarios — do not encode acceptance in assertion scripts.- Pay for verification by the size of the change — a full acceptance costs about an hour and real tokens:
- every change:
make test(andmake e2efor anything a person sees); CI runs them on every push, with the fakes and no tokens; - a driver, the guard, a charter or a role's context: the
hidane-live-checkskill (minutes, a few real calls); - behavior a scenario describes:
make acceptance ARGS="--changed origin/main"(only the scenarios the changed paths map to) orARGS="--only 4F,6E". Rebase onto the trunk first: the script refuses to start whilemainororigin/mainhas commits the branch lacks (--behind-okoverrides), since a run against code about to be rebased is paid for again; - the full
make acceptance: before a minor or major release (thehidane-releaseskill), or after a sweeping change.
- every change:
- Scenarios hold only what needs a real model, a real CLI, the OS, or judgement. An expectation a test can assert — a status code, an event row, a file, a validation error — goes into a Go or Playwright test instead, where CI checks it on every push and no tester pays to re-verify it. Before adding a bullet to a scenario, ask whether a test can assert it; if one can, write the test instead.
- A finding the acceptance makes more than once becomes a Go or Playwright test, so the next run need not rediscover it. Only a violated written expectation is a FAIL; everything else the tester notices is a note for the backlog, and a gateway error is retried, not a FAIL.
- The fakes prove protocol handling, not the real CLIs: after changing a driver, the guard, a charter or a role's context, follow the
hidane-live-checkskill (realclaude/codex/pi, freshHIDANE_HOME, judged against the files on disk — not the reply). - Tests are hermetic: a temp
HIDANE_HOME, fake CLIs by absolute path,HIDANE_LOGIN_SHELL=0for any spawnedhidane. A test must never read the developer's~/.hidane,~/.claude,~/.codex, keychain, or shell startup files, and never start a real agent CLI. - Verify claims with evidence: when you say something works, show the command output, file content, or event rows that prove it.
- Uncommon ports are a deliberate choice (serve 2718, Vite 2719, E2E 2797) — do not switch to 3000/8080-style defaults.
- The event log is append-only (enforced by triggers). Never UPDATE or DELETE rows in
events. Replay = reset a consumer cursor, never mutate history. - Events record facts, not thinking: state changes, boundary crossings (messages, side effects, user interaction), and decision outcomes. Model reasoning and raw tool I/O belong to traces referenced by
execution_id. - Write-through at occurrence time. No batch back-filling of events after the fact.
- Projections (daily worklog, board, indexes) are derived, rebuildable, read-only views over the log — never a second source of truth.
- Connectors capture and normalize only. They never judge, never call agents directly, never block on processing.
- Triage is rules-first; models wake rarely. Events from
agent:*/kernel:*sources must never re-enter triage (loop protection). - Side effects are two-phase:
side_effect.intentbefore the action,side_effect.resultafter. - The kernel stays domain-agnostic: work items, threads, executions, workspaces, artifacts. No software-engineering semantics in the kernel (no built-in CI/CD, dependency graphs, deploy pipelines).
- Memory must be able to expire. Distillation never records current bugs, missing paths, deployment breakage, or safety-bypassing workarounds — those get fixed, and the memory then actively misleads future runs (this happened: a "use
pi -neto skip the guard" lesson survived the fix and was injected into new tasks). Every promotion needs a matching removal path (hidane forget <id>,DELETE /api/memories/:id), and removals are recorded asmemory.forgotten. - Database vs files boundary: the database holds ONLY what needs atomic multi-writer ordering and queries — the event log spine plus small state tables (work_items, cursors, executions, schedules, channel_bindings, repos, checkouts). Everything agents or humans consume is FILES: layered memory (
memory/MEMORY.md,<workspace>/MEMORY.md), daily worklog + session-trace archives (worklogs/YYYY/MM/DD/), session traces, workspace artifacts,settings.json,POLICY.json. Never add a table for content that agents read — write a file projection instead; never make agents query the database directly. - Three agent roles (Primary / Manager / Worker) are one loop at three scopes. Differences live in charter, context, and lifecycle only. Do not fork per-role frameworks: Primary and Manager are turn handlers on the same
kernel.Runtimesharingthink()and the effect-list charter format. - A turn decides; it never waits. A turn handler must not await a worker execution or another agent's answer. Dispatch (
WorkerPool.Dispatch) and return; the outcome comes back as a message to the owner's mailbox. Anything held in memory across a wait is lost on restart. - Mailboxes are views, not tables. An agent's mailbox is the events with
mailbox = <address>after itsmailbox:<address>cursor. Deliver withKernel.Post(which also enforces the causal hop budget); never add a queue table or an in-process queue as the source of truth. - Every execution has an owner mailbox and is recorded in
executions; the only way an execution ends isreportOutcomepostingexecution.finishedto that owner (includinglostafter a restart andcancelled). Never leave an execution without someone to read its outcome. - Every person's message enters through
System.SubmitMessage(internal/agents/ingress.go) and lands on the main thread; every answer (kernel.AnswerKinds:agent.reply,escalation,attribution.ambiguous,agent.error,execution.steered) carriespayload.root— the UI groups by it. The frontend mirrors those lists infrontend/src/lib/kinds.ts; a Go test keeps the two equal. - Propagation across the work tree: bubbling kinds are declared in
kernel.Bubbles(tool traffic never bubbles); the capture phase isinternal/guard— the built-in deny list of very dangerous commands, the person's policy files and the pending-input pause, rules only, never a model call; nothing else is fenced; cancel flows down withCancelTree. - Skills are a shared global pool loaded by each CLI's native discovery. Do not build skill routing, scoping, or injection mechanisms.
- Every work item owns one workspace: where its agents start and put their products by default, not a fence. Its repositories are checkouts (
internal/repos): a git worktree inside the workspace onhidane/<wi>, or the person's own directory only when they ask (a model must quote their words; one task per repo at a time). - Repo and branch are never guessed: an ambiguous or missing repository is a question before anything is created. Only the person archives a worktree (teardown, directory removed, branch kept), and a directory git cannot vouch for is never deleted. Git mechanics stay in
internal/repos, out of the kernel.
- Every role is a CLI subprocess (
internal/agentcli), started in its own process group and killed as a group. The Primary, Managers and workers run with tools in the CLI's own bypass mode (claude --permission-mode bypassPermissions, codexsandbox_mode="danger-full-access", pi with the guard extension) behind the guard; do not add per-project permission layers. The distiller runs without tools — MCP servers from the person's own CLI config included (claude --tools "" --strict-mcp-config; codex with its tool features off and each server fromconfig/readdisabled inthread/start). - The Primary's and a Manager's charter replaces the CLI's system prompt (
--system-prompt; codexbaseInstructions) even with tools, so they answer with an effect list; a worker's is appended (codexdeveloper_instructions). - The Primary and the distiller start in an empty per-role directory under
runtime/roles/, a Manager in its work item's workspace, a worker in its first repository (else the workspace). - The guard is one Go implementation behind
hidane guard --format claude|codex|pi: a Claude Code PreToolUse hook via--settings, a Codex hook via-c hooks.PreToolUse=…oncodex app-serverplusconfig: {"bypass_hook_trust": true}inthread/start/thread/resume(app-server silently skips an untrusted command-line hook, and the key does nothing on its command line; the driver stops any run whose command or file change the guard never saw), and a pi extension shim written toruntime/pi-guard.ts. Never reimplement rules in TypeScript or per CLI. A policy file that exists but cannot be parsed refuses changes — it must never fail open. - Keys reach CLIs through the environment (
ANTHROPIC_AUTH_TOKEN,HIDANE_CODEX_API_KEY), never on a command line other local users can read; pi has no other way and gets--api-key. - Strip parent-session markers (
CLAUDECODE, …) from every CLI's environment; setPI_OFFLINE=1for pi. - pi's RPC mode is driven over stdin JSONL (
prompt,steer,abort,get_state); wait foragent_settled, notagent_end. Claude's stream-json emitsresultper turn while stdin stays open — close stdin when no queued turn remains. Codex runs ascodex app-server(one per run, closed by stdin EOF): answer every server request at once and tell them apart from replies first (they share the id space); steer withturn/steer+expectedTurnId+clientUserMessageIdand count the words taken in only when thatuserMessageitem (itsclientId) appears; a hook-refused call leaves no tool item. - A Finder-launched app has a minimal PATH: resolve the login shell's PATH (
agentcli.LoginShellPath) and allow absolute paths per CLI in settings.
- Prefer official SDKs over hand-rolled protocol code. The Feishu connector uses
github.com/larksuite/oapi-sdk-go/v3for token refresh, the long connection and dispatch — do not reintroduce hand-written crypto, token caching or a public callback endpoint. Same principle as skills (native discovery) and memory (files).
- The desktop webview reaches the API through the Wails asset server only; it needs no token and nothing listens on a port.
- In
hidane serve,/healthis the only open endpoint./api/*always requires a bearer token (HIDANE_API_TOKEN, or a random one generated and printed per run);/webhook/:namerequires anx-hidane-signatureHMAC-SHA256 and is refused outright whileHIDANE_WEBHOOK_SECRETis unset (a webhook wakes the Primary; an unsigned one is reachable by any web page's cross-origin POST). Never remove these gates or add unauthenticated write endpoints. Any endpoint that can start an execution is spending money and granting code execution. - Never treat a library option name as a guarantee on a security boundary. The old Node SDK's
EventDispatcher({ verificationToken })read like validation but returnedtrueoutright when no encrypt key was set —/feishu/eventswas open in production as a result. Read the source, or re-check the invariant yourself at the boundary. Preferring official SDKs (above) applies to protocol mechanics, not to trust decisions.
- Runtime knobs go through
internal/configenv vars; document new vars in the README environment table. Model and provider choice lives insettings.json(internal/settings) and the Settings page, never in env vars — a desktop app is configured from its own UI.
- Code identifiers, comments, and README are English. Acceptance scenarios and other operator-facing docs may be Chinese.
- Comments state constraints the code cannot express; no narrating what the next line does.
- UI strings go through the web i18n layer (
frontend/src/i18n/resources.tsplus its framework adapter): never hardcode user-visible literals in components. Supported locales arezh(default) anden— every new key must be added to BOTH, and keys are typo-checked at compile time via the typed resources declaration.
- This repository's project-local Codex configuration lives in
.codex/config.toml; do not modify~/.codex/config.tomlfor repository-specific behavior. - The runtime's agent-role skills remain each CLI's own pool described above; that is separate from coding-agent guidance. Shared repository coding-agent skills live in
.agents/skills/and must remain agent-neutral. Claude Code's.claude/skillsis only an adapter symlink to that directory. - Project MCP servers are declared in both
.codex/config.tomland.mcp.jsonso Codex and Claude Code get the same tools: svelte (Svelte docs and autofixer — required for.svelteedits) and gopls (the Go team's language server:go_diagnostics,go_symbol_references,go_rename_symbol,go_package_api,go_vulncheck, …). Prefer gopls over grep for "who calls this" and for renames across packages; rungo_diagnosticson files you edited. - Repository skills (
.agents/skills/):svelte-code-writerandsvelte-core-bestpracticesfor frontend work,hidane-live-checkfor verifying behavior on the real agent CLIs,hidane-local-buildfor pulling, building and installing a local macOS app,hidane-releasefor publishing a version (a pushedv*tag runs.github/workflows/release.yml; packaging isscripts/package.sh).
frontendis a Svelte 5 + Vite SPA. Do not introduce React, JSX, SvelteKit, or a router migration without explicit authorization.- When changing
frontend, also readfrontend/AGENTS.md; it is the more specific rule layer when an agent starts in that directory. - Every visible change follows the design system in
frontend/DESIGN.md(tokens infrontend/src/styles.css@theme, primitives infrontend/src/components/ui/, shared patterns infrontend/src/lib/styles.ts). Tailwind's default palette, shadows and radii are switched off; a missing value becomes a new token documented there, never an arbitrary value or literal color.test/design-system.test.tsenforces it. - Before editing Svelte files, use the project-local Svelte MCP/documentation workflow; after editing, run
pnpm -C frontend check. - Preserve the existing API, bearer-token, SSE / desktop live-frame, pending-state, pagination, notification, dark-theme, responsive-navigation, accessibility, and sanitized-Markdown contracts.
- Before handoff, run
pnpm -C frontend check,pnpm -C frontend test, andpnpm -C frontend buildin addition to the root checks.