Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/acceptance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ PR after verification. There is no cleanup or status-only PR.
- [Provider-scoped model discovery](provider-model-discovery.md) — one composition-owned provider lifecycle for shared discovery, ordered publication, context-safe admission, and truthful retry recovery. Status: draft.

- [Purpose-attributed auxiliary token usage](auxiliary-token-usage.md) — records every reachable session-associated auxiliary LLM call in the canonical ledger under a purpose-specific provider/model-attributed kind, without changing main-run accounting or budgets. Status: proposed.
- [Root-conversation provider correlation](root-session-provider-correlation.md) — adds an outbound-only root-session header for grouping delegated and routed model requests while preserving the active session ID and its ingress-affinity contract. Status: proposed; awaiting Plan / Interface review.
- [Root-conversation provider correlation](root-session-provider-correlation.md) — adds an outbound-only root-session header for grouping delegated and routed model requests while preserving the active session ID and its ingress-affinity contract. Status: landed in this implementation candidate; authoritative on merge.
- [Acceptance-proof resolution efficiency](acceptance-proof-resolution-efficiency.md) — keeps plan-proof resolution fail closed while avoiding duplicate task execution within the existing Doc validation runner; Vitest batching is deferred. Status: proposed.
- [Workspace enrollment preserves session authority](workspace-enrollment-authority.md) — preserves all still-authorized composed tools while atomically replacing the authenticated broker bundle; already-corrupted snapshots remain narrowed by explicit compatibility decision. Status: proposed.
- [Contextual investigative guardrails](contextual-guardrails.md) — proposed Architectural contract for one contextual action/inbound reviewer, exact post-mutation permission re-evaluation, session-only exact repeat grants, held-result release-once, main/worker trajectory, session-provider-bound guardrail-slot routing, and separate durable-machine/transient-human status. Product choices are approved; implementation builds capacity calibration and quality-measurement deliverables, while separately authorized real-model release validation is required before any production-readiness claim. Status: proposed.
Expand Down
10 changes: 5 additions & 5 deletions docs/acceptance/root-session-provider-correlation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
**Work classification:** Architectural — adds a durable outbound HTTP correlation contract and an exported engine context API across delegation, model routing, independently versioned providers, and the Jev adapter while preserving the existing affinity contract.
**Decision record:** [ADR 0360](../adr/0360-root-session-provider-correlation.md)
**Phase:** delegated model-request correlation
**Status:** proposed, 2026-09-24. Exact interface contract ready for human Plan / Interface review; it remains proposed until this plan PR merges.
**Delivery:** Split docs → implementation. On 2026-09-24 the directing human asked for the design and implementation branches to be pushed and stacked, and confirmed "yes, keep on going", so the implementation proceeds as a stacked PR on this proposed-docs commit before the plan merges. This does not mark the plan approved, authorize any merge, relax contract-drift stops, or remove either human merge gate.
**Expected tasks:** 1 (implemented on the stacked implementation branch).
**Status:** landed in this implementation candidate; authoritative on merge.
**Delivery:** Split docs → implementation. Plan / Interface PR #1882 merged as approved baseline `8f049d8a4`; this implementation candidate carries the verified contract and its `landed` transition.
**Expected tasks:** 1 (implemented in this candidate).
**Issue:** None — directed operator request.
**Plan PR:** [#1882](https://github.com/stacklok/mecatl/pull/1882).
**Approved baseline:** absent until contract approval by merge.
**Plan PR:** [#1882](https://github.com/stacklok/mecatl/pull/1882), merged.
**Approved baseline:** `8f049d8a4`.

Add an outbound-only `X-Mecatl-Root-Session-ID` alongside the existing active-session
`X-Mecatl-Session-ID`. Provider calls made by a main run and its nested engines, plus both
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0360-root-session-provider-correlation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR 0360 — Separate root-conversation correlation from active-session identity

- Status: Proposed
- Status: Accepted
- Date: 2026-09-24
- Scope: agent run context, provider calls made inside main/delegated run trees, direct teams, official LLM-provider composition, and the Jev delegated-model router.
- Supersedes: ADR 0216 only where one outbound header was the complete correlation surface; retains its active-session header semantics.
Expand Down
5 changes: 5 additions & 0 deletions engine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ The covered surface is the eight core packages (`session`, `governance`, `learni

### Added

- **Root-session run correlation context** — adds `port.WithRootSessionID` and
`port.RootSessionIDFromContext` so trusted engine composition can preserve one
causal session identity across nested runs without changing active session
identity. Added (minor).

- **Delegated-model routing decision evidence** — adds `agent.ModelRouteResult`,
`agent.SubagentModelRouter`, and `session.RoutingDecision`, with optional decision
snapshots on Subagent, Parallel, and Team-member start payloads. Added (minor).
Expand Down
12 changes: 11 additions & 1 deletion engine/agent/childask_adjudicator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stacklok/mecatl/engine/adapter/permpolicy"
"github.com/stacklok/mecatl/engine/agent"
"github.com/stacklok/mecatl/engine/governance"
"github.com/stacklok/mecatl/engine/port"
"github.com/stacklok/mecatl/engine/session"
"github.com/stacklok/mecatl/engine/team"
"github.com/stacklok/mecatl/engine/tool"
Expand All @@ -31,14 +32,17 @@ type scriptedAdjudicator struct {
script []adjOutcome
calls int
isolated []bool
roots []session.SessionID
}

func (s *scriptedAdjudicator) Review(_ context.Context, req agent.ChildAskReviewRequest) (agent.ChildAskReview, error) {
func (s *scriptedAdjudicator) Review(ctx context.Context, req agent.ChildAskReviewRequest) (agent.ChildAskReview, error) {
s.mu.Lock()
defer s.mu.Unlock()
i := s.calls
s.calls++
s.isolated = append(s.isolated, req.Isolated)
root, _ := port.RootSessionIDFromContext(ctx)
s.roots = append(s.roots, root)
if i < len(s.script) {
return s.script[i].review, s.script[i].err
}
Expand All @@ -51,6 +55,12 @@ func (s *scriptedAdjudicator) count() int {
return s.calls
}

func (s *scriptedAdjudicator) capturedRoots() []session.SessionID {
s.mu.Lock()
defer s.mu.Unlock()
return append([]session.SessionID(nil), s.roots...)
}

func allow(reason string) adjOutcome {
return adjOutcome{review: agent.ChildAskReview{Allowed: true, Reason: reason}}
}
Expand Down
7 changes: 3 additions & 4 deletions engine/agent/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -1945,10 +1945,9 @@ func (e *Engine) parentCaps(r *Run, sess *session.Session, turnIdx int) parentCa
return askReviewOutcome{}
default:
}
// A fresh background context + timeout: resolveChildAsk runs on the drain
// path with no ctx of its own, and the parked child must not hang on a
// wedged reviewer. A timed-out review is a failure → fall-through deny.
ctx, cancel := context.WithTimeout(context.Background(), askReviewTimeout)
// Detach cancellation while preserving only the causal root; the review
// remains bounded by its own timeout and the hard-abort gate above.
ctx, cancel := context.WithTimeout(detachedRunContext(r.ctx), askReviewTimeout)
defer cancel()
review, err := reviewer.Review(ctx, ChildAskReviewRequest{Ask: ask, Isolated: isolated})
switch {
Expand Down
19 changes: 19 additions & 0 deletions engine/agent/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -1561,8 +1561,27 @@ func (e *Engine) startRun(ctx context.Context, sess *session.Session, req RunReq
return run
}

// detachedRunContext returns a fresh context that carries only parent's causal
// root session. Use it instead of context.Background() when nested engine work
// must outlive parent cancellation but still belongs to parent's run tree; the
// caller bounds it with its own deadline.
func detachedRunContext(parent context.Context) context.Context {
ctx := context.Background()
if rootID, ok := port.RootSessionIDFromContext(parent); ok {
ctx = port.WithRootSessionID(ctx, rootID)
}
return ctx
}

// prepareRun seeds the causal root from sess only when ctx carries none, so every
// nested Engine.Run whose ctx derives from its parent run (or from
// detachedRunContext) inherits the tree's root without extra wiring.
func (e *Engine) prepareRun(ctx context.Context, sess *session.Session, req RunRequest, budgetBaseline session.Usage, body func(context.Context, *Run)) *PreparedRun {
ctx, cancel := context.WithCancel(ctx)
rootSessionID, hasRootSessionID := port.RootSessionIDFromContext(ctx)
if !hasRootSessionID || rootSessionID == "" {
ctx = port.WithRootSessionID(ctx, sess.ID)
}
serial := runSerial.Add(1)
ctx = port.WithRunAttemptContext(ctx, sess.ID, serial)
ctx = withSessionOrigin(ctx, sess.ID)
Expand Down
Loading
Loading