From 374b87cbd72e4a698261a5cfc6c83a77e2738773 Mon Sep 17 00:00:00 2001 From: NaturalSelect <2145973003@qq.com> Date: Tue, 6 Oct 2026 02:43:15 +0800 Subject: [PATCH 1/3] feat: deliver full code walkthroughs as offline HTML pages Generated with Angela Assisted-by: Angela:Claude Sonnet 5.5 Signed-off-by: NaturalSelect <2145973003@qq.com> --- .agents/skills/builtin-skills/SKILL.md | 2 +- internal/sandbox/child_net.go | 10 +- internal/sandbox/child_net_linux.go | 2 +- .../builtin/builtin-code-walkthrough/SKILL.md | 217 +++++++++---- .../builtin-code-walkthrough/template.html | 288 ++++++++++++++++++ internal/skills/skills_test.go | 33 ++ main.go | 6 +- 7 files changed, 490 insertions(+), 68 deletions(-) create mode 100644 internal/skills/builtin/builtin-code-walkthrough/template.html diff --git a/.agents/skills/builtin-skills/SKILL.md b/.agents/skills/builtin-skills/SKILL.md index 93db363a..a8d2f536 100644 --- a/.agents/skills/builtin-skills/SKILL.md +++ b/.agents/skills/builtin-skills/SKILL.md @@ -55,6 +55,6 @@ These are always available without user configuration. | `angela-setup` | `builtin/angela-setup/` | Interactive new-user onboarding and setup guide | | `angela-shell` | `builtin/angela-shell/` | Embedded POSIX shell semantics, background jobs, permission judgment | | `angela-skills` | `builtin/angela-skills/` | Authoring and discovering skills, frontmatter fields, precedence | -| `builtin-code-walkthrough` | `builtin/builtin-code-walkthrough/` | Evidence-backed walkthroughs of unfamiliar projects, modules, and flows | +| `builtin-code-walkthrough` | `builtin/builtin-code-walkthrough/` | Evidence-backed walkthroughs of unfamiliar projects, modules, and flows, delivered as an offline HTML page built from `template.html` | | `builtin-write-unit-tests` | `builtin/builtin-write-unit-tests/` | General-purpose guide for writing unit and E2E tests in any project | | `jq` | `builtin/jq/` | jq JSON processor usage guide | diff --git a/internal/sandbox/child_net.go b/internal/sandbox/child_net.go index e824eb84..2e88dd9a 100644 --- a/internal/sandbox/child_net.go +++ b/internal/sandbox/child_net.go @@ -36,14 +36,8 @@ func WrapForChildNetworkRestriction(path string, args []string) ([]string, error return wrapped, nil } -// RunChildExecLauncherIfRequested checks whether argv (os.Args) -// requests the launcher path installed by -// WrapForChildNetworkRestriction and, if so, never returns: it -// installs the outbound-network filter on the current process and -// execs into the real command, or exits the process with a -// diagnostic on failure. Callers must invoke this before any normal -// command-line handling, since argv[1] here is not a real Angela -// flag. + +// NOTE: not returning to the caller if the child exec launcher is triggered. func RunChildExecLauncherIfRequested(argv []string) { if len(argv) < 3 || argv[1] != childExecMarker { return diff --git a/internal/sandbox/child_net_linux.go b/internal/sandbox/child_net_linux.go index 44f53385..fb629e4c 100644 --- a/internal/sandbox/child_net_linux.go +++ b/internal/sandbox/child_net_linux.go @@ -87,7 +87,7 @@ func runChildExecLauncher(rest []string) { os.Exit(127) } - // Lock to the current OS thread before touching seccomp: exec + // NOTE: Lock to the current OS thread before touching seccomp: exec // only carries the calling thread's filter into the new program // image, and an unlocked goroutine could otherwise resume on a // different thread between installing the filter and calling diff --git a/internal/skills/builtin/builtin-code-walkthrough/SKILL.md b/internal/skills/builtin/builtin-code-walkthrough/SKILL.md index 852aad7c..fb945104 100644 --- a/internal/skills/builtin/builtin-code-walkthrough/SKILL.md +++ b/internal/skills/builtin/builtin-code-walkthrough/SKILL.md @@ -20,7 +20,11 @@ Two quality bars apply to everything you produce: - **Accurate**: every factual claim traces back to code, docs, or history you actually read in this session. Anything else is labeled as inference. -Write the walkthrough in the user's language. Keep code identifiers as-is. +A full walkthrough is delivered as a **self-contained HTML page** built from +this skill's template (section 6). A quick one is a short chat answer. + +Write the walkthrough in the user's language, including the page's own +headings and labels. Keep code identifiers as-is. ## 1. Scope It Without Interrogating the User @@ -43,10 +47,13 @@ unrelated modules. One focused question beats a questionnaire. Pick a depth: -- **Quick**: an in-chat answer of about one screen. Use it when the user asks - a narrow "how does X work" question. -- **Full**: the complete structure in section 5. Use it for onboarding, - whole-module, or whole-project requests. +- **Quick**: an in-chat answer of about one screen, no file. Use it when the + user asks a narrow "how does X work" question. +- **Full**: the HTML page described in sections 5 and 6, plus a short chat + summary. Use it for onboarding, whole-module, or whole-project requests, and + whenever the user asks for a walkthrough, tour, document, page, or HTML. + When unsure between the two, choose Full only if the target has more than + one part or flow. ## 2. Gather Evidence, Not Impressions @@ -94,7 +101,8 @@ every file. ## 3. Accuracy Rules Every claim in the output falls into one of three levels. Make the level -visible whenever it is not "verified". +visible whenever it is not "verified". In the HTML page, mark the other two +with the template's `inferred` and `unknown` tags. - **Verified**: you read the code that does it. Cite `path:line`. - **Inferred**: based on names, patterns, or partial reading. Say what the @@ -165,61 +173,143 @@ Specific rules: - **End with a reading order.** Tell the reader which three to five places to open first, and why, so they can continue alone. -## 5. Output Structure - -Adapt this template to the target. Drop sections that would be empty or -generic. Never pad a section to fill the template. - -```markdown -# walkthrough - -> Describes commit . - -**In short.** - -## Key terms -| Term | Meaning here | Defined at | -| ---- | ------------ | ---------- | - -## Map -| Part | Owns | Start reading at | -| ---- | ---- | ---------------- | - - - -## How it works: -1. `path:line` - Data now: . -2. ... +## 5. Content Structure - +Adapt this outline to the target. Drop sections that would be empty or +generic. Never pad a section to fill the outline. For a full walkthrough each +row is one `
` of the HTML template. -## State, invariants, and conventions -- - -## Why it is built this way -- Source: . - -## Pitfalls and open questions -- -- - -## Where to start reading -1. `path:line`: . -``` +| Section (template id) | Content | +| --------------------- | ------- | +| Header | Title, the commit it describes, and **In short**: two to four sentences on what it does, who uses it, and the core idea. | +| Key terms (`terms`) | Term, meaning here, defined at `path:line`. Only terms the reader will meet. | +| Map (`map`) | Part, what it owns, where to start reading. Optional traced diagram. | +| How it works (`flow`) | The real-world scenario, followed step by step. Each step: what happens, `path:line`, and **Data now**. Add at most two more flows, only if they differ meaningfully. | +| Parts in depth (`parts`) | Only for parts the flow does not already explain: explanation, trimmed real snippet, and a "why this matters" note. | +| State and invariants (`state`) | Each rule the code relies on, where it is enforced, what breaks if violated. | +| Why it is built this way (`why`) | Each decision with its source: commit, comment, doc, or an explicit inference label. | +| Pitfalls and open questions (`pitfalls`) | Risks and surprises with `path:line`, and unknowns with how to find out. | +| Where to start reading (`start`) | Three to five places in order, each with the reason. | For a **quick** walkthrough, keep only the short summary, the concrete flow, -and where to start reading. - -Deliver the walkthrough in the chat by default. Write it to a file only when -the user asks. In that case, use the path they give or the project's existing -docs convention, and keep the commit line so readers know which version the -document describes. - -## 6. Self-Check Before Delivering +and where to start reading, in the chat. + +## 6. Build the HTML Page + +The template is `angela://skills/builtin-code-walkthrough/template.html`. It +is one file with inline CSS, no script, and no external requests, so it opens +offline from disk. It follows the viewer's light or dark setting, fits a phone +width, and prints cleanly. + +### Procedure + +1. **Read the template** with the Read tool. The tool prefixes each line with + a number and a `|`. Those prefixes are not part of the file, so never copy + them into the output. +2. **Gather the facts first.** Take the commit from + `git rev-parse --short HEAD` and the branch from + `git branch --show-current`. If `git status --short` is not empty, say the + page also reflects uncommitted changes. Finish sections 2 to 4 before you + write any HTML. +3. **Write the page** with the Write tool in one pass. Start from the whole + template and replace each `` marker plus the placeholder + content after it. Every placeholder is wrapped in `«` and `»`. + Keep the ` + + +
+ +
+

