Merge remote-tracking branch 'origin/master' into codex/project-instruction-files
# Conflicts: # AGENTS.md # docs/config-catalog.md # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.md # docs/event-producer-consumer.md # docs/persistence-catalog.md # docs/rfc/implemented/architecture/2026-07-05-reconstructable-requests.md # docs/rfc/implemented/feature/2026-06-15-code-mode.md # docs/rfc/implemented/feature/2026-06-30-hook-bridges.md # docs/rfc/implemented/feature/2026-06-30-interception-seams.md # docs/rfc/implemented/feature/2026-07-08-repeat-tool-guard.md # docs/rfc/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md # examples/AGENTS.md # examples/acp-agent/cordis.yml # examples/acp-agent/tests/acp.snapshot.ts # examples/echo-agent/cordis.yml # examples/sandbox-acp-agent/cordis.yml # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-core/README.md # packages/core/agent-core/src/index.ts # packages/core/agent-loop/README.md # packages/core/agent-loop/src/loop.ts # packages/core/agent-loop/tests/interception.spec.ts # packages/core/agent/src/types.ts # packages/core/tools/README.md # packages/core/tools/src/code-mode.ts # packages/core/tools/src/index.ts # packages/fs/fs-local/src/index.ts # packages/fs/fs/README.md # packages/fs/fs/src/index.ts # packages/guard/repeat-tool-guard/README.md # packages/guard/repeat-tool-guard/src/index.ts # packages/hooks/hooks-claude/src/index.ts # packages/hooks/hooks-codex/src/index.ts # packages/ui/acp-agent/src/index.ts
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
The **default executor-less, UI-less agent spine** as ONE Cordis bundle plugin. It loads the fixed set of services every harness agent needs, including the local skill provider, and forwards the loop's `agents` list as its own config — so an app package composes a working agent by adding only a front door and the swappable backends.
|
||||
|
||||
This is the package to read to see **the whole plugin tree at once** and the canonical teaching map for the shared spine.
|
||||
Read this package for the whole plugin tree and its composition order.
|
||||
|
||||
## The tree it loads
|
||||
|
||||
@@ -17,7 +17,7 @@ This is the package to read to see **the whole plugin tree at once** and the can
|
||||
@deepseek-ai/dsh-skill skill provider registry
|
||||
@deepseek-ai/dsh-skill-local local filesystem skill provider
|
||||
@deepseek-ai/dsh-agent agent registry + agent/* event vocabulary
|
||||
@deepseek-ai/dsh-invariants dev-mode event-contract assertions
|
||||
@deepseek-ai/dsh-invariants runtime event-contract assertions
|
||||
@deepseek-ai/dsh-tool-bash the model-facing bash/bash_output/bash_kill schemas
|
||||
@deepseek-ai/dsh-workspace-context AGENTS.md/CLAUDE.md workspace context loader
|
||||
@deepseek-ai/dsh-tool-skill session-prefix skill catalog + model-facing loader schema
|
||||
@@ -48,4 +48,13 @@ The bundle FORWARDS each field to the child that owns it: `agents` to `agent-loo
|
||||
|
||||
## Why a code bundle, not a shared YAML include
|
||||
|
||||
A YAML include can dedupe config, but it cannot own a `bin` or enforce front-door coupling. The app packages own that cluster, so the default ACP shape contains no stdout logger entry for a leaf to reproduce; a deployment can still add a sibling logger explicitly. Services register in the root store keyed by their isolate symbol, so a child loaded here is visible to the bundle's siblings (the leaf's adapter and executor); Cordis gates every read on `inject`, never on load order.
|
||||
A YAML include can deduplicate config but cannot own a bin or provide front-door defaults. App packages make stdout-safe ACP wiring the default, though a leaf can still add an unsafe logger. Bundle children register services in the root isolate-keyed store, so injected leaf siblings see them without load-order coupling.
|
||||
|
||||
## Model Experience
|
||||
|
||||
Indirectly, through `dsh-system-prompt`, `dsh-tool-skill`, `dsh-tool-bash`, and `dsh-tools`, which this bundle mounts without adding model-bound wrapper content.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **The spine set is fixed in code** — `apply()` mounts every child unconditionally (including `tool-bash`); no config excludes or replaces one, so swapping the loop or dropping a spine member means composing a different bundle.
|
||||
- **`dsh-invariants` mounts unconditionally** — this bundle has no toggle, so every composition using it pays the dev-mode relational assertions; Session's always-on validation and freezing are separate.
|
||||
|
||||
@@ -1,47 +1,10 @@
|
||||
/**
|
||||
* The default executor-less, UI-less agent spine as ONE bundle plugin.
|
||||
*
|
||||
* Loads the fixed set of services every harness agent needs — `timer`, the LLM
|
||||
* service, the session store, system-prompt assembly, the tool registry, the
|
||||
* skill registry plus local skill provider, the agent registry, the dev-mode
|
||||
* invariants, the model-facing `bash` and `skill` tool schemas,
|
||||
* workspace-context loading, and the concrete `agent-loop` — and forwards the
|
||||
* loop's `agents` list as its OWN config (default `[]`), so each app supplies
|
||||
* its own pre-created agents.
|
||||
*
|
||||
* It is deliberately NOT the whole app: the swappable choices stay OUTSIDE the
|
||||
* bundle, picked by whatever loads it.
|
||||
* - the LLM ADAPTER (`llm-deepseek`/`llm-pi-ai`/`llm-replay`) — the bundle
|
||||
* ships the abstract `llm` service + `tool-bash` consumer schema; the leaf
|
||||
* registers a concrete adapter on `ctx.llm`.
|
||||
* - the bash EXECUTOR (`bash-local` or a sandboxed impl) — the bundle ships
|
||||
* the `bash` tool consumer; the leaf provides `ctx.bash`.
|
||||
* - the PRESENTATION (stdio UI / ACP bridge / a logger) and the per-app infra
|
||||
* (a console logger, `hmr`) — these are the coupled "front-door cluster" the
|
||||
* app packages ({@link @deepseek-ai/dsh-stdio-agent},
|
||||
* {@link @deepseek-ai/dsh-acp-agent}) bake in, NOT the shared spine.
|
||||
* - additional SKILL PROVIDERS; the bundle ships the local filesystem provider
|
||||
* because local skills are default agent behavior, while embedded or remote
|
||||
* providers remain deployment choices.
|
||||
*
|
||||
* This is the interface/implementation/consumer seam at the composition level:
|
||||
* the bundle owns the shared spine, the leaf owns the backends, the app package
|
||||
* owns the front door. `timer` is in the spine (common to every front door — it
|
||||
* writes nothing to stdout); the console logger is NOT (it writes to stdout,
|
||||
* which the ACP bridge reserves for its JSON-RPC channel).
|
||||
*
|
||||
* Services register in the root store keyed by their isolate symbol, so a child
|
||||
* loaded here via `ctx.plugin(...)` is visible to the bundle's SIBLINGS (the
|
||||
* leaf's adapter and executor). Cordis gates every read on `inject`, never on
|
||||
* load order, so the fixed child set resolves regardless of which entry loads
|
||||
* first.
|
||||
*
|
||||
* Plugin export shape: named `name`/`Config`/`apply`, NO default export — the
|
||||
* cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray
|
||||
* default would collapse the module to the bare `apply` function and drop the
|
||||
* `Config` schema (see docs/postmortem/0001). The keyless Loader-path smokes in
|
||||
* the app packages guard this end-to-end.
|
||||
*
|
||||
* Default executor-less, UI-less agent spine. It bundles the common services,
|
||||
* concrete loop, local skill and workspace-context providers, and model-facing
|
||||
* bash/skill consumers;
|
||||
* deployments still choose the LLM adapter, bash executor, and presentation.
|
||||
* The plugin intentionally exposes named exports only because Loader default
|
||||
* unwrapping would discard its `Config` schema (see docs/postmortem/0001).
|
||||
* @module @deepseek-ai/dsh-agent-core
|
||||
*/
|
||||
|
||||
@@ -74,17 +37,15 @@ export interface SkillConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle config: each field forwarded verbatim to the child that owns it —
|
||||
* `agents` to the agent loop (an app that pre-creates no agents, like the ACP
|
||||
* bridge, simply omits it), `persona` and `toolOrder` to the system-prompt
|
||||
* plugin (the deployment's persona section and the explicit model-facing tool
|
||||
* order), the `tools` object to the tool registry (its presentation `mode`),
|
||||
* `skills` to the skill registry/local provider/tool consumer, and
|
||||
* `workspaceContext` to the workspace-context plugin. Workspace context must
|
||||
* be configured explicitly with a byte budget or disabled with `false`; the
|
||||
* other fields remain optional inputs whose owner schemas supply defaults. The
|
||||
* schema is the INTERSECTION of the owners' own schemas (with child schemas
|
||||
* nested under their bundle keys), so validation and defaulting cannot drift.
|
||||
* Bundle config: each field forwarded verbatim to the child that owns it — `agents` to the
|
||||
* agent loop (an app that pre-creates no agents, like the ACP bridge, omits it),
|
||||
* `persona` and `toolOrder` to the system-prompt plugin (the deployment's persona section and
|
||||
* the explicit model-facing tool order), the `tools` object to the tool registry (its
|
||||
* presentation `mode`), `skills` to the skill registry/local provider/tool consumer, and
|
||||
* `workspaceContext` to the workspace-context loader.
|
||||
* The schema intersects the owners' schemas, which supply defaults for every
|
||||
* optional input and keep validation from drifting; workspace context instead requires an
|
||||
* explicit byte budget or `false` because it changes model-visible input.
|
||||
*/
|
||||
export interface Config {
|
||||
/** The agent-loop `agents` list (see dsh-agent-loop's `Config`). */
|
||||
@@ -133,12 +94,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
ctx.plugin(Timer)
|
||||
ctx.plugin(LlmService)
|
||||
ctx.plugin(SessionStore)
|
||||
// The forwarded fields are validated + defaulted by this bundle's intersected
|
||||
// schema before apply runs, so the ?? fallbacks only narrow the
|
||||
// optional-input TYPES — they mirror the owners' schema defaults, never
|
||||
// introduce different ones. toolOrder has no owner-supplied default value —
|
||||
// ABSENT means "lexicographic order" — so it is forwarded conditionally
|
||||
// rather than via ??.
|
||||
// Owner schemas resolve defaults; forward toolOrder only when explicitly set.
|
||||
ctx.plugin(SystemPrompt, {
|
||||
persona: config.persona ?? '',
|
||||
...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {},
|
||||
|
||||
@@ -309,15 +309,8 @@ describe('dsh-agent-core bundle', () => {
|
||||
})
|
||||
|
||||
it('has the namespace-plugin export shape (no stray default) so the Loader keeps name/Config/apply', () => {
|
||||
// Postmortem 0001 guard: a stray `export default apply` makes the Loader's
|
||||
// `unwrapExports` (`exports.default ?? exports`) collapse the module to the
|
||||
// bare `apply` function, DROPPING the named `name`/`Config`. This package has
|
||||
// no `inject` export (it mounts children that carry their own), so that
|
||||
// collapse would NOT crash at load — the plugin would boot but silently lose
|
||||
// its config schema. This bundle is also never Loader-unwrapped by any smoke
|
||||
// (the apps import it directly; the mount test namespace-mounts it), so this
|
||||
// is its ONLY export-shape guard. Assert directly AND through the real
|
||||
// `unwrapExports` so adding `export default` to src/index.ts fails here.
|
||||
// A default export would make `unwrapExports` collapse this inject-less namespace and silently
|
||||
// drop `name`/`Config`. Apps import the bundle directly, so this is its Loader-shape guard.
|
||||
expect('default' in agentCore).toBe(false)
|
||||
expect(typeof agentCore.apply).toBe('function')
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/**
|
||||
* Negative-path tests for the config catalog generator (`scripts/gen-config-catalog.ts`).
|
||||
*
|
||||
* The generated catalog is frozen by a regenerate-and-diff freshness gate, so
|
||||
* the freshness half is exercised by `pnpm run verify-config-catalog` in CI.
|
||||
* What a freshness diff CANNOT prove is that the generator REJECTS malformed
|
||||
* source the way it promises to — an unclassifiable package, an undocumented
|
||||
* config field, a schema key the config type does not declare, or a referenced
|
||||
* type name that resolves nowhere. These tests drive `collectConfigCatalog()`
|
||||
* against synthetic fixture packages to prove each guard fires (and that
|
||||
* well-formed packages classify and extract correctly), mirroring the
|
||||
* negative tests for gen-cordis-catalog. The spec lives in this package
|
||||
* because agent-core is the config-composition plugin (its schema is the
|
||||
* intersection of its children's), the shape the generator's cross-package
|
||||
* folding exists for.
|
||||
*/
|
||||
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
||||
|
||||
Reference in New Issue
Block a user