mirror of
https://github.com/bleak-ai/gcontext.git
synced 2026-08-11 13:19:23 +02:00
Add framework prompts to Commands dashboard and clarify setup guidance
- Expose built-in framework prompts alongside project commands/ files in the /api/commands endpoint and Commands.jsx UI, grouped by owner. - Tighten setup.md so approvals aren't re-asked for explicitly requested items and agent.md is written without confirmation. - Document commands/ folders and module portability rules in framework-instructions.md; trim duplicated conventions from docs/modules.md in favor of that single source.
This commit is contained in:
+1
-1
@@ -14,7 +14,7 @@ This principle removed two features in sequence. An early version shipped a ~230
|
||||
|
||||
There is no database, no manifest system, no type registry. An agent is a directory: markdown for context, YAML for config, an env file for secret values. If you can create a folder and put a markdown file in it, you can extend the agent. The whole thing versions with git, which means agent state gets diffs, history, review, and rollback for free.
|
||||
|
||||
The predecessor of this design had typed modules (integration / task / workflow) with manifest files. It was rejected: the classification created decision paralysis ("is this a task or a workflow?") and the manifests had too many fields. A folder is a folder. Progressive complexity instead: start with markdown (knowledge), add a `connection.yaml` when you need secrets (integration), add a `flow.yaml` when work has steps (process).
|
||||
The predecessor of this design had typed modules (integration / task / workflow) with manifest files. It was rejected: the classification created decision paralysis ("is this a task or a workflow?") and the manifests had too many fields. A folder is a folder. Progressive complexity instead: start with markdown (knowledge), add a `connection.yaml` when you need secrets (integration), add scripts and commands when procedures prove themselves.
|
||||
|
||||
## Config is YAML, content is markdown, behavior is scripts
|
||||
|
||||
|
||||
+1
-9
@@ -48,15 +48,7 @@ There is no enforced schema beyond `index.md`. Different modules have different
|
||||
|
||||
## How a module grows
|
||||
|
||||
Nothing is enforced in code; these are the conventions the framework instructions push to every connected agent. Retrieval in gcontext is `list_dir` and `grep`, no index and no search, so the tree itself is the index. The rules keep it navigable:
|
||||
|
||||
- One topic per module. When a second topic appears, it is a second module, not a subfolder.
|
||||
- A folder's `index.md` is its map: what the folder holds, plus one line per child file or subfolder. A reader (human or agent) should know where to go after reading only the `index.md`.
|
||||
- Stay flat until several files share a clear sub-topic. Then make one subfolder per sub-topic (`playbooks/`, `logs/`, `scripts/`), and give it its own `index.md` if it holds more than a handful of files. Never create folders for dates or counts; `logs/2026/08/` hides content that one append-only file with a stated format holds better.
|
||||
- Soft limits, not caps: keep a single listing under a couple dozen entries, and nesting within about three levels below `modules/`. Passing them is a signal to reorganize, not an error.
|
||||
- Split a file when it stops being readable in one pass, not before. Many small fragments cost more round-trips than one coherent file.
|
||||
|
||||
The soft limits deliberately replace harder rules from an earlier design (a fixed maximum of files per level): agents follow numeric caps literally and produce premature subfolders. Judgment plus a self-describing `index.md` scales further.
|
||||
Nothing is enforced in code. The conventions (one topic per module, index.md as the folder's map, stay flat until sub-topics emerge, split files only when they stop being readable in one pass) are pushed to every connected agent by the framework instructions (`gcontext/prompts/framework-instructions.md` in the package); that file is the single source for them.
|
||||
|
||||
## How someone uses a module
|
||||
|
||||
|
||||
@@ -123,6 +123,17 @@ def discover(root: Path) -> list[Path]:
|
||||
)
|
||||
|
||||
|
||||
_FRAMEWORK_SKIP = {"framework-instructions", "resources", "README"}
|
||||
|
||||
|
||||
def discover_framework_prompts() -> list[Path]:
|
||||
"""Framework-shipped prompt files (same filter as register_framework_prompts)."""
|
||||
prompts_dir = Path(__file__).parent / "prompts"
|
||||
return sorted(
|
||||
p for p in prompts_dir.glob("*.md") if p.stem not in _FRAMEWORK_SKIP
|
||||
)
|
||||
|
||||
|
||||
def register_framework_prompts(mcp) -> int:
|
||||
"""Register the framework's own prompts, shipped in the package.
|
||||
|
||||
|
||||
+26
-23
@@ -93,6 +93,28 @@ async def api_modules(request: Request) -> JSONResponse:
|
||||
return JSONResponse(result)
|
||||
|
||||
|
||||
def _command_entry(path: Path, owner: str, rel: str, kind: str) -> dict:
|
||||
entry = {"owner": owner, "name": f"{owner}__{path.stem}", "kind": kind, "path": rel}
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if path.suffix == ".md":
|
||||
meta, _ = commands_mod.parse_command(text)
|
||||
else:
|
||||
meta = commands_mod.parse_script_command(text)
|
||||
entry["description"] = meta.get("description", "")
|
||||
entry["args"] = [
|
||||
{
|
||||
"name": p.get("name", "?"),
|
||||
"description": p.get("description", ""),
|
||||
"required": bool(p.get("required", False)),
|
||||
}
|
||||
for p in (meta.get("parameters") or [])
|
||||
]
|
||||
except (ValueError, KeyError, yaml.YAMLError) as e:
|
||||
entry["error"] = str(e)
|
||||
return entry
|
||||
|
||||
|
||||
@mcp.custom_route("/api/commands", methods=["GET"])
|
||||
async def api_commands(request: Request) -> JSONResponse:
|
||||
root = _root()
|
||||
@@ -100,29 +122,10 @@ async def api_commands(request: Request) -> JSONResponse:
|
||||
for path in commands_mod.discover(root):
|
||||
rel = str(path.relative_to(root))
|
||||
owner = path.parent.parent.name
|
||||
entry = {
|
||||
"owner": owner,
|
||||
"name": f"{owner}__{path.stem}",
|
||||
"kind": path.suffix.lstrip("."),
|
||||
"path": rel,
|
||||
}
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if path.suffix == ".md":
|
||||
meta, _ = commands_mod.parse_command(text)
|
||||
else:
|
||||
meta = commands_mod.parse_script_command(text)
|
||||
entry["description"] = meta.get("description", "")
|
||||
entry["args"] = [
|
||||
{
|
||||
"name": p.get("name", "?"),
|
||||
"description": p.get("description", ""),
|
||||
"required": bool(p.get("required", False)),
|
||||
}
|
||||
for p in (meta.get("parameters") or [])
|
||||
]
|
||||
except (ValueError, KeyError, yaml.YAMLError) as e:
|
||||
entry["error"] = str(e)
|
||||
result.append(_command_entry(path, owner, rel, path.suffix.lstrip(".")))
|
||||
for path in commands_mod.discover_framework_prompts():
|
||||
entry = _command_entry(path, "framework", f"gcontext/prompts/{path.name}", "md")
|
||||
entry["name"] = path.stem
|
||||
result.append(entry)
|
||||
return JSONResponse(result)
|
||||
|
||||
|
||||
@@ -17,9 +17,28 @@ How the folder is organized:
|
||||
practice. Read the index.md before writing a script against a service, and
|
||||
update it when you learn something worth keeping.
|
||||
- modules/<name>/: accumulated knowledge on a topic, entry point index.md.
|
||||
Modules are portable: another agent can use one by copying the folder. So
|
||||
keep a module connection-agnostic in its process files (say "the payment
|
||||
provider", not "Stripe"); the agent finds the concrete service in
|
||||
connections/ at run time. Company-specific facts learned while working
|
||||
(playbooks, logs) are fine; hard-wired service names in the steps are not.
|
||||
- scripts/ folders (inside connections and modules): proven procedures. Run
|
||||
them by path with run_script instead of rewriting them, and save a script
|
||||
there once it has proven itself.
|
||||
- commands/ folders (inside connections and modules): user-invokable entry
|
||||
points, exposed as MCP prompts (slash commands in Claude Code, named
|
||||
/mcp__<server>__<owner>__<command>). Two file types:
|
||||
- <name>.md: a prompt command. Starts with a `---` YAML frontmatter block
|
||||
holding `description` and optional `parameters` (list of `name`,
|
||||
`description`, `required`); the body is injected into the conversation
|
||||
with `$name` placeholders filled from the arguments.
|
||||
- <name>.py: a script command. Starts with the same frontmatter as a
|
||||
`# ---` comment block; invoking it tells the agent to run the file via
|
||||
run_script with the arguments as params.
|
||||
When the user asks for a reusable command or workflow entry point, this
|
||||
is where it goes: write the file with write_file under the connection or
|
||||
module it belongs to. New commands appear after a server restart, which
|
||||
the user must do; tell them.
|
||||
- archive/: retired state. Not scanned or listed, still readable by path.
|
||||
|
||||
How state grows: one topic per module. A folder's index.md is its map and
|
||||
|
||||
@@ -17,6 +17,14 @@ The user's request, possibly empty: "$request"
|
||||
- The user does not need to know gcontext's concepts. Never ask "do you want
|
||||
a connection or a module?". They describe goals; YOU decide what each goal
|
||||
needs and explain your plan in one plain line per item.
|
||||
- Setup is not a one-shot. Say early that nothing has to be decided now:
|
||||
the user can start with one or two items and add or change anything later
|
||||
by running setup again or simply asking the agent.
|
||||
- Do not re-confirm what the user already said. When the user explicitly
|
||||
named a service or a topic, that item is approved; asking "should I add
|
||||
the X connection?" again is noise. The plan confirmation (Step 3) also
|
||||
counts as write approval for the files that build those items: announce
|
||||
each write in one line, but do not ask again per file.
|
||||
- Inspect before you ask. Never ask the user something the state folder can
|
||||
answer. Start with list_dir(".") and read what you need from there.
|
||||
- When your runtime has an interactive question tool (AskUserQuestion in
|
||||
@@ -64,22 +72,27 @@ Translate the description into a plan. The mapping is yours to make:
|
||||
- Knowledge the agent must hold (how the company works, a product, a process,
|
||||
a team's rules): a module each. Prefer a few broad modules over many thin
|
||||
ones; a module can grow files later.
|
||||
- If agent.md is still the placeholder, writing it from the user's
|
||||
description is always part of the plan.
|
||||
- If agent.md is still the placeholder, write it yourself from the user's
|
||||
description. It is not a plan item and never a question: do not ask what
|
||||
it should say or confirm its content, just write it at the start of the
|
||||
build and mention in one line that you did.
|
||||
|
||||
Show the plan as a short list, one plain line per item ("stripe: so the agent
|
||||
can look up payments and refunds"), and confirm it as a choice question:
|
||||
build all of it, or let the user deselect items (multi-select when the tool
|
||||
supports it). First-time setups with many items are the normal case; do not
|
||||
talk the user out of a big plan, but order it so something useful exists
|
||||
early.
|
||||
can look up payments and refunds"). Items the user explicitly asked for are
|
||||
already approved; confirm only the items you inferred yourself, as a choice
|
||||
question (multi-select when the tool supports it). If every item was
|
||||
explicitly requested, skip the confirmation and start building. First-time
|
||||
setups with many items are the normal case; do not talk the user out of a
|
||||
big plan, but order it so something useful exists early, and remind them
|
||||
the rest can always be added later.
|
||||
|
||||
## Step 4: Build, one item at a time
|
||||
|
||||
Work through the confirmed plan. Finish each item before starting the next,
|
||||
and say briefly where you are ("2 of 5"). Suggested order: agent.md
|
||||
first, then modules (they only need conversation), then connections (each
|
||||
needs the user to place secrets).
|
||||
first (written directly from the description, no questions), then modules
|
||||
(they only need conversation), then connections (each needs the user to
|
||||
place secrets).
|
||||
|
||||
**Add a connection:**
|
||||
|
||||
|
||||
+17
-4
@@ -57,7 +57,7 @@ export default function Commands() {
|
||||
<div>
|
||||
<h1 style={{ ...pageTitle, marginBottom: 5 }}>Commands</h1>
|
||||
<p style={{ margin: "0 0 20px", color: C.tMuted, fontSize: 13.5, lineHeight: 1.55, maxWidth: 640 }}>
|
||||
Files under <span style={{ fontFamily: mono }}>commands/</span> folders, served as MCP prompts. New files appear after a server restart.
|
||||
All MCP prompts: project commands from <span style={{ fontFamily: mono }}>commands/</span> folders and built-in framework prompts, grouped by owner.
|
||||
</p>
|
||||
{cmds.length === 0 ? (
|
||||
<EmptyState>
|
||||
@@ -69,9 +69,22 @@ export default function Commands() {
|
||||
) : (
|
||||
<>
|
||||
<div style={{ ...sectionLabel, marginBottom: 11 }}>Commands ({cmds.length})</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: 13 }}>
|
||||
{cmds.map((c) => <CommandCard key={c.path} cmd={c} />)}
|
||||
</div>
|
||||
{(() => {
|
||||
const groups = {};
|
||||
cmds.forEach((c) => { (groups[c.owner] = groups[c.owner] || []).push(c); });
|
||||
const owners = Object.keys(groups).sort((a, b) => a === "framework" ? 1 : b === "framework" ? -1 : a.localeCompare(b));
|
||||
return owners.map((owner) => (
|
||||
<div key={owner} style={{ marginBottom: 22 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
|
||||
<span style={{ fontFamily: mono, fontSize: 12, fontWeight: 600, color: C.ink, textTransform: "uppercase", letterSpacing: "0.04em" }}>{owner}</span>
|
||||
<span style={{ fontSize: 11, color: C.t3 }}>({groups[owner].length})</span>
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: 13 }}>
|
||||
{groups[owner].map((c) => <CommandCard key={c.path} cmd={c} />)}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
})()}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user