Commit graph

26 commits

Author SHA1 Message Date
Snider
6da45637f5 feat(brain): extend /v1/brain/recall with org + keywords + boost_keywords
Hybrid recall: vector (Qdrant) + keyword (ES) with dedupe-by-id and
score re-ranking via configurable boost multiplier.

- org: string → adds to Qdrant must-filter + ES filter
- keywords: array<string> → when present, ES full-text hits merge into
  results; without keywords, path stays purely vector
- boost_keywords: array<string> → each matched boost-keyword amplifies
  the memory's score by mcp.brain.boost_keywords_multiplier (default 1.5)

BrainService gains a hybridRecall() helper; BrainController::recall()
delegates to it. Existing request fields (query, limit, workspace_id,
project, type) unchanged.

php/tests/Feature/Api/BrainRecallExtendedTest.php — Pest coverage with
Http::fake for both Qdrant + ES, asserting dedupe + boost behaviour.

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=63
2026-04-23 13:51:37 +01:00
Snider
34525038a8 feat(brain): add brain:prune artisan command
New artisan command brain:prune {--older-than=90} {--chunk=100} {--dry-run}
that completes the soft-delete → hard-delete lifecycle by:
1. selecting BrainMemory::onlyTrashed() where deleted_at < now - N days
2. dispatching DeleteFromIndex for each (Qdrant + ES cleanup)
3. forceDelete()'ing the rows

--dry-run counts without dispatching.

Complements brain:clean (which cleans recent soft-deletes) with a
retention-bounded terminal cleanup.

Pest coverage: Good (dispatch + forceDelete on aged trashed rows), Bad
(invalid chunk), Ugly (--dry-run skips both dispatch and delete).

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=62
2026-04-23 13:36:41 +01:00
Snider
638091f3b3 feat(brain): add brain:clean artisan command
New artisan command brain:clean {--chunk=100} {--dry-run} that dispatches
the DeleteFromIndex job for soft-deleted BrainMemory rows (those in
onlyTrashed scope). Cleans up orphaned Qdrant + Elasticsearch index
entries that remain after a memory is soft-deleted.

--dry-run counts without dispatching.

php/tests/Feature/Console/BrainCleanCommandTest.php covers Good
(dispatches on trashed), Bad (invalid chunk), Ugly (--dry-run prevents
dispatch).

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=61
2026-04-23 13:30:27 +01:00
Snider
677d890308 feat(brain): make BrainService::remember() async via EmbedMemory job
remember() now writes the brain_memories row with indexed_at=null and
dispatches EmbedMemory::dispatch($memory->id) for async Qdrant + ES
indexing, instead of calling qdrantUpsert() synchronously. Response shape
matches the row state — caller gets the memory immediately, the Job
flips indexed_at once the Qdrant write succeeds.

Superseded rows still soft-delete synchronously (part of the remember
contract, not the indexing path).

php/tests/Feature/Services/BrainServiceRememberTest.php uses Queue::fake()
to assert EmbedMemory is dispatched and BrainService::qdrantUpsert() is
NOT called directly (subclass probe).

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=55
2026-04-23 13:29:30 +01:00
Snider
47f241d880 feat(brain): add brain:reindex artisan command
New artisan command brain:reindex {--all} {--chunk=100} that dispatches
the EmbedMemory job for brain memories needing (re)indexing. Without
--all, only memories where indexed_at IS NULL are dispatched; --all
re-embeds every memory (useful after a Qdrant collection wipe or
embedding model change). Uses chunkById for memory-safe iteration at
scale.

php/tests/Feature/Console/BrainReindexCommandTest.php covers Good
(unindexed-only default), Bad (invalid chunk), Ugly (--all flag).

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=60
2026-04-23 13:29:18 +01:00
Snider
f223a77daa feat(brain): add Elasticsearch indexing to BrainService
Fills in the elasticIndex/elasticDelete stubs added by #56 and #57, plus a
new elasticSearch() method used by the upcoming /v1/brain/search endpoint
(#64).

- elasticIndex(BrainMemory) → PUT /brain_memories/_doc/{id}
- elasticDelete(string $id) → DELETE /brain_memories/_doc/{id}
- elasticSearch(string $query, array $filters) → POST /brain_memories/_search
- ES URL default http://127.0.0.1:9200 (config override via
  BRAIN_ELASTICSEARCH_URL env var)
