Dayward AI
Week 3 · D21About 4 hours

Packaging and Release: a Global Command, a Config Directory, Versioning and Updates — a Twenty-One-Day Retrospective

Turn it into a tool other people can use too: set up an executable entry point and packaging output, gather configuration scattered across environment variables into a config directory while keeping the override order, handle versioning and update prompts, write a README and demo, then look back at how these twenty-one layers grew into one tool.

Today's goals 0/3

Sign in to tick these off and save your progress.

Today's Goals

  1. Package a TS project into a globally installable command-line tool, and verify it actually works after install
  2. Design a layered configuration override order, so environment variables, config files, and CLI arguments each fall into place
  3. Package the project into presentable work: a README, a demo, and known limitations

The last day adds no new capability. It does one thing: let other people use what the previous twenty days built. When you finish, scroll back up and tick the three goals off.

Plain-Language Walkthrough

Going permanent and issuing the badge: other people can bring them in too

On day one we gave this new colleague a desk and an internal phone line. Over twenty days they learned to read code, edit files, run commands, stop and ask, remember last time, borrow another department's tools, work from a craft card, split work among apprentices, read a drawing, and tally their own hours and spend. Today they go permanent — and the marker of that is not one more line on the capability list. It is that another department can borrow them.

That bar has nothing to do with capability. They need a badge anyone can scan for who this is and which version. They need to work in somebody else's shop, and when the host's rules collide with their habits, something must decide who wins. And if their opening line on loan is a string of numbers nobody can read, the loan ends there.

Small enough to be mistaken for tidying up, all of it — but it decides whether this is a piece of work or just a pile of code on your machine. To a tool that will not install and greets you with a stack trace, every mechanism of the last twenty days is worth zero.

So today does four things whose absence means "not finished": the entry point and packaged output, configuration layering, versioning and updates, and the first-run experience — ending in a verification chain you have to actually run.

The first obstacle: how TypeScript gets into the package

Every TS command-line tool hits this first, and the standard answer is usually dodged: declare a TS runner as a runtime dependency and point the entry at the TS source. That works, and it costs everyone who installs your tool a build toolchain of tens of megabytes — a sad way to break twenty days of zero-dependency discipline on the last one.

So the ruling is compile to JS and ship that: source in src, output in dist, only the output in the package. One more build step, in exchange for users installing plain JS that Node runs directly.

That step has a trap: the compiler only moves the files it compiles. Everything under src that is not TS — a subprocess script, a template, a static page, a data file — it ignores. And local development runs the source, never the output, so this failure cannot be reproduced on your own machine; it shows up on somebody else's, as one line saying a module was not found.

The fix is to copy them in the build script, and the copy must walk directories rather than read a hand-written list, which goes stale the day someone adds the first new file with nothing reporting it. This project's output happens to contain exactly two such files — day fifteen's notes-store subprocess and day eighteen's background task runner. Each appeared after the build script was written, and nobody changed a single character of that script for them. The same reasoning settles the manifest: files lists directories, not files.

JSONJSON
"files": ["bin", "dist", "skills", "README.md", ".env.example"]

The moment you enumerate it file by file, that package starts its own expiry countdown.

Entry points: JS ships a file with a shebang, Python ships a function

A global command needs an entry point, and the two ecosystems divide the labor differently. On the Node side you name a file in the bin field and the installer symlinks to it. The shell executes that link, not node, so the first line must be a shebang; without it the error is a syntax error pointing nowhere near the cause. Python is the mirror image: you declare a callable in your packaging config and the installer generates the launcher — you never write a shebang, because you did not write that file.

bin/mca.mjs
#!/usr/bin/env node
// Three things: (1) the shebang on line one is not optional (2) the .mjs extension,
// so nothing depends on the package manifest's type field (3) not one line of logic —
// the bin path is part of the published contract and is painful to change afterwards
import '../dist/index.js'

Both sides share one discipline: the version number may have exactly one source of truth — the package manifest in Node, the package metadata in Python. Declare a second constant in code and some release updates one and forgets the other, after which every issue users file carries the wrong version. No test turns red for that.

Configuration layers: the narrower the scope, the higher it wins

For twenty days mca had one configuration layer: environment variables. That sufficed with one user, one repo, one terminal. Once installed elsewhere, four parties want the last word on one value.

