Skip to content

feat(groups): org/team preset templates (I10) — on the all-features integration branch - #645

Merged
ginccc merged 54 commits into
mainfrom
feat/group-i10-templates
Aug 8, 2026
Merged

feat(groups): org/team preset templates (I10) — on the all-features integration branch#645
ginccc merged 54 commits into
mainfrom
feat/group-i10-templates

Conversation

@ginccc

@ginccc ginccc commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

The final queue item from planning/group-collaboration-improvements-plan.md, shipped last by design so templates only reference features that exist — and, by necessity, the branch that proves everything works together: it merges all seven feature branches (#637 I17, #638 I14, #639 I8, #640 I6, #641 I11, #642 I18, #643 I12, #644 I13) plus the pre-feature defects branch (#636, N1/N2/N3/I9). 3720 targeted tests, zero failures, after real semantic merge resolutions (below).

Stacked PR: the diff shrinks to just I10 as the other PRs merge. Suggested merge order: #636 → the Wave-2 PRs (#637, #638, #639, #640) → Wave 3 (#641, #642, #643, #644) → this.

I10 — Preset templates

Enterprises understand "research pod" and "decision board", not contextScope: OWN_FEEDBACK. Five packaged templates in src/main/resources/group-templates/ (classpath-loaded via the initial-agents/ index pattern) ship complete, validated configs whose member slots are named roles; instantiation assigns agents (or human principals) to them and saves through RestAgentGroupStore.createGroup — the normal path, so every save-time validation applies. Placeholder substitution ($role → agentId) is the ONLY templating.

Template Packages
research-pod DELPHI-style blind estimates → anonymous convergence rounds (I2) → retro into team memory (I8), windowed context (I9), dollar ceiling (I1)
editorial-team Writer drafts a shared artifact (I17), editors propose CAS-guarded updates, dissents recorded (I4)
ops-task-force TASK_FORCE with bid assignment (I18), agent-filed tasks (I5), recruitment (I7)
decision-board A HUMAN director (I6) deliberates and votes (I14); chair distills options and breaks ties
negotiation-table Typed two-party bargaining with concession ledger (I11); human-arbiter option

Surfaces: REST GET/POST /groupstore/templates[/{id}[/instantiate]], MCP list_group_templates + create_group_from_template. Missing/unknown roles fail loudly, naming the template's real roles.

Deviation, upheld and documented: HUMAN_DECIDES vote tie-breaking stays save-time rejected. Wiring it means a pending-ballot pause whose human answer must parse back into the tally — real machinery, not the "small follow-up" the I14 note hoped. decision-board uses a HUMAN voting member + MODERATOR_DECIDES ties instead of shipping a silently-degrading enum value.

Integration-merge resolutions (the load-bearing ones)

  • DiscussionPhase unified to 13 components (voteConfig 12th, skipIf 13th) with both 12-arg compat constructors kept, so every branch's call sites compile unchanged.
  • PhaseType unions to 15 (VOTE + PROPOSAL + BARGAIN + RETRO); every enum pin updated.
  • Schema v4 carries both resume-critical additions (I12 runtimePhases, I11 negotiationState) under one release-shape version.
  • The SYNTHESIS decision block runs debate verdicts AND I11 arbitration with a single late decision_reached fired after the dissent round; an arbitrated flag is true only when this call set the decision, so the event never re-announces an earlier phase's.
  • AgentGroupStore.create/update runs all five validators (HITL, vote, human, facilitator, artifact).

Cross-branch defect only this tree could show (fixed here): I9's windowing overload never reached I6's human-turn prompt render — agents got windowed context while a human's rendered prompt used the unwindowed compat path, breaking I6's "the human sees exactly what an agent speaker would" contract. Both human-prompt sites now pass the context window; the I6 blindness tests verify the windowing overload. The same resolution applies when #636 and #640 meet in main.

Tests

+13 for I10: every template instantiates with dummy assignments and passes the entire save-time validator matrix — the plan's designated integration test of all Wave 1–3 config surfaces; placeholder-free rosters; the human director survives as a HUMAN member; the NEGOTIATION preset expands; missing/unknown-role errors name what's wrong; REST instantiation captured through the store path with 400s saving nothing; MCP filter pins green. Full engine.internal + configs.groups + engine.hitl + engine.mcp + engine.runtime.internal suites: 3720 green.

ginccc added 30 commits August 7, 2026 17:44
…utputPricePer1M (N1)

AUDIT_COST summed cascadeCostUsd + toolCostUsd only, so a plain model call
contributed $0 — I1's group cost ceiling could never trip for ordinary
members and REST served $0.00 as authoritative. Task-level prices (null =
unpriced, $0 as before) now price non-cascade calls; the arithmetic lives
once in TokenPricing, shared with the cascade path. Cascade turns keep
pricing themselves — key-presence discrimination prevents double counting.
Negative prices fail at deployment.
…erwriting

accumulateNestedGroupCost keyed memberCosts by the GROUP member's agentId,
but each turn spawns a fresh child discussion starting at totalCost 0 and
the map records by replacement — so only the last child's spend survived.
Key by agentId:childConversationId: replacement stays idempotent per
conversation and multiple children of one member sum.
…t at creation (N3)

A key-less stored document (every pre-F6 document in production) deserialized
claiming CURRENT_SCHEMA_VERSION, so prepareForResume's migration ladder ran
zero iterations on exactly the documents it exists for. The initialiser is now
LEGACY_SCHEMA_VERSION (1) and the single creation point stamps CURRENT —
Jackson runs the no-arg constructor either way, so only the creation stamp can
distinguish absent from current. Same split applied to ConversationMemorySnapshot,
which was correct only by coincidence (CURRENT == floor == 1). Stale Javadoc in
both migration registries corrected.
FULL/ANONYMOUS-scope phases re-fed the whole transcript to every member
every turn (~quadratic prompt cost). contextWindow config bounds the
rendered context: beyond maxRecentEntries the older entries collapse into
a rolling summary — extended incrementally at phase boundaries via the
shared SummarizationService — or a plain truncation marker when
summarization is off, unconfigured, or failing (never blocks). ANONYMOUS
keeps its own summary built from Anonymous-labelled input so it can never
de-anonymize. Summarizer spend is attributed to the discussion's I1 cost
ledger when priced. The stored transcript is never modified.
…nd validators (I17)

Members co-edit typed documents (TEXT/MARKDOWN/JSON) through four tools
gated by a new artifactConfig, instead of re-parsing each other's prose.
Own collection (never embedded - the loop's stale-snapshot persists would
clobber it); deterministic version CAS with a re-read-and-merge retry
sentence, via a new numeric storeIfFieldEquals overload (Mongo's typed
BSON equality never matches a number against a string); declarative
validators (JSON Schema/regex/length) hard-checked at config save; writes
announced as artifact_updated via a change queue the turn executor drains
(tools have no listener reference); close/delete cascade + user-keyed
GDPR erasure via a stamped ownerUserId.
…boundary (PR #636 findings)

- accumulateCost/recordSystemCost: !(x > 0) + isFinite instead of x <= 0 -
  NaN fails every comparison, slipped through, and silently disabled every
  dollar ceiling (NaN comparisons are all false)
- the I9 boundary summarizer is now ceiling-gated via wouldExceedCeiling,
  like the convergence judge and dissent round (mutation-checked)
- summaryBoundary counts VISIBLE entries back from the tail, not raw ones,
  so bookkeeping rows cannot eat the verbatim window
- blank llmProvider/llmModel normalize to null in the config choke point
- windowed filterByScope copies the live transcript under its monitor
- CodeQL: sanitize gc id + group name in the new WARN sites
- memberCosts Javadoc rewritten to the actual one-key-one-conversation
  invariant (judge/dissent keys predate the nested-child keys)
VOTE phases collect explicit ballots; the deliverable is the auditable
artifact - weighted tally, raw ballots, losing-side dissents - because LLM
ballots are correlated. Independence is enforced at save time (PARALLEL +
NONE scope, hard-rejected otherwise), ballots stay peer-hidden via F4's
commit-reveal, and parsing is three-tier with out-of-contract votes as
non-ballots that count against quorum (as do abstentions). Ties go to the
tiePolicy: one moderator tiebreak turn under a separate conversation key,
or an honest NONE. HUMAN_DECIDES is save-time rejected until I6 ships.
Also folds in the paragraph-4 gap: decision_reached finally has producers
(votes and debate verdicts), with a bounded Slack tally block.
A RETRO phase reviews the discussion and distills lessons that persist
as group-visible entries under the synthetic owner "group:<groupId>" -
institutional knowledge that surfaces as {properties.*} in every
member's later discussions and survives any single human's erasure.

- PhaseType.RETRO + RetroConfig(maxLessonsPerRun, maxStoredLessons)
  with a built-in template; the engine enforces the configured per-run
  cap at parse time regardless of what the model was told.
- RetroEngine: three-tier lesson parse, idempotency key
  retro:<sha256(lesson)> with a fixed sourceAgentId so the same lesson
  from any speaker dedupes, FIFO eviction past maxStoredLessons.
- Additive team-owner recall branch on BOTH backends (Mongo filter +
  Postgres SQL), owners derived from the supplied group ids only; the
  user's own scope is untouched. Both shapes pinned by tests.
- retro_recorded event (SSE + listener), null-safe wiring in the
  discussion loop before the persist.
AgentGroupConfigurationTest.phaseType_allValues pins the enum size;
I14 added VOTE as the 12th value.
AgentGroupConfigurationTest.phaseType_allValues pins the enum size;
I8 added RETRO as the 12th value on this branch.
…ontracts

- Save-time JSON schema specs now also validate against the bundled
  2020-12 meta-schema; parse-only admitted invalid keyword values.
- REGEX validators match through a 500ms deadline-guarded CharSequence
  so a catastrophically backtracking config pattern refuses the write
  instead of pinning the member turn.
- ArtifactConfig tolerates a [null] validator entry so
  requireValidSpecs can report its position instead of an NPE.
- Artifact events: drain+announce serialized on a per-conversation
  mutex (write order), plus a final announce pass per discussion leg
  for writes accepted after a timed-out turn drained.
- listByGroupConversationId re-sorts oldest-first per its contract;
  deleteByGroupConversationId gets the processed-set/no-progress guard.
- Slack mrkdwn-escapes artifact name/editor id; oversize refusal
  rounds up; GDPR cascade Javadoc names artifacts; log sanitized.
…sent carry

- recordVoteDecision now takes (turnCounter, maxTurns): the moderator
  tiebreak is a real LLM turn, gated on both the turn budget and the
  cost ceiling like every other extra call, and counted when it runs.
- TallyOutcome carries the parsed ballots so a tie-policy resolution
  computes losing-side dissents against ITS chosen option; reusing the
  unresolved record dropped the minority report for the closest votes.
- Weighted totals tie by epsilon (1e-9), not ==; ballot weights must
  be finite at save time (NaN passes every < comparison).
- Slack tally lines width-bounded via buildPreview so a synthesis-
  derived option paragraph cannot push the message past Slack limits.
- CodeQL: 6 log-injection sinks sanitized; useless null check removed.
- PostgresUserMemoryStoreUnitTest pinned the pre-I8 bind order; the
  team-owner recall branch adds derived owner ids and a second group
  overlap, so the pin now asserts all nine parameters.
- CodeQL: sanitize the four RetroEngine log sinks (conversation id,
  phase name, team owner, exception message).
- Eviction can never delete a team-owned retro lesson: even with the
  store wall deliberately breached, the sourceAgentId filter holds.
- Personal visibility:self entries never cross users through a shared
  group; the tool only ever queries its own user id.
A MemberType.HUMAN member sits in the roster like any agent, but their
turn pauses the discussion in a new AWAITING_HUMAN_INPUT state until
they submit - deliberately not AWAITING_APPROVAL: approval endpoints
never accept free text, and an inbox must tell "approve/reject" from
"you are up".

- Phase loops intercept HUMAN speakers, render their input exactly like
  an agent turn, and commit a pause carrying PendingHumanInput + the F2
  ResumePoint (its first producer). The turn is counted at the pause.
- Submission via POST .../human-input + MCP submit_group_human_input;
  only the pending member''s own principal (or admin) may submit - an
  approver may decide approvals, but speaking as another human is
  impersonation. Entry lands as the phase''s natural type; bookmark
  advances; CAS makes double-submits a 409; drift-checks refuse before
  any mutation.
- humanMemberConfig {turnTimeout, onTimeout=SKIP_TURN|ABORT} rides the
  HITL schedule machinery on a new group-human surface; crash recovery
  re-arms human-turn timeouts; SKIP_TURN writes a named SKIPPED entry.
- PARALLEL: agents fan out first, humans prompted sequentially against
  the pre-fan-out snapshot; a HUMAN_TURN_PARALLEL resume skips the
  fan-out instead of duplicating agent turns.
- Save-time matrix: displayName required; no humans in task-force or
  targetEachPeer groups; nested groups with humans rejected (+ runtime
  backstop); human moderator allowed + warned, and resolveParticipants
  no longer demotes them to an agent.
- human_input_requested event (SSE/Slack), HUMAN_TURN inbox entries
  with pendingMemberId, availableActions submitHumanInput.
EDDI had win/lose decision forms and no trade form. A negotiation
drafts a compromise with an explicit concession ledger for human
sign-off; the typed structure is the anti-sycophancy mechanism.

- Phase types PROPOSAL + BARGAIN; skipIf=AGREEMENT_REACHED, a single
  enum condition (deliberately not an expression language) that skips
  the arbitration once a typed AGREEMENT exists.
- NegotiationState on GroupConversation: proposals (OPEN/SUPERSEDED,
  acceptedBy, acceptance entry indices) + the concession ledger.
- BARGAIN turns are a typed JSON move with three-tier parsing; an
  unreadable turn is prose with no state effect. A concession that
  names nothing in return is not recorded. A new proposal supersedes
  the mover''s own open one; proposers sign their own terms.
- The open proposals + ledger are appended to every negotiation turn -
  the record the outcome will quote.
- Unanimous acceptance ends the bargaining repeats early (the I2
  outcome plumbing) and records DecisionRecord{AGREEMENT,
  method=negotiation} whose tally.signedAcceptances maps signatories
  to their signed acceptance entries - the co-signatures, no new
  crypto. Failed bargaining runs the arbitration, whose conclusion
  becomes DecisionRecord{VERDICT, method=arbitration}.
- Preset NEGOTIATION: Positions & Interests (parallel, blind) ->
  Opening Proposals -> Bargaining (repeats=maxRounds) -> Arbitration
  (conditional, own template) -> Synthesis.
- McpToolFilter whitelists submit_group_human_input (CI guard test).
- New requireGroupConversationReadAccess: the pending human member may
  read the status carrying their rendered prompt (REST + MCP, summary
  now includes pendingMemberId/prompt on both surfaces); the full
  transcript view stays role-gated.
- Mid-phase resume starts at the bookmarked repeat instead of
  replaying earlier repeats (duplicate turns and spend).
- Human-turn resolution: metric/audit/resume-event deferred until the
  executor submit succeeds; rollback re-checks the control token; the
  caller gets a freshly-read copy, not the live instance the
  background leg mutates.
- Slack listener releases its completion latch on a human pause;
  deleting an AWAITING_HUMAN_INPUT conversation runs the paused
  cleanup; the signing cursor survives a human pause; crash-recovery
  sweeps isolated; inbox merges both pause states oldest-first.
- turnTimeout must be positive; null members list cannot NPE; F2
  drift guard scoped to approval bookmarks; CodeQL logs sanitized;
  HumanTurnRequired @PARAM docs moved to the constructor.
The planner cannot know members'' actual fit or load; the Contract
Net Protocol''s announce-bid-award loop maps onto the existing wave
scheduler.

- assignmentMode = ROLE (default) | BID on TaskDefinition (per task)
  and GroupTaskConfig (group default), compat ctors; pre-I18 configs
  resolve to ROLE.
- PLAN leaves BID-mode tasks unassigned; each execution wave announces
  them to eligible members in blind, parallel bid turns - the prompt
  carries the batch and nothing else (no transcript, no peer bids),
  and replies land as peer-hidden BID transcript entries.
- Deterministic award: highest confidence, ties by speaking order then
  agent id; a task nobody bid on falls back to ROLE; the auction skips
  itself (logged) when it cannot beat its own overhead (<2 bidders,
  <2 tasks, or no turn budget). Bid turns count toward the budget.
- The winning bid is per-task metadata (awardedBids on the task list),
  deliberately not a global DecisionRecord.
…tator

# Conflicts:
#	docs/changelog.md
#	docs/group-conversations.md
#	src/main/java/ai/labs/eddi/configs/groups/mongo/AgentGroupStore.java
#	src/main/java/ai/labs/eddi/integrations/slack/SlackGroupDiscussionListener.java
#	src/test/java/ai/labs/eddi/configs/groups/mongo/AgentGroupStoreTest.java
A facilitator agent is briefed at configured checkpoints (EACH_PHASE or
EACH_REPEAT) with a compact state summary - never the full transcript -
and selects one config-enumerated move: CONTINUE, END_PHASE,
EXTEND_PHASE (<=2/phase), CALL_VOTE (one-off I14 vote phase, PARALLEL+
NONE by construction), RECRUIT (I7 validation path), ESCALATE_HUMAN
(I6 pending-input pause on the configured principal). Every selection
is validated, capped (maxMovesPerDiscussion), and recorded: executed
moves as peer-hidden FACILITATION entries + audit events + metrics,
rejected attempts as FACILITATION entries that never consume budget.
Unparseable, disallowed, invalid-in-context or failed -> CONTINUE.

CALL_VOTE/EXTEND_PHASE diverge a runtime copy of the phase list,
persisted on the conversation (schema v4); every resume surface now
resolves effectivePhases() so bookmarks and drift checks compare
against the list the pause was taken from. Divergence is one-off:
completion and continuation rounds clear it.
CodeQL flagged 6 log-injection sites in the I18 bid round; every
caller-influenced value (groupId, task subject, agentIds, exception
messages) now passes through LogSanitizer - 7 sites, the 6 flagged
plus the bid-turn-failure log.
- Putting new terms on the table now withdraws the mover's signatures
  from every other open proposal, and signing someone else's terms
  supersedes the signer's own open offer - a proposal can no longer
  reach unanimity on a signature its signatory moved away from. A turn
  carrying both accept and proposal resolves for the proposal (WARN).
- CURRENT_SCHEMA_VERSION 3->4: negotiationState is resume-critical; an
  older pod re-saving a paused v4 doc would drop the table (identity
  hop, no migration entry needed).
- NegotiationState getters return unmodifiable views; mutation goes
  through addProposal/replaceProposal/addConcession.
- Unused phase param dropped from applyRepeat; moderator filter
  null-safe; 4 CodeQL log-injection sites sanitized.
A group conversation is an episode; the GroupWorkspace persists: a
backlog (SharedTaskList reused whole), cron cadences that pull
executable tasks into task-force discussions, and running team
metrics with per-member reliability recording (no routing on it, v1).

Cadence fires ride the schedule machinery (poller claim/lease/retry;
a dedicated fireTeamCadence branch, the DreamService pattern). The
fire protocol is crash-proof: reconcile the previous run first, pull
top-N executable tasks by priority, claim via conditional store write
(cluster-safe), then run - pulled tasks injected as a runtime copy of
config.tasks via startCadenceDiscussionAsync, with the cadence's
dollar ceiling riding the inherited-ceiling slot and the run
attributed to the cadence creator. Writeback settles at the next fire
or on workspace read: VERIFIED stays and credits the assignee's
stats; anything else returns to PENDING with reviewer feedback
appended - the cross-run retry loop.

REST /groupstore/groups/{id}/workspace (backlog cap 200 actionable,
cron validated at creation) + MCP add_team_task/list_team_backlog.
Permanent group deletion cascades to the workspace.
…sions

- Briefing task summary reads one getTasks() snapshot instead of an
  emptiness check plus a second read (CodeQL TOCTOU, high).
- facilitatorExtensions getter returns an unmodifiable view; mutation
  goes through recordFacilitatorExtension/clearFacilitatorExtensions.
- Three caller-influenced log values sanitized.
…backs

One slow or backpressured SSE client inside onArtifactUpdated blocked
every other turn's end-of-turn drain on artifactAnnounceMutex. Exactly
one thread at a time is now the publisher: it drains under the mutex,
releases it, fires the callbacks, and loops for late arrivals; other
threads hand off and leave. Write order is preserved (single announcer
over a FIFO queue) and no caller ever blocks on a listener.
The changelog's test tally is 13 (8 TaskBidEngineTest + 5
TaskForceEngineTest), and the bid-assignment subsection now follows the
agent-task caps paragraph so 'Both caps' keeps its referent.
# Conflicts:
#	docs/changelog.md
#	docs/group-conversations.md
#	src/main/java/ai/labs/eddi/configs/groups/model/AgentGroupConfiguration.java
#	src/main/java/ai/labs/eddi/configs/groups/model/DiscussionStylePresets.java
#	src/main/java/ai/labs/eddi/configs/groups/model/GroupConversation.java
#	src/main/java/ai/labs/eddi/engine/internal/GroupConversationService.java
#	src/main/java/ai/labs/eddi/engine/internal/groups/GroupContextBuilder.java
#	src/test/java/ai/labs/eddi/configs/groups/model/AgentGroupConfigurationTest.java
# Conflicts:
#	docs/changelog.md
#	docs/group-conversations.md
…lates

# Conflicts:
#	docs/changelog.md
#	docs/group-conversations.md
#	src/main/java/ai/labs/eddi/configs/groups/model/AgentGroupConfiguration.java
#	src/main/java/ai/labs/eddi/configs/groups/model/DiscussionStylePresets.java
#	src/main/java/ai/labs/eddi/engine/api/IGroupConversationService.java
#	src/main/java/ai/labs/eddi/engine/internal/groups/GroupContextBuilder.java
#	src/main/java/ai/labs/eddi/engine/lifecycle/GroupConversationEventSink.java
#	src/test/java/ai/labs/eddi/configs/groups/model/AgentGroupConfigurationTest.java
Comment thread src/main/java/ai/labs/eddi/configs/groups/model/GroupWorkspace.java Fixed
ginccc added 8 commits August 8, 2026 06:54
…pause

A human turn pauses mid-repeat, after other speakers appended this
repeat's entries; the resumed leg recomputed the slice base from the
current transcript size, so the repeat slice covered only post-pause
entries - the convergence check (and, once merged with I14, the VOTE
tally) silently lost every pre-pause contribution. New persisted
pausedRepeatSliceBase, written at the pause commit and consumed
exactly once with the speaker-bookmark read-and-clear discipline.
Two findings, one root cause: the EACH_REPEAT checkpoint ran after the
last-repeat decision block. END_PHASE broke past it (a VOTE phase it
ended never tallied its cast ballots; verdicts/dissents/retro skipped
alike), and EXTEND_PHASE at a final repeat re-ran an already-fired
block (duplicate dissent rounds, decision_reached twice). The consult
now precedes the block: END_PHASE folds into the phase outcome,
EXTEND_PHASE defers the block to the true final repeat, and
INSERT_VOTE/ESCALATE apply after it.
continueDiscussion cleared every other round-scoped conclusion but
not the persisted negotiation state - round 2 ran against round 1's
proposals, and one fresh acceptance could reach unanimous agreement
on signatures cast for a different question.
…ed cost ceiling, backlog caps, bounded feedback, resume-rollback cancel race

Four confirmed findings from the 23-agent cross-branch final review, all on
this branch's surface, plus one HITL minor:

- settle() released the cadence claim unconditionally, clobbering a claim a
  concurrent reconciler had already taken; now a conditional release via
  casRunningDiscussion(settledDiscussionId) — a lost race drops the caller's
  stale mutations instead of orphaning the newer discussion's outcomes
- inheritedCostCeiling was transient, so a cadence run pausing for approval
  resumed with no cost ceiling; now persisted
- REST addBacklogTask + MCP add_team_task bypassed the agent-surface caps:
  subject 200 / description 4000 enforced, duplicate subjects rejected
  (writeback matches outcomes by subject), cadences capped at 20/workspace,
  inputTemplate bounded at 4000
- COMPLETED writeback appended each failed run's whole agent output to the
  persisted description; appendFeedbackBounded caps 500/run and trims the
  total from the front to the shared 4000 cap
- claim-CAS store failure now cancels the just-started discussion before
  rethrowing; executor-saturated resume rollback uses remove-and-recheck so
  a racing cancel converts instead of being dropped (mutation-verified test)
# Conflicts:
#	docs/changelog.md
#	src/test/java/ai/labs/eddi/engine/internal/GroupConversationServiceExtendedTest.java
# Conflicts:
#	docs/changelog.md
#	src/main/java/ai/labs/eddi/engine/internal/groups/GroupLifecycleOps.java
# Conflicts:
#	docs/changelog.md
#	src/main/java/ai/labs/eddi/engine/internal/GroupConversationService.java
… backlog caps, bounded feedback)

