Every skill added another paragraph to the system prompt — until it became a junk drawer. We moved to on-demand injection, and fixed CJK rendering and "one search, one conclusion" along the way
The old approach: embed the whole skill catalog into the system prompt at startup. Every skill added a paragraph. Dozens of skills later, every request carried paragraphs it didn't need — costing tokens and diluting the model's attention.
Now: each turn runs a retrieval pass first, and only the top hits (name + description) get injected. If nothing hits, we fall back to the full catalog — so a failed retrieval never costs capability.
A concrete example: a user asks in Feishu "how do I wire an approval flow into DingTalk?" That turn needs two skill descriptions — DingTalk integration and approval-flow setup — not forty unrelated ones covering drawing, voice, and file handling.
Retrieval-augmented generation isn't new. But applying it at the "skill injection" slot turned out to be more effective than we expected: shorter prompts, tighter attention.
We gave our agent the ability to produce diagrams — flowcharts, architecture charts. Then we hit a trap: the diagram engine exports PNG at 1x by default. English labels survive; CJK labels turn to mush.
| Export scale | Measured size (3-node diagram) | CJK labels |
|---|---|---|
| 1x (default) | 124×224 px | ❌ strokes blur together |
| 3x | 367×667 px | ✅ readable |
So we now export at 3x by default. There's no theory behind the number — it's what our tests showed. CJK glyphs are stroke-dense; at low resolution the strokes fuse. Don't trust the default. Measure with your own diagrams.
Another trade-off: we kept one diagram engine and cut the second code path we had experimented with. Two parallel engines double both maintenance and failure modes.
This one is a behavior change, not a feature. We added a discipline to the system prompt: concluding after a single search is not allowed — re-query with different wording at least once.
The reason is practical. In search tasks, coming up empty on the first try usually means the keywords were wrong, not that "it doesn't exist". Answering "no results" after one query is the most common failure mode we saw.
The discipline also requires separating two failure kinds: "wrong criterion" and "wrong data". When a user asks "does anything support X?" and the search comes up empty, the agent must first ask whether its own search terms were off, instead of declaring "nothing supports X". We wired this as a switchable gate so it can be turned off per scenario.
Most of the recent work was stability, which doesn't compress well into a blog post. Two items worth mentioning:
Everything above is internal capability and stability work. No customer stories, no usage data — the product is young, and we don't invent numbers we don't have.
If you're building something similar, two points may be worth borrowing: make skill injection retrieval-based; and measure CJK rendering resolution yourself — don't trust defaults.