built-in defaultsthe fallback when nothing is set user configmy personal default on this machine project configthis repo's team convention, committed to git environment variablesthis terminal session command-line flagsjust this once the effective valuewhich remembers which layer it came from
Mermaid source
mermaidmermaid
flowchart TD
  A[built-in defaults<br/>the fallback when nothing is set] --> B[user config<br/>my personal default on this machine]
  B --> C[project config<br/>this repo's team convention, committed to git]
  C --> D[environment variables<br/>this terminal session]
  D --> E[command-line flags<br/>just this once]
  E --> F[the effective value<br/>which remembers which layer it came from]

That order is derived rather than tasteful: the narrower the scope, the higher it wins, because a narrower scope is the user saying "just this once" more explicitly. Reverse any pair and the symptom is the same — someone passes a flag, nothing happens, no error. Arrange the five as an ordered array applied in turn rather than a chain of conditionals, so adding a layer is inserting one element.

More interesting than the order: every value must remember where it came from. The common support question about layered config is not "the value is wrong," it is "I cannot tell why it is this." So each key carries its origin and a command lays them all out — not a debugging feature, but the precondition for anyone using the layering at all.

src/ship/config-layers.ts
// Array order is override order: later entries win. Record the origin as you write the value.
for (const entry of stack) {
  for (const key of ['baseUrl', 'model']) {
    const value = entry.data[key]
    if (value === undefined) continue
    traced[key] = { value, layer: entry.layer, from: entry.from }
  }
}

Since every earlier reader in this codebase reads an environment variable, three of them inside frozen files, the entry point writes the resolved values back into the environment rather than threading a config object through twenty files — fine on one condition: exactly one write point, before any provider is constructed.

What belongs in the config directory, and what must never

Config files live in two places: the user's home directory (personal defaults, never committed) and the project directory (the team convention, which does get committed). The project one is found by walking up to the first ancestor with a config directory, and stopping there — so nested repositories get a deterministic answer: the nearest wins.

Then today's line to remember above the others: secrets do not go in config files.

Not fastidiousness. Project config is committed to git; user config sits in plaintext in a home directory. Both get swept up by backup tools, sync folders, screenshots, and one casual "can you send me your config." An environment variable lives only inside this process.

One fork is easy to get wrong: when a secret turns up in a config file, do you ignore it or refuse and say so? It must be the second — ignoring silently means a user who did write a key keeps being told there is none, and concludes the tool is broken. Same for a misspelled key. And when printing configuration, mask secrets.

After a global install, three roots come apart for the first time

Locally, "where the code is," "which repo I am in," and "where my preferences live" are one directory, so one function returning the current directory covers all three. A global install separates them: the code sits in an install prefix that has nothing to do with the user.

One rule covers it: anything shipped with the package is located relative to the package; anything belonging to the user is never looked for inside the package. Locating the package means resolving upward from the module's own location, not hard-coding a count of relative levels — source and output sit at the same depth today, and a different build config changes that. Break the rule and the symptom is "works for me, missing files for everyone who installs it," again unreproducible locally, because locally those three roots are one directory.

The same rule widens the skills directory to three sources — shipped, the user's, the repo's — nearest wins a collision.

Two small things that must not get in the way: first run and update checks

The first time someone types a freshly installed command is the only attention it gets without earning it, and the worst answer is a stack trace. A decent message answers three things: what exactly is missing, which command fixes it (copyable as-is), and whether there is a way to look around without fixing it yet. That last escape hatch turns "I will look into this later" into "let me just run it."

The hard part of update checks is not the lookup, it is not getting in the way: done badly it adds two seconds to startup, prints red text offline, or takes the tool down when the registry misbehaves — each worse than upgrading a week late. Hence four rules: failures are always silent, there is always a timeout, check at most once a day, never check at all in offline mode.

src/ship/update.ts
export async function fetchLatestFromRegistry(name) {
  try {
    // The timeout is not an optimization; it is the precondition for this feature existing
    const response = await fetch(`${registry}/${name}/latest`, {
      signal: AbortSignal.timeout(1500),
    })
    if (!response.ok) return null
    const body = await response.json()
    return typeof body.version === 'string' ? body.version : null
  } catch {
    // "no answer this time" and "answered, no newer version" look identical to the user:
    // nothing extra on screen
    return null
  }
}

Non-interactive mode: the default answer to an approval prompt must be no

Once installed, this tool lands in places with no human in them: scripts, CI, git hooks. But a REPL is for people — on a non-interactive terminal standard input ends immediately, the program prints one line and exits with exit code 0. That is the false green this course has guarded against since day one, and today it becomes a product requirement: a run-once-and-exit mode.

That mode has its own discipline: the default answer to an approval prompt must be refusal. Interactively, "no answer" means hesitation; non-interactively it is the normal state, and treating it as consent hands every CI job an unlimited grant. Changing anything requires an explicit flag, and a blocked call's refusal should be written for the model to read, so it reroutes to a read-only path rather than retrying in place.

The last step: verifying is not "no errors," it is install it and run it again

The build succeeds, the package builds, not a line of red — and it dies in its first second on somebody else's machine. Same cause every time: locally you run the source, what you shipped is the output.

So the verification chain has to run, all four steps: build the package, install it into a temporary prefix, run the version command in a freshly created empty directory, then run one real task there. The temporary prefix keeps the user's global environment untouched; the empty directory exists because the current directory having nothing to do with the package is the thing under test. Afterwards, delete both.

In the lab this chain is five checks green, with a package of 192 KB containing 134 files — stable and reproducible for the same code. Install duration is not reproducible, depending on network and cache, so that script prints no timing number.

Source Reading

Hands-On Lab

🧪 D21 lab: package mca, install it into a temporary prefix, and run a real task in an unfamiliar directory

Code location: labs/my-coding-agent-21days/day-21-ship-it

All five exercises are the "green locally, explodes after install" family: a manifest enumerating files one by one, an entry point missing its first line, a build that compiles but copies nothing, layers ordered backwards with one missing, no secret gate, a stack trace on the first screen, an update check that takes startup down with it. The starter passes four of fourteen.

  1. Switch the manifest to directory enumeration and add the entry point's first line, then watch checks 2 and 3 go green.
  2. Add the directory walk copying non-TS assets, and watch check 4's missing-file list empty out.
  3. Fix the layer order and add the flag layer, then see one value win at each of four layers, each time able to say where it came from.
  4. Install the secret gate: a key in a config file is refused with a warning, and the printed configuration holds no plaintext.
  5. Replace the first screen with the three-option message, swallow the update check's exception, run MOCK=1 SELFTEST=1 pnpm start for fourteen of fourteen, then pnpm verify:pack.

Acceptance is five ticks: all fourteen self-test checks pass; every source file has a counterpart in the output; the package holds output and entry point but no source and no dotenv file; without the authorization flag a write is refused and the file untouched; packaging verification is five for five with the temporary directory gone.

Interview Questions

Today's three questions test engineering judgment about distribution and configuration:

  1. Publishing a command-line tool as a globally installable package, which traps are easy to overlook?
  2. How do you decide the configuration layers and override order? Where do secrets belong?
  3. Putting this project on your resume, how would you state its technical substance in three sentences?

Full prompts, analyses and key points are in this course's day-twenty-one question bank. Question one discriminates most — most answer "remember the bin and files fields," few say "locally you run the source, what you ship is the output, so install it and run it again" and name a failure that only surfaces after install.

Checklist and Tomorrow

  • I can say why a TS project is compiled to JS for shipping, not shipped with a build tool as a runtime dependency
  • I can state the trap that the compiler does not copy non-TS assets, and why it is unreproducible locally
  • I can explain why the manifest and the asset copy both enumerate directories, not files
  • I can derive the five layers' priority order rather than memorize it
  • I can say why every value must remember its origin, and why printing origins is not a debugging feature
  • I can explain why secrets stay out of config files, and why the response is "refuse and say so"
  • I can name the three roots that come apart after a global install, and why this bug hides locally
  • I can recite the update check's four rules and the consequence of no timeout
  • I can explain why a non-interactive approval prompt defaults to refusal

What twenty-one days built

Go back to day one: a prompt on screen, and typing a sentence into it did nothing. Today it is a command installable anywhere that, in somebody else's repository, reads code, edits files, runs tests, stops to ask you, remembers last time, borrows other people's tools over MCP, works from the team's craft cards, splits work across parallel subagents, uses hooks to turn "read before you write" into a process it cannot route around, backgrounds long commands, understands a pasted screenshot, scores itself against a benchmark set, accounts for what a run cost — and can be rolled back at any point.

Each of those twenty layers did one small thing. The hardest was neither stream parsing nor compaction: it was approval, the first that forced you to answer "what is this allowed to do," which is not a technical question. The most valuable was probably the event log — once everything is an append-only sequence, resume, fork, rewind, evaluation and cost accounting become different readings of one dataset. And the layer most often skipped, most damaging when skipped, is today's.

How far this is from a production-grade Coding Agent

Honestly: quite far, and mostly not where you would guess.

  • Editing: only exact replacement. Production handles patch formats, indentation drift, repeated edits to one region, partial rewrites of large files.
  • Search: our grep is string matching; real repositories want semantic search and symbol indexes.
  • Concurrency and isolation: our subagents are in-process. Production means real working-tree isolation, conflict merging, cleanup after a run is killed halfway.
  • Robustness: we cover the error classes we knew about; the real world returns shapes you have never seen.
  • Security: our approval gate guards local writes. Production also weighs prompt injection, over-broad reads, secret exfiltration.

That list is itself part of the payoff: you can now say specifically where the gap is, instead of vaguely feeling somebody else's is better.

Where to go next

Three directions, in increasing order of effort:

  1. Make it a tool you use daily. Start with the trade-off today's README names (do not write the sandbox into the user's repo), then add two slash commands matching how you work. A week of daily use beats ten more articles.
  2. Go deep on one area. Retrieval: the RAG course. Context budgeting: the context engineering course. Tool ecosystems and reusable experience: the MCP course and Skills course.
  3. Turn it into presentable work. "Implemented a Coding Agent" carries no information on a resume. "Hand-wrote SSE parsing and tool-call fragment merging, an event log for session resume and forking, content-addressed snapshots for rollback" does. Today's packaging and README are the vehicle: one demo that runs beats a screenful of feature bullets.

