Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ All six implement `Agent`: `store: StoreApi<SessionStoreState>`, `run(options)
- `lead` uses `childPolicy.maxDepth = 3`; `discussion`, `analyst`, and `build` use `maxDepth = 2`. Discussion may delegate Explore/Librarian.
- Lead targets Analyst/Build/Explore/Librarian; Analyst targets Explore/Librarian; Build targets Explore.
- `explore` and `librarian` have no `delegateTargets`; they are terminal read-only support agents.
- `agents/factory.ts` resolves definition-based allowed tools and removes delegation capabilities at the runtime depth boundary; SessionExecutionManager enforces each role's child policy before child creation.
- `agents/factory.ts` owns one immutable current-Agent/depth delegation capability snapshot and removes delegation tools at each definition's `childPolicy.maxDepth` or when no direct target exists. Prompt/Tool projection and SessionExecutionManager admission consume that same target/Profile/builtin-Skill authority; Provider-facing Tool schemas remain portable presentation contracts while strict internal schemas still validate execution input.
- `delegate` persists Agent, Profile, Skills, title, objective, and background choice. `resume_session` preserves that identity. Multiple Builds share general Session concurrency; there is no owned-scope or Build lease subsystem.

**Workflow Skills:**
Expand All @@ -399,8 +399,8 @@ All six implement `Agent`: `store: StoreApi<SessionStoreState>`, `run(options)
- `review-work` guides Lead review orchestration. Analyst analysis/review Skills include `analyze-work`, `review-change`, and the reserved `goal-review` final gate.
- A Skill is one package: required `SKILL.md`; optional `scripts/`, `references/`, `assets/`, and other contained resources. Its strict YAML frontmatter accepts `name`, `description`, optional `license`, `compatibility`, and `metadata`; `description` states both method and activation timing.
- Skill precedence is whole-package and strict: project `.archcode/skills/<name>/` > project `.agents/skills/<name>/` > user `~/.archcode/skills/<name>/` > user `~/.agents/skills/<name>/` > embedded builtin. Bodies and resources never merge or fall through. Reserved lifecycle builtins remain unshadowable and Agent-gated.
- Discovery (`skill_list` and available Prompt metadata) returns exactly name, description, and source. Prompt projection is bounded and reports omitted entries; `skill_list` returns digest-bound metadata pages with cursors for continuation. Entry activation (`skill_read({ name })`) returns the entry plus sorted resource descriptors; `skill_read({ name, resource })` reads exactly one listed text resource on demand. Binary assets are valid package resources but are not returned by the text-only tool.
- Invalid package candidates are surfaced as `SKILL_INVALID_PACKAGE` diagnostics. A winning invalid package fails closed; resolution never falls through to a lower-precedence package. The same winning package is claimed once for one `/skill use` logical Execution; `skill_read` uses that Execution snapshot and resume revalidates its digest.
- Discovery (`skill_list` and available Prompt metadata) returns exactly name, description, and source. Prompt projection is bounded and reports omitted entries; `skill_list` returns digest-bound metadata pages with cursors for continuation. `skill_list({ agent_type })` may inspect one currently allowed direct child's catalog for exact `delegate.skills` names, but that target page grants no parent `skill_read` authority. Entry activation (`skill_read({ name })`) returns one current-Agent entry plus sorted resource descriptors; `skill_read({ name, resource })` reads exactly one listed UTF-8 text resource on demand. Binary assets are valid package resources but are not returned by the text-only tool.
- Invalid package candidates are surfaced as `SKILL_INVALID_PACKAGE` diagnostics. A winning invalid package fails closed; resolution never falls through to a lower-precedence package. The same winning package is claimed once for one explicit `/skill use` logical Execution; an in-process resume reuses that snapshot, while process-restart recovery revalidates its persisted source/digest and fails closed on change.
- Skills remain guidance only: their package metadata and resources cannot grant tools or permissions, execute scripts automatically, change Agent/Profile/MCP/workspace scope/delegation, or grant completion authority. Scripts use only existing Bash permissions.

