Reply in English unless the user explicitly asks otherwise. Always talk in ASD-STE100 Simplified Technical English.
TypeScript monorepo for pythinker-code, a provider-agnostic AI coding agent. This file covers product identity, project map, hard constraints, and workflow rules.
pythinker-code plans, writes, tests, and iterates on code autonomously. The same runtime talks to any LLM through the packages/kosong abstraction layer.
| Wire type | SDK / transport | Providers |
|---|---|---|
anthropic |
@anthropic-ai/sdk |
Anthropic (Claude family) |
openai |
OpenAI Chat Completions | OpenAI (GPT-4o/4.1/4.5/5.x, GPT-3.5-turbo) |
openai_responses |
OpenAI Responses API | OpenAI (GPT-4.1/5.x, o-series) |
google-genai |
@google/genai |
Google (Gemini 2.0–3.x) |
vertexai |
Google Vertex AI | Google Cloud–hosted Gemini |
pythinker |
Pythinker-compatible API | PyModel, Kimi, and custom compatible endpoints |
Any OpenAI-compatible endpoint (DeepSeek, Qwen, GLM, Grok, Together AI, Fireworks, etc.) works via the openai/openai_responses wire with a custom baseURL.
Flows through the catalog (catalog.ts):
- JSON catalog maps
providerId → models[]with context window, capabilities, cost, and modality metadata. inferWireType()resolves provider → wire type (explicittypefield, then heuristic onnpm/id).createProvider()instantiates the correctChatProvider.getModelCapability()returns per-model flags (vision, tool-use, thinking, fast-mode).
Adding an OpenAI-compatible provider requires zero code changes — just add a catalog entry.
- Start from requirements and code facts; discuss unclear goals first.
- Code is the source of truth — don't read Markdown to understand implementation.
- Validate version claims against authoritative docs (Context7 MCP, Tavily).
- Read relevant source and follow the nearest
AGENTS.mdbefore changing code. - Keep changes focused — no drive-by refactors.
- Implement current requirements directly; no backward-compatibility shims.
- Simplest implementation first: stdlib → established libraries → custom code.
- No co-author attribution or agent identity in commits/PRs.
- Git identity:
elkaix <melkholy@techmatrix.com>— apply per command; never modify git config.
| Package | Description | Notes |
|---|---|---|
apps/pythinker-code |
CLI / TUI app | Consumes @pymodel/pythinker-code-sdk; no agent-core dep. Use write-tui skill. |
apps/pythinker-web |
Browser UI (Vue 3 + Vite + vue-i18n) | REST + WS /api/v1; no agent-core dep. See its AGENTS.md. |
apps/pythinker-inspect |
Web inspector for the agent-gateway /api/v1/debug RPC surface |
Workspace/session browser, per-session transcript chat, per-scope Service panels, DI unit inspection. See its AGENTS.md. |
apps/vis |
Session replay & debugging visualizer | server/ + web/ subdirs. |
packages/agent-core |
Agent engine | Agent, Session, profile, skills, tools, plan, permission, DI. |
packages/agent-core-v2 |
DI × Scope agent engine (the v2 port behind agent-gateway) | Three LifecycleScope tiers — App / Session / Agent (app/scopes.ts). Workspace resources use App-owned WorkspaceInstance / Program lifetimes, not a DI scope. Also includes the L3 unit layer (Service/Fiber units, collection contribution points, the Feature seam in src/features/). See its AGENTS.md and use the agent-core-dev skill. |
packages/node-sdk |
Public TS SDK & harness | |
packages/kosong |
LLM provider abstraction | Wire types, catalog, capability registry. |
packages/pyaos |
Execution environment | File/process abstractions. |
packages/agent-gateway |
Pythinker Code server | Fastify server backed by @pymodel/agent-core-v2; sessions over REST + WebSocket (/api/v1 + /api/v1/ws), plus /api/v1/debug/* reflection RPC (--debug-endpoints, loopback bind + bearer auth). See its AGENTS.md. |
packages/klient |
Client SDK | Contract-driven facade over agent-core-v2 (global.* / session(id).* / agent(id).*, zod-validated); transport via subpath entry (`@pymodel/klient/ipc |
packages/transcript |
Isomorphic transcript rendering data layer | L1 agent-granular store, L2 idempotent operations, L3 off/turn/block/delta subscription granularity, L4 framework-free view registry, turn-cursor pagination. Pure TypeScript (browser-safe, no engine imports); sole owner of the transcript contract types (src/contract/). See its AGENTS.md. |
packages/oauth |
Auth utilities | |
packages/telemetry |
Client-side telemetry | |
packages/tree-sitter-bash |
Pure-TypeScript bash parser | No runtime deps, no wasm; parse(source, { timeoutMs, maxNodes }) under a deterministic budget returns a discriminated ParseResult — treat aborted/hasError trees as "cannot analyze" and degrade. Parser only, no safety judgments. |
packages/minidb |
Embedded JSON document store | MiniDb behind agent-gateway's search index — snapshot + WAL persistence with an exclusive write lock, larger-than-RAM full-text layer, persistent index generations. See its AGENTS.md. |
packages/acp-server |
Agent Client Protocol host over engine v2 | Drives the engine through a klient memory-transport facade. |
packages/pi-tui |
Vendored TUI library | Upstream fork with local divergences; tests run with node --test, not vitest. See its AGENTS.md. |
packages/protocol |
Shared REST + WS protocol schemas | Envelope, error codes, pagination, WS-control types. |
packages/remote-control |
Remote Control tunnel client | Registers this machine with a relay and forwards HTTP/WebSocket traffic to the local server, behind a machine-wide single-instance lock. Consumed by agent-gateway (the /api/v1/remote-control toggle) and the CLI (pythinker web --remote-control). |
The web bundle: apps/pythinker-code/dist-web is the committed, prebuilt bundle of apps/pythinker-web (built with pnpm --filter @pymodel/pythinker-web run build and copied via scripts/copy-web-assets.mjs). apps/pythinker-code/scripts/check-web-assets.mjs fails when the bundle is missing or stale (it compares a fingerprint of every apps/pythinker-web build input against the one recorded at copy time); it runs in pre-push, in the CLI build, and on prepack. Whenever you touch the web UI, run pnpm run build:web and commit the restaged bundle in the same change. packages/server and packages/server-e2e are empty leftover directories excluded from the workspace — not packages.
- Node.js 24.x, minimum 24.15.0 (
.nvmrcpins 24.15.0). pnpm 10.34.3 (rootpackageManager).engine-strict=true;pnpm installfails outside the supported Node 24 range.
pnpm-workspace.yamlis source of truth, butflake.nixhardcodesworkspacePaths/workspaceNames.- Update both when adding/removing any workspace package — for every package, including leaf / test / e2e packages that nothing depends on. Missing a path silently drops files from Nix's
srcfileset; missing a name breakspnpmConfigHook(dependencies for that workspace are not fetched). - CI (
scripts/check-nix-workspace.mjs) only validates the transitive dependency closure of@pymodel/pythinker-code— a leaf package outside that closure slips through even when missing fromflake.nix. A green check is NOT proof of full sync — keepflake.nixupdated by hand.
- English-only codebase. Use ASCII/Latin fixtures (e.g.
café) for unicode tests. packages/agent-core-v2,packages/agent-gateway, andpackages/transcriptare comment-free zones: no line/block comments; no JSDoc either, not even on exported symbols; the only exception is a load-bearing lint-suppression directive (oxlint-disable/eslint-disable), while other tooling directives (@ts-expect-error, …) stay banned. Enforced byscripts/check-no-comments.mjs, which runs as part ofpnpm lint.tsgo(@typescript/native-preview) available vianpx tsgo -p <tsconfig> --noEmit; committed scripts usetsc— run both for type fixes.- Pass
undefineddirectly for optional props — no conditional spread. user?: User, notuser?: User | undefined.- Single-param internal methods stay single-param — no options-object wrapping.
- Non-root
index.ts: preferexport * from './module'. Agentclass must be standalone — no mandatorySession/agentId. OptionalsessionIdas provider hint only.- Prefer adding tests to existing files. Fix failing tests first (unless there's a real impl bug); when a test fails because of a user modification, default to fixing the test first, not the implementation.
- Do not sacrifice code quality for external compatibility unless the user explicitly asks for it.
- Breaking changes require changesets with
majorbump (user confirmation required). - Identity freeze: never rewind published
package.jsonversions, never change the VS Code publisher or extension id (pymodel.pythinker), never replaceCHANGELOG.mdwith another history.scripts/check-identity-freeze.mjs(viapnpm lint) and Release--npmenforce this.
Gate behind flags. Env: PYTHINKER_CODE_EXPERIMENTAL_<NAME> toggles one; PYTHINKER_CODE_EXPERIMENTAL_FLAG enables all. Precedence is per-flag env > [experimental] config > master env > the flag's default. Release: flip the entry's default to true.
packages/agent-core(v1): add the flag to the central registry atpackages/agent-core/src/flags/registry.ts, then check it withflags.enabled('my-feature').packages/agent-core-v2and agent-gateway modules: no central catalog — declare the flag in the owning domain viaregisterFlagDefinitionat import time (seepackages/agent-core-v2/docs/flag.md), then check it withIFlagService.enabled(id).
- Never commit to
maindirectly. Every change lands through a pull request: branch, push the branch, open a PR, get the checks green, then merge.mainenforces this for everyone including admins, so a direct push is rejected outright (GH006) — do not try to work around it with--admin,--no-verify, or a force push. - A PR is mergeable only when all six required checks pass (
build,test,lint,typecheck,nix build .#pythinker-code,Check flake.nix workspace sync), every review conversation is resolved, and the branch is up to date withmain. - Prefer
rg/rg --filesfor code reading. - Follow existing boundaries and local patterns.
- Replace internal identifiers with neutral placeholders in public text/test data (e.g.
example.com,example.test,YOUR_API_KEY). Before opening a PR, ask a read-only agent to audit the diff for context-specific internal identifiers. - When creating a PR, use the
write-prskill (.agents/skills/write-pr/SKILL.md) to write the PR description. PR titles: Conventional Commit style (e.g.chore: remove legacy format commands). - Fill in
.github/pull_request_template.md— link the issue, describe changes. No placeholder text or vague AI-generated PR summaries; the human author must understand the change well enough to explain the code, edge cases, and why the approach fits. - Run
gen-changesetsskill before submitting PRs. Changesets must strictly follow its rules: one short user-facing sentence stating only what changed; skip any change users cannot perceive. Never decidemajoron your own — stop, explain, and get explicit user confirmation first; default tominor, fall back topatch. - Changeset text is shipped text: the desktop release body is generated from
apps/desktop/CHANGELOG.md, and the in-app updater shows it to users verbatim. A release body must state what changed for users — never a build stamp, a commit hash, or placeholder text.desktop-release.ymlfails a stable release whose version has no changelog entry. - Prefer
import ... from '#/...'(equivalent to@/...). - Do not commit throwaway scratch or exploratory files. Never stage agent working notes or handoff documents (e.g.
HANDOVER-*.md,HANDOFF-*.md,handoff.md), or throwaway UI/UX prototypes or design mockups (e.g.*-designs.html,*-mockup.html,*-demo(s).html). The only tracked.htmlfiles should be Viteindex.htmlentrypoints. Put scratch work under.tmp/(gitignored).
- Hard rules that affect almost every task: update the root
AGENTS.md. - Rules that only affect a specific directory: update the nearest sub-directory
AGENTS.md. - Project-map entries stay at 1–2 sentences; deep package docs live in the package's own
AGENTS.md.
These rules apply to every pull request review, automated or human. The user populations and contract files they refer to are listed in .agents/skills/review-pr/surfaces.md.
Any input that worked before the change — a config key, env var, CLI flag, provider response shape, session written by an older version, client request, or hook payload — must behave the same after it unless the PR declares the change. For every deleted or narrowed branch, condition, default, or prompt sentence, ask who reached it before and where they go now; for every new condition, ask which existing inputs now match it first. Flag a PR that calls a path "unchanged" when its branch condition moved.
Everyone on the old default is affected. Require the changeset to name the behavior users lose, not only the new default; a config, env, or flag escape hatch or a maintainer's explicit sign-off in the PR; and a test that pins the old behavior for the population that keeps it.
Editing or deleting sentences under packages/agent-core-v2/src/**/*.md (system prompt, tool descriptions, reminders, overlays, built-in skills) changes agent behavior for every user who receives that prompt. "No test references the sentence" is not evidence of no impact. Require the PR to name the population that receives the text (every session, plan mode, a flag-gated feature such as Tower), what the sentence enforced, who relied on it, and what enforces it now.
The manifests under packages/agent-core-v2/docs/ (config-manifest.toml, wire-manifest.d.ts, state-manifest.d.ts), packages/agent-gateway/test/__snapshots__/apiSurface.snapshot.test.ts.snap, packages/node-sdk/src/index.ts, packages/agent-core-v2/src/features/externalHooks/, packages/acp-server/, and apps/pythinker-code/src/cli/ are consumed outside the packages that define them: the desktop and web apps in this repository, the inspector app, the VS Code extension, ACP clients such as Zed, SDK users, hook scripts, and headless-output parsers. When they change, require the PR to name the consumers and how data and clients from the previous release keep working.
When a change replaces or bypasses an existing path, require a list of what the old path did — env vars honored, fallbacks, accepted inputs — and where each item lives in the new path. A silently dropped item is a regression, not a cleanup.
A change that makes the product silently ignore configuration, silently approve or skip an action, or silently drop data is the most severe finding: users get no signal to report.
This project is indexed by GitNexus as pythinker-code (91837 symbols, 264777 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
Index stale? Run
node .gitnexus/run.cjs analyzefrom the project root — it auto-selects an available runner. No.gitnexus/run.cjsyet?npx gitnexus analyze(npm 11 crash →npm i -g gitnexus; #1939).
- MUST run impact analysis before editing any symbol. Before modifying a function, class, or method, run
impact({target: "symbolName", direction: "upstream"})and report the blast radius (direct callers, affected processes, risk level) to the user. - MUST run
detect_changes()before committing to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch:detect_changes({scope: "compare", base_ref: "main"}). - MUST warn the user if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use
query({search_query: "concept"})to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. - When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use
context({name: "symbolName"}). - For security review,
explain({target: "fileOrSymbol"})lists taint findings (source→sink flows; needsanalyze --pdg).
- NEVER edit a function, class, or method without first running
impacton it. - NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use
renamewhich understands the call graph. - NEVER commit changes without running
detect_changes()to check affected scope.
| Resource | Use for |
|---|---|
gitnexus://repo/pythinker-code/context |
Codebase overview, check index freshness |
gitnexus://repo/pythinker-code/clusters |
All functional areas |
gitnexus://repo/pythinker-code/processes |
All execution flows |
gitnexus://repo/pythinker-code/process/{name} |
Step-by-step execution trace |
| Task | Read this skill file |
|---|---|
| Understand architecture / "How does X work?" | .claude/skills/gitnexus/gitnexus-exploring/SKILL.md |
| Blast radius / "What breaks if I change X?" | .claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md |
| Trace bugs / "Why is X failing?" | .claude/skills/gitnexus/gitnexus-debugging/SKILL.md |
| Rename / extract / split / refactor | .claude/skills/gitnexus/gitnexus-refactoring/SKILL.md |
| Tools, resources, schema reference | .claude/skills/gitnexus/gitnexus-guide/SKILL.md |
| Index, status, clean, wiki CLI commands | .claude/skills/gitnexus/gitnexus-cli/SKILL.md |