AI Coding Assistant Lessons Learned: A Practical Guide from Configuration to Workflow
After two weeks of daily use, the bottleneck of an AI coding CLI assistant turns out to be configuration and context engineering, not the model. This guide walks through permission tiering, input-key handling, session discipline, project-level context files, context budgeting, usage observability, and the shift from personal tweaks to team-wide conventions — with copy-ready examples and a one-page rollout checklist.
1. Diagnose the problem correctly: the friction isn’t the model, it’s configuration and context engineering
After a little over two weeks with an AI coding CLI assistant (referred to below as “the CLI assistant”), my conclusion is clear: its code generation ability has never been the bottleneck. What actually eats efficiency are three things that have nothing to do with the model.
First, interaction gets interrupted over and over. The default safety behavior asks you to confirm commands one at a time. If a single-line change needs three edits, you click confirm three times, and your flow is cut into fragments.
Second, context gets polluted without you noticing. In one session you first ask it to read logs, then to fix tests, then to refactor an interface. The task goal keeps drifting, and reply quality visibly decays.
Third, the hidden cost is invisible. Nobody knows how many tokens a session has burned, or whether context usage is already close to the ceiling. By the time the output starts to go off track, the earlier turns are beyond recovery.
What these three problems have in common: you pay that cost every single time. Configuration, by contrast, is a one-time investment. The rest of this article proceeds layer by layer — interaction layer → context layer → observability layer → team adoption — and each layer gives you practices you can copy directly.
One caveat first: command names, config file paths and field names differ between tools. The JSON and Markdown below are illustrative structures; treat your tool’s official documentation as the source of truth. The approach matters more than the field names.
2. Interaction layer: minimize interruptions to your flow
2.1 Permission confirmation: from “ask every time” to tiered approval
Most CLI assistants default to the most conservative policy: reading a file requires confirmation, running a build requires confirmation, and deletion certainly does. That feels reassuring at first; once you’re fluent, it’s pure noise.
The right move is not to turn confirmation off entirely, but to tier it by risk:
- Auto-approve read-only operations: reading files, listing directories, searching code, builds, unit tests, linting, type checking. These are idempotent or reversible, and a failure costs nothing.
- Conditionally approve writes: modifying workspace files on a work branch. Version control is the safety net, so rollback cost is near zero.
- Require confirmation every time for the high-risk list: bulk deletion, force push and history rewrites, production config changes, schema migrations or data deletion, global environment installs, outbound network requests that send data, and changes to permissions or secret-bearing files.
If your tool supports allow/deny rules in a config file, put them in the project config instead of granting ad-hoc permission in conversation. Illustrative structure:
{"permissions": {"allow": ["ReadFile","SearchCode","RunBuild","RunUnitTest","RunLint"],"ask": ["WriteFile","RunInstall"],"deny": ["DeleteDirectory","ForcePush","ReadEnvSecrets","ModifyProductionConfig"]}}
If your tool offers an auto-approve (a.k.a. YOLO) mode, my recommendation is this: enable it only in an isolated environment — inside a container, on a dedicated development branch, in a working copy you can rebuild at any time. In a container, the impact of destructive operations is bounded by the container boundary. Enabling it globally on the host you use every day means handing over your home directory and your production config along with it. That isn’t an efficiency optimization; it’s outsourcing risk to luck.
Also, most shells let you define aliases in a user-level rc file to shorten the commands you use constantly. For example, freeze “start with auto-approve plus a specified workspace” into two letters:
# Use only in isolated/container environments; never enable globally on a daily-driver hostai-local() {ai-cli --workspace ./ --auto-approve-readonly "$@"}
Decide actively which dangerous shortcuts you’ll allow yourself and which safe ones you want in muscle memory — rather than patching this up after something goes wrong.
2.2 Input keys: a stray keystroke costs more than you think
Enter-to-send by default is the most underrated trap. A half-written prompt, or a constraint you hadn’t finished adding, gets sent by a single accidental touch; the model starts working from incomplete intent, you have to interrupt and restart, and the interruption itself burns context.
There are three ways to handle it, from least to most invasive:
- Let the tool do a terminal adaptation. Many CLI assistants ship terminal configuration or key-remapping capability. Run it once, and you can insert a newline with a chord (for example Option/Alt + Enter) while Enter still sends. Note that this kind of adaptation usually requires closing and reopening all terminal windows to take effect — skipping the restart is the most common reason a change “doesn’t work.”
- Customize your terminal’s key bindings. In the terminal emulator’s keyboard settings, bind newline to a two-hand chord. The odds of a single finger triggering it by accident drop sharply.
- Externalize long prompts. For task descriptions longer than a dozen lines, don’t type them into the terminal by hand. Write them into a draft file, review it, then have the tool read it. This solves a second problem at the same time: task descriptions become reusable, versionable, and reviewable by teammates.
2.3 Distinguishing concurrent sessions: naming beats color
Running three or four sessions at once is normal: one investigating a production error, one writing unit tests, one updating docs. With enough windows you will inevitably switch to the wrong one, and then you feed context from task A into task B and get self-contradictory answers.
Use two layers of defense together:
- Visual layer: most tools let you assign a theme or accent color per session, so you can tell “the red bug-fix session” from “the blue documentation session” at a glance. A coarse-grained signal that is visible instantly.
- Naming layer: give terminal tabs a fixed naming convention, such as
project-branch-purpose— for exampleorder-svc-feat/refund-debugging. Color is for other people; naming is for you and your teammates, and it also prevents total amnesia when a window is restored.
One rule matters more than any technique: one session serves exactly one goal. When the goal changes, the correct action is to open a new session and re-feed a trimmed context, not to stack a new task onto a long conversation.
3. Context engineering: make it actually understand your project
3.1 A project-level context file: the highest-leverage step
This is the single most valuable recommendation here. Keep a context document at the project root (the common convention is a Markdown file such as AGENTS.md, docs/ai-context.md, or a name your team defines); the tool reads it automatically on startup. Without it, you re-explain the project structure in every conversation. With it, style drift and “correcting the same mistake repeatedly” drop noticeably.
What goes in it? Four categories — omitting any one of them costs you several times the effort in corrections:
- Project purpose and stack: one sentence on what the business does, plus language/framework/runtime versions, package manager, database and cache. Version information matters especially: it directly determines whether the model generates a current API or a deprecated pattern.
- Directory structure and module boundaries: what each top-level directory is responsible for, which directories are generated (do not hand-edit), and the allowed direction of cross-module dependencies.
- Coding conventions and hard constraints: naming, error handling, logging, comments, commit message format, plus explicit prohibitions (for example “never build SQL by string concatenation in business code” or “no new global state”).
- Common commands: install, run, build, test, lint, format — one per line. This alone eliminates a lot of “guess the command” back-and-forth.
The anti-pattern is worth remembering too: don’t paste an entire architecture document or thousands of lines of code snippets into it. Context is not a dumping ground; the fuller you stuff it, the worse instruction adherence gets and the lower your hit rate. Write rules so they are executable and verifiable, not adjectival:
# Project Context## Stack- Node.js 20 + TypeScript 5.x, package manager pnpm- Persistence: PostgreSQL 16; cache: Redis 7; accessed through the repository layer, business code must not connect directly## Directory conventions- src/modules/<domain>/: business modules, cohesive per domain; importing another module's internal is forbidden- src/generated/: auto-generated, do not hand-edit; change the source definition and regenerate- tests/: mirrors the src tree, test file suffix .spec.ts## Hard constraints (must follow)1. Never build SQL by string concatenation in the business layer; always go through repository interfaces2. New dependencies must state their reason and scope of use; never pull in a whole package for a single function3. Error handling uses AppError subclasses exclusively; no bare `throw new Error`4. Every exported function needs an explicit return type; do not rely on type inference## Common commands- Install: pnpm install- Dev: pnpm dev- Test: pnpm test- Full check: pnpm lint && pnpm typecheck && pnpm test
3.2 Splitting rules: from one big file to layered references
Once rules pass a hundred lines or so, a single file starts to fall apart: any edit requires scanning the whole thing, information for different roles gets mixed together, and review can’t tell what actually changed.
A better structure is an index-and-principles main file that references details split into per-domain files:
.ai/├── index.md # General principles + reference index├── frontend.md # Frontend: components, state, styling, accessibility├── backend.md # Backend: layering, transactions, concurrency, error codes├── testing.md # Test strategy: unit/integration boundaries and coverage requirements└── release.md # Commit messages, branch strategy, release checklist
This yields three direct benefits: changes stay local (you only touch the relevant file), reuse (frontend rules can be shared across projects while business constraints travel with the project), and reviewable diffs (reviewing a rule diff is far easier than reviewing one long document).
3.3 Workspace directories: replace repeated commands with configuration
Multi-repo work is common: the main project lives in one directory, while local dependency packages, SDK copies, and services for joint debugging live in another. Most tools can only append a directory within the current session, so every new session means adding it again — pure repetitive labor.
Put your frequently used workspace directories into project-level or user-level configuration and solve it once. Illustrative structure:
{"permissions": {"additionalDirectories": ["/work/projects/order-service","/work/libs/shared-types"]}}
There’s an easily overlooked distinction worth emphasizing:
- Personal configuration (local environment paths, personal preferences, machine-specific paths) → goes in user-level config and is not committed. Your home directory path means nothing to teammates and will only create conflicts in the repo.
- Shared team configuration (allowed commands, directory conventions, prohibited operations) → goes in project config and is committed with the code. It should be reviewed and versioned as part of the team agreement, not someone’s private setting.
3.4 Context budget: actively fight “the longer the conversation, the dumber the model”
One widely observed industry phenomenon: the longer the context, the weaker the model’s adherence to earlier instructions, and compaction/summarization loses detail along the way. So “one session until the job is done” is an illusion of efficiency.
Three workable rules:
- Keep tasks small: one session solves one verifiable goal. Wrap up as soon as the goal is met.
- Externalize reference material: when it needs a lot of background, have it read files or search on demand instead of pasting the full text into the conversation. Files can be cited precisely; conversation history only gets fuzzily summarized.
- Advance long tasks in three stages: stage one, read the code only and produce a plan without touching files — you approve the plan; stage two, execute the changes; stage three, run the full check suite and self-review. Separating “think it through” from “start typing” reduces rework cost by an order of magnitude.
4. Observability: turn hidden cost into numbers you can see
4.1 Watch three metrics
Whatever you monitor with — a built-in terminal status line, a usage query command, a third-party HUD plugin, or usage data piped into a log service — at minimum track:
- Context usage ratio: the most important one. Near the ceiling, compaction quality degrades, so you should compact or switch sessions proactively rather than waiting for the tool to handle it.
- Per-session and per-day usage: a sudden spike usually means the task wasn’t split, or you’re having the model re-read the same large files repeatedly.
- Cost per task: record the usage for “one verifiable requirement completed” to establish an optimization baseline and enable horizontal comparison across a team.
If usage data stays inside the terminal, trend analysis is hard. More mature teams ship invocation logs (after redaction) to a log service, add a dashboard, and set threshold alerts on daily usage, error rate, and timeout rate — all of which can reuse your existing monitoring and alerting stack rather than building a new one.
4.2 A practical trick: install a “canary” in the session
Declining instruction adherence is gradual and hard to notice in the moment. The fix is to plant a low-cost, easy-to-verify behavioral requirement in your rules and use it as a canary for session health.
For example, require every reply to start with a fixed marker, or require it to address you with a specific name throughout:
## Session conventions- The first line of every reply must be: `[ORDER-SVC] `- If a step cannot be completed, explain why first; skipping is forbidden
The principle is simple: this is a nearly zero-cost instruction that must be executed every turn. Once it starts dropping the marker, its attention budget has been consumed to the point where it can no longer reliably follow the simplest constraint — which usually corresponds to a context that’s too long, polluted by large amounts of irrelevant content, or has just been through a poor-quality compaction. The correct action then is to wrap up and start a new session immediately, not to keep asking a degraded session to modify code.
The same idea extends to a “recite before you start” habit: have it output the current list of hard constraints before acting, so one glance tells you whether it remembers the version of the rules you actually wrote.
4.3 Turn every correction into a rule
Post-mortems on your own mistakes are, in my view, the highest-return activity. The practice: the second time you correct the same class of problem, immediately write it up as a rule and add it to the corresponding file under .ai/. Rules grown out of errors survive into new sessions; verbal corrections only burn the current context.
There’s a simple test for whether a rules file is healthy: have you edited it in the last month? A rules file that never changes has usually drifted away from the project.
5. From personal tricks to team standards
Personal configuration improves your own throughput, but durable gains come from turning it into an engineering convention.
Commit rules files and review them. Context documents, permission lists, and prohibited directories all belong in version control, evolving with the project. When someone joins, reading this file gives them the same AI collaboration baseline as senior members, cutting onboarding cost significantly.
Leave quality verification to the pipeline, not to the model’s good intentions. This is the most important principle: formatting, linting, type checking, unit tests, builds, and security scans all belong in the CI pipeline as merge gates. The model generates; the gate verifies. Any expectation that “the model will guarantee it doesn’t break tests” is unreliable, whereas “run the tests and look at the result” is deterministic.
Least privilege and security boundaries. Define explicitly which files the AI must not read (secrets, credentials, production config, customer data) and express that as a config-level deny rather than a verbal agreement. Redact sensitive information before it enters context; no credential ever enters a conversation history or a log. Leave a trail for destructive operations — timestamp, session, operator, and scope of change — so incidents can be traced.
At enterprise scale, govern AI collaboration as part of your existing engineering system. Context assets can be version-managed in repositories on your code hosting platform; usage and invocation logs can feed your log service for auditing and dashboards; execution environments should be containerized so platform isolation limits the blast radius; shared rule libraries and reference material can be distributed from object storage or a central repository. If you want managed building blocks for these pieces, Baidu AI Cloud offers general-purpose capabilities such as model services, log services, monitoring and alerting, container platforms, and object storage that can cover the above — the actual choice still depends on your technology stack and compliance requirements.
6. One-page rollout checklist
Day one
- Create a context file at the project root: stack, directory boundaries, hard constraints, common commands.
- Once rules exceed one screen, split them into an
.ai/subdirectory and keep only the index in the main file. - Configure permission tiers: auto-approve read-only, ask on writes, deny deletes/production/credentials.
- Run a terminal key adaptation once, bind newline to a chord, restart the terminal, and verify it took effect.
- Distinguish parallel sessions with color plus a
project-branch-purposenaming convention.
Weekly review
- Check context usage and usage trends; identify sessions with abnormal growth.
- Plant a canary marker and use it to decide when to switch sessions.
- Write every problem you had to correct repeatedly this week into the rules files.
Long term
- Commit, review, and version the context file and permission config.
- Put every verification gate in the pipeline; quality does not depend on the model’s discretion.
- Use different permission policies for isolated and host environments, and keep destructive operations logged and auditable.
In one sentence: the CLI assistant ships with defaults optimized for “absolute safety,” while your daily development is optimized for “smooth delivery.” Once you configure permission tiering, layered context, and visible usage, it stops being a toy you have to watch and becomes an executor you can trust.