# Conflicts:
#	docs/changelog.md
#	src/main/java/ai/labs/eddi/engine/mcp/McpGroupTools.java
#	src/test/java/ai/labs/eddi/engine/internal/groups/GroupHitlCoordinatorTest.java
ginccc added 6 commits August 8, 2026 07:48
- the RETRO template now quotes the CONFIGURED maxLessonsPerRun (a group
  set above the default could never obtain its configured lesson count);
  buildPhaseInput gained a RetroConfig param, all agent-turn sites pass it
- maxLessonsPerRun bounds the whole harvest, not each transcript entry —
  a multi-speaker retro could store cap x entryCount before eviction
- the null-memory-store branch fires retro_recorded with zero instead of
  returning silently
- the cross-user scoping fixture now genuinely belongs to user-2
…rsist-time test (review round)

- CURRENT_SCHEMA_VERSION 3 -> 4: pausedRepeatSliceBase is resume-consumed
  persisted state; legacy docs default to -1 via Jackson (no migration)
- the approver detail=full window now requires AWAITING_APPROVAL on both
  REST and MCP surfaces — the shared paused predicate also admitted
  approvers to the transcript during AWAITING_HUMAN_INPUT
- the mid-repeat pause test records pausedRepeatSliceBase at persist time
  inside the update() stub instead of asserting the mutable instance
