Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
21a3073
feat(access): manage who an agent serves from its Access tab (ability…
dolho Oct 6, 2026
c124604
feat(access): seats on the assignments section — an agent holding one…
dolho Oct 6, 2026
621c210
feat(objectives): owned objectives come from the seat on record, not …
dolho Oct 6, 2026
02a6158
feat(readiness): a companion is an agent whose primary holds a seat, …
dolho Oct 6, 2026
aa8993e
feat(access): canon seats, three-state consent, and drift on read (ab…
dolho Oct 6, 2026
c840761
feat(access): a shared companion serves the seat of the person each r…
dolho Oct 6, 2026
c3ec316
fix(access): what a live walkthrough of the assignments section turne…
dolho Oct 6, 2026
b2a4c66
fix(access): a failed replacement's message reads as two sentences (a…
dolho Oct 6, 2026
b913536
Merge dev; re-chain proactive_consent_not_asked onto 0091_chat_sessio…
dolho Oct 7, 2026
797a6c3
fix(workspace): the role card reads the seat on record and the viewer…
dolho Oct 7, 2026
0f97d11
Merge origin/dev into feature/ent810-assignments-admin-ui
dolho Oct 8, 2026
96673aa
test: classify ent#814's prompt-context fields for #3274's guard; run…
dolho Oct 8, 2026
c108add
Merge origin/dev into feature/ent810-assignments-admin-ui; re-chain p…
vybe Oct 11, 2026
8eda882
test(ent814): the seat-decision user stub carries agent_name, as the …
vybe Oct 11, 2026
dce477a
docs(architecture): catalog the canon roles read, the seat resolvers …
vybe Oct 11, 2026
eeccbb8
fix(seat-decisions): the seat on record validates as a canon role id,…
vybe Oct 11, 2026
e6271b1
test(ent731): the guard-metrics whole-read test records its seat like…
vybe Oct 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/memory/architecture/api-endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
| POST | `/api/agents/{name}/a2a/task` | Poll a remote A2A task by id on the same registered endpoint (#736). Same gates; deliberately NOT `effect_guard`-wrapped — a poll is a read, and deduping it would answer "has it finished yet?" from a snapshot of the last time it had not |
| GET | `/api/agents/{name}/metrics` | The agent's declared metrics joined to their RECORDED points, with freshness (ent#479) — the ONE read of a business number. Same URL as the `metrics.json` proxy it replaces; **store-only, no container**, so a STOPPED agent answers identically (the proxy returned "Agent must be running", making every number vanish exactly when an operator wanted its last value). `AuthorizedAgentByName` (uniform 404) **then** the metric-read gate — an agent key reads its own numbers, or another agent's while holding an `agent_permissions` grant on it (ent#727; charged to the reader's 240/min budget and audited hourly per reader/target/actor); 240/min per agent. `window ∈ {auto,24h,7d,30d,90d}` + `since`/`until`; `auto` = `max(24h, 12 x cadence)` capped at 90d, because a cadence spans 60s–1y and a fixed 24h shows a weekly metric four points. Bucketed by default (≤120/series); raw `points` only on the single-`metric=` path, truncation keeping the NEWEST. 422 `window_invalid` / `metric_undeclared` (a retired name names `include_retired`); 503 `metric_store_unavailable` + `Retry-After`. NOT 404 for an undeclared metric: the MCP classifier reads 404 as `not_authorized` per the #186 convention, which would tell an agent it lacks access to itself. Echoes the persisted D-010 `metrics.json`-superseded finding with `findings_evaluated_at`, so an empty list before the first compat run reads as *not evaluated* rather than *clean* |
| GET | `/api/agents/{name}/objectives` | The agent's objectives joined to its metrics (ent#666) — target, actual, freshness and the gap, computed in ONE place so the role card, the project view (ent#661 v3) and proactivity cannot disagree. Gate order is `/metrics`'s verbatim: `AuthorizedAgentByName` (uniform 404) → agent self-gate (403; an agent key reads only its own) → limiter on the VALIDATED name. **Its own rate knob** `OBJECTIVES_READ_RATE_LIMIT` (default 60/min, a quarter of `/metrics`; ONE bucket per agent, spelled in `services/objectives_read_budget.py` and shared with the Workspace role card's objective read, which leaves the objectives out instead of answering 429 — ent#676) because unlike that store-only read this one drives the CONTAINER: a directory listing plus up to 100 small file reads through the agent door, ≤ 2 in flight, aborting to `unavailable: agent_unreachable` on the first typed transport failure rather than driving open the circuit chat rides on — plus a 30 s wall-clock budget (`objectives_dir: "timeout"`) and a 5 s per-read timeout, because an agent-server that answers SLOWLY raises no typed error and the abort alone would not bound it. Not store-only by design — files are truth and they live in the container (E7/E13), so a stopped agent answers `unavailable: agent_stopped` with copy naming the fix, never a cached number. `gap.status ∈ {behind, on_target, ahead, off_target, not_computable}` is POSITION relative to target given direction, never pace. 503 `metric_store_unavailable` + `Retry-After: 30` is the only non-2xx below the gates — every other failure is a NAMED field on a 200 (`unavailable`, `source.*`, `findings[]`), because "this agent is stopped" is an answer, not an error. `response_model=ObjectiveJoinRead` with a key-parity test, so an additive service field fails the build instead of being silently filtered out |
| GET | `/api/agents/{name}/canon/roles` | The seats the agent's canon defines — `<canon>/roles/*.yaml` as `{id, title, updated}` (trinity-enterprise#817), read through the same agent door and the same per-agent `objectives_read_budget` bucket as `/objectives`, same gate order (uniform 404 → agent self-gate 403 → limiter). Every empty answer is named (`unavailable: agent_stopped` / `reason: no_canon|absent|unreadable|timeout`), never a blank; `response_model=CanonRolesRead`. OSS-core; its only consumer is the gated assignments section's seat picker and drift check (`services/canon_roles_service.py`) |
| GET | `/api/agents/{name}/metrics/definitions` | The agent's DECLARED metric registry (ent#477) — what `template.yaml metrics:` declares, as the backend reconciled it, NOT what it has measured. `AuthorizedAgentByName` then the same metric-read gate as `/metrics` (ent#727 — before it, any agent key read any owner-accessible registry); `?include_retired` serves definitions the template no longer declares (kept so points stored under their name stay interpretable). Answers on a STOPPED agent (it is a DB read). Carries `declared` + a next-action `message` for the empty state, `type_conflict` for a refused type change, and the retention/cap `policy` — live values resolved through the settings chain, `enforced: true` and a `source` per knob since ent#478 |
| POST | `/api/agents/{name}/metrics/points` | Record a batch of observations of the agent's DECLARED metrics (ent#478) — the ONLY metric write path. `routers/metric_points.py`, registered before the agents router's single-segment routes. `AuthorizedAgent` + the #918 self-gate (an agent key records only as itself), then rate limit → size (2 MiB; a point cap is not a size cap — 1000 points x 10 dims x 128 chars is ~1.4 MB of legal input) → batch idempotency → validate → daily cap. **All-or-nothing**: 422 `{reason: "invalid_points", errors: [{index, metric, code, message, hint?}]}` with the frozen reason-code list (requirements §48.4). 429 splits into per-minute `rate_limited` and `daily_point_cap_exceeded` (with `Retry-After` to the next UTC midnight), because the remedies differ. DB failures are CLASSIFIED — connectivity is 503 + `Retry-After`, a content error is a non-retryable 500, since an agent told to retry a permanently unstorable batch retries forever. Every non-2xx exit after the idempotency claim goes through one `_reject` choke point that releases it, or one bad batch wedges the caller's key for 24 h. Replay returns the first snapshot with `X-Idempotent-Replay`; an in-flight duplicate is 409. A different value at an existing `(metric, ts, dims)` restates the row (ent#729): the 201 counts `recorded` / `deduplicated` / `corrected` separately (requirements §48.9) |
| POST | `/api/agents/{name}/metrics/definitions/refresh` | Re-read the agent's live `template.yaml` and reconcile its registry (ent#477). `AuthorizedAgentByName` — a **use**, not a grant (the same principal can already `pull`; an agent's own scoped key may refresh itself). The third trigger beside creation and the git hooks, because an agent that edits its own block in-container and auto-syncs PUSHES it, so no backend `pull` ever fires. **Running agent only**: 409 `agent_not_running` (the stopped-agent read spawns a throwaway container, and no request-triggered route may create one as a side effect of a read), 503 `template_unreadable` — both named on `X-Refresh-Unavailable`, and both leave the registry untouched (#2196). Idempotent by construction, so Invariant #18 does not apply |
Expand Down
Loading
Loading