You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(memory): L4 GoalAncestry can never render — ancestry chain never passed at the only call site #13687
The only production call site — chat_workflow/llm_handler.py:810-816 — passes user_message, model_name, session_id, memory_graph, knowledge_service, and never passes goal_ancestry. So should_load(None) is always False and L4 never appears in a prompt,
even with the feature flag on.
The producer already exists and matches exactly
llc/services/goal.py:239:
asyncdefget_goal_ancestry_for_work_item(
self, session: AsyncSession, goal_id: uuid.UUID,
) ->List[dict]:
"""Return root-first goal ancestry chain for a goal_id (GH#6469). Includes the goal itself as the last element. Returns plain dicts (``id``, ``title``, ``level``, ``status``) so callers avoid importing LLCGoal. Returns ``[]`` when the goal is not found. """
Root-first, plain dicts, title + level — precisely what Layer4GoalAncestry.render reads
(chat_history/layers.py:368-380), and it cites GH#6469 in its own docstring. The consumer's
docstring names the producer; the producer's docstring names the consumer. Only the call is
missing.
Fix
Resolve the goal for the current session/work item and pass the chain at chat_workflow/llm_handler.py:810.
Open question for the implementer to resolve from the code, not to guess: how a chat session maps
to a goal_id. Check session.metadata (the same dict already read at :798 for company_id)
and the LLC work-item linkage before adding any new plumbing. If no goal is associated with a
session, goal_ancestry stays None and L4 correctly stays silent — that is the designed
behaviour, not a failure.
The lookup must be non-fatal and must not add a blocking DB round-trip to every turn where no
goal is linked: skip the query entirely when the session carries no goal reference.
Acceptance criteria
With TIERED_CONTEXT_ENABLED=true and a session linked to a goal, the prompt contains a ## Goal Ancestry breadcrumb, evidenced by captured prompt output.
A session with no linked goal issues no goal query and renders no L4 block.
A goal-lookup failure is non-fatal: the turn completes without L4.
Layer4GoalAncestry itself is unchanged — this is a call-site fix.
Blocked on #13704 — and the wiring this issue describes is unsafe as written
Recording the blocker here, since this issue is open and gave no indication of why it had stalled.
#13696 removed the L4 wiring rather than shipping it. Review found two problems with the approach this issue specifies:
It cannot work. The only carrier for work_item_id into the prompt path is api/chat.py:1479 — request_data.get("context", {}), a raw client JSON bag. A repo-wide grep finds only readers of that key; nothing server-side ever sets it for a chat turn. L4 would have stayed dark regardless of the call-site fix.
Wiring it as described opens a cross-tenant read. Neither WorkItemService.get (llc/services/work_item_service.py:305) nor GoalService.get (llc/services/goal.py:114) filters on company_id, though both models carry it. Any authenticated user posting {"context": {"work_item_id": "<any uuid>"}} would have had another company's goal title/level/status, plus every ancestor, rendered into their prompt.
This also invalidates the trust argument in build_governed_identity's own docstring (chat_workflow/models.py:407-411), which justifies trusting that bag on the grounds that a caller can only add restrictions to their own run. Turning the same field into a read selector breaks that reasoning.
What unblocks this
#13704 delivers the two prerequisites: a server-side session→work-item binding that is not settable from the request body, and tenant-scoped lookups. Its design is settled — it mirrors the existing trusted-overlay seam (SessionRoleService / apply_role, chat_workflow/manager.py:3636-3644) rather than inventing a new trust model, so context["work_item_id"] becomes a server-written value and the existing build_governed_identity path reads something trusted.
Once that lands, this issue is what it always claimed to be: a call-site fix.
Correction to the issue body
Only the connection is missing.
Not accurate. The producer and consumer both exist, but there is no safe carrier between them — and no carrier at all. That is why this could not be closed alongside #13686.
Closed by #13728 (6324def74), which delivered the two prerequisites this issue was blocked on: a server-side session→work-item binding and tenant-scoped goal lookups.
L4 GoalAncestry now renders. Proven through the production path — test_a_bound_session_renders_the_goal_ancestry_block starts from the binding and asserts ## Goal Ancestry reaches a captured prompt, rather than injecting a chain into the builder (which only ever proved Layer4GoalAncestry renders, and that was never in doubt).
Correction to this issue's premise
The body said "Only the connection is missing." That was wrong twice over, and #13696 removed the wiring rather than ship it:
There was no carrier.work_item_id reached the prompt path only from api/chat.py:1479 — a raw client JSON bag — and nothing server-side ever set it. L4 would have stayed dark regardless of the call-site fix.
Wiring it as described opened a cross-tenant read, since neither LLC lookup filtered on company_id.
The producer and consumer both existed; what was missing was a safe carrier between them. That is why this took a prerequisite issue rather than a one-line change.
Activity
fix(memory): reconnect L2 memory graph and L4 goal ancestry (#13686, #…
fix(memory): reconnect L2 memory graph and L4 goal ancestry (#13686, #…
fix(memory): reconnect the L2 memory graph (#13686) (#13696)
mrveiss commentedon Aug 8, 2026
Blocked on #13704 — and the wiring this issue describes is unsafe as written
Recording the blocker here, since this issue is open and gave no indication of why it had stalled.
#13696 removed the L4 wiring rather than shipping it. Review found two problems with the approach this issue specifies:
It cannot work. The only carrier for
work_item_idinto the prompt path isapi/chat.py:1479—request_data.get("context", {}), a raw client JSON bag. A repo-wide grep finds only readers of that key; nothing server-side ever sets it for a chat turn. L4 would have stayed dark regardless of the call-site fix.Wiring it as described opens a cross-tenant read. Neither
WorkItemService.get(llc/services/work_item_service.py:305) norGoalService.get(llc/services/goal.py:114) filters oncompany_id, though both models carry it. Any authenticated user posting{"context": {"work_item_id": "<any uuid>"}}would have had another company's goaltitle/level/status, plus every ancestor, rendered into their prompt.This also invalidates the trust argument in
build_governed_identity's own docstring (chat_workflow/models.py:407-411), which justifies trusting that bag on the grounds that a caller can only add restrictions to their own run. Turning the same field into a read selector breaks that reasoning.What unblocks this
#13704 delivers the two prerequisites: a server-side session→work-item binding that is not settable from the request body, and tenant-scoped lookups. Its design is settled — it mirrors the existing trusted-overlay seam (
SessionRoleService/apply_role,chat_workflow/manager.py:3636-3644) rather than inventing a new trust model, socontext["work_item_id"]becomes a server-written value and the existingbuild_governed_identitypath reads something trusted.Once that lands, this issue is what it always claimed to be: a call-site fix.
Correction to the issue body
Not accurate. The producer and consumer both exist, but there is no safe carrier between them — and no carrier at all. That is why this could not be closed alongside #13686.
feat(chat): server-side work-item binding and tenant-scoped goal look…
feat(chat): server-side work-item binding and tenant-scoped goal look…
mrveiss commentedon Aug 8, 2026
Closed by #13728 (
6324def74), which delivered the two prerequisites this issue was blocked on: a server-side session→work-item binding and tenant-scoped goal lookups.L4 GoalAncestry now renders. Proven through the production path —
test_a_bound_session_renders_the_goal_ancestry_blockstarts from the binding and asserts## Goal Ancestryreaches a captured prompt, rather than injecting a chain into the builder (which only ever provedLayer4GoalAncestryrenders, and that was never in doubt).Correction to this issue's premise
The body said "Only the connection is missing." That was wrong twice over, and #13696 removed the wiring rather than ship it:
work_item_idreached the prompt path only fromapi/chat.py:1479— a raw client JSON bag — and nothing server-side ever set it. L4 would have stayed dark regardless of the call-site fix.company_id.The producer and consumer both existed; what was missing was a safe carrier between them. That is why this took a prerequisite issue rather than a one-line change.
Full closure evidence is on #13704.
wip(memory): A/B harness for the tiered context stack (#13689)
test(memory): run the #5066 A/B with all five layers live and record …
wip(memory): A/B harness for the tiered context stack (#13689)
test(memory): run the #5066 A/B with all five layers live and record …
wip(memory): A/B harness for the tiered context stack (#13689)
test(memory): run the #5066 A/B with all five layers live and record …
feat(memory): run the #5066 A/B, record the result, and enable the ti…