… round 2)

- optimistic-concurrency revision stamp on GroupWorkspace with a new
  casRevision store primitive; both backlog-add surfaces (REST + MCP) run
  a bounded read-validate-mutate-CAS loop so concurrent adds cannot drop
  each other or slip past the cap; exhaustion is an honest 409
- readOrCreate converges duplicate inserts (no unique constraint exists
  in the storage abstraction): racers delete their own duplicate and
  adopt the deterministic survivor; find() picks the same survivor
- addCadence deletes the just-created schedule when the workspace write
  fails, closing the orphaned-schedule-fires-forever path
- permanent group deletion retires cadence schedules before the
  workspace, in the crash-recoverable order
- run-claim disappearance warning sanitizes groupId (CodeQL)
# Conflicts:
#	docs/changelog.md
#	src/main/java/ai/labs/eddi/configs/groups/model/GroupConversation.java
# Conflicts:
#	docs/changelog.md
#	src/main/java/ai/labs/eddi/engine/internal/groups/GroupContextBuilder.java
#	src/main/java/ai/labs/eddi/engine/internal/groups/PhaseExecutionEngine.java
#	src/test/java/ai/labs/eddi/engine/internal/groups/PhaseExecutionEngineTest.java
ginccc added 4 commits August 8, 2026 08:48
…review round 3)

