using System.Text.Json.Serialization; namespace ClawdDotNet.Core.Config; /// /// #683: A unit — department, team, group — as a node in the organisation tree. /// /// Stored in the instance configuration, next to the agents. An agent's tools /// already live there; a unit's rules belong beside them, so the truth about an agent is in /// one place. Every structural change is written to the audit log by the administration, /// which is what the concept asked the database for. /// /// No value here overrides an agent at run time (Organisation-Konzept, section /// 3). are only the starting values for agents that join; /// is checked when an agent's tools are written, never laid over /// them while it runs. What stands at the agent is what applies. /// public sealed class UnitConfig { [JsonPropertyName("id")] public string Id { get; set; } = ""; [JsonPropertyName("name")] public string Name { get; set; } = ""; /// The parent unit; null for a top-level unit. [JsonPropertyName("parentId")] public string? ParentId { get; set; } /// /// The agent leading this unit — one of its members. May be empty: then the nearest /// unit above that has a lead takes over. /// [JsonPropertyName("leaderAgentId")] public string? LeaderAgentId { get; set; } /// What the unit exists for — the top of every task chain in it (#714). [JsonPropertyName("goal")] public string Goal { get; set; } = ""; /// /// The tools members of this unit may have at most; null means no ceiling. A /// ceiling applies to sub-units as well — the effective ceiling is the intersection /// along the path to the root. /// [JsonPropertyName("toolCeiling")] public List? ToolCeiling { get; set; } /// Starting values for agents that join this unit. [JsonPropertyName("defaults")] public UnitDefaults Defaults { get; set; } = new(); /// /// #687: What the lead of this unit may approve for the agents below it; null means /// nothing — every approval goes to a human. Set only by a human. /// [JsonPropertyName("approvals")] public UnitApprovals? Approvals { get; set; } /// /// #693: How often the lead of this unit looks at the runs of the agents it leads; /// null means no spot checks. Set only by a human — checks cost the lead's runs. /// [JsonPropertyName("inspection")] public UnitInspection? Inspection { get; set; } /// /// #702: Operations that are put to a blind vote instead of the lead; null means none. /// Set only by a human — a vote is a deliberate loosening, not a default. /// [JsonPropertyName("votes")] public UnitVotes? Votes { get; set; } /// #691: The template the unit came from — its texts and rules are someone else's. [JsonPropertyName("templateOrigin")] public string? TemplateOrigin { get; set; } /// /// #692: Credentials the unit holds for its members, by tool and setting — a purchasing /// department with six agents needs the marketplace key once, not six times, and the /// accounts department never gets it. Every value is a secret, stored protected; a member's /// own non-empty value wins, and a credential grants no tool. /// [JsonPropertyName("credentials")] public Dictionary>? Credentials { get; set; } } /// /// #702: A vote — several agents check independently and vote blind before something happens. /// Not opinions are counted but independent checks enforced: every voter looks at its own data. /// public sealed class UnitVotes { /// The operations put to the vote: Tool.operation, or Tool for all of it. [JsonPropertyName("operations")] public List Operations { get; set; } = []; /// The agents that vote. Whoever proposes the call does not vote on it. [JsonPropertyName("voters")] public List Voters { get; set; } = []; /// Yes votes from which the call is approved. [JsonPropertyName("approveAt")] public int ApproveAt { get; set; } = 3; /// At this many yes votes or fewer, the call is rejected. Between the two, a human decides. [JsonPropertyName("rejectAt")] public int RejectAt { get; set; } = 1; /// Votes that must be cast for any result; 0 means . A missing vote is no yes. [JsonPropertyName("quorum")] public int Quorum { get; set; } /// How long the voters have; after that, what was cast is counted. [JsonPropertyName("hours")] public double Hours { get; set; } = 4; /// Votes a day for the unit — every vote costs a run per voter; beyond, the lead or a human decides. [JsonPropertyName("perDay")] public int PerDay { get; set; } = 10; [JsonIgnore] public TimeSpan Window => TimeSpan.FromHours(Math.Clamp(Hours, 0.25, 72)); [JsonIgnore] public int EffectiveQuorum => Quorum > 0 ? Quorum : ApproveAt; /// Whether the vote covers the operation. public bool Covers(string tool, string? operation) => Operations.Any(o => string.Equals(o, tool, StringComparison.OrdinalIgnoreCase) || (!string.IsNullOrWhiteSpace(operation) && string.Equals(o, $"{tool}.{operation}", StringComparison.OrdinalIgnoreCase))); } /// /// #693: The spot checks of a unit's lead. Which run is checked is decided by the run id, not /// by chance — the same run gets the same answer on every pass, and nobody can tell from inside /// a run whether it will be looked at. /// public sealed class UnitInspection { /// Share of ordinary runs that are checked, 0 to 1. [JsonPropertyName("rate")] public double Rate { get; set; } = 0.1; /// Share for a new agent, a new model, or the run after a failure. [JsonPropertyName("denseRate")] public double DenseRate { get; set; } = 0.5; /// How long an agent counts as new after its first run. [JsonPropertyName("newDays")] public int NewDays { get; set; } = 7; /// Checks a day for the whole unit — a lead that checks everything doubles the unit's costs. [JsonPropertyName("perDay")] public int PerDay { get; set; } = 5; } /// /// #687: The limits within which a unit's lead approves instead of the human. They belong to /// the unit, not to the lead as a person — otherwise the power moves with an agent that changes /// its post, and a vacant lead would have none. /// public sealed class UnitApprovals { /// /// The operations the lead may approve: Tool.operation, or Tool for all of its /// operations. A sub-unit can grant no more than every unit above it that grants anything. /// [JsonPropertyName("operations")] public List Operations { get; set; } = []; /// Approvals the lead may give in 24 hours; beyond that the human decides. [JsonPropertyName("perDay")] public int PerDay { get; set; } = 20; /// How long the lead has; after that the call is the human's alone. [JsonPropertyName("hours")] public double Hours { get; set; } = 4; [JsonIgnore] public TimeSpan Window => TimeSpan.FromHours(Math.Clamp(Hours, 0.25, 72)); /// Whether this grant covers the operation. public bool Covers(string tool, string? operation) => Operations.Any(o => string.Equals(o, tool, StringComparison.OrdinalIgnoreCase) || (!string.IsNullOrWhiteSpace(operation) && string.Equals(o, $"{tool}.{operation}", StringComparison.OrdinalIgnoreCase))); } /// /// #683: What an agent gets when it joins a unit. null leaves the agent's own value. /// Changing these does not touch existing members — writing a value into them is a /// separate, previewed step. /// public sealed class UnitDefaults { [JsonPropertyName("model")] public string? Model { get; set; } [JsonPropertyName("dailyCostUsd")] public decimal? DailyCostUsd { get; set; } [JsonPropertyName("dailyTokens")] public long? DailyTokens { get; set; } [JsonPropertyName("whenExhausted")] public string? WhenExhausted { get; set; } }