Adds a private http() helper that wraps Http::timeout() with
conditional withoutVerifying() for self-signed .lan certs behind
Traefik. Boot singleton auto-detects .lan URLs at construction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `*/` in `projects/*/memory/` was closing the docblock comment
early, causing PHP to see `for` as a keyword on the same line.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ollama is behind Traefik reverse proxy on ollama.lan, so no port
needed and TLS should be enforced by default.
Co-Authored-By: Virgil <virgil@lethean.io>
Make embedding model swappable via BRAIN_EMBEDDING_MODEL env var.
Switch default from nomic-embed-text to embeddinggemma (Gemma 3
based, 2x better cluster separation in benchmarks).
Default Ollama URL now points to ollama.lan (Linux homelab GPU).
Co-Authored-By: Virgil <virgil@lethean.io>
Scans ~/.claude/projects/*/memory/ for MEMORY.md and topic markdown
files, parses sections, infers memory types, and imports into
OpenBrain via BrainService::remember().
Supports --dry-run, --workspace, --agent, and --path options.
Co-Authored-By: Virgil <virgil@lethean.io>
- Move BrainMemory::create() inside BrainService::remember() for
full atomicity (DB + Qdrant in single transaction)
- Add forWorkspace() scope to recall() MariaDB query (tenant isolation)
- Wrap forget() in DB::transaction (MariaDB first, then Qdrant)
- Check qdrantDelete() response and log warnings on failure
- Validate embed() response is a non-empty array
Co-Authored-By: Virgil <virgil@lethean.io>
UUID-keyed brain_memories table with workspace scoping, self-referential
supersession chain, TTL expiry, and confidence scoring. Eloquent model
includes all scopes and helpers needed by the MCP tool layer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shared vector-indexed knowledge store accessible by all agents via MCP.
MariaDB for relational metadata, Qdrant for semantic search, Ollama for
embeddings. Four MCP tools: brain_remember, brain_recall, brain_forget,
brain_list. Replaces scattered MEMORY.md files with singular state.
Co-Authored-By: Virgil <virgil@lethean.io>
Replace github.server_url/GITHUB_REF_NAME with explicit forge URL
and GITEA_REF_NAME/GITEA_OUTPUT.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On tag push (v*), zips the package and publishes to the
forge.lthn.ai Composer package registry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Delete Models/AgentWorkspaceState.php (legacy port, no backing table)
- Rewrite Models/WorkspaceState.php as the single canonical state model
backed by agent_workspace_states table with array value cast,
type helpers, scopeForPlan/scopeOfType, static getValue/setValue,
and toMcpContext() for MCP tool output
- Update AgentPlan::states() relation and setState() return type
- Update StateSet MCP tool import
- Update SecurityTest to use WorkspaceState
- Add WorkspaceStateTest covering table, casts, type helpers, scopes,
static helpers, toMcpContext, and AgentPlan integration
- Mark CQ-001 done in TODO.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Snapshots YAML template content in a new `plan_template_versions` table
whenever a plan is created from a template. Plans reference their version
via `template_version_id` so existing plans are unaffected by future
template file edits.
Key changes:
- Migration 0006: create `plan_template_versions` table (slug, version,
name, content JSON, content_hash SHA-256); add nullable FK
`template_version_id` to `agent_plans`
- Model `PlanTemplateVersion`: `findOrCreateFromTemplate()` deduplicates
identical content by hash; `historyFor()` returns versions newest-first
- `AgentPlan`: add `template_version_id` fillable and `templateVersion()`
relationship
- `PlanTemplateService::createPlan()`: snapshot raw template before
variable substitution; store version id and version number in metadata;
add `getVersionHistory()` and `getVersion()` public methods
- Tests: `TemplateVersionManagementTest` covering model behaviour, plan
creation snapshotting, deduplication, history ordering, and service
methods
Closes#35
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Audited all PHP files for US English spellings per CLAUDE.md convention.
Fixed "Organize" → "Organise" in Mcp/Servers/Marketing.php docstring.
CSS/JS identifiers (borderColor, backgroundColor, transition-colors) and
array keys that form interface contracts with the host-uk/core package are
unchanged as they are not prose.
Closes#36
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `agentic.plan_retention_days` config (default 90 days via AGENTIC_PLAN_RETENTION_DAYS env)
- Add SoftDeletes and `archived_at` timestamp to AgentPlan model
- Add migration for `deleted_at` and `archived_at` columns on agent_plans
- Create `agentic:plan-cleanup` command with --dry-run and --days options
- Schedule retention cleanup to run daily via service provider
- Register PlanRetentionCommand in ConsoleBooting handler
- Add PlanRetentionTest feature test suite covering all retention scenarios
- Fix archive() to store archived_at as dedicated column (not metadata string)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace setup-php action with pre-built container.
Eliminates ~50s setup overhead per matrix job.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace anonymous class extending ApiKey with Mockery mock to avoid
requiring php-api package at load time
- Replace with() named args with withSomeOfArgs() for Mockery compat
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- interpolateVariables: use string concatenation for triple-brace
placeholders instead of PHP string interpolation which only
produces single braces
- AgentToolRegistryTest: replace Cache::fake() (not available) with
Cache::flush() since array driver is already in-memory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add README to Mcp/Tools/Agent/ explaining:
- How ToolDependency works (contextExists, sessionState, entityExists)
- Context requirements (workspace_id, session_id) and multi-tenant safety
- Step-by-step guide for creating new tools
- AgentTool base class property and method reference
- Dependency resolution order and recommended declaration sequence
- Troubleshooting guide for common dependency errors
Closes#32
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document each PROVIDER_PATTERNS entry with real User-Agent examples,
add inline comments to BROWSER_INDICATORS and NON_AGENT_BOTS with
categorised UA examples, document MCP_TOKEN_HEADER with token format
details, and add class-level usage examples and detection priority
ordering.
Closes#31
Refs: DOC-001
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProcessContentTaskTest: set mock properties directly instead of
shouldReceive('__get') which doesn't reliably intercept property
access on Mockery mocks of non-existent classes.
HasStreamParsing: fix parseJSONStream chunked read bug where the
inner parse loop restarted at position 0 with stale state from
a previous partial parse. Track scan position across chunks.
AgentDetection: fix Postman regex \bPostman\b → \bPostman/ so it
matches PostmanRuntime (no word boundary between n and R).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enhance `validateVariables()` in `PlanTemplateService` to produce
actionable errors instead of the generic "Required variable '...' is missing".
Changes:
- Extracted `buildVariableError()` helper that composes the message from
the variable's `description`, `format`, `example`, and `examples` fields
- Added `naming_convention` key to the returned array so callers have
a constant reminder that variable names use snake_case
- Added a `NAMING_CONVENTION` private const to avoid string duplication
Tests (6 new cases in `PlanTemplateServiceTest`):
- description included in error message
- single `example` value included
- `examples` list (first two) included
- `format` hint included alongside example
- `naming_convention` present in both valid and invalid results
- bare variable (no description) still produces useful "missing" message
Closes#30
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Log a warning for each AI provider registered without an API key so
that misconfiguration is surfaced at boot time (not silently on the
first API call). Each message names the environment variable to set:
ANTHROPIC_API_KEY – Claude
GOOGLE_AI_API_KEY – Gemini
OPENAI_API_KEY – OpenAI
Providers without a key remain registered but are marked unavailable
via isAvailable(), preserving backward compatibility.
- Add Log::warning() calls in registerProviders() for empty keys
- Extend AgenticManagerTest with a dedicated 'API key validation
warnings' describe block (7 new test cases)
- Update DX-002 in TODO.md as resolved
Closes#29
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updated workspace_id error messages in all MCP tools to include
actionable guidance and a documentation link. Affected tools:
PlanCreate, PlanGet, PlanList, StateSet, StateGet, StateList,
SessionStart.
Resolves DX-001 from TODO.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Track progress in a per-batch JSON state file after each article so
a mid-run crash leaves a recoverable checkpoint
- Add `maxRetries` parameter to generateBatch() with per-article retry
loop (default: 1 extra attempt)
- Add `resumeBatch()` to re-process only failed/pending articles,
skipping those already successfully generated in a prior run
- Add `loadBatchProgress()` public method for inspecting state
- State stores per-article status, attempt counts, error messages,
and timestamps for full observability
Tests: 6 new scenarios covering state persistence, resume capability,
retry logic, and the no-state error case
Closes#27
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap stream() in try/catch to prevent silent failures.
On exception, log the error and yield a structured error event:
['type' => 'error', 'message' => string]
Adds tests for connection errors, runtime exceptions, error event
format, and Log::error invocation. Closes ERR-001 in TODO.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cache the list of permitted tool names in `AgentToolRegistry::forApiKey()`
using a 1-hour TTL to avoid O(n) filtering on every request (PERF-002).
- Add `Cache::remember()` in `forApiKey()` storing tool names keyed by API
key ID (`agent_tool_registry:api_key:{id}`)
- Add `flushCacheForApiKey(int|string $id)` for explicit invalidation
- Add `CACHE_TTL` constant (3600 s) for easy tuning
- Invalidate cache in `AgentApiKeyService::updatePermissions()` and `revoke()`
so permission changes take effect immediately
- Add `tests/Unit/AgentToolRegistryTest.php` covering cache hit/miss,
per-key isolation, scope filtering, TTL constant, and flush behaviour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>