- RetroConfig clamps maxLessonsPerRun/maxStoredLessons to hard ceilings
  (20/500) — a config carrying Integer.MAX_VALUE unbounded the LLM write
  surface the record exists to bound
- FIFO eviction sorts by createdAt (setOnInsert at the store) instead of
  the updatedAt-ordered recall — a reharvested old lesson refreshed its
  updatedAt and shielded itself while a later-created lesson was evicted
…h test pins (review round 3)

- casRevision converts a corrupt (non-numeric) persisted revision into
  the declared ResourceStoreException instead of an uncaught
  NumberFormatException (CodeQL)
- findWorkspaceIds fetches up to 50 ids — a limit of 2 let 3+ racers
  compute different survivors and never converge
- casRunningDiscussion's Javadoc re-attached (casRevision had been
  inserted between doc and declaration); legacy null-revision one-write
  window documented on the interface
- schedule-retirement test pins the crash-recoverable ORDER via InOrder;
  backlog rejection tests pin never-casRevision (the real write path);
  the lost-settle test pins the failed CAS as the only store interaction
@aisabella-ai
aisabella-ai self-requested a review August 8, 2026 08:15
…ded metrics, template honesty (pre-merge review)

Six confirmed findings from the final pre-merge deep review of the
facilitator subsystem (never externally bot-reviewed) and the
integration-only content:

