Posts tagged with “dev”

Three-agent caucus before opening a design ticket

A solo design pick ships a brittle ticket. A small jury forces you to surface the hard rule, spot when "labeling problem" is really a data gap, and converge to ready instead of refining.

When to convene

Caucus is for design questions with real divergence, where the goal is a ready ticket. Skip it for one-line facts (one agent), pure execution (just do it), or where there's only one sane option.

Composition: two advocates + one skeptic

Two advocates pick majority-wins and call it consensus. Add an impartial chair/skeptic whose job is failure modes and a decision criterion. Three is convention, not doctrine — two advocates + one chair works; one advocate + one skeptic works; three advocates doesn't.

The three prompts share one SCENARIO

Each agent gets the same verified facts (IDs, code-line refs, observed outcomes). Letting each agent self-research drifts the facts and you can't synthesize. Then each gets a distinct lens and a boundary:

SCENARIO (verified): ...
CONTEXT (read these files, these are the related issues, decision-maker's steer): ...
YOUR LENS: [extend-X | separate-track | skeptic/chair]
BOUNDARY: read-only, no code, ≤500 words, return VERDICT line.

Use VERDICT as the last line so synthesis can grep it. Pick lens names that frame the choice (extend-#122 advocate, separate-track advocate, skeptic/chair) — the framing shapes the output.

Run the three concurrently in background; wait for all three before synthesizing. Sequential loses the wall-clock and the parallel disagreement.

Synthesize

  1. Consensus → hard rule. Whatever all three agree on goes in as a non-negotiable constraint, not a soft preference.
  2. Divergence → chair's criterion + compatibility points. The skeptic's job is to name when each advocate breaks. Often the advocates are compatible once you apply the criterion (e.g. fallback rule + quarantine path, gated by data-first verification).
  3. Lock → ready. The whole point of the caucus is to converge. If you converged, open ready and release the lock. Don't re-refine.

Traps

  • Treating caucus as solution. Three agents return positions; you synthesize. Letting them agree with each other is a fragile consensus.
  • No skeptic. Two advocates ship majority-wins.
  • No shared SCENARIO. Facts diverge, synthesis collapses.
  • No VERDICT line. Agent writes an essay, you can't grep.
  • Caucus for execution. Locating a bug, writing a doc, picking a flag value — none of these need a jury.
  • "Three" as dogma. Two advocates + one chair is the minimum useful shape. Three advocates is the maximum useless shape.

The synthesis rule that matters most: when the chair says "this is a data problem disguised as a labeling problem," that's the reframe — verify the data first, then design the fallback. Without the skeptic, you'd have built a clever rule on top of an incomplete export and shipped proxy labels.

Reviewing a branch: `git diff` wants three dots, `git log` wants two

Same task — "show me what this branch changed" — but the two tools take opposite dot conventions. Get it backwards and your review fills with commits the author never touched.

The diff: use three dots

git diff origin/main...HEAD

Three-dot diff is git diff $(git merge-base origin/main HEAD) HEAD — it diffs against the branch point, not the tip of main. That's exactly what you want: the author's delta and nothing else.

The nice property: it's immune to origin/main moving forward. New commits landing on main after the branch point aren't ancestors of HEAD, so they don't shift the merge-base. The diff stays clean.

The trap is a stale base, not a newer one. If your local main is older than the branch point, the merge-base slides back to an older ancestor and the diff swallows unrelated upstream commits — making the author look like they changed far more than they did. So fetch first:

git fetch origin
git diff origin/main...HEAD

Want to see the branch point itself? git merge-base origin/main HEAD.

The log: use two dots

git log origin/main..HEAD

Two-dot log = commits reachable from HEAD but not from origin/main = the branch's own commits, exactly.

Don't reach for three dots here out of habit — git log A...B is the symmetric difference, so it also lists the commits main picked up that HEAD doesn't have. That's the noise you were trying to avoid.

So: diff three-dot, log two-dot. Different tools, opposite defaults, same job.

VARCHAR2(4000 CHAR) Might Not Store 4000 Characters

VARCHAR2(4000) means 4000 bytes, not characters. Most people know this. What's less obvious: VARCHAR2(4000 CHAR) doesn't guarantee 4000 characters either.

Under the default MAX_STRING_SIZE=STANDARD, the hard column cap is 4000 bytes regardless of whether you declared BYTE or CHAR. In AL32UTF8, a Chinese character takes ~3 bytes, so VARCHAR2(4000 CHAR) on a column storing CJK text will fail once the actual byte count exceeds 4000 — around ~1333 characters in.

To actually store 4000 CJK characters in a single VARCHAR2, the instance needs MAX_STRING_SIZE=EXTENDED (12c+), which raises the limit to 32767 bytes. This is not the default — not even in 19c — and it's a one-way migration that requires running utl32k.sql in upgrade mode. Oracle keeps it off by default precisely because it changes data dictionary behavior and breaks compatibility.

Quick check for your instance:

SELECT value FROM v$parameter WHERE name = 'max_string_size';

STANDARD = 4000-byte ceiling. EXTENDED = 32767-byte ceiling.

Why Vite dev server needs a proxy (and production doesn't)

In development, your frontend runs on localhost:5173 and your API server on localhost:3000. The browser blocks cross-origin requests — that's CORS. Vite's dev proxy solves this by forwarding /api/* requests to the backend, making them look same-origin to the browser:

// vite.config.ts
server: {
  proxy: {
    '/api': 'http://localhost:3000'
  }
}

In production this proxy disappears. The built frontend is just static files (HTML/JS/CSS) — no port, no process. Nginx or a CDN serves them, and reverse-proxies /api/* to the backend the same way Vite did in dev:

user → Nginx :80
         ├── /api/*  → backend :3000
         └── /*      → dist/ static files

One port from the user's perspective, no CORS issue. The backend port is always real and needed; the frontend "port" only exists during development because Vite's dev server is a live process.