Code walkthrough · «project | module | flow»

+

«Target» walkthrough

+ +

Describes commit «abc1234» on «branch» · «YYYY-MM-DD»

+ +

In short. «What it does, who uses it, and the core idea.»

+
+ +
+ + + + +
+ + +
+

Key terms

+
+ + + + +
TermMeaning hereDefined at
«Term»«Meaning in this codebase.»«path:line»
+
+ + +
+

Map

+
+ + + + +
PartOwnsStart reading at
«Part»«What it is responsible for.»«path:line»
+ + +
+
+ + + + «Caller» + + «verb» + + «Focus» + + «verb» + + «Callee» + +
+
«The same claim as the aria-label, in one sentence.»
+
+
+ + +
+

How it works: «real-world scenario»

+

Scenario. «Who does what, with which realistic input, config, or request.»

+
    +
  1. +

    «What happens, in plain words»

    +

    «Why this step exists and what it does. Add inferred or unknown next to anything not verified in code, with the basis.» «path:line»

    +
    + Data now +
    «type, key fields, and what changed»
    +
    + +
    +
    «path:start-end» «what to notice»
    +
    «first line of the real code» 1
    +«…»
    +
      +
    1. «What the pinned line does that the code does not say directly.»
    2. +
    +
    +
  2. +
  3. +

    «Next step»

    +

    «What happens.» «path:line»

    +
    + Data now +

    «Shape or key values.»

    +
    +
  4. +
+
+ + +
+

Parts in depth

+
+ «Part name» +
+

«What this part does and how the rest of the system uses it.»

+
+
«path:start-end» «what to notice»
+
«real code»
+
+ +
+
+
+ + +
+

State and invariants

+
    +
  • «Rule the code relies on.» Enforced at «path:line». «What breaks if violated.»
  • +
+
+ + +
+

Why it is built this way

+
    +
  • «Decision.» Source: «commit hash, comment at path:line, doc, or inferred from ...».
  • +
+
+ + +
+

Pitfalls and open questions

+ +
    +
  • unknown «What could not be determined.» «How to find out.»
  • +
+
+ + +
+

Where to start reading

+
    +
  1. «path:line» «Why this first.»
  2. +
+
+ +
+
+ +
+

Every path:line refers to the commit above. Claims tagged inferred or unknown were not confirmed in code.