- CRITICAL: a deferred EACH_REPEAT escalation at a phase's final repeat
  returned before the phase-boundary HITL gate, silently skipping a
  requiresApproval phase's mandatory approval; the gate now supersedes
  the escalation (suppression entry preserves the question)
- CALL_VOTE rejected when the phase ended by signal or a DecisionRecord
  exists — the inserted vote's tally replaced signed AGREEMENTs and
  debate VERDICTs
- rejection metrics tag bounded to enum names + UNKNOWN (raw LLM move
  strings created unbounded Prometheus label cardinality); rawMove
  length-capped at parse
- negotiation-table manifest no longer promises a nonexistent
  human-arbiter mode; research-pod sets summarizeOverflow:false
  explicitly (no summarizer model is named)
- human-input 409 body surfaces the config-drift cause instead of
  misstating the state
- three e2e tests for the previously untested deferred EACH_REPEAT
  branch (mid-phase repeat+1 arithmetic, boundary phase+1, gate wins)
phaseIdx, phase.name(), TranscriptEntryType.FACILITATION, Instant.now(),
null, null));
LOGGER.infof("Facilitator escalation for group %s suppressed at phase %d — the "
+ "phase's own approval gate takes precedence", gc.getGroupId(), phaseIdx);
@ginccc
ginccc merged commit d5294a6 into main Aug 8, 2026
24 checks passed
@ginccc
ginccc deleted the feat/group-i10-templates branch August 8, 2026 09:30
ginccc added a commit that referenced this pull request Aug 8, 2026
…ion, tool provenance

