Repository navigation
Conversation
…-enterprise#706) Amends §11.8 (health now measures agreement with the repository, not only push success) and adds §11.19: the four persisted columns, the one policy module and its scheduler mirror, the work-agent predicate, the state rules, the freshness-guarded freeze and the one-item-per-episode alert. Refs Abilityai/trinity-enterprise#706
…ty-enterprise#706) services/sync_freeze_policy.py::classify decides an agent's sync state (green / yellow / red / unknown), the operator-readable reason, the recommendation and whether its schedules freeze. Stdlib only, never raises, vendored byte-identically to src/scheduler/ (the #1088 precedent) with a parity test. The scheduler's SYNC_FAILURE_FREEZE_THRESHOLD now comes from the mirror, so there is one number. Nothing calls classify yet; the poller, the gates and the surfaces follow. Refs Abilityai/trinity-enterprise#706
…count, last push (trinity-enterprise#706) agent_sync_state gains diverged_since, dirty_files, dirty_since and last_successful_push_at, on both tracks: the SQLite migration agent_sync_state_divergence and Alembic 0080_agent_sync_state_divergence (parent 0079_telegram_group_context, the live head), plus schema.py and tables.py. All nullable, no backfill: the clocks start at the first poll after upgrade, which is the 24 h soak before any divergence freeze. The upsert takes a KEEP sentinel for the two clocks so a caller can say clear (None) as distinct from unchanged, which _merged() cannot express. Refs Abilityai/trinity-enterprise#706
…d the last push (trinity-enterprise#706) On every poll, inside the leased cycle: diverged_since is set once when the working tuple (origin on the agent's own branch) shows ahead or behind, cleared when both are known 0 (or ahead is 0 with no upstream), and kept when ahead could not be computed. dirty_files comes from the changes_count the status call already makes, with dirty_since as its own episode clock. last_successful_push_at is the later of the heartbeat's #3011 record, an operator Push and the stored value; an image older than #3011 falls back to its last success cycle. Agent timestamps are validated (explicit offset, not in the future) before they can win a max(). Refs Abilityai/trinity-enterprise#706
…ce freeze (trinity-enterprise#706)
When a divergence freeze is in force, the poller (inside its leader lease)
raises one sync_diverged item with the deterministic id
sync-diverged-{agent}-{diverged_since}. create_item's (agent_name,
request_id) conflict target makes repeat polls, a restart and a fail-open
double leader converge on one row; a new episode has a new id. The prefix
is platform-reserved so an agent cannot pre-create the id and silence the
alarm about itself. No item for red-but-not-frozen; not auto-resolved. The
context carries counts and the recommendation, no agent-authored error text.
Refs Abilityai/trinity-enterprise#706
… agents (trinity-enterprise#706)
Both enforcement points now decide with the one policy module:
- src/scheduler/database.py: sync_freeze_reason() returns why an agent's
cron firing is frozen (or None), reading the divergence inputs in the same
fail-open query; should_freeze_schedules() is its bool view. A reason
string, not a (bool, reason) tuple, because a tuple is truthy and a caller
on the old shape would freeze the fleet.
- src/scheduler/service.py: the skipped row's skip_reason carries the
reason ("Git sync frozen: diverged 0 behind / 7 ahead for 26h"), and the
comment no longer calls the row count bounded.
- routers/internal.py: sync-health-status keeps its keys and adds
freeze_reason, divergence_age_s and work_agent via the new
services/sync_health_view.py, which the poller now uses too.
Refs Abilityai/trinity-enterprise#706
…verdict (trinity-enterprise#706)
GET /api/agents/sync-health and GET /api/agents/{name}/git/sync-state add
state, reason, recommendation, binding, freeze, the divergence age and the
new columns; every pre-existing key is unchanged. MCP get_git_sync_state
passes the backend JSON through, so it carries them too with no TS change.
The batch now reads through one shared query, db.list_sync_health_rows
(agent_git_config joined to live ownership, left-joined to
agent_sync_state), so the dot and the poller see the same set of agents.
Refs Abilityai/trinity-enterprise#706
…(trinity-enterprise#706) utils/syncHealth.js no longer owns a threshold: classifySyncHealth maps the backend's state (unknown -> gray) and syncHealthLabel shows its reason and recommendation on hover. The 24 h / 7 d / behind-is-red rules are gone from the frontend, so the dot cannot disagree with the scheduler's freeze. Visible change: an agent ahead of origin with auto-sync off used to render gray (it had never synced); it now renders the backend's yellow or red. Refs Abilityai/trinity-enterprise#706
…freeze (trinity-enterprise#706) Architecture (agent-lifecycle Git Sync Health, database DDL, background services, backend catalog, api-endpoints), the git-sync-health flow (data model, poller, the new §2b policy and freeze, the dot, tests, controls, known limitations), the github-sync journey note, the feature-flows index row, the RD-03 invariant note, and the user docs: what the sync state means, when schedules pause and resume, the new Operating Room entry. The MCP get_git_sync_state description names the new fields (the tool is a pass-through; no behaviour change). Refs Abilityai/trinity-enterprise#706
…nterprise#706) When the agent cannot compute its working tuple (a git timeout sends ahead_working/behind_working as null), the poller correctly KEEPS diverged_since, but _coerce_counter still stored the unknown counts as 0. The row then read "diverged 25 h" next to 0/0: the freeze fired with the self-contradicting reason "diverged 0 behind / 0 ahead" and the episode's one sync_diverged item carried ahead 0 / behind 0. Pass None (upsert's "keep the prior value") for the two working counts exactly when the clock is kept on an unknown tuple. Scoped to that case: with no episode to keep, an unknown tuple is still stored as 0 (#2827). Found by /review on #3035. The new test was red before the fix (assert (0, 0) == (7, 31)); the control arm is green before and after.
…trinity-enterprise#706)
#3005 (skill sets) landed 0080_agent_skill_sets on 0079. This branch's Alembic revision becomes 0081_agent_sync_state_divergence on top of it, and the SQLite entry follows agent_skill_sets in MIGRATIONS. No change to what the migration does; docs updated to the new revision id.
GET /client-portal/agents/{name}/history serialised `cost` on every message to
every principal, a portal token (an external Workspace client) included. The
Workspace contract excludes costs for external clients, and every other
projection already honours it. The history door was the one that leaked.
- PortalHistoryMessage no longer declares `cost`, so the route's response_model
strips it. The row still stores it.
- Dropped for EVERY principal, not split by principal: nothing reads a history
message's cost. replyFromHistory copied it into a reply object whose `cost`
no caller used, so that dead copy goes too.
- Execution ids are kept on purpose. in_flight_execution_id and
last_turn_outcome.execution_id are the client's OWN turn handles: the 202
dispatch response issues the same id, and the client streams, cancels and
reattaches by it. Removing them would break reattach-after-reload and the
failed-turn verdict (#2320).
Test: test_3063 runs the real route against a real sqlite row written with a
cost of 0.4242, for a portal token and a platform principal, plus the reply
poll's narrow read. It was red on dev ("cost reached the wire … 0.4242") and is
green now. It has a positive control that the costed row is in the response.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on pull agents (#2392) The effect guard de-duplicates outbound messages, calls, shares and A2A calls on the execution id, which reached it only as an optional value the agent typed. Pull re-delivers the same execution after a lease expiry, so a missing id meant a duplicate reaching a real recipient. - Agent MCP config sends X-Trinity-Execution-Id per process: ${TRINITY_EXECUTION_ID:-manual} for Claude Code and Gemini, env_http_headers for Codex. Validator accepts that one extra shape. - MCP server reads the header on every POST; it wins over the tool arg, which stays as the older-image fallback. - effect_guard: on a pull-mode agent an effect with no usable id (absent, unknown, another agent's) is refused with a 422 effect_unguarded and a budgeted operator alarm. `manual` (a person's terminal session) and a lookup error proceed. Other agents proceed and log effect_guard.degraded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2392) A model typing execution_id="manual" as a tool argument (Codex, which sends no header when unset, or an older image) would skip the pull-mode refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#3063) POST /client-portal/agents/{name}/chat, the streaming fallback, still declared PortalChatResponse.cost, so a portal token received the turn's cost. Drop the field; the service keeps returning it for internal accounting and response_model strips it. Route test for both principals (positive control on response/message_id) plus a model-level pin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dentity (Abilityai/trinity-enterprise#715) The operator queue's reads handed agent-, system- and other machine keys the email of the person who answered an ask and of the person it was for, and the file write-back put the answerer's email into the agent's own queue file. - GET /api/operator-queue, /{id} and /agents/{name}: a non-person principal gets an ALLOWLIST projection (`_MACHINE_ROW_FIELDS`) with no responded_by_id/responded_by_email, addressed_to_email (file rows too), disposed_by_email or resolved_to. A person (JWT or their own user-scoped key, `is_person_principal`) still reads the whole row. - The platform's heads-ups ABOUT a person (the ent#499 problem report: a client's email and verbatim words; the ent#308 inbox collision: client addresses, an email-slug id) are not returned to a machine at all (skipped on list, 404 on the item route). `portal-inbox-collision-` joins the reserved prefixes, so that alert also stays out of the agent's file, its resume turn and the Execution Context "Ended asks" line. - The file write-back no longer writes `responded_by` (delivery and reconstruction); the prompt says `response`, `response_text` and `responded_at`. - The clear-resolved /ws trigger is the count only (no `cleared_by`). Tests: tests/unit/test_ent715_queue_person_fields.py (63). Mutations, each reverted from a scratch copy and red: the allowlist (42 red), the row filter and the item 404 (3 each), both write-back lines (1 each), the reserved prefix (2), the trigger (1), the prompt (1), and a new unclassified column (the person-side classification test, 6 red). One existing assertion flipped: test_ent611_native_ask.py (a file row's addressee is now withheld). Registered residuals: the answer/ending wakes still attribute the turn to the person (source_user_email on the execution row); REST row breadth for an admin-owned agent key stays with trinity-enterprise#719. Fixes Abilityai/trinity-enterprise#715 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… read rule (Abilityai/trinity-enterprise#715) The ent#499 problem report's "the agent never sees the words" now holds through the agent's own key too (workspace.md, core-agent.md). /cso --diff: no findings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is reserved (Abilityai/trinity-enterprise#715) /review I1 on #3090. `_ABOUT_A_PERSON_ID_PREFIXES` said it was a subset of `_RESERVED_ID_PREFIXES`, and nothing checked that. The machine reads key on the first tuple; the file write-back, the resume wake and the ended-asks line key on the second. A heads-up prefix outside the reserved set would be hidden from a machine's read yet still written into the agent's file. Mutation: a third, unreserved prefix on the tuple turns exactly this test red (1 failed, 63 passed); the emitter-driven tests stay green, which is the gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Alembic 0081_agent_sync_state_divergence -> 0082, down_revision 0081_portal_messages_unread_idx; SQLite entry after portal_messages_unread_index. Docs/registry conflicts keep both sides.
…06-707 # Conflicts: # tests/registry.json
…L_MIGRATION_TESTING.md (#2392) — mechanical, per the merge-train note on the PR Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…06-707 # Conflicts: # docs/memory/feature-flows.md
3 tasks done
This was referenced Sep 29, 2026
Merged
Closed
2 tasks
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.
Integration surface for #3075, #3090, #3086, #3035. Never merged; members merge individually once green.