+
+ +
+ + diff --git a/internal/skills/skills_test.go b/internal/skills/skills_test.go index 1093248a..3e4dbf54 100644 --- a/internal/skills/skills_test.go +++ b/internal/skills/skills_test.go @@ -5,6 +5,7 @@ import ( "io/fs" "os" "path/filepath" + "regexp" "strings" "testing" "time" @@ -645,6 +646,38 @@ func TestDiscoverBuiltin(t *testing.T) { require.Equal(t, 1, angelaConfigCount, "reference/*.md files must not be discovered as their own skills") } +func TestWalkthroughTemplate(t *testing.T) { + t.Parallel() + + data, err := fs.ReadFile(BuiltinFS(), "builtin/builtin-code-walkthrough/template.html") + require.NoError(t, err, "walkthrough template must be embedded next to its SKILL.md") + html := string(data) + + require.NotContains(t, html, " Date: Thu, 8 Oct 2026 22:46:36 +0800 Subject: [PATCH 2/3] refactor: merge sketch agent into plan Generated with Angela Assisted-by: Angela:Claude Opus 5.5 Signed-off-by: NaturalSelect <2145973003@qq.com> --- docs/agents/README.md | 78 +++-- internal/agent/agent_tool_test.go | 5 +- internal/agent/branch_agents_test.go | 4 +- internal/agent/prompts.go | 11 +- internal/agent/prompts_test.go | 1 - .../agent/templates/branch_fork_prompt.md.tpl | 4 +- internal/agent/templates/plan.md.tpl | 314 +++++++++++++----- internal/agent/templates/sketch.md.tpl | 216 ------------ internal/config/agents_resolve_test.go | 42 ++- internal/config/config.go | 37 ++- .../skills/builtin/angela-agents/SKILL.md | 18 +- .../builtin/angela-config/reference/agents.md | 2 +- internal/skills/builtin/angela-setup/SKILL.md | 2 +- 13 files changed, 339 insertions(+), 395 deletions(-) delete mode 100644 internal/agent/templates/sketch.md.tpl diff --git a/docs/agents/README.md b/docs/agents/README.md index e59eee39..cab7346d 100644 --- a/docs/agents/README.md +++ b/docs/agents/README.md @@ -11,8 +11,7 @@ tasks to specialized sub-agents via the `agent` tool. | `deep-research` | branch | Settles a question ordinary investigation could not: a stubborn root cause, or a hard-to-reverse design choice. Read-only plus `bash`. | | `explore` | subagent | Fast codebase explorer. Tools: Glob, Grep, LS, Read, Fetch, Sourcegraph, AngelaInfo, Git (read-only), LSP (read-only). | | `general` | subagent | General-purpose agent for multi-step tasks. Inherits the coder's tools, minus `todos`. | -| `plan` | branch | Turns a request into an ordered implementation plan, agreed with you first. Read-only. | -| `sketch` | branch | Designs a change with you as pseudo-code, one piece at a time, so you see and accept everything that gets built. Read-only. Only forked when you ask. | +| `plan` | branch | Works out a change with you as pseudo-code, one piece at a time, and hands back an ordered implementation plan. Read-only. | | `web-fetch` | subagent | Fetches and analyzes web pages, or searches the web. Tools: Fetch, WebFetch, WebSearch, Glob, Grep, Read, Sourcegraph. | Every sub-agent additionally loses the interactive `question` tool at run @@ -88,21 +87,41 @@ A **branch** agent turns a delegation into a conversation you take part in. When the coder dispatches one, Angela forks the current session, suspends the coder's turn, and drops you into the fork. You talk to the branch directly, the way you talk to the coder. When you are done, the branch hands -a summary back and the coder's turn resumes where it left off. +a proposal back and the coder's turn resumes where it left off. Use it for work the model cannot finish alone: a design decision only you can make, an exploration whose direction you have to steer, a discussion that has to happen before the task is even well-defined. -Angela ships three branch agents, `plan`, `deep-research` and `sketch`, and +Angela ships two branch agents, `plan` and `deep-research`, and you can configure your own with your own system prompt. ### `plan` The coder forks `plan` before non-trivial work — a new feature, a refactor, a change with several viable designs, or a request whose scope has to be pinned -down first. You settle the approach together, and `plan` hands back an ordered, -step-by-step plan for the coder to execute. +down first. A change that adds no data structure, no cross-module flow, and no +interface change has no design to work out, so `plan` says so and goes +straight to a short ordered plan. Otherwise you and the agent work out the +change's data structures, how they interact, and where they land in the +repository, all as pseudo-code. `plan` hands back one ordered, step-by-step +plan for the coder to execute, and that plan carries the pseudo-code design. + +It first asks whether you already know the code the change passes through. If +you do not, it probes with a short quiz, then fills the gaps. From then on you +state decisions in plain words and it writes each one back as pseudo-code for +you to confirm. It does not design on its own initiative: where it thinks +something is missing it asks, and it offers a design of its own only when you +ask for one, marked as its own and entered only after you accept it. Finally +it checks the design against the repository, reports where the two disagree, +and orders the work into steps with the commands that verify them. + +The rule behind all of this is that what you see is what gets built. The plan +it hands back holds only pseudo-code you have seen, and the coder that +implements it must stop and tell you before deviating from any of it. Detail +below the level of that pseudo-code, such as function bodies, is left to the +coder. That is slower than signing a finished document, and it is the point: +approving a plan does not repay the understanding you skip. `plan` is read-only. It reads, searches, and asks you questions, but it holds no `bash`, no `edit`, and no `write`: the plan is the only thing it produces. @@ -149,31 +168,6 @@ history, or run the one test that separates two hypotheses. Every command asks your permission first, and it still holds no `edit` or `write`: the finding is its only product, and acting on it is the coder's job. -### `sketch` - -`sketch` is for the times you want to understand a design, not only approve -it. You and the agent work out a change's data structures, how they interact, -and where they land in the repository, all as pseudo-code. It first probes what -you already know about the code, then fills the gaps. From then on you state -decisions in plain words and it writes each one back as pseudo-code for you to -confirm. It may suggest additions of its own, but a suggestion is marked as -its own and enters the sketch only after you accept it. Finally it checks the -sketch against the repository and reports where the two disagree. - -The rule behind all of this is that what you see is what gets built. The -sketch it hands back holds only pseudo-code you have seen, and the coder that -implements it must stop and tell you before deviating from any of it. - -Unlike `plan`, which hands back a finished document to approve, `sketch` builds -the design piece by piece in front of you. That is slower, and it is the point: -signing a plan does not repay the understanding you skip. The coder never -forks `sketch` on its own. Ask for it, or accept when the coder offers it -before a change that adds a data structure, introduces a cross-module flow, or -alters an existing interface. - -Like `plan`, `sketch` is read-only, holds no `bash`, and can only delegate to -`explore`. - ### Configuring one Add a branch agent in `angela.json` or as a markdown file in an agent @@ -202,14 +196,16 @@ prompt follows and decides everything else. point of the call, then a message stating the task the coder gave it. 2. **Talk.** You drive it. Everything works as usual — tools, permissions, `/` commands. -3. **Merge.** When the work is settled, the branch calls the `merge` tool - with a summary. Merging always asks for your approval, even under an - allow-list, because the summary is what the coder will believe. +3. **Merge.** While you talk, the branch drafts a proposal document in + memory. When the work is settled, it calls the `merge` tool, which takes + no arguments and hands that proposal back. Merging always asks for your + approval, even under an allow-list, because the proposal is what the + coder will believe. - **Approve** — the branch ends, you return to the parent conversation, - and the coder resumes with the summary as the `agent` tool's result. + and the coder resumes with the proposal as the `agent` tool's result. - **Deny** — nothing is merged and nothing is sent. The branch stays - open, so you can say what was wrong with the summary and have it try - again. + open and keeps the proposal, so you can say what was wrong with it and + have it revise and try again. - Under yolo mode (`--yolo`, or permissions set to skip requests), merge still asks for approval by default. Pass `--yolo-merge` at startup or use the "Enable Yolo Skip Merge" command to auto-approve merges too. @@ -217,7 +213,7 @@ prompt follows and decides everything else. their config, which forces merge to prompt even when `--yolo-merge` is on. 4. **Abandon.** If the branch led nowhere, drop it: the coder is told the - branch was abandoned and continues without a summary. That is a normal + branch was abandoned and continues without a proposal. That is a normal outcome, not an error. ### Ending a branch without merging @@ -361,9 +357,9 @@ does. after a lower-priority layer narrowed it, since an unset field only ever keeps whatever a lower layer decided rather than clearing it. -The built-in `plan`, `deep-research` and `sketch` agents all set -`"allowed_agents": ["explore"]`: a plan, a sketch or a root-cause finding is -only as trustworthy as the read-only legwork behind it, so none of them can +The built-in `plan` and `deep-research` agents both set +`"allowed_agents": ["explore"]`: a plan or a root-cause finding is +only as trustworthy as the read-only legwork behind it, so neither of them can hand the decision off to `general` or to each other — they can only delegate the read-only search `explore` provides. diff --git a/internal/agent/agent_tool_test.go b/internal/agent/agent_tool_test.go index e21c36b7..97727124 100644 --- a/internal/agent/agent_tool_test.go +++ b/internal/agent/agent_tool_test.go @@ -256,14 +256,14 @@ func TestAgentToolAllowedAgentsEmptyOmitsTheTool(t *testing.T) { } // TestBuiltinBranchesOnlyDispatchExplore pins the allowed_agents -// restriction on the built-in branch agents: a plan, a sketch or a +// restriction on the built-in branch agents: a plan or a // root-cause finding is only as trustworthy as the read-only legwork // behind it, so none should be able to reach general, or hand the // decision off to another branch, through its own agent tool. func TestBuiltinBranchesOnlyDispatchExplore(t *testing.T) { coord := newGateTestCoordinator(t, true) - for _, id := range []string{config.AgentPlan, config.AgentDeepResearch, config.AgentSketch} { + for _, id := range []string{config.AgentPlan, config.AgentDeepResearch} { t.Run(id, func(t *testing.T) { agentCfg := coord.cfg.Config().Agents[id] toolList, err := coord.buildTools(agentCfg, config.ActiveAgent{}, "", 0) @@ -284,7 +284,6 @@ func TestBuiltinBranchesOnlyDispatchExplore(t *testing.T) { require.NotContains(t, agentTool.Info().Description, "- "+config.AgentGeneral+":") require.NotContains(t, agentTool.Info().Description, "- "+config.AgentPlan+":") require.NotContains(t, agentTool.Info().Description, "- "+config.AgentDeepResearch+":") - require.NotContains(t, agentTool.Info().Description, "- "+config.AgentSketch+":") ctx := context.WithValue(context.Background(), tools.SessionIDContextKey, "session-1") ctx = context.WithValue(ctx, tools.MessageIDContextKey, "msg-1") diff --git a/internal/agent/branch_agents_test.go b/internal/agent/branch_agents_test.go index 497fa6d6..1db3cdb5 100644 --- a/internal/agent/branch_agents_test.go +++ b/internal/agent/branch_agents_test.go @@ -23,7 +23,7 @@ func toolNamesFor(t *testing.T, coord *coordinator, agentID string, depth int) [ // builtinBranches are the branch agents Angela ships. Everything in this file // that is not about one of them specifically applies to all of them. -var builtinBranches = []string{config.AgentPlan, config.AgentDeepResearch, config.AgentSketch} +var builtinBranches = []string{config.AgentPlan, config.AgentDeepResearch} // TestBranchAgentsNeverWrite pins the boundary that makes a branch safe to // approve on merge: both of them investigate and hand back a document, so @@ -97,8 +97,6 @@ func TestBranchExecutionBoundaryDiffers(t *testing.T) { require.NotContains(t, toolNamesFor(t, coord, config.AgentPlan, 1), toolnames.Bash, "plan reasons from what it reads") - require.NotContains(t, toolNamesFor(t, coord, config.AgentSketch, 1), toolnames.Bash, - "sketch designs from what it reads") deepResearch := toolNamesFor(t, coord, config.AgentDeepResearch, 1) for _, tool := range []string{toolnames.Bash, toolnames.JobOutput, toolnames.JobKill} { diff --git a/internal/agent/prompts.go b/internal/agent/prompts.go index 6fddbaae..2052643c 100644 --- a/internal/agent/prompts.go +++ b/internal/agent/prompts.go @@ -23,9 +23,6 @@ var planPromptTmpl []byte //go:embed templates/deep_research.md.tpl var deepResearchPromptTmpl []byte -//go:embed templates/sketch.md.tpl -var sketchPromptTmpl []byte - //go:embed templates/initialize.md.tpl var initializePromptTmpl []byte @@ -45,7 +42,7 @@ var webFetchPromptTmpl []byte var generateAgentPromptTmpl []byte // branchPreambleTmpl fronts the system prompt of every branch-mode agent, -// the built-in plan, deep-research and sketch agents as well as any the +// the built-in plan and deep-research agents as well as any the // user defines. It is the only place the rules the fork machinery // depends on are guaranteed to be stated, so a custom branch prompt // cannot drop them by omission. @@ -85,10 +82,6 @@ func deepResearchPrompt(opts ...prompt.Option) (*prompt.Prompt, error) { return prompt.NewPrompt(config.AgentDeepResearch, string(deepResearchPromptTmpl), opts...) } -func sketchPrompt(opts ...prompt.Option) (*prompt.Prompt, error) { - return prompt.NewPrompt(config.AgentSketch, string(sketchPromptTmpl), opts...) -} - func titlePrompt(opts ...prompt.Option) (*prompt.Prompt, error) { return prompt.NewPrompt(config.AgentTitle, string(titlePromptTmpl), opts...) } @@ -122,7 +115,6 @@ var builtinPromptForAgent = map[string]func(...prompt.Option) (*prompt.Prompt, e config.AgentExplore: explorePrompt, config.AgentGeneral: generalPrompt, config.AgentPlan: planPrompt, - config.AgentSketch: sketchPrompt, config.AgentTitle: titlePrompt, config.AgentCompact: compactPrompt, config.AgentWebFetch: webFetchPrompt, @@ -142,7 +134,6 @@ var builtinPromptTemplateFile = map[string]string{ config.AgentExplore: "explore.md.tpl", config.AgentGeneral: "general.md.tpl", config.AgentPlan: "plan.md.tpl", - config.AgentSketch: "sketch.md.tpl", config.AgentTitle: "title.md", config.AgentCompact: "summary.md", config.AgentWebFetch: "web_fetch_prompt.md.tpl", diff --git a/internal/agent/prompts_test.go b/internal/agent/prompts_test.go index ba5cabf6..e1c7a259 100644 --- a/internal/agent/prompts_test.go +++ b/internal/agent/prompts_test.go @@ -48,7 +48,6 @@ func TestAgentPrompt_SubagentTemplatesRenderContextFiles(t *testing.T) { config.AgentGeneral, config.AgentPlan, config.AgentDeepResearch, - config.AgentSketch, } { t.Run(id, func(t *testing.T) { agentCfg, ok := store.Config().Agents[id] diff --git a/internal/agent/templates/branch_fork_prompt.md.tpl b/internal/agent/templates/branch_fork_prompt.md.tpl index dd4b3495..9e6d27ad 100644 --- a/internal/agent/templates/branch_fork_prompt.md.tpl +++ b/internal/agent/templates/branch_fork_prompt.md.tpl @@ -8,5 +8,5 @@ Your task: {{.Prompt}} -Work with the user on this, then call `Merge` with a summary for the -conversation you were forked from. +Work with the user on this, then call `Merge` to hand your proposal back to +the conversation you were forked from. diff --git a/internal/agent/templates/plan.md.tpl b/internal/agent/templates/plan.md.tpl index 69ec68b6..995f696a 100644 --- a/internal/agent/templates/plan.md.tpl +++ b/internal/agent/templates/plan.md.tpl @@ -1,95 +1,239 @@ -You are Angela's planning agent. Your job is to turn a request into an ordered, -component-level implementation plan that another agent can execute in this -repository without rediscovering what you already worked out. - -You have no editing tools. That is deliberate, not an oversight — do not look -for a way around it, and do not promise to make a change yourself. The plan is -the product. - -## What a plan is here - -A plan decides **what changes, where, in what order, and how you will know it -worked**. It names the components involved, says how each one's responsibility -shifts, and puts the steps in an order that respects their dependencies. - -It is not a system-architecture essay, and it is not code. Skip the line-by-line -implementation: state the type or signature when it pins down an interface -between steps, and leave the body to the agent that writes it. - -## Plan workflow - -Work through these phases in order. If something in a later phase upends an -earlier decision, go back and fix it there instead of patching around it in -the final text. +You are Angela's planning agent. You and the user work out a change on scratch +paper before any code is written, and you hand back one document: an ordered +implementation plan that another agent can execute without rediscovering what +you worked out. The plan carries the design as pseudo-code — its data +structures, how they interact, and where they land in this repository. The +user brings their intent and their decisions; you bring what the code really +does. Every piece of pseudo-code in the plan is something the user has seen and +accepted. + +You have no editing tools. That is deliberate — nothing in the working tree +changes until the plan is merged and someone else implements it. + +## The two rules + +**Nothing enters the plan unseen.** Every item in the plan — a type, a field, +a step, a lock, a cache — is there because the user saw it and accepted it. + +**The design is the user's.** You do not design on your own initiative. + +- When the user states a decision, transcribe exactly that decision. +- When you think the design is missing something — a field, an error path, a + case a snippet leaves open — ask about it as a question. Do not answer your + own question with a design. +- Design from you enters only when the user asks for it ("what would you do + here", "you decide"). Then say what you propose, why, and what it costs, show + it as pseudo-code marked as yours (for example `// suggested: …`), kept apart + from what the user decided, and recommend plainly. It enters the plan only + once the user accepts it, and never slips into a transcription. + +## Pseudo-code is the medium + +Every turn that carries a design idea carries pseudo-code — about new code or +existing code. Prose lets both sides believe they agree while picturing +different things; pseudo-code makes the mismatch visible. Keep it at the level +of the design: types with their fields, function signatures, the calls between +them, and the branches that matter. Leave out bodies that do not affect the +design. Write it in the repository's language when that reads naturally, +otherwise in plain Go-like pseudo-code, and always in the message body — never +inside a Question description, which is too short to hold it. + +## Workflow + +Four phases, in order. Any phase may send you back to an earlier one, and a +change with no design to work out skips from phase 1 straight to phase 4. + +Draft the proposal with ProposalWrite the first time you have something to +record in it: the section headings from "Shape of the proposal", the Goal in +the user's words, and the implementation contract. After that, change it only +with ProposalEdit. + +### Phase 1: Size the change, then fill the gap + +Find out whether there is a design to work out, and what the user already +believes about the code it will pass through, before you explain anything. + +- Read the code this change will pass through first, so you know the real + answers. Keep it to the path the change will take; delegate breadth to the + explore agent instead of reading breadth-first yourself. The conversation + you forked from is blocked while you work, so read enough to be right and + then stop. +- Every file path, symbol, and command you later write down must come from + something you actually read this session. A plausible-looking path that + does not exist costs the executing agent more than no path at all. +- If the change adds no data structure, no cross-module flow, and no + interface change, there is no design to work out. Say so, skip to phase 4, + and keep the plan short. If this only becomes clear later, skip to phase 4 + from wherever you are. +- Otherwise, ask whether the user already knows the code this change passes + through, naming it in a phrase. If they do, go straight to phase 2. +- If they do not, or are unsure, probe with 3–5 short multiple-choice + questions sent together in one Question call — where something lives, what + calls what, which runs first. Always offer an "I don't know" choice. You + already know the answers: the questions measure what the user knows, so the + usual advice against asking what the code can answer does not apply here. + Wrong answers and "I don't know" mark what to explain. +- Explain only what the probe showed is missing, and only along the path this + change will take. Tell it as one concrete execution path with real values + ("when the user presses enter, the message goes to …"), not as an inventory + of structures, and show that path as simplified pseudo-code of the existing + code, one line per step, each with its file:line. Define each repository + term in one sentence the first time it appears. Cite file:line for every + fact. +- Keep this to facts: what the code is, and which existing feature already + solved a problem of this shape ("X does something similar, at a.go:42"). + Whether to follow it is the user's decision in phase 2. + +### Phase 2: Decide together, one snippet at a time + +The user states a decision in plain words — "a struct called Draft that holds +the sections". You answer with: + +- a pseudo-code snippet for exactly that decision, one concept at a time; +- under it, a "to confirm" list of what the snippet still needs that they did + not say, as plain questions; +- then end your turn and let them answer: yes, no, or a correction. At any point, ask the user when the answer would materially change the plan: a genuine fork between approaches, a scope boundary you cannot infer, a -constraint only they know. Recommend a default when you ask — an open-ended -question hands the work back to them. Do not ask for routine confirmation, and -do not narrate your reading. - -### Phase 1: Understand - -- Read the relevant code before judging it. A plan built on a guess about how - something works is worse than no plan. -- Every file path, symbol, and command in the plan must come from something - you actually read this session. A plausible-looking path that does not - exist costs the executing agent more than no path at all. -- Find the closest existing feature that already solved a problem of this - shape and follow it. Matching an established pattern beats inventing a - better one. -- If the scope is broad, spans parts of the codebase you have not read, or - covers several independent areas, delegate to the explore agent for the - part that needs covering instead of reading breadth-first yourself. The - conversation you forked from is blocked while you work, so read enough to - be right and then stop. - -### Phase 2: Design - -- Decide what changes, where, and in what order, respecting the dependencies - between steps. +constraint only they know. When the user asks for your recommendation, give a +default rather than an open-ended question. Do not ask for routine +confirmation, and do not narrate your reading. + +When they correct a snippet, rewrite it their way. If you believe their version +has a problem — it breaks an invariant they stated, contradicts a part already +confirmed, fails on a concrete input, or collides with the code — do not +transcribe it silently. Say so before moving on: what the problem is, why it +is a problem, and a pseudo-code trace of the case where it goes wrong, with +file:line when the reason comes from the code. Then let them decide. If they +keep their version, transcribe it exactly as they wrote it, mark it +`// decided:`, and do not raise the same point again. An objection they have +heard and overruled is a decision; repeating it is taking the pen back. + +As soon as a snippet is accepted, add it to the proposal as its own entry under +Steps; phase 3 adds where it lands and phase 4 puts the entries in order. The +proposal always holds exactly what the user has seen and accepted. Never write +an unaccepted snippet into it. + +Flows are harder to dictate than structures. For a flow, have the user state +the steps and write them back as numbered pseudo-code to confirm step by step. +If they ask you to draft the flow, mark which steps restate what they said and +which are yours. An accepted flow that spans several entries goes under Goal. + +If the user cannot write some part of the design, your phase 1 explanation did +not land there. Go back, explain that part, then return. + +### Phase 3: Check the design against the repository + +Walk the confirmed design through the code and report where it does not fit. +You are reporting conflicts, not listing files. Show each conflict as two +short snippets: what the design says, and what the code at file:line actually +does. + +- A local conflict — a wrong name, a missing parameter, an interface that + takes a different type — is offered as options in pseudo-code. Ask about it + with Question. The user picks. +- A structural conflict — a layer the design skips, unclear ownership, an + invariant the code already breaks — gets described with file:line, together + with at least two ways out, each as pseudo-code. It changes the shape of the + design, so it goes back to phase 2 and the user decides there. +- For every piece that fits, record in its entry where it lands: file:line, + the existing symbol it adds to, reuses, or modifies, and the snippet as it + fits there. Show these to the user as you go. + +Lay the options for a conflict out evenly. Recommend one only when the user +asks for your recommendation, as in phase 2. Mark the user's choice +`// decided:` in the entry it changes. + +### Phase 4: Order the work + +Turn the confirmed design into steps that another agent can execute. + +- Order the steps by the dependencies you found in phase 3, not by whatever + was convenient to write. Each step must be completable and checkable before + the next begins. Where two are genuinely independent, say so. +- Complete each entry as "Shape of the proposal" describes. A step carries + out accepted pseudo-code and adds no behavior of its own. - Take the verification commands from the repository itself — its manifest, task runner, or CI config — never from memory of how projects like this usually work. -- Weigh alternatives privately. The plan only needs to carry the one you - recommend, not the ones you ruled out. - -### Phase 3: Review - -- Trace every step back to something you read this session. Anything you - could not verify goes under risks or assumptions, stated plainly — do not - let an unchecked belief sit in the plan looking like a fact. -- Check the step order against the dependencies you actually found, not - against whatever order was convenient to write. -- If a question remains whose answer would still change the plan, this is the - last point to ask it before the user reviews the final version. - -### Phase 4: Write the plan - -Write it for the agent that will execute it, in this order: - -- **Context** — what exists today that the plan has to fit into. -- **Goal** — what is true when the work is done. -- **Steps** — each one: the component and file it touches, the responsibility it - takes on, any behavior or interface change, which earlier step it depends on, - and how to tell it is finished. +- Anything you could not verify goes under risks, stated plainly. Do not let + an unchecked belief sit in the plan looking like a fact. +- Show the user the ordered steps, one line each, before you record them. The + order is theirs to change. + +## Pace + +Keep each turn small: one question, one snippet, or one conflict. A long turn +turns confirmation back into rubber-stamping. The phase 1 probe is the one +exception: its questions go out together in a single Question call. Being +quick here means small turns and no wandering — never skipping a decision +that belongs to the user. + +The user may say "skip this, you decide" at any point. Handing over the +decision does not hand over the understanding. Make the decision, then show +it before moving on: the pseudo-code of what you chose, and one sentence on +the choice that matters most and what it trades away. The user does not have +to approve it, but they must see it — if they object, it becomes a phase 2 +decision again. Record that pseudo-code in its entry, marked `// delegated:`. +A delegated part must stay visible, and the user should still know roughly +how it will work. + +If, after an explanation, the user still cannot state a part of the design, say +so plainly and offer two options: spend time on that code first, or delegate +that part and record it. Never let the plan look complete where the user did +not understand it. + +## When the plan is ready + +The plan is ready — and only then do you call Merge — when: + +- every data structure and flow step in it was seen and accepted by the + user; +- phase 3 has covered the whole design and no structural conflict is open; +- the ordered steps from phase 4 have been shown to the user; +- every "to confirm" item is resolved, delegated, or left open by the user's + explicit choice; +- every delegated item has been shown to the user as pseudo-code. + +The user can end earlier by telling you to merge. Do it, and mark whatever is +unresolved `// open:` in the entry it belongs to. + +## Shape of the proposal + +The proposal is what the agent that receives it will implement. Write it for +that agent. Leave out your probe results, your explanations, and anything the +user rejected. Keep it concise enough to scan, and describe a pattern that +repeats across many files once, with a few representative paths. + +- **Goal** — what the change does and what is true when it is done, in one or + two lines, in the user's words, followed by any accepted flow that spans + several steps, as numbered pseudo-code. +- **Steps** — in dependency order. Each step: the file:line and the existing + symbol it adds to, reuses, or modifies; the accepted pseudo-code it carries + out, exactly as accepted, with the invariants the user stated; the earlier + step it depends on; and how to tell it is finished. - **Verification** — the actual commands, and what a pass looks like. -- **Risks** — what could go wrong, what you could not verify, decisions the - executing agent may need to revisit. -- **Key files** — the paths that matter, so the executor starts in the right - place. - -Order the steps so each one can be completed and checked before the next begins. -Where two are genuinely independent, say so. - -Keep it concise enough to scan quickly, but detailed enough to execute -effectively: - -- Include only your recommended approach, not every alternative you weighed. -- For a change that repeats a pattern across many files, describe the pattern - once and list a few representative paths. Do not enumerate every file. -- Reference the existing functions and utilities you found that should be - reused, with their file paths. +- **Risks** — what could go wrong and what you could not verify. +- **Implementation contract** — copy this unchanged: + + > Implement the steps above. Every change must trace to a step. The + > pseudo-code is what the user saw and accepted. Detail below it, such as + > function bodies and local names, is yours to write; if the code forces you + > to deviate from the pseudo-code itself, stop and tell the user first. Do + > not undo anything marked `// decided:`, and ask the user before coding + > anything marked `// open:`. When done, report only surprises: assumptions + > you made, where you deviated and why, and what you deliberately did not + > do. + +How a piece was settled is marked in its pseudo-code, where the implementer +will read it: + +- `// decided: …` — the user kept this over an objection or a conflict you + raised. Give the problem and the user's reason in one line, so the + implementer does not "fix" it back. +- `// delegated: …` — the user handed this decision to you and saw the result. +- `// open: …` — still unresolved when the user chose to merge. Working directory: {{.WorkingDir}} diff --git a/internal/agent/templates/sketch.md.tpl b/internal/agent/templates/sketch.md.tpl deleted file mode 100644 index 78d488bd..00000000 --- a/internal/agent/templates/sketch.md.tpl +++ /dev/null @@ -1,216 +0,0 @@ -You are Angela's sketch agent. You and the user work out the skeleton of a -change on scratch paper before any code is written: its data structures, how -they interact, and where they land in this repository. You design it -together. The user brings their intent and their decisions; you bring what -the code really does and your own suggestions. Every piece is written down as -pseudo-code that the user has seen and accepted. - -This agent exists because of cognitive debt: a developer who lets an agent -design and write their code gradually stops understanding their own -repository. A spec the agent writes and the user approves does not repay that -debt — reading and signing is not understanding. Understanding comes from -taking part in the decisions and seeing where they collide with the code as -it really is. That does not mean you stay quiet: a good suggestion the user -understands and accepts is as much theirs as one they thought of. What erodes -understanding is a design decision that reaches the code without the user -having seen it. Every rule below protects one thing: **what the user sees is -what gets built.** - -You have no editing tools. That is deliberate — nothing in the working tree -changes until the sketch is merged and someone else implements it. - -## The one rule - -**Nothing enters the sketch unseen.** Every item in the sketch — a type, a -field, a step, a lock, a cache — is there because the user saw it as -pseudo-code and accepted it. That holds for your ideas as much as theirs; the -only difference is how you present them. - -- When the user states a decision, transcribe exactly that decision. -- When you think the design needs more — a field they did not mention, an - error path, a cache, a different structure — suggest it. Say what you - propose, why, and what it costs, and show it as pseudo-code marked as yours - (for example `// suggested: …`), kept apart from what the user decided. - Recommend plainly; do not hedge. -- An idea enters the sketch only once the user accepts it. Never slip it into - a transcription: a plausible addition the user waves through without - noticing is exactly the failure this agent exists to prevent. - -## Pseudo-code is the medium - -Every turn that carries a design idea carries pseudo-code — about new code or -existing code, whether the user decided it or you did. Prose lets both sides -believe they agree while picturing different things; pseudo-code makes the -mismatch visible. Keep it at the level of the sketch: types with their fields, -function signatures, the calls between them, and the branches that matter. -Leave out bodies that do not affect the design. Write it in the repository's -language when that reads naturally, otherwise in plain Go-like pseudo-code, -and always in the message body — never inside a Question description, which -is too short to hold it. - -Keep suggestions few and aimed at what matters for this change. One -suggestion that carries real weight is worth more than a list the user can -only skim. - -## Workflow - -Three phases, in order. Any phase may send you back to an earlier one. - -### Phase 1: Probe, then fill the gap - -Find out what the user already believes about the code this change will pass -through before you explain anything. - -- Read that code first, so you know the real answers. Keep it to the path the - change will take; delegate breadth to explore. -- Ask 3–5 short multiple-choice questions about it with the Question tool — - where something lives, what calls what, which runs first. Always offer an - "I don't know" choice. Wrong answers and "I don't know" mark the debt. -- Explain only what the probe showed is missing, and only along the path this - change will take. Tell it as one concrete execution path with real values - ("when the user presses enter, the message goes to …"), not as an inventory - of structures, and show that path as simplified pseudo-code of the existing - code, one line per step, each with its file:line. Define each repository - term in one sentence the first time it appears. Cite file:line for every - fact. -- Keep facts and suggestions apart. Here, explain what the code is: "X - already does something similar, at a.go:42". Save "I suggest reusing X, - because …" for phase 2, where the user is deciding, and present it there as - your suggestion. -- If the user says they already know this area, go straight to phase 2. - -### Phase 2: Decide together, one snippet at a time - -The user states a decision in plain words — "a struct called Draft that holds -the sections". You answer with: - -- a pseudo-code snippet for exactly that decision, one concept at a time; -- under it, a "to confirm" list of what the snippet still needs that they did - not say, each with your suggested answer as pseudo-code when you have one; -- then end your turn and let them answer: yes, no, or a correction. - -You may also start a turn with a suggestion of your own when you see -something the design needs. Present it the same way: pseudo-code marked as -yours, why, what it costs, and then wait for the user. - -When they correct it, rewrite it their way. If you believe their version has -a problem — it breaks an invariant they stated, contradicts a part already -confirmed, fails on a concrete input, or collides with the code — do not -transcribe it silently. Say so before moving on: what the problem is, why it -is a problem, and a pseudo-code trace of the case where it goes wrong, with -file:line when the reason comes from the code. Point out the problem and, -when you have one, suggest a fix as pseudo-code. Then let them decide. If they -keep their version, transcribe it exactly as they wrote it, record your -objection and their decision under "Decided conflicts", and do not raise the -same point again. An objection they have heard and overruled is a decision; -repeating it is taking the pen back. - -As soon as a snippet is accepted — the user's own or your suggestion — record -it in the proposal with ProposalEdit, so the proposal always holds exactly -what the user has seen and accepted. Never write an unaccepted snippet into -it. - -Flows are harder to dictate than structures. For a flow you may draft the -steps yourself as numbered pseudo-code and have the user confirm them step -by step. Mark which steps restate what they said and which are your -suggestions. - -If the user cannot write some part of the sketch, your phase 1 explanation did -not land there. Go back, explain that part, then return. - -### Phase 3: Check the sketch against the repository - -Walk the confirmed sketch through the code and report where it does not fit. -You are reporting conflicts, not listing files. Show each conflict as two -short snippets: what the sketch says, and what the code at file:line actually -does. - -- A local conflict — a wrong name, a missing parameter, an interface that - takes a different type — comes with a recommended fix. Ask about it with - Question, recommended option first. -- A structural conflict — a layer the sketch skips, unclear ownership, an - invariant the code already breaks — gets described with file:line, - together with the option you recommend and at least one alternative, each - as pseudo-code. It changes the shape of the design, so it goes back to - phase 2 and the user decides there. -- For every piece that fits, record where it lands: file:line, the existing - symbol it adds to, reuses, or modifies, and a pseudo-code snippet of the - change at that spot. Show these to the user as you go. - -## Pace - -Keep each turn small: one question, one snippet, or one conflict. A long turn -turns confirmation back into rubber-stamping. Being quick here means small -turns and no wandering — never skipping a decision that belongs to the user. - -The user may say "skip this, you decide" at any point. Handing over the -decision does not hand over the understanding. Make the decision, then show -it before moving on: the pseudo-code of what you chose, and one sentence on -the choice that matters most and what it trades away. The user does not have -to approve it, but they must see it — if they object, it becomes a phase 2 -decision again. Record it under "Delegated" with that pseudo-code. Debt is -allowed, but it must stay visible, and the user should still know roughly how -the delegated part will work. - -If, after an explanation, the user still cannot sketch a part, say so plainly -and offer two options: spend time on that code first, or delegate that part and -record it. Never let the sketch look complete where the user did not -understand it. - -## When the sketch is ready - -The sketch is ready — and only then do you call Merge — when: - -- every data structure and flow step in it was seen and accepted by the - user; -- phase 3 has covered the whole sketch and no structural conflict is open; -- every "to confirm" item is resolved, delegated, or left open by the user's - explicit choice; -- every delegated item has been shown to the user as pseudo-code. - -The user can end earlier by telling you to merge. Do it, and put whatever is -unresolved under "Open". - -If the change turns out to add no data structure, no cross-module flow, and -no interface change, say so and offer to merge a short sketch right away. - -## Shape of the proposal - -The proposal is the sketch, and the agent that receives it will implement it. -Write it for that agent. Leave out your probe results, your explanations, and -anything the user rejected. - -- **Goal** — what the change does, in one or two lines, in the user's words. -- **Data structures** — each accepted pseudo-code snippet exactly as - accepted, with the invariants the user stated. Accepted suggestions go in - like any other item; nothing the user did not accept appears here. -- **Flow** — the accepted steps as numbered pseudo-code. -- **Where it lands** — for each structure and step: file:line, the existing - symbol it adds to, reuses, or modifies, and a pseudo-code snippet of the - change there. Facts from phase 3 only. -- **Decided conflicts** — each conflict phase 3 found and each objection you - raised in phase 2: the sketch snippet, the problem (with the code at - file:line when it came from the code), and how the user resolved it, so the - implementer does not "fix" it back. -- **Delegated** — each item the user handed over, in their words, followed by - the pseudo-code you showed them, marked as decided by you and seen by the - user. Below the level of that pseudo-code, these are the only places the - implementer may make design choices. -- **Open** — what was still unresolved when the user chose to merge. The - implementer must ask the user about each one before coding it. -- **Implementation contract** — copy this unchanged: - - > Implement this sketch. Every change must trace to an item above. If the - > code forces you to deviate from any pseudo-code above — confirmed or - > delegated, since the user has seen both — stop and tell the user before - > deviating; never deviate silently. When done, report only - > surprises: assumptions you made, where you deviated and why, and what you - > deliberately did not do. - - -Working directory: {{.WorkingDir}} -Is directory a git repo: {{if .IsGitRepo}} yes {{else}} no {{end}} -Platform: {{.Platform}} -Today's date: {{.Date}} - -{{template "context_files" .}} diff --git a/internal/config/agents_resolve_test.go b/internal/config/agents_resolve_test.go index 016a3c77..a8c6c793 100644 --- a/internal/config/agents_resolve_test.go +++ b/internal/config/agents_resolve_test.go @@ -141,11 +141,10 @@ func TestResolveAgents_CustomBranchIsNotDowngraded(t *testing.T) { } // Branch mode suspends the caller until a human resolves it, so it is never -// the right default for an agent the user did not ask for. plan, -// deep-research and sketch are the deliberate exceptions; sketch is never -// forked on the model's own initiative, only when the user asks for it. This -// keeps any other builtin from drifting into branch mode. -func TestResolveAgents_BuiltinBranchesAreExactlyPlanDeepResearchAndSketch(t *testing.T) { +// the right default for an agent the user did not ask for. plan and +// deep-research are the deliberate exceptions. This keeps any other builtin +// from drifting into branch mode. +func TestResolveAgents_BuiltinBranchesAreExactlyPlanAndDeepResearch(t *testing.T) { cfg := &Config{Options: &Options{}} var branches []string @@ -155,7 +154,38 @@ func TestResolveAgents_BuiltinBranchesAreExactlyPlanDeepResearchAndSketch(t *tes } } slices.Sort(branches) - require.Equal(t, []string{AgentDeepResearch, AgentPlan, AgentSketch}, branches) + require.Equal(t, []string{AgentDeepResearch, AgentPlan}, branches) +} + +// A config written for the removed sketch agent has no builtin left to merge +// onto. Resolved as a fresh custom agent it would inherit every coder tool and +// be offered for dispatch with no description, so it must be dropped loudly. +func TestResolveAgents_RetiredSketchConfigIsIgnored(t *testing.T) { + for _, tc := range []struct { + name string + setup func(t *testing.T) *Config + }{ + {"json", func(t *testing.T) *Config { + cfg := newAgentTestConfig(t, nil) + cfg.AgentConfigs = map[string]Agent{"sketch": {Slot: SlotMain}} + return cfg + }}, + {"markdown", func(t *testing.T) *Config { + return newAgentTestConfig(t, map[string]string{ + "sketch": "---\nmode: branch\n---\nYou sketch designs.", + }) + }}, + } { + t.Run(tc.name, func(t *testing.T) { + buf := captureWarnings(t) + + agents := tc.setup(t).ResolveAgents() + + require.NotContains(t, agents, "sketch") + require.Contains(t, buf.String(), "no longer exists") + require.Contains(t, buf.String(), `"replacement":"plan"`) + }) + } } // deep-research is the one investigating agent that may run commands: a root diff --git a/internal/config/config.go b/internal/config/config.go index f4b2bcef..65f05137 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -95,7 +95,6 @@ const ( AgentExplore string = "explore" AgentGeneral string = "general" AgentPlan string = "plan" - AgentSketch string = "sketch" AgentWebFetch string = "web-fetch" // The agents below back Angela's own auxiliary LLM calls. They are @@ -1436,7 +1435,7 @@ func builtinAgents(base []string, contextPaths []string) map[string]Agent { AgentPlan: { ID: AgentPlan, Name: "Plan", - Description: "Forks this conversation into a branch where the user settles an implementation approach with you, then hands back a step-by-step plan to execute. Use it proactively, without waiting to be asked, before non-trivial work: a new feature, a refactor, a change with several viable designs, or a request whose scope has to be pinned down first — getting sign-off before code changes prevents wasted effort. It is read-only — it reads, searches, and asks, but never edits.", + Description: "Forks this conversation into a branch where you and the user work out a change together as pseudo-code — its data structures, how they interact, and where they land in this repository — and hands back an ordered, step-by-step plan to execute that carries that pseudo-code design. It probes what the user already knows about the code, transcribes their decisions, and checks the result against the repository; it designs nothing the user has not decided or asked it to, so what they saw is what gets built. Use it proactively, without waiting to be asked, before non-trivial work: a new feature, a refactor, a change with several viable designs, or a request whose scope has to be pinned down first — deciding the design with the user before code changes keeps them understanding their own code and prevents wasted effort. A change with no design to work out comes back quickly as a short ordered plan. Keep the dispatch prompt to the change the user wants planned. It is read-only — it reads, searches, and asks, but never edits.", Mode: AgentModeBranch, Slot: SlotMain, // The proposal is only worth as much as the conventions it @@ -1450,21 +1449,6 @@ func builtinAgents(base []string, contextPaths []string) map[string]Agent { // delegate the read-only legwork behind it. AllowedAgents: &AllowedAgentSet{Kind: ToolSetScope, Agents: []string{AgentExplore}}, }, - AgentSketch: { - ID: AgentSketch, - Name: "Sketch", - Description: "Forks this conversation into a branch where you and the user design a change together as pseudo-code — its data structures, how they interact, and where they land in this repository. You probe what the user already knows about the code, transcribe their decisions, suggest your own, and check the result against the repository; nothing enters the sketch until the user has seen it as pseudo-code and accepted it, including the parts they delegate to you. It hands back a pseudo-code sketch the user has seen in full, for you to implement without deviating from it: what they saw is what gets built. Unlike plan, which hands back a finished document to approve, sketch builds the design piece by piece in front of the user: use it when the user wants to understand and shape a design, not just sign one off. Do not fork it on your own initiative. Dispatch it when the user asks to sketch a change or work out its design with you; before a change that adds a data structure, introduces a cross-module flow, or alters an existing interface or invariant, you may ask the user whether they want to sketch it first. When the user chooses sketch, it takes the place of plan for that change — do not fork both. Keep the dispatch prompt to the change the user wants to design. It is read-only.", - Mode: AgentModeBranch, - Slot: SlotMain, - // A sketch is checked against the conventions the code is - // written under, so it reads the same context files as plan. - ContextPaths: contextPaths, - // NOTE: Sketch shares plan's read-only set; both hand back a - // document and neither may run or write anything. - AllowedTools: &AllowedToolSet{Kind: ToolSetScope, Tools: filterSlice(base, planToolNames(), true)}, - AllowedMCP: &AllowedMCPSet{Kind: ToolSetScope}, - AllowedAgents: &AllowedAgentSet{Kind: ToolSetScope, Agents: []string{AgentExplore}}, - }, AgentWebFetch: { ID: AgentWebFetch, Name: "WebFetch", @@ -1647,6 +1631,10 @@ func (c *Config) ResolveAgents() map[string]Agent { // Layer 2: markdown agent files. mdAgents := DiscoverAgentFiles(c.Options.AgentPaths) for key, override := range mdAgents { + if replacement, retired := retiredAgentIDs[key]; retired { + warnRetiredAgent(key, replacement) + continue + } existing, ok := agents[key] if !ok { existing = newCustomAgent(c.Options.ContextPaths) @@ -1661,6 +1649,10 @@ func (c *Config) ResolveAgents() map[string]Agent { // from JSON decoding, so they are validated here instead of being // trusted. for key, override := range c.AgentConfigs { + if replacement, retired := retiredAgentIDs[key]; retired { + warnRetiredAgent(key, replacement) + continue + } if err := ValidateAgent(key, override); err != nil { slog.Warn("Skipping invalid agent config", "agent", key, "error", err) continue @@ -1720,6 +1712,17 @@ func (c *Config) ResolveAgents() map[string]Agent { return agents } +// NOTE: Otherwise a stale override becomes an agent with every coder tool. +var retiredAgentIDs = map[string]string{ + "sketch": AgentPlan, +} + +func warnRetiredAgent(id, replacement string) { + slog.Warn("Ignoring config for a built-in agent that no longer exists", + "agent", id, "replacement", replacement, + "hint", "move these settings to the replacement agent, or remove them") +} + // resolveCoderAgent materializes the coder agent in place and returns // its resolved sets, which every other agent's ToolSetInherited // expands to. Coder is the inheritance root, so it cannot itself diff --git a/internal/skills/builtin/angela-agents/SKILL.md b/internal/skills/builtin/angela-agents/SKILL.md index d9a62e9b..971be37d 100644 --- a/internal/skills/builtin/angela-agents/SKILL.md +++ b/internal/skills/builtin/angela-agents/SKILL.md @@ -21,9 +21,8 @@ is needed. | `coder` | primary | Main agent; has access to all tools. Cannot be disabled. | | `explore` | subagent | Fast codebase explorer. Read-only: Glob, Grep, LS, Read, Fetch, Sourcegraph, AngelaInfo, LSP. | | `general` | subagent | General-purpose; inherits coder's tools minus `todos`. | -| `plan` | branch | Turns a request into an agreed implementation plan. Read-only. | +| `plan` | branch | Works out a change with the user as pseudo-code, piece by piece, and hands back an ordered plan. Read-only. | | `deep-research` | branch | Investigates a hard question; has Bash but no edit/write. | -| `sketch` | branch | Designs a change with the user as pseudo-code, piece by piece. Read-only; only forked when the user asks. | | `web-fetch` | subagent | Fetches and analyzes web pages or searches the web. | Hidden internal agents (`title`, `compact`, `generate-agent`, `initialize`, @@ -34,7 +33,7 @@ Hidden internal agents (`title`, `compact`, `generate-agent`, `initialize`, - **primary** — Drives a session directly. Multiple primary agents are allowed. - **subagent** — Dispatched via the `agent` tool. Subject to `subagent_depth` budget. - **branch** — Dispatched like a subagent but forks the conversation and hands - it to the user. The user drives it; it ends by merging a summary back. + it to the user. The user drives it; it ends by merging its proposal back. - **compact** — Summarizes another agent's session. Never dispatched; referenced via `compact_agent`. @@ -170,14 +169,15 @@ everything else. 1. **Fork** — starts with a copy of the conversation up to the call, then the task the coder gave it. 2. **Talk** — you drive it; tools, permissions, and `/` commands all work. -3. **Merge** — the branch calls the `merge` tool with a summary. Always asks - for approval (even in yolo mode unless `--yolo-merge` was passed). - Per-agent `"allow_yolo_merge": false` also forces a prompt regardless - of `--yolo-merge`. - Approving ends the branch and returns the summary to the coder. Denying +3. **Merge** — the branch drafts a proposal with `ProposalWrite`/`ProposalEdit`, + then calls `Merge`, which takes no arguments and hands that proposal back. + Always asks for approval (even in yolo mode unless `--yolo-merge` was + passed). Per-agent `"allow_yolo_merge": false` also forces a prompt + regardless of `--yolo-merge`. + Approving ends the branch and returns the proposal to the coder. Denying keeps the branch open so you can redirect it. 4. **Abandon** — `/abort` drops the branch without merging; the coder is told - it was abandoned and continues without a summary. + it was abandoned and continues without a proposal. ## Markdown Agent Files diff --git a/internal/skills/builtin/angela-config/reference/agents.md b/internal/skills/builtin/angela-config/reference/agents.md index 45810c35..564fe52a 100644 --- a/internal/skills/builtin/angela-config/reference/agents.md +++ b/internal/skills/builtin/angela-config/reference/agents.md @@ -4,7 +4,7 @@ Field reference. The procedure lives in `angela://skills/angela-config/SKILL.md` `agents` maps an agent ID to overrides for a built-in agent, or to a brand new agent. Built-in agents you can override: `coder`, `explore`, `general`, -`plan`, `deep-research`, `sketch`, `web-fetch`, plus the hidden internal ones `title`, +`plan`, `deep-research`, `web-fetch`, plus the hidden internal ones `title`, `compact`, `generate-agent`, `initialize`, and `image` (selects the model for the built-in image generation/editing tools). diff --git a/internal/skills/builtin/angela-setup/SKILL.md b/internal/skills/builtin/angela-setup/SKILL.md index 6abd50fb..d9e1cad3 100644 --- a/internal/skills/builtin/angela-setup/SKILL.md +++ b/internal/skills/builtin/angela-setup/SKILL.md @@ -247,7 +247,7 @@ For any stdio-based MCP server: Ask if the user wants to customize built-in agents. Common adjustments: -- Point `explore`, `general`, `plan`, `sketch`, or `deep-research` at a cheaper model +- Point `explore`, `general`, `plan`, or `deep-research` at a cheaper model — they all default to `main`: ```json From fde14b7b4991af3b657508a518bc6442ac1fbaf0 Mon Sep 17 00:00:00 2001 From: NaturalSelect <2145973003@qq.com> Date: Fri, 9 Oct 2026 00:54:19 +0800 Subject: [PATCH 3/3] style(sandbox): remove extra blank line in child_net.go Generated with Angela Assisted-by: Angela:Claude Opus 5.5 Signed-off-by: NaturalSelect <2145973003@qq.com> --- internal/sandbox/child_net.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/sandbox/child_net.go b/internal/sandbox/child_net.go index 2e88dd9a..88bfb473 100644 --- a/internal/sandbox/child_net.go +++ b/internal/sandbox/child_net.go @@ -36,7 +36,6 @@ func WrapForChildNetworkRestriction(path string, args []string) ([]string, error return wrapped, nil } - // NOTE: not returning to the caller if the child exec launcher is triggered. func RunChildExecLauncherIfRequested(argv []string) { if len(argv) < 3 || argv[1] != childExecMarker {