add autonomous coding runner with guardrails and completion gates

This commit is contained in:
Raven Scott
2026-04-26 05:09:36 -04:00
parent 0893938b16
commit 057208e262
18 changed files with 1285 additions and 42 deletions
+404 -12
View File
@@ -1100,7 +1100,18 @@ function bareAgentDefaultConfig() {
allow_bridge_mutations: false,
allow_host_notifications: false,
allow_host_actions: false,
emergency_stop_mutations: false
emergency_stop_mutations: false,
autonomous_mode_enabled: false,
autonomous_max_runtime_ms: 1800000,
autonomous_completion_required_checks: ['coreutils-test', 'verify-kernel-seeder-parity', 'verify-man-coverage'],
autonomous_allow_paths: ['*'],
autonomous_deny_ops: ['delete_path', 'request_host_action', 'emit_host_notification'],
autonomous_active: false,
autonomous_started_at_ms: 0,
autonomous_stop_requested: false,
autonomous_goal: '',
autonomous_status: 'idle',
autonomous_last_error: ''
}
}
@@ -1142,7 +1153,18 @@ function bareAgentMergeConfig(defaults, src) {
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations'
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
])
for (const k of Object.keys(src)) {
if (k.startsWith('x-')) continue
@@ -1158,7 +1180,10 @@ function bareAgentMergeConfig(defaults, src) {
k === 'model' ||
k === 'provider' ||
k === 'owner_name' ||
k === 'agent_label'
k === 'agent_label' ||
k === 'autonomous_goal' ||
k === 'autonomous_status' ||
k === 'autonomous_last_error'
) {
out[k] = String(val ?? '')
continue
@@ -1175,12 +1200,22 @@ function bareAgentMergeConfig(defaults, src) {
k === 'max_iterations' ||
k === 'tool_parallelism' ||
k === 'request_timeout_ms' ||
k === 'reasoning_max_chars'
k === 'reasoning_max_chars' ||
k === 'autonomous_max_runtime_ms' ||
k === 'autonomous_started_at_ms'
) {
const n = Number(val)
out[k] = Number.isFinite(n) ? n : defaults[k]
continue
}
if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops'
) {
out[k] = Array.isArray(val) ? val.map((x) => String(x ?? '')).filter(Boolean) : defaults[k]
continue
}
if (
k === 'stream' ||
k === 'allow_delete' ||
@@ -1189,7 +1224,10 @@ function bareAgentMergeConfig(defaults, src) {
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations'
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested'
) {
out[k] = Boolean(val)
continue
@@ -1232,7 +1270,18 @@ function bareAgentValidateConfigShape(raw) {
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations'
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
]
if (!known.includes(k)) {
throw new Error('unknown config key: ' + k)
@@ -1623,8 +1672,9 @@ async function bareAgentEnsureWorkspace(ctx, paths) {
* Ensure workspace/skills templates and ~/.agent/skill-loader.js exist (idempotent; for upgrades).
* @param {Record<string, unknown>} ctx
* @param {{ workspace: string, workspaceSkills: string, dir: string }} paths
* @param {Record<string, unknown>} [config]
*/
async function bareAgentEnsureSkillTemplates(ctx, paths) {
async function bareAgentEnsureSkillTemplates(ctx, paths, config) {
const vfs = ctx.vfs
if (
!vfs ||
@@ -1633,6 +1683,8 @@ async function bareAgentEnsureSkillTemplates(ctx, paths) {
typeof vfs.mkdir !== 'function'
)
return
const provider =
config && typeof config === 'object' ? String(config.provider || '').trim().toLowerCase() : ''
try {
await vfs.mkdir(paths.workspaceSkills, { recursive: true })
} catch {
@@ -1640,6 +1692,16 @@ async function bareAgentEnsureSkillTemplates(ctx, paths) {
}
const share = BARE_AGENT_WORKSPACE_SHARE
for (const rel of BARE_AGENT_SKILL_SEED_REL) {
if (rel === 'skills/xai-compat/SKILL.md' && provider !== 'xai') {
if (typeof vfs.unlink === 'function') {
try {
await vfs.unlink(paths.workspace + '/' + rel)
} catch {
/* ignore */
}
}
continue
}
const dest = paths.workspace + '/' + rel
try {
const b = await vfs.readFile(dest)
@@ -3693,6 +3755,54 @@ function bareAgentToolDefinitions() {
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run',
description:
'Start an autonomous coding run with goal, optional scope path, and runtime cap. This enables autonomous mode in config.',
parameters: {
type: 'object',
properties: {
goal: { type: 'string', description: 'Task goal the agent should complete autonomously' },
scope_path: { type: 'string', description: 'Preferred working scope path (optional)' },
max_runtime_ms: { type: 'integer', description: 'Optional runtime cap override' },
required_checks: {
type: 'array',
items: { type: 'string' },
description: 'Optional quality gates (allowlisted check ids)'
}
},
required: ['goal']
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_status',
description:
'Return current autonomous run state, elapsed/runtime budget, configured checks, and latest status.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_stop',
description:
'Request manual stop for an autonomous run; loop will stop safely on next control checkpoint.',
parameters: {
type: 'object',
properties: {
reason: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
@@ -3772,8 +3882,32 @@ async function bareAgentDispatchTool(o) {
} catch {
return bareAgentJsonResult({ ok: false, error: 'invalid_tool_arguments_json' })
}
const cfgNow = configRef.current || {}
if (
cfgNow.autonomous_active &&
Array.isArray(cfgNow.autonomous_deny_ops) &&
cfgNow.autonomous_deny_ops.map((x) => String(x)).includes(toolName)
) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_op_denied', tool: toolName })
}
const vfs = ctx.vfs
const AUTONOMOUS_DENY_PATH_PREFIXES = [
'/.git',
'/proc',
'/dev',
'/sys',
'/run',
'/boot',
'/lib',
'/usr/lib'
]
const AUTONOMOUS_CHECK_ALLOW = {
'coreutils-test': 'npm test -w bare-os-coreutils',
'verify-kernel-seeder-parity': 'node scripts/verify-kernel-seeder-parity.mjs',
'verify-man-coverage': 'node scripts/verify-man-coverage.mjs',
'verify-ctx-api-feature-bits': 'node scripts/verify-ctx-api-feature-bits.mjs'
}
const execLine =
typeof ctx.execLine === 'function'
? /** @type {(s: string, opts?: unknown) => Promise<unknown>} */ (
@@ -3902,6 +4036,38 @@ async function bareAgentDispatchTool(o) {
}
}
/**
* @param {string} p
*/
function autonomousPathAllowed(p) {
const s = String(p || '').trim()
if (!s) return true
if (!s.startsWith('/')) return false
for (const pref of AUTONOMOUS_DENY_PATH_PREFIXES) {
if (s === pref || s.startsWith(pref + '/')) return false
}
return true
}
/**
* @param {string} p
*/
function enforceAutonomousPath(p) {
const cfg = configRef.current || {}
if (!cfg.autonomous_active) return true
const path = String(p || '').trim()
if (!path) return true
if (!autonomousPathAllowed(path)) return false
const allowList = Array.isArray(cfg.autonomous_allow_paths)
? cfg.autonomous_allow_paths.map((x) => String(x || '').trim()).filter(Boolean)
: []
if (!allowList.length || allowList.includes('*')) return true
for (const pref of allowList) {
if (path === pref || path.startsWith(pref.endsWith('/') ? pref : pref + '/')) return true
}
return false
}
try {
if (toolName === 'read_skill') {
const skill = typeof args.skill === 'string' ? args.skill.trim() : ''
@@ -3952,8 +4118,97 @@ async function bareAgentDispatchTool(o) {
})
}
if (toolName === 'autonomous_run') {
const goal = typeof args.goal === 'string' ? args.goal.trim() : ''
const scopePath = typeof args.scope_path === 'string' ? args.scope_path.trim() : ''
const cfg = configRef.current || {}
if (!cfg.autonomous_mode_enabled) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_mode_disabled' })
}
if (!goal) return bareAgentJsonResult({ ok: false, error: 'goal_required' })
if (scopePath && !autonomousPathAllowed(scopePath)) {
return bareAgentJsonResult({ ok: false, error: 'scope_path_denied' })
}
const maxRuntimeMsRaw =
typeof args.max_runtime_ms === 'number' && Number.isFinite(args.max_runtime_ms)
? args.max_runtime_ms
: cfg.autonomous_max_runtime_ms
const maxRuntimeMs = Math.min(Math.max(Math.floor(Number(maxRuntimeMsRaw) || 0), 60000), 7_200_000)
const requiredChecks = Array.isArray(args.required_checks)
? args.required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
const unknown = requiredChecks.filter((x) => !Object.prototype.hasOwnProperty.call(AUTONOMOUS_CHECK_ALLOW, x))
if (unknown.length) {
return bareAgentJsonResult({ ok: false, error: 'unknown_required_checks', unknown, allowlist: Object.keys(AUTONOMOUS_CHECK_ALLOW) })
}
const merged = bareAgentMergeConfigPatch(cfg, {
autonomous_active: true,
autonomous_stop_requested: false,
autonomous_started_at_ms: Date.now(),
autonomous_goal: goal,
autonomous_status: 'running',
autonomous_last_error: '',
autonomous_max_runtime_ms: maxRuntimeMs,
autonomous_completion_required_checks: requiredChecks
})
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run start goal=' + goal.slice(0, 160))
return bareAgentJsonResult({
ok: true,
active: true,
goal,
scope_path: scopePath || null,
max_runtime_ms: maxRuntimeMs,
required_checks: requiredChecks,
status: 'running'
})
}
if (toolName === 'autonomous_run_status') {
const cfg = configRef.current || {}
const started = Number(cfg.autonomous_started_at_ms) || 0
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
const maxRuntime = Number(cfg.autonomous_max_runtime_ms) || 0
return bareAgentJsonResult({
ok: true,
autonomous_mode_enabled: Boolean(cfg.autonomous_mode_enabled),
active: Boolean(cfg.autonomous_active),
stop_requested: Boolean(cfg.autonomous_stop_requested),
goal: String(cfg.autonomous_goal || ''),
status: String(cfg.autonomous_status || 'idle'),
last_error: String(cfg.autonomous_last_error || ''),
started_at_ms: started,
elapsed_ms: elapsed,
max_runtime_ms: maxRuntime,
remaining_ms: maxRuntime > 0 ? Math.max(0, maxRuntime - elapsed) : 0,
required_checks: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks
: []
})
}
if (toolName === 'autonomous_run_stop') {
const cfg = configRef.current || {}
const reason = typeof args.reason === 'string' ? args.reason.trim() : ''
const merged = bareAgentMergeConfigPatch(cfg, {
autonomous_stop_requested: true,
autonomous_status: 'stopped',
autonomous_last_error: reason || String(cfg.autonomous_last_error || '')
})
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run_stop ' + (reason || 'requested'))
return bareAgentJsonResult({ ok: true, stop_requested: true, reason: reason || null })
}
if (toolName === 'read_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 1_000_000)
@@ -3977,6 +4232,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'write_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
const content = typeof args.content === 'string' ? args.content : ''
if (!path.startsWith('/') || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
@@ -3997,6 +4255,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'edit_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
if (!bareAgentPathAllowed(path) || !vfs?.readFile || !vfs?.writeFile) {
return bareAgentJsonResult({ ok: false, error: 'path_or_vfs' })
}
@@ -4038,6 +4299,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'create_directory') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
if (!path.startsWith('/')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
}
@@ -4075,6 +4339,12 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'run_command') {
const command = typeof args.command === 'string' ? args.command : ''
if ((configRef.current || {}).autonomous_active) {
const cmd = command.trim().toLowerCase()
if (cmd.includes('rm ') || cmd.includes(' git reset') || cmd.includes(' git clean')) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_command_denied' })
}
}
const timeoutMs =
typeof args.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
? Math.min(Math.floor(args.timeout_ms), 600000)
@@ -5188,7 +5458,18 @@ function bareAgentMergeConfigPatch(base, patch) {
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations'
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
]
const numKeys = new Set([
'max_tokens',
@@ -5196,7 +5477,9 @@ function bareAgentMergeConfigPatch(base, patch) {
'max_iterations',
'tool_parallelism',
'request_timeout_ms',
'reasoning_max_chars'
'reasoning_max_chars',
'autonomous_max_runtime_ms',
'autonomous_started_at_ms'
])
for (const k of keys) {
if (Object.prototype.hasOwnProperty.call(patch, k)) {
@@ -5205,6 +5488,12 @@ function bareAgentMergeConfigPatch(base, patch) {
if (numKeys.has(k)) {
const n = Number(v)
if (Number.isFinite(n)) out[k] = n
} else if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops'
) {
out[k] = Array.isArray(v) ? v.map((x) => String(x ?? '')).filter(Boolean) : out[k]
} else if (
k === 'stream' ||
k === 'allow_delete' ||
@@ -5213,7 +5502,10 @@ function bareAgentMergeConfigPatch(base, patch) {
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations'
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested'
) {
out[k] = Boolean(v)
} else if (k === 'reasoning_mode') {
@@ -5559,6 +5851,27 @@ function bareAgentApplyProviderProfile(cfg) {
return out
}
/**
* @param {Record<string, unknown>} cfg
*/
function bareAgentAutonomousSettings(cfg) {
const enabled = Boolean(cfg.autonomous_mode_enabled)
const active = Boolean(cfg.autonomous_active)
const stopRequested = Boolean(cfg.autonomous_stop_requested)
const startedAtMs =
typeof cfg.autonomous_started_at_ms === 'number' && Number.isFinite(cfg.autonomous_started_at_ms)
? Math.max(0, Math.floor(cfg.autonomous_started_at_ms))
: 0
const maxRuntimeMs =
typeof cfg.autonomous_max_runtime_ms === 'number' && Number.isFinite(cfg.autonomous_max_runtime_ms)
? Math.min(Math.max(Math.floor(cfg.autonomous_max_runtime_ms), 60000), 7_200_000)
: 1_800_000
const requiredChecks = Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
return { enabled, active, stopRequested, startedAtMs, maxRuntimeMs, requiredChecks }
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} argv0
@@ -5724,7 +6037,7 @@ async function bareAgentRunSetupOnly(ctx, argv0) {
}
try {
await bareAgentEnsureWorkspace(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths, config)
await bareAgentSyncWorkspaceFromConfig(ctx, paths, config)
} catch {
bareAgentErr(
@@ -5826,7 +6139,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const manDigest = await bareAgentManDigest(ctx)
await bareAgentEnsureWorkspace(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths, config)
if (needWizard) await bareAgentSyncWorkspaceFromConfig(ctx, paths, config)
const workspacePromptBlock = await bareAgentLoadWorkspacePrompt(
ctx,
@@ -5893,6 +6206,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
'/chat/completions'
const tools = bareAgentToolDefinitions()
let reasoningSettings = bareAgentReasoningSettings(configRef.current)
let autonomousSettings = bareAgentAutonomousSettings(configRef.current)
let reasoningCharCount = 0
let suspended = replSuspendedForSetup
@@ -5929,7 +6243,39 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
for (;;) {
reasoningSettings = bareAgentReasoningSettings(configRef.current)
autonomousSettings = bareAgentAutonomousSettings(configRef.current)
if (completed) break
if (autonomousSettings.enabled && autonomousSettings.active) {
const now = Date.now()
if (autonomousSettings.stopRequested) {
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'stopped'
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous stopped by manual request')
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped by request' + EDIT_ANSI_RESET + '\n'
)
break
}
if (
autonomousSettings.startedAtMs > 0 &&
now - autonomousSettings.startedAtMs >= autonomousSettings.maxRuntimeMs
) {
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'timebox_expired'
configRef.current.autonomous_last_error = 'timebox_expired'
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous timebox expired')
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped (timebox expired)' + EDIT_ANSI_RESET + '\n'
)
break
}
}
iter++
if (iter > maxIter) {
bareAgentErr(ctx, argv0 + ': max_iterations exceeded')
@@ -6086,6 +6432,52 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
}
if (!hasTools) {
if (autonomousSettings.enabled && autonomousSettings.active && autonomousSettings.requiredChecks.length) {
/** @type {string[]} */
const failedChecks = []
for (const check of autonomousSettings.requiredChecks) {
const res = await bareAgentDispatchTool({
ctx,
toolName: 'run_maintenance_gate',
argsJson: JSON.stringify({ command: check, timeout_ms: 300000 }),
paths,
signal: masterAbort.signal,
appendProgress,
home,
configRef,
manCacheRef,
onTaskComplete
})
let ok = false
try {
const j = JSON.parse(res)
const body = typeof j.stdout_stderr === 'string' ? j.stdout_stderr : ''
ok = Boolean(j.ok) && !/EXIT:[1-9]/.test(body)
} catch {
ok = false
}
if (!ok) failedChecks.push(check)
}
if (failedChecks.length) {
configRef.current.autonomous_status = 'needs_fixups'
configRef.current.autonomous_last_error = 'failed_checks:' + failedChecks.join(',')
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous checks failed ' + failedChecks.join(','))
messages.push({
role: 'user',
content:
'Autonomous completion gates failed for checks: ' +
failedChecks.join(', ') +
'. Fix the issues, rerun required checks, and only call task_complete when all pass.'
})
continue
}
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'completed'
configRef.current.autonomous_last_error = ''
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous completion gates passed')
}
await bareAgentSaveHistory(ctx, paths.history, messages)
bareAgentWriteOut(ctx, stdout, '\n')
break
+1 -1
View File
@@ -1,7 +1,7 @@
{
"schema": 2,
"profileId": "bare-os-posix-like",
"generatedAt": "2026-04-26T09:01:31.590Z",
"generatedAt": "2026-04-26T09:08:53.424Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [
{
+1 -1
View File
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1777194091590,
"atMs": 1777194533423,
"commands": [
"agent",
"arch",
+10 -1
View File
@@ -22,7 +22,7 @@ This tree follows the **agent** Markdown workspace convention: “soul” files
3. During a session, the model loads the full document with the **`read_skill`** tool (do not paste huge skills into the user channel unless asked).
4. Shared skills can live under **`~/.agent/skills/`**; keep **`workspace/skills/`** for machine-local or repo-specific behavior.
Seeded examples in this repo (under **`skills/`**): **`p2p-os-status`**, **`bare-os-kernel-proc`**, **`bare-os-super-developer`**, **`agent-ops`**, **`xai-compat`**, **`holesail`** (managed **`state.json`**, **`seed`**/**`key`**, stock **`bare-www-*`** / **`bare-ssh-*`**), and **`hdms`** (Hyperdrive mounts and invite/pair).
Seeded examples in this repo (under **`skills/`**): **`p2p-os-status`**, **`bare-os-kernel-proc`**, **`bare-os-super-developer`**, **`agent-ops`**, **`holesail`** (managed **`state.json`**, **`seed`**/**`key`**, stock **`bare-www-*`** / **`bare-ssh-*`**), and **`hdms`** (Hyperdrive mounts and invite/pair). Provider-specific skill **`xai-compat`** is only seeded when `provider` is configured as `xai`.
After **`agent --config`** / **`--setup`** (or changing **`owner_name`** / **`agent_label`** via **`edit_agent_config`**), **`IDENTITY.md`** and **`USER.md`** are regenerated from **`config.json`** so the workspace matches the operator and agent label.
@@ -40,6 +40,15 @@ Max-autonomy bridge policy switches are also in `config.json`:
- `allow_host_actions` — host action route gate
- `emergency_stop_mutations` — kill switch for mutating bridge tools
Autonomous coding runner controls in `config.json`:
- `autonomous_mode_enabled` — master toggle for autonomous loop mode
- `autonomous_max_runtime_ms` — runtime timebox for autonomous sessions
- `autonomous_completion_required_checks` — quality gates that must pass before done
- `autonomous_allow_paths` — allowed write/shell scope paths (`*` for unrestricted by path policy)
- `autonomous_deny_ops` — hard denylist of tool operations blocked during autonomous runs
- `autonomous_active`, `autonomous_stop_requested`, `autonomous_status`, `autonomous_last_error` — run state/status fields managed by tools/runtime
Provider profile notes:
- `groq` profile defaults to `https://api.groq.com/openai/v1` and OpenAI-compatible chat completions/tool calling semantics.
+8
View File
@@ -32,6 +32,14 @@
- `allow_host_actions`: required for `request_host_action`.
- `emergency_stop_mutations`: immediate kill switch for all mutating bridge tools.
## Autonomous coding runner
- `autonomous_run` starts autonomous project execution with a goal, runtime cap, and required checks.
- `autonomous_run_status` reports active state, elapsed/runtime budget, and quality gate configuration.
- `autonomous_run_stop` requests a safe stop at the next loop checkpoint.
- Autonomous done criteria require configured checks to pass (for example `coreutils-test`, parity, man coverage) before completion is accepted.
- Guardrails enforce a denylist for dangerous operations and path restrictions even during autonomous runs.
## Skills system
The agent has access to modular **skills** under `~/.agent/workspace/skills/` (and optionally shared skills under `~/.agent/skills/`).
File diff suppressed because one or more lines are too long
+55 -6
View File
@@ -65,7 +65,18 @@ function bareAgentDefaultConfig() {
allow_bridge_mutations: false,
allow_host_notifications: false,
allow_host_actions: false,
emergency_stop_mutations: false
emergency_stop_mutations: false,
autonomous_mode_enabled: false,
autonomous_max_runtime_ms: 1800000,
autonomous_completion_required_checks: ['coreutils-test', 'verify-kernel-seeder-parity', 'verify-man-coverage'],
autonomous_allow_paths: ['*'],
autonomous_deny_ops: ['delete_path', 'request_host_action', 'emit_host_notification'],
autonomous_active: false,
autonomous_started_at_ms: 0,
autonomous_stop_requested: false,
autonomous_goal: '',
autonomous_status: 'idle',
autonomous_last_error: ''
}
}
@@ -107,7 +118,18 @@ function bareAgentMergeConfig(defaults, src) {
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations'
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
])
for (const k of Object.keys(src)) {
if (k.startsWith('x-')) continue
@@ -123,7 +145,10 @@ function bareAgentMergeConfig(defaults, src) {
k === 'model' ||
k === 'provider' ||
k === 'owner_name' ||
k === 'agent_label'
k === 'agent_label' ||
k === 'autonomous_goal' ||
k === 'autonomous_status' ||
k === 'autonomous_last_error'
) {
out[k] = String(val ?? '')
continue
@@ -140,12 +165,22 @@ function bareAgentMergeConfig(defaults, src) {
k === 'max_iterations' ||
k === 'tool_parallelism' ||
k === 'request_timeout_ms' ||
k === 'reasoning_max_chars'
k === 'reasoning_max_chars' ||
k === 'autonomous_max_runtime_ms' ||
k === 'autonomous_started_at_ms'
) {
const n = Number(val)
out[k] = Number.isFinite(n) ? n : defaults[k]
continue
}
if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops'
) {
out[k] = Array.isArray(val) ? val.map((x) => String(x ?? '')).filter(Boolean) : defaults[k]
continue
}
if (
k === 'stream' ||
k === 'allow_delete' ||
@@ -154,7 +189,10 @@ function bareAgentMergeConfig(defaults, src) {
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations'
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested'
) {
out[k] = Boolean(val)
continue
@@ -197,7 +235,18 @@ function bareAgentValidateConfigShape(raw) {
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations'
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
]
if (!known.includes(k)) {
throw new Error('unknown config key: ' + k)
+233 -3
View File
@@ -776,6 +776,54 @@ function bareAgentToolDefinitions() {
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run',
description:
'Start an autonomous coding run with goal, optional scope path, and runtime cap. This enables autonomous mode in config.',
parameters: {
type: 'object',
properties: {
goal: { type: 'string', description: 'Task goal the agent should complete autonomously' },
scope_path: { type: 'string', description: 'Preferred working scope path (optional)' },
max_runtime_ms: { type: 'integer', description: 'Optional runtime cap override' },
required_checks: {
type: 'array',
items: { type: 'string' },
description: 'Optional quality gates (allowlisted check ids)'
}
},
required: ['goal']
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_status',
description:
'Return current autonomous run state, elapsed/runtime budget, configured checks, and latest status.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_stop',
description:
'Request manual stop for an autonomous run; loop will stop safely on next control checkpoint.',
parameters: {
type: 'object',
properties: {
reason: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
@@ -855,8 +903,32 @@ async function bareAgentDispatchTool(o) {
} catch {
return bareAgentJsonResult({ ok: false, error: 'invalid_tool_arguments_json' })
}
const cfgNow = configRef.current || {}
if (
cfgNow.autonomous_active &&
Array.isArray(cfgNow.autonomous_deny_ops) &&
cfgNow.autonomous_deny_ops.map((x) => String(x)).includes(toolName)
) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_op_denied', tool: toolName })
}
const vfs = ctx.vfs
const AUTONOMOUS_DENY_PATH_PREFIXES = [
'/.git',
'/proc',
'/dev',
'/sys',
'/run',
'/boot',
'/lib',
'/usr/lib'
]
const AUTONOMOUS_CHECK_ALLOW = {
'coreutils-test': 'npm test -w bare-os-coreutils',
'verify-kernel-seeder-parity': 'node scripts/verify-kernel-seeder-parity.mjs',
'verify-man-coverage': 'node scripts/verify-man-coverage.mjs',
'verify-ctx-api-feature-bits': 'node scripts/verify-ctx-api-feature-bits.mjs'
}
const execLine =
typeof ctx.execLine === 'function'
? /** @type {(s: string, opts?: unknown) => Promise<unknown>} */ (
@@ -985,6 +1057,38 @@ async function bareAgentDispatchTool(o) {
}
}
/**
* @param {string} p
*/
function autonomousPathAllowed(p) {
const s = String(p || '').trim()
if (!s) return true
if (!s.startsWith('/')) return false
for (const pref of AUTONOMOUS_DENY_PATH_PREFIXES) {
if (s === pref || s.startsWith(pref + '/')) return false
}
return true
}
/**
* @param {string} p
*/
function enforceAutonomousPath(p) {
const cfg = configRef.current || {}
if (!cfg.autonomous_active) return true
const path = String(p || '').trim()
if (!path) return true
if (!autonomousPathAllowed(path)) return false
const allowList = Array.isArray(cfg.autonomous_allow_paths)
? cfg.autonomous_allow_paths.map((x) => String(x || '').trim()).filter(Boolean)
: []
if (!allowList.length || allowList.includes('*')) return true
for (const pref of allowList) {
if (path === pref || path.startsWith(pref.endsWith('/') ? pref : pref + '/')) return true
}
return false
}
try {
if (toolName === 'read_skill') {
const skill = typeof args.skill === 'string' ? args.skill.trim() : ''
@@ -1035,8 +1139,97 @@ async function bareAgentDispatchTool(o) {
})
}
if (toolName === 'autonomous_run') {
const goal = typeof args.goal === 'string' ? args.goal.trim() : ''
const scopePath = typeof args.scope_path === 'string' ? args.scope_path.trim() : ''
const cfg = configRef.current || {}
if (!cfg.autonomous_mode_enabled) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_mode_disabled' })
}
if (!goal) return bareAgentJsonResult({ ok: false, error: 'goal_required' })
if (scopePath && !autonomousPathAllowed(scopePath)) {
return bareAgentJsonResult({ ok: false, error: 'scope_path_denied' })
}
const maxRuntimeMsRaw =
typeof args.max_runtime_ms === 'number' && Number.isFinite(args.max_runtime_ms)
? args.max_runtime_ms
: cfg.autonomous_max_runtime_ms
const maxRuntimeMs = Math.min(Math.max(Math.floor(Number(maxRuntimeMsRaw) || 0), 60000), 7_200_000)
const requiredChecks = Array.isArray(args.required_checks)
? args.required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
const unknown = requiredChecks.filter((x) => !Object.prototype.hasOwnProperty.call(AUTONOMOUS_CHECK_ALLOW, x))
if (unknown.length) {
return bareAgentJsonResult({ ok: false, error: 'unknown_required_checks', unknown, allowlist: Object.keys(AUTONOMOUS_CHECK_ALLOW) })
}
const merged = bareAgentMergeConfigPatch(cfg, {
autonomous_active: true,
autonomous_stop_requested: false,
autonomous_started_at_ms: Date.now(),
autonomous_goal: goal,
autonomous_status: 'running',
autonomous_last_error: '',
autonomous_max_runtime_ms: maxRuntimeMs,
autonomous_completion_required_checks: requiredChecks
})
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run start goal=' + goal.slice(0, 160))
return bareAgentJsonResult({
ok: true,
active: true,
goal,
scope_path: scopePath || null,
max_runtime_ms: maxRuntimeMs,
required_checks: requiredChecks,
status: 'running'
})
}
if (toolName === 'autonomous_run_status') {
const cfg = configRef.current || {}
const started = Number(cfg.autonomous_started_at_ms) || 0
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
const maxRuntime = Number(cfg.autonomous_max_runtime_ms) || 0
return bareAgentJsonResult({
ok: true,
autonomous_mode_enabled: Boolean(cfg.autonomous_mode_enabled),
active: Boolean(cfg.autonomous_active),
stop_requested: Boolean(cfg.autonomous_stop_requested),
goal: String(cfg.autonomous_goal || ''),
status: String(cfg.autonomous_status || 'idle'),
last_error: String(cfg.autonomous_last_error || ''),
started_at_ms: started,
elapsed_ms: elapsed,
max_runtime_ms: maxRuntime,
remaining_ms: maxRuntime > 0 ? Math.max(0, maxRuntime - elapsed) : 0,
required_checks: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks
: []
})
}
if (toolName === 'autonomous_run_stop') {
const cfg = configRef.current || {}
const reason = typeof args.reason === 'string' ? args.reason.trim() : ''
const merged = bareAgentMergeConfigPatch(cfg, {
autonomous_stop_requested: true,
autonomous_status: 'stopped',
autonomous_last_error: reason || String(cfg.autonomous_last_error || '')
})
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run_stop ' + (reason || 'requested'))
return bareAgentJsonResult({ ok: true, stop_requested: true, reason: reason || null })
}
if (toolName === 'read_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 1_000_000)
@@ -1060,6 +1253,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'write_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
const content = typeof args.content === 'string' ? args.content : ''
if (!path.startsWith('/') || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
@@ -1080,6 +1276,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'edit_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
if (!bareAgentPathAllowed(path) || !vfs?.readFile || !vfs?.writeFile) {
return bareAgentJsonResult({ ok: false, error: 'path_or_vfs' })
}
@@ -1121,6 +1320,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'create_directory') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
if (!path.startsWith('/')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
}
@@ -1158,6 +1360,12 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'run_command') {
const command = typeof args.command === 'string' ? args.command : ''
if ((configRef.current || {}).autonomous_active) {
const cmd = command.trim().toLowerCase()
if (cmd.includes('rm ') || cmd.includes(' git reset') || cmd.includes(' git clean')) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_command_denied' })
}
}
const timeoutMs =
typeof args.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
? Math.min(Math.floor(args.timeout_ms), 600000)
@@ -2271,7 +2479,18 @@ function bareAgentMergeConfigPatch(base, patch) {
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations'
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
]
const numKeys = new Set([
'max_tokens',
@@ -2279,7 +2498,9 @@ function bareAgentMergeConfigPatch(base, patch) {
'max_iterations',
'tool_parallelism',
'request_timeout_ms',
'reasoning_max_chars'
'reasoning_max_chars',
'autonomous_max_runtime_ms',
'autonomous_started_at_ms'
])
for (const k of keys) {
if (Object.prototype.hasOwnProperty.call(patch, k)) {
@@ -2288,6 +2509,12 @@ function bareAgentMergeConfigPatch(base, patch) {
if (numKeys.has(k)) {
const n = Number(v)
if (Number.isFinite(n)) out[k] = n
} else if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops'
) {
out[k] = Array.isArray(v) ? v.map((x) => String(x ?? '')).filter(Boolean) : out[k]
} else if (
k === 'stream' ||
k === 'allow_delete' ||
@@ -2296,7 +2523,10 @@ function bareAgentMergeConfigPatch(base, patch) {
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations'
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested'
) {
out[k] = Boolean(v)
} else if (k === 'reasoning_mode') {
+100
View File
@@ -305,6 +305,27 @@ function bareAgentApplyProviderProfile(cfg) {
return out
}
/**
* @param {Record<string, unknown>} cfg
*/
function bareAgentAutonomousSettings(cfg) {
const enabled = Boolean(cfg.autonomous_mode_enabled)
const active = Boolean(cfg.autonomous_active)
const stopRequested = Boolean(cfg.autonomous_stop_requested)
const startedAtMs =
typeof cfg.autonomous_started_at_ms === 'number' && Number.isFinite(cfg.autonomous_started_at_ms)
? Math.max(0, Math.floor(cfg.autonomous_started_at_ms))
: 0
const maxRuntimeMs =
typeof cfg.autonomous_max_runtime_ms === 'number' && Number.isFinite(cfg.autonomous_max_runtime_ms)
? Math.min(Math.max(Math.floor(cfg.autonomous_max_runtime_ms), 60000), 7_200_000)
: 1_800_000
const requiredChecks = Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
return { enabled, active, stopRequested, startedAtMs, maxRuntimeMs, requiredChecks }
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} argv0
@@ -639,6 +660,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
'/chat/completions'
const tools = bareAgentToolDefinitions()
let reasoningSettings = bareAgentReasoningSettings(configRef.current)
let autonomousSettings = bareAgentAutonomousSettings(configRef.current)
let reasoningCharCount = 0
let suspended = replSuspendedForSetup
@@ -675,7 +697,39 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
for (;;) {
reasoningSettings = bareAgentReasoningSettings(configRef.current)
autonomousSettings = bareAgentAutonomousSettings(configRef.current)
if (completed) break
if (autonomousSettings.enabled && autonomousSettings.active) {
const now = Date.now()
if (autonomousSettings.stopRequested) {
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'stopped'
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous stopped by manual request')
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped by request' + EDIT_ANSI_RESET + '\n'
)
break
}
if (
autonomousSettings.startedAtMs > 0 &&
now - autonomousSettings.startedAtMs >= autonomousSettings.maxRuntimeMs
) {
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'timebox_expired'
configRef.current.autonomous_last_error = 'timebox_expired'
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous timebox expired')
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped (timebox expired)' + EDIT_ANSI_RESET + '\n'
)
break
}
}
iter++
if (iter > maxIter) {
bareAgentErr(ctx, argv0 + ': max_iterations exceeded')
@@ -832,6 +886,52 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
}
if (!hasTools) {
if (autonomousSettings.enabled && autonomousSettings.active && autonomousSettings.requiredChecks.length) {
/** @type {string[]} */
const failedChecks = []
for (const check of autonomousSettings.requiredChecks) {
const res = await bareAgentDispatchTool({
ctx,
toolName: 'run_maintenance_gate',
argsJson: JSON.stringify({ command: check, timeout_ms: 300000 }),
paths,
signal: masterAbort.signal,
appendProgress,
home,
configRef,
manCacheRef,
onTaskComplete
})
let ok = false
try {
const j = JSON.parse(res)
const body = typeof j.stdout_stderr === 'string' ? j.stdout_stderr : ''
ok = Boolean(j.ok) && !/EXIT:[1-9]/.test(body)
} catch {
ok = false
}
if (!ok) failedChecks.push(check)
}
if (failedChecks.length) {
configRef.current.autonomous_status = 'needs_fixups'
configRef.current.autonomous_last_error = 'failed_checks:' + failedChecks.join(',')
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous checks failed ' + failedChecks.join(','))
messages.push({
role: 'user',
content:
'Autonomous completion gates failed for checks: ' +
failedChecks.join(', ') +
'. Fix the issues, rerun required checks, and only call task_complete when all pass.'
})
continue
}
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'completed'
configRef.current.autonomous_last_error = ''
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous completion gates passed')
}
await bareAgentSaveHistory(ctx, paths.history, messages)
bareAgentWriteOut(ctx, stdout, '\n')
break
@@ -40,6 +40,15 @@ Max-autonomy bridge policy switches are also in `config.json`:
- `allow_host_actions` — host action route gate
- `emergency_stop_mutations` — kill switch for mutating bridge tools
Autonomous coding runner controls in `config.json`:
- `autonomous_mode_enabled` — master toggle for autonomous loop mode
- `autonomous_max_runtime_ms` — runtime timebox for autonomous sessions
- `autonomous_completion_required_checks` — quality gates that must pass before done
- `autonomous_allow_paths` — allowed write/shell scope paths (`*` for unrestricted by path policy)
- `autonomous_deny_ops` — hard denylist of tool operations blocked during autonomous runs
- `autonomous_active`, `autonomous_stop_requested`, `autonomous_status`, `autonomous_last_error` — run state/status fields managed by tools/runtime
Provider profile notes:
- `groq` profile defaults to `https://api.groq.com/openai/v1` and OpenAI-compatible chat completions/tool calling semantics.
@@ -32,6 +32,14 @@
- `allow_host_actions`: required for `request_host_action`.
- `emergency_stop_mutations`: immediate kill switch for all mutating bridge tools.
## Autonomous coding runner
- `autonomous_run` starts autonomous project execution with a goal, runtime cap, and required checks.
- `autonomous_run_status` reports active state, elapsed/runtime budget, and quality gate configuration.
- `autonomous_run_stop` requests a safe stop at the next loop checkpoint.
- Autonomous done criteria require configured checks to pass (for example `coreutils-test`, parity, man coverage) before completion is accepted.
- Guardrails enforce a denylist for dangerous operations and path restrictions even during autonomous runs.
## Skills system
The agent has access to modular **skills** under `~/.agent/workspace/skills/` (and optionally shared skills under `~/.agent/skills/`).
@@ -28,6 +28,24 @@ test('agent-state exposes bridge policy keys', async (t) => {
}
})
test('agent-state exposes autonomous mode config keys', async (t) => {
for (const key of [
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
]) {
t.ok(STATE.includes(key), 'missing autonomous key in agent-state.js: ' + key)
}
})
test('agent-tools exposes agent ops tools', async (t) => {
for (const toolName of [
'list_services',
@@ -50,7 +68,10 @@ test('agent-tools exposes agent ops tools', async (t) => {
'get_hrpc_bridge_health',
'get_hrpc_allowlist_status',
'emit_host_notification',
'request_host_action'
'request_host_action',
'autonomous_run',
'autonomous_run_status',
'autonomous_run_stop'
]) {
t.ok(TOOLS.includes("name: '" + toolName + "'"), 'missing tool definition: ' + toolName)
}
@@ -69,3 +90,11 @@ test('agent-tui contains groq provider profile/request shaping', async (t) => {
t.ok(TUI.includes('body.parallel_tool_calls'))
t.ok(TUI.includes('body.max_completion_tokens'))
})
test('agent-tui contains autonomous loop controls and completion gates', async (t) => {
t.ok(TUI.includes('bareAgentAutonomousSettings'))
t.ok(TUI.includes('autonomous run stopped by request'))
t.ok(TUI.includes('autonomous run stopped (timebox expired)'))
t.ok(TUI.includes('Autonomous completion gates failed for checks'))
t.ok(TUI.includes('autonomous completion gates passed'))
})
+404 -12
View File
@@ -1100,7 +1100,18 @@ function bareAgentDefaultConfig() {
allow_bridge_mutations: false,
allow_host_notifications: false,
allow_host_actions: false,
emergency_stop_mutations: false
emergency_stop_mutations: false,
autonomous_mode_enabled: false,
autonomous_max_runtime_ms: 1800000,
autonomous_completion_required_checks: ['coreutils-test', 'verify-kernel-seeder-parity', 'verify-man-coverage'],
autonomous_allow_paths: ['*'],
autonomous_deny_ops: ['delete_path', 'request_host_action', 'emit_host_notification'],
autonomous_active: false,
autonomous_started_at_ms: 0,
autonomous_stop_requested: false,
autonomous_goal: '',
autonomous_status: 'idle',
autonomous_last_error: ''
}
}
@@ -1142,7 +1153,18 @@ function bareAgentMergeConfig(defaults, src) {
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations'
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
])
for (const k of Object.keys(src)) {
if (k.startsWith('x-')) continue
@@ -1158,7 +1180,10 @@ function bareAgentMergeConfig(defaults, src) {
k === 'model' ||
k === 'provider' ||
k === 'owner_name' ||
k === 'agent_label'
k === 'agent_label' ||
k === 'autonomous_goal' ||
k === 'autonomous_status' ||
k === 'autonomous_last_error'
) {
out[k] = String(val ?? '')
continue
@@ -1175,12 +1200,22 @@ function bareAgentMergeConfig(defaults, src) {
k === 'max_iterations' ||
k === 'tool_parallelism' ||
k === 'request_timeout_ms' ||
k === 'reasoning_max_chars'
k === 'reasoning_max_chars' ||
k === 'autonomous_max_runtime_ms' ||
k === 'autonomous_started_at_ms'
) {
const n = Number(val)
out[k] = Number.isFinite(n) ? n : defaults[k]
continue
}
if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops'
) {
out[k] = Array.isArray(val) ? val.map((x) => String(x ?? '')).filter(Boolean) : defaults[k]
continue
}
if (
k === 'stream' ||
k === 'allow_delete' ||
@@ -1189,7 +1224,10 @@ function bareAgentMergeConfig(defaults, src) {
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations'
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested'
) {
out[k] = Boolean(val)
continue
@@ -1232,7 +1270,18 @@ function bareAgentValidateConfigShape(raw) {
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations'
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
]
if (!known.includes(k)) {
throw new Error('unknown config key: ' + k)
@@ -1623,8 +1672,9 @@ async function bareAgentEnsureWorkspace(ctx, paths) {
* Ensure workspace/skills templates and ~/.agent/skill-loader.js exist (idempotent; for upgrades).
* @param {Record<string, unknown>} ctx
* @param {{ workspace: string, workspaceSkills: string, dir: string }} paths
* @param {Record<string, unknown>} [config]
*/
async function bareAgentEnsureSkillTemplates(ctx, paths) {
async function bareAgentEnsureSkillTemplates(ctx, paths, config) {
const vfs = ctx.vfs
if (
!vfs ||
@@ -1633,6 +1683,8 @@ async function bareAgentEnsureSkillTemplates(ctx, paths) {
typeof vfs.mkdir !== 'function'
)
return
const provider =
config && typeof config === 'object' ? String(config.provider || '').trim().toLowerCase() : ''
try {
await vfs.mkdir(paths.workspaceSkills, { recursive: true })
} catch {
@@ -1640,6 +1692,16 @@ async function bareAgentEnsureSkillTemplates(ctx, paths) {
}
const share = BARE_AGENT_WORKSPACE_SHARE
for (const rel of BARE_AGENT_SKILL_SEED_REL) {
if (rel === 'skills/xai-compat/SKILL.md' && provider !== 'xai') {
if (typeof vfs.unlink === 'function') {
try {
await vfs.unlink(paths.workspace + '/' + rel)
} catch {
/* ignore */
}
}
continue
}
const dest = paths.workspace + '/' + rel
try {
const b = await vfs.readFile(dest)
@@ -3693,6 +3755,54 @@ function bareAgentToolDefinitions() {
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run',
description:
'Start an autonomous coding run with goal, optional scope path, and runtime cap. This enables autonomous mode in config.',
parameters: {
type: 'object',
properties: {
goal: { type: 'string', description: 'Task goal the agent should complete autonomously' },
scope_path: { type: 'string', description: 'Preferred working scope path (optional)' },
max_runtime_ms: { type: 'integer', description: 'Optional runtime cap override' },
required_checks: {
type: 'array',
items: { type: 'string' },
description: 'Optional quality gates (allowlisted check ids)'
}
},
required: ['goal']
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_status',
description:
'Return current autonomous run state, elapsed/runtime budget, configured checks, and latest status.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_stop',
description:
'Request manual stop for an autonomous run; loop will stop safely on next control checkpoint.',
parameters: {
type: 'object',
properties: {
reason: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
@@ -3772,8 +3882,32 @@ async function bareAgentDispatchTool(o) {
} catch {
return bareAgentJsonResult({ ok: false, error: 'invalid_tool_arguments_json' })
}
const cfgNow = configRef.current || {}
if (
cfgNow.autonomous_active &&
Array.isArray(cfgNow.autonomous_deny_ops) &&
cfgNow.autonomous_deny_ops.map((x) => String(x)).includes(toolName)
) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_op_denied', tool: toolName })
}
const vfs = ctx.vfs
const AUTONOMOUS_DENY_PATH_PREFIXES = [
'/.git',
'/proc',
'/dev',
'/sys',
'/run',
'/boot',
'/lib',
'/usr/lib'
]
const AUTONOMOUS_CHECK_ALLOW = {
'coreutils-test': 'npm test -w bare-os-coreutils',
'verify-kernel-seeder-parity': 'node scripts/verify-kernel-seeder-parity.mjs',
'verify-man-coverage': 'node scripts/verify-man-coverage.mjs',
'verify-ctx-api-feature-bits': 'node scripts/verify-ctx-api-feature-bits.mjs'
}
const execLine =
typeof ctx.execLine === 'function'
? /** @type {(s: string, opts?: unknown) => Promise<unknown>} */ (
@@ -3902,6 +4036,38 @@ async function bareAgentDispatchTool(o) {
}
}
/**
* @param {string} p
*/
function autonomousPathAllowed(p) {
const s = String(p || '').trim()
if (!s) return true
if (!s.startsWith('/')) return false
for (const pref of AUTONOMOUS_DENY_PATH_PREFIXES) {
if (s === pref || s.startsWith(pref + '/')) return false
}
return true
}
/**
* @param {string} p
*/
function enforceAutonomousPath(p) {
const cfg = configRef.current || {}
if (!cfg.autonomous_active) return true
const path = String(p || '').trim()
if (!path) return true
if (!autonomousPathAllowed(path)) return false
const allowList = Array.isArray(cfg.autonomous_allow_paths)
? cfg.autonomous_allow_paths.map((x) => String(x || '').trim()).filter(Boolean)
: []
if (!allowList.length || allowList.includes('*')) return true
for (const pref of allowList) {
if (path === pref || path.startsWith(pref.endsWith('/') ? pref : pref + '/')) return true
}
return false
}
try {
if (toolName === 'read_skill') {
const skill = typeof args.skill === 'string' ? args.skill.trim() : ''
@@ -3952,8 +4118,97 @@ async function bareAgentDispatchTool(o) {
})
}
if (toolName === 'autonomous_run') {
const goal = typeof args.goal === 'string' ? args.goal.trim() : ''
const scopePath = typeof args.scope_path === 'string' ? args.scope_path.trim() : ''
const cfg = configRef.current || {}
if (!cfg.autonomous_mode_enabled) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_mode_disabled' })
}
if (!goal) return bareAgentJsonResult({ ok: false, error: 'goal_required' })
if (scopePath && !autonomousPathAllowed(scopePath)) {
return bareAgentJsonResult({ ok: false, error: 'scope_path_denied' })
}
const maxRuntimeMsRaw =
typeof args.max_runtime_ms === 'number' && Number.isFinite(args.max_runtime_ms)
? args.max_runtime_ms
: cfg.autonomous_max_runtime_ms
const maxRuntimeMs = Math.min(Math.max(Math.floor(Number(maxRuntimeMsRaw) || 0), 60000), 7_200_000)
const requiredChecks = Array.isArray(args.required_checks)
? args.required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
const unknown = requiredChecks.filter((x) => !Object.prototype.hasOwnProperty.call(AUTONOMOUS_CHECK_ALLOW, x))
if (unknown.length) {
return bareAgentJsonResult({ ok: false, error: 'unknown_required_checks', unknown, allowlist: Object.keys(AUTONOMOUS_CHECK_ALLOW) })
}
const merged = bareAgentMergeConfigPatch(cfg, {
autonomous_active: true,
autonomous_stop_requested: false,
autonomous_started_at_ms: Date.now(),
autonomous_goal: goal,
autonomous_status: 'running',
autonomous_last_error: '',
autonomous_max_runtime_ms: maxRuntimeMs,
autonomous_completion_required_checks: requiredChecks
})
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run start goal=' + goal.slice(0, 160))
return bareAgentJsonResult({
ok: true,
active: true,
goal,
scope_path: scopePath || null,
max_runtime_ms: maxRuntimeMs,
required_checks: requiredChecks,
status: 'running'
})
}
if (toolName === 'autonomous_run_status') {
const cfg = configRef.current || {}
const started = Number(cfg.autonomous_started_at_ms) || 0
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
const maxRuntime = Number(cfg.autonomous_max_runtime_ms) || 0
return bareAgentJsonResult({
ok: true,
autonomous_mode_enabled: Boolean(cfg.autonomous_mode_enabled),
active: Boolean(cfg.autonomous_active),
stop_requested: Boolean(cfg.autonomous_stop_requested),
goal: String(cfg.autonomous_goal || ''),
status: String(cfg.autonomous_status || 'idle'),
last_error: String(cfg.autonomous_last_error || ''),
started_at_ms: started,
elapsed_ms: elapsed,
max_runtime_ms: maxRuntime,
remaining_ms: maxRuntime > 0 ? Math.max(0, maxRuntime - elapsed) : 0,
required_checks: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks
: []
})
}
if (toolName === 'autonomous_run_stop') {
const cfg = configRef.current || {}
const reason = typeof args.reason === 'string' ? args.reason.trim() : ''
const merged = bareAgentMergeConfigPatch(cfg, {
autonomous_stop_requested: true,
autonomous_status: 'stopped',
autonomous_last_error: reason || String(cfg.autonomous_last_error || '')
})
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run_stop ' + (reason || 'requested'))
return bareAgentJsonResult({ ok: true, stop_requested: true, reason: reason || null })
}
if (toolName === 'read_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 1_000_000)
@@ -3977,6 +4232,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'write_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
const content = typeof args.content === 'string' ? args.content : ''
if (!path.startsWith('/') || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
@@ -3997,6 +4255,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'edit_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
if (!bareAgentPathAllowed(path) || !vfs?.readFile || !vfs?.writeFile) {
return bareAgentJsonResult({ ok: false, error: 'path_or_vfs' })
}
@@ -4038,6 +4299,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'create_directory') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
if (!path.startsWith('/')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
}
@@ -4075,6 +4339,12 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'run_command') {
const command = typeof args.command === 'string' ? args.command : ''
if ((configRef.current || {}).autonomous_active) {
const cmd = command.trim().toLowerCase()
if (cmd.includes('rm ') || cmd.includes(' git reset') || cmd.includes(' git clean')) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_command_denied' })
}
}
const timeoutMs =
typeof args.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
? Math.min(Math.floor(args.timeout_ms), 600000)
@@ -5188,7 +5458,18 @@ function bareAgentMergeConfigPatch(base, patch) {
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations'
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
]
const numKeys = new Set([
'max_tokens',
@@ -5196,7 +5477,9 @@ function bareAgentMergeConfigPatch(base, patch) {
'max_iterations',
'tool_parallelism',
'request_timeout_ms',
'reasoning_max_chars'
'reasoning_max_chars',
'autonomous_max_runtime_ms',
'autonomous_started_at_ms'
])
for (const k of keys) {
if (Object.prototype.hasOwnProperty.call(patch, k)) {
@@ -5205,6 +5488,12 @@ function bareAgentMergeConfigPatch(base, patch) {
if (numKeys.has(k)) {
const n = Number(v)
if (Number.isFinite(n)) out[k] = n
} else if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops'
) {
out[k] = Array.isArray(v) ? v.map((x) => String(x ?? '')).filter(Boolean) : out[k]
} else if (
k === 'stream' ||
k === 'allow_delete' ||
@@ -5213,7 +5502,10 @@ function bareAgentMergeConfigPatch(base, patch) {
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations'
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested'
) {
out[k] = Boolean(v)
} else if (k === 'reasoning_mode') {
@@ -5559,6 +5851,27 @@ function bareAgentApplyProviderProfile(cfg) {
return out
}
/**
* @param {Record<string, unknown>} cfg
*/
function bareAgentAutonomousSettings(cfg) {
const enabled = Boolean(cfg.autonomous_mode_enabled)
const active = Boolean(cfg.autonomous_active)
const stopRequested = Boolean(cfg.autonomous_stop_requested)
const startedAtMs =
typeof cfg.autonomous_started_at_ms === 'number' && Number.isFinite(cfg.autonomous_started_at_ms)
? Math.max(0, Math.floor(cfg.autonomous_started_at_ms))
: 0
const maxRuntimeMs =
typeof cfg.autonomous_max_runtime_ms === 'number' && Number.isFinite(cfg.autonomous_max_runtime_ms)
? Math.min(Math.max(Math.floor(cfg.autonomous_max_runtime_ms), 60000), 7_200_000)
: 1_800_000
const requiredChecks = Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
return { enabled, active, stopRequested, startedAtMs, maxRuntimeMs, requiredChecks }
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} argv0
@@ -5724,7 +6037,7 @@ async function bareAgentRunSetupOnly(ctx, argv0) {
}
try {
await bareAgentEnsureWorkspace(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths, config)
await bareAgentSyncWorkspaceFromConfig(ctx, paths, config)
} catch {
bareAgentErr(
@@ -5826,7 +6139,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const manDigest = await bareAgentManDigest(ctx)
await bareAgentEnsureWorkspace(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths, config)
if (needWizard) await bareAgentSyncWorkspaceFromConfig(ctx, paths, config)
const workspacePromptBlock = await bareAgentLoadWorkspacePrompt(
ctx,
@@ -5893,6 +6206,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
'/chat/completions'
const tools = bareAgentToolDefinitions()
let reasoningSettings = bareAgentReasoningSettings(configRef.current)
let autonomousSettings = bareAgentAutonomousSettings(configRef.current)
let reasoningCharCount = 0
let suspended = replSuspendedForSetup
@@ -5929,7 +6243,39 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
for (;;) {
reasoningSettings = bareAgentReasoningSettings(configRef.current)
autonomousSettings = bareAgentAutonomousSettings(configRef.current)
if (completed) break
if (autonomousSettings.enabled && autonomousSettings.active) {
const now = Date.now()
if (autonomousSettings.stopRequested) {
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'stopped'
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous stopped by manual request')
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped by request' + EDIT_ANSI_RESET + '\n'
)
break
}
if (
autonomousSettings.startedAtMs > 0 &&
now - autonomousSettings.startedAtMs >= autonomousSettings.maxRuntimeMs
) {
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'timebox_expired'
configRef.current.autonomous_last_error = 'timebox_expired'
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous timebox expired')
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped (timebox expired)' + EDIT_ANSI_RESET + '\n'
)
break
}
}
iter++
if (iter > maxIter) {
bareAgentErr(ctx, argv0 + ': max_iterations exceeded')
@@ -6086,6 +6432,52 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
}
if (!hasTools) {
if (autonomousSettings.enabled && autonomousSettings.active && autonomousSettings.requiredChecks.length) {
/** @type {string[]} */
const failedChecks = []
for (const check of autonomousSettings.requiredChecks) {
const res = await bareAgentDispatchTool({
ctx,
toolName: 'run_maintenance_gate',
argsJson: JSON.stringify({ command: check, timeout_ms: 300000 }),
paths,
signal: masterAbort.signal,
appendProgress,
home,
configRef,
manCacheRef,
onTaskComplete
})
let ok = false
try {
const j = JSON.parse(res)
const body = typeof j.stdout_stderr === 'string' ? j.stdout_stderr : ''
ok = Boolean(j.ok) && !/EXIT:[1-9]/.test(body)
} catch {
ok = false
}
if (!ok) failedChecks.push(check)
}
if (failedChecks.length) {
configRef.current.autonomous_status = 'needs_fixups'
configRef.current.autonomous_last_error = 'failed_checks:' + failedChecks.join(',')
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous checks failed ' + failedChecks.join(','))
messages.push({
role: 'user',
content:
'Autonomous completion gates failed for checks: ' +
failedChecks.join(', ') +
'. Fix the issues, rerun required checks, and only call task_complete when all pass.'
})
continue
}
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'completed'
configRef.current.autonomous_last_error = ''
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous completion gates passed')
}
await bareAgentSaveHistory(ctx, paths.history, messages)
bareAgentWriteOut(ctx, stdout, '\n')
break
@@ -1,7 +1,7 @@
{
"schema": 2,
"profileId": "bare-os-posix-like",
"generatedAt": "2026-04-26T09:01:31.590Z",
"generatedAt": "2026-04-26T09:08:53.424Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [
{
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1777194091590,
"atMs": 1777194533423,
"commands": [
"agent",
"arch",
@@ -22,7 +22,7 @@ This tree follows the **agent** Markdown workspace convention: “soul” files
3. During a session, the model loads the full document with the **`read_skill`** tool (do not paste huge skills into the user channel unless asked).
4. Shared skills can live under **`~/.agent/skills/`**; keep **`workspace/skills/`** for machine-local or repo-specific behavior.
Seeded examples in this repo (under **`skills/`**): **`p2p-os-status`**, **`bare-os-kernel-proc`**, **`bare-os-super-developer`**, **`agent-ops`**, **`xai-compat`**, **`holesail`** (managed **`state.json`**, **`seed`**/**`key`**, stock **`bare-www-*`** / **`bare-ssh-*`**), and **`hdms`** (Hyperdrive mounts and invite/pair).
Seeded examples in this repo (under **`skills/`**): **`p2p-os-status`**, **`bare-os-kernel-proc`**, **`bare-os-super-developer`**, **`agent-ops`**, **`holesail`** (managed **`state.json`**, **`seed`**/**`key`**, stock **`bare-www-*`** / **`bare-ssh-*`**), and **`hdms`** (Hyperdrive mounts and invite/pair). Provider-specific skill **`xai-compat`** is only seeded when `provider` is configured as `xai`.
After **`agent --config`** / **`--setup`** (or changing **`owner_name`** / **`agent_label`** via **`edit_agent_config`**), **`IDENTITY.md`** and **`USER.md`** are regenerated from **`config.json`** so the workspace matches the operator and agent label.
@@ -40,6 +40,15 @@ Max-autonomy bridge policy switches are also in `config.json`:
- `allow_host_actions` — host action route gate
- `emergency_stop_mutations` — kill switch for mutating bridge tools
Autonomous coding runner controls in `config.json`:
- `autonomous_mode_enabled` — master toggle for autonomous loop mode
- `autonomous_max_runtime_ms` — runtime timebox for autonomous sessions
- `autonomous_completion_required_checks` — quality gates that must pass before done
- `autonomous_allow_paths` — allowed write/shell scope paths (`*` for unrestricted by path policy)
- `autonomous_deny_ops` — hard denylist of tool operations blocked during autonomous runs
- `autonomous_active`, `autonomous_stop_requested`, `autonomous_status`, `autonomous_last_error` — run state/status fields managed by tools/runtime
Provider profile notes:
- `groq` profile defaults to `https://api.groq.com/openai/v1` and OpenAI-compatible chat completions/tool calling semantics.
@@ -32,6 +32,14 @@
- `allow_host_actions`: required for `request_host_action`.
- `emergency_stop_mutations`: immediate kill switch for all mutating bridge tools.
## Autonomous coding runner
- `autonomous_run` starts autonomous project execution with a goal, runtime cap, and required checks.
- `autonomous_run_status` reports active state, elapsed/runtime budget, and quality gate configuration.
- `autonomous_run_stop` requests a safe stop at the next loop checkpoint.
- Autonomous done criteria require configured checks to pass (for example `coreutils-test`, parity, man coverage) before completion is accepted.
- Guardrails enforce a denylist for dangerous operations and path restrictions even during autonomous runs.
## Skills system
The agent has access to modular **skills** under `~/.agent/workspace/skills/` (and optionally shared skills under `~/.agent/skills/`).
File diff suppressed because one or more lines are too long