Day one's sentence can be handed back to you now: the model has no memory and does not act on its own — it is the twenty-one layers you wrote that make it look as though it does.

Interview questions

  • What is easy to overlook when shipping a command line tool as a globally installable package?把一个命令行工具发布成可全局安装的包,有哪些容易忽略的坑?
    Common in ChinaCommon overseasIntermediate#packaging#cli#distribution

    How to reason about it · think before answering

    1. This tests whether you have actually shipped one. People who have not answer "remember the bin and files fields"; people who have open with the real point - locally you run sources, what you ship is build output, and those two paths differ, so you must install it and run it again.
    2. How to break it down - two items each under "missing from the package", "looking in the wrong place at runtime", and "verifying the wrong way", then finish with a concrete verification chain.
    3. Missing from the package - a file list that enumerates files instead of directories goes stale the day someone adds a file; the compiler only moves files it compiles, so scripts spawned as subprocesses, templates and static pages never reach the output; and an entry script without a shebang fails because on Unix-like systems a shell, not the runtime, executes it, producing a syntax error that points nowhere near the cause.
    4. Wrong place at runtime - after a global install, "where the code lives", "which repository the user is working in" and "where personal config lives" are three directories, while during local development they happen to be one, so this class of bug never shows up locally. The rule is to locate bundled assets by walking up from the module's own location, and never to look for user data inside the package.
    5. Verifying the wrong way - a clean build is not proof it installs. Real verification is pack it, install it into a temporary prefix, then run a version command and one real task from a freshly created empty directory. The temporary prefix keeps the user's global environment untouched; the empty directory is the point, because cwd having nothing to do with the package is exactly what is under test.
    6. Two more - the version number must have a single source of truth in package metadata, since a duplicated constant makes every bug report carry the wrong version; and decide runtime dependencies deliberately, because dragging a build tool into them is the most common kind of bloat.
    7. Likely follow-ups - whether build output belongs in version control; line endings and executable bits across platforms; how to run this verification chain in CI.

    分析过程 · 先想清楚再作答

    1. 这题在考「你有没有真的发过一次」。没发过的人会答「记得配入口字段和包清单」,发过的人第一句会是:本地跑的是源码、发出去的是产物,这两条路径不一样,所以必须装完再跑一遍。
    2. 怎么拆:按「打包时漏了什么」「运行时找错了地方」「验证方式不对」三类各说两条,最后给一条可执行的验证链。
    3. 第一类,打包时漏东西:包清单按文件枚举而不是按目录,加一个新文件的那天就悄悄过期;编译器只搬它会编译的文件,要被子进程起起来的脚本、模板、静态页一个都不会进产物;入口脚本少了 shebang,类 Unix 上执行它的是 shell 不是运行时,报的是一句指不到病根的语法错误。
    4. 第二类,运行时找错地方:全局安装之后「代码在哪」「用户在哪个仓库干活」「个人配置在哪」是三个目录,本地开发时它们恰好是同一个,所以这类 bug 本地一次都不会出现。规矩是随包分发的资产从模块自身地址向上找包根,用户的东西一律不从包里找。
    5. 第三类,验证方式不对:构建没报错不等于装得上。真正的验证是打包、装到一个临时前缀、在一个新建的空目录里跑一次版本命令和一次真实任务。用临时前缀是为了不碰使用者的全局环境,用空目录是因为「当前目录和包毫无关系」正是要验的那件事。
    6. 还有两条配套的:版本号只能有一个真源(包元数据),代码里另写一个常量会让所有 issue 都带着错的版本;发布前先想清楚运行时依赖,把构建工具拖进运行时依赖是最常见的一种膨胀。
    7. 可预期的追问:产物要不要进版本库;跨平台的换行与可执行位怎么处理;怎么让这条验证链在 CI 里跑。

    Key points

    • Core point - locally you run sources but you ship build output, so a clean build is not proof of a working install; install it and run it again
    • List directories, not files, in both the package manifest and the asset copy step, or it silently goes stale the day a file is added
    • The compiler does not move non-source assets, and the entry script's first line must be a shebang
    • After a global install the package root, project root and user directory are three different places; find bundled assets by walking up from the module's own location
    • Verification chain - pack, install into a temporary prefix, run the version command and one real task from an empty directory, then clean up without touching the global environment
    • One source of truth for the version; never drag a build tool into runtime dependencies

    答题要点

    • 核心一句:本地跑源码、发出去是产物,构建通过不等于装得上,必须装完再跑一遍
    • 包清单与资产复制都要按目录而不是按文件枚举,否则加一个新文件就悄悄过期且不报错
    • 编译器不搬非源码资产;入口脚本第一行必须是 shebang
    • 全局安装后包根、项目根、用户目录三者分开,随包资产从模块自身位置上溯去找
    • 验证链:打包 → 临时前缀安装 → 空目录里跑版本命令与一次真实任务 → 清理,全程不碰全局环境
    • 版本号只有一个真源;别把构建工具拖成运行时依赖
  • How do you decide the layering and override order of configuration, and where should secrets live?配置的分层与覆盖顺序你怎么定?密钥该放哪里?
    Common in ChinaCommon overseasIntermediate#configuration#secrets#cli

    How to reason about it · think before answering

    1. This tests whether you can derive the priority order rather than recite it, and whether you have a clear position on where secrets live. Answering only "flags beat env vars beat files" is recitation, with no reason attached.
    2. How to break it down - state the ordering principle, then two implementation points, then secrets on their own.
    3. The principle in one line - the narrower the scope, the higher the priority. A flag applies to this one run, an environment variable to this shell, project config to this repository, user config to this machine, and defaults to everything. A narrower scope means the user is being more specific about "just this time, just here". Reversing it produces the symptom "I passed the flag and nothing happened", with no error anywhere.
    4. Implementation point one - express the layers as an ordered array applied in sequence rather than a chain of conditionals, so adding a layer means inserting an element instead of rearranging logic.
    5. Implementation point two - every value must remember which layer it came from, and there must be a command that prints all of it. The most common support question about layered config is not "the value is wrong" but "I do not know why it is this value", so printing provenance is not a debug feature, it is what makes the layering usable.
    6. Be explicit about secrets - they do not go in config files. Project config is committed, user config sits in plaintext in the home directory, and both get carried off by backups, sync folders, screenshots and a casual "send me your config". Secrets come from environment variables or a local file that is explicitly never committed, and above that from a system keychain or a managed secret store.
    7. One more fork that is easy to get wrong - a secret found in a config file should be rejected with a warning, not silently ignored. Silent ignoring leaves a user who did write a key being told there is none, and they will conclude the tool is broken. Likewise, mask secrets whenever configuration is printed.
    8. Likely follow-ups - what to do when the same key has different types in two layers; whether a broken config file should stop the tool from starting; how to make team config work for a new hire with zero setup.

    分析过程 · 先想清楚再作答

    1. 这题在考你能不能把优先级推导出来而不是背下来,以及你对密钥的位置有没有明确立场。只答「命令行大于环境变量大于配置文件」是背的,加不上一句为什么。
    2. 怎么拆:先给排序的判据,再说实现上的两个要点,最后单独说密钥。
    3. 判据一句话:作用范围越窄的优先级越大。命令行参数只管这一次、环境变量只管这个终端、项目配置只管这个仓库、用户配置只管这台机器、内置默认值管所有情况——范围越窄说明用户越明确地在说「就这一次、就这里」。倒过来排的症状是「我加了参数但没生效」,而且没有任何报错。
    4. 实现要点一:把层排成一个有序数组依次覆盖,不要写成一串条件判断。加一层只要插一个元素,不用重排任何判断。
    5. 实现要点二:每个值都要记住自己来自哪一层,并提供一条把它们全摊开的命令。配置分层最常见的支持问题不是「值错了」而是「我不知道它为什么是这样」,所以打印来源不是调试功能,是这套分层可用的前提。
    6. 密钥的立场要明确:不进配置文件。项目配置要进版本库,用户配置明文躺在家目录里,两者都会被备份、同步盘、截屏和一句「把配置发我看看」顺走。密钥只从环境变量或一个明确不提交的本地文件来,再往上是系统钥匙串或云上的密钥管理。
    7. 还有一个容易做错的分叉:在配置文件里读到密钥应该拒绝并警告,不是静默忽略。静默忽略会让用户明明写了却一直被告知没有,他会以为工具坏了。同理打印配置时密钥必须打码。
    8. 可预期的追问:同一个键在两层里类型不同怎么办;配置文件坏了要不要让工具起不来;怎么让团队配置对新人零成本生效。

    Key points

    • Order by narrowness of scope - flags over environment variables over project config over user config over built-in defaults
    • Implement as an ordered array applied in sequence, not a chain of conditionals; adding a layer is inserting an element
    • Every value carries its source, and one command prints all sources - a usability prerequisite, not a debug feature
    • Secrets stay out of config files - project config is committed and user config sits in plaintext at home, and both leak
    • Reject and warn when a secret appears in a config file rather than ignoring it silently, and always mask secrets when printing config

    答题要点

    • 排序判据是「作用范围越窄优先级越大」:命令行 大于 环境变量 大于 项目配置 大于 用户配置 大于 内置默认值
    • 实现成有序数组依次覆盖,而不是一串条件判断;加一层只插一个元素
    • 每个值都要带来源,并提供一条打印全部来源的命令——这是可用性前提不是调试功能
    • 密钥不进配置文件:项目配置要进版本库,用户配置明文在家目录,都会被顺走
    • 读到密钥要拒绝并警告,不能静默忽略;打印配置时必须打码
  • If you put this hand-built coding agent on your resume, how would you convey its technical substance in three sentences?把这个自己实现的 Coding Agent 写进简历,你会怎么用三句话说清它的技术含量?
    Common in ChinaCommon overseasDeep dive#portfolio#communication#agent-engineering

    How to reason about it · think before answering

    1. On the surface this tests communication; underneath it tests self-assessment - do you know which parts of your own project were hard and which were just legwork. "Built a coding agent" carries zero information, because someone who wired up a framework can say the same sentence.
    2. How to break it down - one sentence on boundaries and constraints, one on the two or three hardest mechanisms, one on verification and honest limitations. The order matters, because the mechanisms only carry weight once the constraints are on the table.
    3. Sentence one states the boundary - no framework and no SDK, only an OpenAI-compatible endpoint, with streaming parsing, the tool loop, the approval gate and the protocol all written by hand, and exactly one runtime dependency. The constraint is itself information, because it rules out gluing libraries together.
    4. Sentence two picks the mechanisms with the most signal rather than listing features. Candidates - hand-written incremental parsing and merging of tool-call fragments, where the index cannot be assumed to start at zero so merging must key off a dictionary; an append-only event log that supports resume, fork and rewind at once; content-addressed snapshots giving file-level rollback plus detection of unrecorded edits; progressive skill loading that reduces the cost of unrelated turns to a single summary line. Pick two or three and attach one reason each.
    5. Sentence three covers verification and limits - how you proved it runs (an end-to-end self test on an offline script, plus packing it, installing into a temporary prefix and running a real task again) and an honest account of where it falls short of a production tool. Volunteering the limits beats being asked, and being able to name them precisely is itself evidence of depth.
    6. Be able to name the anti-patterns - a pile of feature nouns, quoting model sizes or benchmark scores, or claiming to be close to some shipped product. All three collapse at the first follow-up question.
    7. Likely follow-ups - which of those mechanisms did you rewrite once and why; if you could keep only three features which three; what would break first if you used it in anger.

    分析过程 · 先想清楚再作答

    1. 这题表面考表达,实际考自我评估:你知不知道自己做的东西里哪部分难、哪部分只是体力活。答「实现了一个 Coding Agent」信息量为零,因为这句话调一个框架也能说。
    2. 怎么拆:一句讲边界与约束、一句讲最难的那两三个机制、一句讲验证与诚实的局限。顺序不能反——先说约束,后面的机制才有分量。
    3. 第一句给边界:零框架零 SDK,只依赖一个 OpenAI 兼容接口,流式解析、工具循环、审批、协议全部手写,运行时依赖只有一个读环境文件的库。约束本身就是信息,它排除了「调库拼起来」这种可能。
    4. 第二句挑最有区分度的机制,不要罗列功能清单。可选的有:手写增量解析与工具调用分片归并(分片索引的起点不可假设,所以归并必须按索引建字典);只追加的事件日志同时支撑会话恢复、分叉与回退;内容寻址快照做文件级回滚并能识别未记录的改动;渐进披露的技能加载把不相干轮次的开销降到只剩一行摘要。挑两三个,每个都带一句「为什么这么设计」。
    5. 第三句说验证与局限:怎么证明它真的能跑(离线剧本下的端到端自检、打包后装到临时前缀再跑一次真实任务),以及诚实地说清它和产品级的差距在哪几块。主动说局限比等人问出来强,而且能说清局限本身就是懂行的证据。
    6. 反面示范要能指出来:堆一串功能名词、引用参数量或跑分、宣称「接近某某产品」。这三种写法都会在追问第一层就塌。
    7. 可预期的追问:这些机制里哪一个你重写过一次、为什么;如果只能保留三个功能你留哪三个;线上用它时最先会坏在哪里。

    Key points

    • State constraints before mechanisms - no framework, no SDK, one generic endpoint, one runtime dependency - because the constraint rules out the "glued libraries" reading
    • Pick two or three high-signal mechanisms and give one design reason each instead of listing feature nouns
    • Candidate mechanisms - merging streamed tool-call fragments where the index start cannot be assumed, an append-only event log serving resume, fork and rewind, content-addressed snapshots, and progressive disclosure for skills
    • Third sentence is verification - an offline end-to-end self test plus packing, installing into a temporary prefix and running a real task again
    • Volunteer the gap to production tools - editing capability, semantic retrieval, real isolation, unknown error shapes, security boundaries - naming limits precisely is what reads as depth
    • Avoid three collapsing patterns - piling up feature nouns, quoting benchmark numbers, or claiming to be close to a shipped product

    答题要点

    • 先说约束再说机制:零框架零 SDK、只认一个通用接口、运行时依赖只有一个——约束排除了「拼库」这种解释
    • 挑两三个有区分度的机制并各给一句设计理由,不要罗列功能名词
    • 可选机制:流式分片归并(索引起点不可假设)、只追加事件日志支撑恢复与分叉与回退、内容寻址快照、渐进披露的技能加载
    • 第三句讲验证:离线端到端自检 + 打包装到临时前缀后再跑一次真实任务
    • 主动说清与产品级的差距(编辑能力、语义检索、真实隔离、未知错误形状、安全边界),说得出局限才显得懂
    • 避开三种塌方写法:堆功能名词、引用跑分、宣称接近某个产品

Comments