**MCP visibility**: User MCP servers are process-global and visible to all six
Expand Down
184 changes: 184 additions & 0 deletions docs/goals/provider-safe-delegation-skill-contract-plan-goal.md

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions docs/goals/provider-safe-delegation-skill-contract-progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Provider-safe contextual delegation and Skill contract progress

Status: complete
Started: 2026-08-10
Completed: 2026-08-10
Plan: `docs/goals/provider-safe-delegation-skill-contract-plan-goal.md`

## Execution log

### 2026-08-10 — baseline and decomposition

- Confirmed execution is isolated in Worktree `/Users/bo/.codex/worktrees/system-qa/archcode` on `codex/system-qa` at `4b2be9b8`.
- Preserved the accepted Plan Goal as the normative scope; progress and evidence are recorded only in this file.
- Confirmed the only pre-existing Worktree change is the untracked Plan Goal document.
- Reconfirmed the live blocker: internal Skill-name regexes are projected into Provider JSON Schema, while delegation capability and target Skill discovery are not projected from one contextual authority.
- Locked implementation boundaries: no full Skill enum, no persisted projection/catalog state, no fallback schema, no legacy compatibility, no tombstone tests, and no new Skill lifecycle.
- Started three independent codebase investigations: Agent capability/model projection, target-aware `skill_list` recovery context, and stable delegation error mapping/test seams.
- Reproduced the Provider boundary from the current source: both `delegate.skills.items` and `skill_read.name` serialize the negative-lookaround Skill regex.

## Decisions and corrections

- First-principles correction: `factoryResolveAllowedTools` currently hides delegation at the global depth `3`, while Discussion, Analyst, and Build declare `childPolicy.maxDepth = 2`. The model projection and runtime admission can therefore disagree at depth 2. The accepted exact-capability criterion requires replacing the global filter with each Agent definition's own child policy; the old global behavior will not be retained.
- Recovery-safe target discovery will execute through the global `skill_list` descriptor and a capability resolver reconstructed in `ToolExecutionContext`; model-call descriptor clones will contain presentation only.
- Known child-admission exceptions will be translated narrowly at the `delegate` Tool boundary. Generic `TOOL_DELEGATE_FAILED` remains only for genuinely unclassified failures.

## Verification evidence

- Internal/Provider boundary reproduction before the fix:
- `delegate.skills.items.pattern = ^(?!.*--)[a-z0-9]+(?:-[a-z0-9]+)*$`
- `skill_read.name.pattern = ^(?!.*--)[a-z0-9]+(?:-[a-z0-9]+)*$`
- Parallel implementation completed:
- Factory capability authority and model-call-local `delegate`/`skill_list` presentation.
- Recovery-safe target Skill resolver in every reconstructed `ToolExecutionContext`.
- Strict internal schemas plus regex-free Provider schemas.
- Stable delegate admission error mapping.
- Fresh and resumed child admission migrated to the same capability snapshot.
- `packages/agent-core` typecheck passed after integration.
- Focused Session Agent/Execution tests passed: 119 tests, zero failures.
- Skill, scheduler, Tool contract, Factory, ConfiguredAgent, and delegation focused tests passed in their scoped runs. A combined first run exposed only sandbox-denied fixture writes plus two stale depth-error expectations; the fixture tests passed when rerun with Worktree write access, and the expectations were updated to the new capability-level rejection.
- Repository `bun run typecheck` passed: 5/5 packages, zero failures.
- Repository `bun run test` passed with exit code 0: 8/8 Turbo tasks, including Agent Core unit, integration, and architecture lanes.
- Production `bun run build` passed with exit code 0 after its required typecheck, Vite build, generated embedded-asset entrypoint, and binary compilation pipeline.
- `git diff --check` passed, and source search confirms the removed `MAX_SUB_AGENT_DEPTH` and `getDelegateTargetsFor` contracts have no remaining references.

