github/dev tree was reduced to 2 files (pkg/agentic/prep{,_test}.go) after
an experimental branch reset. Cumulative net diff vs merge-base b338e12
is only those 2 files. Local has been growing the full tree for 120
commits and absorbed every substantive prep.go change in passing —
dispatch sync hooks, AddToolRecorded migrations, ensureWorkspaceTaskFile
wiring, atomic CODEX.md write. prep_test.go missing only the
TestPrep_EnsureWorkspaceTaskFile_Bad case; appended.
The ~1,500 "deleted by them" entries are artifacts of github's branch
reset, not a delete intent. Kept canonical local tree.
Co-authored-by: Hephaestus <hephaestus@cladius>
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
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
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
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
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
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>
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
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
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
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
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
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>
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>
.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
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>
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>
- `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>
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>
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>
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>