- AGENTS.md phase-8 row said 60+ MCP tools while every other doc now says
  80+ (actual 82)
- the opt-in-by-absence convention over-claimed: only artifactConfig and
  taskListConfig assemble tools; contextWindow/facilitator/humanMemberConfig
  gate behaviour, not tool assembly
- #636 carried the pre-feature defects, not one of the nine items — the
  changelog and planning header now say #637-#645 with #636 named separately
- the MCP table notes which group tools come from the HITL tool set, since
  they are not declared in McpGroupTools
pull Bot pushed a commit to Stars1233/EDDI that referenced this pull request Aug 12, 2026
Brings every user-facing doc in line with what shipped in PRs labsai#636-labsai#645.
Each claim verified against source, not against the plan.

- group-conversations.md: fixed a nonexistent REST endpoint and the stale
  HUMAN_DECIDES note; completed the REST (+21), MCP (+10), PhaseType,
  TaskStatus, ProtocolConfig and DynamicAgentConfig tables; new sections
  for per-phase controls, dissent and the 23 SSE events; documented the
  RETRO ceilings; fixed the orphaned task-cap paragraph, re-parented
  bid-based assignment under TASK_FORCE, unglued 8 headings
- README: 6 -> 7 styles, ten new capability bullets, MCP 60+ -> 80+,
  tests 11,000+ -> 14,000+, OpenAI-compatible docs row
- AGENTS: 7 styles, new 10c/10d roadmap rows, tests -> 14,000+, HITL row
  clarified, opt-in-by-absence convention documented in 4.2
- docs/README: 6.0.0 -> 6.2.0, MCP 48+ -> 80+, HITL + Open WebUI entries
- SUMMARY: hitl.md and open-webui-integration.md were missing entirely
- rag.md: gemini embedding provider and chroma vector store rows
- planning: the group-collaboration queue is empty; PRs recorded
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants