Skip to content

Commit 904252d

Browse files
committed
feat: merge feat/tenure-borrowed-beliefs into develop
2 parents 817c6ff + d082628 commit 904252d

16 files changed

Lines changed: 647 additions & 3 deletions

app/Domain/Memory/Actions/ExtractAndStoreMemoriesAction.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
use App\Domain\Agent\Models\Agent;
66
use App\Domain\Agent\Models\AgentExecution;
77
use App\Domain\Experiment\Models\Experiment;
8+
use App\Domain\Memory\Enums\MemoryBeliefStatus;
9+
use App\Domain\Memory\Enums\MemoryBeliefType;
810
use App\Domain\Memory\Enums\MemoryCategory;
11+
use App\Domain\Memory\Enums\MemoryPreferenceSubtype;
912
use App\Domain\Memory\Enums\MemoryTier;
1013
use App\Domain\Shared\Models\Team;
1114
use App\Infrastructure\AI\Contracts\AiGatewayInterface;
@@ -45,6 +48,10 @@ class ExtractAndStoreMemoriesAction
4548
"fact": "concise, durable statement",
4649
"confidence": 0.85,
4750
"category": "knowledge",
51+
"belief_type": "preference",
52+
"preference_subtype": "style",
53+
"why_it_matters": "actionable directive: how a future run should behave because of this fact",
54+
"domain": "domain:code",
4855
"tags": ["capability"]
4956
}
5057
]
@@ -57,6 +64,20 @@ class ExtractAndStoreMemoriesAction
5764
- context: situational context, recent events, current state
5865
- behavior: working patterns, process preferences, habits
5966
- goal: objectives, targets, desired outcomes
67+
belief_type must be exactly one of: preference, decision, entity, relation, open_question
68+
- preference: how the user/agent works and communicates
69+
- decision: a commitment future runs must respect
70+
- entity: a named thing in the user's world (a service, team, system)
71+
- relation: a connection between entities (a dependency, ownership)
72+
- open_question: something being worked through, not yet decided
73+
preference_subtype is OPTIONAL and only valid when belief_type is "preference":
74+
- expertise: depth calibration — how much to explain
75+
- style: communication tone, format, voice
76+
why_it_matters: a single actionable directive, not a restatement of the fact.
77+
Write "shapes all code examples toward TypeScript strict mode" — not "uses TypeScript".
78+
domain: a scope tag so the belief only surfaces in matching sessions. Use
79+
"domain:code", "domain:writing", "domain:ops", etc., or "user:universal" when
80+
the fact applies everywhere. Omit if unsure.
6081
Tags must be one or more of: capability, constraint, preference, pattern, domain, tooling
6182
PROMPT;
6283

@@ -145,6 +166,19 @@ public function execute(string $agentId, string $teamId, string $executionId, ?s
145166
$categoryValue = $item['category'] ?? null;
146167
$category = $categoryValue ? MemoryCategory::tryFrom($categoryValue) : null;
147168

169+
$beliefType = isset($item['belief_type']) && is_string($item['belief_type'])
170+
? MemoryBeliefType::tryFrom($item['belief_type'])
171+
: null;
172+
$preferenceSubtype = isset($item['preference_subtype']) && is_string($item['preference_subtype'])
173+
? MemoryPreferenceSubtype::tryFrom($item['preference_subtype'])
174+
: null;
175+
$whyItMatters = isset($item['why_it_matters']) && is_string($item['why_it_matters'])
176+
? trim($item['why_it_matters'])
177+
: null;
178+
$domain = isset($item['domain']) && is_string($item['domain']) && $item['domain'] !== ''
179+
? $item['domain']
180+
: null;
181+
148182
if ($fact === '' || $confidence < self::MIN_CONFIDENCE) {
149183
continue;
150184
}
@@ -162,6 +196,13 @@ public function execute(string $agentId, string $teamId, string $executionId, ?s
162196
tier: MemoryTier::Proposed,
163197
proposedBy: "agent:{$agentId}",
164198
category: $category,
199+
beliefType: $beliefType,
200+
preferenceSubtype: $preferenceSubtype,
201+
whyItMatters: $whyItMatters !== '' ? $whyItMatters : null,
202+
// Extracted facts are derived, not explicitly stated — they
203+
// await confirmation before becoming active beliefs.
204+
beliefStatus: MemoryBeliefStatus::Inferred,
205+
domain: $domain,
165206
);
166207

167208
$stored++;

app/Domain/Memory/Actions/RetrieveRelevantMemoriesAction.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public function execute(
3333
float $minConfidence = 0.3,
3434
?array $tags = null,
3535
?string $topic = null,
36+
?string $domain = null,
3637
): Collection {
3738
if (! config('memory.enabled', true)) {
3839
return collect();
@@ -73,6 +74,8 @@ public function execute(
7374
->where('confidence', '>=', $minConfidence)
7475
// Exclude rejected proposals — keep NULL (legacy) and approved.
7576
->where(fn ($q) => $q->whereNull('proposal_status')->orWhere('proposal_status', '!=', 'rejected'))
77+
// Superseded beliefs are retained for audit but never injected.
78+
->where(fn ($q) => $q->whereNull('belief_status')->orWhere('belief_status', '!=', 'superseded'))
7679
->orderByDesc('composite_score');
7780

7881
// Topic namespace pre-filter: narrows the candidate set before the pgvector scan.
@@ -81,6 +84,13 @@ public function execute(
8184
$builder->where('topic', $topic);
8285
}
8386

87+
// Domain scope: a hard filter applied after scoring. A belief scoped
88+
// to one domain (e.g. domain:code) never surfaces in another domain's
89+
// session. NULL-domain beliefs are universal and always eligible.
90+
if ($domain !== null) {
91+
$builder->where(fn ($q) => $q->where('domain', $domain)->orWhereNull('domain'));
92+
}
93+
8494
// Tag-based filtering (opt-in: only applied when tags are passed)
8595
if (! empty($tags)) {
8696
$this->applyTagFilter($builder, $tags);

app/Domain/Memory/Actions/StoreMemoryAction.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace App\Domain\Memory\Actions;
44

5+
use App\Domain\Memory\Enums\MemoryBeliefStatus;
6+
use App\Domain\Memory\Enums\MemoryBeliefType;
57
use App\Domain\Memory\Enums\MemoryCategory;
8+
use App\Domain\Memory\Enums\MemoryPreferenceSubtype;
69
use App\Domain\Memory\Enums\MemoryTier;
710
use App\Domain\Memory\Enums\MemoryVisibility;
811
use App\Domain\Memory\Enums\WriteGateDecision;
@@ -55,6 +58,11 @@ public function execute(
5558
?MemoryCategory $category = null,
5659
?string $topic = null,
5760
?string $documentContext = null,
61+
?MemoryBeliefType $beliefType = null,
62+
?MemoryPreferenceSubtype $preferenceSubtype = null,
63+
?string $whyItMatters = null,
64+
MemoryBeliefStatus $beliefStatus = MemoryBeliefStatus::Active,
65+
?string $domain = null,
5866
): array {
5967
if (! config('memory.enabled', true)) {
6068
return [];
@@ -76,6 +84,7 @@ public function execute(
7684
$teamId, $agentId, $chunk, $sourceType,
7785
$projectId, $sourceId, $metadata, $confidence,
7886
$importance, $tags, $visibility, $tier, $proposedBy, $category, $topic,
87+
$beliefType, $preferenceSubtype, $whyItMatters, $beliefStatus, $domain,
7988
);
8089

8190
if ($memory) {
@@ -121,6 +130,11 @@ private function storeChunk(
121130
?string $proposedBy = null,
122131
?MemoryCategory $category = null,
123132
?string $topic = null,
133+
?MemoryBeliefType $beliefType = null,
134+
?MemoryPreferenceSubtype $preferenceSubtype = null,
135+
?string $whyItMatters = null,
136+
MemoryBeliefStatus $beliefStatus = MemoryBeliefStatus::Active,
137+
?string $domain = null,
124138
): ?Memory {
125139
$contentHash = hash('sha256', mb_strtolower(trim($chunk)));
126140
$embedding = $this->generateEmbedding($chunk, $teamId);
@@ -150,6 +164,7 @@ private function storeChunk(
150164
$teamId, $agentId, $chunk, $embedding, $contentHash,
151165
$sourceType, $projectId, $sourceId, $metadata,
152166
$confidence, $importance, $tags, $visibility, $tier, $proposedBy, $category, $topic,
167+
$beliefType, $preferenceSubtype, $whyItMatters, $beliefStatus, $domain,
153168
),
154169
};
155170
}
@@ -297,7 +312,17 @@ private function handleAdd(
297312
?string $proposedBy = null,
298313
?MemoryCategory $category = null,
299314
?string $topic = null,
315+
?MemoryBeliefType $beliefType = null,
316+
?MemoryPreferenceSubtype $preferenceSubtype = null,
317+
?string $whyItMatters = null,
318+
MemoryBeliefStatus $beliefStatus = MemoryBeliefStatus::Active,
319+
?string $domain = null,
300320
): Memory {
321+
// A preference subtype only applies to Preference beliefs — drop it otherwise.
322+
if ($beliefType?->acceptsPreferenceSubtype() !== true) {
323+
$preferenceSubtype = null;
324+
}
325+
301326
return Memory::create([
302327
'team_id' => $teamId,
303328
'agent_id' => $agentId,
@@ -316,6 +341,11 @@ private function handleAdd(
316341
'category' => $category,
317342
'topic' => $topic,
318343
'proposed_by' => $proposedBy,
344+
'belief_type' => $beliefType,
345+
'preference_subtype' => $preferenceSubtype,
346+
'why_it_matters' => $whyItMatters,
347+
'belief_status' => $beliefStatus,
348+
'domain' => $domain,
319349
]);
320350
}
321351

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace App\Domain\Memory\Enums;
4+
5+
/**
6+
* Belief lifecycle status (Tenure-inspired).
7+
*
8+
* Runs parallel to the existing tier/proposal_status workflow. Where
9+
* proposal_status tracks human review of agent-proposed memories, belief
10+
* status tracks the epistemic confidence in the fact itself.
11+
*
12+
* active → explicitly stated or decided
13+
* inferred → derived without an explicit statement; awaits confirmation
14+
* exploratory → being considered but not committed
15+
* superseded → replaced by a newer belief; retained for audit, never injected
16+
*/
17+
enum MemoryBeliefStatus: string
18+
{
19+
case Active = 'active';
20+
case Inferred = 'inferred';
21+
case Exploratory = 'exploratory';
22+
case Superseded = 'superseded';
23+
24+
/** Whether a belief in this status may be injected into agent context. */
25+
public function isInjectable(): bool
26+
{
27+
return $this !== self::Superseded;
28+
}
29+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace App\Domain\Memory\Enums;
4+
5+
/**
6+
* Structured belief taxonomy (Tenure-inspired).
7+
*
8+
* Sits alongside the existing {@see MemoryCategory} taxonomy. Where category
9+
* describes the memory hall a record lives in, belief type describes the
10+
* epistemic shape of the fact: is it a preference, a committed decision, a
11+
* named thing, a link between things, or an unresolved question.
12+
*
13+
* preference → how the user/agent works and communicates
14+
* decision → a commitment future runs must respect
15+
* entity → a named thing in the user's world
16+
* relation → a connection between entities
17+
* open_question → something actively being worked through, not yet decided
18+
*/
19+
enum MemoryBeliefType: string
20+
{
21+
case Preference = 'preference';
22+
case Decision = 'decision';
23+
case Entity = 'entity';
24+
case Relation = 'relation';
25+
case OpenQuestion = 'open_question';
26+
27+
/** Whether this belief type accepts a {@see MemoryPreferenceSubtype}. */
28+
public function acceptsPreferenceSubtype(): bool
29+
{
30+
return $this === self::Preference;
31+
}
32+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace App\Domain\Memory\Enums;
4+
5+
/**
6+
* Optional subtype for {@see MemoryBeliefType::Preference} beliefs.
7+
*
8+
* expertise → depth calibration: how much the agent should explain
9+
* style → communication patterns: tone, format, voice
10+
*/
11+
enum MemoryPreferenceSubtype: string
12+
{
13+
case Expertise = 'expertise';
14+
case Style = 'style';
15+
}

app/Domain/Memory/Models/Memory.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
namespace App\Domain\Memory\Models;
44

55
use App\Domain\Agent\Models\Agent;
6+
use App\Domain\Memory\Enums\MemoryBeliefStatus;
7+
use App\Domain\Memory\Enums\MemoryBeliefType;
68
use App\Domain\Memory\Enums\MemoryCategory;
9+
use App\Domain\Memory\Enums\MemoryPreferenceSubtype;
710
use App\Domain\Memory\Enums\MemoryTier;
811
use App\Domain\Memory\Enums\MemoryVisibility;
912
use App\Domain\Project\Models\Project;
@@ -36,6 +39,11 @@ class Memory extends Model
3639
'tags',
3740
'tier',
3841
'category',
42+
'belief_type',
43+
'preference_subtype',
44+
'why_it_matters',
45+
'belief_status',
46+
'domain',
3947
'topic',
4048
'proposed_by',
4149
'proposal_status',
@@ -61,6 +69,9 @@ protected function casts(): array
6169
'visibility' => MemoryVisibility::class,
6270
'tier' => MemoryTier::class,
6371
'category' => MemoryCategory::class,
72+
'belief_type' => MemoryBeliefType::class,
73+
'preference_subtype' => MemoryPreferenceSubtype::class,
74+
'belief_status' => MemoryBeliefStatus::class,
6475
];
6576
}
6677

app/Http/Controllers/Api/V1/MemoryController.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
use App\Domain\Memory\Actions\RetrieveRelevantMemoriesAction;
66
use App\Domain\Memory\Actions\StoreMemoryAction;
7+
use App\Domain\Memory\Enums\MemoryBeliefStatus;
8+
use App\Domain\Memory\Enums\MemoryBeliefType;
9+
use App\Domain\Memory\Enums\MemoryPreferenceSubtype;
710
use App\Domain\Memory\Models\Memory;
811
use App\Http\Controllers\Controller;
912
use App\Http\Resources\Api\V1\MemoryResource;
@@ -25,6 +28,9 @@ public function index(Request $request): AnonymousResourceCollection
2528
AllowedFilter::exact('agent_id'),
2629
AllowedFilter::exact('project_id'),
2730
AllowedFilter::exact('source_type'),
31+
AllowedFilter::exact('belief_type'),
32+
AllowedFilter::exact('belief_status'),
33+
AllowedFilter::exact('domain'),
2834
AllowedFilter::partial('content'),
2935
)
3036
->allowedSorts('created_at', 'confidence')
@@ -51,6 +57,7 @@ public function search(Request $request, RetrieveRelevantMemoriesAction $action)
5157
'top_k' => ['sometimes', 'integer', 'min:1', 'max:50'],
5258
'threshold' => ['sometimes', 'numeric', 'min:0', 'max:1'],
5359
'scope' => ['sometimes', 'in:agent,team,project'],
60+
'domain' => ['sometimes', 'nullable', 'string', 'max:64'],
5461
]);
5562

5663
$memories = $action->execute(
@@ -61,6 +68,7 @@ public function search(Request $request, RetrieveRelevantMemoriesAction $action)
6168
threshold: $request->input('threshold'),
6269
scope: $request->input('scope', $request->has('agent_id') ? 'agent' : 'team'),
6370
teamId: $request->user()->current_team_id,
71+
domain: $request->input('domain'),
6472
);
6573

6674
return response()->json([
@@ -108,6 +116,11 @@ public function store(Request $request, StoreMemoryAction $action): JsonResponse
108116
'metadata' => ['sometimes', 'array'],
109117
'tags' => ['sometimes', 'array'],
110118
'confidence' => ['sometimes', 'numeric', 'min:0', 'max:1'],
119+
'belief_type' => ['sometimes', 'nullable', 'string', 'in:preference,decision,entity,relation,open_question'],
120+
'preference_subtype' => ['sometimes', 'nullable', 'string', 'in:expertise,style'],
121+
'why_it_matters' => ['sometimes', 'nullable', 'string', 'max:2000'],
122+
'belief_status' => ['sometimes', 'string', 'in:active,inferred,exploratory,superseded'],
123+
'domain' => ['sometimes', 'nullable', 'string', 'max:64'],
111124
]);
112125

113126
$memories = $action->execute(
@@ -120,6 +133,11 @@ public function store(Request $request, StoreMemoryAction $action): JsonResponse
120133
metadata: $request->input('metadata', []),
121134
confidence: (float) $request->input('confidence', 1.0),
122135
tags: $request->input('tags', []),
136+
beliefType: MemoryBeliefType::tryFrom((string) $request->input('belief_type')),
137+
preferenceSubtype: MemoryPreferenceSubtype::tryFrom((string) $request->input('preference_subtype')),
138+
whyItMatters: $request->input('why_it_matters'),
139+
beliefStatus: MemoryBeliefStatus::tryFrom((string) $request->input('belief_status', 'active')) ?? MemoryBeliefStatus::Active,
140+
domain: $request->input('domain'),
123141
);
124142

125143
return response()->json([

app/Http/Resources/Api/V1/MemoryResource.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ public function toArray(Request $request): array
1919
'metadata' => $this->metadata,
2020
'tags' => $this->tags,
2121
'confidence' => $this->confidence,
22+
'belief_type' => $this->belief_type?->value,
23+
'preference_subtype' => $this->preference_subtype?->value,
24+
'why_it_matters' => $this->why_it_matters,
25+
'belief_status' => $this->belief_status?->value,
26+
'domain' => $this->domain,
2227
'created_at' => $this->created_at->toISOString(),
2328
'updated_at' => $this->updated_at->toISOString(),
2429
];

0 commit comments

Comments
 (0)