### 2026-08-10 — real default-model browser acceptance

- Built and launched the isolated QA Worktree production binary on `127.0.0.1:4196` with the configured default `principal` model, `local:gpt-5.6-luna`; no alternate Provider or model was substituted.
- Temporarily isolated the global project index because unrelated registered projects contain pre-hard-cut Session records that the current strict schema rejects. The original index was restored byte-for-file after QA; the QA-only index was retained as `/Users/bo/.archcode/projects/index.provider-safe-qa-20260810.json`. The old QA fixture runtime was moved, not deleted, to `.archcode/runtime.pre-provider-safe-qa-20260810`.
- Used the real in-app browser against the rebuilt product and completed all required lanes:
- Discussion / Generate Plan: Session `cba19064-00c1-4c7b-9540-344e5a9ee73d` reached `completed`, wrote the ordinary Plan, and displayed a nonempty final response.
- Todo Ready / Start Work: Session `3dd6ec36-2158-40a7-a522-f523b2e2f99d` reached `completed`, executed `bun test` through the Agent, displayed `1 pass, 0 fail`, and returned a nonempty final response.
- Direct Session: Session `16c16944-f185-4222-81ce-591c132a7ddb` reached `completed`, displayed finalized `file_read / README.md / Completed`, and returned the exact heading plus a nonempty summary.
- Manual Automation: invocation `cad41c98-907b-4e69-82bb-517c7432d0cc` is exactly `dispatched`, links to Session `48ee77b2-71f8-4d9b-992f-b4b4f2694c13`, and that Session reached `completed` with finalized `file_read` work and a nonempty final response.
- Persisted records for all four latest Executions are exactly `completed` with `error = null`; final-answer text lengths are 567, 790, 153, and 57 characters respectively.
- Browser console errors were zero before restart and zero after restart.
- Performed a full graceful service shutdown and production-binary restart. After reload, all four Sessions remained `Completed`; the Automation remained paused with its latest invocation `Dispatched`, and the invocation-to-Session link remained visible and usable.
- The configured Provider accepted every real Tool schema; no `Invalid JSON schema`, lookaround rejection, or Tool Schema finalization error occurred.

### 2026-08-10 — independent review and fix-review loop

- An independent `gpt-5.6-sol` reviewer at maximum reasoning reviewed the complete Plan Goal, progress evidence, active architecture contract, production diff, tests, and browser evidence.
- First review found and fixed:
- Existing child/resume Profile admission still read `AgentDefinition.profiles`; that second authority was deleted, and recovery now validates target, Profile, and Skills from the same parent/depth capability snapshot before activation side effects.
- The global Registry contract test still treated the unprojected static `delegate` schema as contextual truth; contextual target/Profile assertions now live only in Factory/ConfiguredAgent/model-projection tests.
- The now-unreachable `DelegationToolNotAllowedError` mapping and test were hard-deleted rather than retained as a legacy branch or tombstone.
- Second review found and fixed:
- AC-05 now uses one shared `SkillService` and one concurrent `Promise.all` to prove two projects isolate both Prompt catalogs and `skill_list` pages.
- Internal/global delegation descriptions no longer copy the target-to-Profile authorization matrix; exact mapping exists only in the contextual capability projection.
- Third review found only one stale `aiInputSchema` comment; it now documents the hard-cut portable model-schema versus strict internal-schema boundary for both builtin and MCP tools.
- Final independent verdict: `PASS`, with no blocking or material finding. The reviewer independently passed 240 focused tests, the full 8/8 repository test graph, production build, and `git diff --check` on the final diff.
- Primary-agent post-review gates also passed: 168 combined delegation/Skill/recovery tests, cold-cache repository typecheck 5/5, repository test 8/8 with exit code 0, production build with exit code 0, and `git diff --check`.

## Residual risks