- RuntimeException on HTTP failures (same pattern as qdrantUpsert)

php/tests/Feature/Services/BrainServiceElasticTest.php covers Good/Bad/Ugly
for index, delete, and search using Http::fake.

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=59
2026-04-23 13:11:29 +01:00
Snider
8d520adb5e feat(brain): add DeleteFromIndex job
Inverse of the EmbedMemory job (#56): removes a memory from Qdrant (and
the future Elasticsearch index) when brain_forget fires or a memory is
soft-deleted.

- php/Jobs/DeleteFromIndex.php — Laravel Job, 3 retries with backoff
- BrainService: qdrantDelete() private→public and now throws on HTTP
  failure (was silent Log::warning — wouldn't trigger Job retry)
- elasticDelete() stub added (fills in with the ES integration ticket)
- php/tests/Feature/Jobs/DeleteFromIndexTest.php — success + HTTP-failure
  paths via mocked Http

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=57
2026-04-23 12:55:45 +01:00
Snider
4dc5ed8d14 feat(brain): add EmbedMemory job + indexed_at tracking
Implements the async-embedding pipeline's worker side:

- php/Jobs/EmbedMemory.php — Laravel Job that calls BrainService::embed()
  + qdrantUpsert() and sets indexed_at on success
- php/Migrations/…_add_indexed_at_to_brain_memories.php — nullable
  timestamp + index, portable across pgsql/mariadb (hasColumn guard)
- BrainMemory: +indexed_at fillable + datetime cast + PHPDoc
- BrainService: qdrantUpsert() private→public so the Job can use it;
  elasticIndex() stub added (to be implemented by the ES ticket)
- php/tests/Feature/Jobs/EmbedMemoryTest.php — Pest tests for success
  path and Qdrant-failure path

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=56
2026-04-23 12:47:10 +01:00
Snider
bd060c8aa0 feat(brain): add org filter to BrainService::buildQdrantFilter()
Adds an `org` match filter between workspace_id and project in the Qdrant
payload filter chain. Multi-org isolation for OpenBrain memory retrieval.

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=58
2026-04-23 12:32:58 +01:00
e58986a3b4 revert fcb9c189e5
revert fix(agentic): harden TODO workspace write

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-23 12:32:57 +01:00
Codex
cbc262add4 fix(agentic): harden TODO workspace write
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-23 12:32:57 +01:00
Snider
ccedf536d6 fix(agent-tool-registry): harden rate limiting and api key identifiers
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:48:29 +01:00
Snider
1e8af462f2 fix(agentic): harden tool execution and template validation
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:25:19 +01:00
Snider
39914fbf14 refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.

Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:00 +01:00
Virgil
425008f855 feat(brain): expose supersession metadata
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-02 07:53:58 +00:00
Virgil
c4f5b77786 feat(agentic): add canonical generate command
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-02 04:54:20 +00:00
Virgil
ff24898cd4 feat(session): persist handoff notes on end
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-02 04:03:12 +00:00
Virgil
6cb5a9f39a feat(agentic): add workspace state aliases
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-02 03:59:30 +00:00
Virgil
c8a2d62d27 feat(brain): recurse seed-memory scans
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-02 03:55:45 +00:00
Virgil
886461ca28 feat(session): expose replay context on read scope
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-02 02:19:57 +00:00
Virgil
1f333fc53d fix(agent-session): preserve handed-off sessions
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-01 21:53:19 +00:00
Virgil
036c09c235 feat(php-agent): stream fleet events continuously
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-01 21:45:39 +00:00
Virgil
47bc3dc0fa feat(agentic): add task update and toggle console actions
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-01 12:55:04 +00:00
Virgil
25ee288bd2 fix(agentic): align php state and fleet runtime
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-31 08:08:04 +00:00
Snider
bb88604045 feat(core): wire Core framework into agentic + monitor subsystems
Phase 2 of Core DI migration:
- Add *core.Core field + SetCore() to PrepSubsystem and monitor.Subsystem
- Register agentic/monitor/brain as Core services with lifecycle hooks
- Mark SetCompletionNotifier and SetNotifier as deprecated (removed in Phase 3)
- Fix monitor test to match actual event names
- initServices() now wires Core refs before legacy callbacks

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 14:44:53 +00:00
Snider
be1130f470 agent updates 2026-03-21 11:10:44 +00:00