Repository navigation
Conversation
…ai/trinity-enterprise#810) Assignments (primary, approver, collaborator, viewer) had no UI: setting an agent's primary human or its approvers took an API call. Agent Detail › Access now has a "People this agent serves" section above the operator list, over the existing ent#500 endpoints. An instance admin sees the primary apart from the stakeholders, with the seat it serves, each row's proactive-brief consent and role-check state; adds a person picked by name, changes a kind in one action on the row, removes a row, and replaces the primary as one flow that demotes the old primary first and puts it back if the new one cannot be written. Every refused write shows the backend's own reason. Non-admins see the same section read-only. The section is absent without the assignments module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, optional and clearable roles (Abilityai/trinity-enterprise#811) The public half of ent#811, on top of the ent#810 section. - services/assignment_provider: the optional provider method seat_for and resolve_seat(agent), which answers which seat an agent holds itself or serves through its primary with no triggering user. Always a dict, never a raise: no provider, a provider without seat_for, no answer, a raise or a malformed shape all read as "no seat" with a named reason, the failure direction the readiness gate needs. - The section's header has both forms: "Holds the seat: <id>" (the agent itself, which wins) or "Serves the seat of its primary". An admin can record, change or clear the seat the agent holds; a refusal names the agent that already holds it. - The seat on a row is optional when adding or replacing the primary, and a row's seat can be added, changed or cleared (explicit null). - With no primary yet, the add form starts on Primary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x-role (Abilityai/trinity-enterprise#812) Under the 2026-10-06 ruling (R50 + R50a) the seat is recorded in Trinity, held by a person or by an agent; x-role.role in template.yaml stops being truth. The objective join was the one surface that relied on it. - "Owned" resolves through assignment_provider.resolve_seat: the seat the agent holds (that seat only), else the seats its assigned people hold, primary first; a shared companion owns the union. - The canon declaration alone enables the join; x-role is neither required nor read. - An agent with no seat owns nothing and a no_seat finding says why and what to do (it replaces role_id_invalid); supporting objectives still show. The role card renders the sentence. - supporting_agents is read as before, documented as frozen. - The response's role carries case and seats. Existing suites that declared their seat through x-role now record it through a registered provider, which is what an assigned primary does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…not one whose template has x-role (Abilityai/trinity-enterprise#813) The readiness gate (ent#689) held an unstamped agent's proactive brief when its template declared x-role. Under the 2026-10-06 ruling the seat is recorded in Trinity, so "is this a companion" is now answered by assignment_provider.resolve_seat: a primary that holds a seat (serves). - An agent holding a seat itself, no primary with a seat, no seat lookup or a lookup error all read as "not a companion", so the brief fires as before (fails open, #1638). - The owner's stamp still decides first and the seat is not looked up for a stamped agent; the rollout seed and held-schedule behaviour of ent#689 are unchanged. - The template is no longer read: one container read less per brief, and an agent can no longer take itself out of scope by editing its own files. - Each decision is logged with its reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ilityai/trinity-enterprise#817) The public half of ent#817, completing ent#810's remaining criteria. - Canon seats (OSS-core, operator ruling 2026-10-06): GET /api/agents/{name}/canon/roles lists <canon>/roles/*.yaml with id, title and updated through the agent door the objective join uses, on the same per-agent read budget, self-gated for agent keys. Every empty answer is named (stopped, no canon, no roles/, unreadable, timeout). - Proactive consent in three states: agent_sharing.allow_proactive NULL = not asked, 0 = declined, 1 = consented. New shares are written NULL; a data migration on both tracks (SQLite + Alembic 0090) moves the legacy DEFAULT-0 rows to NULL. Who may be messaged is unchanged: only 1 allows it. Shares and the Access list carry proactive_consent. - The assignments section: seat fields suggest the canon's seats (a typed id still works), the header and rows show the role's title and the file's updated stamp, drift reads current / changed / missing / unknown with its reason, and consent reads in three states. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…un is for (Abilityai/trinity-enterprise#814) - assignment_provider.resolve_served_seat(agent, email): the person's own seat, the agent's own when it holds one (holds wins), the primary's as the fallback; never raises. A provider that predates the OPTIONAL served_seat_for falls back to seat_for. - Seat decisions (ent#638) are stamped with the served seat; the agent's decided_by_role is only the fallback when nobody's seat is on record (its prompt used to name the primary's seat on every run). - The prompt keeps its Primary human line and adds "Seat this run serves" only when the run serves a different seat: a chat user, or the Workspace a scheduled brief is delivered to (read off the execution row, never rendered). Suppressed audiences render nothing. - The add form no longer pre-fills the primary's seat for every new person. The one-primary rule, the primary display name and the primary address are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d up (Abilityai/trinity-enterprise#810, #812, #814) Found by driving the section end to end on a local instance (seeded people, canon, three agents) with layout-shift capture on every step. - Replacing the primary with someone already on the agent promotes their existing row instead of adding a second one. Before, the person was listed twice (primary and approver) and, under ent#814, held two seats to choose between. The old primary still steps down first. - The add form clears the seat after a successful add. Before, the seat typed for one person was silently given to the next one added. - First load keeps the loaded section's shape (seat bar, a two-person list card, the add form) and no longer flashes "No seat yet" for an agent that has one. On a slow network the operator list below moved 133px on arrival; it now moves 5-24px for agents with up to two people. - A failed replacement says where the agent stands, from the re-read roster ("Leo Park is still the primary."), not only the error. - On a phone each row stacks its details above its controls; the kind select no longer covered the person's name. - The Workspace role card renders the no_seat finding. ent#812 had mapped it as a role error, which the backend never sends, so the sentence could not appear. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bilityai/trinity-enterprise#810) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
|
@dolho a heads-up from #3304 (draft): with it, agent file download and preview no longer open a path that is a link, for every caller, the platform's own reads included. A |
…n_claude_id Conflicts: SQLite MIGRATIONS list (dev's platform_alert_subjects and chat_session_claude_id first, then proactive_consent_not_asked) and tests/registry.json (union of both sides). Alembic: 0090_proactive_consent_not_asked forked off 0089 alongside dev's 0090_platform_alert_subjects; renamed to 0092_proactive_consent_not_asked with down_revision = 0091_chat_session_claude_id. One head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s assignment, not x-role (live-check follow-up) The 2026-10-06 live check of this PR and ent#818 left three open items: - The Workspace role card took its role from x-role.role and its "Seat ·" line from x-role.seat (an email), so a shared companion's card said "sales-lead" while the Access tab said Head of Sales. It now reads the seat this agent serves for the viewer (resolve_served_seat: held, else the viewer's own, else the primary's) — the answer the agent's prompt gets — and says where it came from (holds / your seat / its primary's), never a person. No seat → role.error no_seat, said once, with the supported objectives still listed. x-role remains only as the readiness claim and as a reason to show the card. - "Your relationship" was never wired: the agent's own primary read "no assignment recorded". It is now the viewer's assignment kind via the provider's kinds_for (Abilityai/trinity-enterprise#818), answered for a stopped agent too. - "Add operator" pushed the page 47px wider than a 390px screen: the email input now gives way (min-w-0) and the button does not wrap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The three "Still open" items from the 2026-10-06 live check are fixed in 1. The Workspace role card now reads the seat on record, not
2. "Your relationship" now shows the viewer's assignment kind.
3. "Add operator" no longer overflows on phones. The email input gets Tests
Docs:
🤖 Generated with Claude Code |
|
Resolve by running |
|
Resolve by merging |
Re-chain the proactive-consent revision: 0092_proactive_consent_not_asked forked with dev's 0092_portal_messages_attachments off 0091; it is now 0095_proactive_consent_not_asked off 0094_agent_skill_gates (1 head). migrations.py and tests/registry.json: keep both, dev's entries first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-chain seat_ask_class_state: 0093 forked with dev's 0093_platform_alert_responded_heal; it is now 0096_seat_ask_class_state off 0094_agent_skill_gates (1 head). 0096, not 0095, because #3271 takes 0095_proactive_consent_not_asked off the same parent — whichever of the two lands second re-chains onto the other. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged Heads-up: #2984 now chains Related: trinity-enterprise#818 now leaves 🤖 Generated with Claude Code |
… the ent#817 migration on the engine's connection
- test_3274's free-text guard (from dev) requires every Optional[str]
ExecutionContext field to be scanned or named platform-controlled:
served_role_id is a sanitized seat id, served_person_email is never
rendered.
- The ent#817 migration test wrote through get_engine() but migrated
through get_db_connection(); under seed 12345 the two pointed at
different files ('no such table: agent_sharing'). It now migrates on the
engine's own DBAPI connection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Who an agent serves, which seat it holds, and what it owns because of it: one feature, managed from Agent Detail › Access. This PR is the public half of six private-tracker tickets:
Its private pair is Abilityai/trinity-enterprise#818, the assignments module changes for ent#811. Neither PR depends on the other to build or pass tests.
ent#810: "People this agent serves" (above the operator list)
LoadFailedwith retry; a failed refresh keeps the data and shows a stale banner.ent#811: seats (operator ruling 2026-10-06, R50 + R50a)
services/assignment_provider.resolve_seat(agent)answers which seat an agent holds itself or serves through its primary, with no triggering user. It's for the readiness gate (ent#813), the objective join (ent#812) and the role card. It always returns a dict and never raises: every degraded case reads as "no seat" with a named reason. It reads the provider's optionalseat_forthroughgetattr.<id>" when the agent holds it itself (this wins when both apply);<id>()";nullclears).ent#812: owned objectives come from the seat on record
resolve_seat, notx-role.role. That means the seat the agent holds (that seat only), else the seats its assigned people hold, primary first; a shared companion owns the union.x-canonalone enables the join;x-roleis neither required nor read. Behaviour change: an agent withx-rolebut nox-canonno longer joins, as the issue specifies.no_seatfinding says why and what to do (assign the primary with their seat on the Access tab). It replacesrole_id_invalid, and supporting objectives still show. The Workspace role card renders that sentence.supporting_agentsis read as before, documented as frozen.rolenow carriescaseandseats(model updated, soresponse_modeldoesn't drop them).x-role. They now record it through a registered provider, which is what an assigned primary does. Assertions about objectives, numbers and findings are unchanged.requirements/lifecycle-observability.md§50) are updated.ent#813: the readiness gate asks the seat, not the template
resolve_seat. A companion is an agent whose primary holds a seat (serves).core-agent.md§5.36) say so.ent#817: canon seats, three-state consent, drift on read
GET /api/agents/{name}/canon/roles(services/canon_roles_service.py) lists<canon>/roles/*.yamlwith id,titleandupdated.roles/, unreadable, timeout.agent_sharing.allow_proactiveNULL = not asked, 0 = declined, 1 = consented.0090_proactive_consent_not_asked) moves the legacy DEFAULT-0 rows to NULL. Data only; the DDL is untouched, so there's no table rebuild.proactive_consent.updatedstamp;requirements/infrastructure.md.ent#814: a shared companion serves each person's own seat
assignment_provider.resolve_served_seat(agent, email)returns the person's own seat. The agent's own seat wins when it holds one (holds wins), and the primary's is the fallback. It never raises. A provider without the OPTIONALserved_seat_forfalls back toseat_for.decided_by_roleis used only when nobody's seat is on record, because the old prompt named the primary's seat on every run.Primary humanline is unchanged. ASeat this run servesline is added only when the run serves a different seat: a chat user, or the Workspace a scheduled brief is delivered to. That address is read off the execution row and never rendered. Suppressed audiences still render nothing.primaryaddress.Still open
The Workspace role card still decides whether to show from
x-roleand takes its role title from there. ent#812 only moved its objectives line, so switching the card is a follow-up.The drift loop is replaced by the check on read (ent#817).
Changes
src/backend/services/assignment_provider.py: the optionalseat_formethod andresolve_seat()src/backend/services/objective_join_service.py,models.py(ObjectiveRoleRead),client_portal'sPortalAgentRole.vuecopy (ent#812)src/backend/services/role_readiness_gate.py(ent#813)services/assignment_provider.py,routers/seat_decisions.py,services/platform_prompt_service.py,services/task_execution_service.py(ent#814)src/backend/services/canon_roles_service.py(new),routers/agent_files.py,db/agent_settings/sharing.py,db_models.py,db/migrations.py,migrations/versions/0090_proactive_consent_not_asked.py(ent#817)src/frontend/src/components/AgentAssignmentsSection.vue(new), mounted inAccessPanel.vuesrc/frontend/src/stores/assignments.js(new, through the singleapiclient)src/frontend/src/utils/assignments.js(new, pure: seat line, replace-primary steps + rollback, consent/drift labels, error text)tests/unit/test_ent811_resolve_seat.py,tests/unit/test_ent812_objective_join_seat.py,src/frontend/tests/unit/agentAssignmentsSection.mount.spec.jsTest Plan
tests/unit/test_ent811_resolve_seat.py: 17 passed. Each answer passes through; no provider, no agent, unsupported provider, a raise, no answer and eight malformed shapes all read "no seat". The neighbouring ent#500 provider suites: 72 passed together.tests/unit/test_ent812_objective_join_seat.py: 12 passed, through the realread_objective_joinwith the ent#666 fakes. Covers:x-roleis not read;x-canonalone enables the join;no_seat, and still shows supporting work.Five mutations each turn tests red:
x-rolestill enabling the join;no_seatfinding;tests/unit/test_ent689_readiness_gate.py(TestBriefReadinessrewritten for seats): 32 passed. Covers:Four mutations each turn tests red:
The 24 unit files touching the join, role card, provider interface, seat decisions and readiness gate: 847 passed on seeds 12345 and 106.
tests/unit/test_ent817_proactive_consent.py(8) andtest_ent817_canon_roles.py(14) pass, on real SQLite and the agent-door fakes. The 42 consent and sharing files, including schema parity, pass: 1,139. The bug(auth): agent-scoped keys can toggle their own agent's autonomy — PUT /api/agents/{name}/autonomy lacks reject_agent_principal #2996 route census passes.tests/unit/test_ent814_served_seat.py: 22 passed. A mutation check reverting each half (the decision stamp, the prompt line, the seat-run address) turns it red.Full public unit suite: 21,973 passed. The 42 failures (payments pin parity, a2a, ssrf, mcp_validator, ipv6) fail identically on a clean
devcheckout, caused by local venv package versions (e.g.1.12.0vs the1.18.0pin); the other 6 are order-flaky in those same files.Live on a local instance:
Seat this run serves: project-lead, with no email in the prompt;project-lead, though the agent claimedcfo;agentAssignmentsSection.mount.spec.js: 32 passed (mounted, jsdom). These mutations each turn tests red:Full frontend unit suite: 274 files, 4,769 tests passed, including the raw-colour, loading-gate and source-text ratchets.
check:tokensOK.Live on a local instance with the assignments module enabled (this branch + ent#818), light and dark:
The live run also caught that the routes are mounted at
/api/enterprise/assignments, not/api/assignmentsas the issue's notes say. The spec pins the real paths.Live walkthrough (2026-10-06)
Every claim above was driven end to end on a local instance:
The run found seven defects the unit suites missed. All are fixed here, each with a test that fails when the fix is reverted:
no_seatsentence. Before, it was mapped as a role error and never appeared.During actions (add, edit, replace, seat changes) the section causes no layout shift.
After merge
Trinity
devneeds an enterprise pointer bump once ent#818 lands.Related to abilityai/trinity-enterprise#810, #811, #812, #813, #814 and #817 (cross-tracker: closed by hand at release)
🤖 Generated with Claude Code