Commit graph

571 commits

Author SHA1 Message Date
Snider
084e69357f feat(agent/content): SEO Natural Progression scheduler with Googlebot trigger (#543)
Implements pkg/agentic/content_seo.go: SEORevision, ScheduleRevision,
GetPendingRevisions, OnGooglebotVisit, Gin middleware hook, and
content_seo_schedule MCP tool. ScheduledAt assigned random 8-62m window
on first Googlebot hit. Persistence via .core/db.duckdb seo_revisions
group.

AX-10 coverage in content_seo_test.go (Good/Bad/Ugly + MCP tool registration).

Closes tasks.lthn.sh/view.php?id=543

Co-authored-by: Codex <noreply@openai.com>
2026-04-26 00:17:01 +01:00
Snider
7a9dbadb57 feat(agent/fleet): core login CODE + fleet connect/poll/heartbeat (#539)
Per RFC §9 Fleet Mode: device pairing + SSE-with-poll-fallback +
heartbeat + status reporting now wired.

Lands:
* pkg/agentic/fleet_login.go — `core login CODE` POSTs /v1/device/pair
  with the 6-digit code; writes {agent_api_key, agent_id, expires_at}
  to ~/.core/agent.key (mode 0600). Errors clean (no panic) on invalid
  code / network fail.
* pkg/agentic/fleet_connect.go — Connect(ctx) opens SSE to
  /v1/fleet/events with Bearer auth; reconnect backoff 1s→2s→4s→8s→
  16s→30s. PollFallback via /v1/fleet/task/next every 30s when SSE
  keeps failing. Heartbeat goroutine POSTs /v1/fleet/heartbeat every
  60s with {agent_id, status}. Persists last-known fleet snapshot to
  ~/.core/fleet.status.json so fleet/status survives restart.
* pkg/agentic/fleet_mode.go — `core fleet` top-level + `fleet/nodes`
  (lists registered nodes) + `fleet/status` (connection state, last
  heartbeat, last task). All exit cleanly on API-unreachable.
* commands.go — registerFleetCommands wired into registerCommands.
* AX-10 tests + CLI Taskfiles for login + nodes (unreachable-API
  asserted clean-exit, no panic).

Sandbox blocked from go test by go.work + private-module-graph
(pre-existing); gofmt clean.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=539
2026-04-26 00:13:19 +01:00
Snider
c6415aa53a feat(agent/state): OnStartup queue+registry restore from .core/db.duckdb (#537)
Per RFC §15.3: restart was losing in-flight queue + workspace registry.
"Ghost agents" and "lost queue" pain now fixed.

Lands:
* pkg/agentic/persist.go (NEW):
  - OnStartup(ctx, c): opens .core/db.duckdb via go-store, restores
    registry/queue/concurrency groups
  - Dead-PID detection: registry entries with status=running but
    !pidAlive(PID) → marked failed with question="dead worker on
    restart"; status.json files re-written to disk
  - Orphaned workspace cleanup: walk .core/workspace/, dir-exists +
    registry-says-completed → delete
  - OnShutdown(ctx): flushes in-memory registry + queue back to store
    before close
* pkg/agentic/prep.go — PrepSubsystem.OnStartup/OnShutdown wired
* pkg/agentic/persist_test.go — AX-10 covering queue restore,
  dead-worker reaping, shutdown persistence, invalid-store-payload,
  orphan cleanup
* tests/cli/restart/Taskfile.yaml — extended smoke seeds DuckDB state
  for queued workspace + dead running worker, asserts status.json
  reflects restore correctly

Sandbox blocked from go test by go.work conflicting dappco.re/go/api
replacements (pre-existing); gofmt clean. Supervisor's clean workspace
catches.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=537
2026-04-26 00:04:56 +01:00
Snider
2f9ffd5324 feat(agent/pipeline): implement pipeline/budget + pipeline/training (#536)
Replaces the #535 stubs with full impl per RFC.pipeline.md.

Lands:
* pkg/agentic/pipeline_budget.go (extend) — budget/plan reads pool/rate
  config, counts logged dispatches from .core/db.duckdb (JSONL fallback),
  prints per-pool remaining budget. budget/log appends to
  .core/journal/dispatch.jsonl + mirrors to state store.
* pkg/agentic/pipeline_training.go (extend) — training/capture pulls PR
  meta via MetaReader, captures PR diff via Forge PR-diff endpoint with
  `git show` fallback, records structural CodeRabbit-equivalent finding
  counts from review-thread totals, appends to .core/training/journal.jsonl.
  training/stats aggregates totals + zero-finding counts by repo.
  training/export filters to zero-finding entries → .core/training/export.jsonl
  (clean LEM training data).
* pkg/agentic/training_journal.go (NEW) — shared journal helpers
* AX-10 tests replace stubs (pipeline_budget_test.go +
  pipeline_training_test.go)
* tests/cli/pipeline/Taskfile.yaml — end-to-end smoke covers all 5
  subcommands against isolated temp workspace + local Forge stub

LEM training data pipeline now feedable: merged PRs → training/capture
→ journal.jsonl → training/export (zero-finding filter) → ready for next
LEK iteration.

Sandbox blocked from go test by go.work + private-dep resolution;
gofmt clean. Forge PR diff endpoint shape verified against Gitea API
docs (1.19).

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=536
2026-04-25 23:57:27 +01:00
Snider
23f4be5b85 feat(agent/pipeline): wire branch cleanup into createPR + cmdComplete success paths (#545)
Per RFC §15.5.1: agent branches (agent/*) must be deleted from Forge
after successful push or merge — stale branches pollute workspace prep
and cause clone confusion.

Lands:
* pkg/agentic/branch_cleanup.go (NEW) — cleanupBranch(ctx, repo, branch)
  helper. Refuses main/dev/master regardless of input (defensive).
  Normalises refs/heads/* prefix. Treats missing-remote-branch as
  harmless cleanup-success (idempotent).
* pkg/agentic/branch_cleanup_test.go (NEW) — AX-10 TestCleanupBranch_
  {Good_DeletesAgentBranch, Bad_RefuseProtected, Ugly_DeleteFailsForge}.
* pkg/agentic/pr.go — createPR success-on-push path now calls cleanupBranch.
* pkg/agentic/commands.go — cmdComplete success path also calls cleanupBranch.
* tests/cli/branch/Taskfile.yaml — end-to-end smoke + AX-10 unit hook.

agentic.branch.delete action was already registered in prep.go; this lane
routes the actual delete behaviour through the new helper instead of
editing the registration site.

Sandbox blocked from go test by outer go.work conflicting replacements;
gofmt clean. Supervisor's clean workspace catches.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=545
2026-04-25 23:56:34 +01:00
Snider
92b433ad76 feat(agent/sync): WorkspacePushed IPC → go-scm local repo sync (#546)
Per RFC §7 Post-Completion Repo Sync: workspace push → IPC event →
local clone fetch+reset so the supervisor's working tree always
matches Forge.

Lands:
* pkg/agentic/repo_sync.go — registers sync.fetch + sync.reset core
  actions; subscribes to WorkspacePushed IPC; exposes core-agent
  repo/sync --repo <name> [--reset] manual command
* commands.go — wires the subscriber + actions at startup
* pkg/agentic/repo_sync_test.go — AX-10: WorkspacePushed handler,
  branch-switch/reset behaviour, command path
* tests/cli/sync/{Taskfile.yaml,repo/Taskfile.yaml} — end-to-end
  smoke proving local clone matches Forge HEAD after sync

The existing 5min fallback fetch loop in fetch_loop.go is reused
unchanged — this lane fills the event-driven half of the contract.

Sandbox blocked from go test / go build by pre-existing go.work
dappco.re/go/api replacement conflict; supervisor's clean workspace
catches.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=546
2026-04-25 23:33:38 +01:00
Snider
5c942a8928 feat(agent/qa): post-run Poindexter workspace analysis per RFC §7 (#538)
Per RFC §7 Post-Run Analysis: analyseWorkspace() builds 5D Poindexter
points (tool_id, severity_score, file_hash, category_id, frequency),
clusters by distance 0.15, diffs against previous journal entries to
classify New / Resolved / Persistent (≥5 consecutive cycles).

Lands:
* pkg/agentic/qa_analysis.go — analyseWorkspace, DispatchReport,
  findingToPoint, diffFindings, persistentFindings; integrates with
  forge.lthn.ai/Snider/Poindexter (canonical path per memory)
* pkg/agentic/qa.go — wires analysis into runQAWithReport before
  ws.Commit() (sync.go untouched — ws.Commit lives in runQAWithReport
  in this branch)
* journal publication extended so summary text + analysis fields travel
  with the report
* qa_analysis_test.go — TestAnalyseWorkspace_{Good_EmptyFindings,
  Good_FiveClusters,Bad_NilWorkspace,Ugly_PoindexterPanic}; the panic
  test uses a panic-injecting clusterer override and asserts graceful
  recovery
* go.mod — adds forge.lthn.ai/Snider/Poindexter (canonical, NOT
  dappco.re — Poindexter is OG load-bearing math primitive)

Sandbox go test blocked by pre-existing unrelated issues in
commands_forge.go / fetch_loop.go / commands_flow_test.go (out of
allowlist); supervisor catches in clean workspace.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=538
2026-04-25 23:27:30 +01:00
Snider
53b46c33da feat(agent/pipeline): add core pipeline command tree (#535)
Per RFC.pipeline.md "core pipeline Command Tree": full audit/epic/
monitor/fix/onboard/budget/training subcommand tree wired into
core-agent.

Lands across 18 files:
* pkg/agentic/pipeline_commands.go — registry: audit, epic/create|run|
  status|sync, monitor, fix/reviews|conflicts|format|threads, onboard,
  budget/*, training/*
* pkg/agentic/commands.go — pipeline registration wired in
* pkg/agentic/pipeline_audit.go — audit issue expansion + bug fix:
  audit-created implementation issues no longer carry the 'audit' label,
  so epic + onboard see them as implementation candidates
* pkg/agentic/pipeline_epic.go — epic group/run/sync
* pkg/agentic/pipeline_monitor.go — open-PR watcher
* pkg/agentic/pipeline_fix.go — reviews/conflicts/format/threads helpers
* pkg/agentic/pipeline_onboard.go — chained audit → epic → dispatch
* pkg/agentic/pipeline_budget.go + pipeline_training.go — stubs
  returning blocked-on-sibling pointer (sibling tickets own deep impl)
* pkg/agentic/pipeline_*_test.go — AX-10 per handler
* tests/cli/pipeline/Taskfile.yaml — CLI smoke coverage

go test could not complete in this sandbox due to wider workspace
go.sum/private-module issues outside this ticket; supervisor catches
in clean workspace.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=535
2026-04-25 23:25:37 +01:00
Snider
d47946ff82 feat(agent/process): add Timeout + GracePeriod + KillGroup to dispatch (#540)
Per RFC.pipeline.md "go-process Improvements Needed": hung agent
processes blocked dispatch slots forever. Now killed after configured
timeout, with SIGTERM-then-SIGKILL grace period and process-group kill
to prevent orphaned subprocesses.

Lands:
* pkg/agentic/dispatch.go — every c.Process().Run() that spawns an
  agent now passes Timeout (DispatchConfig.TimeoutMinutes, default 60),
  GracePeriod: 30s, KillGroup: true. Watchdog writes timeout-specific
  failure reason into workspace status.
* pkg/agentic/queue.go — DispatchConfig adds TimeoutMinutes int (YAML:
  timeout_minutes, default 60) so operators can tune per-deployment.
* dispatch_test.go — TestDispatch_Run_Bad_Timeout asserts slow process
  transitions to failed state with timeout reason
* queue_test.go — TestQueue_Config_Good_TimeoutDefault asserts default 60

Verified go-process exposes timeout/gracePeriod/killGroup option keys
in the local checkout — no BLOCKED sibling needed.

Plain go build blocked by unrelated go.work conflict + sibling go-ws
coreerr.Warn missing (out of allowlist); supervisor's clean workspace
build will catch any remaining compile.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=540
2026-04-25 23:08:19 +01:00
Snider
64852472de feat(agent/agentic): RFC §16.5 offline sync queue with exponential backoff
Adds remote_sync_queue.go with persistent queue + 1s→30s exponential
backoff drainer, max-attempt drop with audit record, ctx.Done() clean
shutdown. sync.go rewired to enqueue dispatches via the queue rather
than the legacy minute-ticker schedule.

CORE_AGENT_SYNC_MAX_ATTEMPTS / CORE_SYNC_MAX_ATTEMPTS env vars override
the default 100-attempt cap.

Targeted tests cover happy-path drain, retry/backoff with stub clock,
cancellation, max-attempt exhaustion, and file-backed restart persistence.
go test was blocked by go.work workspace dep resolution in sandbox; CI
catches the real run.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=231
2026-04-25 20:57:12 +01:00
Snider
35b327d47e fix(agent/agentic): nil-guard PrepSubsystem.Core() in handleComplete
Defensive nil check returns a typed error result when the subsystem
is constructed without a runtime, matching the pattern in adjacent
handlers. Adds Bad case test.

Co-authored-by: Cerberus <noreply@anthropic.com>
2026-04-25 20:49:57 +01:00
Snider
9628e5d088 feat(agent/brain): non-Laravel callers route through shared OpenBrain client (#121)
Audit + remediation pass on non-Laravel Brain callers per #121.

docs/brain-callers-audit.md (NEW): audit baseline. Lists every PHP +
Go + script call site, current protections (org auth, retry, circuit
breaker), and gaps. Cross-references recent fixes (#312, #998, #1052,
#1055, #1006, #985).

New pkg/agentic/brain_client.go helper: routes Go callers through
the shared OpenBrain client path with org injection (CORE_BRAIN_ORG
fallback) and a shared circuit breaker.

Patched call sites:
- pkg/agentic/prep.go (line ~1200)
- pkg/agentic/session.go (line ~826)
- pkg/agentic/brain_seed_memory.go (line ~153)
- pkg/brain/direct.go (line ~106)

These previously made raw Brain HTTP posts. Now they go through the
hardened client path inheriting #998 (key-perm), #1052 (bearer SSRF
guard), #1055 (jitter), and the circuit breaker.

Remaining non-Laravel gaps (documented in audit doc, not patched here
because they're outside this lane's path-allowlist):
- core/mcp/pkg/mcp/agentic/prep.go
- Hermes Python plugins
- Claude shell hooks
- plugins/core-go api-endpoints SKILL example

Follow-up tickets needed for those (file separately).

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=121
2026-04-25 20:33:33 +01:00
Snider
40728e68d1 feat(agent/agentic): Poindexter KD-tree clustering replaces exact-key bucketing in runQAWithReport
qa_cluster.go wires QA finding clustering through Poindexter instead
of the old exact (tool, severity, category, rule) bucketing. Hashed
feature vectors built from finding metadata + message text are
indexed in Poindexter KD-trees with cosine + Euclidean distance,
near-neighbours unioned, then emitted as the existing DispatchCluster
shape (so consumers don't break).

Old exact-key grouping kept as fallback if Poindexter tree
construction ever fails.

qa.go updated to describe new similarity-based contract;
runQAWithReport already consumes clusterFindings, picks up the new
grouping automatically.

Tests cover: 3 similar findings + 2 distinct → grouped correctly;
5 distinct findings → 5 clusters; 0 findings → empty/nil no panic;
sample-cap regression preserved.

Note: github.com/Snider/Poindexter added as a direct dep. Per the
non-negotiable migration policy on Snider personal-namespace deps
(see #219), file follow-up to migrate Poindexter to its canonical
core/* home if/when that lands.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=164
2026-04-25 20:25:05 +01:00
Snider
b42cf5a18c feat(agent/agentic): 5-min background git fetch loop for registered repos
fetch_loop.go starts at PrepSubsystem.OnStartup, ticks on configurable
interval (default 5min), respects ctx.Done() for clean shutdown.
Each tick runs `git fetch origin <DefaultBranch>` via s.Core().Process()
— no worktree mutation.

Repo discovery sources (priority):
1. agents.fetch_repos in runtime config
2. repos / agents.*.repos in agents.yaml
3. Fallback scan of WorkspaceRoot() per RFC §7

Interval config: agents.fetch_interval, dispatch.fetch_interval, or
top-level fetch_interval in agents.yaml.

Loop survives individual repo failures (logs + continues — one bad
repo can't kill the loop).

Tests cover tick timing, failure isolation, ctx cancellation.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=165
2026-04-25 20:24:21 +01:00
Snider
820d33ebec feat(agent/agentic): scaffold core pipeline command tree per RFC-AGENT-PIPELINE.md
Codex preflight found docs/RFC-AGENT-PIPELINE.md (not the speculative
RFC.pipeline.md the ticket title referenced). Implementation matches
the actual RFC tree:

- core (top-level)
- core pipeline (router)
- core pipeline epic / fix / budget / training (grouped routers)
- All RFC leaf commands under each grouped router

Routers print scoped help. Each leaf currently returns "not yet
implemented" with a concrete next doc/flow reference (e.g. docs/flow/
RFC.flow-audit-issues.md). Future tickets wire the leaves to real
handlers.

Tests cover registration, descriptions, --help routing through
core pipeline audit.

Note: docs/RFC.pipeline.md alias still missing — TODO note in
commands_core.go for that follow-up.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=228
2026-04-25 20:24:21 +01:00
Snider
8858545f63 feat(agent/lib/flow): YAML flow library — Parse + ParseFile + LoadEmbedded
New pkg/lib/flow package per RFC §Flow System:

types.Flow{Name, Description, Steps}, types.Step{Name, Cmd, Args,
ContinueOnError}.

Parse(reader io.Reader) (Flow, error): YAML decoder
ParseFile(path string) (Flow, error): reads via core.Fs, then Parse
LoadEmbedded(name string) (Flow, error): bundled flow templates;
.md files only treated as flows when they contain YAML frontmatter

Validation: steps may be absent (empty Steps slice OK); any declared
step must define cmd.

Pairs with #160 (run/flow command at pkg/agentic/flow.go) — that
consumes types from this library for sequential step execution.

Tests cover: valid YAML, continueOnError, empty input, malformed
YAML, missing cmd, temp-file ParseFile, missing embedded files,
markdown-template failure (current state — embedded markdown is
content not YAML).

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=229
2026-04-25 20:21:33 +01:00
Snider
eed51d72b8 feat(agent/agentic): run/flow now executes sequential YAML steps
run/flow command now runs flow steps via the existing command tree:
- Each step's cmd is dispatched through s.Command(...)
- stdout/stderr captured per step
- Stops on first untolerated failure
- continueOnError: true allows step to fail without aborting flow
- Parse-time validation rejects unknown/non-executable commands
  BEFORE any step runs

flow/preview keeps the old inspection-only behaviour. Empty flows
succeed as no-ops.

Returns FlowRunOutput with summary: OK boolean, Executed/Passed/
Failed counts, per-step results.

Pest-equivalent Go tests cover: 3-step happy path, non-existent
cmd parse-time error, mid-flow failure (with + without
continueOnError), empty flow.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=160
2026-04-25 20:09:52 +01:00
Snider
99b0865303 fix(agent/agentic): annotate net/http as AX-6 structural exception
pkg/agentic/transport.go is the low-level HTTP boundary behind
core.Drive / core.API — there is no exported core/api generic
request/response wrapper that covers protocol-level MCP POST/SSE
exchange. Codex preflight confirmed: http.Client state, request
construction, response handling, and header mutation are all
present and intrinsic.

Resolution: annotation rather than purge. Per AX-6 doctrine, when
a core wrapper genuinely doesn't exist, explicit annotation is the
correct outcome (vs forcing through a wrapper that doesn't fit).

Doc comment now explains: "structural HTTP transport boundary for
core.API protocol streams and raw MCP POST/SSE exchange; no
exported core/api generic request wrapper covers this file."

Future PR: when core/api exposes a generic request wrapper
(potentially via #197 TransformerIn/Out gateway), revisit this
file and replace where feasible.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=222
2026-04-25 19:52:38 +01:00
Snider
f2b6ff29bd fix(agent): tighten directory perms in .core/reference/ siblings (Athena #988)
Mantis #324 narrowly tightened fs.go from 0644/0755→0600/0700. Athena audit
during task #20 closure-verification (2026-04-25) found sibling files in the
same directory still using 0755 for MkdirAll, leaving parent dirs world-listable
even when file content is 0600.

This commit applies the same hardening to:
- .core/reference/error.go:393 — crash-report parent dir 0755→0700
- .core/reference/embed.go:514/567/656 — workspace template extract dirs 0755→0700
- .core/reference/embed.go:595/660 — os.Create→os.OpenFile(...0600) for
  template renders + standard-file copies (default umask 0644 was leaking
  workspace-template content to other users on shared hosts)
- pkg/lib/workspace/default/.core/reference/error.go:414 — same crash-report fix
- pkg/lib/workspace/default/.core/reference/embed.go:518/571/660 — same template fixes

Workspace-template duplicates are kept in sync so newly-scaffolded workspaces
inherit the hardened perms instead of regressing to 0755/0644.

Closes Mantis #988.

Co-authored-by: Codex <noreply@openai.com>
2026-04-25 16:29:28 +01:00
Snider
6be6cb095c feat(agent/brain): adopt shared T1 client + propagate org through actions (#177)
#177 (T3/5 — direct subsystem adopts shared client):
- pkg/brain/direct.go: HTTP transport now delegates to shared T1 client
  in core/mcp's pkg/mcp/brain/client (retry, circuit breaker, org propagation)
- pkg/brain/actions.go: org now survives from action options through
  remember/recall/list calls
- pkg/brain/direct_test.go + actions_test.go: tests updated for org propagation

Tickets deferred:
- #179 (T5/5 — cross-runtime contract test + BRAIN-CALLERS.md): needs
  cross-repo edits to mcp + external runtime consumers
- #180 (lift RFC-OPENBRAIN features into vendored BrainService):
  base schema lacks memory_scope; no agentBoot, brain:consolidate,
  agent-context endpoint, or lifecycle events present

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=177
2026-04-25 16:22:38 +01:00
Snider
bf10d16f49 feat(agent): batch — sprint MCP tools + cmd cleanup (#142 #225 #226 #227)
Codex 5.5 batch lane processed 26 open Mantis tickets. 13 stale-fixed,
4 implemented, 9 deferred.

Tickets implemented:
- #142 — agentic_sprint_start + agentic_sprint_complete MCP tools wired to /v1/sprints/{id}/{start,complete} platform endpoints with tests
- #225 — cmd/core-agent/commands.go: removed raw flag parsing; startupArgs() uses Core arg filtering + local log-level strip
- #226 — cmd/core-agent/main.go: syscall.Exit(1) → core.Exit(1)
- #227 — pkg/agentic/dispatch.go: runtime.GOOS → Core environment-backed OS detection

Tickets stale-fixed: #161, #162, #163, #166, #167, #168, #171, #172, #223, #224, #230, #231, #232, #233
Tickets deferred: #160, #164, #165, #173, #222, #228, #229, #234

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=142
Closes tasks.lthn.sh/view.php?id=225
Closes tasks.lthn.sh/view.php?id=226
Closes tasks.lthn.sh/view.php?id=227
2026-04-25 14:55:23 +01:00
Snider
56a97e9178 fix(agent/brain): AX-6 sweep on direct.go — net/url → core.URLEncode + core.Join
url.Values manipulation replaced with []string builder + core.URLEncode
+ core.Join("&", params...). net/url import removed.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=966
2026-04-25 13:34:18 +01:00
Snider
a0ba74d220 fix(agent/monitor): AX-6 sweep on sync.go — net/url → core.URLEncode
url.QueryEscape → core.URLEncode for checkin URL agent param.
net/url import removed.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=965
2026-04-25 13:32:52 +01:00
Snider
14b0ef529c fix(agent/agentic): AX-6 sweep on scan.go — net/url → core.URLEncode
url.QueryEscape → core.URLEncode in listRepoIssues label encoding.
net/url import removed.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=963
2026-04-25 13:30:27 +01:00
Snider
fedb1f3b00 fix(agent/monitor): AX-6 sweep on monitor.go — net/url → core.URLEncode
url.QueryEscape → core.URLEncode for inbox URL agent param encoding.
net/url import removed.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=962
2026-04-25 13:30:27 +01:00
Snider
9ed15cbb42 fix(agent): replace sync/atomic plan ID generator with core.ID() (#863)
Removed local sync/atomic + crypto/rand + encoding/hex based plan ID
generator from pkg/agentic/plan.go. Switched planID() to core.ID()
primitive. Preserves id-{counter}-{suffix} shape via Core's primitive.

prep.go, sync.go, pkg/brain/*.go scanned — no sync imports remained
in non-test files.

Closes tasks.lthn.sh/view.php?id=863

Co-authored-by: Codex <noreply@openai.com>
2026-04-25 09:53:09 +01:00
Snider
e04f018b4c test(agent): add AX-10 unit tests for dispatch/session/sync/tools/content (#169)
Append-only — no existing tests modified.

- dispatch_test.go: TestDispatch_agentCommand_{Good,Bad,Ugly}
- session_test.go: TestSession_normaliseSessionAgentType_{Good,Bad,Ugly}
- sync_test.go: TestSync_syncBackoffSchedule_{Good,Bad,Ugly}
- tools_test.go: TestTools_RememberInput_{Bad,Ugly} (Good was pre-existing)
- content_test.go: TestContent_contentSchemaType_{Good,Bad,Ugly}

gofmt clean. Test verification deferred (private dappco.re/go/* deps
missing go.sum entries with GOWORK=off — would resolve under workspace).

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=169
2026-04-25 07:57:30 +01:00
Snider
f293d48006 fix(agent): tighten workspace file perms 0644→0600 to protect extracted secrets (Cerberus #324)
.core/reference/fs.go (canonical) + pkg/lib/workspace/default/.core/reference/fs.go (embedded copy):
- Write/WriteAtomic/Create/Append default to 0600
- Parent directories use 0700 (was 0755)
- WriteMode reapplies the requested mode after writes so overwriting an
  existing file also tightens permissions

Test (pkg/lib/lib_test.go) keeps embedded fs.go synced with canonical +
asserts extracted workspaces carry the secure permission defaults.

tests/cli/extract copy not hand-edited — that flows from regeneration.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=324
2026-04-25 04:19:30 +01:00
Snider
ba8de0c0bb fix(agent): purge sync.Once from pkg/agentic via core.Once (§14A)
Closes Mantis #863 ([agent] Phase 2: purge sync stdlib).

Per RFC plans/code/core/go/RFC.primitives-lifecycle.md §14A (landed core/go
dev 8995a80), swaps the four sync.Once usages to core.Once and the two
sync.Once{} reset-pattern callsites to core.Once.Reset():

pkg/agentic/statestore.go:
- Drop `import "sync"`.
- stateStoreRef.once: sync.Once → core.Once
- closeStateStore reset: `s.stateOnce = sync.Once{}` → `s.stateOnce.Reset()`

pkg/agentic/workspace_stats.go:
- Drop `import "sync"`.
- workspaceStatsRef.once: sync.Once → core.Once
- closeWorkspaceStatsStore reset: `s.workspaceStatsOnce = sync.Once{}` →
  `s.workspaceStatsOnce.Reset()`

pkg/agentic/prep.go:
- Drop `import "sync"`.
- PrepSubsystem.stateOnce + .workspaceStatsOnce: sync.Once → core.Once

The Reset() pattern matches stdlib semantics (see RFC §14A "Tradeoff: Once.
Reset semantics") — caller serialises via the existing closeStateStore /
closeWorkspaceStatsStore structure that nests Reset inside the lifecycle
inverse, so no concurrent Do races are introduced.

Net: 3 files, +7/-11. Mechanical line-edit per RFC §16 migration plan.

Audit re-check post-commit:
  grep -n '"sync"\|sync\.Once\|sync\.Mutex' pkg/agentic/{statestore,workspace_stats,prep}.go
  → empty (lib local variable named `sync` in mirror.go is unrelated; not
    in scope of this ticket).

Pre-flight verification: core.Once + Reset symbols verified present on
core/go dev 8995a80. Local AX-10 build blocked by the same pre-existing
workspace forge dep break that affects all consumers (root cause: fake
v0.8.0-alpha.1 pins per task #28); CI in healthy env will validate.

Co-Authored-By: Athena <athena@lthn.ai>
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-25 00:58:49 +01:00
Snider
34010f6d35 feat(ax-10): bring agent to v0.8.0-alpha.1 + CLI test scaffold
- Bump dappco.re/go/* deps to v0.8.0-alpha.1 in go.mod (any forge.lthn.ai/core/* paths migrated to canonical dappco.re/go/* form)
- Update Go source imports across 29 .go files
- Add tests/cli/agent/Taskfile.yaml AX-10 scaffold (build/vet/test under default deps), per RFC-CORE-008-AGENT-EXPERIENCE.md §10

Co-Authored-By: Athena <athena@lthn.ai>
2026-04-24 23:48:34 +01:00
Snider
a50e3d8291 test(agentic): add HTTPS cert regression tests + fleet sync audit
Fleet registration in pkg/agentic already goes through the shared
&http.Client{Timeout: 30s} at transport.go:13 — no InsecureSkipVerify,
no custom TLS transport. This audit documents that finding and adds
regression coverage so future refactors can't silently strip TLS
validation from the /v1/fleet/register path.

Verdict: OK. No production bug. Tests pass trusted TLS server case
and reject untrusted cert with a wrapped error that surfaces the
certificate / x509 / tls signal in the message.

Closes tasks.lthn.sh/view.php?id=29

Co-authored-by: Codex <noreply@openai.com>
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-23 18:40:02 +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
401487301a feat(agent): gpt-5.4-mini/mature pass 5
- `go test ./... -count=1 -timeout 60s`

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-18 08:55:41 +01:00
Snider
b6d67ae634 feat(agent): gpt-5.4-mini/mature pass 4
Commit landed on `dev` at `a7c16de9715a653bc335d076982eaf9ce04b54bc`.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-18 08:48:06 +01:00
Snider
beff657e57 feat(agent): gpt-5.4-mini/mature pass 3
- `go test ./pkg/agentic -count=1 -timeout 60s`

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-18 08:42:14 +01:00
Snider
60f4cb6fdb feat(agent): gpt-5.4-mini/mature pass 2
- `git add` / `git commit` fail with `Operation not permitted` on `.git/index.lock`
  - even a plain `touch .git/...` is blocked

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-18 08:34:52 +01:00
Snider
651783e1f5 feat(agent): gpt-5.4-mini/mature pass 1
Commit:

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-18 08:32:10 +01:00
Snider
43568cae01 test(agentic): cover message and dispatch sync contracts
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 21:01:10 +01:00
Snider
2daabf27f7 fix(agentic): check append write failures
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:52:37 +01:00
Snider
4cea9555d4 fix(agentic): reject empty MCP session ids
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:50:17 +01:00
Snider
2eda43d5ad hardening(prep): fail closed on specs copy
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:38:42 +01:00
Snider
7bb5c31746 fix(agentic): surface persistence failures
Add warnings for silent filesystem write/delete failures in agentic persistence helpers and record two adjacent hardening gaps for follow-up.\n\nCo-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:36:14 +01:00
Snider
db3ddc133e test(monitor): force binary fixtures into harvest tests
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:32:32 +01:00
Snider
b54daae418 fix(reference): harden core reference edge cases
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:31:06 +01:00
Snider
e837a284af feat(agent): RFC §9 agentic_auth_login MCP tool + dedupe tool registrations
Three load-bearing gaps between the agent RFC and the MCP surface:

- RFC §9 Fleet Mode describes the 6-digit pairing-code bootstrap as the
  primary way an unauthenticated node provisions its first AgentApiKey.
  `handleAuthLogin` existed as an Action but never surfaced as an MCP
  tool, so IDE/CLI callers had to shell out. Adds `agentic_auth_login`
  under `registerPlatformTools` with a thin wrapper over the existing
  handler so the platform contract stays single-sourced.
- `RegisterTools` was double-registering `agentic_scan` (bare
  `mcp.AddTool` before the CORE_MCP_FULL gate, then again via
  `AddToolRecorded` inside the gate). The second call silently replaced
  the first and bypassed tool-registry accounting, so REST bridging and
  metrics saw a zero for scan. Collapses both into a single recorded
  registration before the gate.
- `registerPlanTools` and `registerWatchTool` were also fired twice in
  the CORE_MCP_FULL branch. Removes the duplicates so the extended
  registration list mirrors the always-on list exactly once.
- Switches `agentic_prep_workspace` from bare `mcp.AddTool` to
  `AddToolRecorded` so prep-workspace participates in the same
  accounting as every other RFC §6 tool.

TestPrep_RegisterTools_Good_RegistersCompletionTool now asserts all
three `agentic_auth_*` tools surface, covering the new login registration
alongside provision/revoke.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-14 20:27:44 +01:00
Snider
b338e12fbf fix(agent): process action overrides survive ServiceStartup
go-process's OnStartup re-registers process.start/run/kill with
string-ID variants, clobbering the agent's custom handlers that return
*process.Process. This broke pid/queue helpers and 7 tests that need
the rich handle (TestPid_ProcessAlive_Good, TestQueue_CanDispatchAgent_Bad_AgentAtLimit,
etc). Register a Startable override service that reapplies the agent
handlers after every service finishes OnStartup — since services run in
registration order, "agentic.process-overrides" always runs last and
wins.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-14 18:24:47 +01:00
Snider
30cc4239d8 refactor(agent): runtimeAvailable uses core/process primitive
Replace os/exec.LookPath with process.Program.Find() — keeps dispatch
runtime detection in line with the repo's process-execution convention
and removes the os/exec import from pkg/agentic/dispatch.go.

Convergence-pass from spark-medium — no new features found on this
sample, confirms core/agent and go-store RFC parity is complete.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-14 14:21:09 +01:00
Snider
5ef2aba27b fix(agent): workspace prep falls back to GOWORK search
runWorkspaceLanguagePrep now appends `GOWORK=` (empty) to the env passed
to `go work sync` so inherited `GOWORK=off` from a test runner or CI
environment doesn't short-circuit the workspace lookup. The extracted
workspace template includes a go.work referencing ./repo; without this
override the sync fails even though the file is right there.

Converged pass — no new features found this sample.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-14 14:13:02 +01:00
Snider
2fc0de321d feat(agent): RFC §15.5 orphan QA buffer recovery on startup
Adds `recoverStateOrphans` per RFC §15.5 — startup scans `.core/state/`
for leftover QA workspace buffers from dispatches that crashed before
commit, and discards them so partial cycles do not poison the diff
history described in RFC §7.

- `statestore.go` — new `recoverStateOrphans` wrapper around go-store's
  `RecoverOrphans("")` so the agent inherits the store's configured
  state directory
- `prep.go` — wires the recovery into OnStartup immediately after
  `hydrateWorkspaces` so the registry, queue, and buffers all come back
  into a consistent state on restart
- `statestore_test.go` — Good/Bad/Ugly coverage, includes the cwd
  redirect guard so the go-store default relative path cannot leak test
  artefacts into the package working tree

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-14 13:51:54 +01:00