- Recovery, target-only Skill read isolation, and zero-child-artifact rejection are closed by automated tests and the independent review.
- Out-of-scope baseline finding: one registered project containing an incompatible pre-hard-cut Session currently aborts startup for the entire multi-project Runtime. This Goal does not add a migration, fallback, or compatibility branch; a separate product decision is needed if per-project quarantine is preferred over the current fail-fast behavior.
- The real browser gate exercised default-model Tool schema acceptance through all four required product entry paths, while the exact custom-Skill `skill_list → delegate` chain remains covered by automated capability, recovery, and admission tests rather than a dedicated browser run.
43 changes: 33 additions & 10 deletions packages/agent-core/src/agents/configured-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import type { AnyToolDescriptor } from "../tools/types";
import { createTextToolResult } from "../tools/results";
import { createTestToolRegistryFixture, type TestToolRegistryFixture } from "../tools/test-registry";
import { worktreeEnterTool, worktreeExitTool } from "../tools/builtins/worktree";
import { DELEGATION_CORE_TOOLS, MAX_SUB_AGENT_DEPTH } from "./constants";
import { DELEGATION_CORE_TOOLS } from "./constants";
import {
ConfiguredAgent,
IneligibleSessionWorktreeToolError,
UnknownExtraToolError,
} from "./configured-agent";
import { discussionAgentDefinition, exploreAgentDefinition, leadAgentDefinition } from "./definitions";
import { defaultAgentDefinitions, discussionAgentDefinition, exploreAgentDefinition, leadAgentDefinition } from "./definitions";
import { isRootAgentName } from "./root-session-identity";
import type { AgentDefinition } from "./factory-types";
import type { VersionControl } from "../version-control/detector";
Expand Down Expand Up @@ -270,6 +270,31 @@ function createAgent(options: {
},
});
}
const depth = options.depth ?? 0;
const resolveAllowedTools = (definition: AgentDefinition, agentDepth: number) => {
const requested = [...definition.tools.tools, ...definition.roleContract.requiredCapabilities];
const resolved = toolRegistry.resolveForAgent(requested).descriptors.map((tool) => tool.name);
if (
definition.childPolicy === undefined
|| (definition.tools.delegateTargets?.length ?? 0) === 0
|| agentDepth >= definition.childPolicy.maxDepth
) {
return resolved.filter((name) => !(DELEGATION_CORE_TOOLS as readonly string[]).includes(name));
}
return resolved;
};
const canDelegate = resolveAllowedTools(options.definition, depth).includes("delegate");
const delegationTargets = canDelegate
? (options.definition.tools.delegateTargets ?? []).map((agentName) => {
const target = defaultAgentDefinitions.find((candidate) => candidate.name === agentName);
if (target === undefined) throw new Error(`Missing test Agent definition: ${agentName}`);
return Object.freeze({
agentName: target.name,
profiles: Object.freeze([...target.profiles]),
builtinSkillNames: Object.freeze([...target.skills]),
});
})
: [];
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return new ConfiguredAgent({
definition: options.definition,
toolRegistry,
Expand All @@ -287,14 +312,12 @@ function createAgent(options: {
attachmentProjector: EMPTY_ATTACHMENT_MODEL_PROJECTOR,
resolveAttachmentReadPaths: resolveEmptyAttachmentReadPaths,
logger: options.logger ?? silentLogger,
resolveAllowedTools: (definition, depth) => {
const requested = [...definition.tools.tools, ...definition.roleContract.requiredCapabilities];
const resolved = toolRegistry.resolveForAgent(requested).descriptors.map((tool) => tool.name);
if (depth >= MAX_SUB_AGENT_DEPTH) {
return resolved.filter((name) => !(DELEGATION_CORE_TOOLS as readonly string[]).includes(name));
}
return resolved;
},
delegationCapabilities: Object.freeze({
parentAgentName: options.definition.name,
depth,
targets: Object.freeze(delegationTargets),
}),
resolveAllowedTools,
});
}

Expand Down
Loading