LLMDesktopAssistant
AGENTS.md1 starsChanged 31 days agoMITView on GitHub
The project is cross-platform C#/Avalonia application for universal LLM/agentic interactions. The project has 2 separate agentic execution systems:
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,843 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository11 other files it gives its agents.
- code-traceSkill
- command-authoringSkill
- dass-reportSkill
- dass-testSkill
- equipSkill
- grillingSkill
- idea-to-ticketSkill
- lua-ui-controlsSkill
- rival-reconSkill
- skill-authoringSkill
- tool-authoringSkill
The file
We split this AGENTS.md into the rules it gives an agent
6 rules. About 0 are common, 4 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 282
Use LLMDesktopAssistant.Localization.Locale static facade class for getting locale keys and values:
- Distinctive: line 470
DO NOT build the entire solution!
- Distinctive: line 480
Do not filter, truncate or post-process the build output in any way - run the command bare and let it stream.
- Distinctive: line 484
If the output is too long - just read the part you need from the streamed result, never by piping it.
- Distinctive: line 488
Use specified memory block to store completed work inside the epizodic logs after the completed session.
- Very specific: line 498
Default canonical roles, used verbatim as the Status: string (needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix).
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
dASS - Desktop Assistant (AGENTS.md context)
The project is cross-platform C#/Avalonia application for universal LLM/agentic interactions.
Agents
The project has 2 separate agentic execution systems:
- Chat agents - agents that live in a chat, triggered by user messages, ordered by consecutive execution stages, and cannot execute in parallel.
- Agentic tasks - agents that runs once for doing specific tasks
- Internal system tasks, such as chat naming, automatic memory recording/retrieval, etc.
- Explicit invokation via
agent-calltool or viadass.agent.callLua API - Predefined sub-agents - markdown addons (
agents/<name>.md/agents/<name>/AGENT.md) loaded from addon packs, see Addons
Directories
src/LLMDesktopAssistant/Addons/
AddonBase.cs - base of every agentic addon (name, order, aliases, description/body, paths, diagnostics)
AddonKind.cs - [Flags] kinds: Pack, Skill, SubAgent, Tool, PromptContext, Template, LuaScript, SlashCommand (+ MemoryBlock reserved)
IAddonTypeDescriptor.cs - per-kind descriptor marked with [AddonTypeDescriptor]
AddonSetCollectorBase.cs - merges addons across packs, applies per-agent changes, freezes clones
Loading/ - AddonFileLocatorBase<T>, pack locators (App/Chat/Combined), AddonPackSearchFoldersProvider
Management/ - addon managers, invalidators, settings watchers
Parsers/ - FrontmatterBasedAddonParser + YAML-frontmatter property parsers
Search/ - BM25 search over the addons available to an agent
MVVM/ - addon list/card UI (AddonListView, AddonCardView, card factory)
... per-kind models and parsers live in their own domain folders, not here (see **Addons**)
src/LLMDesktopAssistant/Agents/
ChatAgentDescriptor.cs - the descriptor/configuration object for the chat agent
ChatAgentInstance.cs - the agent reference (by agent's GUID) used in execution stages ONLY
... other root files used mostly for ChatAgentDescriptor's configuration
ExecutionStages/
AgentExecutionStage.cs - abstract base class for execution stages
AgentPreExecutionContext.cs - context that taken by execution stages to select next agent
AdaptiveAgentExecutionStage.cs - the adaptive execution stage that uses LLM-based router for selecting next agent
...
Memory/ - agentic memory-related directory (facts/episodic logs)
IMemoryFactStore.cs
IMemoryLogStore.cs
...
SubAgents/ - the sub-agent addon kind: info object, parser, loader, file locator, type descriptor
SubAgentInfo.cs - the main sub-agent information object, contains name, description, metadata, used tools, skill, memory blocks, inner sub-agents
...
Tasks/
AgentTask.cs - agentic task object, produced by AgentTaskExecutor
AgentTaskExecutor.cs - the implementation of IAgentTaskExecutor
AgentTool.cs - abstract definition of tool, used by agent inside task
ChatAgentTool.cs - wrapper for chat's ToolInfo that inherits AgentTool
...
src/LLMDesktopAssistant/Prompting/
Context/ - SCM: anchors, deltas, sections, checkpoint kinds (see **Prompting & SCM**)
Providers/<Name>/ - one folder per section (state/delta provider and renderer quartets)
Management/ - LLT template importers and prompt part managers (personas, specializations, slots, skills, sub-agents)
Resources/ - built-in *.llt templates (core_prompt, components, sliders, message_prompt, naming, router, summarizer)
LLT/ - LLT editor control, tokenizer, diagnostics
Skills/ - the skill addon kind (SkillInfo, parser, locator, type descriptor)
ContextCheckpoint.cs, ContextCheckpointKind.cs, SystemPromptSnapshot.cs
src/LLMDesktopAssistant/LLM/
Domain/
Chat.cs - the chat instance itself, contains a messages sequence
ChatMessage.cs - abstract base class for messages
...
Services/
ChatExecutionService.cs - **central** service for chat execution pipeline
...
Agents/
AgentManagementService.cs - service for chat agents retrieval
AgentOrderingService.cs - service for getting next chat agent for execution
...
Prompting/
AgentPromptComposer.cs - **central** per-agent prompt entry point: builds the (messages + tools) bundle, owns header mode and toolset-cache invalidation
AgentEffectiveMessagesProvider.cs - effective message set for an agent plus its active checkpoints
PromptAnchoredSectionProcessor.cs - SCM anchor lifecycle and delta emission
MessageVisibilityService.cs - per-agent message visibility
ChatMessageQuoteRenderer.cs - neutral content quote of a message (naming, summarizer, router)
...
Tools/
ToolExecutionService.cs - service for chat-related tool execution
ToolsetBuildingService.cs - service for collecting and building toolset from all sources for each agent
...
MVVM/
Additional/ - AdditionalData, the point of persisted/visual extension, carried by chats, messages and tool calls (AdditionalChatData, AdditionalMessagePart, DynamicAxamlControlAdditionalData + their views); a message's AdditionalData also holds the slash-command fingerprint and the executor-added message content parts
src/LLMDesktopAssistant/SlashCommands/
SlashCommandInfo.cs - the slash-command addon info (namespaces, ModelFacingMode, Generate ceiling, Executor)
SlashCommandExtractor.cs - the single owner of the leading '/' marker and the '//' escape; everything downstream is slash-free
SlashCommandSetCollector.cs - merges ISlashCommandProvider into the chat's set + the EnableCommands gate
SlashCommandSource.cs / SlashCommandOrderTiers.cs - source kind and the native > scriptable > derived tiers
ModelFacingMode.cs - Raw / Neutral / Hidden (Raw by default; Neutral replaces the content with the bare token, Hidden drops it)
Loading/ - SlashCommandAddonTypeDescriptor ('commands'), SlashCommandFileLocator (its Extensions are contributed by the registered scriptable engines), SlashCommandParser (frontmatter-based; picks the engine by file extension, which owns the frontmatter markers and builds the executor)
Providers/ - DerivedSlashCommandProvider<T>, Skill/SubAgent providers and their executors, NativeSlashCommandProvider (+Attribute) with the built-in /addon-reload and /call, SlashCommandVariableExpander
Scripting/ - IScriptableSlashCommandEngine (+Descriptor) and CommandArgsNode; the engines themselves sit next to the tool engines (Scripting/Lua, Scripting/CSX, Desktop/Scripting/Python)
Resolution/ - SlashCommandResolver, SlashCommandMatcher (exact match), SlashCommandToken
Arguments/ - SlashCommandArgumentParser/Binder/Schema, ISlashCommandArgumentFormatProvider
Execution/ - ISlashCommandExecutor, execution context/result, SlashCommandIntent, SlashCommandFingerprint
Input/ - SlashCommandCompletionSource, InputAnalyzer, PrefixMatcher, HighlightPalette
src/LLMDesktopAssistant/InputCompletion/
IInputCompletionSource.cs - generic source (Priority, TryCompute, TryHighlight); the first source that claims the caret wins
InputCompletionService.cs - picks the owning source, keeps the result, raises ResultChanged/SelectedIndexChanged
InputCompletionResult/Item/State/Span/Kind - the completion snapshot (span to replace, continuations, popup state)
Addons
Skills, sub-agents, tools, prompt contexts, templates and Lua scripts are all addons, discovered from addon packs on disk. Infra lives in src/LLMDesktopAssistant/Addons/; each kind's model, parser and locator live in that kind's domain folder.
A pack is a directory scanned for one folder per kind:
| Kind | Folder in pack | Layout |
Skill | skills/ | skills/<name>/SKILL.md or skills/<name>.md (.md, .mdx) |
SubAgent | agents/ | agents/<name>/AGENT.md or agents/<name>.md |
Tool | tools/ | extensions of the registered script engines (.lua, .alua, .py, .csx) |
LuaScript | scripts/lua/ | .lua, .alua |
Template | templates/ | LLT template extensions |
PromptContext | context/ | extensions not implemented yet |
SlashCommand | commands/ | commands/<name>.<ext> - a single file, no folder format; the extension picks the scripting engine (.lua/.alua, .csx, .py - Python only in the Desktop build) |
Packs are discovered under the working directories, the user profile and Directories.AddonPacks. AddonPackSearchFoldersProvider also scans other agent runtimes' home folders (.agents, .claude, .gemini, .codex, .github, .cursor, .windsurf, .opencode, .cline, .roocode, .lmstudio, .junie, .everywhere, ...), so third-party skills and sub-agents are picked up as-is - expect addons you did not create.
Adding a kind = <X>Info : AddonBase<X> + parser + IAddonFileLocator<X> (Folders, Extensions ordered by priority, AllowShortFormat, FullFormatName) + IAddonTypeDescriptor marked [AddonTypeDescriptor] + AddonSetCollectorBase<X, TChange>, plus an optional card factory and search provider. MemoryBlock is declared but reserved; SlashCommand is an active kind with three sources - native, scriptable (files) and derived from other addons (see Slash commands).
Addons are frozen after collection - addon.Clone() returns an unfrozen copy to mutate.
Slash commands
A slash command is a user-only token at the very start of a message (/skill:grilling, /agent:web-searcher <input>) that triggers a runtime action before the message reaches the model. Commands are never exposed to agents - the model only sees the message a command produced. They are the AddonKind.SlashCommand kind, chat-level and agent-agnostic.
A command has one of three sources, and SlashCommandOrderTiers mirrors them: native (2000) built in code, scriptable (1000) loaded from a pack's commands/ folder, derived (0) synthesised one-per-addon for skills and sub-agents. SlashCommandSource says which (descriptive: it drives the card's source chip and the fingerprint), while the ordering authority stays OverrideOrder = SlashCommandOrderTiers.ForSource(source).
Wiring (all chat-scoped):
ISlashCommandProvidercontributes commands;DerivedSlashCommandProvider<TSource>builds one per addon of a kind (SkillSlashCommandProvider->/skill:<name>,SubAgentSlashCommandProvider->/agent:<name>), stampingSlashCommandSourceand an executor.NativeSlashCommandProviderbuilds its commands in the constructor throughAddCommand(base class stampsNative+ the native tier and freezes them);[NativeSlashCommandProvider]auto-registers a provider,chatScopeddeciding the scope. Built-ins:/addon-reload(alias/reload,IsFixedso it cannot be disabled;Generate = false,ModelFacingMode.Neutral) and/call <agent>(forces the selected agent to run, bypassing the router and the execution-stage pipeline).- Scriptable commands ride the ordinary addon machinery:
SlashCommandFileLocatorscanscommands/and takes itsExtensionsfrom the registeredIScriptableSlashCommandEngines (a new engine/language plugs in without touching the locator),SlashCommandParserpicks the engine by file extension and requires frontmatter (RequiresFrontmatter, no markdown fallback - the body is code); it appliesnamespaces(plus the source pack),model-facing,generateandargument-schema(the command flavour - a bracket hint or the keyed DSL, not a tool's JSON schema), then stampsScript+SlashCommandOrderTiers.Scriptableand asks the engine for the executor.SlashCommandInfo.Sourceisnullfor native and scriptable commands. SlashCommandSetCollector : AddonSetCollectorBase<SlashCommandInfo, SlashCommandChange>merges the providers into the chat's set.GetAddonsForChat()is the only consumer path;GetAddonsForAgentis deliberately left unimplemented.SlashCommandInfo.Key(namespaces sorted, then name) is the dedup/settings identity;CanonicalToken(type:pack:name) is the typed form.SlashCommandOrderTiers: native2000> script1000> derived0.
Pipeline (ChatMessageInsertionService; pre-flight CanInsertUserInput and the send):
SlashCommandExtractor.TryExtractToken- the single owner of the/marker; the token is slash-free everywhere downstream.//escapes a literal leading slash.SlashCommandResolver+SlashCommandMatcher- exact match; total order is exact name > alias, thenOverrideOrderdesc,Orderasc,Key; the winner's losers andOverridesform the defeated set.- Arguments: the schema lives on the executor (
ISlashCommandExecutor.ArgumentSchema);SlashCommandArgumentParser+SlashCommandArgumentBinderapply defaults, validate and convert (viaISlashCommandArgumentFormatProvider). An invalid argument set blocks the send before execution. - Execute:
ISlashCommandExecutor.ExecuteAsync(ctx, ct)runs under aChatExecutionLevel.Commandtoken and mutatesctx.Messagedirectly.SlashCommandIntent.Resolve(generateIntent, command.Generate, result.Generate)is the final generation decision - it can only lower the caller's intent. SlashCommandFingerprint(data-only, persisted) goes intoAdditionalData;MessageContentProjectorshapes the model-facing content byModelFacingMode(Rawsends the content as typed - the default;Neutralreplaces it with the bare/<token>;Hiddendrops it) plus theAdditionalMessageContentParts the executor added.
Built-in executors: SkillCommandExecutor injects the skill body in full (no skill-load round-trip), expanded through SlashCommandVariableExpander ($ARGUMENTS, $SKILL_NAME, $SKILL_DIR/$CLAUDE_SKILL_DIR, otherwise the process environment; an unknown variable is left verbatim); SubAgentCommandExecutor launches the sub-agent like agent-callsub, with a wait keyed boolean argument (default true) deciding whether it awaits and injects the last answer or fires-and-forgets.
Scriptable executors run the body: Lua in-process on AsyncLua (full chat API - dass.chat, dass.command.context.get_message(), message read/write and append_*_part, dass.ui.create_control - await works, no stdout), C# script in-process (CommandArgs, Context.Message, Workdir), Python as an external process in the Desktop build only (stdout is the whole result, no chat API, its part carries no icon).
Input side: SlashCommandCompletionSource is an IInputCompletionSource (Priority = 100, above generic sources) implementing both TryCompute (token state / argument state) and TryHighlight (token colour by resolution state, argument chips). Analysis is shared in the pure SlashCommandInputAnalyzer; prefix matching in SlashCommandPrefixMatcher; shortest-token logic in SlashCommandShortToken. The inline ghost preview comes from InputCompletionTransformProvider in the generic InputCompletion/ layer.
Settings: ChatCommandSettings.EnableCommands is a chat-local master gate (never inherited); CommandsSet (SlashCommandSet, keyed by SlashCommandInfo.Key) holds per-command overrides and inherits the app bag.
Prompting & SCM
ChatPromptBuilder is gone; the per-agent pipeline is AgentPromptComposer (messages + tools bundle, header mode, toolset-cache invalidation) -> AgentEffectiveMessagesProvider (effective messages plus active ContextCheckpoints) -> PromptAnchoredSectionProcessor (SCM stage) -> message conversion and hooks. Visibility lives in MessageVisibilityService, neutral message quoting in ChatMessageQuoteRenderer.
SCM (Sequential Context Management) keeps the prompt prefix byte-stable and reports state changes as events in the message stream instead of silently rewriting the prompt. Mode is per-agent: ChatAgentDescriptor.Context.PromptMode (AgentContextSettings), default Hybrid.
| Mode | System prompt source | Change tracking |
Dynamic | rebuilt on every request | none |
Static | frozen in agent config until manual refresh | none |
Hybrid | bytes blitted from a PromptStateAnchorMessageData in the history | anchors + deltas + rebaseline |
In Hybrid a live anchor pins rendered bytes and section states to a message; on change, the delta is attached to the agent's own pending assistant message; any ContextCheckpoint cut newer than the anchor forces a rebaseline. History is append-only - dead anchors stay inert and are never deleted - and everything is per-agent (AgentId). MaxVisibleRounds is deliberately ignored in Hybrid, since a round window would evict the anchor.
A section is one tracked unit of prompt state (identity, system slot, system reminder, tools, skills, sub-agents, memory blocks), implemented as a state provider + state renderer + delta provider + delta renderer quartet under Prompting/Context/Providers/<Name>/. Addon-backed sections share AddonSectionDeltaEngine: a new item is announced in full, an unchanged one by name, a changed one field-by-field, and hiding an item is silent.
Checkpoints are ContextCheckpoint carrying the [Flags] kind ContextCheckpointKind (Shield, Summary, ToolCompaction, ForcedToolCompaction, ReasoningCompaction). AgentContextSettings carries PromptMode, MaxVisibleRounds, DisabledFlags, Snapshot and ContextSet.
Dependency Injection
Project uses Microsoft.Extensions.DependencyInjection, paired with own reflection-based registration system. It uses three main scopes:
App- registered withLLMDesktopAssistant.Services.ServiceAttribute(Type? serviceType = null)as a singleton, multiple attributes are allowed to register under multiple service types.Chat- registered withLLMDesktopAssistant.LLM.Services.ChatServiceAttribute(Type? serviceType = null)as a scoped type, where single scope = single chat, multiple attributes are also allowed. All app services are avaliable within chat services.WebUI- registered withLLMDesktopAssistant.Blazor.Services.WebUIServiceAttribute(Type? serviceType = null, IsScoped = true|false)as service within ASP.NET web application (scoping is optional). Multiple attributes are not allowed here. All chat services (including app) available for WebUI services.
Sugar attributes:
LLMDesktopAssistant.Tools.ToolModuleAttribute(bool chatScoped = true)used to define inheritants ofLLMDesktopAssistant.Tools.ToolModuleas services. Sugar for[Chat]Service(typeof(ToolModule))LLMDesktopAssistant.Scripting.Lua.LuaApiAttribute(bool chatScoped = true)used to define inheritants ofLLMDesktopAssistant.Scripting.Lua.LuaApiBaseAsyncas services. Sugar for[Chat]Service(typeof(LuaApiBaseAsync))
Examples of registering service:
using LLMDesktopAssistant.Services;
namespace LLMDesktopAssistant.Agents.Tasks
{
[Service(typeof(IAgentTaskExecutor))]
public class AgentTaskExecutor : IAgentTaskExecutor
{
...
}
}namespace LLMDesktopAssistant.Tools.Implementations.Filesystem
{
[ToolModule] // Chat-scoped by default
public class FilesystemToolModule : ToolModule
{
private readonly IWorkingDirectoryAccessService _fileAccess;
private readonly IDocumentReadingService _documentReader;
public FilesystemToolModule(IWorkingDirectoryAccessService fileAccess, IDocumentReadingService documentReader)
{
...
}
...
}
}Service configurators
You can define a class that inherits LLMDesktopAssistant.Services.ServiceConfigurator. Also put LLMDesktopAssistant.Services.ServiceConfiguratorAttribute(ServiceScope scope = ServiceScope.App) attribute on top of it, for example:
using LLMDesktopAssistant.Tools;
using LLMDesktopAssistant.Utils;
[ServiceConfigurator(ServiceScope.App)]
public class AppToolModulesConfigurator : ServiceConfigurator
{
public override void Configure(IServiceCollection services)
{
var toolModules = ReflectionUtility.GetTypesWithAttribute<ToolModule, ToolModuleAttribute>();
foreach (var toolModule in toolModules)
{
if (!toolModule.Attribute.ChatScoped)
services.AddSingleton(typeof(ToolModule), toolModule.Type);
}
}
}
[ServiceConfigurator(ServiceScope.Chat)]
public class ChatToolModulesConfigurator : ServiceConfigurator
{
public override void Configure(IServiceCollection services)
{
var toolModules = ReflectionUtility.GetTypesWithAttribute<ToolModule, ToolModuleAttribute>();
foreach (var toolModule in toolModules)
{
if (toolModule.Attribute.ChatScoped)
services.AddScoped(typeof(ToolModule), toolModule.Type); // Note that chat services are scoped!
}
}
}Localization
Project uses own localization system based on .loc files:
// Comment; only exact single line comments are supported, lines that not starts with '//' cannot contain comments
// Locale definition, leave empty for 'iv' (invariant locale)
%locale:
// Namespace, used as prefix for all locale keys in locale file
%namespace: model
// For example, this locale key is 'model.capability.chat'
capability.chat: Chat completionsLocalization files are located in src/LLMDesktopAssistant/Localization/Resources/*locale*/*.loc as an embedded resources and auto-imported by LLMDesktopAssistant.Localization.LocFileLocalizationManager (do not worry about that, just put .loc files and it will just work!).
Important note: when searching for existing locale files using fs-grep - DO NOT PUT POSSIBLE NAMESPACE AS SEARCH PATTERN - or tool will not find anything (namespace is located separately). Example: when searching for model.capability.chat, search for capability.chat instead. Also notice: file name != namespace (possibly, e.g. tools.loc has tool namespace).
How to use locale keys
Use LLMDesktopAssistant.Localization.Locale static facade class for getting locale keys and values:
using LLMDesktopAssistant.Localization;
LocaleKeyBase dynamicKey = Locale.GetKey("tool.name.fs-edit");
LocaleKeyBase constKey = Locale.GetConstKey("Edit file"); // Get wrapper around LocaleKeyBase that returns static string without localization
// Three ways to get SAME value
string localized = Locale.Get("tool.name.fs-edit");
localized = dynamicKey.Value;
localized = dynamicKey.RawValue ?? "tool.name.fs-edit";
localized = constKey.Value;Inside AXAML (via LocExtension):
<...
xmlns:loc="using:LLMDesktopAssistant.Localization"
...>
<!-- Use static string key -->
<Button Content="{loc:Loc common.save}"/>
<!-- Use reactive binding to LocaleKeyBase -->
<TextBlock Text="{loc:Loc {Binding TitleKey}}"/>
</...>Settings system
Settings are managed by LLMDesktopAssistant.Settings.SettingsManager using SettingsCategory<TObject> where TObject : SettingsObject. Settings are auto-saved on even deep changes (using LLMDesktopAssistant.Utils.ChangeTracker), and serialized using System.Text.Json with string enum conversion and own abstract type resolution (via LLMDesktopAssistant.Utils.Json.JsonDerivedAttribute(Type baseType, string discriminator) on implementation/derived types). SettingsAttribute(string name) is used to define name for JSON file that will contain configuration.
Accessing app & chat settings
For accessing app settings - use LLMDesktopAssistant.Settings.Application.ApplicationSettingsAccessor, it has ApplicationSettings property that returns LLMDesktopAssistant.Settings.Application.ApplicationSettings. Always use accessor - its good for testing purposes.
For chat settings - inject LLMDesktopAssistant.LLM.Services.IChatSettingsService, it has LLMDesktopAssistant.LLM.Settings.ChatSettings Settings property.
Chat & Agent settings inheritance
ChatSettings and AgentDescriptor can have inherited settings inside each category. Chat settings can be inherited from App settings, and Agent settings can inherit from both. Source generator generates for each "inherited" setting that have LLMDesktopAssistant.SourceGenerators.InheritedChatSettingAttribute (for Chat) and LLMDesktopAssistant.SourceGenerators.InheritedChatAgentSettingAttribute (for Agent). Each category must have SettingsRouteAttribute(string route) for enable generation. Examples:
[SettingsRoute(nameof(ChatAgentDescriptor.Read))]
public partial class AgentReadSettings : AgentSettingsCategoryBase
{
private AgentReadPermissions _readPermissions = ...;
/// <summary>
/// The permissions that determine what the agent can read.
/// </summary>
[InheritedChatAgentSetting]
public AgentReadPermissions ReadPermissions
{
get => _readPermissions;
set => SetProperty(ref _readPermissions, value);
}
...
}Generator will generate for each inherited property:
partial class AgentReadSettings
{
public global::LLMDesktopAssistant.LLM.Settings.ChatSettingsInheritanceLevel ReadPermissionsInheritance { get; set; }
public global::LLMDesktopAssistant.Agents.AgentReadPermissions GetEffectiveReadPermissions(global::LLMDesktopAssistant.LLM.Settings.ChatSettings chatSettings)
{
...
}
public void SetEffectiveReadPermissions(global::LLMDesktopAssistant.LLM.Settings.ChatSettings chatSettings, global::LLMDesktopAssistant.Agents.AgentReadPermissions value)
{
...
}
}Chat settings have same generated methods signature, except for removed chatSettings parameter.
Using inherited settings
ChatAgentDescriptor agent = ...;
ChatSettings chatSettings = ...;
var readPerms = agent.Read.GetEffectiveReadPermissions(chatSettings);
var skillSources = chatSettings.Skills.GetEffectiveSources();
chatSettings.Skills.PropertyChanged += (s, e)
{
if (e.PropertyName == nameof(ChatSkillSettings.SourcesInheritance))
{
// Catch inheritance level changes
}
};Note: ChatSettingsInheritanceLevel enum have { Application, Profile, Agent } values.
MVVM
For most ViewModels this project uses LLMDesktopAssistant.MVVM.ViewModelBase and LLMDesktopAssistant.NotifyPropertyChanged (parent for ViewModelBase) classes. Main usage pattern: ViewModelBase used for ViewModels, and NotifyPropertyChanged used for parts/items inside ViewModels. Project uses reflection-base view locator, VM's can be bound to views used LLMDesktopAssistant.MVVM.ViewModelForAttribute(Type targetView):
using LLMDesktopAssistant.MVVM;
[ViewModelFor(typeof(SomeView))]
public class SomeViewModel : ViewModelBase
{
private bool _enabled;
public bool Enabled
{
get => _enabled;
set
{
if (SetProperty(ref _enabled, value))
RaisePropertyChanged(nameof(EnabledChanged)); // Optional: raise changed event on depended property
}
}
private RangeObjservableCollection<int> _ids = [];
public RangeObjservableCollection<int> Ids
{
get => _ids;
set => _ids.Reset(value); // Reset the collection with new items instead of setting the entire property
}
}Dialogs
You can use LLMDesktopAssistant.Controls.Dialogs.DialogManager for showing dialogs:
using LLMDesktopAssistant.Controls.Dialogs;
using LLMDesktopAssistant.Localization;
var dialog = new ConfirmDialogViewModel
{
Title = LocalizationManager.LocalizeStatic("settings.memory.delete.title"),
Description = LocalizationManager.LocalizeStatic("settings.memory.delete.confirm"),
ConfirmText = LocalizationManager.LocalizeStatic("settings.memory.delete.action"),
CancelText = LocalizationManager.LocalizeStatic("common.cancel"),
IsDanger = true
};
var result = await DialogManager.ShowDialogAsync(dialog);
var confirmed = (bool)result!;
// Or inside the dialog's VM
DialogManager.CloseDialog(true);Useful utilities
These are located in LLMDesktopAssistant.Utils:
RangeObservableCollection- thread-safe alternative toObservableCollectionthat can be used for settings and MVVM, is NEVER raisesCollectionChangedevent withoutOldItemsandNewitems, which is convenient whenResetevent is unwanted to deal with. Important notice: useReset(IEnumerable<T>)method for setter inside reactive objects to reset the collection instead of resetting entire property.ReadOnlyObservableCollection- wrapper around any ofIReadOnlyList<T>,INotifyCollectionChangedorINotifyPropertyChanged(every implementaion is optional, but at least on must be implemented).AsyncCache- thread-safe async dictionary with cleanup intervals and sliding expiration time.ChangeTracker- deep tracker for one specific reactive object, useChangeTracker.Untrackedattribute on properties to prevent deep observation for complex objects (Taskfor example).LLMDesktopAssistant.Disposable- base class for all disposable objects, you can create direct instance of it with providedAction(e.g.new Disposable(() => ...)).
Important notes
The info given in this context document is ACTUAL and you are not needed to observe files by self (unless REALLY needed), even if you miss the right signatures, the dotnet build will show all the errors - it's better "invent" signatures and fix errors later, because you will spend less tokens!
So, the GOOD example:
Okay, I will just use `GetEffectiveReadPermissions(_chatSettings.ChatSettings)`, i don't need to observe the real generator and spend input tokens + one request.The BAD example:
Let's view the `src/LLMDesktopAssistant.SourceGenerators/InheritedSettingsGenerator.cs`, who knows if user is right?
*fs-read_entry call*
The user was right, it works as he said...Also, observe the MINIMAL number of files that really needed for understanding behaviour and signatures.
Building project
DO NOT build the entire solution! Build the each project separately instead (but desktop project should be built inside the temporary directory):
# The artifacts path is built into a variable first: $env:TEMP does NOT expand inside single quotes.
dotnet build 'src/LLMDesktopAssistant/LLMDesktopAssistant.csproj'
$a = Join-Path $env:TEMP 'dass-temp-build'
dotnet build 'src/LLMDesktopAssistant.Desktop/LLMDesktopAssistant.Desktop.csproj' -p:UseArtifactsOutput=true -p:ArtifactsPath="$a"NEVER pipe the build output through Select-Object (| Select-Object -Last 40, | Select, | select, etc.)!
Do not filter, truncate or post-process the build output in any way - run the command bare and let it stream.
Reasons:
- it breaks the output encoding (the Russian text in the build log turns into mojibake);
- it kills the streaming/animation of the shell tool, which makes
runTerminal: trueessentially useless (no live progress, output appears only when the command finishes). If the output is too long - just read the part you need from the streamed result, never by piping it.
Project memory
Use specified memory block to store completed work inside the epizodic logs after the completed session. You also can get last 5-10 logs to view the actual state of the project.
Agent skills
Issue tracker
Local markdown tracker under docs/issues/ (not GitHub Issues). See docs/issues/README.md.
Triage labels
Default canonical roles, used verbatim as the Status: string (needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix). See docs/issues/README.md.
Domain docs
Single-context: GLOSSARY.md at the repo root, ADRs in docs/adr/. See docs/issues/README.md.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

