Repository navigation
feat(agents): every Board agent is configured from Agent profiles and Prompts - #278
Open
hoangsnowy wants to merge 4 commits into
Open
hoangsnowy wants to merge 4 commits into
hoangsnowy wants to merge 4 commits into
Conversation
… Prompts
One agent registry (AgentRoles): every agent has one stable role key used for
its profile (Agents:<Key>:*), its prompt (Settings -> Prompts), the AgentRole
of its LLM calls (Insights "by agent") and its run label.
- Reviewer, Testing (tickets) = Acceptance, Autopilot lead =
AutopilotOrchestrator and Retrospective no longer hard-code
factory.Create("RemoteAgent") + "claude-sonnet-4-6": they resolve their
profile per call (seeds keep RemoteAgent + claude-sonnet-4-6).
- Agent profiles shows the Board agents and the Workflow pipeline in two
tables; pipeline labels say "(Workflow pipeline)". Discovery replaces the
Decomposer profile/prompt and also drives New idea's requirement step
(AgentRunScope).
- Settings -> Prompts lists every Board agent; Board prompts are templates
with {placeholders} filled per run (PromptTemplate); the untrusted-text rule
stays code-appended.
- Budget gate on every Board agent, decided from the EFFECTIVE provider
(client.Provider after force-provider); only real member's-computer runs
are exempt. IssueWork now derives CLI mode from it too.
- Testing (tickets) and the Autopilot lead read the project practices
(ProjectPractices, IBoardRepos from Workspaces).
- The paired runner passes the profile model to the CLI: claude --model,
codex -m (CliModel), only when that CLI can serve it.
- Insights groups by AgentRoles.Canonical and shows the Agent profiles label.
- Guard: AgentRegistryGuardTests parses Agents/ and Decomposition/ with Roslyn
and fails on a literal provider to ILlmClientFactory.Create or a literal
LlmRequest Model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WAdQ7Rm24xss5QDSvXtJB
Comment on lines
+73
to
+76
| foreach (var role in AgentRoles.All) | ||
| { | ||
| if (For(role.Key) is { } options) { yield return options; } | ||
| } |
Comment on lines
+78
to
+87
| foreach (var call in root.DescendantNodes().OfType<InvocationExpressionSyntax>()) | ||
| { | ||
| if (call.Expression is MemberAccessExpressionSyntax { Name.Identifier.ValueText: "Create" } | ||
| && call.ArgumentList.Arguments is [var provider, ..] | ||
| && provider.NameColon is null or { Name.Identifier.ValueText: "providerName" } | ||
| && HardCoded(provider.Expression)) | ||
| { | ||
| yield return $"line {Line(call)}: ILlmClientFactory.Create({provider.Expression})"; | ||
| } | ||
| } |
… values keep working Review of #278: 1. No model downgrade. Board agents (Discovery, Coding/Reviewer/Testing (tickets), Autopilot lead, Retrospective) now choose NO model by default ("Computer's default" in Agent profiles). On a member's computer the request carries an empty model (RemoteAgentLlmClient accepts it via LlmRequest.ValidateAllowingNoModel) and CliModel adds no flag, so the CLI runs its own default. On our servers the provider client fills in its configured model before Validate (ProviderDefaultModel, wired into PooledChatLlmClient per provider, MafChatClient, ChatClientLlmClient). An explicitly chosen model is still passed (--model / -m). Startup validation still requires a model for the Workflow pipeline agents. 2. No silent loss on the Decomposer -> Discovery rename. LegacyAgentSettings reads Agents:Decomposer:* (tenant, platform, appsettings) for any Discovery field with nothing stored, and the Decomposer prompt override applies until a Discovery one is saved. Agent profiles marks the row and Reset clears the legacy values too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018WAdQ7Rm24xss5QDSvXtJB
Comment on lines
+232
to
+239
| foreach (var field in RoleFields.Where(f => byKey.GetValueOrDefault(AgentSettings.Key(role, f))?.Source is null or SettingSource.Default)) | ||
| { | ||
| if (await Legacy.ReadAsync(role, field, _tenantId) is { } old) | ||
| { | ||
| row.Loaded[field] = old; | ||
| row.Legacy = true; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change description
One agent registry. Every agent that runs in the Board / Autopilot flow is now configured from Settings → Agent profiles and Settings → Prompts exactly like the pipeline agents, shows up in Insights under the same name, reads the project practices and passes the budget gate. Before this PR the UI implied all of that but the runtime ignored it: four Board agents hard-coded
factory.Create("RemoteAgent")andModel: "claude-sonnet-4-6", and the paired runner never passed the model to the CLI.Role keys (
AgentRoles). Each key is used for the profile (Agents:<Key>:*), the prompt (prompt/<Key>/system), theAgentRoleof LLM calls (what Insights groups by) and the run label:DiscoveryIssueWorkReviewerAcceptanceAutopilotOrchestratorRetrospectiveOrchestratorRequirementCodingTestingQaWhat changed:
ReviewAgent,AcceptanceAgent,AutopilotOrchestratorAgentandRetrospectiveAgentresolve their profile throughIAgentProfiles(BoardAgent.ProfileAsync) with seeds inAgentsOptions.IssueWorkand the decomposer use the registry keys too.data-role="<key>". Discovery replaces theDecomposerrow. New idea's requirement step runs the Requirement agent under anAgentRunScope("Discovery"), so it uses the Discovery profile and is filed as Discovery, while keeping the Requirement prompt and spec contract.claude-sonnet-4-6would have downgraded it.RemoteAgentLlmClientaccepts it via the newLlmRequest.ValidateAllowingNoModel, the runner adds no flag, and metrics name itcomputer-default.Validate(ProviderDefaultModel). That is Claude'sLlm:Claude:Model, the Azure default deployment, the vendor's or the OpenAI-compatible server's model. It is wired intoPooledChatLlmClientfor every provider,MafChatClientandChatClientLlmClient.LlmClientFactory, Offline and Failover are untouched.--model <id>for claude,-m <id>for codex (CliModel). Nothing is added when that CLI can't serve the model (a GPT id on claude), for an unknown CLI, or whenREMOTE_AGENT_ARGSalready names a model. The id is validated so it can never become a flag. The runner log showsmodel=<id|cli default>.PromptCataloglists every Board agent, grouped like Agent profiles: Discovery, Coding (tickets) on a member's computer (IssueWork), Coding (tickets) on the server (IssueWork.Server), Reviewer, Testing (tickets), Autopilot lead and Retrospective. Board prompts are templates. Their{owner},{repo},{branch}… placeholders are filled per run in a single pass (PromptTemplate), and the UI lists them. The untrusted-text security rule is appended by code and can't be overridden. Workspace rules apply to all of them. The first lines stay "You are the Agent …"; the IssueWork openings are unchanged.AcceptanceAgentandAutopilotOrchestratorAgentreadProjectMemory.PracticesBlockthrough the sharedProjectPracticeshelper, which the Reviewer now uses too. The Autopilot lead gets the board's repositories from a new DomainIBoardRepos, implemented in Workspaces. The KnowledgeTab copy now names the agents that actually read practices.AgentBudgetGate) applies to every Board agent and to Discovery. It is decided from the effective provider (client.Providerafter force-provider). Only a call that really runs on a member's computer is exempt.IssueWorknow derives CLI mode from the effective provider too, so a forced server provider can't skip the gate.AgentRoles.Canonicaland shows the Agent profiles label. Board agents keep their own keys (IssueWorkandAcceptanceare no longer merged into pipelineCodingandTesting). Run labels come from the registry. The mascot maps the Board keys.AgentRegistryGuardTestsparses every file underModules.Pipeline/Agentsand/Decompositionwith Roslyn (syntax only). It fails on a string literal, an interpolated string or a same-fileconstpassed as the provider toILlmClientFactory.Createor as anLlmRequestModel, whether named or positional,new LlmRequest(…)or target-typednew(…). Theory cases prove the scanner flags each form. This adds a test-only reference toMicrosoft.CodeAnalysis.CSharp5.9.0, the latest stable on nuget.org today.Docs:
docs/CONFIG.md(Agent profiles section rewritten: keys, seeds, model-to-CLI, budget gate, Discovery, prompt placeholders, practices) andREADME.md(Concepts, default profiles, "Add a pipeline agent").Decomposer → Discovery, without losing saved values.
LegacyAgentSettingsreadsAgents:Decomposer:<Field>for any Discovery field with nothing stored. It checks the tenant first, then the platform, then appsettings. The Decomposer prompt override, its history and the Prompts page fall back the same way until a Discovery override is saved. In Agent profiles the row shows Saved as "Decomposer" — still applies. Saving a field stores it under Discovery; Reset, for the profile or the prompt, also clears the tenant's Decomposer values. This is documented indocs/CONFIG.md.Run labels for new Board runs are
IssueWorkandAcceptance. Older rows labelledCodingorTestingstill group with the pipeline roles in Insights.Claude Design: push pending. The new Agent profiles layout (two tables, "Computer's default") and the grouped Prompts page are only in the repo for now. They still have to be pushed to the Claude Design project by a user-run
/design-sync; a non-interactive session can't authenticate DesignSync.Not touched (owned by other changes):
LlmClientFactory,LlmCallScope,OfflineLlmClient,FailoverLlmClient,AutopilotPlanner/AutopilotSettings/TicketWorkflow/ProjectOrchestrator*.ProjectOrchestrator.Agent.csstill saves the decision run labelled "Orchestrator"; Insights files its calls underAutopilotOrchestratorbecause the request now carries thatAgentRole.Type of change
Related issues
Audit "one agent registry": hard-coded Board agent provider/model, model never reaching the CLI, Board prompts not tunable, practices and budget gate missing on Board agents, Insights role names.
Checklist
dotnet buildpasses locally in Release mode: 0 errors. No new warnings. Remaining warnings are pre-existing, inAgentOs.Web(TreatWarningsAsErrors=false): CA2016/CA1001 in untouched lines ofBoardTaskService,BoardTicketSteps,ProjectOrchestrator.Agent.cs, plusASPIRE010.dotnet test --solution AgentOs.slnx -c Release: 1712 total, 1637 passed, 0 failed, 75 skipped (live/real-auth gated), after merging main (fix(llm): Board agent runs never get the offline demo in place of the real model #277).Test plan
BoardAgentRegistryTestscovers:AgentRoleset;IssueWorkorIssueWork.Serverprompt by effective provider;CliModelTests(claude--model, codex-m, skip cases, operator override, Windows paths on Linux) andAgentRegistryGuardTests.ComputersDefaultModelTests:CliModel.Flagsreturns[];Validate;--model <id>;computer-default;ASPNETCORE_ENVIRONMENT=Development dotnet run --project src/AgentOs.Web, DevAutoLogin): the fullAgentOs.E2E.Testssuite ran 50 tests, 30 passed and 20 skipped (real-auth).AgentProfilesTestswas updated: two tables, the Reviewer row is present, the QA row is found bydata-role="Qa"/"QA (Workflow pipeline) model".model=<id>. Standalone shows only the degraded path.🤖 Generated with Claude Code
https://claude.ai/code/session_018WAdQ7Rm24xss5QDSvXtJB