Skip to content

feat(appkit): auto-discover code agents from server/agents/ - #533

Open
MarioCadenas wants to merge 6 commits into
mainfrom
agents-discovery-dx
Open

feat(appkit): auto-discover code agents from server/agents/#533
MarioCadenas wants to merge 6 commits into
mainfrom
agents-discovery-dx

Conversation

@MarioCadenas

@MarioCadenasMarioCadenas commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

What

Every agent is now a folder under server/agents/<id>/, discovered at startup — agent.md for a markdown agent, agent.ts (export default createAgent(...)) for a code agent. The folder name is the id, so the plugin call collapses to agents() with no map and no imports, and neither form restates its id.

- // server/agents/helper.ts- export const helper = createAgent({ name: 'helper', instructions, tools });- // server/server.ts- import { helper } from './agents/helper';- agents({ agents: { helper } }) // id restated; hand-built map+ // server/agents/helper/agent.ts ← id IS the folder name+ export default createAgent({ instructions, tools });+ // server/server.ts+ agents() // no import, no map

Markdown and code agents live side by side under the same root; a markdown agent.md and code agent.ts are just two entry-file kinds.

How

  • Code: built-first resolution (NODE_ENV-independent). Compiled output wins over source, so a bundled server never import()s a .ts (plain Node can't). Dev (tsx) loads server/agents/<id>/agent.ts; a build loads the compiled dist/agents/<id>/agent.js (or build/agents). A relative custom dir is resolved the same way; an absolute dir is scanned verbatim.
  • Markdown: read from source in both dev and prod (it's data, not compiled).
  • Branded detection.createAgent stamps a non-enumerable Symbol.for("appkit.agent"); the loader keeps branded exports and skips helpers/chunks. A folder is an agent iff it holds agent.md or agent.ts; one holding both errors at boot (one kind per id).
  • Prod bundling (the trap this avoids). A runtime scan of a dynamic path is dropped by the bundler, so the build lists server/agents/*/agent.ts as entries and the compiled dist/agents/*/agent.js exist for the scan. That wiring now ships in the package (below), not hand-copied into every scaffold.

New: @databricks/appkit/tsdown build preset

A scaffolded app's tsdown.server.config.ts is a one-liner:

import{appkitServerConfig}from'@databricks/appkit/tsdown';exportdefaultappkitServerConfig();

appkitServerConfig() auto-detects server/agents/ and adds the entry glob + clean only when code agents exist; object overrides merge with intent (entry unioned so the glob can't be dropped, external composed), and a function form gives full control. The build wiring lives in the package, so it reaches existing apps on upgrade instead of being maintained per-scaffold.

Backward compatibility

  • Markdown still under config/agents/ is read as a deprecated fallback — merged per-agent with server/agents (new location wins on an id clash), with a one-time deprecation warning. Cross-location sub-agent refs resolve one direction (a parent moved to server/agents can reference a child still in config/agents).
  • agents({ agents: { ... } }) map still works, one-time deprecation warning; discovery wins on an id clash.
  • createAgent({ name }) still honored (the registry key wins).
  • Cross-kind sub-agents: a markdown parent can delegate to a code sibling folder and vice versa, resolved by folder id.

Also in this PR

  • Migrates the dev-playground (8 agents → server/agents/<id>/ folders) and the template (helper/agent.ts + planner/agent.md, tsdown one-liner).
  • Docs: rewrites the agents plugin page for the folder model and adds an "Adding agents to an existing app" section (markdown = no build change; code = adopt the preset).
  • Retires the never-shipped codeAgentsDir config knob (unified into dir, default server/agents).

Verification

  • pnpm -r typecheck clean · oxlint + oxfmt clean · appkit + shared 4392 tests pass · pnpm build + pnpm docs:build succeed.
  • E2E (the acceptance gate): discovery verified in both npm run dev (tsx, .ts) and a bundled build (node, compiled dist/agents/<id>/agent.js), plus the config/agents fallback and built-first paths under test.

Notes

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 887 KB (+14 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

distrawgzip
JS (runtime)905 KB (+13 KB)317 KB (+5.1 KB)
Type declarations344 KB (+4.3 KB)120 KB (+1.8 KB)
Source maps1.8 MB (+25 KB)596 KB (+9.5 KB)
Other11 KB3.7 KB
Total3.0 MB (+42 KB)1.0 MB (+16 KB)
Per-entry composition (own code — deps external (as shipped))
EntryInitial (gz)Lazy (gz)Total (gz)node_modules (min)Own code (min)
.88 KB2.5 KB91 KBexternal288 KB
./beta50 KB (+1.7 KB)457 B51 KB (+1.7 KB)external147 KB (+4.3 KB)
./testing17 KB0 B17 KBexternal50 KB
./tsdown520 B0 B520 Bexternal813 B
./type-generator21 KB0 B21 KBexternal61 KB

Chunks:

EntryChunkLoadSize (gz)
.index.jsinitial84 KB
.utils.jsinitial4.0 KB
.remote-tunnel-manager.jslazy2.5 KB
./betabeta.jsinitial34 KB
./betastream-manager.jsinitial5.8 KB
./betawide-event-emitter.jsinitial3.2 KB
./betadatabricks.jsinitial3.0 KB
./betaconfiguration.jsinitial2.1 KB
./betaservice-context.jsinitial1.3 KB
./betaclient.jsinitial434 B
./betaclient-options.jsinitial220 B
./betasupervisor-api.jslazy192 B
./betadatabricks.jslazy142 B
./betaindex.jslazy123 B
./testingindex.jsinitial17 KB
./tsdownindex.jsinitial520 B
./type-generatorindex.jsinitial21 KB

@databricks/appkit-ui

npm tarball (packed): 348 KB (+2 B) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

distrawgzip
JS (runtime)394 KB132 KB
Type declarations228 KB83 KB
Source maps764 KB252 KB
CSS16 KB3.2 KB
Total1.4 MB471 KB
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
EntryInitial (gz)Lazy (gz)Total (gz)node_modules (min)Own code (min)
./js5.3 KB49 KB55 KB208 KB14 KB
./js/beta20 B0 B20 B0 B0 B
./react432 KB49 KB481 KB1.3 MB177 KB
./react/beta1.0 KB0 B1.0 KB0 B1.9 KB

Chunks:

EntryChunkLoadSize (gz)
./jsindex.jsinitial5.2 KB
./jschunkinitial120 B
./jsapache-arrowlazy49 KB
./js/betabeta.jsinitial20 B
./reactindex.jsinitial430 KB
./reacttslibinitial2.1 KB
./reactapache-arrowlazy49 KB
./react/betabeta.jsinitial1.0 KB

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 32393214595 -R databricks/appkit -n appkit-template-0.63.0-pr.440c3a5-agents-discovery-dx-533 -D appkit-pr-533 \
&& unzip -o "appkit-pr-533/appkit-template-0.63.0-pr.440c3a5-agents-discovery-dx-533.zip" -d "appkit-pr-533" \
&& databricks apps init --template "appkit-pr-533"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

@MarioCadenas
MarioCadenasforce-pushed the agents-discovery-dx branch 2 times, most recently from 45e7284 to 55e3bcdCompareAugust 18, 2026 09:04
@MarioCadenas
MarioCadenas marked this pull request as ready for review August 18, 2026 15:34
@MarioCadenas
MarioCadenas requested a review from a team as a code ownerAugust 18, 2026 15:34
…er/agents
Every agent is a folder under server/agents/<id>/ holding agent.md (markdown)
or agent.ts (code); the folder name is the id.
- Code loader scans <id>/agent.{ts,tsx,js,mjs}, built-first: a relative dir
resolves dist/<name>|build/<name> before source, an absolute dir is verbatim,
so a bundled server never imports .ts under plain Node.
- Markdown loader skips folders without agent.md so code + asset dirs coexist;
drop the RESERVED_DIRS list.
- One `dir` knob (default server/agents) feeds both loaders; codeAgentsDir
retired. config/agents is read as a deprecated fallback (per-agent merge, new
location wins, one-time warning); cross-location sub-agent refs resolve.
- Cross-kind sub-agent references resolve by folder id.
- Migrate template, dev-playground, docs, and tests to the folder layout; add
fallback / built-first / cross-dir test coverage.
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Follow-up to the review + inconsistency fan-out:
- findEntryFile now rethrows non-ENOENT/ENOTDIR errors (an unreadable agent
folder no longer silently vanishes in prod).
- Clearer discovered-vs-markdown collision message (covers the cross-root
config/agents fallback case, not just one folder).
- Template tsdown: scope clean:true to the agents case so a non-agents
scaffold's build config is unchanged.
- Docs: fix DATABRICKS_SERVING_ENDPOINT_NAME, the auto-inherit default
(off for both), cycle-rejection scope, /api/agents/approve path,
defaultAgent precedence, dir:false wording, stale-dist note, and add
the agents/generationParams frontmatter keys + toolCallTimeoutMs limit.
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Ships the server build wiring from the package so a scaffolded app's
tsdown.server.config.ts is a one-liner instead of hand-maintained config:
import { appkitServerConfig } from '@databricks/appkit/tsdown';
export default appkitServerConfig();
- appkitServerConfig(overrides?, opts?) auto-detects server/agents/<id>/agent.ts
and adds the entry glob + clean only when code agents exist.
- Object overrides merge with intent (entry unioned so the agent glob can't be
clobbered, external composed, other keys win); a function override receives
the computed base for full control.
- Dependency-free (node: builtins only) so it stays lean at build time.
- New ./tsdown export subpath (attw + publint clean); template drops its
{{if .plugins.agents}} conditional.
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Markdown agents need no build change; code agents require the server build
to emit them (dev via tsx hides this — only a bundled build breaks). Points
at the appkitServerConfig() preset as the one-line fix, notes the manual
entry-glob alternative, and the startup warning that catches a forgotten
build change.
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…ion .ts-only
From the /simplify pass:
- Extract agentDirNames() so both loaders share one folder-selection policy
(dir + symlink) instead of duplicating the subtle filter + comment.
- Code-agent source detection is .ts-only (resolveCodeAgentsDir source exts +
hasCodeAgentSources), matching the build entry glob — an agent.tsx would
otherwise load in dev but never be emitted for a prod bundle.
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…r option)
Remove the agents({ dir }) config option and make server/agents a fixed
convention. This deletes the build<->runtime dir disconnect at the root:
the tsdown build glob and the runtime scan can no longer disagree about
where agents live, since the path is now a single constant.
- resolveCodeAgentsDir drops the override param; always built-first from
server/agents (dist/agents | build/agents, then source .ts).
- config/agents/ is still read as the deprecated markdown fallback and
still emits the one-time deprecation warning.
- dir:false (disable discovery) is gone too; a map-only app works via an
absent server/agents folder.
Tests point discovery at fixtures via chdir + a symlinked server/agents
instead of an absolute dir.
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@MarioCadenas